@burdenoff/microfe-export 2026.830.1 → 2026.830.2

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.
Files changed (51) hide show
  1. package/dist/export/ExportRoot.d.ts.map +1 -1
  2. package/dist/export/ExportRoot.js +41 -36
  3. package/dist/export/ExportRoot.js.map +1 -1
  4. package/dist/export/ExportRoutes.d.ts.map +1 -1
  5. package/dist/export/ExportRoutes.js +75 -66
  6. package/dist/export/ExportRoutes.js.map +1 -1
  7. package/dist/export/components/ExportProgressBar.d.ts.map +1 -1
  8. package/dist/export/components/ExportProgressBar.js +19 -18
  9. package/dist/export/components/ExportProgressBar.js.map +1 -1
  10. package/dist/export/components/ExportStatusBadge.d.ts.map +1 -1
  11. package/dist/export/components/ExportStatusBadge.js +19 -20
  12. package/dist/export/components/ExportStatusBadge.js.map +1 -1
  13. package/dist/export/hooks/useTr.d.ts +19 -0
  14. package/dist/export/hooks/useTr.d.ts.map +1 -0
  15. package/dist/export/hooks/useTr.js +15 -0
  16. package/dist/export/hooks/useTr.js.map +1 -0
  17. package/dist/export/index.d.ts +1 -0
  18. package/dist/export/index.d.ts.map +1 -1
  19. package/dist/export/index.js +1 -0
  20. package/dist/export/pages/CreateExportPage.d.ts +4 -0
  21. package/dist/export/pages/CreateExportPage.d.ts.map +1 -1
  22. package/dist/export/pages/CreateExportPage.js +271 -283
  23. package/dist/export/pages/CreateExportPage.js.map +1 -1
  24. package/dist/export/pages/ExportDashboardPage.d.ts.map +1 -1
  25. package/dist/export/pages/ExportDashboardPage.js +71 -72
  26. package/dist/export/pages/ExportDashboardPage.js.map +1 -1
  27. package/dist/export/pages/ExportDetailPage.d.ts.map +1 -1
  28. package/dist/export/pages/ExportDetailPage.js +171 -173
  29. package/dist/export/pages/ExportDetailPage.js.map +1 -1
  30. package/dist/export/pages/ExportHistoryPage.d.ts.map +1 -1
  31. package/dist/export/pages/ExportHistoryPage.js +110 -111
  32. package/dist/export/pages/ExportHistoryPage.js.map +1 -1
  33. package/dist/export/pages/ExportLandingPage.d.ts.map +1 -1
  34. package/dist/export/pages/ExportLandingPage.js +28 -27
  35. package/dist/export/pages/ExportLandingPage.js.map +1 -1
  36. package/dist/export/pages/ExportTemplatesPage.d.ts.map +1 -1
  37. package/dist/export/pages/ExportTemplatesPage.js +115 -116
  38. package/dist/export/pages/ExportTemplatesPage.js.map +1 -1
  39. package/dist/export/pages/TemplateDetailPage.d.ts.map +1 -1
  40. package/dist/export/pages/TemplateDetailPage.js +122 -118
  41. package/dist/export/pages/TemplateDetailPage.js.map +1 -1
  42. package/dist/export/utils/i18n.d.ts +18 -2
  43. package/dist/export/utils/i18n.d.ts.map +1 -1
  44. package/dist/export/utils/i18n.js +2 -9
  45. package/dist/export/utils/i18n.js.map +1 -1
  46. package/dist/index.js +2 -1
  47. package/dist/translations/en.d.ts +454 -0
  48. package/dist/translations/en.d.ts.map +1 -0
  49. package/dist/translations/en.js +417 -0
  50. package/dist/translations/en.js.map +1 -0
  51. package/package.json +1 -1
@@ -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\";\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
+ {"version":3,"file":"ExportDetailPage.js","names":[],"sources":["../../../src/export/pages/ExportDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, useNavigate } from \"react-router\";\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 { useTr } from \"../hooks/useTr\";\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 const tr = useTr();\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 {tr(\"export.detail.loading\", \"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={tr(\"export.detail.actions.back\", \"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\">\n {tr(\"export.detail.notFound.title\", \"Export Not Found\")}\n </h1>\n </div>\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr(\"export.detail.notFound.heading\", \"Export not found\")}\n description={tr(\n \"export.detail.notFound.description\",\n \"The export job you are looking for does not exist or has been deleted.\",\n )}\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 {tr(\"export.detail.actions.backToDashboard\", \"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: tr(\"export.detail.actions.download\", \"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: tr(\"export.detail.actions.retry\", \"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: tr(\"export.detail.actions.retry\", \"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: tr(\"export.detail.actions.cancel\", \"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: tr(\"export.detail.actions.delete\", \"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: tr(\n \"export.detail.nextSteps.download.label\",\n \"Download your export\",\n ),\n description: tr(\n \"export.detail.nextSteps.download.description\",\n \"Grab the generated files below before their download links expire.\",\n ),\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: tr(\n \"export.detail.nextSteps.retry.label\",\n \"Retry this export\",\n ),\n description: tr(\n \"export.detail.nextSteps.retry.description\",\n \"This export didn't finish — start a fresh run with the same settings.\",\n ),\n icon: <RotateCcw size={16} />,\n onClick: () => {\n void handleRetry();\n },\n } as NextStep,\n ]\n : []),\n {\n id: \"history\",\n label: tr(\"export.detail.nextSteps.history.label\", \"Back to History\"),\n description: tr(\n \"export.detail.nextSteps.history.description\",\n \"See all your exports and track other jobs in progress.\",\n ),\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={tr(\"export.detail.actions.back\", \"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 {tr(\n \"export.detail.progress.entitiesProcessed\",\n \"{{processed}} / {{total}} entities processed\",\n {\n processed: liveJob.processedEntities.toLocaleString(),\n total: liveJob.totalEntities.toLocaleString(),\n },\n )}\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 {tr(\"export.detail.error.title\", \"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: tr(\"export.detail.info.format\", \"Format\"),\n value: getFormatDisplayName(liveJob.format),\n },\n {\n icon: <HardDrive size={18} />,\n label: tr(\"export.detail.info.archive\", \"Archive\"),\n value: getArchiveFormatDisplayName(liveJob.archiveFormat),\n },\n {\n icon: <Download size={18} />,\n label: tr(\"export.detail.info.totalSize\", \"Total Size\"),\n value:\n liveJob.totalSizeBytes != null\n ? formatBytes(liveJob.totalSizeBytes)\n : \"-\",\n },\n {\n icon: <Clock size={18} />,\n label: tr(\"export.detail.info.created\", \"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={tr(\"export.detail.nextSteps.title\", \"What's next\")}\n steps={nextSteps}\n />\n\n {/* Parts */}\n {liveJob.parts.length > 0 && (\n <PageSection\n title={tr(\"export.detail.parts.title\", \"Export Parts\")}\n description={tr(\n \"export.detail.parts.description\",\n \"{{count}} file(s) generated\",\n { count: liveJob.parts.length },\n )}\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 ` · ${tr(\n \"export.detail.parts.expires\",\n \"expires {{date}}\",\n {\n date: new Date(\n part.downloadExpiresAt,\n ).toLocaleDateString(),\n },\n )}`}\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 {tr(\"export.detail.parts.download\", \"Download\")}\n </a>\n ) : (\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Clock size={14} />\n {tr(\"export.detail.parts.pending\", \"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 {tr(\"export.detail.complete.title\", \"Export Complete\")}\n </h3>\n <p className=\"text-sm text-status-success-text\">\n {tr(\n \"export.detail.complete.noParts\",\n \"Your export has completed but no download parts are available yet.\",\n )}\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\"\n ? tr(\"export.detail.confirm.cancel.title\", \"Cancel export?\")\n : tr(\"export.detail.confirm.delete.title\", \"Delete export?\")}\n </h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {pendingAction === \"cancel\"\n ? tr(\n \"export.detail.confirm.cancel.description\",\n \"This stops the running export job. You can retry it later if needed.\",\n )\n : tr(\n \"export.detail.confirm.delete.description\",\n \"This removes the export job from history. This action cannot be undone.\",\n )}\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 {tr(\"export.detail.confirm.keep\", \"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\"\n ? tr(\"export.detail.actions.cancel\", \"Cancel Export\")\n : tr(\"export.detail.actions.delete\", \"Delete Export\")}\n </button>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ExportHistoryPage.d.ts","sourceRoot":"","sources":["../../../src/export/pages/ExportHistoryPage.tsx"],"names":[],"mappings":"AAsCA;;;GAGG;AACH,wBAAgB,iBAAiB,gCA0ahC"}
1
+ {"version":3,"file":"ExportHistoryPage.d.ts","sourceRoot":"","sources":["../../../src/export/pages/ExportHistoryPage.tsx"],"names":[],"mappings":"AAqCA;;;GAGG;AACH,wBAAgB,iBAAiB,gCAuahC"}
@@ -1,65 +1,64 @@
1
1
  import { useExport as e } from "../providers/ExportProvider.js";
2
- import { useExportJobs as t } from "../hooks/useExportQueries.js";
3
- import { cn as n } from "../utils/cn.js";
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";
2
+ import { useTr as t } from "../hooks/useTr.js";
3
+ import { useExportJobs as n } from "../hooks/useExportQueries.js";
4
+ import { cn as r } from "../utils/cn.js";
5
+ import { getAllStatuses as ee, getFormatDisplayName as i, getStatusDisplayName as a } from "../constants/enums.js";
6
+ import { ExportStatusBadge as o } from "../components/ExportStatusBadge.js";
7
+ import { PageLayout as s } from "../components/PageLayout.js";
8
+ import { useExportStore as c } from "../store/exportStore.js";
9
+ import { useEffect as l, useMemo as u, useRef as d, useState as f } from "react";
10
+ import { AlertCircle as te, ChevronLeft as ne, ChevronRight as p, Clock as m, Download as h, Filter as g, RefreshCw as _, Search as v, X as y } from "lucide-react";
11
11
  import { useSearchParams as b } from "react-router";
12
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";
13
+ import { IllustratedEmptyState as w, PagePurpose as T } from "@burdenoff/fe-libs/ui";
15
14
  //#region src/export/pages/ExportHistoryPage.tsx
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));
34
- }, ae = () => {
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
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(), k = t(), { jobFilter: A, updateFilter: j, resetFilter: M, pagination: N, setPagination: P } = c(), [F] = b(), I = d(!1);
18
+ l(() => {
19
+ if (I.current) return;
20
+ I.current = !0;
21
+ let e = F.get("status");
22
+ e && j({ status: e });
23
+ }, [F, j]);
24
+ let [L, R] = f(!1), [z, B] = f([null]), [V, H] = f(0), U = z[V] ?? null, { data: W, totalCount: G, pageInfo: K, isLoading: q, error: J, refetch: Y } = n(A, {
25
+ first: N.pageSize,
26
+ after: U ?? void 0
27
+ }), X = u(() => {
28
+ if (!A.search) return W;
29
+ let e = A.search.toLowerCase();
30
+ return W.filter((t) => t.name.toLowerCase().includes(e) || t.entityTypes.some((t) => t.toLowerCase().includes(e)));
31
+ }, [W, A.search]), Z = A.search || A.status !== "all" || A.format !== "all", re = (e) => {
32
+ O?.(E(D, e));
33
+ }, ie = () => {
34
+ !K?.hasNextPage || !K.endCursor || (B([...z.slice(0, V + 1), K.endCursor]), H((e) => e + 1), P({
35
+ page: N.page + 1,
36
+ cursor: K.endCursor
38
37
  }));
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
38
+ }, ae = () => {
39
+ if (V === 0) return;
40
+ let e = V - 1;
41
+ H(e), P({
42
+ page: N.page - 1,
43
+ cursor: z[e] ?? null
45
44
  });
46
- }, $ = () => {
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 }),
45
+ }, Q = () => {
46
+ M(), B([null]), H(0);
47
+ }, oe = V > 0, $ = K?.hasNextPage ?? !1, se = V * N.pageSize + 1, ce = Math.min((V + 1) * N.pageSize, G), le = (e) => k(`export.shared.status.${e.toLowerCase()}`, a(e));
48
+ return /* @__PURE__ */ C(s, {
49
+ title: k("export.history.title", "Export History"),
50
+ subtitle: k("export.history.subtitle", "View and manage all your past export jobs"),
51
+ icon: /* @__PURE__ */ S(m, { size: 24 }),
53
52
  headerActions: /* @__PURE__ */ C("button", {
54
53
  type: "button",
55
- onClick: () => k?.(D(O, "new")),
54
+ onClick: () => O?.(E(D, "new")),
56
55
  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",
57
- children: [/* @__PURE__ */ S(g, { size: 16 }), j("export.history.actions.newExport", "New Export")]
56
+ children: [/* @__PURE__ */ S(h, { size: 16 }), k("export.history.actions.newExport", "New Export")]
58
57
  }),
59
58
  children: [
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.")
59
+ /* @__PURE__ */ S(T, {
60
+ icon: /* @__PURE__ */ S(m, { size: 16 }),
61
+ children: k("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
62
  }),
64
63
  /* @__PURE__ */ C("div", {
65
64
  className: "space-y-4",
@@ -67,38 +66,38 @@ function O() {
67
66
  className: "flex flex-col sm:flex-row gap-3",
68
67
  children: [/* @__PURE__ */ C("div", {
69
68
  className: "flex-1 relative group",
70
- children: [/* @__PURE__ */ S(ne, {
69
+ children: [/* @__PURE__ */ S(v, {
71
70
  size: 18,
72
71
  className: "absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary group-focus-within:text-primary transition-colors"
73
72
  }), /* @__PURE__ */ S("input", {
74
73
  type: "text",
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
+ "aria-label": k("export.history.search.placeholder", "Search exports by name or entity type"),
75
+ placeholder: k("export.history.search.placeholder", "Search exports by name or entity type..."),
76
+ value: A.search,
77
+ onChange: (e) => j({ search: e.target.value }),
79
78
  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"
80
79
  })]
81
80
  }), /* @__PURE__ */ C("button", {
82
81
  type: "button",
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")]
82
+ onClick: () => R(!L),
83
+ className: r("inline-flex items-center gap-2 px-4 py-2.5 border-2 rounded-lg transition-all duration-200 font-medium", L || Z ? "bg-action-primary-bg text-action-primary-text border-primary" : "border-border-subtle/50 hover:border-primary/30"),
84
+ children: [/* @__PURE__ */ S(g, { size: 16 }), L ? k("export.history.filters.hide", "Hide") : k("export.history.filters.show", "Filter")]
86
85
  })]
87
- }), z && /* @__PURE__ */ C("div", {
86
+ }), L && /* @__PURE__ */ C("div", {
88
87
  className: "border border-border-seam rounded-xl p-4 animate-in fade-in-0 duration-200",
89
88
  children: [/* @__PURE__ */ C("div", {
90
89
  className: "flex items-center justify-between mb-4",
91
90
  children: [/* @__PURE__ */ C("h3", {
92
91
  className: "font-semibold flex items-center gap-2",
93
- children: [/* @__PURE__ */ S(_, {
92
+ children: [/* @__PURE__ */ S(g, {
94
93
  size: 16,
95
94
  className: "text-primary"
96
- }), j("export.history.filters.title", "Filters")]
97
- }), Q && /* @__PURE__ */ C("button", {
95
+ }), k("export.history.filters.title", "Filters")]
96
+ }), Z && /* @__PURE__ */ C("button", {
98
97
  type: "button",
99
- onClick: $,
98
+ onClick: Q,
100
99
  className: "text-sm text-text-secondary hover:text-status-error-text flex items-center gap-1",
101
- children: [/* @__PURE__ */ S(y, { size: 14 }), j("export.history.filters.clearAll", "Clear All")]
100
+ children: [/* @__PURE__ */ S(y, { size: 14 }), k("export.history.filters.clearAll", "Clear All")]
102
101
  })]
103
102
  }), /* @__PURE__ */ C("div", {
104
103
  className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
@@ -106,40 +105,40 @@ function O() {
106
105
  className: "space-y-2",
107
106
  children: [/* @__PURE__ */ S("label", {
108
107
  className: "block text-sm font-medium",
109
- children: j("export.history.filters.status.label", "Status")
108
+ children: k("export.history.filters.status.label", "Status")
110
109
  }), /* @__PURE__ */ C("select", {
111
- value: M.status,
112
- onChange: (e) => N({ status: e.target.value }),
110
+ value: A.status,
111
+ onChange: (e) => j({ status: e.target.value }),
113
112
  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",
114
113
  children: [/* @__PURE__ */ S("option", {
115
114
  value: "all",
116
- children: j("export.history.filters.status.all", "All Statuses")
117
- }), r().map((e) => /* @__PURE__ */ S("option", {
115
+ children: k("export.history.filters.status.all", "All Statuses")
116
+ }), ee().map((e) => /* @__PURE__ */ S("option", {
118
117
  value: e,
119
- children: de(e)
118
+ children: le(e)
120
119
  }, e))]
121
120
  })]
122
121
  }), /* @__PURE__ */ C("div", {
123
122
  className: "space-y-2",
124
123
  children: [/* @__PURE__ */ S("label", {
125
124
  className: "block text-sm font-medium",
126
- children: j("export.history.filters.format.label", "Format")
125
+ children: k("export.history.filters.format.label", "Format")
127
126
  }), /* @__PURE__ */ C("select", {
128
- value: M.format,
129
- onChange: (e) => N({ format: e.target.value }),
127
+ value: A.format,
128
+ onChange: (e) => j({ format: e.target.value }),
130
129
  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",
131
130
  children: [
132
131
  /* @__PURE__ */ S("option", {
133
132
  value: "all",
134
- children: j("export.history.filters.format.all", "All Formats")
133
+ children: k("export.history.filters.format.all", "All Formats")
135
134
  }),
136
135
  /* @__PURE__ */ S("option", {
137
136
  value: "CSV",
138
- children: "CSV"
137
+ children: k("export.format.csv", "CSV")
139
138
  }),
140
139
  /* @__PURE__ */ S("option", {
141
140
  value: "JSON",
142
- children: "JSON"
141
+ children: k("export.format.json", "JSON")
143
142
  })
144
143
  ]
145
144
  })]
@@ -147,29 +146,29 @@ function O() {
147
146
  })]
148
147
  })]
149
148
  }),
150
- X && /* @__PURE__ */ C("div", {
149
+ J && /* @__PURE__ */ C("div", {
151
150
  className: "border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-6 flex items-start gap-4",
152
- children: [/* @__PURE__ */ S(f, {
151
+ children: [/* @__PURE__ */ S(te, {
153
152
  size: 24,
154
153
  className: "text-status-error-text flex-shrink-0"
155
154
  }), /* @__PURE__ */ C("div", { children: [
156
155
  /* @__PURE__ */ S("h3", {
157
156
  className: "font-semibold text-status-error-text mb-1",
158
- children: j("export.history.error.title", "Error loading exports")
157
+ children: k("export.history.error.title", "Error loading exports")
159
158
  }),
160
159
  /* @__PURE__ */ S("p", {
161
160
  className: "text-sm text-status-error-text mb-3",
162
- children: X.message
161
+ children: J.message
163
162
  }),
164
163
  /* @__PURE__ */ C("button", {
165
164
  type: "button",
166
- onClick: () => re(),
165
+ onClick: () => Y(),
167
166
  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",
168
- children: [/* @__PURE__ */ S(v, { size: 14 }), j("export.history.error.retry", "Try Again")]
167
+ children: [/* @__PURE__ */ S(_, { size: 14 }), k("export.history.error.retry", "Try Again")]
169
168
  })
170
169
  ] })]
171
170
  }),
172
- Y && /* @__PURE__ */ S("div", {
171
+ q && /* @__PURE__ */ S("div", {
173
172
  className: "space-y-3",
174
173
  children: [...[
175
174
  ,
@@ -188,12 +187,12 @@ function O() {
188
187
  })
189
188
  }, t))
190
189
  }),
191
- !Y && !X && (Z.length > 0 ? /* @__PURE__ */ C(x, { children: [/* @__PURE__ */ S("div", {
190
+ !q && !J && (X.length > 0 ? /* @__PURE__ */ C(x, { children: [/* @__PURE__ */ S("div", {
192
191
  className: "space-y-2",
193
- children: Z.map((e, t) => /* @__PURE__ */ S("button", {
192
+ children: X.map((e, t) => /* @__PURE__ */ S("button", {
194
193
  type: "button",
195
- onClick: () => ie(e.id),
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"),
194
+ onClick: () => re(e.id),
195
+ className: r("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"),
197
196
  style: { animationDelay: `${t * 30}ms` },
198
197
  children: /* @__PURE__ */ C("div", {
199
198
  className: "flex items-start sm:items-center justify-between gap-3",
@@ -214,65 +213,65 @@ function O() {
214
213
  })]
215
214
  }), /* @__PURE__ */ C("div", {
216
215
  className: "flex items-center gap-2 flex-shrink-0",
217
- children: [/* @__PURE__ */ S(s, { status: e.status }), /* @__PURE__ */ S(m, {
216
+ children: [/* @__PURE__ */ S(o, { status: e.status }), /* @__PURE__ */ S(p, {
218
217
  size: 18,
219
218
  className: "text-text-secondary group-hover:text-primary transition-colors hidden sm:block"
220
219
  })]
221
220
  })]
222
221
  })
223
222
  }, e.id))
224
- }), q > F.pageSize && /* @__PURE__ */ C("div", {
223
+ }), G > N.pageSize && /* @__PURE__ */ C("div", {
225
224
  className: "flex flex-col sm:flex-row items-center justify-between gap-3 pt-2",
226
225
  children: [/* @__PURE__ */ S("p", {
227
226
  className: "text-sm text-text-secondary",
228
- children: j("export.history.pagination.showing", "Showing {start}-{end} of {total}", {
229
- start: le,
230
- end: ue,
231
- total: q
227
+ children: k("export.history.pagination.showing", "Showing {{start}}-{{end}} of {{total}}", {
228
+ start: se,
229
+ end: ce,
230
+ total: G
232
231
  })
233
232
  }), /* @__PURE__ */ C("div", {
234
233
  className: "flex items-center gap-2",
235
234
  children: [
236
235
  /* @__PURE__ */ C("button", {
237
236
  type: "button",
238
- onClick: oe,
239
- disabled: !se,
237
+ onClick: ae,
238
+ disabled: !oe,
240
239
  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",
241
- children: [/* @__PURE__ */ S(p, { size: 16 }), j("export.history.pagination.prev", "Prev")]
240
+ children: [/* @__PURE__ */ S(ne, { size: 16 }), k("export.history.pagination.prev", "Prev")]
242
241
  }),
243
242
  /* @__PURE__ */ S("span", {
244
243
  className: "text-sm text-text-secondary px-2",
245
- children: j("export.history.pagination.page", "Page {page}", { page: F.page })
244
+ children: k("export.history.pagination.page", "Page {{page}}", { page: N.page })
246
245
  }),
247
246
  /* @__PURE__ */ C("button", {
248
247
  type: "button",
249
- onClick: ae,
250
- disabled: !ce,
248
+ onClick: ie,
249
+ disabled: !$,
251
250
  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",
252
- children: [j("export.history.pagination.next", "Next"), /* @__PURE__ */ S(m, { size: 16 })]
251
+ children: [k("export.history.pagination.next", "Next"), /* @__PURE__ */ S(p, { size: 16 })]
253
252
  })
254
253
  ]
255
254
  })]
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", {
255
+ })] }) : /* @__PURE__ */ S(w, {
256
+ illustration: Z ? "empty-search" : "empty-data",
257
+ title: Z ? k("export.history.empty.filtered.title", "No matching exports") : k("export.history.empty.title", "No exports yet"),
258
+ description: Z ? k("export.history.empty.filtered.description", "Try adjusting your search or filters.") : k("export.history.empty.description", "Create your first export to see it here."),
259
+ action: Z ? /* @__PURE__ */ C("button", {
261
260
  type: "button",
262
- onClick: $,
261
+ onClick: Q,
263
262
  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",
264
- children: [/* @__PURE__ */ S(y, { size: 16 }), j("export.history.empty.filtered.actions.clear", "Clear Filters")]
263
+ children: [/* @__PURE__ */ S(y, { size: 16 }), k("export.history.empty.filtered.actions.clear", "Clear Filters")]
265
264
  }) : /* @__PURE__ */ C("button", {
266
265
  type: "button",
267
- onClick: () => k?.(D(O, "new")),
266
+ onClick: () => O?.(E(D, "new")),
268
267
  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",
269
- children: [/* @__PURE__ */ S(g, { size: 16 }), j("export.history.empty.actions.createFirst", "Create First Export")]
268
+ children: [/* @__PURE__ */ S(h, { size: 16 }), k("export.history.empty.actions.createFirst", "Create First Export")]
270
269
  })
271
270
  }))
272
271
  ]
273
272
  });
274
273
  }
275
274
  //#endregion
276
- export { O as ExportHistoryPage };
275
+ export { D as ExportHistoryPage };
277
276
 
278
277
  //# sourceMappingURL=ExportHistoryPage.js.map