QuizFlash / src /lib /exportUtils.ts
Shih-hungg's picture
Fix type errors
c7d096e
import { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType } from 'docx';
import { GeneratedQuestion } from '@/types/quiz';
export async function exportQuestionsToDocx(
questions: GeneratedQuestion[],
filename: string = 'quiz-questions.docx',
includeAnswers: boolean = true
) {
// Create document
const doc = new Document({
sections: [{
properties: {},
children: [
// Title
new Paragraph({
text: 'Quiz Questions',
heading: HeadingLevel.HEADING_1,
alignment: AlignmentType.CENTER,
spacing: {
after: 400,
}
}),
// Date created
new Paragraph({
text: `Generated: ${new Date().toLocaleDateString()}`,
alignment: AlignmentType.CENTER,
spacing: {
after: 200
}
}),
// Total questions
new Paragraph({
text: `Total Questions: ${questions.length}`,
alignment: AlignmentType.CENTER,
spacing: {
after: 300
}
}),
// Questions
...questions.map((question, index) =>
renderQuestion(question, index + 1, includeAnswers)
).flat()
]
}]
});
// Generate and download the document
const blob = await Packer.toBlob(doc);
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
function renderQuestion(question: GeneratedQuestion, questionNumber: number, includeAnswers: boolean = true): Paragraph[] {
const elements: Paragraph[] = [];
// Question number and type
elements.push(
new Paragraph({
children: [
new TextRun({
text: `Question ${questionNumber}`,
bold: true,
size: 24
}),
new TextRun({
text: ` (${question.type})`,
size: 20,
color: '666666'
})
],
spacing: {
before: 300,
after: 150
}
})
);
// Question stem
elements.push(
new Paragraph({
children: [
new TextRun({
text: question.stem,
size: 22
})
],
spacing: {
after: 200
}
})
);
// Points
elements.push(
new Paragraph({
children: [
new TextRun({
text: `Points: ${question.points}`,
size: 18,
italics: true,
color: '666666'
})
],
spacing: {
after: 150
}
})
);
// Question content
elements.push(...renderQuestionContent(question, includeAnswers));
// Separator
elements.push(
new Paragraph({
children: [
new TextRun({
text: '─'.repeat(50),
color: 'CCCCCC'
})
],
spacing: {
before: 200,
after: 200
}
})
);
return elements;
}
function renderQuestionContent(question: GeneratedQuestion, includeAnswers: boolean = true): Paragraph[] {
const elements: Paragraph[] = [];
// Handle the actual content structure for multiple choice questions
if (question.content && typeof question.content === 'object') {
// Handle multiple choice questions with the actual structure
if ('Options' in question.content && question.content.Options) {
// Options is an object with keys A, B, C, D
const options = question.content.Options as { A: string; B: string; C: string; D: string; };
const correctAnswer = (question.content as { Answer?: string }).Answer;
Object.entries(options).forEach(([key, value]) => {
const isCorrect = key === correctAnswer;
elements.push(
new Paragraph({
children: [
new TextRun({
text: `${key}. ${value}`,
size: 20,
color: includeAnswers && isCorrect ? '008000' : '000000',
bold: includeAnswers && isCorrect
})
],
spacing: {
after: 100
}
})
);
});
// Add answer key if including answers
if (includeAnswers && correctAnswer) {
elements.push(
new Paragraph({
children: [
new TextRun({
text: `Answer: ${correctAnswer}`,
bold: true,
size: 20,
color: '008000'
})
],
spacing: {
before: 200,
after: 200
}
})
);
}
}
}
return elements;
}
export async function exportQuestionsToJson(questions: GeneratedQuestion[], filename: string = 'quiz-questions.json') {
const data = {
metadata: {
exportDate: new Date().toISOString(),
totalQuestions: questions.length,
totalPoints: questions.reduce((sum, q) => sum + q.points, 0)
},
questions: questions.map(q => ({
id: q.id,
type: q.type,
stem: q.stem,
content: q.content,
points: q.points,
createdAt: q.createdAt
}))
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
export async function exportQuestionsToMarkdown(
questions: GeneratedQuestion[],
filename: string = 'quiz-questions.md',
includeAnswers: boolean = true
) {
let markdown = '# Quiz Questions\n\n';
markdown += `Generated: ${new Date().toLocaleDateString()}\n\n`;
markdown += `Total Questions: ${questions.length}\n\n`;
markdown += '---\n\n';
questions.forEach((question, index) => {
markdown += `## Question ${index + 1} (${question.type})\n\n`;
markdown += `**Points:** ${question.points}\n\n`;
markdown += `${question.stem}\n\n`;
if (question.content && typeof question.content === 'object') {
if ('Options' in question.content && question.content.Options) {
const options = question.content.Options as { A: string; B: string; C: string; D: string; };
const correctAnswer = (question.content as { Answer?: string }).Answer;
Object.entries(options).forEach(([key, value]) => {
const isCorrect = includeAnswers && key === correctAnswer;
markdown += `${key}. ${value}${isCorrect ? ' ✓' : ''}\n`;
});
if (includeAnswers && correctAnswer) {
markdown += `\n**Answer:** ${correctAnswer}\n`;
}
}
}
markdown += '\n---\n\n';
});
const blob = new Blob([markdown], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
export async function exportQuestionsToCsv(questions: GeneratedQuestion[], filename: string = 'quiz-questions.csv') {
// CSV header
let csv = 'Question Number,Type,Points,Question,Option A,Option B,Option C,Option D,Correct Answer\n';
questions.forEach((question, index) => {
const row = [];
row.push(index + 1);
row.push(question.type);
row.push(question.points);
row.push(`"${question.stem.replace(/"/g, '""')}"`);
if (question.content && typeof question.content === 'object' && 'Options' in question.content) {
const options = question.content.Options as { A: string; B: string; C: string; D: string; };
const correctAnswer = (question.content as { Answer?: string }).Answer;
row.push(`"${options.A.replace(/"/g, '""')}"`);
row.push(`"${options.B.replace(/"/g, '""')}"`);
row.push(`"${options.C.replace(/"/g, '""')}"`);
row.push(`"${options.D.replace(/"/g, '""')}"`);
row.push(correctAnswer || '');
} else {
// Add empty cells for non-multiple choice questions
row.push('', '', '', '', '');
}
csv += row.join(',') + '\n';
});
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}