lms/resources/js/components/certificate-generator.tsx
Ahmed Darrazi 60cc9db469
All checks were successful
Build & Push Docker Image / docker (push) Successful in 1m50s
some bugfixes
2025-12-18 22:57:40 +01:00

338 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import jsPDF from 'jspdf';
import { Award, Calendar, Download, FileImage, FileText } from 'lucide-react';
import { useRef, useState } from 'react';
import { toast } from 'sonner';
const CertificateGenerator = () => {
const [studentName, setStudentName] = useState('');
const [courseName, setCourseName] = useState('');
const [completionDate, setCompletionDate] = useState('');
const [isGenerating, setIsGenerating] = useState(false);
const [certificateSize, setCertificateSize] = useState('standard');
const [downloadFormat, setDownloadFormat] = useState('png');
const certificateRef = useRef<HTMLDivElement>(null);
const handleGenerateCertificate = async () => {
if (!studentName || !courseName || !completionDate) {
toast.error('Bitte fülle alle Pflichtfelder aus.');
return;
}
setIsGenerating(true);
// Simulate certificate generation
setTimeout(() => {
setIsGenerating(false);
toast.success('Dein Kursabschlusszertifikat wurde erfolgreich erstellt.');
}, 2000);
};
const getSizeDimensions = () => {
return certificateSize === 'a4'
? { width: 842, height: 595 } // A4 landscape
: { width: 800, height: 600 }; // Standard
};
const handleDownloadCertificate = () => {
if (!certificateRef.current) return;
if (downloadFormat === 'pdf') {
downloadAsPDF();
} else {
downloadAsPNG();
}
};
const downloadAsPNG = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) return;
const dimensions = getSizeDimensions();
canvas.width = dimensions.width;
canvas.height = dimensions.height;
drawCertificate(ctx, dimensions);
canvas.toBlob((blob) => {
if (!blob) return;
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${studentName}_${courseName}_Certificate.png`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
toast.success('Dein PNG-Zertifikat wurde in deinem Download-Ordner gespeichert.');
}, 'image/png');
};
const downloadAsPDF = () => {
const dimensions = getSizeDimensions();
const isLandscape = dimensions.width > dimensions.height;
// Create PDF with proper dimensions
const pdf = new jsPDF({
orientation: isLandscape ? 'landscape' : 'portrait',
unit: 'px',
format: [dimensions.width, dimensions.height],
});
// Create canvas for drawing
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) return;
canvas.width = dimensions.width;
canvas.height = dimensions.height;
// Draw certificate on canvas
drawCertificate(ctx, dimensions);
// Convert canvas to image and add to PDF
const imgData = canvas.toDataURL('image/png');
pdf.addImage(imgData, 'PNG', 0, 0, dimensions.width, dimensions.height);
// Save the PDF
pdf.save(`${studentName}_${courseName}_Certificate.pdf`);
toast.success('Dein PDF-Zertifikat wurde in deinem Download-Ordner gespeichert.');
};
const drawCertificate = (ctx: CanvasRenderingContext2D, dimensions: { width: number; height: number }) => {
// Create gradient background
const gradient = ctx.createLinearGradient(0, 0, dimensions.width, dimensions.height);
gradient.addColorStop(0, '#dbeafe');
gradient.addColorStop(1, '#e0e7ff');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, dimensions.width, dimensions.height);
// Add decorative border
ctx.strokeStyle = '#f59e0b';
ctx.lineWidth = 8;
ctx.strokeRect(20, 20, dimensions.width - 40, dimensions.height - 40);
// Inner border
ctx.strokeStyle = '#3730a3';
ctx.lineWidth = 2;
ctx.strokeRect(40, 40, dimensions.width - 80, dimensions.height - 80);
// Set text styles
ctx.fillStyle = '#1f2937';
ctx.textAlign = 'center';
// Title
ctx.font = 'bold 42px serif';
ctx.fillText('Abschlusszertifikat', dimensions.width / 2, 120);
// Decorative line under title
ctx.strokeStyle = '#f59e0b';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(dimensions.width / 2 - 150, 140);
ctx.lineTo(dimensions.width / 2 + 150, 140);
ctx.stroke();
// "This is to certify that"
ctx.font = '22px serif';
ctx.fillStyle = '#4b5563';
ctx.fillText('Hiermit wird bescheinigt, dass', dimensions.width / 2, 190);
// Student name with underline
ctx.font = 'bold 36px serif';
ctx.fillStyle = '#3730a3';
ctx.fillText(studentName, dimensions.width / 2, 250);
// Underline for student name
const nameWidth = ctx.measureText(studentName).width;
ctx.strokeStyle = '#f59e0b';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo((dimensions.width - nameWidth) / 2 - 20, 270);
ctx.lineTo((dimensions.width + nameWidth) / 2 + 20, 270);
ctx.stroke();
// "has successfully completed the course"
ctx.font = '22px serif';
ctx.fillStyle = '#4b5563';
ctx.fillText('den Kurs erfolgreich abgeschlossen hat', dimensions.width / 2, 320);
// Course name
ctx.font = 'bold 28px serif';
ctx.fillStyle = '#3730a3';
ctx.fillText(courseName, dimensions.width / 2, 370);
// Completion date
ctx.font = '18px serif';
ctx.fillStyle = '#6b7280';
ctx.fillText(`Abgeschlossen am: ${completionDate}`, dimensions.width / 2, 430);
// Footer
ctx.font = '16px serif';
ctx.fillStyle = '#9ca3af';
ctx.fillText('Authorized Certificate of Achievement', dimensions.width / 2, dimensions.height - 60);
};
const currentDate = new Date().toISOString().split('T')[0];
return (
<div className="space-y-8">
<div className="text-center">
<h1 className="mb-4 text-4xl font-bold text-gray-800">Zertifikatsgenerator</h1>
<p className="text-muted-foreground text-lg">Erstelle dein offizielles Kursabschlusszertifikat</p>
</div>
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
{/* Form Section */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Award className="h-5 w-5 text-amber-600" />
Zertifikatsdetails
</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="studentName">Name des Teilnehmers *</Label>
<Input
id="studentName"
value={studentName}
onChange={(e) => setStudentName(e.target.value)}
placeholder="Gib deinen vollständigen Namen ein"
/>
</div>
<div className="space-y-2">
<Label htmlFor="courseName">Kursname *</Label>
<Input id="courseName" value={courseName} onChange={(e) => setCourseName(e.target.value)} placeholder="Gib den Kursnamen ein" />
</div>
<div className="space-y-2">
<Label htmlFor="completionDate">Completion Date *</Label>
<Input
id="completionDate"
type="date"
value={completionDate}
onChange={(e) => setCompletionDate(e.target.value)}
max={currentDate}
/>
</div>
<div className="space-y-4">
<div className="space-y-2">
<Label>Zertifikatsgröße</Label>
<Select value={certificateSize} onValueChange={setCertificateSize}>
<SelectTrigger>
<SelectValue placeholder="Wähle Zertifikatsgröße" />
</SelectTrigger>
<SelectContent>
<SelectItem value="standard">Standard (800×600)</SelectItem>
<SelectItem value="a4">A4 Querformat (842×595)</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<Label>Download-Format</Label>
<RadioGroup value={downloadFormat} onValueChange={setDownloadFormat} className="flex space-x-6">
<div className="flex items-center space-x-2">
<RadioGroupItem className="cursor-pointer" value="png" id="png" />
<Label htmlFor="png" className="flex cursor-pointer items-center gap-2">
<FileImage className="h-4 w-4" />
PNG-Bild
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem className="cursor-pointer" value="pdf" id="pdf" />
<Label htmlFor="pdf" className="flex cursor-pointer items-center gap-2">
<FileText className="h-4 w-4" />
PDF-Dokument
</Label>
</div>
</RadioGroup>
</div>
</div>
<Button onClick={handleGenerateCertificate} disabled={isGenerating} className="w-full" size="lg">
{isGenerating ? (
<>
<div className="mr-2 h-4 w-4 animate-spin rounded-full border-b-2 border-white" />
Zertifikat wird erstellt...
</>
) : (
<>
<Award className="mr-2 h-4 w-4" />
Zertifikat erstellen
</>
)}
</Button>
</CardContent>
</Card>
{/* Preview Section */}
<Card>
<CardHeader>
<CardTitle>Zertifikatsvorschau</CardTitle>
</CardHeader>
<CardContent>
<div
ref={certificateRef}
className={`relative flex flex-col justify-center rounded-lg border-4 border-amber-400 bg-gradient-to-br from-blue-50 to-indigo-100 p-6 text-center`}
>
{/* Inner decorative border */}
<div className="absolute inset-4 rounded border-2 border-indigo-700"></div>
<div className="relative z-10">
<div className="mb-6">
<Award className="mx-auto mb-3 h-12 w-12 text-amber-600" />
<h2 className="mb-2 font-serif text-2xl font-bold text-gray-800">Abschlusszertifikat</h2>
<div className="mx-auto h-0.5 w-32 bg-amber-400"></div>
</div>
<div className="space-y-4 text-gray-700">
<p className="font-serif text-lg">Hiermit wird bescheinigt, dass</p>
<div className="relative">
<p className="mx-8 pb-2 font-serif text-2xl font-bold text-indigo-800">{studentName || 'Student Name'}</p>
<div className="absolute bottom-0 left-1/2 h-0.5 w-48 -translate-x-1/2 transform bg-amber-400"></div>
</div>
<p className="font-serif text-lg">den Kurs erfolgreich abgeschlossen hat</p>
<p className="font-serif text-xl font-semibold text-indigo-700">{courseName || 'Kursname'}</p>
<div className="mt-6 flex items-center justify-center gap-2">
<Calendar className="text-muted-foreground h-4 w-4" />
<p className="text-muted-foreground font-serif text-sm">Abgeschlossen am: {completionDate || 'Datum'}</p>
</div>
</div>
<div className="mt-6 border-t border-amber-400 pt-4">
<p className="font-serif text-sm text-gray-500">Autorisierte Leistungsurkunde</p>
</div>
</div>
</div>
{studentName && courseName && completionDate && (
<Button variant="outline" className="mt-4 w-full" onClick={handleDownloadCertificate}>
<Download className="mr-2 h-4 w-4" />
Herunterladen als {downloadFormat.toUpperCase()}
</Button>
)}
</CardContent>
</Card>
</div>
</div>
);
};
export default CertificateGenerator;