@burdenoff/microfe-export 2026.525.2 → 2026.527.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/export/ExportRoutes.js +5 -4
- package/dist/export/ExportRoutes.js.map +1 -1
- package/dist/export/components/ExportStatusBadge.js +2 -2
- package/dist/export/components/ExportStatusBadge.js.map +1 -1
- package/dist/export/constants/enums.js +13 -13
- package/dist/export/constants/enums.js.map +1 -1
- package/dist/export/hooks/useExportQueries.js.map +1 -1
- package/dist/export/pages/CreateExportPage.js +10 -1
- package/dist/export/pages/CreateExportPage.js.map +1 -1
- package/dist/export/pages/ExportDashboardPage.js +5 -0
- package/dist/export/pages/ExportDashboardPage.js.map +1 -1
- package/dist/export/pages/ExportDetailPage.js +7 -1
- package/dist/export/pages/ExportDetailPage.js.map +1 -1
- package/dist/export/pages/ExportHistoryPage.js +136 -124
- package/dist/export/pages/ExportHistoryPage.js.map +1 -1
- package/dist/export/pages/ExportTemplatesPage.js +148 -136
- package/dist/export/pages/ExportTemplatesPage.js.map +1 -1
- package/dist/export/pages/TemplateDetailPage.js +6 -1
- package/dist/export/pages/TemplateDetailPage.js.map +1 -1
- package/package.json +1 -1
- package/dist/dev/main.d.ts +0 -2
- package/dist/dev/main.d.ts.map +0 -1
- package/dist/export/ExportRoot.d.ts +0 -31
- package/dist/export/ExportRoot.d.ts.map +0 -1
- package/dist/export/ExportRoutes.d.ts +0 -14
- package/dist/export/ExportRoutes.d.ts.map +0 -1
- package/dist/export/components/ExportProgressBar.d.ts +0 -12
- package/dist/export/components/ExportProgressBar.d.ts.map +0 -1
- package/dist/export/components/ExportStatusBadge.d.ts +0 -12
- package/dist/export/components/ExportStatusBadge.d.ts.map +0 -1
- package/dist/export/components/PageLayout.d.ts +0 -55
- package/dist/export/components/PageLayout.d.ts.map +0 -1
- package/dist/export/components/ui.d.ts +0 -6
- package/dist/export/components/ui.d.ts.map +0 -1
- package/dist/export/constants/enums.d.ts +0 -79
- package/dist/export/constants/enums.d.ts.map +0 -1
- package/dist/export/constants/index.d.ts +0 -7
- package/dist/export/constants/index.d.ts.map +0 -1
- package/dist/export/hooks/useExportQueries.d.ts +0 -224
- package/dist/export/hooks/useExportQueries.d.ts.map +0 -1
- package/dist/export/index.d.ts +0 -29
- package/dist/export/index.d.ts.map +0 -1
- package/dist/export/pages/CreateExportPage.d.ts +0 -2
- package/dist/export/pages/CreateExportPage.d.ts.map +0 -1
- package/dist/export/pages/ExportDashboardPage.d.ts +0 -6
- package/dist/export/pages/ExportDashboardPage.d.ts.map +0 -1
- package/dist/export/pages/ExportDetailPage.d.ts +0 -7
- package/dist/export/pages/ExportDetailPage.d.ts.map +0 -1
- package/dist/export/pages/ExportHistoryPage.d.ts +0 -6
- package/dist/export/pages/ExportHistoryPage.d.ts.map +0 -1
- package/dist/export/pages/ExportLandingPage.d.ts +0 -9
- package/dist/export/pages/ExportLandingPage.d.ts.map +0 -1
- package/dist/export/pages/ExportTemplatesPage.d.ts +0 -6
- package/dist/export/pages/ExportTemplatesPage.d.ts.map +0 -1
- package/dist/export/pages/TemplateDetailPage.d.ts +0 -7
- package/dist/export/pages/TemplateDetailPage.d.ts.map +0 -1
- package/dist/export/providers/ExportProvider.d.ts +0 -13
- package/dist/export/providers/ExportProvider.d.ts.map +0 -1
- package/dist/export/store/exportStore.d.ts +0 -17
- package/dist/export/store/exportStore.d.ts.map +0 -1
- package/dist/export/types/index.d.ts +0 -230
- package/dist/export/types/index.d.ts.map +0 -1
- package/dist/export/utils/cn.d.ts +0 -8
- package/dist/export/utils/cn.d.ts.map +0 -1
- package/dist/export/utils/events.d.ts +0 -7
- package/dist/export/utils/events.d.ts.map +0 -1
- package/dist/export/utils/i18n.d.ts +0 -6
- package/dist/export/utils/i18n.d.ts.map +0 -1
- package/dist/generated/global-operations.d.ts +0 -2
- package/dist/generated/global-operations.d.ts.map +0 -1
- package/dist/generated/global-types.d.ts +0 -23527
- package/dist/generated/global-types.d.ts.map +0 -1
- package/dist/generated/wspace-operations.d.ts +0 -773
- package/dist/generated/wspace-operations.d.ts.map +0 -1
- package/dist/generated/wspace-types.d.ts +0 -57851
- package/dist/generated/wspace-types.d.ts.map +0 -1
- package/dist/index.d.ts +0 -6
- package/dist/index.d.ts.map +0 -1
- package/dist/utils/nativeBridge.d.ts +0 -3
- package/dist/utils/nativeBridge.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateExportPage.js","names":[],"sources":["../../../src/export/pages/CreateExportPage.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\nimport {\n ArrowLeft,\n ArrowRight,\n Download,\n CheckCircle2,\n Loader2,\n FileSpreadsheet,\n FileJson,\n Package,\n CalendarRange,\n User,\n Users,\n Building2,\n Lock,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { usePermission } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport {\n useCreateExportJob,\n useAvailableEntities,\n useExportTemplates,\n} from \"../hooks/useExportQueries\";\nimport { getFormatDisplayName } from \"../constants/enums\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { cn } from \"../utils/cn\";\nimport type { ExportFormat, ExportScope } from \"../types\";\nimport { tWithFallback } from \"../utils/i18n\";\n\ntype WizardStep = \"entities\" | \"options\" | \"review\";\n\n// Backend only supports CSV and JSON\nconst SUPPORTED_FORMATS: ExportFormat[] = [\"CSV\", \"JSON\"];\n\nconst FORMAT_ICONS: Record<ExportFormat, typeof FileJson> = {\n CSV: FileSpreadsheet,\n JSON: FileJson,\n};\n\n/**\n * Create Export Page\n * Multi-step form: select entities, configure options, review & start\n */\nconst SCOPE_OPTIONS: Array<{\n value: ExportScope;\n label: string;\n description: string;\n Icon: typeof User;\n}> = [\n {\n value: \"INDIVIDUAL\",\n label: \"My Data Only\",\n description: \"Export data you personally created across selected modules.\",\n Icon: User,\n },\n {\n value: \"WORKSPACE_ADMIN\",\n label: \"All Workspace Data\",\n description:\n \"Export all data in this workspace regardless of who created it. Requires workspace admin access.\",\n Icon: Users,\n },\n {\n value: \"ORG_ADMIN\",\n label: \"All Organization Data\",\n description:\n \"Export data from every workspace in your organization. Requires org-admin access.\",\n Icon: Building2,\n },\n];\n\nexport function CreateExportPage() {\n const { basePath = \"/\", navigate, organizationId } = useExport();\n const { t } = useI18n();\n const tr = (\n key: string,\n fallback: string,\n params?: Record<string, string | number>,\n ) => tWithFallback(t, key, fallback, params);\n const { createJob, isPending } = useCreateExportJob();\n const { data: availableEntities } = useAvailableEntities();\n const { data: templates } = useExportTemplates();\n const [searchParams] = useSearchParams();\n\n // RBAC: Check permissions for admin scopes\n const canExportWorkspaceAdmin = usePermission(\"export:workspace:admin\");\n const canExportOrgAdmin = usePermission(\"export:org:admin\");\n\n const [step, setStep] = useState<WizardStep>(\"entities\");\n const [selectedEntities, setSelectedEntities] = useState<string[]>([]);\n const [exportName, setExportName] = useState(\"\");\n const [selectedFormat, setSelectedFormat] = useState<ExportFormat>(\"CSV\");\n const [selectedScope, setSelectedScope] = useState<ExportScope>(\"INDIVIDUAL\");\n const [selectedTemplateId, setSelectedTemplateId] = useState<\n string | undefined\n >(searchParams.get(\"templateId\") ?? undefined);\n const [dateRangeStart, setDateRangeStart] = useState(\"\");\n const [dateRangeEnd, setDateRangeEnd] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n\n // Pre-populate form when arriving from \"Use Template\" — apply only once per templateId\n const appliedTemplateRef = useRef<string | null>(null);\n useEffect(() => {\n const paramTemplateId = searchParams.get(\"templateId\");\n if (!paramTemplateId || !templates?.length) return;\n if (appliedTemplateRef.current === paramTemplateId) return;\n const tpl = templates.find((t) => t.id === paramTemplateId);\n if (!tpl) return;\n appliedTemplateRef.current = paramTemplateId;\n setSelectedTemplateId(tpl.id);\n setSelectedEntities(tpl.entityTypes ?? []);\n const normalizedFormat = tpl.format?.toUpperCase() as\n | ExportFormat\n | undefined;\n if (normalizedFormat && SUPPORTED_FORMATS.includes(normalizedFormat)) {\n setSelectedFormat(normalizedFormat);\n }\n if (tpl.name) setExportName(tpl.name);\n }, [searchParams, templates]);\n\n const handleBack = () => {\n if (step === \"options\") setStep(\"entities\");\n else if (step === \"review\") setStep(\"options\");\n else navigate?.(basePath);\n };\n\n const handleNext = () => {\n if (step === \"entities\") {\n if (selectedEntities.length === 0) {\n setError(\n tr(\n \"export.create.errors.selectEntity\",\n \"Please select at least one entity type to export\",\n ),\n );\n return;\n }\n setError(null);\n setStep(\"options\");\n } else if (step === \"options\") {\n if (!exportName.trim()) {\n setError(\n tr(\n \"export.create.errors.exportNameRequired\",\n \"Please provide a name for this export\",\n ),\n );\n return;\n }\n setError(null);\n setStep(\"review\");\n }\n };\n\n const handleSubmit = async () => {\n try {\n setError(null);\n await createJob({\n name: exportName,\n entityTypes: selectedEntities,\n format: selectedFormat,\n exportScope: selectedScope,\n organizationId:\n selectedScope === \"ORG_ADMIN\" ? organizationId : undefined,\n templateId: selectedTemplateId,\n dateRangeStart: dateRangeStart\n ? new Date(dateRangeStart).toISOString()\n : null,\n dateRangeEnd: dateRangeEnd\n ? new Date(dateRangeEnd).toISOString()\n : null,\n });\n navigate?.(basePath);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"export.create.errors.createFailed\", \"Failed to create export\"),\n );\n }\n };\n\n const toggleEntity = (entityType: string) => {\n setSelectedEntities((prev) =>\n prev.includes(entityType)\n ? prev.filter((e) => e !== entityType)\n : [...prev, entityType],\n );\n };\n\n const getScopeLabel = (scope: ExportScope) => {\n switch (scope) {\n case \"INDIVIDUAL\":\n return tr(\"export.create.scope.individual.label\", \"My Data Only\");\n case \"WORKSPACE_ADMIN\":\n return tr(\n \"export.create.scope.workspaceAdmin.label\",\n \"All Workspace Data\",\n );\n case \"ORG_ADMIN\":\n return tr(\n \"export.create.scope.orgAdmin.label\",\n \"All Organization Data\",\n );\n default:\n return scope;\n }\n };\n\n const getScopeDescription = (scope: ExportScope) => {\n switch (scope) {\n case \"INDIVIDUAL\":\n return tr(\n \"export.create.scope.individual.description\",\n \"Export data you personally created across selected modules.\",\n );\n case \"WORKSPACE_ADMIN\":\n return tr(\n \"export.create.scope.workspaceAdmin.description\",\n \"Export all data in this workspace regardless of who created it. Requires workspace admin access.\",\n );\n case \"ORG_ADMIN\":\n return tr(\n \"export.create.scope.orgAdmin.description\",\n \"Export data from every workspace in your organization. Requires org-admin access.\",\n );\n default:\n return \"\";\n }\n };\n\n const fallbackEntityCopy: Record<\n string,\n { label: string; description: string }\n > = {\n users: {\n label: tr(\"export.create.entities.users.label\", \"Users\"),\n description: tr(\n \"export.create.entities.users.description\",\n \"Workspace members and profiles\",\n ),\n },\n projects: {\n label: tr(\"export.create.entities.projects.label\", \"Projects\"),\n description: tr(\n \"export.create.entities.projects.description\",\n \"Projects and their metadata\",\n ),\n },\n tasks: {\n label: tr(\"export.create.entities.tasks.label\", \"Tasks\"),\n description: tr(\n \"export.create.entities.tasks.description\",\n \"Task items and assignments\",\n ),\n },\n files: {\n label: tr(\"export.create.entities.files.label\", \"Files\"),\n description: tr(\n \"export.create.entities.files.description\",\n \"File metadata and references\",\n ),\n },\n conversations: {\n label: tr(\"export.create.entities.conversations.label\", \"Conversations\"),\n description: tr(\n \"export.create.entities.conversations.description\",\n \"Chat messages and threads\",\n ),\n },\n tags: {\n label: tr(\"export.create.entities.tags.label\", \"Tags\"),\n description: tr(\n \"export.create.entities.tags.description\",\n \"Tags and vocabularies\",\n ),\n },\n activity: {\n label: tr(\"export.create.entities.activity.label\", \"Activity Logs\"),\n description: tr(\n \"export.create.entities.activity.description\",\n \"Audit trail and activity history\",\n ),\n },\n calendar: {\n label: tr(\"export.create.entities.calendar.label\", \"Calendar Events\"),\n description: tr(\n \"export.create.entities.calendar.description\",\n \"Events and bookings\",\n ),\n },\n };\n\n // Only show enabled entities\n const enabledEntities = availableEntities.filter((e) => e.enabled !== false);\n\n return (\n <PageLayout showHeader={false}>\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-xl bg-gradient-to-br from-primary/5 via-primary/3 to-background border border-border/50 p-6 shadow-sm\">\n <div className=\"relative z-10 flex items-center gap-4\">\n <button\n onClick={() => navigate?.(basePath)}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-all duration-200 hover:scale-110\"\n aria-label={tr(\n \"export.create.actions.backToDashboard\",\n \"Back to export dashboard\",\n )}\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2.5 rounded-lg bg-primary/10 backdrop-blur-sm\">\n <Download size={24} className=\"text-primary\" />\n </div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tr(\"export.create.title\", \"New Export\")}\n </h1>\n </div>\n </div>\n </div>\n\n {/* Step Indicator */}\n <div className=\"flex items-center gap-2 justify-center flex-wrap\">\n {([\"entities\", \"options\", \"review\"] as WizardStep[]).map((s, i) => (\n <div key={s} className=\"flex items-center gap-2\">\n {i > 0 && (\n <ArrowRight size={18} className=\"text-muted-foreground\" />\n )}\n <div\n className={cn(\n \"flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium border-2 transition-all duration-200\",\n step === s\n ? \"bg-primary text-primary-foreground border-primary shadow-md scale-105\"\n : \"bg-muted/50 text-muted-foreground border-border/50\",\n )}\n >\n <div className=\"flex items-center justify-center w-6 h-6 rounded-full bg-background/20 font-bold\">\n {i + 1}\n </div>\n <span className=\"hidden sm:inline capitalize\">\n {s === \"entities\"\n ? tr(\"export.create.steps.entities\", \"Select Data\")\n : s === \"options\"\n ? tr(\"export.create.steps.options\", \"Options\")\n : tr(\"export.create.steps.review\", \"Review\")}\n </span>\n </div>\n </div>\n ))}\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-4 text-status-error-text text-sm\">\n {error}\n </div>\n )}\n\n {/* Step 1: Select Entities */}\n {step === \"entities\" && (\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-bold\">\n {tr(\"export.create.select.title\", \"Select data to export\")}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n \"export.create.select.description\",\n \"Choose which entity types to include in your export.\",\n )}\n </p>\n {enabledEntities.length === 0 ? (\n <div className=\"border-2 border-border rounded-xl bg-muted/30 p-6 text-sm text-muted-foreground\">\n {tr(\n \"export.create.select.empty\",\n \"No exportable entity types are configured for this workspace yet.\",\n )}\n </div>\n ) : (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {enabledEntities.map((entity) => {\n const isSelected = selectedEntities.includes(entity.entityType);\n return (\n <button\n key={entity.entityType}\n onClick={() => toggleEntity(entity.entityType)}\n className={cn(\n \"text-left border-2 rounded-xl p-4 transition-all duration-200\",\n isSelected\n ? \"border-primary bg-primary/5 shadow-md\"\n : \"border-border/50 hover:border-primary/30 hover:shadow-sm\",\n )}\n >\n <div className=\"flex items-center justify-between mb-2\">\n <h3 className=\"font-semibold\">\n {fallbackEntityCopy[entity.entityType]?.label ??\n entity.displayName}\n </h3>\n {isSelected && (\n <CheckCircle2 size={18} className=\"text-primary\" />\n )}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {fallbackEntityCopy[entity.entityType]?.description ??\n entity.description}\n </p>\n {entity.estimatedSize && (\n <p className=\"text-xs text-muted-foreground mt-2\">\n ~{entity.estimatedSize}\n </p>\n )}\n </button>\n );\n })}\n </div>\n )}\n </div>\n )}\n\n {/* Step 2: Configure Options */}\n {step === \"options\" && (\n <div className=\"space-y-6\">\n <div className=\"border-2 border-border/50 rounded-xl p-6 space-y-5\">\n <h2 className=\"text-lg font-bold\">\n {tr(\"export.create.options.title\", \"Export Options\")}\n </h2>\n\n {/* Scope selection */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\"export.create.options.scope\", \"Export Scope\")}\n </label>\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-3\">\n {SCOPE_OPTIONS.map(({ value, Icon }) => {\n // RBAC: Disable admin scopes if user lacks permission\n const isDisabled =\n (value === \"WORKSPACE_ADMIN\" && !canExportWorkspaceAdmin) ||\n (value === \"ORG_ADMIN\" && !canExportOrgAdmin);\n\n return (\n <button\n key={value}\n type=\"button\"\n onClick={() => !isDisabled && setSelectedScope(value)}\n disabled={isDisabled}\n className={cn(\n \"text-left border-2 rounded-xl p-4 transition-all duration-200\",\n selectedScope === value\n ? \"border-primary bg-primary/5 shadow-md\"\n : isDisabled\n ? \"border-border/30 opacity-50 cursor-not-allowed\"\n : \"border-border/50 hover:border-primary/30 hover:shadow-sm\",\n )}\n title={\n isDisabled\n ? value === \"WORKSPACE_ADMIN\"\n ? \"Workspace admin access required\"\n : \"Organization admin access required\"\n : undefined\n }\n >\n <div className=\"flex items-center justify-between mb-2\">\n <Icon\n size={18}\n className={\n selectedScope === value\n ? \"text-primary\"\n : isDisabled\n ? \"text-muted-foreground/50\"\n : \"text-muted-foreground\"\n }\n />\n {selectedScope === value && (\n <CheckCircle2 size={16} className=\"text-primary\" />\n )}\n {isDisabled && (\n <Lock\n size={14}\n className=\"text-muted-foreground/50\"\n />\n )}\n </div>\n <p className=\"font-semibold text-sm\">\n {getScopeLabel(value)}\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {getScopeDescription(value)}\n </p>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Name */}\n <div>\n <label\n htmlFor=\"exportName\"\n className=\"block text-sm font-semibold mb-2\"\n >\n {tr(\"export.create.options.name\", \"Export Name\")}{\" \"}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n type=\"text\"\n id=\"exportName\"\n value={exportName}\n onChange={(e) => setExportName(e.target.value)}\n className=\"w-full px-4 py-2.5 border-2 border-border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all duration-200\"\n placeholder={tr(\n \"export.create.options.namePlaceholder\",\n \"e.g., Monthly User Export\",\n )}\n />\n </div>\n\n {/* Format — only CSV and JSON are supported */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\"export.create.options.outputFormat\", \"Output Format\")}\n </label>\n <div className=\"grid grid-cols-2 gap-3\">\n {SUPPORTED_FORMATS.map((format) => {\n const Icon = FORMAT_ICONS[format];\n return (\n <button\n key={format}\n onClick={() => setSelectedFormat(format)}\n className={cn(\n \"flex items-center gap-3 border-2 rounded-lg p-3 transition-all duration-200\",\n selectedFormat === format\n ? \"border-primary bg-primary/5\"\n : \"border-border/50 hover:border-primary/30\",\n )}\n >\n <Icon\n size={18}\n className={\n selectedFormat === format\n ? \"text-primary\"\n : \"text-muted-foreground\"\n }\n />\n <span className=\"font-medium text-sm\">\n {getFormatDisplayName(format)}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Archive format — only ZIP is supported */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\"export.create.options.archiveFormat\", \"Archive Format\")}\n </label>\n <div className=\"flex gap-3\">\n <div\n className={cn(\n \"flex items-center gap-2 border-2 rounded-lg px-4 py-2 border-primary bg-primary/5\",\n )}\n >\n <Package size={16} className=\"text-primary\" />\n <span className=\"font-medium text-sm\">ZIP</span>\n </div>\n </div>\n <p className=\"text-xs text-muted-foreground mt-1.5\">\n {tr(\n \"export.create.options.archiveDescription\",\n \"Exports are packaged as ZIP archives.\",\n )}\n </p>\n </div>\n\n {/* Date range filter — always visible */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n <span className=\"inline-flex items-center gap-1.5\">\n <CalendarRange size={15} />\n {tr(\"export.create.options.dateRange\", \"Date Range\")}{\" \"}\n <span className=\"font-normal text-muted-foreground\">\n {tr(\"export.create.shared.optional\", \"(optional)\")}\n </span>\n </span>\n </label>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n <div>\n <label\n htmlFor=\"dateFrom\"\n className=\"block text-xs text-muted-foreground mb-1\"\n >\n {tr(\"export.create.options.dateFrom\", \"From\")}\n </label>\n <input\n type=\"date\"\n id=\"dateFrom\"\n value={dateRangeStart}\n onChange={(e) => setDateRangeStart(e.target.value)}\n className=\"w-full px-3 py-2 border-2 border-border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary text-sm\"\n />\n </div>\n <div>\n <label\n htmlFor=\"dateTo\"\n className=\"block text-xs text-muted-foreground mb-1\"\n >\n {tr(\"export.create.options.dateTo\", \"To\")}\n </label>\n <input\n type=\"date\"\n id=\"dateTo\"\n value={dateRangeEnd}\n min={dateRangeStart}\n onChange={(e) => setDateRangeEnd(e.target.value)}\n className=\"w-full px-3 py-2 border-2 border-border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary text-sm\"\n />\n </div>\n </div>\n <p className=\"text-xs text-muted-foreground mt-1.5\">\n {tr(\n \"export.create.options.dateRangeDescription\",\n \"Leave blank to export all data regardless of date.\",\n )}\n </p>\n </div>\n\n {/* Optional: Use a template */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\n \"export.create.options.basedOnTemplate\",\n \"Based on Template\",\n )}{\" \"}\n <span className=\"font-normal text-muted-foreground\">\n {tr(\"export.create.shared.optional\", \"(optional)\")}\n </span>\n </label>\n {templates.length > 0 ? (\n <select\n value={selectedTemplateId ?? \"\"}\n onChange={(e) =>\n setSelectedTemplateId(e.target.value || undefined)\n }\n className=\"w-full px-3 py-2 border-2 border-border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary\"\n >\n <option value=\"\">\n {tr(\"export.create.options.noTemplate\", \"No template\")}\n </option>\n {templates.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {tr(\"export.create.options.noTemplates\", \"No templates yet.\")}{\" \"}\n <button\n type=\"button\"\n onClick={() => navigate?.(basePath + \"/templates\")}\n className=\"text-primary underline-offset-2 hover:underline\"\n >\n {tr(\n \"export.create.options.createTemplateLink\",\n \"Create one\",\n )}\n </button>{\" \"}\n {tr(\n \"export.create.options.reuseTemplateHint\",\n \"to reuse export configurations.\",\n )}\n </p>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* Step 3: Review */}\n {step === \"review\" && (\n <div className=\"space-y-6\">\n <div className=\"border-2 border-primary/50 rounded-xl bg-primary/5 p-6 space-y-4\">\n <h2 className=\"text-lg font-bold flex items-center gap-2\">\n <CheckCircle2 size={20} className=\"text-primary\" />\n {tr(\"export.create.review.title\", \"Review Export\")}\n </h2>\n <dl className=\"space-y-3 text-sm\">\n {[\n {\n label: tr(\"export.create.review.fields.name\", \"Name\"),\n value: exportName,\n },\n {\n label: tr(\"export.create.review.fields.scope\", \"Scope\"),\n value: getScopeLabel(selectedScope),\n },\n {\n label: tr(\n \"export.create.review.fields.entityTypes\",\n \"Entity Types\",\n ),\n value: selectedEntities.join(\", \"),\n },\n {\n label: tr(\"export.create.review.fields.format\", \"Format\"),\n value: getFormatDisplayName(selectedFormat),\n },\n {\n label: tr(\"export.create.review.fields.archive\", \"Archive\"),\n value: \"ZIP\",\n },\n ...(dateRangeStart || dateRangeEnd\n ? [\n {\n label: tr(\n \"export.create.review.fields.dateRange\",\n \"Date Range\",\n ),\n value: [\n dateRangeStart || \"—\",\n dateRangeEnd || \"—\",\n ].join(\" → \"),\n },\n ]\n : []),\n ...(selectedTemplateId\n ? [\n {\n label: tr(\n \"export.create.review.fields.template\",\n \"Template\",\n ),\n value:\n templates.find((t) => t.id === selectedTemplateId)\n ?.name ?? selectedTemplateId,\n },\n ]\n : []),\n ].map(({ label, value }) => (\n <div\n key={label}\n className=\"flex flex-col sm:flex-row sm:justify-between gap-1\"\n >\n <dt className=\"text-muted-foreground\">{label}</dt>\n <dd className=\"font-semibold sm:text-right break-words\">\n {value}\n </dd>\n </div>\n ))}\n </dl>\n </div>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex flex-col sm:flex-row gap-3 pt-2\">\n <button\n onClick={handleBack}\n className=\"px-6 py-3 border-2 border-border/50 rounded-lg hover:bg-accent hover:border-primary/30 transition-all duration-200 font-medium\"\n >\n {step === \"entities\"\n ? tr(\"export.create.actions.cancel\", \"Cancel\")\n : tr(\"export.create.actions.back\", \"Back\")}\n </button>\n {step === \"review\" ? (\n <button\n onClick={handleSubmit}\n disabled={isPending}\n className=\"flex-1 inline-flex items-center justify-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-all duration-200 hover:scale-105 font-bold shadow-lg disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isPending ? (\n <Loader2 size={18} className=\"animate-spin\" />\n ) : (\n <Download size={18} />\n )}\n {tr(\"export.create.actions.startExport\", \"Start Export\")}\n </button>\n ) : (\n <button\n onClick={handleNext}\n className=\"flex-1 inline-flex items-center justify-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-all duration-200 hover:scale-105 font-bold shadow-lg\"\n >\n {tr(\"export.create.actions.next\", \"Next\")}\n <ArrowRight size={18} />\n </button>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CreateExportPage.js","names":[],"sources":["../../../src/export/pages/CreateExportPage.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\nimport {\n ArrowLeft,\n ArrowRight,\n Download,\n CheckCircle2,\n Loader2,\n FileSpreadsheet,\n FileJson,\n Package,\n CalendarRange,\n User,\n Users,\n Building2,\n Lock,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { usePermission } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport {\n useCreateExportJob,\n useAvailableEntities,\n useExportTemplates,\n} from \"../hooks/useExportQueries\";\nimport { getFormatDisplayName } from \"../constants/enums\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { cn } from \"../utils/cn\";\nimport type { ExportFormat, ExportScope } from \"../types\";\nimport { tWithFallback } from \"../utils/i18n\";\n\ntype WizardStep = \"entities\" | \"options\" | \"review\";\n\n// Backend only supports CSV and JSON\nconst SUPPORTED_FORMATS: ExportFormat[] = [\"CSV\", \"JSON\"];\n\nconst FORMAT_ICONS: Record<ExportFormat, typeof FileJson> = {\n CSV: FileSpreadsheet,\n JSON: FileJson,\n};\n\n/**\n * Create Export Page\n * Multi-step form: select entities, configure options, review & start\n */\nconst SCOPE_OPTIONS: Array<{\n value: ExportScope;\n label: string;\n description: string;\n Icon: typeof User;\n}> = [\n {\n value: \"INDIVIDUAL\",\n label: \"My Data Only\",\n description: \"Export data you personally created across selected modules.\",\n Icon: User,\n },\n {\n value: \"WORKSPACE_ADMIN\",\n label: \"All Workspace Data\",\n description:\n \"Export all data in this workspace regardless of who created it. Requires workspace admin access.\",\n Icon: Users,\n },\n {\n value: \"ORG_ADMIN\",\n label: \"All Organization Data\",\n description:\n \"Export data from every workspace in your organization. Requires org-admin access.\",\n Icon: Building2,\n },\n];\n\nexport function CreateExportPage() {\n const { basePath = \"/\", navigate, organizationId } = useExport();\n const { t } = useI18n();\n const tr = (\n key: string,\n fallback: string,\n params?: Record<string, string | number>,\n ) => tWithFallback(t, key, fallback, params);\n const { createJob, isPending } = useCreateExportJob();\n const { data: availableEntities } = useAvailableEntities();\n const { data: templates } = useExportTemplates();\n const [searchParams] = useSearchParams();\n\n // RBAC: Check permissions for admin scopes\n const canExportWorkspaceAdmin = usePermission(\"export:workspace:admin\");\n const canExportOrgAdmin = usePermission(\"export:org:admin\");\n\n const [step, setStep] = useState<WizardStep>(\"entities\");\n const [selectedEntities, setSelectedEntities] = useState<string[]>([]);\n const [exportName, setExportName] = useState(\"\");\n const [selectedFormat, setSelectedFormat] = useState<ExportFormat>(\"CSV\");\n const [selectedScope, setSelectedScope] = useState<ExportScope>(\"INDIVIDUAL\");\n const [selectedTemplateId, setSelectedTemplateId] = useState<\n string | undefined\n >(searchParams.get(\"templateId\") ?? undefined);\n const [dateRangeStart, setDateRangeStart] = useState(\"\");\n const [dateRangeEnd, setDateRangeEnd] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n\n // Pre-populate form when arriving from \"Use Template\" — apply only once per templateId\n const appliedTemplateRef = useRef<string | null>(null);\n useEffect(() => {\n const paramTemplateId = searchParams.get(\"templateId\");\n if (!paramTemplateId || !templates?.length) return;\n if (appliedTemplateRef.current === paramTemplateId) return;\n const tpl = templates.find((t) => t.id === paramTemplateId);\n if (!tpl) return;\n appliedTemplateRef.current = paramTemplateId;\n setSelectedTemplateId(tpl.id);\n setSelectedEntities(tpl.entityTypes ?? []);\n const normalizedFormat = tpl.format?.toUpperCase() as\n | ExportFormat\n | undefined;\n if (normalizedFormat && SUPPORTED_FORMATS.includes(normalizedFormat)) {\n setSelectedFormat(normalizedFormat);\n }\n if (tpl.name) setExportName(tpl.name);\n }, [searchParams, templates]);\n\n const handleBack = () => {\n if (step === \"options\") setStep(\"entities\");\n else if (step === \"review\") setStep(\"options\");\n else navigate?.(basePath);\n };\n\n const handleNext = () => {\n if (step === \"entities\") {\n if (selectedEntities.length === 0) {\n setError(\n tr(\n \"export.create.errors.selectEntity\",\n \"Please select at least one entity type to export\",\n ),\n );\n return;\n }\n setError(null);\n setStep(\"options\");\n } else if (step === \"options\") {\n if (!exportName.trim()) {\n setError(\n tr(\n \"export.create.errors.exportNameRequired\",\n \"Please provide a name for this export\",\n ),\n );\n return;\n }\n setError(null);\n setStep(\"review\");\n }\n };\n\n const handleSubmit = async () => {\n try {\n setError(null);\n await createJob({\n name: exportName,\n entityTypes: selectedEntities,\n format: selectedFormat,\n exportScope: selectedScope,\n organizationId:\n selectedScope === \"ORG_ADMIN\" ? organizationId : undefined,\n templateId: selectedTemplateId,\n dateRangeStart: dateRangeStart\n ? new Date(dateRangeStart).toISOString()\n : null,\n dateRangeEnd: dateRangeEnd\n ? new Date(dateRangeEnd).toISOString()\n : null,\n });\n navigate?.(basePath);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"export.create.errors.createFailed\", \"Failed to create export\"),\n );\n }\n };\n\n const toggleEntity = (entityType: string) => {\n setSelectedEntities((prev) =>\n prev.includes(entityType)\n ? prev.filter((e) => e !== entityType)\n : [...prev, entityType],\n );\n };\n\n const getScopeLabel = (scope: ExportScope) => {\n switch (scope) {\n case \"INDIVIDUAL\":\n return tr(\"export.create.scope.individual.label\", \"My Data Only\");\n case \"WORKSPACE_ADMIN\":\n return tr(\n \"export.create.scope.workspaceAdmin.label\",\n \"All Workspace Data\",\n );\n case \"ORG_ADMIN\":\n return tr(\n \"export.create.scope.orgAdmin.label\",\n \"All Organization Data\",\n );\n default:\n return scope;\n }\n };\n\n const getScopeDescription = (scope: ExportScope) => {\n switch (scope) {\n case \"INDIVIDUAL\":\n return tr(\n \"export.create.scope.individual.description\",\n \"Export data you personally created across selected modules.\",\n );\n case \"WORKSPACE_ADMIN\":\n return tr(\n \"export.create.scope.workspaceAdmin.description\",\n \"Export all data in this workspace regardless of who created it. Requires workspace admin access.\",\n );\n case \"ORG_ADMIN\":\n return tr(\n \"export.create.scope.orgAdmin.description\",\n \"Export data from every workspace in your organization. Requires org-admin access.\",\n );\n default:\n return \"\";\n }\n };\n\n const fallbackEntityCopy: Record<\n string,\n { label: string; description: string }\n > = {\n users: {\n label: tr(\"export.create.entities.users.label\", \"Users\"),\n description: tr(\n \"export.create.entities.users.description\",\n \"Workspace members and profiles\",\n ),\n },\n projects: {\n label: tr(\"export.create.entities.projects.label\", \"Projects\"),\n description: tr(\n \"export.create.entities.projects.description\",\n \"Projects and their metadata\",\n ),\n },\n tasks: {\n label: tr(\"export.create.entities.tasks.label\", \"Tasks\"),\n description: tr(\n \"export.create.entities.tasks.description\",\n \"Task items and assignments\",\n ),\n },\n files: {\n label: tr(\"export.create.entities.files.label\", \"Files\"),\n description: tr(\n \"export.create.entities.files.description\",\n \"File metadata and references\",\n ),\n },\n conversations: {\n label: tr(\"export.create.entities.conversations.label\", \"Conversations\"),\n description: tr(\n \"export.create.entities.conversations.description\",\n \"Chat messages and threads\",\n ),\n },\n tags: {\n label: tr(\"export.create.entities.tags.label\", \"Tags\"),\n description: tr(\n \"export.create.entities.tags.description\",\n \"Tags and vocabularies\",\n ),\n },\n activity: {\n label: tr(\"export.create.entities.activity.label\", \"Activity Logs\"),\n description: tr(\n \"export.create.entities.activity.description\",\n \"Audit trail and activity history\",\n ),\n },\n calendar: {\n label: tr(\"export.create.entities.calendar.label\", \"Calendar Events\"),\n description: tr(\n \"export.create.entities.calendar.description\",\n \"Events and bookings\",\n ),\n },\n };\n\n // Only show enabled entities\n const enabledEntities = availableEntities.filter((e) => e.enabled !== false);\n\n return (\n <PageLayout showHeader={false}>\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-xl bg-gradient-to-br from-primary/5 via-primary/3 to-background border border-border/50 p-6 shadow-sm\">\n <div className=\"relative z-10 flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate?.(basePath)}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-all duration-200 hover:scale-110\"\n aria-label={tr(\n \"export.create.actions.backToDashboard\",\n \"Back to export dashboard\",\n )}\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2.5 rounded-lg bg-primary/10 backdrop-blur-sm\">\n <Download size={24} className=\"text-primary\" />\n </div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tr(\"export.create.title\", \"New Export\")}\n </h1>\n </div>\n </div>\n </div>\n\n {/* Step Indicator */}\n <div className=\"flex items-center gap-2 justify-center flex-wrap\">\n {([\"entities\", \"options\", \"review\"] as WizardStep[]).map((s, i) => (\n <div key={s} className=\"flex items-center gap-2\">\n {i > 0 && (\n <ArrowRight size={18} className=\"text-muted-foreground\" />\n )}\n <div\n className={cn(\n \"flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium border-2 transition-all duration-200\",\n step === s\n ? \"bg-primary text-primary-foreground border-primary shadow-md scale-105\"\n : \"bg-muted/50 text-muted-foreground border-border/50\",\n )}\n >\n <div className=\"flex items-center justify-center size-6 rounded-full bg-background/20 font-bold\">\n {i + 1}\n </div>\n <span className=\"hidden sm:inline capitalize\">\n {s === \"entities\"\n ? tr(\"export.create.steps.entities\", \"Select Data\")\n : s === \"options\"\n ? tr(\"export.create.steps.options\", \"Options\")\n : tr(\"export.create.steps.review\", \"Review\")}\n </span>\n </div>\n </div>\n ))}\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-4 text-status-error-text text-sm\">\n {error}\n </div>\n )}\n\n {/* Step 1: Select Entities */}\n {step === \"entities\" && (\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-bold\">\n {tr(\"export.create.select.title\", \"Select data to export\")}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n \"export.create.select.description\",\n \"Choose which entity types to include in your export.\",\n )}\n </p>\n {enabledEntities.length === 0 ? (\n <div className=\"border-2 border-border rounded-xl bg-muted/30 p-6 text-sm text-muted-foreground\">\n {tr(\n \"export.create.select.empty\",\n \"No exportable entity types are configured for this workspace yet.\",\n )}\n </div>\n ) : (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {enabledEntities.map((entity) => {\n const isSelected = selectedEntities.includes(entity.entityType);\n return (\n <button\n type=\"button\"\n key={entity.entityType}\n onClick={() => toggleEntity(entity.entityType)}\n className={cn(\n \"text-left border-2 rounded-xl p-4 transition-all duration-200\",\n isSelected\n ? \"border-primary bg-primary/5 shadow-md\"\n : \"border-border/50 hover:border-primary/30 hover:shadow-sm\",\n )}\n >\n <div className=\"flex items-center justify-between mb-2\">\n <h3 className=\"font-semibold\">\n {fallbackEntityCopy[entity.entityType]?.label ??\n entity.displayName}\n </h3>\n {isSelected && (\n <CheckCircle2 size={18} className=\"text-primary\" />\n )}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {fallbackEntityCopy[entity.entityType]?.description ??\n entity.description}\n </p>\n {entity.estimatedSize && (\n <p className=\"text-xs text-muted-foreground mt-2\">\n ~{entity.estimatedSize}\n </p>\n )}\n </button>\n );\n })}\n </div>\n )}\n </div>\n )}\n\n {/* Step 2: Configure Options */}\n {step === \"options\" && (\n <div className=\"space-y-6\">\n <div className=\"border-2 border-border/50 rounded-xl p-6 space-y-5\">\n <h2 className=\"text-lg font-bold\">\n {tr(\"export.create.options.title\", \"Export Options\")}\n </h2>\n\n {/* Scope selection */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\"export.create.options.scope\", \"Export Scope\")}\n </label>\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-3\">\n {SCOPE_OPTIONS.map(({ value, Icon }) => {\n // RBAC: Disable admin scopes if user lacks permission\n const isDisabled =\n (value === \"WORKSPACE_ADMIN\" && !canExportWorkspaceAdmin) ||\n (value === \"ORG_ADMIN\" && !canExportOrgAdmin);\n\n return (\n <button\n key={value}\n type=\"button\"\n onClick={() => !isDisabled && setSelectedScope(value)}\n disabled={isDisabled}\n className={cn(\n \"text-left border-2 rounded-xl p-4 transition-all duration-200\",\n selectedScope === value\n ? \"border-primary bg-primary/5 shadow-md\"\n : isDisabled\n ? \"border-border/30 opacity-50 cursor-not-allowed\"\n : \"border-border/50 hover:border-primary/30 hover:shadow-sm\",\n )}\n title={\n isDisabled\n ? value === \"WORKSPACE_ADMIN\"\n ? \"Workspace admin access required\"\n : \"Organization admin access required\"\n : undefined\n }\n >\n <div className=\"flex items-center justify-between mb-2\">\n <Icon\n size={18}\n className={\n selectedScope === value\n ? \"text-primary\"\n : isDisabled\n ? \"text-muted-foreground/50\"\n : \"text-muted-foreground\"\n }\n />\n {selectedScope === value && (\n <CheckCircle2 size={16} className=\"text-primary\" />\n )}\n {isDisabled && (\n <Lock\n size={14}\n className=\"text-muted-foreground/50\"\n />\n )}\n </div>\n <p className=\"font-semibold text-sm\">\n {getScopeLabel(value)}\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {getScopeDescription(value)}\n </p>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Name */}\n <div>\n <label\n htmlFor=\"exportName\"\n className=\"block text-sm font-semibold mb-2\"\n >\n {tr(\"export.create.options.name\", \"Export Name\")}{\" \"}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n type=\"text\"\n id=\"exportName\"\n aria-label={tr(\"export.create.options.name\", \"Export Name\")}\n value={exportName}\n onChange={(e) => setExportName(e.target.value)}\n className=\"w-full px-4 py-2.5 border-2 border-border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all duration-200\"\n placeholder={tr(\n \"export.create.options.namePlaceholder\",\n \"e.g., Monthly User Export\",\n )}\n />\n </div>\n\n {/* Format — only CSV and JSON are supported */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\"export.create.options.outputFormat\", \"Output Format\")}\n </label>\n <div className=\"grid grid-cols-2 gap-3\">\n {SUPPORTED_FORMATS.map((format) => {\n const Icon = FORMAT_ICONS[format];\n return (\n <button\n type=\"button\"\n key={format}\n onClick={() => setSelectedFormat(format)}\n className={cn(\n \"flex items-center gap-3 border-2 rounded-lg p-3 transition-all duration-200\",\n selectedFormat === format\n ? \"border-primary bg-primary/5\"\n : \"border-border/50 hover:border-primary/30\",\n )}\n >\n <Icon\n size={18}\n className={\n selectedFormat === format\n ? \"text-primary\"\n : \"text-muted-foreground\"\n }\n />\n <span className=\"font-medium text-sm\">\n {getFormatDisplayName(format)}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Archive format — only ZIP is supported */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\"export.create.options.archiveFormat\", \"Archive Format\")}\n </label>\n <div className=\"flex gap-3\">\n <div\n className={cn(\n \"flex items-center gap-2 border-2 rounded-lg px-4 py-2 border-primary bg-primary/5\",\n )}\n >\n <Package size={16} className=\"text-primary\" />\n <span className=\"font-medium text-sm\">ZIP</span>\n </div>\n </div>\n <p className=\"text-xs text-muted-foreground mt-1.5\">\n {tr(\n \"export.create.options.archiveDescription\",\n \"Exports are packaged as ZIP archives.\",\n )}\n </p>\n </div>\n\n {/* Date range filter — always visible */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n <span className=\"inline-flex items-center gap-1.5\">\n <CalendarRange size={15} />\n {tr(\"export.create.options.dateRange\", \"Date Range\")}{\" \"}\n <span className=\"font-normal text-muted-foreground\">\n {tr(\"export.create.shared.optional\", \"(optional)\")}\n </span>\n </span>\n </label>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n <div>\n <label\n htmlFor=\"dateFrom\"\n className=\"block text-xs text-muted-foreground mb-1\"\n >\n {tr(\"export.create.options.dateFrom\", \"From\")}\n </label>\n <input\n type=\"date\"\n id=\"dateFrom\"\n aria-label={tr(\"export.create.options.dateFrom\", \"From\")}\n value={dateRangeStart}\n onChange={(e) => setDateRangeStart(e.target.value)}\n className=\"w-full px-3 py-2 border-2 border-border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary text-sm\"\n />\n </div>\n <div>\n <label\n htmlFor=\"dateTo\"\n className=\"block text-xs text-muted-foreground mb-1\"\n >\n {tr(\"export.create.options.dateTo\", \"To\")}\n </label>\n <input\n type=\"date\"\n id=\"dateTo\"\n aria-label={tr(\"export.create.options.dateTo\", \"To\")}\n value={dateRangeEnd}\n min={dateRangeStart}\n onChange={(e) => setDateRangeEnd(e.target.value)}\n className=\"w-full px-3 py-2 border-2 border-border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary text-sm\"\n />\n </div>\n </div>\n <p className=\"text-xs text-muted-foreground mt-1.5\">\n {tr(\n \"export.create.options.dateRangeDescription\",\n \"Leave blank to export all data regardless of date.\",\n )}\n </p>\n </div>\n\n {/* Optional: Use a template */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\n \"export.create.options.basedOnTemplate\",\n \"Based on Template\",\n )}{\" \"}\n <span className=\"font-normal text-muted-foreground\">\n {tr(\"export.create.shared.optional\", \"(optional)\")}\n </span>\n </label>\n {templates.length > 0 ? (\n <select\n value={selectedTemplateId ?? \"\"}\n onChange={(e) =>\n setSelectedTemplateId(e.target.value || undefined)\n }\n className=\"w-full px-3 py-2 border-2 border-border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary\"\n >\n <option value=\"\">\n {tr(\"export.create.options.noTemplate\", \"No template\")}\n </option>\n {templates.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {tr(\"export.create.options.noTemplates\", \"No templates yet.\")}{\" \"}\n <button\n type=\"button\"\n onClick={() => navigate?.(basePath + \"/templates\")}\n className=\"text-primary underline-offset-2 hover:underline\"\n >\n {tr(\n \"export.create.options.createTemplateLink\",\n \"Create one\",\n )}\n </button>{\" \"}\n {tr(\n \"export.create.options.reuseTemplateHint\",\n \"to reuse export configurations.\",\n )}\n </p>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* Step 3: Review */}\n {step === \"review\" && (\n <div className=\"space-y-6\">\n <div className=\"border-2 border-primary/50 rounded-xl bg-primary/5 p-6 space-y-4\">\n <h2 className=\"text-lg font-bold flex items-center gap-2\">\n <CheckCircle2 size={20} className=\"text-primary\" />\n {tr(\"export.create.review.title\", \"Review Export\")}\n </h2>\n <dl className=\"space-y-3 text-sm\">\n {[\n {\n label: tr(\"export.create.review.fields.name\", \"Name\"),\n value: exportName,\n },\n {\n label: tr(\"export.create.review.fields.scope\", \"Scope\"),\n value: getScopeLabel(selectedScope),\n },\n {\n label: tr(\n \"export.create.review.fields.entityTypes\",\n \"Entity Types\",\n ),\n value: selectedEntities.join(\", \"),\n },\n {\n label: tr(\"export.create.review.fields.format\", \"Format\"),\n value: getFormatDisplayName(selectedFormat),\n },\n {\n label: tr(\"export.create.review.fields.archive\", \"Archive\"),\n value: \"ZIP\",\n },\n ...(dateRangeStart || dateRangeEnd\n ? [\n {\n label: tr(\n \"export.create.review.fields.dateRange\",\n \"Date Range\",\n ),\n value: [\n dateRangeStart || \"—\",\n dateRangeEnd || \"—\",\n ].join(\" → \"),\n },\n ]\n : []),\n ...(selectedTemplateId\n ? [\n {\n label: tr(\n \"export.create.review.fields.template\",\n \"Template\",\n ),\n value:\n templates.find((t) => t.id === selectedTemplateId)\n ?.name ?? selectedTemplateId,\n },\n ]\n : []),\n ].map(({ label, value }) => (\n <div\n key={label}\n className=\"flex flex-col sm:flex-row sm:justify-between gap-1\"\n >\n <dt className=\"text-muted-foreground\">{label}</dt>\n <dd className=\"font-semibold sm:text-right break-words\">\n {value}\n </dd>\n </div>\n ))}\n </dl>\n </div>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex flex-col sm:flex-row gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"px-6 py-3 border-2 border-border/50 rounded-lg hover:bg-accent hover:border-primary/30 transition-all duration-200 font-medium\"\n >\n {step === \"entities\"\n ? tr(\"export.create.actions.cancel\", \"Cancel\")\n : tr(\"export.create.actions.back\", \"Back\")}\n </button>\n {step === \"review\" ? (\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={isPending}\n className=\"flex-1 inline-flex items-center justify-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-all duration-200 hover:scale-105 font-bold shadow-lg disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isPending ? (\n <Loader2 size={18} className=\"animate-spin\" />\n ) : (\n <Download size={18} />\n )}\n {tr(\"export.create.actions.startExport\", \"Start Export\")}\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={handleNext}\n className=\"flex-1 inline-flex items-center justify-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-all duration-200 hover:scale-105 font-bold shadow-lg\"\n >\n {tr(\"export.create.actions.next\", \"Next\")}\n <ArrowRight size={18} />\n </button>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -51,6 +51,7 @@ function y() {
|
|
|
51
51
|
color: "bg-status-success-bg-subtle text-status-success-text"
|
|
52
52
|
}
|
|
53
53
|
].map((e) => /* @__PURE__ */ g("button", {
|
|
54
|
+
type: "button",
|
|
54
55
|
onClick: () => D(e.path),
|
|
55
56
|
className: n("group relative overflow-hidden border-2 border-border/50 rounded-xl p-5", "bg-gradient-to-br from-card/50 to-background shadow-sm", "hover:shadow-md hover:border-primary/30 transition-all duration-200", "text-left"),
|
|
56
57
|
children: [
|
|
@@ -79,6 +80,7 @@ function y() {
|
|
|
79
80
|
children: /* @__PURE__ */ h("div", {
|
|
80
81
|
className: "space-y-3",
|
|
81
82
|
children: E.map((e) => /* @__PURE__ */ g("button", {
|
|
83
|
+
type: "button",
|
|
82
84
|
onClick: () => D(e.id),
|
|
83
85
|
className: n("w-full text-left border-2 border-border/50 rounded-xl p-4", "hover:shadow-md hover:border-primary/30 transition-all duration-200", "bg-gradient-to-r from-card/50 to-background"),
|
|
84
86
|
children: [/* @__PURE__ */ g("div", {
|
|
@@ -98,6 +100,7 @@ function y() {
|
|
|
98
100
|
title: S("export.dashboard.recent.title", "Recent Exports"),
|
|
99
101
|
description: S("export.dashboard.recent.description", "Your latest export jobs"),
|
|
100
102
|
actions: T.length > 0 ? /* @__PURE__ */ h("button", {
|
|
103
|
+
type: "button",
|
|
101
104
|
onClick: () => D("history"),
|
|
102
105
|
className: "text-sm text-primary hover:underline font-medium",
|
|
103
106
|
children: S("export.dashboard.recent.actions.viewAll", "View All")
|
|
@@ -118,6 +121,7 @@ function y() {
|
|
|
118
121
|
}) : T.length > 0 ? /* @__PURE__ */ h("div", {
|
|
119
122
|
className: "space-y-3",
|
|
120
123
|
children: T.map((e, t) => /* @__PURE__ */ h("button", {
|
|
124
|
+
type: "button",
|
|
121
125
|
onClick: () => D(e.id),
|
|
122
126
|
className: n("w-full text-left border-2 border-border/50 rounded-xl p-4", "hover:shadow-md hover:border-primary/30 transition-all duration-200", "bg-gradient-to-r from-card/50 to-background", "animate-in fade-in-0 slide-in-from-bottom-2"),
|
|
123
127
|
style: { animationDelay: `${t * 50}ms` },
|
|
@@ -151,6 +155,7 @@ function y() {
|
|
|
151
155
|
title: S("export.dashboard.empty.title", "No exports yet"),
|
|
152
156
|
description: S("export.dashboard.empty.description", "Create your first export to start downloading your workspace data."),
|
|
153
157
|
action: /* @__PURE__ */ g("button", {
|
|
158
|
+
type: "button",
|
|
154
159
|
onClick: () => D("new"),
|
|
155
160
|
className: "inline-flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors font-medium",
|
|
156
161
|
children: [/* @__PURE__ */ h(m, { size: 16 }), S("export.dashboard.empty.actions.createFirst", "Create First Export")]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportDashboardPage.js","names":[],"sources":["../../../src/export/pages/ExportDashboardPage.tsx"],"sourcesContent":["import {\n Plus,\n Download,\n Clock,\n FileBox,\n ArrowRight,\n Activity,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport { useExportJobs } from \"../hooks/useExportQueries\";\nimport { ExportStatusBadge } from \"../components/ExportStatusBadge\";\nimport { ExportProgressBar } from \"../components/ExportProgressBar\";\nimport { PageLayout, PageSection, EmptyState } from \"../components/PageLayout\";\nimport { cn } from \"../utils/cn\";\nimport { tWithFallback } from \"../utils/i18n\";\n\n/**\n * Helper to join paths correctly, avoiding double slashes\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 Dashboard Page\n * Overview with recent exports, quick actions, and status summary\n */\nexport function ExportDashboardPage() {\n const { basePath = \"/\", navigate } = useExport();\n const { t } = useI18n();\n const tr = (\n key: string,\n fallback: string,\n params?: Record<string, string | number>,\n ) => tWithFallback(t, key, fallback, params);\n const { data: jobs, isLoading } = useExportJobs({});\n\n const recentJobs = jobs.slice(0, 5);\n const activeJobs = jobs.filter(\n (j) => j.status === \"RUNNING\" || j.status === \"QUEUED\",\n );\n\n const handleNavigate = (path: string) => {\n if (!navigate) return;\n navigate(joinPath(basePath, path));\n };\n\n return (\n <PageLayout\n title={tr(\"export.dashboard.title\", \"Export\")}\n subtitle={tr(\n \"export.dashboard.subtitle\",\n \"Export your workspace data in multiple formats\",\n )}\n icon={<Download size={24} />}\n >\n {/* Quick Actions */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n {[\n {\n icon: <Plus size={20} />,\n label: tr(\"export.dashboard.actions.newExport.label\", \"New Export\"),\n description: tr(\n \"export.dashboard.actions.newExport.description\",\n \"Create a new data export\",\n ),\n path: \"new\",\n color: \"bg-primary/10 text-primary\",\n },\n {\n icon: <Clock size={20} />,\n label: tr(\"export.dashboard.actions.history.label\", \"History\"),\n description: tr(\n \"export.dashboard.actions.history.description\",\n \"View past exports\",\n ),\n path: \"history\",\n color: \"bg-status-info-bg-subtle text-status-info-text\",\n },\n {\n icon: <FileBox size={20} />,\n label: tr(\"export.dashboard.actions.templates.label\", \"Templates\"),\n description: tr(\n \"export.dashboard.actions.templates.description\",\n \"Reusable export configs\",\n ),\n path: \"templates\",\n color: \"bg-accent-purple-subtle text-accent-purple\",\n },\n {\n icon: <Activity size={20} />,\n label: tr(\n \"export.dashboard.actions.activeJobs.label\",\n \"Active Jobs\",\n ),\n description: tr(\n \"export.dashboard.actions.activeJobs.description\",\n \"{count} running\",\n { count: activeJobs.length },\n ),\n path: \"history?status=RUNNING\",\n color: \"bg-status-success-bg-subtle text-status-success-text\",\n },\n ].map((action) => (\n <button\n key={action.path + action.label}\n onClick={() => handleNavigate(action.path)}\n className={cn(\n \"group relative overflow-hidden border-2 border-border/50 rounded-xl p-5\",\n \"bg-gradient-to-br from-card/50 to-background shadow-sm\",\n \"hover:shadow-md hover:border-primary/30 transition-all duration-200\",\n \"text-left\",\n )}\n >\n <div className={cn(\"p-2.5 rounded-lg w-fit mb-3\", action.color)}>\n {action.icon}\n </div>\n <h3 className=\"font-semibold text-foreground mb-1\">\n {action.label}\n </h3>\n <p className=\"text-sm text-muted-foreground\">\n {action.description}\n </p>\n <ArrowRight\n size={16}\n className=\"absolute top-5 right-5 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity\"\n />\n </button>\n ))}\n </div>\n\n {/* Active Exports */}\n {activeJobs.length > 0 && (\n <PageSection\n title={tr(\"export.dashboard.active.title\", \"Active Exports\")}\n description={tr(\n \"export.dashboard.active.description\",\n \"Currently running export jobs\",\n )}\n >\n <div className=\"space-y-3\">\n {activeJobs.map((job) => (\n <button\n key={job.id}\n onClick={() => handleNavigate(job.id)}\n className={cn(\n \"w-full text-left border-2 border-border/50 rounded-xl p-4\",\n \"hover:shadow-md hover:border-primary/30 transition-all duration-200\",\n \"bg-gradient-to-r from-card/50 to-background\",\n )}\n >\n <div className=\"flex items-center justify-between mb-2\">\n <h4 className=\"font-semibold text-foreground\">{job.name}</h4>\n <ExportStatusBadge status={job.status} />\n </div>\n <ExportProgressBar progress={job.progress} size=\"sm\" />\n </button>\n ))}\n </div>\n </PageSection>\n )}\n\n {/* Recent Exports */}\n <PageSection\n title={tr(\"export.dashboard.recent.title\", \"Recent Exports\")}\n description={tr(\n \"export.dashboard.recent.description\",\n \"Your latest export jobs\",\n )}\n actions={\n recentJobs.length > 0 ? (\n <button\n onClick={() => handleNavigate(\"history\")}\n className=\"text-sm text-primary hover:underline font-medium\"\n >\n {tr(\"export.dashboard.recent.actions.viewAll\", \"View All\")}\n </button>\n ) : undefined\n }\n >\n {isLoading ? (\n <div className=\"space-y-3\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"border-2 border-border/50 rounded-xl p-4 animate-pulse\"\n >\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"h-4 w-48 bg-muted rounded\" />\n <div className=\"h-5 w-20 bg-muted rounded-full\" />\n </div>\n <div className=\"h-3 w-32 bg-muted rounded mt-2\" />\n </div>\n ))}\n </div>\n ) : recentJobs.length > 0 ? (\n <div className=\"space-y-3\">\n {recentJobs.map((job, index) => (\n <button\n key={job.id}\n onClick={() => handleNavigate(job.id)}\n className={cn(\n \"w-full text-left border-2 border-border/50 rounded-xl p-4\",\n \"hover:shadow-md hover:border-primary/30 transition-all duration-200\",\n \"bg-gradient-to-r from-card/50 to-background\",\n \"animate-in fade-in-0 slide-in-from-bottom-2\",\n )}\n style={{ animationDelay: `${index * 50}ms` }}\n >\n <div className=\"flex items-start sm:items-center justify-between gap-3\">\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-semibold text-foreground truncate\">\n {job.name}\n </h4>\n <p className=\"text-sm text-muted-foreground mt-1 line-clamp-2\">\n {job.entityTypes.join(\", \")} · {job.format}\n {job.totalEntities != null &&\n ` · ${tr(\n \"export.dashboard.recent.entitiesCount\",\n \"{count} entities\",\n { count: job.totalEntities.toLocaleString() },\n )}`}\n </p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <ExportStatusBadge status={job.status} />\n <ArrowRight\n size={16}\n className=\"text-muted-foreground hidden sm:block\"\n />\n </div>\n </div>\n </button>\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<Download size={48} />}\n title={tr(\"export.dashboard.empty.title\", \"No exports yet\")}\n description={tr(\n \"export.dashboard.empty.description\",\n \"Create your first export to start downloading your workspace data.\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"new\")}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors font-medium\"\n >\n <Plus size={16} />\n {tr(\n \"export.dashboard.empty.actions.createFirst\",\n \"Create First Export\",\n )}\n </button>\n }\n />\n )}\n </PageSection>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExportDashboardPage.js","names":[],"sources":["../../../src/export/pages/ExportDashboardPage.tsx"],"sourcesContent":["import {\n Plus,\n Download,\n Clock,\n FileBox,\n ArrowRight,\n Activity,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport { useExportJobs } from \"../hooks/useExportQueries\";\nimport { ExportStatusBadge } from \"../components/ExportStatusBadge\";\nimport { ExportProgressBar } from \"../components/ExportProgressBar\";\nimport { PageLayout, PageSection, EmptyState } from \"../components/PageLayout\";\nimport { cn } from \"../utils/cn\";\nimport { tWithFallback } from \"../utils/i18n\";\n\n/**\n * Helper to join paths correctly, avoiding double slashes\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 Dashboard Page\n * Overview with recent exports, quick actions, and status summary\n */\nexport function ExportDashboardPage() {\n const { basePath = \"/\", navigate } = useExport();\n const { t } = useI18n();\n const tr = (\n key: string,\n fallback: string,\n params?: Record<string, string | number>,\n ) => tWithFallback(t, key, fallback, params);\n const { data: jobs, isLoading } = useExportJobs({});\n\n const recentJobs = jobs.slice(0, 5);\n const activeJobs = jobs.filter(\n (j) => j.status === \"RUNNING\" || j.status === \"QUEUED\",\n );\n\n const handleNavigate = (path: string) => {\n if (!navigate) return;\n navigate(joinPath(basePath, path));\n };\n\n return (\n <PageLayout\n title={tr(\"export.dashboard.title\", \"Export\")}\n subtitle={tr(\n \"export.dashboard.subtitle\",\n \"Export your workspace data in multiple formats\",\n )}\n icon={<Download size={24} />}\n >\n {/* Quick Actions */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n {[\n {\n icon: <Plus size={20} />,\n label: tr(\"export.dashboard.actions.newExport.label\", \"New Export\"),\n description: tr(\n \"export.dashboard.actions.newExport.description\",\n \"Create a new data export\",\n ),\n path: \"new\",\n color: \"bg-primary/10 text-primary\",\n },\n {\n icon: <Clock size={20} />,\n label: tr(\"export.dashboard.actions.history.label\", \"History\"),\n description: tr(\n \"export.dashboard.actions.history.description\",\n \"View past exports\",\n ),\n path: \"history\",\n color: \"bg-status-info-bg-subtle text-status-info-text\",\n },\n {\n icon: <FileBox size={20} />,\n label: tr(\"export.dashboard.actions.templates.label\", \"Templates\"),\n description: tr(\n \"export.dashboard.actions.templates.description\",\n \"Reusable export configs\",\n ),\n path: \"templates\",\n color: \"bg-accent-purple-subtle text-accent-purple\",\n },\n {\n icon: <Activity size={20} />,\n label: tr(\n \"export.dashboard.actions.activeJobs.label\",\n \"Active Jobs\",\n ),\n description: tr(\n \"export.dashboard.actions.activeJobs.description\",\n \"{count} running\",\n { count: activeJobs.length },\n ),\n path: \"history?status=RUNNING\",\n color: \"bg-status-success-bg-subtle text-status-success-text\",\n },\n ].map((action) => (\n <button\n type=\"button\"\n key={action.path + action.label}\n onClick={() => handleNavigate(action.path)}\n className={cn(\n \"group relative overflow-hidden border-2 border-border/50 rounded-xl p-5\",\n \"bg-gradient-to-br from-card/50 to-background shadow-sm\",\n \"hover:shadow-md hover:border-primary/30 transition-all duration-200\",\n \"text-left\",\n )}\n >\n <div className={cn(\"p-2.5 rounded-lg w-fit mb-3\", action.color)}>\n {action.icon}\n </div>\n <h3 className=\"font-semibold text-foreground mb-1\">\n {action.label}\n </h3>\n <p className=\"text-sm text-muted-foreground\">\n {action.description}\n </p>\n <ArrowRight\n size={16}\n className=\"absolute top-5 right-5 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity\"\n />\n </button>\n ))}\n </div>\n\n {/* Active Exports */}\n {activeJobs.length > 0 && (\n <PageSection\n title={tr(\"export.dashboard.active.title\", \"Active Exports\")}\n description={tr(\n \"export.dashboard.active.description\",\n \"Currently running export jobs\",\n )}\n >\n <div className=\"space-y-3\">\n {activeJobs.map((job) => (\n <button\n type=\"button\"\n key={job.id}\n onClick={() => handleNavigate(job.id)}\n className={cn(\n \"w-full text-left border-2 border-border/50 rounded-xl p-4\",\n \"hover:shadow-md hover:border-primary/30 transition-all duration-200\",\n \"bg-gradient-to-r from-card/50 to-background\",\n )}\n >\n <div className=\"flex items-center justify-between mb-2\">\n <h4 className=\"font-semibold text-foreground\">{job.name}</h4>\n <ExportStatusBadge status={job.status} />\n </div>\n <ExportProgressBar progress={job.progress} size=\"sm\" />\n </button>\n ))}\n </div>\n </PageSection>\n )}\n\n {/* Recent Exports */}\n <PageSection\n title={tr(\"export.dashboard.recent.title\", \"Recent Exports\")}\n description={tr(\n \"export.dashboard.recent.description\",\n \"Your latest export jobs\",\n )}\n actions={\n recentJobs.length > 0 ? (\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"history\")}\n className=\"text-sm text-primary hover:underline font-medium\"\n >\n {tr(\"export.dashboard.recent.actions.viewAll\", \"View All\")}\n </button>\n ) : undefined\n }\n >\n {isLoading ? (\n <div className=\"space-y-3\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"border-2 border-border/50 rounded-xl p-4 animate-pulse\"\n >\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"h-4 w-48 bg-muted rounded\" />\n <div className=\"h-5 w-20 bg-muted rounded-full\" />\n </div>\n <div className=\"h-3 w-32 bg-muted rounded mt-2\" />\n </div>\n ))}\n </div>\n ) : recentJobs.length > 0 ? (\n <div className=\"space-y-3\">\n {recentJobs.map((job, index) => (\n <button\n type=\"button\"\n key={job.id}\n onClick={() => handleNavigate(job.id)}\n className={cn(\n \"w-full text-left border-2 border-border/50 rounded-xl p-4\",\n \"hover:shadow-md hover:border-primary/30 transition-all duration-200\",\n \"bg-gradient-to-r from-card/50 to-background\",\n \"animate-in fade-in-0 slide-in-from-bottom-2\",\n )}\n style={{ animationDelay: `${index * 50}ms` }}\n >\n <div className=\"flex items-start sm:items-center justify-between gap-3\">\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-semibold text-foreground truncate\">\n {job.name}\n </h4>\n <p className=\"text-sm text-muted-foreground mt-1 line-clamp-2\">\n {job.entityTypes.join(\", \")} · {job.format}\n {job.totalEntities != null &&\n ` · ${tr(\n \"export.dashboard.recent.entitiesCount\",\n \"{count} entities\",\n { count: job.totalEntities.toLocaleString() },\n )}`}\n </p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <ExportStatusBadge status={job.status} />\n <ArrowRight\n size={16}\n className=\"text-muted-foreground hidden sm:block\"\n />\n </div>\n </div>\n </button>\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<Download size={48} />}\n title={tr(\"export.dashboard.empty.title\", \"No exports yet\")}\n description={tr(\n \"export.dashboard.empty.description\",\n \"Create your first export to start downloading your workspace data.\",\n )}\n action={\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"new\")}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors font-medium\"\n >\n <Plus size={16} />\n {tr(\n \"export.dashboard.empty.actions.createFirst\",\n \"Create First Export\",\n )}\n </button>\n }\n />\n )}\n </PageSection>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -34,7 +34,7 @@ function P() {
|
|
|
34
34
|
className: "animate-spin text-primary"
|
|
35
35
|
}), /* @__PURE__ */ A("span", {
|
|
36
36
|
className: "mt-6 text-sm font-medium text-muted-foreground",
|
|
37
|
-
children: "Loading export details
|
|
37
|
+
children: "Loading export details…"
|
|
38
38
|
})]
|
|
39
39
|
}) : X ? /* @__PURE__ */ j(d, {
|
|
40
40
|
showHeader: !1,
|
|
@@ -44,6 +44,7 @@ function P() {
|
|
|
44
44
|
children: /* @__PURE__ */ j("div", {
|
|
45
45
|
className: "relative z-10 flex items-center gap-4",
|
|
46
46
|
children: [/* @__PURE__ */ A("button", {
|
|
47
|
+
type: "button",
|
|
47
48
|
onClick: R,
|
|
48
49
|
className: "p-2.5 hover:bg-accent rounded-lg transition-all duration-200",
|
|
49
50
|
children: /* @__PURE__ */ A(_, { size: 20 })
|
|
@@ -201,6 +202,7 @@ function P() {
|
|
|
201
202
|
className: "flex flex-col sm:flex-row gap-3 pt-2",
|
|
202
203
|
children: [
|
|
203
204
|
Z && /* @__PURE__ */ j("button", {
|
|
205
|
+
type: "button",
|
|
204
206
|
onClick: async () => {
|
|
205
207
|
P && U("cancel");
|
|
206
208
|
},
|
|
@@ -212,6 +214,7 @@ function P() {
|
|
|
212
214
|
}) : /* @__PURE__ */ A(D, { size: 18 }), "Cancel Export"]
|
|
213
215
|
}),
|
|
214
216
|
ee && /* @__PURE__ */ j("button", {
|
|
217
|
+
type: "button",
|
|
215
218
|
onClick: async () => {
|
|
216
219
|
if (!P) return;
|
|
217
220
|
let e = await J(P);
|
|
@@ -225,6 +228,7 @@ function P() {
|
|
|
225
228
|
}) : /* @__PURE__ */ A(T, { size: 18 }), "Retry Export"]
|
|
226
229
|
}),
|
|
227
230
|
!Z && /* @__PURE__ */ j("button", {
|
|
231
|
+
type: "button",
|
|
228
232
|
onClick: async () => {
|
|
229
233
|
P && (p("medium"), U("delete"));
|
|
230
234
|
},
|
|
@@ -290,6 +294,7 @@ function P() {
|
|
|
290
294
|
children: [/* @__PURE__ */ j("div", {
|
|
291
295
|
className: "flex items-center gap-3 mb-6",
|
|
292
296
|
children: [/* @__PURE__ */ A("button", {
|
|
297
|
+
type: "button",
|
|
293
298
|
onClick: R,
|
|
294
299
|
className: "p-2.5 hover:bg-accent rounded-lg transition-all duration-200",
|
|
295
300
|
children: /* @__PURE__ */ A(_, { size: 20 })
|
|
@@ -313,6 +318,7 @@ function P() {
|
|
|
313
318
|
children: "The export job you are looking for does not exist or has been deleted."
|
|
314
319
|
}),
|
|
315
320
|
/* @__PURE__ */ j("button", {
|
|
321
|
+
type: "button",
|
|
316
322
|
onClick: R,
|
|
317
323
|
className: "inline-flex items-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors font-medium",
|
|
318
324
|
children: [/* @__PURE__ */ A(_, { size: 18 }), "Back to Dashboard"]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportDetailPage.js","names":[],"sources":["../../../src/export/pages/ExportDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, useNavigate } from \"react-router-dom\";\nimport {\n ArrowLeft,\n Download,\n XCircle,\n Trash2,\n RotateCcw,\n Loader2,\n FileDown,\n Clock,\n AlertCircle,\n CheckCircle2,\n HardDrive,\n FileText,\n} from \"lucide-react\";\nimport { 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 // 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-muted-foreground\">\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 onClick={handleBack}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-all duration-200\"\n >\n <ArrowLeft size={20} />\n </button>\n <h1 className=\"text-xl font-bold\">Export Not Found</h1>\n </div>\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-12 text-center\">\n <AlertCircle\n size={48}\n className=\"text-status-error-text mx-auto mb-4\"\n />\n <h3 className=\"text-lg font-bold text-status-error-text mb-2\">\n Export Not Found\n </h3>\n <p className=\"text-status-error-text mb-6\">\n The export job you are looking for does not exist or has been\n deleted.\n </p>\n <button\n onClick={handleBack}\n className=\"inline-flex items-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors font-medium\"\n >\n <ArrowLeft size={18} />\n Back to Dashboard\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout showHeader={false}>\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-xl bg-gradient-to-br from-primary/5 via-primary/3 to-background border border-border/50 p-6 shadow-sm\">\n <div className=\"relative z-10 flex items-center gap-4\">\n <button\n onClick={handleBack}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-all duration-200\"\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex items-center gap-3 flex-1\">\n <div className=\"p-2.5 rounded-lg bg-primary/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-muted-foreground mt-1\">\n {liveJob.entityTypes.join(\", \")}\n </p>\n </div>\n <ExportStatusBadge status={liveJob.status} />\n </div>\n </div>\n </div>\n\n {/* Progress (for active jobs) */}\n {(liveJob.status === \"RUNNING\" || liveJob.status === \"QUEUED\") && (\n <div className=\"border-2 border-border-default 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-muted-foreground 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 <div\n key={item.label}\n className=\"border-2 border-border/50 rounded-xl p-4 bg-gradient-to-br from-card/50 to-background\"\n >\n <div className=\"flex items-center gap-2 text-muted-foreground mb-1\">\n {item.icon}\n <span className=\"text-xs font-medium\">{item.label}</span>\n </div>\n <p className=\"font-bold text-lg\">{item.value}</p>\n </div>\n ))}\n </div>\n\n {/* Parts */}\n {liveJob.parts.length > 0 && (\n <PageSection\n title=\"Export Parts\"\n description={`${liveJob.parts.length} file(s) generated`}\n >\n <div className=\"space-y-2\">\n {liveJob.parts.map((part) => (\n <div\n key={part.id}\n className={cn(\n \"border-2 border-border/50 rounded-xl p-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\",\n \"hover:border-primary/30 transition-all 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-muted-foreground\">\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-primary text-primary-foreground rounded-lg hover:bg-primary/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-muted-foreground\">\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 {/* Actions */}\n <div className=\"flex flex-col sm:flex-row gap-3 pt-2\">\n {isCancellable && (\n <button\n onClick={handleCancel}\n disabled={cancelling}\n className=\"inline-flex items-center justify-center gap-2 px-6 py-3 border-2 border-status-warning-bg/50 text-status-warning-text rounded-lg hover:bg-status-warning-bg-subtle transition-all duration-200 font-medium disabled:opacity-50\"\n >\n {cancelling ? (\n <Loader2 size={18} className=\"animate-spin\" />\n ) : (\n <XCircle size={18} />\n )}\n Cancel Export\n </button>\n )}\n {isRetryable && (\n <button\n onClick={handleRetry}\n disabled={retrying}\n className=\"inline-flex items-center justify-center gap-2 px-6 py-3 border-2 border-primary/50 text-primary rounded-lg hover:bg-primary/5 transition-all duration-200 font-medium disabled:opacity-50\"\n >\n {retrying ? (\n <Loader2 size={18} className=\"animate-spin\" />\n ) : (\n <RotateCcw size={18} />\n )}\n Retry Export\n </button>\n )}\n {!isCancellable && (\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"inline-flex items-center justify-center gap-2 px-6 py-3 border-2 border-status-error-bg/50 text-status-error-text rounded-lg hover:bg-status-error-bg-subtle transition-all duration-200 font-medium disabled:opacity-50\"\n >\n {deleting ? (\n <Loader2 size={18} className=\"animate-spin\" />\n ) : (\n <Trash2 size={18} />\n )}\n Delete Export\n </button>\n )}\n </div>\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-background/80 backdrop-blur-sm p-4\"\n >\n <div className=\"w-full max-w-md rounded-xl border-2 border-border bg-card 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-muted-foreground\">\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 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-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors font-medium disabled:opacity-50\"\n >\n {cancelling || deleting ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : null}\n {pendingAction === \"cancel\" ? \"Cancel Export\" : \"Delete Export\"}\n </button>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExportDetailPage.js","names":[],"sources":["../../../src/export/pages/ExportDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, useNavigate } from \"react-router-dom\";\nimport {\n ArrowLeft,\n Download,\n XCircle,\n Trash2,\n RotateCcw,\n Loader2,\n FileDown,\n Clock,\n AlertCircle,\n CheckCircle2,\n HardDrive,\n FileText,\n} from \"lucide-react\";\nimport { 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 // 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-muted-foreground\">\n Loading export details…\n </span>\n </div>\n );\n }\n\n // Not found\n if (!liveJob) {\n return (\n <PageLayout showHeader={false}>\n <div className=\"flex items-center gap-3 mb-6\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-all duration-200\"\n >\n <ArrowLeft size={20} />\n </button>\n <h1 className=\"text-xl font-bold\">Export Not Found</h1>\n </div>\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-12 text-center\">\n <AlertCircle\n size={48}\n className=\"text-status-error-text mx-auto mb-4\"\n />\n <h3 className=\"text-lg font-bold text-status-error-text mb-2\">\n Export Not Found\n </h3>\n <p className=\"text-status-error-text mb-6\">\n The export job you are looking for does not exist or has been\n deleted.\n </p>\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"inline-flex items-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors font-medium\"\n >\n <ArrowLeft size={18} />\n Back to Dashboard\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout showHeader={false}>\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-xl bg-gradient-to-br from-primary/5 via-primary/3 to-background border border-border/50 p-6 shadow-sm\">\n <div className=\"relative z-10 flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-all duration-200\"\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex items-center gap-3 flex-1\">\n <div className=\"p-2.5 rounded-lg bg-primary/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-muted-foreground mt-1\">\n {liveJob.entityTypes.join(\", \")}\n </p>\n </div>\n <ExportStatusBadge status={liveJob.status} />\n </div>\n </div>\n </div>\n\n {/* Progress (for active jobs) */}\n {(liveJob.status === \"RUNNING\" || liveJob.status === \"QUEUED\") && (\n <div className=\"border-2 border-border-default 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-muted-foreground 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 <div\n key={item.label}\n className=\"border-2 border-border/50 rounded-xl p-4 bg-gradient-to-br from-card/50 to-background\"\n >\n <div className=\"flex items-center gap-2 text-muted-foreground mb-1\">\n {item.icon}\n <span className=\"text-xs font-medium\">{item.label}</span>\n </div>\n <p className=\"font-bold text-lg\">{item.value}</p>\n </div>\n ))}\n </div>\n\n {/* Parts */}\n {liveJob.parts.length > 0 && (\n <PageSection\n title=\"Export Parts\"\n description={`${liveJob.parts.length} file(s) generated`}\n >\n <div className=\"space-y-2\">\n {liveJob.parts.map((part) => (\n <div\n key={part.id}\n className={cn(\n \"border-2 border-border/50 rounded-xl p-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\",\n \"hover:border-primary/30 transition-all 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-muted-foreground\">\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-primary text-primary-foreground rounded-lg hover:bg-primary/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-muted-foreground\">\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 {/* Actions */}\n <div className=\"flex flex-col sm:flex-row gap-3 pt-2\">\n {isCancellable && (\n <button\n type=\"button\"\n onClick={handleCancel}\n disabled={cancelling}\n className=\"inline-flex items-center justify-center gap-2 px-6 py-3 border-2 border-status-warning-bg/50 text-status-warning-text rounded-lg hover:bg-status-warning-bg-subtle transition-all duration-200 font-medium disabled:opacity-50\"\n >\n {cancelling ? (\n <Loader2 size={18} className=\"animate-spin\" />\n ) : (\n <XCircle size={18} />\n )}\n Cancel Export\n </button>\n )}\n {isRetryable && (\n <button\n type=\"button\"\n onClick={handleRetry}\n disabled={retrying}\n className=\"inline-flex items-center justify-center gap-2 px-6 py-3 border-2 border-primary/50 text-primary rounded-lg hover:bg-primary/5 transition-all duration-200 font-medium disabled:opacity-50\"\n >\n {retrying ? (\n <Loader2 size={18} className=\"animate-spin\" />\n ) : (\n <RotateCcw size={18} />\n )}\n Retry Export\n </button>\n )}\n {!isCancellable && (\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={deleting}\n className=\"inline-flex items-center justify-center gap-2 px-6 py-3 border-2 border-status-error-bg/50 text-status-error-text rounded-lg hover:bg-status-error-bg-subtle transition-all duration-200 font-medium disabled:opacity-50\"\n >\n {deleting ? (\n <Loader2 size={18} className=\"animate-spin\" />\n ) : (\n <Trash2 size={18} />\n )}\n Delete Export\n </button>\n )}\n </div>\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-background/80 backdrop-blur-sm p-4\"\n >\n <div className=\"w-full max-w-md rounded-xl border-2 border-border bg-card 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-muted-foreground\">\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 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-destructive text-destructive-foreground hover:bg-destructive/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":";;;;;;;;;;;;;AAqCA,SAAS,EAAY,GAAuB;AAC1C,KAAI,MAAU,EAAG,QAAO;CACxB,IAAM,IAAI,MACJ,IAAQ;EAAC;EAAK;EAAM;EAAM;EAAM;EAAK,EACrC,IAAI,KAAK,MAAM,KAAK,IAAI,EAAM,GAAG,KAAK,IAAI,EAAE,CAAC;AACnD,QAAO,GAAG,YAAY,IAAiB,MAAG,GAAI,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAM;;AAMrE,IAAM,KAAY,GAAc,OACZ,EAAK,SAAS,IAAI,GAAG,EAAK,MAAM,GAAG,GAAG,GAAG,MACzC,EAAK,WAAW,IAAI,GAAG,IAAO,IAAI;AAStD,SAAgB,IAAmB;CACjC,IAAM,EAAE,aAAU,GAA8B,EAC1C,EAAE,cAAW,KAAK,gBAAa,GAAW,EAC1C,IAAiB,GAAa,EAE9B,UAAmB;AACvB,EAAI,OAAO,QAAQ,SAAS,IAC1B,EAAe,GAAG,GAElB,IAAW,EAAS;IAIlB,EAAE,MAAM,GAAK,iBAAc,EAAa,EAAM,EAC9C,EAAE,MAAM,MAAgB,EAAqB,EAAM,EACnD,CAAC,GAAe,KAAoB,EAExC,KAAK,EAED,EAAE,cAAW,WAAW,MAAe,GAAoB,EAC3D,EAAE,cAAW,WAAW,MAAa,GAAoB,EACzD,EAAE,aAAU,WAAW,MAAa,GAAmB,EAEvD,IAAU,KAAe,KAAO,MAGhC,IACJ,GAAS,WAAW,aACpB,GAAS,WAAW,YACpB,GAAS,WAAW,WAChB,IAAc,GAAS,WAAW,aAClC,IAAW,GAAS,WAAW,UAC/B,KACJ,GAAS,WAAW,YACpB,GAAS,WAAW,eACpB,GAAS,WAAW;AAyFtB,QAlDI,IAEA,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD;GAAS,MAAM;GAAI,WAAU;GAA8B,CAAA,EAC3D,kBAAC,QAAD;GAAM,WAAU;aAAiD;GAE1D,CAAA,CACH;MAKL,IAuCH,kBAAC,GAAD;EAAY,YAAY;YAAxB;GAEE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;gBAEV,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;MAChB,CAAA,EACT,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD;SAAU,MAAM;SAAI,WAAU;SAAiB,CAAA;QAC3C,CAAA;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAQ;SACN,CAAA,EACL,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAQ,YAAY,KAAK,KAAK;SAC7B,CAAA,CACA;;OACN,kBAAC,GAAD,EAAmB,QAAQ,EAAQ,QAAU,CAAA;OACzC;QACF;;IACF,CAAA;IAGJ,EAAQ,WAAW,aAAa,EAAQ,WAAW,aACnD,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAmB,UAAU,EAAQ;KAAU,MAAK;KAAO,CAAA,EAC1D,EAAQ,qBAAqB,QAC5B,EAAQ,iBAAiB,QACvB,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAQ,kBAAkB,gBAAgB;MAAC;MAAG;MAC9C,EAAQ,cAAc,gBAAgB;MAAC;MACtC;OAEJ;;GAIP,KAAY,EAAQ,gBACnB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KACE,MAAM;KACN,WAAU;KACV,CAAA,EACF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eAA4C;KAErD,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAQ;KACP,CAAA,CACA,EAAA,CAAA,CACF;;GAIR,kBAAC,OAAD;IAAK,WAAU;cACZ;KACC;MACE,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;MAC5B,OAAO;MACP,OAAO,EAAqB,EAAQ,OAAO;MAC5C;KACD;MACE,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;MAC7B,OAAO;MACP,OAAO,EAA4B,EAAQ,cAAc;MAC1D;KACD;MACE,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;MAC5B,OAAO;MACP,OACE,EAAQ,kBAAkB,OAEtB,MADA,EAAY,EAAQ,eAAe;MAE1C;KACD;MACE,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;MACzB,OAAO;MACP,OAAO,IAAI,KAAK,EAAQ,UAAU,CAAC,oBAAoB;MACxD;KACF,CAAC,KAAK,MACL,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,EAAK,MACN,kBAAC,QAAD;OAAM,WAAU;iBAAuB,EAAK;OAAa,CAAA,CACrD;SACN,kBAAC,KAAD;MAAG,WAAU;gBAAqB,EAAK;MAAU,CAAA,CAC7C;OARC,EAAK,MAQN,CACN;IACE,CAAA;GAGL,EAAQ,MAAM,SAAS,KACtB,kBAAC,GAAD;IACE,OAAM;IACN,aAAa,GAAG,EAAQ,MAAM,OAAO;cAErC,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAQ,MAAM,KAAK,MAClB,kBAAC,OAAD;MAEE,WAAW,EACT,+GACA,sDACD;gBALH,CAOE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAU,MAAM;QAAI,WAAU;QAA+B,CAAA,EAC7D,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAwB,EAAK;SAAa,CAAA,EACvD,kBAAC,KAAD;SAAG,WAAU;mBAAb;UACG,EAAK,YAAY,KAAK,KAAK;UAAC;UAAU;UACtC,EAAY,EAAK,UAAU;UAC3B,EAAK,qBACJ,cAAc,IAAI,KAAK,EAAK,kBAAkB,CAAC,oBAAoB;UACnE;WACA;UACF;UACN,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAK,cACJ,kBAAC,KAAD;QACE,MAAM,EAAK;QACX,WAAU;kBAFZ,CAIE,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA,EAAA,WAEpB;YAEJ,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA,EAAA,UAEd;;OAEL,CAAA,CACF;QAlCC,EAAK,GAkCN,CACN;KACE,CAAA;IACM,CAAA;GAIf,KAAe,EAAQ,MAAM,WAAW,KACvC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,MAAM;MACN,WAAU;MACV,CAAA;KACF,kBAAC,MAAD;MAAI,WAAU;gBAAkD;MAE3D,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAAmC;MAE5C,CAAA;KACA;;GAIR,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,KACC,kBAAC,UAAD;MACE,MAAK;MACL,SAlQW,YAAY;AAC1B,YACL,EAAiB,SAAS;;MAiQlB,UAAU;MACV,WAAU;gBAJZ,CAMG,IACC,kBAAC,GAAD;OAAS,MAAM;OAAI,WAAU;OAAiB,CAAA,GAE9C,kBAAC,GAAD,EAAS,MAAM,IAAM,CAAA,EACrB,gBAEK;;KAEV,MACC,kBAAC,UAAD;MACE,MAAK;MACL,SAtPU,YAAY;AAC9B,WAAI,CAAC,EAAO;OACZ,IAAM,IAAS,MAAM,EAAS,EAAM;AAEpC,OAAI,GAAQ,MACV,IAAW,EAAS,GAAU,EAAO,GAAG,CAAC;;MAkPnC,UAAU;MACV,WAAU;gBAJZ,CAMG,IACC,kBAAC,GAAD;OAAS,MAAM;OAAI,WAAU;OAAiB,CAAA,GAE9C,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA,EACvB,eAEK;;KAEV,CAAC,KACA,kBAAC,UAAD;MACE,MAAK;MACL,SA3RW,YAAY;AAC1B,aACL,EAAa,SAAS,EACtB,EAAiB,SAAS;;MAyRlB,UAAU;MACV,WAAU;gBAJZ,CAMG,IACC,kBAAC,GAAD;OAAS,MAAM;OAAI,WAAU;OAAiB,CAAA,GAE9C,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA,EACpB,gBAEK;;KAEP;;GAEL,KACC,kBAAC,OAAD;IACE,MAAK;IACL,cAAW;IACX,mBAAgB;IAChB,WAAU;cAEV,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,MAAD;OAAI,IAAG;OAAuB,WAAU;iBACrC,MAAkB,WAAW,mBAAmB;OAC9C,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,MAAkB,WACf,yEACA;OACF,CAAA;MACJ,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAiB,KAAK;QACrC,WAAU;kBACX;QAEQ,CAAA,EACT,kBAAC,UAAD;QACE,MAAK;QACL,SA7Te,YAAY;AACnC,gBAAC,KAAS,CAAC,IACf;cAAI;AAOF,WANI,MAAkB,WACpB,MAAM,EAAU,EAAM,IAEtB,MAAM,EAAU,EAAM,EACtB,IAAW,EAAS,GAEtB,EAAa,UAAU;kBACjB;AACN,aAAa,QAAQ;;AAEvB,YAAiB,KAAK;;;QAiTV,UAAU,KAAc;QACxB,WAAU;kBAJZ,CAMG,KAAc,IACb,kBAAC,GAAD;SAAS,MAAM;SAAI,WAAU;SAAiB,CAAA,GAC5C,MACH,MAAkB,WAAW,kBAAkB,gBACzC;UACL;;MACF;;IACF,CAAA;GAEG;MAlSX,kBAAC,GAAD;EAAY,YAAY;YAAxB,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cAEV,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;IAChB,CAAA,EACT,kBAAC,MAAD;IAAI,WAAU;cAAoB;IAAqB,CAAA,CACnD;MACN,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KACE,MAAM;KACN,WAAU;KACV,CAAA;IACF,kBAAC,MAAD;KAAI,WAAU;eAAgD;KAEzD,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eAA8B;KAGvC,CAAA;IACJ,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA,EAAA,oBAEhB;;IACL;KACK"}
|