import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import jsPDF from 'jspdf'; import { Calendar, ClipboardList, Download, FileImage, FileText } from 'lucide-react'; import { useRef, useState } from 'react'; import { toast } from 'sonner'; interface DynamicMarksheetProps { template: MarksheetTemplate; courseName: string; studentName: string; completionDate: string; studentMarks: StudentMarks; } const DynamicMarksheet = ({ template, courseName, studentName, completionDate, studentMarks }: DynamicMarksheetProps) => { const [downloadFormat, setDownloadFormat] = useState('png'); const marksheetRef = useRef(null); const dimensions = { width: 700, height: 900 }; const { template_data } = template; const handleDownloadMarksheet = async () => { if (!marksheetRef.current) return; if (downloadFormat === 'pdf') { await downloadAsPDF(); } else { await downloadAsPNG(); } }; const loadImage = (src: string): Promise => { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => resolve(img); img.onerror = reject; img.src = src; }); }; const downloadAsPNG = async () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) return; canvas.width = dimensions.width; canvas.height = dimensions.height; // Load logo first if it exists let logoImage: HTMLImageElement | null = null; if (template.logo_path) { try { logoImage = await loadImage(template.logo_path); } catch (error) { console.error('Failed to load logo:', error); } } await drawMarksheet(ctx, dimensions, logoImage); canvas.toBlob((blob) => { if (!blob) return; const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${studentName}_${courseName}_Marksheet.png`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toast.success('Marksheet saved as PNG!'); }, 'image/png'); }; const downloadAsPDF = async () => { const isLandscape = dimensions.width > dimensions.height; const pdf = new jsPDF({ orientation: isLandscape ? 'landscape' : 'portrait', unit: 'px', format: [dimensions.width, dimensions.height], }); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) return; canvas.width = dimensions.width; canvas.height = dimensions.height; // Load logo first if it exists let logoImage: HTMLImageElement | null = null; if (template.logo_path) { try { logoImage = await loadImage(template.logo_path); } catch (error) { console.error('Failed to load logo:', error); } } await drawMarksheet(ctx, dimensions, logoImage); const imgData = canvas.toDataURL('image/png'); pdf.addImage(imgData, 'PNG', 0, 0, dimensions.width, dimensions.height); pdf.save(`${studentName}_${courseName}_Marksheet.pdf`); toast.success('Marksheet saved as PDF!'); }; const drawMarksheet = async ( ctx: CanvasRenderingContext2D, dimensions: { width: number; height: number }, logoImage: HTMLImageElement | null = null, ) => { // Background ctx.fillStyle = template_data.backgroundColor; ctx.fillRect(0, 0, dimensions.width, dimensions.height); // Outer border ctx.strokeStyle = template_data.borderColor; ctx.lineWidth = 6; ctx.strokeRect(15, 15, dimensions.width - 30, dimensions.height - 30); const leftMargin = 80; const rightMargin = dimensions.width - 80; const middleX = dimensions.width / 2; let currentY = 60; // Header Section - Horizontal Layout (Logo on left, text on right) const logoSize = 60; const logoX = leftMargin; const textStartX = leftMargin + logoSize + 20; if (logoImage) { ctx.drawImage(logoImage, logoX, currentY, logoSize, logoSize); } // Header text next to logo ctx.textAlign = 'left'; ctx.font = `bold 28px ${template_data.fontFamily}`; ctx.fillStyle = template_data.primaryColor; ctx.fillText(template_data.headerText, textStartX, currentY + 25); // Institution Name ctx.font = `18px ${template_data.fontFamily}`; ctx.fillStyle = template_data.secondaryColor; ctx.fillText(template_data.institutionName, textStartX, currentY + 50); currentY += logoSize + 30; // Border below header ctx.strokeStyle = template_data.borderColor; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(leftMargin, currentY); ctx.lineTo(rightMargin, currentY); ctx.stroke(); currentY += 40; // Student Info Section - 2 Column Grid Layout const col1X = leftMargin; const col2X = middleX + 20; const labelOffset = 25; const valueOffset = 50; // Column 1 - Student Name ctx.font = `16px ${template_data.fontFamily}`; ctx.fillStyle = template_data.secondaryColor; ctx.fillText('Student Name', col1X, currentY); ctx.font = `bold 20px ${template_data.fontFamily}`; ctx.fillStyle = template_data.primaryColor; ctx.fillText(studentName, col1X, currentY + labelOffset); // Column 2 - Course ctx.font = `16px ${template_data.fontFamily}`; ctx.fillStyle = template_data.secondaryColor; ctx.fillText('Course', col2X, currentY); ctx.font = `bold 20px ${template_data.fontFamily}`; ctx.fillStyle = template_data.primaryColor; // Wrap course name if too long const maxCourseWidth = rightMargin - col2X - 10; const courseMetrics = ctx.measureText(courseName); if (courseMetrics.width > maxCourseWidth) { const words = courseName.split(' '); let line = ''; let lineY = currentY + labelOffset; for (let n = 0; n < words.length; n++) { const testLine = line + words[n] + ' '; const metrics = ctx.measureText(testLine); if (metrics.width > maxCourseWidth && n > 0) { ctx.fillText(line, col2X, lineY); line = words[n] + ' '; lineY += 25; } else { line = testLine; } } ctx.fillText(line, col2X, lineY); } else { ctx.fillText(courseName, col2X, currentY + labelOffset); } currentY += 92; // Column 1 - Completion Date ctx.font = `16px ${template_data.fontFamily}`; ctx.fillStyle = template_data.secondaryColor; ctx.fillText('Completion Date', col1X, currentY); ctx.font = `18px ${template_data.fontFamily}`; ctx.fillStyle = template_data.primaryColor; ctx.fillText(completionDate, col1X, currentY + labelOffset); // Column 2 - Overall Grade ctx.font = `16px ${template_data.fontFamily}`; ctx.fillStyle = template_data.secondaryColor; ctx.fillText('Overall Grade', col2X, currentY); ctx.font = `bold 20px ${template_data.fontFamily}`; ctx.fillStyle = template_data.primaryColor; ctx.fillText(`${studentMarks.overall.grade} (${studentMarks.overall.percentage}%)`, col2X, currentY + labelOffset); currentY += 80; // Exam Type Section ctx.textAlign = 'left'; ctx.font = `bold 22px ${template_data.fontFamily}`; ctx.fillStyle = template_data.primaryColor; ctx.fillText('Exam Type', leftMargin, currentY); currentY += 35; // Table Header Background const tableWidth = rightMargin - leftMargin; ctx.fillStyle = `${template_data.primaryColor}30`; ctx.fillRect(leftMargin, currentY, tableWidth, 45); // Table Headers ctx.font = `bold 18px ${template_data.fontFamily}`; ctx.fillStyle = template_data.primaryColor; ctx.fillText('Exam Type', leftMargin + 15, currentY + 28); ctx.textAlign = 'right'; ctx.fillText('Total Marks', rightMargin - 15, currentY + 28); // Table Border ctx.strokeStyle = template_data.borderColor; ctx.lineWidth = 2; ctx.strokeRect(leftMargin, currentY, tableWidth, 45); currentY += 45; // Assignment Row ctx.fillStyle = template_data.backgroundColor; ctx.fillRect(leftMargin, currentY, tableWidth, 45); ctx.textAlign = 'left'; ctx.font = `18px ${template_data.fontFamily}`; ctx.fillStyle = template_data.secondaryColor; ctx.fillText('Assignment', leftMargin + 15, currentY + 28); ctx.textAlign = 'right'; ctx.font = `bold 18px ${template_data.fontFamily}`; ctx.fillStyle = template_data.primaryColor; ctx.fillText(`${studentMarks.assignment.obtained}/${studentMarks.assignment.total}`, rightMargin - 15, currentY + 28); // Border ctx.strokeStyle = template_data.borderColor; ctx.lineWidth = 1; ctx.strokeRect(leftMargin, currentY, tableWidth, 45); currentY += 45; // Quiz Row ctx.fillStyle = template_data.backgroundColor; ctx.fillRect(leftMargin, currentY, tableWidth, 45); ctx.textAlign = 'left'; ctx.font = `18px ${template_data.fontFamily}`; ctx.fillStyle = template_data.secondaryColor; ctx.fillText('Quiz', leftMargin + 15, currentY + 28); ctx.textAlign = 'right'; ctx.font = `bold 18px ${template_data.fontFamily}`; ctx.fillStyle = template_data.primaryColor; ctx.fillText(`${studentMarks.quiz.obtained}/${studentMarks.quiz.total}`, rightMargin - 15, currentY + 28); // Border ctx.strokeStyle = template_data.borderColor; ctx.lineWidth = 1; ctx.strokeRect(leftMargin, currentY, tableWidth, 45); currentY += 70; // Footer ctx.strokeStyle = template_data.borderColor; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(leftMargin, currentY); ctx.lineTo(rightMargin, currentY); ctx.stroke(); currentY += 30; ctx.textAlign = 'center'; ctx.font = `16px ${template_data.fontFamily}`; ctx.fillStyle = template_data.secondaryColor; ctx.fillText(template_data.footerText, dimensions.width / 2, currentY); }; return (
{/* Header Section */}
{template.logo_path ? (
Logo
) : ( )}

{template_data.headerText}

{template_data.institutionName}

{/* Student Info */}

Student Name

{studentName}

Course

{courseName}

Completion Date

{completionDate}

Overall Grade

{studentMarks.overall.grade} ({studentMarks.overall.percentage}%)

{/* Exam Type Section */}

Exam Type

Exam Type Total Marks
Assignment {studentMarks.assignment.obtained}/{studentMarks.assignment.total}
Quiz {studentMarks.quiz.obtained}/{studentMarks.quiz.total}
{/* Footer */}

{template_data.footerText}

); }; export default DynamicMarksheet;