Files
Krow-workspace/frontend-web/src/pages/Certification.jsx
2025-12-26 15:14:51 -05:00

768 lines
32 KiB
JavaScript

import React, { useState, useMemo } from "react";
import { base44 } from "@/api/base44Client";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Progress } from "@/components/ui/progress";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import {
Award, Search, Plus, AlertTriangle, CheckCircle2, Clock, XCircle,
Download, Send, Eye, Edit2, ShieldCheck, FileText, Sparkles,
Calendar, User, Building2, ChevronRight, Filter, Bell, TrendingUp
} from "lucide-react";
import { format, differenceInDays, parseISO } from "date-fns";
import { useToast } from "@/components/ui/use-toast";
import { motion, AnimatePresence } from "framer-motion";
const REQUIRED_CERTIFICATIONS = ["Background Check", "RBS", "Food Handler"];
const CERT_CONFIG = {
"Background Check": {
color: "from-purple-500 to-purple-600",
bgColor: "bg-purple-50",
textColor: "text-purple-700",
borderColor: "border-purple-200",
icon: ShieldCheck,
description: "Criminal background verification"
},
"RBS": {
color: "from-blue-500 to-blue-600",
bgColor: "bg-blue-50",
textColor: "text-blue-700",
borderColor: "border-blue-200",
icon: Award,
description: "Responsible Beverage Server"
},
"Food Handler": {
color: "from-emerald-500 to-emerald-600",
bgColor: "bg-emerald-50",
textColor: "text-emerald-700",
borderColor: "border-emerald-200",
icon: FileText,
description: "Food safety certification"
},
};
export default function Certification() {
const { toast } = useToast();
const queryClient = useQueryClient();
const [searchTerm, setSearchTerm] = useState("");
const [activeTab, setActiveTab] = useState("all");
const [certTypeFilter, setCertTypeFilter] = useState("all");
const [showAddModal, setShowAddModal] = useState(false);
const [editingCert, setEditingCert] = useState(null);
const [showReportModal, setShowReportModal] = useState(false);
const [selectedEmployee, setSelectedEmployee] = useState(null);
const { data: user } = useQuery({
queryKey: ['current-user-cert'],
queryFn: () => base44.auth.me(),
});
const { data: certifications = [] } = useQuery({
queryKey: ['certifications'],
queryFn: () => base44.entities.Certification.list(),
initialData: [],
});
const { data: staff = [] } = useQuery({
queryKey: ['staff-for-cert'],
queryFn: () => base44.entities.Staff.list(),
initialData: [],
});
const userRole = user?.user_role || user?.role || "admin";
const isVendor = userRole === "vendor";
const isProcurement = userRole === "procurement";
const calculateStatus = (expiryDate) => {
if (!expiryDate) return "pending";
const days = differenceInDays(parseISO(expiryDate), new Date());
if (days < 0) return "expired";
if (days <= 30) return "expiring_soon";
return "current";
};
const processedCerts = useMemo(() => {
return certifications.map(cert => ({
...cert,
days_until_expiry: cert.expiry_date ? differenceInDays(parseISO(cert.expiry_date), new Date()) : null,
status: calculateStatus(cert.expiry_date),
}));
}, [certifications]);
const employeeCertMap = useMemo(() => {
const map = {};
staff.forEach(s => {
map[s.id] = {
employee_id: s.id,
employee_name: s.employee_name,
vendor_id: s.vendor_id,
vendor_name: s.vendor_name,
position: s.position,
certifications: { "Background Check": null, "RBS": null, "Food Handler": null },
allCurrent: false,
hasExpired: false,
hasExpiringSoon: false,
missingCount: 3,
canWork: false,
complianceScore: 0,
};
});
processedCerts.forEach(cert => {
const key = cert.employee_id;
if (!map[key]) {
map[key] = {
employee_id: cert.employee_id,
employee_name: cert.employee_name,
vendor_id: cert.vendor_id,
vendor_name: cert.vendor_name,
position: "",
certifications: { "Background Check": null, "RBS": null, "Food Handler": null },
allCurrent: false,
hasExpired: false,
hasExpiringSoon: false,
missingCount: 3,
canWork: false,
complianceScore: 0,
};
}
if (REQUIRED_CERTIFICATIONS.includes(cert.certification_type)) {
map[key].certifications[cert.certification_type] = cert;
}
});
Object.values(map).forEach(emp => {
const certs = Object.values(emp.certifications);
const validCerts = certs.filter(c => c && c.status === "current");
const expiredCerts = certs.filter(c => c && c.status === "expired");
const expiringSoonCerts = certs.filter(c => c && c.status === "expiring_soon");
const missingCerts = certs.filter(c => !c);
emp.allCurrent = validCerts.length === 3;
emp.hasExpired = expiredCerts.length > 0;
emp.hasExpiringSoon = expiringSoonCerts.length > 0;
emp.missingCount = missingCerts.length;
emp.canWork = validCerts.length === 3 || (validCerts.length + expiringSoonCerts.length === 3);
emp.complianceScore = Math.round(((validCerts.length + expiringSoonCerts.length * 0.5) / 3) * 100);
});
return map;
}, [processedCerts, staff]);
const employeeList = Object.values(employeeCertMap);
const filteredEmployees = useMemo(() => {
let filtered = employeeList;
if (isVendor && user?.vendor_id) {
filtered = filtered.filter(e => e.vendor_id === user.vendor_id);
}
if (searchTerm) {
filtered = filtered.filter(e =>
e.employee_name?.toLowerCase().includes(searchTerm.toLowerCase())
);
}
if (activeTab === "compliant") filtered = filtered.filter(e => e.allCurrent);
else if (activeTab === "expiring") filtered = filtered.filter(e => e.hasExpiringSoon);
else if (activeTab === "expired") filtered = filtered.filter(e => e.hasExpired);
else if (activeTab === "incomplete") filtered = filtered.filter(e => e.missingCount > 0);
if (certTypeFilter !== "all") {
filtered = filtered.filter(e => {
const cert = e.certifications[certTypeFilter];
return cert !== null;
});
}
return filtered;
}, [employeeList, searchTerm, activeTab, certTypeFilter, isVendor, user]);
const stats = useMemo(() => {
const total = employeeList.length;
const compliant = employeeList.filter(e => e.allCurrent).length;
const expiring = employeeList.filter(e => e.hasExpiringSoon).length;
const expired = employeeList.filter(e => e.hasExpired).length;
const incomplete = employeeList.filter(e => e.missingCount > 0).length;
const avgCompliance = total > 0 ? Math.round(employeeList.reduce((sum, e) => sum + e.complianceScore, 0) / total) : 0;
return { total, compliant, expiring, expired, incomplete, avgCompliance };
}, [employeeList]);
const saveCertMutation = useMutation({
mutationFn: async (data) => {
if (data.id) return base44.entities.Certification.update(data.id, data);
return base44.entities.Certification.create(data);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['certifications'] });
setShowAddModal(false);
setEditingCert(null);
toast({ title: "✅ Certification saved" });
},
});
const sendExpiryAlert = async (cert) => {
try {
await base44.integrations.Core.SendEmail({
to: user?.email || "admin@company.com",
subject: `⚠️ Certification Expiring: ${cert.employee_name} - ${cert.certification_type}`,
body: `<h2>Certification Expiring Alert</h2>
<p><strong>Employee:</strong> ${cert.employee_name}</p>
<p><strong>Certification:</strong> ${cert.certification_type}</p>
<p><strong>Expiry Date:</strong> ${format(parseISO(cert.expiry_date), 'MMM d, yyyy')}</p>
<p><strong>Days Until Expiry:</strong> ${cert.days_until_expiry} days</p>`
});
toast({ title: "✅ Alert sent" });
} catch (error) {
toast({ title: "Failed to send alert", variant: "destructive" });
}
};
const sendComplianceReport = async (clientEmail) => {
const compliantEmployees = employeeList.filter(e => e.allCurrent);
try {
await base44.integrations.Core.SendEmail({
to: clientEmail,
subject: "Staff Compliance Report",
body: `<h2>Staff Compliance Report</h2>
<p>Generated: ${format(new Date(), 'MMM d, yyyy')}</p>
<p><strong>Total Staff:</strong> ${stats.total}</p>
<p><strong>Fully Compliant:</strong> ${stats.compliant}</p>
<p><strong>Average Compliance:</strong> ${stats.avgCompliance}%</p>
<hr/><h3>Compliant Staff</h3>
<ul>${compliantEmployees.map(e => `<li>${e.employee_name}</li>`).join('')}</ul>`
});
toast({ title: "✅ Report sent" });
setShowReportModal(false);
} catch (error) {
toast({ title: "Failed to send report", variant: "destructive" });
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50/30 to-slate-50">
<div className="p-4 md:p-6 max-w-[1800px] mx-auto">
{/* Hero Header */}
<div className="relative overflow-hidden bg-gradient-to-r from-[#0A39DF] via-blue-600 to-[#1C323E] rounded-2xl p-6 md:p-8 mb-6 text-white">
<div className="absolute inset-0 opacity-50" style={{ backgroundImage: "url(\"data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")" }} />
<div className="relative z-10">
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<div className="flex items-center gap-3 mb-2">
<div className="w-12 h-12 bg-white/20 backdrop-blur rounded-xl flex items-center justify-center">
<Award className="w-6 h-6" />
</div>
<div>
<h1 className="text-2xl md:text-3xl font-bold">Certification Hub</h1>
<p className="text-blue-100 text-sm">Track & manage workforce compliance</p>
</div>
</div>
</div>
<div className="flex gap-2">
<Button variant="secondary" className="bg-white/20 hover:bg-white/30 text-white border-0" onClick={() => setShowReportModal(true)}>
<Send className="w-4 h-4 mr-2" />Send Report
</Button>
{(isVendor || userRole === "admin") && (
<Button className="bg-white text-blue-600 hover:bg-blue-50" onClick={() => setShowAddModal(true)}>
<Plus className="w-4 h-4 mr-2" />Add Certification
</Button>
)}
</div>
</div>
{/* Stats Row */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 mt-6">
<div className="bg-white/10 backdrop-blur rounded-xl p-4">
<div className="flex items-center gap-2 mb-1">
<User className="w-4 h-4 text-blue-200" />
<span className="text-xs text-blue-200">Total Staff</span>
</div>
<p className="text-2xl font-bold">{stats.total}</p>
</div>
<div className="bg-emerald-500/30 backdrop-blur rounded-xl p-4">
<div className="flex items-center gap-2 mb-1">
<CheckCircle2 className="w-4 h-4 text-emerald-200" />
<span className="text-xs text-emerald-200">Compliant</span>
</div>
<p className="text-2xl font-bold">{stats.compliant}</p>
</div>
<div className="bg-amber-500/30 backdrop-blur rounded-xl p-4">
<div className="flex items-center gap-2 mb-1">
<Clock className="w-4 h-4 text-amber-200" />
<span className="text-xs text-amber-200">Expiring 30d</span>
</div>
<p className="text-2xl font-bold">{stats.expiring}</p>
</div>
<div className="bg-red-500/30 backdrop-blur rounded-xl p-4">
<div className="flex items-center gap-2 mb-1">
<XCircle className="w-4 h-4 text-red-200" />
<span className="text-xs text-red-200">Expired</span>
</div>
<p className="text-2xl font-bold">{stats.expired}</p>
</div>
<div className="bg-white/10 backdrop-blur rounded-xl p-4">
<div className="flex items-center gap-2 mb-1">
<TrendingUp className="w-4 h-4 text-blue-200" />
<span className="text-xs text-blue-200">Avg. Compliance</span>
</div>
<p className="text-2xl font-bold">{stats.avgCompliance}%</p>
</div>
</div>
</div>
</div>
{/* Filters Bar */}
<Card className="border-0 shadow-lg mb-6 overflow-hidden">
<CardContent className="p-4">
<div className="flex flex-col md:flex-row md:items-center gap-4">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<Input
placeholder="Search employees..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 h-11 bg-slate-50 border-slate-200 rounded-xl"
/>
</div>
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-shrink-0">
<TabsList className="bg-slate-100 p-1 rounded-xl h-11">
<TabsTrigger value="all" className="rounded-lg data-[state=active]:bg-white data-[state=active]:shadow">
All
</TabsTrigger>
<TabsTrigger value="compliant" className="rounded-lg data-[state=active]:bg-emerald-500 data-[state=active]:text-white">
Compliant
</TabsTrigger>
<TabsTrigger value="expiring" className="rounded-lg data-[state=active]:bg-amber-500 data-[state=active]:text-white">
30 Days
</TabsTrigger>
<TabsTrigger value="expired" className="rounded-lg data-[state=active]:bg-red-500 data-[state=active]:text-white">
Expired
</TabsTrigger>
<TabsTrigger value="incomplete" className="rounded-lg data-[state=active]:bg-slate-600 data-[state=active]:text-white">
Missing
</TabsTrigger>
</TabsList>
</Tabs>
<Select value={certTypeFilter} onValueChange={setCertTypeFilter}>
<SelectTrigger className="w-[180px] h-11 rounded-xl">
<Filter className="w-4 h-4 mr-2 text-slate-400" />
<SelectValue placeholder="Filter by type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Types</SelectItem>
<SelectItem value="Background Check">Background Check</SelectItem>
<SelectItem value="RBS">RBS</SelectItem>
<SelectItem value="Food Handler">Food Handler</SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
{/* Employee Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
<AnimatePresence>
{filteredEmployees.length === 0 ? (
<div className="col-span-full">
<Card className="border-0 shadow-lg">
<CardContent className="p-12 text-center">
<Award className="w-16 h-16 mx-auto mb-4 text-slate-200" />
<h3 className="text-lg font-semibold text-slate-700 mb-2">No employees found</h3>
<p className="text-slate-500">Try adjusting your search or filters</p>
</CardContent>
</Card>
</div>
) : (
filteredEmployees.map((emp, idx) => (
<motion.div
key={emp.employee_id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ delay: idx * 0.05 }}
>
<EmployeeCertCard
employee={emp}
onAddCert={(type) => {
setEditingCert({
employee_id: emp.employee_id,
employee_name: emp.employee_name,
vendor_id: emp.vendor_id,
vendor_name: emp.vendor_name,
certification_type: type,
});
setShowAddModal(true);
}}
onEditCert={(cert) => {
setEditingCert(cert);
setShowAddModal(true);
}}
onSendAlert={sendExpiryAlert}
showVendor={isProcurement || userRole === "admin"}
/>
</motion.div>
))
)}
</AnimatePresence>
</div>
{/* Add/Edit Modal */}
<Dialog open={showAddModal} onOpenChange={setShowAddModal}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Award className="w-5 h-5 text-blue-600" />
{editingCert?.id ? 'Update' : 'Add'} Certification
</DialogTitle>
{editingCert?.employee_name && (
<DialogDescription>For: {editingCert.employee_name}</DialogDescription>
)}
</DialogHeader>
<CertificationForm
certification={editingCert}
staff={staff}
onSave={(data) => saveCertMutation.mutate(data)}
onCancel={() => { setShowAddModal(false); setEditingCert(null); }}
isLoading={saveCertMutation.isPending}
/>
</DialogContent>
</Dialog>
{/* Report Modal */}
<Dialog open={showReportModal} onOpenChange={setShowReportModal}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Send className="w-5 h-5 text-blue-600" />
Send Compliance Report
</DialogTitle>
</DialogHeader>
<ReportForm onSend={sendComplianceReport} onCancel={() => setShowReportModal(false)} stats={stats} />
</DialogContent>
</Dialog>
</div>
</div>
);
}
function EmployeeCertCard({ employee, onAddCert, onEditCert, onSendAlert, showVendor }) {
const emp = employee;
return (
<Card className={`border-0 shadow-lg hover:shadow-xl transition-all duration-300 overflow-hidden ${
!emp.canWork ? 'ring-2 ring-red-200' : emp.allCurrent ? 'ring-2 ring-emerald-200' : ''
}`}>
<CardContent className="p-0">
{/* Header */}
<div className={`p-4 ${emp.allCurrent ? 'bg-gradient-to-r from-emerald-500 to-emerald-600' : emp.hasExpired ? 'bg-gradient-to-r from-red-500 to-red-600' : emp.hasExpiringSoon ? 'bg-gradient-to-r from-amber-500 to-amber-600' : 'bg-gradient-to-r from-slate-500 to-slate-600'} text-white`}>
<div className="flex items-center gap-3">
<Avatar className="w-12 h-12 border-2 border-white/30">
<AvatarFallback className="bg-white/20 text-white font-bold text-lg">
{emp.employee_name?.charAt(0)}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<h3 className="font-bold text-lg truncate">{emp.employee_name}</h3>
<p className="text-sm text-white/80 truncate">{emp.position || "Staff Member"}</p>
</div>
<div className="text-right">
<div className={`px-3 py-1 rounded-full text-xs font-bold ${emp.canWork ? 'bg-white/20' : 'bg-white text-red-600'}`}>
{emp.canWork ? "Can Work" : "Cannot Work"}
</div>
</div>
</div>
{/* Compliance Progress */}
<div className="mt-4">
<div className="flex items-center justify-between text-sm mb-1">
<span className="text-white/80">Compliance Score</span>
<span className="font-bold">{emp.complianceScore}%</span>
</div>
<Progress value={emp.complianceScore} className="h-2 bg-white/20" />
</div>
</div>
{/* Certifications */}
<div className="p-4 space-y-3">
{REQUIRED_CERTIFICATIONS.map(type => {
const cert = emp.certifications[type];
const config = CERT_CONFIG[type];
const Icon = config.icon;
return (
<div
key={type}
className={`flex items-center gap-3 p-3 rounded-xl border-2 transition-all ${
cert ? (
cert.status === "current" ? `${config.bgColor} ${config.borderColor}` :
cert.status === "expiring_soon" ? "bg-amber-50 border-amber-200" :
"bg-red-50 border-red-200"
) : "bg-slate-50 border-dashed border-slate-300"
}`}
>
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${
cert ? (
cert.status === "current" ? `bg-gradient-to-br ${config.color} text-white` :
cert.status === "expiring_soon" ? "bg-amber-500 text-white" :
"bg-red-500 text-white"
) : "bg-slate-200 text-slate-400"
}`}>
<Icon className="w-5 h-5" />
</div>
<div className="flex-1 min-w-0">
<p className="font-semibold text-slate-900 text-sm">{type}</p>
{cert ? (
<p className={`text-xs ${cert.status === "current" ? "text-slate-500" : cert.status === "expiring_soon" ? "text-amber-600" : "text-red-600"}`}>
{cert.status === "expired" ? "Expired" : `Expires: ${format(parseISO(cert.expiry_date), 'MMM d, yyyy')}`}
{cert.days_until_expiry !== null && cert.days_until_expiry >= 0 && ` (${cert.days_until_expiry}d)`}
</p>
) : (
<p className="text-xs text-slate-400">Not uploaded</p>
)}
</div>
<div className="flex items-center gap-1">
{cert ? (
<>
{cert.status === "current" && (
<CheckCircle2 className="w-5 h-5 text-emerald-500" />
)}
{cert.status === "expiring_soon" && (
<button
onClick={() => onSendAlert(cert)}
className="p-1.5 rounded-lg bg-amber-100 text-amber-600 hover:bg-amber-200 transition-colors"
title="Send reminder"
>
<Bell className="w-4 h-4" />
</button>
)}
{cert.status === "expired" && (
<XCircle className="w-5 h-5 text-red-500" />
)}
<button
onClick={() => onEditCert(cert)}
className="p-1.5 rounded-lg bg-slate-100 text-slate-600 hover:bg-slate-200 transition-colors"
title="Edit"
>
<Edit2 className="w-4 h-4" />
</button>
</>
) : (
<button
onClick={() => onAddCert(type)}
className="p-1.5 rounded-lg bg-blue-100 text-blue-600 hover:bg-blue-200 transition-colors"
title="Add certification"
>
<Plus className="w-4 h-4" />
</button>
)}
</div>
</div>
);
})}
</div>
{/* Footer */}
{showVendor && emp.vendor_name && (
<div className="px-4 pb-4">
<div className="flex items-center gap-2 text-xs text-slate-500">
<Building2 className="w-3 h-3" />
<span>{emp.vendor_name}</span>
</div>
</div>
)}
</CardContent>
</Card>
);
}
function CertificationForm({ certification, staff, onSave, onCancel, isLoading }) {
const [formData, setFormData] = useState({
employee_id: certification?.employee_id || "",
employee_name: certification?.employee_name || "",
vendor_id: certification?.vendor_id || "",
vendor_name: certification?.vendor_name || "",
certification_type: certification?.certification_type || "",
issue_date: certification?.issue_date || "",
expiry_date: certification?.expiry_date || "",
issuer: certification?.issuer || "",
certificate_number: certification?.certificate_number || "",
notes: certification?.notes || "",
...certification,
});
const handleSubmit = (e) => {
e.preventDefault();
onSave(formData);
};
const handleStaffSelect = (staffId) => {
const selectedStaff = staff.find(s => s.id === staffId);
if (selectedStaff) {
setFormData(prev => ({
...prev,
employee_id: staffId,
employee_name: selectedStaff.employee_name,
vendor_id: selectedStaff.vendor_id,
vendor_name: selectedStaff.vendor_name,
}));
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
{!certification?.employee_id && (
<div>
<Label className="text-sm font-medium">Employee *</Label>
<Select value={formData.employee_id} onValueChange={handleStaffSelect}>
<SelectTrigger className="mt-1.5">
<SelectValue placeholder="Select employee" />
</SelectTrigger>
<SelectContent>
{staff.map(s => (
<SelectItem key={s.id} value={s.id}>{s.employee_name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
<div>
<Label className="text-sm font-medium">Certification Type *</Label>
<Select
value={formData.certification_type}
onValueChange={(v) => setFormData(prev => ({ ...prev, certification_type: v }))}
disabled={!!certification?.certification_type}
>
<SelectTrigger className="mt-1.5">
<SelectValue placeholder="Select type" />
</SelectTrigger>
<SelectContent>
{REQUIRED_CERTIFICATIONS.map(type => (
<SelectItem key={type} value={type}>
<div className="flex items-center gap-2">
{React.createElement(CERT_CONFIG[type].icon, { className: "w-4 h-4" })}
{type}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="text-sm font-medium">Issue Date</Label>
<Input
type="date"
value={formData.issue_date}
onChange={(e) => setFormData(prev => ({ ...prev, issue_date: e.target.value }))}
className="mt-1.5"
/>
</div>
<div>
<Label className="text-sm font-medium">Expiry Date *</Label>
<Input
type="date"
value={formData.expiry_date}
onChange={(e) => setFormData(prev => ({ ...prev, expiry_date: e.target.value }))}
className="mt-1.5"
required
/>
</div>
</div>
<div>
<Label className="text-sm font-medium">Issuing Authority</Label>
<Input
value={formData.issuer}
onChange={(e) => setFormData(prev => ({ ...prev, issuer: e.target.value }))}
placeholder="e.g., California ABC"
className="mt-1.5"
/>
</div>
<div>
<Label className="text-sm font-medium">Certificate Number</Label>
<Input
value={formData.certificate_number}
onChange={(e) => setFormData(prev => ({ ...prev, certificate_number: e.target.value }))}
placeholder="Certificate ID"
className="mt-1.5"
/>
</div>
<div className="flex justify-end gap-2 pt-4 border-t">
<Button type="button" variant="outline" onClick={onCancel}>Cancel</Button>
<Button type="submit" disabled={isLoading} className="bg-blue-600 hover:bg-blue-700">
{isLoading ? "Saving..." : "Save Certification"}
</Button>
</div>
</form>
);
}
function ReportForm({ onSend, onCancel, stats }) {
const [email, setEmail] = useState("");
return (
<div className="space-y-4">
<div className="p-4 bg-gradient-to-br from-blue-50 to-slate-50 rounded-xl border border-blue-100">
<h4 className="font-semibold text-slate-900 mb-3 flex items-center gap-2">
<Sparkles className="w-4 h-4 text-blue-600" />
Report Preview
</h4>
<div className="grid grid-cols-2 gap-3 text-sm">
<div className="bg-white p-3 rounded-lg">
<span className="text-slate-500 text-xs">Total Staff</span>
<p className="font-bold text-lg text-slate-900">{stats.total}</p>
</div>
<div className="bg-emerald-50 p-3 rounded-lg">
<span className="text-emerald-600 text-xs">Compliant</span>
<p className="font-bold text-lg text-emerald-700">{stats.compliant}</p>
</div>
<div className="bg-amber-50 p-3 rounded-lg">
<span className="text-amber-600 text-xs">Expiring Soon</span>
<p className="font-bold text-lg text-amber-700">{stats.expiring}</p>
</div>
<div className="bg-red-50 p-3 rounded-lg">
<span className="text-red-600 text-xs">Expired</span>
<p className="font-bold text-lg text-red-700">{stats.expired}</p>
</div>
</div>
</div>
<div>
<Label className="text-sm font-medium">Recipient Email *</Label>
<Input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="client@company.com"
className="mt-1.5"
required
/>
</div>
<div className="flex justify-end gap-2 pt-2">
<Button variant="outline" onClick={onCancel}>Cancel</Button>
<Button onClick={() => onSend(email)} disabled={!email} className="bg-blue-600 hover:bg-blue-700">
<Send className="w-4 h-4 mr-2" />Send Report
</Button>
</div>
</div>
);
}