import { useState, useEffect, useRef, useCallback } from "react"; import { PDFDocument } from "pdf-lib"; import { supabase } from "@/integrations/supabase/client"; import { useToast } from "@/hooks/use-toast"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Checkbox } from "@/components/ui/checkbox"; import { ArrowRight, ArrowLeft, Download, FileText, Layout, Eye, Printer, Loader2, CheckSquare, Plus, Trash2, GripVertical, Paperclip, } from "lucide-react"; import { jsPDF } from "jspdf"; import autoTable from "jspdf-autotable"; import { drawReportCoverPage } from "@/lib/reportCover"; import { format, subMonths } from "date-fns"; import type { Tables } from "@/integrations/supabase/types"; import { ensureFontsLoaded } from "@/lib/googleFontsManager"; import { loadFontForPdf, registerFontWithJsPdf } from "@/lib/fontPdfLoader"; import { DragDropContext, Droppable, Draggable, type DropResult, } from "@hello-pangea/dnd"; // ─── Strict-mode-safe Droppable wrapper ─── function StrictModeDroppable({ children, ...props }: React.ComponentProps) { const [enabled, setEnabled] = useState(false); useEffect(() => { const id = requestAnimationFrame(() => setEnabled(true)); return () => { cancelAnimationFrame(id); setEnabled(false); }; }, []); if (!enabled) return null; return {children}; } interface CoverData { title: string; date: string; companyName: string; preparedBy: string; logoUrl: string; bgUrl: string; } /** A single entry in the unified report order list */ interface ReportEntry { id: string; type: "module" | "attachment"; /** For module type: the DATA_MODULES key. For attachment: undefined */ moduleKey?: string; label: string; /** For attachment type: how many pages this attachment occupies */ pageCount: number; enabled: boolean; /** For attachment type: the uploaded PDF file */ file?: File; /** AI-generated summary for attachment PDFs */ aiSummary?: string; /** Whether AI summary is being generated */ summarizing?: boolean; } const DATA_MODULES = [ { key: "statusUpdates", label: "Status Updates", table: "status_updates", dateCol: "created_at", columns: ["Date", "Title", "Content"], fields: ["created_at", "title", "content"] }, { key: "ownerUpdates", label: "Owner Updates", table: "owner_updates", dateCol: "posted_at", columns: ["Date", "Property", "Title", "Content"], fields: ["posted_at", "_property", "title", "content"], customSelect: "*, units(unit_number, address)" }, { key: "violations", label: "Violations", table: "violations", dateCol: "created_at", columns: ["Date", "Address", "Type", "Status", "Stage"], fields: ["created_at", "address", "violation_type", "status", "stage"] }, { key: "collections", label: "Collections", table: "collections", dateCol: "updated_at", columns: ["Property", "Owner", "Amount Owed", "Status", "Last Notice"], fields: ["_property", "_owner_name", "amount_owed", "status", "last_notice_date"], noDateFilter: true, customSelect: "*, units(unit_number, address), owners(first_name, last_name)" }, { key: "legalMatters", label: "Legal Cases", table: "legal_matters", dateCol: "created_at", columns: ["Case #", "Title", "Category", "Stage", "Status"], fields: ["case_number", "title", "category", "stage", "status"], noDateFilter: true }, { key: "arcApplications", label: "ARC Applications", table: "arc_applications", dateCol: "created_at", columns: ["Date", "Title", "Type", "Status"], fields: ["created_at", "title", "project_type", "status"] }, { key: "boardVotes", label: "Board Votes", table: "board_votes", dateCol: "created_at", columns: ["Date", "Title", "Status"], fields: ["created_at", "title", "status"] }, { key: "estoppels", label: "Estoppels", table: "estoppels", dateCol: "created_at", columns: ["Date", "Address", "Status", "Notes"], fields: ["created_at", "address", "status", "notes"] }, { key: "homeownerRequests", label: "Homeowner Requests", table: "homeowner_requests", dateCol: "created_at", columns: ["Date", "Subject", "Status"], fields: ["created_at", "subject", "status"], noDateFilter: true }, { key: "bidsQuotes", label: "Bids & Quotes", table: "bids_quotes", dateCol: "created_at", columns: ["Date", "Vendor", "Amount", "Status"], fields: ["created_at", "vendor_name", "amount", "status"] }, { key: "callLogs", label: "Call Logs", table: "call_logs", dateCol: "created_at", columns: ["Date", "Caller", "Type", "Notes"], fields: ["created_at", "caller_name", "call_type", "notes"] }, ] as const; type ModuleKey = typeof DATA_MODULES[number]["key"]; export default function ReportGeneratorPage() { const { toast } = useToast(); const [associations, setAssociations] = useState[]>([]); const [selectedAssociationId, setSelectedAssociationId] = useState(""); const [startDate, setStartDate] = useState(format(subMonths(new Date(), 1), "yyyy-MM-dd")); const [endDate, setEndDate] = useState(format(new Date(), "yyyy-MM-dd")); const [step, setStep] = useState(1); const [isExporting, setIsExporting] = useState(false); const [dataLoading, setDataLoading] = useState(false); const [reportData, setReportData] = useState>({}); const previewRef = useRef(null); const [coverData, setCoverData] = useState({ title: "MANAGEMENT\nREPORT", date: format(new Date(), "MMMM yyyy"), companyName: "", preparedBy: "Avria Community Management, LLC", logoUrl: "", bgUrl: "", }); // Unified ordered list of report entries const [reportEntries, setReportEntries] = useState(() => DATA_MODULES.map((m) => ({ id: m.key, type: "module" as const, moduleKey: m.key, label: m.label, pageCount: 1, enabled: true, })) ); // Attachment add form const [newAttLabel, setNewAttLabel] = useState(""); const [newAttPages, setNewAttPages] = useState("1"); const [newAttFile, setNewAttFile] = useState(null); const fileInputRef = useRef(null); useEffect(() => { supabase.from("associations").select("*").eq("status", "active").order("name").then(({ data }) => { setAssociations(data || []); }); }, []); useEffect(() => { const assoc = associations.find((a) => a.id === selectedAssociationId); if (assoc) { setCoverData((prev) => ({ ...prev, companyName: assoc.name, logoUrl: assoc.logo_url || "" })); } }, [selectedAssociationId, associations]); const fetchReportData = useCallback(async () => { if (!selectedAssociationId) return; setDataLoading(true); const result: Record = {}; const sd = startDate; const ed = endDate + "T23:59:59"; const enabledModuleKeys = reportEntries.filter((e) => e.type === "module" && e.enabled).map((e) => e.moduleKey!); const enabledList = DATA_MODULES.filter((m) => enabledModuleKeys.includes(m.key)); await Promise.all( enabledList.map(async (mod) => { try { const selectStr = (mod as any).customSelect || "*"; let query = supabase .from(mod.table as any) .select(selectStr) .eq("association_id", selectedAssociationId) .order(mod.dateCol, { ascending: false }); if (!("noDateFilter" in mod && mod.noDateFilter)) { query = query.gte(mod.dateCol, sd).lte(mod.dateCol, ed); } const { data } = await query; if (mod.key === "collections" && data) { result[mod.key] = data.map((row: any) => ({ ...row, _property: row.units?.address || row.units?.unit_number || "-", _owner_name: row.owners ? `${row.owners.first_name || ""} ${row.owners.last_name || ""}`.trim() : "-", })); } else if (mod.key === "ownerUpdates" && data) { result[mod.key] = data.map((row: any) => ({ ...row, _property: row.units?.address || row.units?.unit_number || "-", })); } else { result[mod.key] = data || []; } } catch { result[mod.key] = []; } }) ); setReportData(result); setDataLoading(false); }, [selectedAssociationId, startDate, endDate, reportEntries]); const handleCoverChange = (e: React.ChangeEvent) => { setCoverData((prev) => ({ ...prev, [e.target.name]: e.target.value })); }; const toggleEntry = (id: string) => { setReportEntries((prev) => prev.map((e) => e.id === id ? { ...e, enabled: !e.enabled } : e)); }; const removeEntry = (id: string) => { setReportEntries((prev) => prev.filter((e) => e.id !== id)); }; const addAttachment = () => { if (!newAttLabel.trim()) return; const pc = Math.max(1, parseInt(newAttPages) || 1); const entryId = crypto.randomUUID(); const file = newAttFile || undefined; setReportEntries((prev) => [ ...prev, { id: entryId, type: "attachment", label: newAttLabel.trim(), pageCount: pc, enabled: true, file, summarizing: !!file }, ]); setNewAttLabel(""); setNewAttPages("1"); setNewAttFile(null); if (fileInputRef.current) fileInputRef.current.value = ""; // Trigger AI summary generation in the background if (file) { generateAttachmentSummary(entryId, file, newAttLabel.trim()); } }; const generateAttachmentSummary = async (entryId: string, file: File, label: string) => { try { // Upload temporarily to get a URL for the summarizer const tempPath = `temp-report-summaries/${entryId}.pdf`; const { error: uploadError } = await supabase.storage .from("files") .upload(tempPath, file, { upsert: true }); if (uploadError) throw uploadError; const { data: urlData } = await supabase.storage .from("files") .createSignedUrl(tempPath, 300); // 5 min expiry if (!urlData?.signedUrl) throw new Error("Failed to get signed URL"); const { data, error } = await supabase.functions.invoke("summarize-document", { body: { documentId: entryId, fileUrl: urlData.signedUrl, title: label }, }); if (error) throw error; setReportEntries((prev) => prev.map((e) => e.id === entryId ? { ...e, aiSummary: data?.summary || undefined, summarizing: false } : e) ); // Clean up temp file await supabase.storage.from("files").remove([tempPath]); if (data?.summary) { toast({ title: "AI Summary Generated", description: `Summary ready for "${label}"` }); } } catch (err) { console.error("Attachment summary failed:", err); setReportEntries((prev) => prev.map((e) => e.id === entryId ? { ...e, summarizing: false } : e) ); } }; const handleAttachmentFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (file.type !== "application/pdf") { toast({ variant: "destructive", title: "Invalid File", description: "Only PDF files are supported." }); e.target.value = ""; return; } setNewAttFile(file); // Auto-detect page count try { const buffer = await file.arrayBuffer(); const pdfDoc = await PDFDocument.load(buffer); setNewAttPages(String(pdfDoc.getPageCount())); } catch { // Keep manual page count if detection fails } // Auto-fill label from filename if empty if (!newAttLabel.trim()) { setNewAttLabel(file.name.replace(/\.pdf$/i, "")); } }; const onDragEnd = (result: DropResult) => { if (!result.destination) return; const items = Array.from(reportEntries); const [moved] = items.splice(result.source.index, 1); items.splice(result.destination.index, 0, moved); setReportEntries(items); }; /** Compute page number for each enabled entry. Cover=1, TOC=2, content starts at 3. */ const computePageNumbers = useCallback((): Map => { const map = new Map(); let page = 3; // cover=1, TOC=2 for (const entry of reportEntries) { if (!entry.enabled) continue; map.set(entry.id, page); if (entry.type === "attachment") { page += entry.pageCount; } else { const rows = reportData[entry.moduleKey!]?.length || 0; page += Math.max(1, Math.ceil(rows / 30)); } } return map; }, [reportEntries, reportData]); const pageNumbers = computePageNumbers(); const handlePrint = () => { if (!previewRef.current) return; const printWindow = window.open("", "_blank"); if (!printWindow) return; printWindow.document.write(`${coverData.title.replace(/\n/g, " ")}${previewRef.current.innerHTML}`); printWindow.document.close(); printWindow.focus(); printWindow.print(); printWindow.close(); }; const cleanText = (text: string) => { if (!text) return "-"; return text.replace(/<[^>]*>/g, "").replace(/ /g, " ").trim() || "-"; }; const formatDate = (d: string) => { if (!d) return "-"; try { return format(new Date(d), "MM/dd/yyyy"); } catch { return "-"; } }; const formatCurrency = (v: any) => { const n = parseFloat(v); if (isNaN(n)) return "-"; return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(n); }; const getCellValue = (row: any, field: string) => { const val = row[field]; if (val === null || val === undefined) return "-"; if (field.includes("date") || field === "created_at" || field === "updated_at" || field === "posted_at") return formatDate(val); if (field === "amount" || field === "amount_due" || field === "amount_owed") return formatCurrency(val); if (field === "content") return cleanText(String(val)).slice(0, 120); return cleanText(String(val)); }; /** Only enabled entries that are modules with data, in order */ const getOrderedActiveModules = () => { return reportEntries .filter((e) => e.enabled && e.type === "module" && reportData[e.moduleKey!]?.length > 0) .map((e) => ({ ...DATA_MODULES.find((m) => m.key === e.moduleKey)!, entryId: e.id })); }; /** All enabled entries (modules with data + attachments) in order */ const getOrderedEnabledEntries = () => { return reportEntries.filter((e) => { if (!e.enabled) return false; if (e.type === "attachment") return true; return reportData[e.moduleKey!]?.length > 0; }); }; // ── PDF Generation ── const generatePDF = async () => { setIsExporting(true); toast({ title: "Generating PDF…", description: "Please wait." }); try { await ensureFontsLoaded(["Open Sans"]); const doc = new jsPDF({ orientation: "portrait", unit: "pt", format: "letter" }); const openSansRegular = await loadFontForPdf("Open Sans", "Regular"); const openSansBold = await loadFontForPdf("Open Sans", "Bold"); registerFontWithJsPdf(doc, "Open Sans", openSansRegular, "normal"); registerFontWithJsPdf(doc, "Open Sans", openSansBold, "bold"); const W = doc.internal.pageSize.getWidth(); const H = doc.internal.pageSize.getHeight(); const M = 40; const cx = W / 2; const pdfFont = openSansRegular ? "Open Sans" : "helvetica"; const drawPageFooter = (pageNum: number) => { doc.setFont(pdfFont, "normal"); doc.setFontSize(8); doc.setTextColor(148, 163, 184); doc.text(`${coverData.companyName} — ${coverData.date}`, M, H - 20); doc.text(`Page ${pageNum}`, W - M, H - 20, { align: "right" }); doc.setDrawColor(226, 232, 240); doc.setLineWidth(0.5); doc.line(M, H - 30, W - M, H - 30); }; // ═══ COVER PAGE ═══ await drawReportCoverPage(doc, W, H, coverData); const orderedEntries = getOrderedEnabledEntries(); // ═══ TABLE OF CONTENTS ═══ if (orderedEntries.length > 0) { doc.addPage(); let pageNum = 2; drawPageFooter(pageNum); let y = 60; doc.setFillColor(30, 41, 59); doc.rect(M, y - 20, 4, 28, "F"); doc.setFont("helvetica", "bold"); doc.setFontSize(24); doc.setTextColor(15, 23, 42); doc.text("TABLE OF CONTENTS", M + 16, y); y += 12; doc.setDrawColor(30, 41, 59); doc.setLineWidth(2); doc.line(M, y, W - M, y); y += 8; doc.setDrawColor(226, 232, 240); doc.setLineWidth(0.5); doc.line(M, y, W - M, y); y += 30; orderedEntries.forEach((entry, idx) => { if (y > H - 80) { doc.addPage(); pageNum++; drawPageFooter(pageNum); y = 60; } const sectionNum = String(idx + 1).padStart(2, "0"); const pg = String(pageNumbers.get(entry.id) || ""); const isAtt = entry.type === "attachment"; doc.setFont("helvetica", "bold"); doc.setFontSize(11); doc.setTextColor(30, 41, 59); doc.text(sectionNum, M + 4, y); doc.setFont("helvetica", "normal"); doc.setFontSize(12); doc.setTextColor(51, 65, 85); doc.text(entry.label, M + 36, y); const titleEndX = M + 36 + doc.getTextWidth(entry.label) + 8; const pageNumX = W - M - doc.getTextWidth(pg) - 4; doc.setFontSize(10); doc.setTextColor(203, 213, 225); let dotX = titleEndX; while (dotX < pageNumX - 4) { doc.text(".", dotX, y); dotX += 5; } doc.setFont("helvetica", "bold"); doc.setFontSize(12); doc.setTextColor(30, 41, 59); doc.text(pg, W - M, y, { align: "right" }); if (!isAtt && entry.moduleKey) { doc.setFont("helvetica", "normal"); doc.setFontSize(8); doc.setTextColor(148, 163, 184); doc.text(`${reportData[entry.moduleKey]?.length || 0} records`, M + 36, y + 12); } else { doc.setFont("helvetica", "normal"); doc.setFontSize(8); doc.setTextColor(148, 163, 184); doc.text(`${entry.pageCount} page${entry.pageCount > 1 ? "s" : ""} (attachment)`, M + 36, y + 12); } y += 32; doc.setDrawColor(241, 245, 249); doc.setLineWidth(0.5); doc.line(M + 36, y - 10, W - M, y - 10); }); } // ═══ DATA SECTION PAGES ═══ const tableTheme = { headStyles: { fillColor: [30, 41, 59] as [number, number, number], textColor: 255, fontStyle: "bold" as const, fontSize: 9, cellPadding: 6 }, bodyStyles: { textColor: [51, 65, 85] as [number, number, number], fontSize: 8, cellPadding: 5 }, alternateRowStyles: { fillColor: [248, 250, 252] as [number, number, number] }, styles: { lineColor: [226, 232, 240] as [number, number, number], lineWidth: 0.5, overflow: "linebreak" as const }, margin: { top: 70, right: M, bottom: 50, left: M }, }; // Track which attachment files need to be merged and at which page position const attachmentMergeQueue: { file: File; afterPage: number }[] = []; let sectionIdx = 0; for (const entry of orderedEntries) { sectionIdx++; if (entry.type === "attachment") { if (entry.file) { // Add a section title page, then we'll merge actual PDF pages after jsPDF generation doc.addPage(); let y = 50; doc.setFillColor(30, 41, 59); doc.roundedRect(M, y - 16, 36, 36, 4, 4, "F"); doc.setFont("helvetica", "bold"); doc.setFontSize(18); doc.setTextColor(255, 255, 255); doc.text(`${sectionIdx}`, M + 18, y + 6, { align: "center" }); doc.setFontSize(20); doc.setTextColor(15, 23, 42); doc.text(entry.label, M + 48, y + 4); doc.setFontSize(10); doc.setTextColor(148, 163, 184); doc.text(`${entry.pageCount} page${entry.pageCount > 1 ? "s" : ""}`, W - M, y + 4, { align: "right" }); y += 20; doc.setDrawColor(226, 232, 240); doc.setLineWidth(1); doc.line(M, y, W - M, y); // Render AI summary if available if (entry.aiSummary) { y += 24; doc.setFont("helvetica", "bold"); doc.setFontSize(11); doc.setTextColor(30, 41, 59); doc.text("✦ Executive Summary", M, y); y += 16; doc.setFont("helvetica", "normal"); doc.setFontSize(10); doc.setTextColor(71, 85, 105); const summaryLines = doc.splitTextToSize(entry.aiSummary, W - M * 2); doc.text(summaryLines, M, y); y += summaryLines.length * 14 + 8; doc.setDrawColor(226, 232, 240); doc.setLineWidth(0.5); doc.line(M, y, W - M, y); } drawPageFooter((doc as any).internal.getNumberOfPages()); attachmentMergeQueue.push({ file: entry.file, afterPage: (doc as any).internal.getNumberOfPages(), }); } else { // No file — add blank placeholder pages for (let p = 0; p < entry.pageCount; p++) { doc.addPage(); const pn = (doc as any).internal.getNumberOfPages(); if (p === 0) { let y = 50; doc.setFillColor(30, 41, 59); doc.roundedRect(M, y - 16, 36, 36, 4, 4, "F"); doc.setFont("helvetica", "bold"); doc.setFontSize(18); doc.setTextColor(255, 255, 255); doc.text(`${sectionIdx}`, M + 18, y + 6, { align: "center" }); doc.setFontSize(20); doc.setTextColor(15, 23, 42); doc.text(entry.label, M + 48, y + 4); doc.setFontSize(10); doc.setTextColor(148, 163, 184); doc.text("(attachment placeholder)", W - M, y + 4, { align: "right" }); y += 20; doc.setDrawColor(226, 232, 240); doc.setLineWidth(1); doc.line(M, y, W - M, y); } drawPageFooter(pn); } } continue; } const mod = DATA_MODULES.find((m) => m.key === entry.moduleKey)!; doc.addPage(); let y = 50; doc.setFillColor(30, 41, 59); doc.roundedRect(M, y - 16, 36, 36, 4, 4, "F"); doc.setFont("helvetica", "bold"); doc.setFontSize(18); doc.setTextColor(255, 255, 255); doc.text(`${sectionIdx}`, M + 18, y + 6, { align: "center" }); doc.setFontSize(20); doc.setTextColor(15, 23, 42); doc.text(mod.label, M + 48, y + 4); doc.setFontSize(10); doc.setTextColor(148, 163, 184); doc.text(`${reportData[mod.key].length} records`, W - M, y + 4, { align: "right" }); y += 20; doc.setDrawColor(226, 232, 240); doc.setLineWidth(1); doc.line(M, y, W - M, y); const rows = reportData[mod.key].map((row: any) => mod.fields.map((f) => getCellValue(row, f))); autoTable(doc, { startY: y + 10, head: [mod.columns as unknown as string[]], body: rows, ...tableTheme, didDrawPage: () => { const currentPage = (doc as any).internal.getNumberOfPages(); drawPageFooter(currentPage); }, }); } // ═══ MERGE ATTACHMENT PDFs ═══ if (attachmentMergeQueue.length > 0) { const jsPdfBytes = doc.output("arraybuffer"); const jsPdfDoc = await PDFDocument.load(jsPdfBytes); // Create a brand new document and copy pages to avoid font corruption const mergedPdf = await PDFDocument.create(); // Copy ALL jsPDF pages first const allJsPdfPages = await mergedPdf.copyPages(jsPdfDoc, jsPdfDoc.getPageIndices()); allJsPdfPages.forEach((page) => mergedPdf.addPage(page)); // Now insert attachment file pages at the correct positions // Process in reverse order so page indices stay correct for (let i = attachmentMergeQueue.length - 1; i >= 0; i--) { const { file, afterPage } = attachmentMergeQueue[i]; try { const fileBuffer = await file.arrayBuffer(); const attachPdf = await PDFDocument.load(fileBuffer); const copiedPages = await mergedPdf.copyPages(attachPdf, attachPdf.getPageIndices()); // afterPage is 1-indexed, pdf-lib insertPage is 0-indexed copiedPages.forEach((page, idx) => { mergedPdf.insertPage(afterPage + idx, page); }); } catch (e) { console.error(`Failed to merge attachment: ${file.name}`, e); } } const finalBytes = await mergedPdf.save(); const blob = new Blob([new Uint8Array(finalBytes)], { type: "application/pdf" }); const { saveFile } = await import("@/lib/saveFile"); await saveFile(blob, { suggestedName: `Management_Report_${format(new Date(), "yyyy-MM-dd")}.pdf`, mimeType: "application/pdf", description: "PDF Document", }); } else { doc.save(`Management_Report_${format(new Date(), "yyyy-MM-dd")}.pdf`); } toast({ title: "Success", description: "Report downloaded." }); } catch (err) { console.error(err); toast({ variant: "destructive", title: "Export Failed", description: String(err) }); } finally { setIsExporting(false); } }; const goToStep = (newStep: number) => { if (newStep === 2) fetchReportData(); setStep(newStep); }; const PREVIEW_SCALE = 0.26; const ReportCoverFrame = ({ rootRef }: { rootRef?: React.RefObject }) => (
{coverData.bgUrl ? ( <> Cover ) : (
)}
{coverData.logoUrl && (
Logo
)}

{coverData.title}

Prepared For

{coverData.companyName || "Association Name"}

{coverData.date}

Prepared By

{coverData.preparedBy}

); const ReportCover = ({ isPreview = false, rootRef }: { isPreview?: boolean; rootRef?: React.RefObject }) => { if (!isPreview) { return ; } return (
); }; const steps2UI = [ { num: 1, label: "Cover & Modules", icon: Layout }, { num: 2, label: "Preview & Export", icon: Eye }, ]; const enabledCount = reportEntries.filter((e) => e.enabled).length; return (
setStartDate(e.target.value)} />
setEndDate(e.target.value)} />

Report Builder

Drag to reorder sections. Page numbers auto-update. Add attachments for external documents.

{steps2UI.map((s) => (
= s.num ? "text-primary" : "text-muted-foreground"}`}>{s.label}
))}
{step === 1 && (
{/* Cover Page */} Cover Page