@burdenoff/microfe-export 2026.801.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.
- package/dist/export/ExportRoot.d.ts.map +1 -1
- package/dist/export/ExportRoot.js +41 -36
- package/dist/export/ExportRoot.js.map +1 -1
- package/dist/export/ExportRoutes.d.ts.map +1 -1
- package/dist/export/ExportRoutes.js +75 -66
- package/dist/export/ExportRoutes.js.map +1 -1
- package/dist/export/components/ExportProgressBar.d.ts.map +1 -1
- package/dist/export/components/ExportProgressBar.js +19 -18
- package/dist/export/components/ExportProgressBar.js.map +1 -1
- package/dist/export/components/ExportStatusBadge.d.ts.map +1 -1
- package/dist/export/components/ExportStatusBadge.js +19 -20
- package/dist/export/components/ExportStatusBadge.js.map +1 -1
- package/dist/export/hooks/useTr.d.ts +19 -0
- package/dist/export/hooks/useTr.d.ts.map +1 -0
- package/dist/export/hooks/useTr.js +15 -0
- package/dist/export/hooks/useTr.js.map +1 -0
- package/dist/export/index.d.ts +1 -0
- package/dist/export/index.d.ts.map +1 -1
- package/dist/export/index.js +1 -0
- package/dist/export/pages/CreateExportPage.d.ts +4 -0
- package/dist/export/pages/CreateExportPage.d.ts.map +1 -1
- package/dist/export/pages/CreateExportPage.js +271 -283
- package/dist/export/pages/CreateExportPage.js.map +1 -1
- package/dist/export/pages/ExportDashboardPage.d.ts.map +1 -1
- package/dist/export/pages/ExportDashboardPage.js +71 -72
- package/dist/export/pages/ExportDashboardPage.js.map +1 -1
- package/dist/export/pages/ExportDetailPage.d.ts.map +1 -1
- package/dist/export/pages/ExportDetailPage.js +171 -173
- package/dist/export/pages/ExportDetailPage.js.map +1 -1
- package/dist/export/pages/ExportHistoryPage.d.ts.map +1 -1
- package/dist/export/pages/ExportHistoryPage.js +110 -111
- package/dist/export/pages/ExportHistoryPage.js.map +1 -1
- package/dist/export/pages/ExportLandingPage.d.ts.map +1 -1
- package/dist/export/pages/ExportLandingPage.js +28 -27
- package/dist/export/pages/ExportLandingPage.js.map +1 -1
- package/dist/export/pages/ExportTemplatesPage.d.ts.map +1 -1
- package/dist/export/pages/ExportTemplatesPage.js +115 -116
- package/dist/export/pages/ExportTemplatesPage.js.map +1 -1
- package/dist/export/pages/TemplateDetailPage.d.ts.map +1 -1
- package/dist/export/pages/TemplateDetailPage.js +122 -118
- package/dist/export/pages/TemplateDetailPage.js.map +1 -1
- package/dist/export/utils/i18n.d.ts +18 -2
- package/dist/export/utils/i18n.d.ts.map +1 -1
- package/dist/export/utils/i18n.js +2 -9
- package/dist/export/utils/i18n.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/translations/en.d.ts +454 -0
- package/dist/translations/en.d.ts.map +1 -0
- package/dist/translations/en.js +417 -0
- package/dist/translations/en.js.map +1 -0
- package/package.json +2 -2
|
@@ -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(\", \")} ·{\" \"}\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(\", \")} ·{\" \"}\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":"
|
|
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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { ExportStatusBadge as
|
|
7
|
-
import { PageLayout as
|
|
8
|
-
import { useExportStore as
|
|
9
|
-
import { useEffect as
|
|
10
|
-
import { AlertCircle as
|
|
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 {
|
|
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
|
|
17
|
-
function
|
|
18
|
-
let { basePath:
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
let e =
|
|
23
|
-
e &&
|
|
24
|
-
}, [
|
|
25
|
-
let [
|
|
26
|
-
first:
|
|
27
|
-
after:
|
|
28
|
-
}),
|
|
29
|
-
if (!
|
|
30
|
-
let e =
|
|
31
|
-
return
|
|
32
|
-
}, [
|
|
33
|
-
|
|
34
|
-
},
|
|
35
|
-
!
|
|
36
|
-
page:
|
|
37
|
-
cursor:
|
|
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
|
-
},
|
|
40
|
-
if (
|
|
41
|
-
let e =
|
|
42
|
-
|
|
43
|
-
page:
|
|
44
|
-
cursor:
|
|
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
|
-
|
|
48
|
-
},
|
|
49
|
-
return /* @__PURE__ */ C(
|
|
50
|
-
title:
|
|
51
|
-
subtitle:
|
|
52
|
-
icon: /* @__PURE__ */ S(
|
|
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: () =>
|
|
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(
|
|
56
|
+
children: [/* @__PURE__ */ S(h, { size: 16 }), k("export.history.actions.newExport", "New Export")]
|
|
58
57
|
}),
|
|
59
58
|
children: [
|
|
60
|
-
/* @__PURE__ */ S(
|
|
61
|
-
icon: /* @__PURE__ */ S(
|
|
62
|
-
children:
|
|
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(
|
|
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":
|
|
76
|
-
placeholder:
|
|
77
|
-
value:
|
|
78
|
-
onChange: (e) =>
|
|
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: () =>
|
|
84
|
-
className:
|
|
85
|
-
children: [/* @__PURE__ */ S(
|
|
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
|
-
}),
|
|
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
|
-
}),
|
|
97
|
-
}),
|
|
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 }),
|
|
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:
|
|
108
|
+
children: k("export.history.filters.status.label", "Status")
|
|
110
109
|
}), /* @__PURE__ */ C("select", {
|
|
111
|
-
value:
|
|
112
|
-
onChange: (e) =>
|
|
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:
|
|
117
|
-
}),
|
|
115
|
+
children: k("export.history.filters.status.all", "All Statuses")
|
|
116
|
+
}), ee().map((e) => /* @__PURE__ */ S("option", {
|
|
118
117
|
value: e,
|
|
119
|
-
children:
|
|
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:
|
|
125
|
+
children: k("export.history.filters.format.label", "Format")
|
|
127
126
|
}), /* @__PURE__ */ C("select", {
|
|
128
|
-
value:
|
|
129
|
-
onChange: (e) =>
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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:
|
|
161
|
+
children: J.message
|
|
163
162
|
}),
|
|
164
163
|
/* @__PURE__ */ C("button", {
|
|
165
164
|
type: "button",
|
|
166
|
-
onClick: () =>
|
|
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(
|
|
167
|
+
children: [/* @__PURE__ */ S(_, { size: 14 }), k("export.history.error.retry", "Try Again")]
|
|
169
168
|
})
|
|
170
169
|
] })]
|
|
171
170
|
}),
|
|
172
|
-
|
|
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
|
-
!
|
|
190
|
+
!q && !J && (X.length > 0 ? /* @__PURE__ */ C(x, { children: [/* @__PURE__ */ S("div", {
|
|
192
191
|
className: "space-y-2",
|
|
193
|
-
children:
|
|
192
|
+
children: X.map((e, t) => /* @__PURE__ */ S("button", {
|
|
194
193
|
type: "button",
|
|
195
|
-
onClick: () =>
|
|
196
|
-
className:
|
|
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(
|
|
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
|
-
}),
|
|
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:
|
|
229
|
-
start:
|
|
230
|
-
end:
|
|
231
|
-
total:
|
|
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:
|
|
239
|
-
disabled: !
|
|
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(
|
|
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:
|
|
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:
|
|
250
|
-
disabled:
|
|
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: [
|
|
251
|
+
children: [k("export.history.pagination.next", "Next"), /* @__PURE__ */ S(p, { size: 16 })]
|
|
253
252
|
})
|
|
254
253
|
]
|
|
255
254
|
})]
|
|
256
|
-
})] }) : /* @__PURE__ */ S(
|
|
257
|
-
illustration:
|
|
258
|
-
title:
|
|
259
|
-
description:
|
|
260
|
-
action:
|
|
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 }),
|
|
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: () =>
|
|
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(
|
|
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 {
|
|
275
|
+
export { D as ExportHistoryPage };
|
|
277
276
|
|
|
278
277
|
//# sourceMappingURL=ExportHistoryPage.js.map
|