@burdenoff/microfe-export 2026.625.1 → 2026.626.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"ExportDetailPage.js","names":[],"sources":["../../../src/export/pages/ExportDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, useNavigate } from \"react-router-dom\";\nimport {\n ArrowLeft,\n Download,\n XCircle,\n Trash2,\n RotateCcw,\n Loader2,\n FileDown,\n Clock,\n AlertCircle,\n CheckCircle2,\n HardDrive,\n FileText,\n} from \"lucide-react\";\nimport { GlassCard, CTAOverflowMenu } from \"@burdenoff/fe-libs/ui\";\nimport type { CTAAction, CTAPrimaryAction } from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport {\n useExportJob,\n useExportJobProgress,\n useCancelExportJob,\n useDeleteExportJob,\n useRetryExportJob,\n} from \"../hooks/useExportQueries\";\nimport { ExportStatusBadge } from \"../components/ExportStatusBadge\";\nimport { ExportProgressBar } from \"../components/ExportProgressBar\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n getFormatDisplayName,\n getArchiveFormatDisplayName,\n} from \"../constants/enums\";\nimport { cn } from \"../utils/cn\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\n/**\n * Format bytes to human-readable size\n */\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;\n}\n\n/**\n * Helper to join paths correctly\n */\nconst joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n return cleanBase + cleanPath;\n};\n\n/**\n * Export Detail Page\n * Single export job detail view with parts, progress, download links.\n * Uses GraphQL subscriptions for live progress updates when a job is active.\n */\nexport function ExportDetailPage() {\n const { jobId } = useParams<{ jobId: string }>();\n const { basePath = \"/\", navigate } = useExport();\n const routerNavigate = useNavigate();\n\n const handleBack = () => {\n if (window.history.length > 1) {\n routerNavigate(-1);\n } else {\n navigate?.(basePath);\n }\n };\n\n const { data: job, isLoading } = useExportJob(jobId);\n const { data: progressJob } = useExportJobProgress(jobId);\n const [pendingAction, setPendingAction] = useState<\n \"cancel\" | \"delete\" | null\n >(null);\n\n const { cancelJob, isPending: cancelling } = useCancelExportJob();\n const { deleteJob, isPending: deleting } = useDeleteExportJob();\n const { retryJob, isPending: retrying } = useRetryExportJob();\n\n const liveJob = progressJob ?? job ?? null;\n\n // Jobs in CREATED/QUEUED/RUNNING can still be cancelled\n const isCancellable =\n liveJob?.status === \"CREATED\" ||\n liveJob?.status === \"QUEUED\" ||\n liveJob?.status === \"RUNNING\";\n const isCompleted = liveJob?.status === \"COMPLETED\";\n const isFailed = liveJob?.status === \"FAILED\";\n const isRetryable =\n liveJob?.status === \"FAILED\" ||\n liveJob?.status === \"CANCELLED\" ||\n liveJob?.status === \"EXPIRED\";\n\n const handleCancel = async () => {\n if (!jobId) return;\n setPendingAction(\"cancel\");\n };\n\n const handleDelete = async () => {\n if (!jobId) return;\n nativeImpact(\"medium\");\n setPendingAction(\"delete\");\n };\n\n const confirmPendingAction = async () => {\n if (!jobId || !pendingAction) return;\n try {\n if (pendingAction === \"cancel\") {\n await cancelJob(jobId);\n } else {\n await deleteJob(jobId);\n navigate?.(basePath);\n }\n nativeNotify(\"success\");\n } catch {\n nativeNotify(\"error\");\n }\n setPendingAction(null);\n };\n\n const handleRetry = async () => {\n if (!jobId) return;\n const newJob = await retryJob(jobId);\n // Navigate to the new job created by retry\n if (newJob?.id) {\n navigate?.(joinPath(basePath, newJob.id));\n }\n };\n\n // Build a single primary CTA + overflow menu for the header. Download (the\n // first available part) is the primary action when the export is ready;\n // otherwise the most relevant lifecycle action leads. Destructive Delete\n // always lives in the overflow menu and is confirmed via the dialog.\n const firstDownloadablePart = liveJob?.parts.find((p) => p.downloadUrl);\n\n // Loading\n if (isLoading) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20\">\n <Loader2 size={48} className=\"animate-spin text-primary\" />\n <span className=\"mt-6 text-sm font-medium text-text-secondary\">\n Loading export details…\n </span>\n </div>\n );\n }\n\n // Not found\n if (!liveJob) {\n return (\n <PageLayout showHeader={false}>\n <div className=\"flex items-center gap-3 mb-6\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-all duration-200\"\n >\n <ArrowLeft size={20} />\n </button>\n <h1 className=\"text-xl font-bold\">Export Not Found</h1>\n </div>\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-12 text-center\">\n <AlertCircle\n size={48}\n className=\"text-status-error-text mx-auto mb-4\"\n />\n <h3 className=\"text-lg font-bold text-status-error-text mb-2\">\n Export Not Found\n </h3>\n <p className=\"text-status-error-text mb-6\">\n The export job you are looking for does not exist or has been\n deleted.\n </p>\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"inline-flex items-center gap-2 px-6 py-3 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <ArrowLeft size={18} />\n Back to Dashboard\n </button>\n </div>\n </PageLayout>\n );\n }\n\n // Primary CTA: download when ready, otherwise retry when retryable.\n const primaryAction: CTAPrimaryAction | undefined = firstDownloadablePart\n ? {\n label: \"Download\",\n icon: <Download size={16} />,\n onSelect: () => {\n if (firstDownloadablePart.downloadUrl) {\n window.location.assign(firstDownloadablePart.downloadUrl);\n }\n },\n }\n : isRetryable\n ? {\n label: \"Retry Export\",\n icon: <RotateCcw size={16} />,\n disabled: retrying,\n onSelect: () => {\n void handleRetry();\n },\n }\n : undefined;\n\n // Overflow actions: lifecycle controls + destructive delete.\n const overflowActions: CTAAction[] = [];\n if (isRetryable && firstDownloadablePart) {\n overflowActions.push({\n label: \"Retry Export\",\n icon: <RotateCcw size={16} />,\n disabled: retrying,\n onSelect: () => {\n void handleRetry();\n },\n });\n }\n if (isCancellable) {\n overflowActions.push({\n label: \"Cancel Export\",\n icon: <XCircle size={16} />,\n disabled: cancelling,\n onSelect: () => {\n void handleCancel();\n },\n });\n }\n if (!isCancellable) {\n overflowActions.push({\n label: \"Delete Export\",\n icon: <Trash2 size={16} />,\n intent: \"destructive\",\n disabled: deleting,\n onSelect: () => {\n void handleDelete();\n },\n });\n }\n\n return (\n <PageLayout showHeader={false}>\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-xl bg-accent-wash border border-border-seam p-6 shadow-[var(--shadow-pop)]\">\n <div className=\"relative z-10 flex flex-col sm:flex-row sm:items-center gap-4\">\n <button\n type=\"button\"\n onClick={handleBack}\n aria-label=\"Go back\"\n className=\"p-2.5 hover:bg-accent rounded-lg transition-colors duration-200 self-start focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\"\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex items-center gap-3 flex-1 min-w-0\">\n <div className=\"p-2.5 rounded-lg bg-action-primary-bg/10\">\n <Download size={24} className=\"text-primary\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <h1 className=\"text-xl sm:text-2xl font-bold truncate\">\n {liveJob.name}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {liveJob.entityTypes.join(\", \")}\n </p>\n </div>\n <ExportStatusBadge status={liveJob.status} />\n </div>\n {(primaryAction || overflowActions.length > 0) && (\n <div className=\"flex-shrink-0\">\n <CTAOverflowMenu\n primary={primaryAction}\n actions={overflowActions}\n />\n </div>\n )}\n </div>\n </div>\n\n {/* Progress (for active jobs) */}\n {(liveJob.status === \"RUNNING\" || liveJob.status === \"QUEUED\") && (\n <div className=\"border border-border-seam rounded-xl bg-status-info-bg-subtle p-6\">\n <ExportProgressBar progress={liveJob.progress} size=\"lg\" />\n {liveJob.processedEntities != null &&\n liveJob.totalEntities != null && (\n <p className=\"text-sm text-text-secondary mt-2\">\n {liveJob.processedEntities.toLocaleString()} /{\" \"}\n {liveJob.totalEntities.toLocaleString()} entities processed\n </p>\n )}\n </div>\n )}\n\n {/* Error (for failed jobs) */}\n {isFailed && liveJob.errorMessage && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-6 flex items-start gap-4\">\n <AlertCircle\n size={24}\n className=\"text-status-error-text flex-shrink-0 mt-0.5\"\n />\n <div>\n <h3 className=\"font-semibold text-status-error-text mb-1\">\n Export Failed\n </h3>\n <p className=\"text-sm text-status-error-text\">\n {liveJob.errorMessage}\n </p>\n </div>\n </div>\n )}\n\n {/* Info Grid */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n {[\n {\n icon: <FileText size={18} />,\n label: \"Format\",\n value: getFormatDisplayName(liveJob.format),\n },\n {\n icon: <HardDrive size={18} />,\n label: \"Archive\",\n value: getArchiveFormatDisplayName(liveJob.archiveFormat),\n },\n {\n icon: <Download size={18} />,\n label: \"Total Size\",\n value:\n liveJob.totalSizeBytes != null\n ? formatBytes(liveJob.totalSizeBytes)\n : \"-\",\n },\n {\n icon: <Clock size={18} />,\n label: \"Created\",\n value: new Date(liveJob.createdAt).toLocaleDateString(),\n },\n ].map((item) => (\n <GlassCard\n key={item.label}\n treatment=\"metric\"\n className=\"p-4 border-border-seam\"\n >\n <div className=\"flex items-center gap-2 text-text-secondary mb-1\">\n {item.icon}\n <span className=\"text-xs font-medium\">{item.label}</span>\n </div>\n <p className=\"font-bold text-lg tabular-nums\">{item.value}</p>\n </GlassCard>\n ))}\n </div>\n\n {/* Parts */}\n {liveJob.parts.length > 0 && (\n <PageSection\n title=\"Export Parts\"\n description={`${liveJob.parts.length} file(s) generated`}\n >\n <div className=\"space-y-2\">\n {liveJob.parts.map((part) => (\n <div\n key={part.id}\n className={cn(\n \"border border-border-seam rounded-xl p-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 bg-bg-surface\",\n \"hover:border-primary/40 transition-colors duration-200\",\n )}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <FileDown size={20} className=\"text-primary flex-shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"font-medium truncate\">{part.fileName}</p>\n <p className=\"text-sm text-text-secondary\">\n {part.entityTypes.join(\", \")} &middot;{\" \"}\n {formatBytes(part.sizeBytes)}\n {part.downloadExpiresAt &&\n ` · expires ${new Date(part.downloadExpiresAt).toLocaleDateString()}`}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n {part.downloadUrl ? (\n <a\n href={part.downloadUrl}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors text-sm font-medium\"\n >\n <Download size={14} />\n Download\n </a>\n ) : (\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Clock size={14} />\n Pending\n </span>\n )}\n </div>\n </div>\n ))}\n </div>\n </PageSection>\n )}\n\n {/* Completed message when no parts yet */}\n {isCompleted && liveJob.parts.length === 0 && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-success-bg-subtle p-6 text-center\">\n <CheckCircle2\n size={48}\n className=\"text-status-success-text mx-auto mb-3\"\n />\n <h3 className=\"text-lg font-bold text-status-success-text mb-1\">\n Export Complete\n </h3>\n <p className=\"text-sm text-status-success-text\">\n Your export has completed but no download parts are available yet.\n </p>\n </div>\n )}\n\n {pendingAction && (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"export-confirm-title\"\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-surface/80 backdrop-blur-sm p-4\"\n >\n <div className=\"w-full max-w-md rounded-xl border-2 border-border-subtle bg-bg-surface p-6 shadow-xl\">\n <h2 id=\"export-confirm-title\" className=\"text-lg font-bold\">\n {pendingAction === \"cancel\" ? \"Cancel export?\" : \"Delete export?\"}\n </h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {pendingAction === \"cancel\"\n ? \"This stops the running export job. You can retry it later if needed.\"\n : \"This removes the export job from history. This action cannot be undone.\"}\n </p>\n <div className=\"mt-6 flex flex-col-reverse sm:flex-row sm:justify-end gap-3\">\n <button\n type=\"button\"\n onClick={() => setPendingAction(null)}\n className=\"px-4 py-2 rounded-lg border-2 border-border-subtle hover:bg-accent transition-colors font-medium\"\n >\n Keep Export\n </button>\n <button\n type=\"button\"\n onClick={confirmPendingAction}\n disabled={cancelling || deleting}\n className=\"inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors font-medium disabled:opacity-50\"\n >\n {cancelling || deleting ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : null}\n {pendingAction === \"cancel\" ? \"Cancel Export\" : \"Delete Export\"}\n </button>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ExportDetailPage.js","names":[],"sources":["../../../src/export/pages/ExportDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, useNavigate } from \"react-router-dom\";\nimport {\n ArrowLeft,\n Download,\n XCircle,\n Trash2,\n RotateCcw,\n Loader2,\n FileDown,\n Clock,\n AlertCircle,\n CheckCircle2,\n HardDrive,\n FileText,\n} from \"lucide-react\";\nimport {\n GlassCard,\n CTAOverflowMenu,\n IllustratedEmptyState,\n NextSteps,\n type NextStep,\n} from \"@burdenoff/fe-libs/ui\";\nimport type { CTAAction, CTAPrimaryAction } from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport {\n useExportJob,\n useExportJobProgress,\n useCancelExportJob,\n useDeleteExportJob,\n useRetryExportJob,\n} from \"../hooks/useExportQueries\";\nimport { ExportStatusBadge } from \"../components/ExportStatusBadge\";\nimport { ExportProgressBar } from \"../components/ExportProgressBar\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n getFormatDisplayName,\n getArchiveFormatDisplayName,\n} from \"../constants/enums\";\nimport { cn } from \"../utils/cn\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\n/**\n * Format bytes to human-readable size\n */\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;\n}\n\n/**\n * Helper to join paths correctly\n */\nconst joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n return cleanBase + cleanPath;\n};\n\n/**\n * Export Detail Page\n * Single export job detail view with parts, progress, download links.\n * Uses GraphQL subscriptions for live progress updates when a job is active.\n */\nexport function ExportDetailPage() {\n const { jobId } = useParams<{ jobId: string }>();\n const { basePath = \"/\", navigate } = useExport();\n const routerNavigate = useNavigate();\n\n const handleBack = () => {\n if (window.history.length > 1) {\n routerNavigate(-1);\n } else {\n navigate?.(basePath);\n }\n };\n\n const { data: job, isLoading } = useExportJob(jobId);\n const { data: progressJob } = useExportJobProgress(jobId);\n const [pendingAction, setPendingAction] = useState<\n \"cancel\" | \"delete\" | null\n >(null);\n\n const { cancelJob, isPending: cancelling } = useCancelExportJob();\n const { deleteJob, isPending: deleting } = useDeleteExportJob();\n const { retryJob, isPending: retrying } = useRetryExportJob();\n\n const liveJob = progressJob ?? job ?? null;\n\n // Jobs in CREATED/QUEUED/RUNNING can still be cancelled\n const isCancellable =\n liveJob?.status === \"CREATED\" ||\n liveJob?.status === \"QUEUED\" ||\n liveJob?.status === \"RUNNING\";\n const isCompleted = liveJob?.status === \"COMPLETED\";\n const isFailed = liveJob?.status === \"FAILED\";\n const isRetryable =\n liveJob?.status === \"FAILED\" ||\n liveJob?.status === \"CANCELLED\" ||\n liveJob?.status === \"EXPIRED\";\n\n const handleCancel = async () => {\n if (!jobId) return;\n setPendingAction(\"cancel\");\n };\n\n const handleDelete = async () => {\n if (!jobId) return;\n nativeImpact(\"medium\");\n setPendingAction(\"delete\");\n };\n\n const confirmPendingAction = async () => {\n if (!jobId || !pendingAction) return;\n try {\n if (pendingAction === \"cancel\") {\n await cancelJob(jobId);\n } else {\n await deleteJob(jobId);\n navigate?.(basePath);\n }\n nativeNotify(\"success\");\n } catch {\n nativeNotify(\"error\");\n }\n setPendingAction(null);\n };\n\n const handleRetry = async () => {\n if (!jobId) return;\n const newJob = await retryJob(jobId);\n // Navigate to the new job created by retry\n if (newJob?.id) {\n navigate?.(joinPath(basePath, newJob.id));\n }\n };\n\n // Build a single primary CTA + overflow menu for the header. Download (the\n // first available part) is the primary action when the export is ready;\n // otherwise the most relevant lifecycle action leads. Destructive Delete\n // always lives in the overflow menu and is confirmed via the dialog.\n const firstDownloadablePart = liveJob?.parts.find((p) => p.downloadUrl);\n\n // Loading\n if (isLoading) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20\">\n <Loader2 size={48} className=\"animate-spin text-primary\" />\n <span className=\"mt-6 text-sm font-medium text-text-secondary\">\n Loading export details…\n </span>\n </div>\n );\n }\n\n // Not found\n if (!liveJob) {\n return (\n <PageLayout showHeader={false}>\n <div className=\"flex items-center gap-3 mb-6\">\n <button\n type=\"button\"\n onClick={handleBack}\n aria-label=\"Go back\"\n className=\"p-2.5 hover:bg-accent rounded-lg transition-all duration-200\"\n >\n <ArrowLeft size={20} />\n </button>\n <h1 className=\"text-xl font-bold\">Export Not Found</h1>\n </div>\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"Export not found\"\n description=\"The export job you are looking for does not exist or has been deleted.\"\n action={\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"inline-flex items-center gap-2 px-6 py-3 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <ArrowLeft size={18} />\n Back to Dashboard\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n // Primary CTA: download when ready, otherwise retry when retryable.\n const primaryAction: CTAPrimaryAction | undefined = firstDownloadablePart\n ? {\n label: \"Download\",\n icon: <Download size={16} />,\n onSelect: () => {\n if (firstDownloadablePart.downloadUrl) {\n window.location.assign(firstDownloadablePart.downloadUrl);\n }\n },\n }\n : isRetryable\n ? {\n label: \"Retry Export\",\n icon: <RotateCcw size={16} />,\n disabled: retrying,\n onSelect: () => {\n void handleRetry();\n },\n }\n : undefined;\n\n // Overflow actions: lifecycle controls + destructive delete.\n const overflowActions: CTAAction[] = [];\n if (isRetryable && firstDownloadablePart) {\n overflowActions.push({\n label: \"Retry Export\",\n icon: <RotateCcw size={16} />,\n disabled: retrying,\n onSelect: () => {\n void handleRetry();\n },\n });\n }\n if (isCancellable) {\n overflowActions.push({\n label: \"Cancel Export\",\n icon: <XCircle size={16} />,\n disabled: cancelling,\n onSelect: () => {\n void handleCancel();\n },\n });\n }\n if (!isCancellable) {\n overflowActions.push({\n label: \"Delete Export\",\n icon: <Trash2 size={16} />,\n intent: \"destructive\",\n disabled: deleting,\n onSelect: () => {\n void handleDelete();\n },\n });\n }\n\n // Next-step guidance — concrete actions wired to real handlers/links, tuned\n // to the job's current state so a new user knows what to do from here.\n const nextSteps: NextStep[] = [\n {\n id: \"download\",\n label: \"Download your export\",\n description:\n \"Grab the generated files below before their download links expire.\",\n icon: <Download size={16} />,\n onClick: () => {\n if (firstDownloadablePart?.downloadUrl) {\n window.location.assign(firstDownloadablePart.downloadUrl);\n }\n },\n done: isCompleted,\n },\n ...(isRetryable\n ? [\n {\n id: \"retry\",\n label: \"Retry this export\",\n description:\n \"This export didn't finish — start a fresh run with the same settings.\",\n icon: <RotateCcw size={16} />,\n onClick: () => {\n void handleRetry();\n },\n } as NextStep,\n ]\n : []),\n {\n id: \"history\",\n label: \"Back to History\",\n description: \"See all your exports and track other jobs in progress.\",\n icon: <Clock size={16} />,\n onClick: () => navigate?.(basePath),\n },\n ];\n\n return (\n <PageLayout showHeader={false}>\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-xl bg-accent-wash border border-border-seam p-6 shadow-[var(--shadow-pop)]\">\n <div className=\"relative z-10 flex flex-col sm:flex-row sm:items-center gap-4\">\n <button\n type=\"button\"\n onClick={handleBack}\n aria-label=\"Go back\"\n className=\"p-2.5 hover:bg-accent rounded-lg transition-colors duration-200 self-start focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\"\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex items-center gap-3 flex-1 min-w-0\">\n <div className=\"p-2.5 rounded-lg bg-action-primary-bg/10\">\n <Download size={24} className=\"text-primary\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <h1 className=\"text-xl sm:text-2xl font-bold truncate\">\n {liveJob.name}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {liveJob.entityTypes.join(\", \")}\n </p>\n </div>\n <ExportStatusBadge status={liveJob.status} />\n </div>\n {(primaryAction || overflowActions.length > 0) && (\n <div className=\"flex-shrink-0\">\n <CTAOverflowMenu\n primary={primaryAction}\n actions={overflowActions}\n />\n </div>\n )}\n </div>\n </div>\n\n {/* Progress (for active jobs) */}\n {(liveJob.status === \"RUNNING\" || liveJob.status === \"QUEUED\") && (\n <div className=\"border border-border-seam rounded-xl bg-status-info-bg-subtle p-6\">\n <ExportProgressBar progress={liveJob.progress} size=\"lg\" />\n {liveJob.processedEntities != null &&\n liveJob.totalEntities != null && (\n <p className=\"text-sm text-text-secondary mt-2\">\n {liveJob.processedEntities.toLocaleString()} /{\" \"}\n {liveJob.totalEntities.toLocaleString()} entities processed\n </p>\n )}\n </div>\n )}\n\n {/* Error (for failed jobs) */}\n {isFailed && liveJob.errorMessage && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-6 flex items-start gap-4\">\n <AlertCircle\n size={24}\n className=\"text-status-error-text flex-shrink-0 mt-0.5\"\n />\n <div>\n <h3 className=\"font-semibold text-status-error-text mb-1\">\n Export Failed\n </h3>\n <p className=\"text-sm text-status-error-text\">\n {liveJob.errorMessage}\n </p>\n </div>\n </div>\n )}\n\n {/* Info Grid */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n {[\n {\n icon: <FileText size={18} />,\n label: \"Format\",\n value: getFormatDisplayName(liveJob.format),\n },\n {\n icon: <HardDrive size={18} />,\n label: \"Archive\",\n value: getArchiveFormatDisplayName(liveJob.archiveFormat),\n },\n {\n icon: <Download size={18} />,\n label: \"Total Size\",\n value:\n liveJob.totalSizeBytes != null\n ? formatBytes(liveJob.totalSizeBytes)\n : \"-\",\n },\n {\n icon: <Clock size={18} />,\n label: \"Created\",\n value: new Date(liveJob.createdAt).toLocaleDateString(),\n },\n ].map((item) => (\n <GlassCard\n key={item.label}\n treatment=\"metric\"\n className=\"p-4 border-border-seam\"\n >\n <div className=\"flex items-center gap-2 text-text-secondary mb-1\">\n {item.icon}\n <span className=\"text-xs font-medium\">{item.label}</span>\n </div>\n <p className=\"font-bold text-lg tabular-nums\">{item.value}</p>\n </GlassCard>\n ))}\n </div>\n\n {/* Next-step guidance for this export (dismissable, per-job) */}\n <NextSteps\n storageKey={`export-detail-${liveJob.id}`}\n title=\"What's next\"\n steps={nextSteps}\n />\n\n {/* Parts */}\n {liveJob.parts.length > 0 && (\n <PageSection\n title=\"Export Parts\"\n description={`${liveJob.parts.length} file(s) generated`}\n >\n <div className=\"space-y-2\">\n {liveJob.parts.map((part) => (\n <div\n key={part.id}\n className={cn(\n \"border border-border-seam rounded-xl p-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 bg-bg-surface\",\n \"hover:border-primary/40 transition-colors duration-200\",\n )}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <FileDown size={20} className=\"text-primary flex-shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"font-medium truncate\">{part.fileName}</p>\n <p className=\"text-sm text-text-secondary\">\n {part.entityTypes.join(\", \")} &middot;{\" \"}\n {formatBytes(part.sizeBytes)}\n {part.downloadExpiresAt &&\n ` · expires ${new Date(part.downloadExpiresAt).toLocaleDateString()}`}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n {part.downloadUrl ? (\n <a\n href={part.downloadUrl}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors text-sm font-medium\"\n >\n <Download size={14} />\n Download\n </a>\n ) : (\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Clock size={14} />\n Pending\n </span>\n )}\n </div>\n </div>\n ))}\n </div>\n </PageSection>\n )}\n\n {/* Completed message when no parts yet */}\n {isCompleted && liveJob.parts.length === 0 && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-success-bg-subtle p-6 text-center\">\n <CheckCircle2\n size={48}\n className=\"text-status-success-text mx-auto mb-3\"\n />\n <h3 className=\"text-lg font-bold text-status-success-text mb-1\">\n Export Complete\n </h3>\n <p className=\"text-sm text-status-success-text\">\n Your export has completed but no download parts are available yet.\n </p>\n </div>\n )}\n\n {pendingAction && (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"export-confirm-title\"\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-surface/80 backdrop-blur-sm p-4\"\n >\n <div className=\"w-full max-w-md rounded-xl border-2 border-border-subtle bg-bg-surface p-6 shadow-xl\">\n <h2 id=\"export-confirm-title\" className=\"text-lg font-bold\">\n {pendingAction === \"cancel\" ? \"Cancel export?\" : \"Delete export?\"}\n </h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {pendingAction === \"cancel\"\n ? \"This stops the running export job. You can retry it later if needed.\"\n : \"This removes the export job from history. This action cannot be undone.\"}\n </p>\n <div className=\"mt-6 flex flex-col-reverse sm:flex-row sm:justify-end gap-3\">\n <button\n type=\"button\"\n onClick={() => setPendingAction(null)}\n className=\"px-4 py-2 rounded-lg border-2 border-border-subtle hover:bg-accent transition-colors font-medium\"\n >\n Keep Export\n </button>\n <button\n type=\"button\"\n onClick={confirmPendingAction}\n disabled={cancelling || deleting}\n className=\"inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors font-medium disabled:opacity-50\"\n >\n {cancelling || deleting ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : null}\n {pendingAction === \"cancel\" ? \"Cancel Export\" : \"Delete Export\"}\n </button>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,138 +1,143 @@
1
1
  import { useExport as e } from "../providers/ExportProvider.js";
2
2
  import { useExportJobs as t } from "../hooks/useExportQueries.js";
3
3
  import { cn as n } from "../utils/cn.js";
4
- import { getAllStatuses as r, getFormatDisplayName as ee, getStatusDisplayName as i } from "../constants/enums.js";
5
- import { tWithFallback as a } from "../utils/i18n.js";
6
- import { ExportStatusBadge as o } from "../components/ExportStatusBadge.js";
7
- import { EmptyState as s, PageLayout as c } from "../components/PageLayout.js";
8
- import { useExportStore as l } from "../store/exportStore.js";
9
- import { useEffect as u, useMemo as d, useRef as f, useState as p } from "react";
10
- import { AlertCircle as te, ChevronLeft as ne, ChevronRight as m, Clock as h, Download as g, Filter as _, RefreshCw as v, Search as y, X as b } from "lucide-react";
11
- import { useSearchParams as x } from "react-router-dom";
12
- import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
13
- import { useI18n as T } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
+ import { getAllStatuses as r, getFormatDisplayName as i, getStatusDisplayName as a } from "../constants/enums.js";
5
+ import { tWithFallback as o } from "../utils/i18n.js";
6
+ import { ExportStatusBadge as s } from "../components/ExportStatusBadge.js";
7
+ import { PageLayout as c } from "../components/PageLayout.js";
8
+ import { useExportStore as ee } from "../store/exportStore.js";
9
+ import { useEffect as te, useMemo as l, useRef as u, useState as d } from "react";
10
+ import { AlertCircle as f, ChevronLeft as p, ChevronRight as m, Clock as h, Download as g, Filter as _, RefreshCw as v, Search as ne, X as y } from "lucide-react";
11
+ import { useSearchParams as b } from "react-router-dom";
12
+ import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
13
+ import { useI18n as w } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
14
+ import { IllustratedEmptyState as T, PagePurpose as E } from "@burdenoff/fe-libs/ui";
14
15
  //#region src/export/pages/ExportHistoryPage.tsx
15
- var E = (e, t) => (e.endsWith("/") ? e.slice(0, -1) : e) + (t.startsWith("/") ? t : `/${t}`);
16
- function D() {
17
- let { basePath: D = "/", navigate: O } = e(), { t: k } = T(), A = (e, t, n) => a(k, e, t, n), { jobFilter: j, updateFilter: M, resetFilter: N, pagination: P, setPagination: F } = l(), [I] = x(), L = f(!1);
18
- u(() => {
19
- if (L.current) return;
20
- L.current = !0;
21
- let e = I.get("status");
22
- e && M({ status: e });
23
- }, [I, M]);
24
- let [R, z] = p(!1), [B, V] = p([null]), [H, U] = p(0), W = B[H] ?? null, { data: G, totalCount: K, pageInfo: q, isLoading: J, error: Y, refetch: X } = t(j, {
25
- first: P.pageSize,
26
- after: W ?? void 0
27
- }), Z = d(() => {
28
- if (!j.search) return G;
29
- let e = j.search.toLowerCase();
30
- return G.filter((t) => t.name.toLowerCase().includes(e) || t.entityTypes.some((t) => t.toLowerCase().includes(e)));
31
- }, [G, j.search]), Q = j.search || j.status !== "all" || j.format !== "all", re = (e) => {
32
- O?.(E(D, e));
33
- }, ie = () => {
34
- !q?.hasNextPage || !q.endCursor || (V([...B.slice(0, H + 1), q.endCursor]), U((e) => e + 1), F({
35
- page: P.page + 1,
36
- cursor: q.endCursor
37
- }));
16
+ var D = (e, t) => (e.endsWith("/") ? e.slice(0, -1) : e) + (t.startsWith("/") ? t : `/${t}`);
17
+ function O() {
18
+ let { basePath: O = "/", navigate: k } = e(), { t: A } = w(), j = (e, t, n) => o(A, e, t, n), { jobFilter: M, updateFilter: N, resetFilter: P, pagination: F, setPagination: I } = ee(), [L] = b(), R = u(!1);
19
+ te(() => {
20
+ if (R.current) return;
21
+ R.current = !0;
22
+ let e = L.get("status");
23
+ e && N({ status: e });
24
+ }, [L, N]);
25
+ let [z, B] = d(!1), [V, H] = d([null]), [U, W] = d(0), G = V[U] ?? null, { data: K, totalCount: q, pageInfo: J, isLoading: Y, error: X, refetch: re } = t(M, {
26
+ first: F.pageSize,
27
+ after: G ?? void 0
28
+ }), Z = l(() => {
29
+ if (!M.search) return K;
30
+ let e = M.search.toLowerCase();
31
+ return K.filter((t) => t.name.toLowerCase().includes(e) || t.entityTypes.some((t) => t.toLowerCase().includes(e)));
32
+ }, [K, M.search]), Q = M.search || M.status !== "all" || M.format !== "all", ie = (e) => {
33
+ k?.(D(O, e));
38
34
  }, ae = () => {
39
- if (H === 0) return;
40
- let e = H - 1;
41
- U(e), F({
42
- page: P.page - 1,
43
- cursor: B[e] ?? null
35
+ !J?.hasNextPage || !J.endCursor || (H([...V.slice(0, U + 1), J.endCursor]), W((e) => e + 1), I({
36
+ page: F.page + 1,
37
+ cursor: J.endCursor
38
+ }));
39
+ }, oe = () => {
40
+ if (U === 0) return;
41
+ let e = U - 1;
42
+ W(e), I({
43
+ page: F.page - 1,
44
+ cursor: V[e] ?? null
44
45
  });
45
46
  }, $ = () => {
46
- N(), V([null]), U(0);
47
- }, oe = H > 0, se = q?.hasNextPage ?? !1, ce = H * P.pageSize + 1, le = Math.min((H + 1) * P.pageSize, K), ue = (e) => A(`export.shared.status.${e.toLowerCase()}`, i(e));
48
- return /* @__PURE__ */ w(c, {
49
- title: A("export.history.title", "Export History"),
50
- subtitle: A("export.history.subtitle", "View and manage all your past export jobs"),
51
- icon: /* @__PURE__ */ C(h, { size: 24 }),
52
- headerActions: /* @__PURE__ */ w("button", {
47
+ P(), H([null]), W(0);
48
+ }, se = U > 0, ce = J?.hasNextPage ?? !1, le = U * F.pageSize + 1, ue = Math.min((U + 1) * F.pageSize, q), de = (e) => j(`export.shared.status.${e.toLowerCase()}`, a(e));
49
+ return /* @__PURE__ */ C(c, {
50
+ title: j("export.history.title", "Export History"),
51
+ subtitle: j("export.history.subtitle", "View and manage all your past export jobs"),
52
+ icon: /* @__PURE__ */ S(h, { size: 24 }),
53
+ headerActions: /* @__PURE__ */ C("button", {
53
54
  type: "button",
54
- onClick: () => O?.(E(D, "new")),
55
+ onClick: () => k?.(D(O, "new")),
55
56
  className: "inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium",
56
- children: [/* @__PURE__ */ C(g, { size: 16 }), A("export.history.actions.newExport", "New Export")]
57
+ children: [/* @__PURE__ */ S(g, { size: 16 }), j("export.history.actions.newExport", "New Export")]
57
58
  }),
58
59
  children: [
59
- /* @__PURE__ */ w("div", {
60
+ /* @__PURE__ */ S(E, {
61
+ icon: /* @__PURE__ */ S(h, { size: 16 }),
62
+ children: j("export.history.purpose", "Every export you've ever started lives here. Track jobs as they run, see which finished or failed, and grab download links for completed exports before they expire. Search by name or entity type, or filter by status and format to find a specific one.")
63
+ }),
64
+ /* @__PURE__ */ C("div", {
60
65
  className: "space-y-4",
61
- children: [/* @__PURE__ */ w("div", {
66
+ children: [/* @__PURE__ */ C("div", {
62
67
  className: "flex flex-col sm:flex-row gap-3",
63
- children: [/* @__PURE__ */ w("div", {
68
+ children: [/* @__PURE__ */ C("div", {
64
69
  className: "flex-1 relative group",
65
- children: [/* @__PURE__ */ C(y, {
70
+ children: [/* @__PURE__ */ S(ne, {
66
71
  size: 18,
67
72
  className: "absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary group-focus-within:text-primary transition-colors"
68
- }), /* @__PURE__ */ C("input", {
73
+ }), /* @__PURE__ */ S("input", {
69
74
  type: "text",
70
- "aria-label": A("export.history.search.placeholder", "Search exports by name or entity type"),
71
- placeholder: A("export.history.search.placeholder", "Search exports by name or entity type..."),
72
- value: j.search,
73
- onChange: (e) => M({ search: e.target.value }),
75
+ "aria-label": j("export.history.search.placeholder", "Search exports by name or entity type"),
76
+ placeholder: j("export.history.search.placeholder", "Search exports by name or entity type..."),
77
+ value: M.search,
78
+ onChange: (e) => N({ search: e.target.value }),
74
79
  className: "w-full pl-[3.25rem] pr-4 py-2.5 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all duration-200"
75
80
  })]
76
- }), /* @__PURE__ */ w("button", {
81
+ }), /* @__PURE__ */ C("button", {
77
82
  type: "button",
78
- onClick: () => z(!R),
79
- className: n("inline-flex items-center gap-2 px-4 py-2.5 border-2 rounded-lg transition-all duration-200 font-medium", R || Q ? "bg-action-primary-bg text-action-primary-text border-primary" : "border-border-subtle/50 hover:border-primary/30"),
80
- children: [/* @__PURE__ */ C(_, { size: 16 }), R ? A("export.history.filters.hide", "Hide") : A("export.history.filters.show", "Filter")]
83
+ onClick: () => B(!z),
84
+ className: n("inline-flex items-center gap-2 px-4 py-2.5 border-2 rounded-lg transition-all duration-200 font-medium", z || Q ? "bg-action-primary-bg text-action-primary-text border-primary" : "border-border-subtle/50 hover:border-primary/30"),
85
+ children: [/* @__PURE__ */ S(_, { size: 16 }), z ? j("export.history.filters.hide", "Hide") : j("export.history.filters.show", "Filter")]
81
86
  })]
82
- }), R && /* @__PURE__ */ w("div", {
87
+ }), z && /* @__PURE__ */ C("div", {
83
88
  className: "border border-border-seam rounded-xl p-4 animate-in fade-in-0 duration-200",
84
- children: [/* @__PURE__ */ w("div", {
89
+ children: [/* @__PURE__ */ C("div", {
85
90
  className: "flex items-center justify-between mb-4",
86
- children: [/* @__PURE__ */ w("h3", {
91
+ children: [/* @__PURE__ */ C("h3", {
87
92
  className: "font-semibold flex items-center gap-2",
88
- children: [/* @__PURE__ */ C(_, {
93
+ children: [/* @__PURE__ */ S(_, {
89
94
  size: 16,
90
95
  className: "text-primary"
91
- }), A("export.history.filters.title", "Filters")]
92
- }), Q && /* @__PURE__ */ w("button", {
96
+ }), j("export.history.filters.title", "Filters")]
97
+ }), Q && /* @__PURE__ */ C("button", {
93
98
  type: "button",
94
99
  onClick: $,
95
100
  className: "text-sm text-text-secondary hover:text-status-error-text flex items-center gap-1",
96
- children: [/* @__PURE__ */ C(b, { size: 14 }), A("export.history.filters.clearAll", "Clear All")]
101
+ children: [/* @__PURE__ */ S(y, { size: 14 }), j("export.history.filters.clearAll", "Clear All")]
97
102
  })]
98
- }), /* @__PURE__ */ w("div", {
103
+ }), /* @__PURE__ */ C("div", {
99
104
  className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
100
- children: [/* @__PURE__ */ w("div", {
105
+ children: [/* @__PURE__ */ C("div", {
101
106
  className: "space-y-2",
102
- children: [/* @__PURE__ */ C("label", {
107
+ children: [/* @__PURE__ */ S("label", {
103
108
  className: "block text-sm font-medium",
104
- children: A("export.history.filters.status.label", "Status")
105
- }), /* @__PURE__ */ w("select", {
106
- value: j.status,
107
- onChange: (e) => M({ status: e.target.value }),
109
+ children: j("export.history.filters.status.label", "Status")
110
+ }), /* @__PURE__ */ C("select", {
111
+ value: M.status,
112
+ onChange: (e) => N({ status: e.target.value }),
108
113
  className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary",
109
- children: [/* @__PURE__ */ C("option", {
114
+ children: [/* @__PURE__ */ S("option", {
110
115
  value: "all",
111
- children: A("export.history.filters.status.all", "All Statuses")
112
- }), r().map((e) => /* @__PURE__ */ C("option", {
116
+ children: j("export.history.filters.status.all", "All Statuses")
117
+ }), r().map((e) => /* @__PURE__ */ S("option", {
113
118
  value: e,
114
- children: ue(e)
119
+ children: de(e)
115
120
  }, e))]
116
121
  })]
117
- }), /* @__PURE__ */ w("div", {
122
+ }), /* @__PURE__ */ C("div", {
118
123
  className: "space-y-2",
119
- children: [/* @__PURE__ */ C("label", {
124
+ children: [/* @__PURE__ */ S("label", {
120
125
  className: "block text-sm font-medium",
121
- children: A("export.history.filters.format.label", "Format")
122
- }), /* @__PURE__ */ w("select", {
123
- value: j.format,
124
- onChange: (e) => M({ format: e.target.value }),
126
+ children: j("export.history.filters.format.label", "Format")
127
+ }), /* @__PURE__ */ C("select", {
128
+ value: M.format,
129
+ onChange: (e) => N({ format: e.target.value }),
125
130
  className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary",
126
131
  children: [
127
- /* @__PURE__ */ C("option", {
132
+ /* @__PURE__ */ S("option", {
128
133
  value: "all",
129
- children: A("export.history.filters.format.all", "All Formats")
134
+ children: j("export.history.filters.format.all", "All Formats")
130
135
  }),
131
- /* @__PURE__ */ C("option", {
136
+ /* @__PURE__ */ S("option", {
132
137
  value: "CSV",
133
138
  children: "CSV"
134
139
  }),
135
- /* @__PURE__ */ C("option", {
140
+ /* @__PURE__ */ S("option", {
136
141
  value: "JSON",
137
142
  children: "JSON"
138
143
  })
@@ -142,29 +147,29 @@ function D() {
142
147
  })]
143
148
  })]
144
149
  }),
145
- Y && /* @__PURE__ */ w("div", {
150
+ X && /* @__PURE__ */ C("div", {
146
151
  className: "border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-6 flex items-start gap-4",
147
- children: [/* @__PURE__ */ C(te, {
152
+ children: [/* @__PURE__ */ S(f, {
148
153
  size: 24,
149
154
  className: "text-status-error-text flex-shrink-0"
150
- }), /* @__PURE__ */ w("div", { children: [
151
- /* @__PURE__ */ C("h3", {
155
+ }), /* @__PURE__ */ C("div", { children: [
156
+ /* @__PURE__ */ S("h3", {
152
157
  className: "font-semibold text-status-error-text mb-1",
153
- children: A("export.history.error.title", "Error loading exports")
158
+ children: j("export.history.error.title", "Error loading exports")
154
159
  }),
155
- /* @__PURE__ */ C("p", {
160
+ /* @__PURE__ */ S("p", {
156
161
  className: "text-sm text-status-error-text mb-3",
157
- children: Y.message
162
+ children: X.message
158
163
  }),
159
- /* @__PURE__ */ w("button", {
164
+ /* @__PURE__ */ C("button", {
160
165
  type: "button",
161
- onClick: () => X(),
166
+ onClick: () => re(),
162
167
  className: "inline-flex items-center gap-2 px-3 py-1.5 text-sm bg-status-error-bg text-status-error-text rounded-lg hover:bg-status-error-bg/90 transition-colors",
163
- children: [/* @__PURE__ */ C(v, { size: 14 }), A("export.history.error.retry", "Try Again")]
168
+ children: [/* @__PURE__ */ S(v, { size: 14 }), j("export.history.error.retry", "Try Again")]
164
169
  })
165
170
  ] })]
166
171
  }),
167
- J && /* @__PURE__ */ C("div", {
172
+ Y && /* @__PURE__ */ S("div", {
168
173
  className: "space-y-3",
169
174
  children: [...[
170
175
  ,
@@ -172,102 +177,102 @@ function D() {
172
177
  ,
173
178
  ,
174
179
  ,
175
- ]].map((e, t) => /* @__PURE__ */ C("div", {
176
- className: "border border-border-seam rounded-xl p-4 animate-pulse",
177
- children: /* @__PURE__ */ w("div", {
180
+ ]].map((e, t) => /* @__PURE__ */ S("div", {
181
+ className: "border border-border-seam rounded-xl p-4",
182
+ children: /* @__PURE__ */ C("div", {
178
183
  className: "flex items-center justify-between",
179
- children: [/* @__PURE__ */ w("div", {
184
+ children: [/* @__PURE__ */ C("div", {
180
185
  className: "space-y-2",
181
- children: [/* @__PURE__ */ C("div", { className: "h-4 w-48 bg-bg-sunken rounded" }), /* @__PURE__ */ C("div", { className: "h-3 w-32 bg-bg-sunken rounded" })]
182
- }), /* @__PURE__ */ C("div", { className: "h-5 w-20 bg-bg-sunken rounded-full" })]
186
+ children: [/* @__PURE__ */ S("div", { className: "h-4 w-48 bg-bg-sunken rounded animate-vc-shimmer" }), /* @__PURE__ */ S("div", { className: "h-3 w-32 bg-bg-sunken rounded animate-vc-shimmer" })]
187
+ }), /* @__PURE__ */ S("div", { className: "h-5 w-20 bg-bg-sunken rounded-full animate-vc-shimmer" })]
183
188
  })
184
189
  }, t))
185
190
  }),
186
- !J && !Y && (Z.length > 0 ? /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C("div", {
191
+ !Y && !X && (Z.length > 0 ? /* @__PURE__ */ C(x, { children: [/* @__PURE__ */ S("div", {
187
192
  className: "space-y-2",
188
- children: Z.map((e, t) => /* @__PURE__ */ C("button", {
193
+ children: Z.map((e, t) => /* @__PURE__ */ S("button", {
189
194
  type: "button",
190
- onClick: () => re(e.id),
195
+ onClick: () => ie(e.id),
191
196
  className: n("w-full text-left border border-border-seam rounded-xl p-4 bg-bg-surface", "hover:border-primary/40 transition-colors duration-200", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40", "group animate-in fade-in-0"),
192
197
  style: { animationDelay: `${t * 30}ms` },
193
- children: /* @__PURE__ */ w("div", {
198
+ children: /* @__PURE__ */ C("div", {
194
199
  className: "flex items-start sm:items-center justify-between gap-3",
195
- children: [/* @__PURE__ */ w("div", {
200
+ children: [/* @__PURE__ */ C("div", {
196
201
  className: "flex-1 min-w-0",
197
- children: [/* @__PURE__ */ C("h4", {
202
+ children: [/* @__PURE__ */ S("h4", {
198
203
  className: "font-semibold text-text-primary truncate",
199
204
  children: e.name
200
- }), /* @__PURE__ */ w("p", {
205
+ }), /* @__PURE__ */ C("p", {
201
206
  className: "text-sm text-text-secondary mt-1 line-clamp-2",
202
207
  children: [
203
208
  e.entityTypes.join(", "),
204
209
  " ·",
205
210
  " ",
206
- ee(e.format),
211
+ i(e.format),
207
212
  e.createdAt && ` · ${new Date(e.createdAt).toLocaleDateString()}`
208
213
  ]
209
214
  })]
210
- }), /* @__PURE__ */ w("div", {
215
+ }), /* @__PURE__ */ C("div", {
211
216
  className: "flex items-center gap-2 flex-shrink-0",
212
- children: [/* @__PURE__ */ C(o, { status: e.status }), /* @__PURE__ */ C(m, {
217
+ children: [/* @__PURE__ */ S(s, { status: e.status }), /* @__PURE__ */ S(m, {
213
218
  size: 18,
214
219
  className: "text-text-secondary group-hover:text-primary transition-colors hidden sm:block"
215
220
  })]
216
221
  })]
217
222
  })
218
223
  }, e.id))
219
- }), K > P.pageSize && /* @__PURE__ */ w("div", {
224
+ }), q > F.pageSize && /* @__PURE__ */ C("div", {
220
225
  className: "flex flex-col sm:flex-row items-center justify-between gap-3 pt-2",
221
- children: [/* @__PURE__ */ C("p", {
226
+ children: [/* @__PURE__ */ S("p", {
222
227
  className: "text-sm text-text-secondary",
223
- children: A("export.history.pagination.showing", "Showing {start}-{end} of {total}", {
224
- start: ce,
225
- end: le,
226
- total: K
228
+ children: j("export.history.pagination.showing", "Showing {start}-{end} of {total}", {
229
+ start: le,
230
+ end: ue,
231
+ total: q
227
232
  })
228
- }), /* @__PURE__ */ w("div", {
233
+ }), /* @__PURE__ */ C("div", {
229
234
  className: "flex items-center gap-2",
230
235
  children: [
231
- /* @__PURE__ */ w("button", {
236
+ /* @__PURE__ */ C("button", {
232
237
  type: "button",
233
- onClick: ae,
234
- disabled: !oe,
238
+ onClick: oe,
239
+ disabled: !se,
235
240
  className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm border-2 border-border-subtle/50 rounded-lg hover:border-primary/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium",
236
- children: [/* @__PURE__ */ C(ne, { size: 16 }), A("export.history.pagination.prev", "Prev")]
241
+ children: [/* @__PURE__ */ S(p, { size: 16 }), j("export.history.pagination.prev", "Prev")]
237
242
  }),
238
- /* @__PURE__ */ C("span", {
243
+ /* @__PURE__ */ S("span", {
239
244
  className: "text-sm text-text-secondary px-2",
240
- children: A("export.history.pagination.page", "Page {page}", { page: P.page })
245
+ children: j("export.history.pagination.page", "Page {page}", { page: F.page })
241
246
  }),
242
- /* @__PURE__ */ w("button", {
247
+ /* @__PURE__ */ C("button", {
243
248
  type: "button",
244
- onClick: ie,
245
- disabled: !se,
249
+ onClick: ae,
250
+ disabled: !ce,
246
251
  className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm border-2 border-border-subtle/50 rounded-lg hover:border-primary/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium",
247
- children: [A("export.history.pagination.next", "Next"), /* @__PURE__ */ C(m, { size: 16 })]
252
+ children: [j("export.history.pagination.next", "Next"), /* @__PURE__ */ S(m, { size: 16 })]
248
253
  })
249
254
  ]
250
255
  })]
251
- })] }) : /* @__PURE__ */ C(s, {
252
- icon: C(Q ? y : h, { size: 48 }),
253
- title: Q ? A("export.history.empty.filtered.title", "No matching exports") : A("export.history.empty.title", "No exports yet"),
254
- description: Q ? A("export.history.empty.filtered.description", "Try adjusting your search or filters.") : A("export.history.empty.description", "Create your first export to see it here."),
255
- action: Q ? /* @__PURE__ */ w("button", {
256
+ })] }) : /* @__PURE__ */ S(T, {
257
+ illustration: Q ? "empty-search" : "empty-data",
258
+ title: Q ? j("export.history.empty.filtered.title", "No matching exports") : j("export.history.empty.title", "No exports yet"),
259
+ description: Q ? j("export.history.empty.filtered.description", "Try adjusting your search or filters.") : j("export.history.empty.description", "Create your first export to see it here."),
260
+ action: Q ? /* @__PURE__ */ C("button", {
256
261
  type: "button",
257
262
  onClick: $,
258
263
  className: "inline-flex items-center gap-2 px-4 py-2 border-2 border-border-subtle rounded-lg hover:bg-accent transition-colors font-medium",
259
- children: [/* @__PURE__ */ C(b, { size: 16 }), A("export.history.empty.filtered.actions.clear", "Clear Filters")]
260
- }) : /* @__PURE__ */ w("button", {
264
+ children: [/* @__PURE__ */ S(y, { size: 16 }), j("export.history.empty.filtered.actions.clear", "Clear Filters")]
265
+ }) : /* @__PURE__ */ C("button", {
261
266
  type: "button",
262
- onClick: () => O?.(E(D, "new")),
267
+ onClick: () => k?.(D(O, "new")),
263
268
  className: "inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium",
264
- children: [/* @__PURE__ */ C(g, { size: 16 }), A("export.history.empty.actions.createFirst", "Create First Export")]
269
+ children: [/* @__PURE__ */ S(g, { size: 16 }), j("export.history.empty.actions.createFirst", "Create First Export")]
265
270
  })
266
271
  }))
267
272
  ]
268
273
  });
269
274
  }
270
275
  //#endregion
271
- export { D as ExportHistoryPage };
276
+ export { O as ExportHistoryPage };
272
277
 
273
278
  //# sourceMappingURL=ExportHistoryPage.js.map