@burdenoff/microfe-export 2026.625.1 → 2026.626.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/export/components/ExportStatusBadge.js +1 -1
- package/dist/export/components/ExportStatusBadge.js.map +1 -1
- package/dist/export/components/PageLayout.js +7 -18
- package/dist/export/components/PageLayout.js.map +1 -1
- package/dist/export/pages/CreateExportPage.js +95 -90
- package/dist/export/pages/CreateExportPage.js.map +1 -1
- package/dist/export/pages/ExportDashboardPage.js +148 -94
- package/dist/export/pages/ExportDashboardPage.js.map +1 -1
- package/dist/export/pages/ExportDetailPage.js +157 -134
- package/dist/export/pages/ExportDetailPage.js.map +1 -1
- package/dist/export/pages/ExportHistoryPage.js +147 -142
- package/dist/export/pages/ExportHistoryPage.js.map +1 -1
- package/dist/export/pages/ExportTemplatesPage.js +133 -129
- package/dist/export/pages/ExportTemplatesPage.js.map +1 -1
- package/dist/export/pages/TemplateDetailPage.js +103 -97
- package/dist/export/pages/TemplateDetailPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportHistoryPage.js","names":[],"sources":["../../../src/export/pages/ExportHistoryPage.tsx"],"sourcesContent":["import { useState, useMemo, useEffect, useRef } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\nimport {\n Search,\n X,\n Clock,\n Download,\n ChevronRight,\n ChevronLeft,\n RefreshCw,\n AlertCircle,\n Filter,\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 { useExportStore } from \"../store/exportStore\";\nimport { ExportStatusBadge } from \"../components/ExportStatusBadge\";\nimport { PageLayout, EmptyState } from \"../components/PageLayout\";\nimport { cn } from \"../utils/cn\";\nimport {\n getFormatDisplayName,\n getAllStatuses,\n getStatusDisplayName,\n} from \"../constants/enums\";\nimport type { ExportJobStatus, ExportFormat } from \"../types\";\nimport { tWithFallback } from \"../utils/i18n\";\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 History Page\n * Table/list of all past exports with filters and cursor-based pagination\n */\nexport function ExportHistoryPage() {\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 { jobFilter, updateFilter, resetFilter, pagination, setPagination } =\n useExportStore();\n const [searchParams] = useSearchParams();\n\n // Initialize status filter from URL search params (e.g. ?status=RUNNING) — once\n const didInitFromUrlRef = useRef(false);\n useEffect(() => {\n if (didInitFromUrlRef.current) return;\n didInitFromUrlRef.current = true;\n const statusParam = searchParams.get(\"status\");\n if (statusParam) {\n updateFilter({\n status: statusParam as ExportJobStatus,\n });\n }\n }, [searchParams, updateFilter]);\n\n const [showFilters, setShowFilters] = useState(false);\n // Cursor stack: index 0 = first page (no cursor), subsequent entries are \"after\" cursors\n const [cursorStack, setCursorStack] = useState<(string | null)[]>([null]);\n const [currentPageIndex, setCurrentPageIndex] = useState(0);\n\n const currentCursor = cursorStack[currentPageIndex] ?? null;\n\n const {\n data: jobs,\n totalCount,\n pageInfo,\n isLoading,\n error,\n refetch,\n } = useExportJobs(jobFilter, {\n first: pagination.pageSize,\n after: currentCursor ?? undefined,\n });\n\n // Client-side search filter (server-side filtering handles status/format)\n const filteredJobs = useMemo(() => {\n if (!jobFilter.search) return jobs;\n const q = jobFilter.search.toLowerCase();\n return jobs.filter(\n (job) =>\n job.name.toLowerCase().includes(q) ||\n job.entityTypes.some((e) => e.toLowerCase().includes(q)),\n );\n }, [jobs, jobFilter.search]);\n\n const hasActiveFilters =\n jobFilter.search ||\n jobFilter.status !== \"all\" ||\n jobFilter.format !== \"all\";\n\n const handleJobClick = (jobId: string) => {\n navigate?.(joinPath(basePath, jobId));\n };\n\n const handleNextPage = () => {\n if (!pageInfo?.hasNextPage || !pageInfo.endCursor) return;\n const newStack = [\n ...cursorStack.slice(0, currentPageIndex + 1),\n pageInfo.endCursor,\n ];\n setCursorStack(newStack);\n setCurrentPageIndex((prev) => prev + 1);\n setPagination({ page: pagination.page + 1, cursor: pageInfo.endCursor });\n };\n\n const handlePrevPage = () => {\n if (currentPageIndex === 0) return;\n const prevIndex = currentPageIndex - 1;\n setCurrentPageIndex(prevIndex);\n setPagination({\n page: pagination.page - 1,\n cursor: cursorStack[prevIndex] ?? null,\n });\n };\n\n const handleResetFilters = () => {\n resetFilter();\n setCursorStack([null]);\n setCurrentPageIndex(0);\n };\n\n const hasPrevPage = currentPageIndex > 0;\n const hasNextPage = pageInfo?.hasNextPage ?? false;\n const pageStart = currentPageIndex * pagination.pageSize + 1;\n const pageEnd = Math.min(\n (currentPageIndex + 1) * pagination.pageSize,\n totalCount,\n );\n const getStatusLabel = (status: ExportJobStatus) =>\n tr(\n `export.shared.status.${status.toLowerCase()}`,\n getStatusDisplayName(status),\n );\n\n return (\n <PageLayout\n title={tr(\"export.history.title\", \"Export History\")}\n subtitle={tr(\n \"export.history.subtitle\",\n \"View and manage all your past export jobs\",\n )}\n icon={<Clock size={24} />}\n headerActions={\n <button\n type=\"button\"\n onClick={() => navigate?.(joinPath(basePath, \"new\"))}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <Download size={16} />\n {tr(\"export.history.actions.newExport\", \"New Export\")}\n </button>\n }\n >\n {/* Search & Filters */}\n <div className=\"space-y-4\">\n <div className=\"flex flex-col sm:flex-row gap-3\">\n {/* Search */}\n <div className=\"flex-1 relative group\">\n <Search\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary group-focus-within:text-primary transition-colors\"\n />\n <input\n type=\"text\"\n aria-label={tr(\n \"export.history.search.placeholder\",\n \"Search exports by name or entity type\",\n )}\n placeholder={tr(\n \"export.history.search.placeholder\",\n \"Search exports by name or entity type...\",\n )}\n value={jobFilter.search}\n onChange={(e) => updateFilter({ search: e.target.value })}\n className=\"w-full pl-[3.25rem] pr-4 py-2.5 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all duration-200\"\n />\n </div>\n\n {/* Filter Toggle */}\n <button\n type=\"button\"\n onClick={() => setShowFilters(!showFilters)}\n className={cn(\n \"inline-flex items-center gap-2 px-4 py-2.5 border-2 rounded-lg transition-all duration-200 font-medium\",\n showFilters || hasActiveFilters\n ? \"bg-action-primary-bg text-action-primary-text border-primary\"\n : \"border-border-subtle/50 hover:border-primary/30\",\n )}\n >\n <Filter size={16} />\n {showFilters\n ? tr(\"export.history.filters.hide\", \"Hide\")\n : tr(\"export.history.filters.show\", \"Filter\")}\n </button>\n </div>\n\n {/* Filter Panel */}\n {showFilters && (\n <div className=\"border border-border-seam rounded-xl p-4 animate-in fade-in-0 duration-200\">\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"font-semibold flex items-center gap-2\">\n <Filter size={16} className=\"text-primary\" />\n {tr(\"export.history.filters.title\", \"Filters\")}\n </h3>\n {hasActiveFilters && (\n <button\n type=\"button\"\n onClick={handleResetFilters}\n className=\"text-sm text-text-secondary hover:text-status-error-text flex items-center gap-1\"\n >\n <X size={14} />\n {tr(\"export.history.filters.clearAll\", \"Clear All\")}\n </button>\n )}\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n {/* Status Filter */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium\">\n {tr(\"export.history.filters.status.label\", \"Status\")}\n </label>\n <select\n value={jobFilter.status}\n onChange={(e) =>\n updateFilter({\n status: e.target.value as ExportJobStatus | \"all\",\n })\n }\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary\"\n >\n <option value=\"all\">\n {tr(\"export.history.filters.status.all\", \"All Statuses\")}\n </option>\n {getAllStatuses().map((status) => (\n <option key={status} value={status}>\n {getStatusLabel(status)}\n </option>\n ))}\n </select>\n </div>\n\n {/* Format Filter — only CSV and JSON are supported */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium\">\n {tr(\"export.history.filters.format.label\", \"Format\")}\n </label>\n <select\n value={jobFilter.format}\n onChange={(e) =>\n updateFilter({\n format: e.target.value as ExportFormat | \"all\",\n })\n }\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary\"\n >\n <option value=\"all\">\n {tr(\"export.history.filters.format.all\", \"All Formats\")}\n </option>\n <option value=\"CSV\">CSV</option>\n <option value=\"JSON\">JSON</option>\n </select>\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Error State */}\n {error && (\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\"\n />\n <div>\n <h3 className=\"font-semibold text-status-error-text mb-1\">\n {tr(\"export.history.error.title\", \"Error loading exports\")}\n </h3>\n <p className=\"text-sm text-status-error-text mb-3\">\n {error.message}\n </p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 text-sm bg-status-error-bg text-status-error-text rounded-lg hover:bg-status-error-bg/90 transition-colors\"\n >\n <RefreshCw size={14} />\n {tr(\"export.history.error.retry\", \"Try Again\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Loading */}\n {isLoading && (\n <div className=\"space-y-3\">\n {[...Array(5)].map((_, i) => (\n <div\n key={i}\n className=\"border border-border-seam rounded-xl p-4 animate-pulse\"\n >\n <div className=\"flex items-center justify-between\">\n <div className=\"space-y-2\">\n <div className=\"h-4 w-48 bg-bg-sunken rounded\" />\n <div className=\"h-3 w-32 bg-bg-sunken rounded\" />\n </div>\n <div className=\"h-5 w-20 bg-bg-sunken rounded-full\" />\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Jobs List */}\n {!isLoading &&\n !error &&\n (filteredJobs.length > 0 ? (\n <>\n <div className=\"space-y-2\">\n {filteredJobs.map((job, index) => (\n <button\n type=\"button\"\n key={job.id}\n onClick={() => handleJobClick(job.id)}\n className={cn(\n \"w-full text-left border border-border-seam rounded-xl p-4 bg-bg-surface\",\n \"hover:border-primary/40 transition-colors duration-200\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\",\n \"group animate-in fade-in-0\",\n )}\n style={{ animationDelay: `${index * 30}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-text-primary truncate\">\n {job.name}\n </h4>\n <p className=\"text-sm text-text-secondary mt-1 line-clamp-2\">\n {job.entityTypes.join(\", \")} ·{\" \"}\n {getFormatDisplayName(job.format)}\n {job.createdAt &&\n ` · ${new Date(job.createdAt).toLocaleDateString()}`}\n </p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <ExportStatusBadge status={job.status} />\n <ChevronRight\n size={18}\n className=\"text-text-secondary group-hover:text-primary transition-colors hidden sm:block\"\n />\n </div>\n </div>\n </button>\n ))}\n </div>\n\n {/* Pagination */}\n {totalCount > pagination.pageSize && (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-3 pt-2\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"export.history.pagination.showing\",\n \"Showing {start}-{end} of {total}\",\n {\n start: pageStart,\n end: pageEnd,\n total: totalCount,\n },\n )}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handlePrevPage}\n disabled={!hasPrevPage}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm border-2 border-border-subtle/50 rounded-lg hover:border-primary/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n <ChevronLeft size={16} />\n {tr(\"export.history.pagination.prev\", \"Prev\")}\n </button>\n <span className=\"text-sm text-text-secondary px-2\">\n {tr(\"export.history.pagination.page\", \"Page {page}\", {\n page: pagination.page,\n })}\n </span>\n <button\n type=\"button\"\n onClick={handleNextPage}\n disabled={!hasNextPage}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm border-2 border-border-subtle/50 rounded-lg hover:border-primary/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {tr(\"export.history.pagination.next\", \"Next\")}\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n )}\n </>\n ) : (\n <EmptyState\n icon={hasActiveFilters ? <Search size={48} /> : <Clock size={48} />}\n title={\n hasActiveFilters\n ? tr(\n \"export.history.empty.filtered.title\",\n \"No matching exports\",\n )\n : tr(\"export.history.empty.title\", \"No exports yet\")\n }\n description={\n hasActiveFilters\n ? tr(\n \"export.history.empty.filtered.description\",\n \"Try adjusting your search or filters.\",\n )\n : tr(\n \"export.history.empty.description\",\n \"Create your first export to see it here.\",\n )\n }\n action={\n hasActiveFilters ? (\n <button\n type=\"button\"\n onClick={handleResetFilters}\n className=\"inline-flex items-center gap-2 px-4 py-2 border-2 border-border-subtle rounded-lg hover:bg-accent transition-colors font-medium\"\n >\n <X size={16} />\n {tr(\n \"export.history.empty.filtered.actions.clear\",\n \"Clear Filters\",\n )}\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigate?.(joinPath(basePath, \"new\"))}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <Download size={16} />\n {tr(\n \"export.history.empty.actions.createFirst\",\n \"Create First Export\",\n )}\n </button>\n )\n }\n />\n ))}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExportHistoryPage.js","names":[],"sources":["../../../src/export/pages/ExportHistoryPage.tsx"],"sourcesContent":["import { useState, useMemo, useEffect, useRef } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\nimport {\n Search,\n X,\n Clock,\n Download,\n ChevronRight,\n ChevronLeft,\n RefreshCw,\n AlertCircle,\n Filter,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { IllustratedEmptyState, PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport { useExportJobs } from \"../hooks/useExportQueries\";\nimport { useExportStore } from \"../store/exportStore\";\nimport { ExportStatusBadge } from \"../components/ExportStatusBadge\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { cn } from \"../utils/cn\";\nimport {\n getFormatDisplayName,\n getAllStatuses,\n getStatusDisplayName,\n} from \"../constants/enums\";\nimport type { ExportJobStatus, ExportFormat } from \"../types\";\nimport { tWithFallback } from \"../utils/i18n\";\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 History Page\n * Table/list of all past exports with filters and cursor-based pagination\n */\nexport function ExportHistoryPage() {\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 { jobFilter, updateFilter, resetFilter, pagination, setPagination } =\n useExportStore();\n const [searchParams] = useSearchParams();\n\n // Initialize status filter from URL search params (e.g. ?status=RUNNING) — once\n const didInitFromUrlRef = useRef(false);\n useEffect(() => {\n if (didInitFromUrlRef.current) return;\n didInitFromUrlRef.current = true;\n const statusParam = searchParams.get(\"status\");\n if (statusParam) {\n updateFilter({\n status: statusParam as ExportJobStatus,\n });\n }\n }, [searchParams, updateFilter]);\n\n const [showFilters, setShowFilters] = useState(false);\n // Cursor stack: index 0 = first page (no cursor), subsequent entries are \"after\" cursors\n const [cursorStack, setCursorStack] = useState<(string | null)[]>([null]);\n const [currentPageIndex, setCurrentPageIndex] = useState(0);\n\n const currentCursor = cursorStack[currentPageIndex] ?? null;\n\n const {\n data: jobs,\n totalCount,\n pageInfo,\n isLoading,\n error,\n refetch,\n } = useExportJobs(jobFilter, {\n first: pagination.pageSize,\n after: currentCursor ?? undefined,\n });\n\n // Client-side search filter (server-side filtering handles status/format)\n const filteredJobs = useMemo(() => {\n if (!jobFilter.search) return jobs;\n const q = jobFilter.search.toLowerCase();\n return jobs.filter(\n (job) =>\n job.name.toLowerCase().includes(q) ||\n job.entityTypes.some((e) => e.toLowerCase().includes(q)),\n );\n }, [jobs, jobFilter.search]);\n\n const hasActiveFilters =\n jobFilter.search ||\n jobFilter.status !== \"all\" ||\n jobFilter.format !== \"all\";\n\n const handleJobClick = (jobId: string) => {\n navigate?.(joinPath(basePath, jobId));\n };\n\n const handleNextPage = () => {\n if (!pageInfo?.hasNextPage || !pageInfo.endCursor) return;\n const newStack = [\n ...cursorStack.slice(0, currentPageIndex + 1),\n pageInfo.endCursor,\n ];\n setCursorStack(newStack);\n setCurrentPageIndex((prev) => prev + 1);\n setPagination({ page: pagination.page + 1, cursor: pageInfo.endCursor });\n };\n\n const handlePrevPage = () => {\n if (currentPageIndex === 0) return;\n const prevIndex = currentPageIndex - 1;\n setCurrentPageIndex(prevIndex);\n setPagination({\n page: pagination.page - 1,\n cursor: cursorStack[prevIndex] ?? null,\n });\n };\n\n const handleResetFilters = () => {\n resetFilter();\n setCursorStack([null]);\n setCurrentPageIndex(0);\n };\n\n const hasPrevPage = currentPageIndex > 0;\n const hasNextPage = pageInfo?.hasNextPage ?? false;\n const pageStart = currentPageIndex * pagination.pageSize + 1;\n const pageEnd = Math.min(\n (currentPageIndex + 1) * pagination.pageSize,\n totalCount,\n );\n const getStatusLabel = (status: ExportJobStatus) =>\n tr(\n `export.shared.status.${status.toLowerCase()}`,\n getStatusDisplayName(status),\n );\n\n return (\n <PageLayout\n title={tr(\"export.history.title\", \"Export History\")}\n subtitle={tr(\n \"export.history.subtitle\",\n \"View and manage all your past export jobs\",\n )}\n icon={<Clock size={24} />}\n headerActions={\n <button\n type=\"button\"\n onClick={() => navigate?.(joinPath(basePath, \"new\"))}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <Download size={16} />\n {tr(\"export.history.actions.newExport\", \"New Export\")}\n </button>\n }\n >\n {/* Page purpose — what History is for */}\n <PagePurpose icon={<Clock size={16} />}>\n {tr(\n \"export.history.purpose\",\n \"Every export you've ever started lives here. Track jobs as they run, see which finished or failed, and grab download links for completed exports before they expire. Search by name or entity type, or filter by status and format to find a specific one.\",\n )}\n </PagePurpose>\n\n {/* Search & Filters */}\n <div className=\"space-y-4\">\n <div className=\"flex flex-col sm:flex-row gap-3\">\n {/* Search */}\n <div className=\"flex-1 relative group\">\n <Search\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary group-focus-within:text-primary transition-colors\"\n />\n <input\n type=\"text\"\n aria-label={tr(\n \"export.history.search.placeholder\",\n \"Search exports by name or entity type\",\n )}\n placeholder={tr(\n \"export.history.search.placeholder\",\n \"Search exports by name or entity type...\",\n )}\n value={jobFilter.search}\n onChange={(e) => updateFilter({ search: e.target.value })}\n className=\"w-full pl-[3.25rem] pr-4 py-2.5 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all duration-200\"\n />\n </div>\n\n {/* Filter Toggle */}\n <button\n type=\"button\"\n onClick={() => setShowFilters(!showFilters)}\n className={cn(\n \"inline-flex items-center gap-2 px-4 py-2.5 border-2 rounded-lg transition-all duration-200 font-medium\",\n showFilters || hasActiveFilters\n ? \"bg-action-primary-bg text-action-primary-text border-primary\"\n : \"border-border-subtle/50 hover:border-primary/30\",\n )}\n >\n <Filter size={16} />\n {showFilters\n ? tr(\"export.history.filters.hide\", \"Hide\")\n : tr(\"export.history.filters.show\", \"Filter\")}\n </button>\n </div>\n\n {/* Filter Panel */}\n {showFilters && (\n <div className=\"border border-border-seam rounded-xl p-4 animate-in fade-in-0 duration-200\">\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"font-semibold flex items-center gap-2\">\n <Filter size={16} className=\"text-primary\" />\n {tr(\"export.history.filters.title\", \"Filters\")}\n </h3>\n {hasActiveFilters && (\n <button\n type=\"button\"\n onClick={handleResetFilters}\n className=\"text-sm text-text-secondary hover:text-status-error-text flex items-center gap-1\"\n >\n <X size={14} />\n {tr(\"export.history.filters.clearAll\", \"Clear All\")}\n </button>\n )}\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n {/* Status Filter */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium\">\n {tr(\"export.history.filters.status.label\", \"Status\")}\n </label>\n <select\n value={jobFilter.status}\n onChange={(e) =>\n updateFilter({\n status: e.target.value as ExportJobStatus | \"all\",\n })\n }\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary\"\n >\n <option value=\"all\">\n {tr(\"export.history.filters.status.all\", \"All Statuses\")}\n </option>\n {getAllStatuses().map((status) => (\n <option key={status} value={status}>\n {getStatusLabel(status)}\n </option>\n ))}\n </select>\n </div>\n\n {/* Format Filter — only CSV and JSON are supported */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium\">\n {tr(\"export.history.filters.format.label\", \"Format\")}\n </label>\n <select\n value={jobFilter.format}\n onChange={(e) =>\n updateFilter({\n format: e.target.value as ExportFormat | \"all\",\n })\n }\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary\"\n >\n <option value=\"all\">\n {tr(\"export.history.filters.format.all\", \"All Formats\")}\n </option>\n <option value=\"CSV\">CSV</option>\n <option value=\"JSON\">JSON</option>\n </select>\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Error State */}\n {error && (\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\"\n />\n <div>\n <h3 className=\"font-semibold text-status-error-text mb-1\">\n {tr(\"export.history.error.title\", \"Error loading exports\")}\n </h3>\n <p className=\"text-sm text-status-error-text mb-3\">\n {error.message}\n </p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 text-sm bg-status-error-bg text-status-error-text rounded-lg hover:bg-status-error-bg/90 transition-colors\"\n >\n <RefreshCw size={14} />\n {tr(\"export.history.error.retry\", \"Try Again\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Loading */}\n {isLoading && (\n <div className=\"space-y-3\">\n {[...Array(5)].map((_, i) => (\n <div\n key={i}\n className=\"border border-border-seam rounded-xl p-4\"\n >\n <div className=\"flex items-center justify-between\">\n <div className=\"space-y-2\">\n <div className=\"h-4 w-48 bg-bg-sunken rounded animate-vc-shimmer\" />\n <div className=\"h-3 w-32 bg-bg-sunken rounded animate-vc-shimmer\" />\n </div>\n <div className=\"h-5 w-20 bg-bg-sunken rounded-full animate-vc-shimmer\" />\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Jobs List */}\n {!isLoading &&\n !error &&\n (filteredJobs.length > 0 ? (\n <>\n <div className=\"space-y-2\">\n {filteredJobs.map((job, index) => (\n <button\n type=\"button\"\n key={job.id}\n onClick={() => handleJobClick(job.id)}\n className={cn(\n \"w-full text-left border border-border-seam rounded-xl p-4 bg-bg-surface\",\n \"hover:border-primary/40 transition-colors duration-200\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\",\n \"group animate-in fade-in-0\",\n )}\n style={{ animationDelay: `${index * 30}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-text-primary truncate\">\n {job.name}\n </h4>\n <p className=\"text-sm text-text-secondary mt-1 line-clamp-2\">\n {job.entityTypes.join(\", \")} ·{\" \"}\n {getFormatDisplayName(job.format)}\n {job.createdAt &&\n ` · ${new Date(job.createdAt).toLocaleDateString()}`}\n </p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <ExportStatusBadge status={job.status} />\n <ChevronRight\n size={18}\n className=\"text-text-secondary group-hover:text-primary transition-colors hidden sm:block\"\n />\n </div>\n </div>\n </button>\n ))}\n </div>\n\n {/* Pagination */}\n {totalCount > pagination.pageSize && (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-3 pt-2\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"export.history.pagination.showing\",\n \"Showing {start}-{end} of {total}\",\n {\n start: pageStart,\n end: pageEnd,\n total: totalCount,\n },\n )}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handlePrevPage}\n disabled={!hasPrevPage}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm border-2 border-border-subtle/50 rounded-lg hover:border-primary/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n <ChevronLeft size={16} />\n {tr(\"export.history.pagination.prev\", \"Prev\")}\n </button>\n <span className=\"text-sm text-text-secondary px-2\">\n {tr(\"export.history.pagination.page\", \"Page {page}\", {\n page: pagination.page,\n })}\n </span>\n <button\n type=\"button\"\n onClick={handleNextPage}\n disabled={!hasNextPage}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm border-2 border-border-subtle/50 rounded-lg hover:border-primary/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {tr(\"export.history.pagination.next\", \"Next\")}\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n )}\n </>\n ) : (\n <IllustratedEmptyState\n illustration={hasActiveFilters ? \"empty-search\" : \"empty-data\"}\n title={\n hasActiveFilters\n ? tr(\n \"export.history.empty.filtered.title\",\n \"No matching exports\",\n )\n : tr(\"export.history.empty.title\", \"No exports yet\")\n }\n description={\n hasActiveFilters\n ? tr(\n \"export.history.empty.filtered.description\",\n \"Try adjusting your search or filters.\",\n )\n : tr(\n \"export.history.empty.description\",\n \"Create your first export to see it here.\",\n )\n }\n action={\n hasActiveFilters ? (\n <button\n type=\"button\"\n onClick={handleResetFilters}\n className=\"inline-flex items-center gap-2 px-4 py-2 border-2 border-border-subtle rounded-lg hover:bg-accent transition-colors font-medium\"\n >\n <X size={16} />\n {tr(\n \"export.history.empty.filtered.actions.clear\",\n \"Clear Filters\",\n )}\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigate?.(joinPath(basePath, \"new\"))}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <Download size={16} />\n {tr(\n \"export.history.empty.actions.createFirst\",\n \"Create First Export\",\n )}\n </button>\n )\n }\n />\n ))}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,202 +1,206 @@
|
|
|
1
1
|
import { useExport as e } from "../providers/ExportProvider.js";
|
|
2
2
|
import { useAvailableEntities as t, useCreateExportTemplate as n, useDeleteExportTemplate as r, useExportTemplates as i } from "../hooks/useExportQueries.js";
|
|
3
3
|
import { cn as a } from "../utils/cn.js";
|
|
4
|
-
import { getFormatDisplayName as
|
|
5
|
-
import { tWithFallback as
|
|
6
|
-
import {
|
|
7
|
-
import { nativeImpact as
|
|
8
|
-
import { useMemo as
|
|
9
|
-
import { ChevronRight as
|
|
10
|
-
import { jsx as
|
|
11
|
-
import { useI18n as
|
|
12
|
-
import { CTAOverflowMenu as C } from "@burdenoff/fe-libs/ui";
|
|
4
|
+
import { getFormatDisplayName as ee } from "../constants/enums.js";
|
|
5
|
+
import { tWithFallback as te } from "../utils/i18n.js";
|
|
6
|
+
import { PageLayout as o } from "../components/PageLayout.js";
|
|
7
|
+
import { nativeImpact as s, nativeNotify as c } from "../../utils/nativeBridge.js";
|
|
8
|
+
import { useMemo as l, useState as u } from "react";
|
|
9
|
+
import { ChevronRight as d, FileBox as f, Loader2 as p, Plus as m, Search as h, Sparkles as g, X as _ } from "lucide-react";
|
|
10
|
+
import { jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
11
|
+
import { useI18n as b } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
12
|
+
import { CTAOverflowMenu as x, IllustratedEmptyState as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
|
|
13
13
|
//#region src/export/pages/ExportTemplatesPage.tsx
|
|
14
14
|
var w = (e, t) => (e.endsWith("/") ? e.slice(0, -1) : e) + (t.startsWith("/") ? t : `/${t}`);
|
|
15
15
|
function T() {
|
|
16
|
-
let { basePath: T = "/", navigate: E } = e(), { t: D } =
|
|
17
|
-
|
|
18
|
-
},
|
|
19
|
-
if (!
|
|
20
|
-
|
|
16
|
+
let { basePath: T = "/", navigate: E } = e(), { t: D } = b(), O = (e, t, n) => te(D, e, t, n), { data: k, isLoading: A } = i(), { deleteTemplate: j } = r(), { createTemplate: M, isPending: N } = n(), { data: P } = t(), [F, I] = u(""), [L, R] = u(!1), [z, B] = u(""), [V, H] = u(""), [U, W] = u([]), [G, K] = u("CSV"), [q, J] = u(null), [Y, X] = u(null), Z = () => {
|
|
17
|
+
B(""), H(""), W([]), K("CSV"), J(null), R(!0);
|
|
18
|
+
}, ne = async () => {
|
|
19
|
+
if (!z.trim()) {
|
|
20
|
+
J(O("export.templates.form.errors.nameRequired", "Template name is required."));
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
|
-
if (
|
|
24
|
-
|
|
23
|
+
if (U.length === 0) {
|
|
24
|
+
J(O("export.templates.form.errors.entityRequired", "Select at least one entity type."));
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
J(null);
|
|
28
28
|
try {
|
|
29
29
|
await M({
|
|
30
|
-
name:
|
|
31
|
-
description:
|
|
32
|
-
entityTypes:
|
|
33
|
-
format:
|
|
34
|
-
}),
|
|
30
|
+
name: z.trim(),
|
|
31
|
+
description: V.trim() || void 0,
|
|
32
|
+
entityTypes: U,
|
|
33
|
+
format: G
|
|
34
|
+
}), R(!1);
|
|
35
35
|
} catch (e) {
|
|
36
|
-
|
|
36
|
+
J(e instanceof Error ? e.message : O("export.templates.form.errors.createFailed", "Failed to create template."));
|
|
37
37
|
}
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
}, Q =
|
|
38
|
+
}, re = (e) => {
|
|
39
|
+
W((t) => t.includes(e) ? t.filter((t) => t !== e) : [...t, e]);
|
|
40
|
+
}, Q = l(() => {
|
|
41
41
|
if (!F) return k;
|
|
42
42
|
let e = F.toLowerCase();
|
|
43
43
|
return k.filter((t) => t.name.toLowerCase().includes(e) || t.entityTypes.some((t) => t.toLowerCase().includes(e)));
|
|
44
44
|
}, [k, F]), $ = (e) => {
|
|
45
45
|
E?.(w(T, `templates/${e}`));
|
|
46
|
-
},
|
|
47
|
-
e.stopPropagation(),
|
|
48
|
-
},
|
|
49
|
-
if (
|
|
46
|
+
}, ie = async (e, t) => {
|
|
47
|
+
e.stopPropagation(), s("medium"), X(t);
|
|
48
|
+
}, ae = async () => {
|
|
49
|
+
if (Y) {
|
|
50
50
|
try {
|
|
51
|
-
await j(
|
|
51
|
+
await j(Y), c("success");
|
|
52
52
|
} catch {
|
|
53
|
-
|
|
53
|
+
c("error");
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
X(null);
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
|
-
return /* @__PURE__ */
|
|
58
|
+
return /* @__PURE__ */ y(o, {
|
|
59
59
|
title: O("export.templates.title", "Export Templates"),
|
|
60
60
|
subtitle: O("export.templates.subtitle", "Reusable export configurations for common data exports"),
|
|
61
|
-
icon: /* @__PURE__ */
|
|
62
|
-
headerActions: /* @__PURE__ */ x
|
|
61
|
+
icon: /* @__PURE__ */ v(f, { size: 24 }),
|
|
62
|
+
headerActions: /* @__PURE__ */ v(x, {
|
|
63
63
|
primary: {
|
|
64
64
|
label: O("export.templates.actions.newTemplate", "New Template"),
|
|
65
|
-
icon: /* @__PURE__ */
|
|
66
|
-
onSelect:
|
|
65
|
+
icon: /* @__PURE__ */ v(m, { size: 16 }),
|
|
66
|
+
onSelect: Z
|
|
67
67
|
},
|
|
68
68
|
actions: [{
|
|
69
69
|
label: O("export.templates.actions.newExport", "New Export"),
|
|
70
|
-
icon: /* @__PURE__ */
|
|
70
|
+
icon: /* @__PURE__ */ v(m, { size: 16 }),
|
|
71
71
|
onSelect: () => E?.(w(T, "new"))
|
|
72
72
|
}]
|
|
73
73
|
}),
|
|
74
|
-
headerContent: /* @__PURE__ */
|
|
74
|
+
headerContent: /* @__PURE__ */ y("span", {
|
|
75
75
|
className: "inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-bg-sunken text-text-secondary",
|
|
76
|
-
children: [/* @__PURE__ */
|
|
76
|
+
children: [/* @__PURE__ */ v(g, { size: 12 }), O("export.templates.summary.available", "{count} template{suffix} available", {
|
|
77
77
|
count: Q.length,
|
|
78
78
|
suffix: Q.length === 1 ? "" : "s"
|
|
79
79
|
})]
|
|
80
80
|
}),
|
|
81
81
|
children: [
|
|
82
|
-
|
|
82
|
+
/* @__PURE__ */ v(C, {
|
|
83
|
+
icon: /* @__PURE__ */ v(f, { size: 16 }),
|
|
84
|
+
children: O("export.templates.purpose", "Templates save an export setup — the entity types and format you regularly need — so you don't rebuild it each time. Create one here, then start a new export from it in a single click. Handy for recurring backups, periodic audits, or any export you run more than once.")
|
|
85
|
+
}),
|
|
86
|
+
L && /* @__PURE__ */ y("div", {
|
|
83
87
|
className: "border border-primary/30 rounded-xl p-6 bg-accent-wash space-y-4",
|
|
84
88
|
children: [
|
|
85
|
-
/* @__PURE__ */
|
|
89
|
+
/* @__PURE__ */ y("div", {
|
|
86
90
|
className: "flex items-center justify-between",
|
|
87
|
-
children: [/* @__PURE__ */
|
|
91
|
+
children: [/* @__PURE__ */ v("h3", {
|
|
88
92
|
className: "font-semibold text-lg",
|
|
89
93
|
children: O("export.templates.form.title", "New Template")
|
|
90
|
-
}), /* @__PURE__ */
|
|
94
|
+
}), /* @__PURE__ */ v("button", {
|
|
91
95
|
type: "button",
|
|
92
|
-
onClick: () =>
|
|
96
|
+
onClick: () => R(!1),
|
|
93
97
|
className: "p-1.5 rounded-md hover:bg-accent",
|
|
94
|
-
children: /* @__PURE__ */
|
|
98
|
+
children: /* @__PURE__ */ v(_, { size: 16 })
|
|
95
99
|
})]
|
|
96
100
|
}),
|
|
97
|
-
|
|
101
|
+
q && /* @__PURE__ */ v("p", {
|
|
98
102
|
className: "text-sm text-status-error-text bg-status-error-bg/10 rounded-lg px-3 py-2",
|
|
99
|
-
children:
|
|
103
|
+
children: q
|
|
100
104
|
}),
|
|
101
|
-
/* @__PURE__ */
|
|
105
|
+
/* @__PURE__ */ y("div", {
|
|
102
106
|
className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
|
|
103
|
-
children: [/* @__PURE__ */
|
|
107
|
+
children: [/* @__PURE__ */ y("div", {
|
|
104
108
|
className: "space-y-1",
|
|
105
|
-
children: [/* @__PURE__ */
|
|
109
|
+
children: [/* @__PURE__ */ y("label", {
|
|
106
110
|
className: "text-sm font-medium",
|
|
107
111
|
children: [O("export.templates.form.fields.name", "Name"), " *"]
|
|
108
|
-
}), /* @__PURE__ */
|
|
112
|
+
}), /* @__PURE__ */ v("input", {
|
|
109
113
|
type: "text",
|
|
110
114
|
"aria-label": O("export.templates.form.fields.name", "Name"),
|
|
111
115
|
placeholder: O("export.templates.form.fields.namePlaceholder", "e.g. Weekly Tags Export"),
|
|
112
|
-
value:
|
|
113
|
-
onChange: (e) =>
|
|
116
|
+
value: z,
|
|
117
|
+
onChange: (e) => B(e.target.value),
|
|
114
118
|
className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all"
|
|
115
119
|
})]
|
|
116
|
-
}), /* @__PURE__ */
|
|
120
|
+
}), /* @__PURE__ */ y("div", {
|
|
117
121
|
className: "space-y-1",
|
|
118
|
-
children: [/* @__PURE__ */
|
|
122
|
+
children: [/* @__PURE__ */ y("label", {
|
|
119
123
|
className: "text-sm font-medium",
|
|
120
124
|
children: [O("export.templates.form.fields.format", "Format"), " *"]
|
|
121
|
-
}), /* @__PURE__ */
|
|
122
|
-
value:
|
|
123
|
-
onChange: (e) =>
|
|
125
|
+
}), /* @__PURE__ */ y("select", {
|
|
126
|
+
value: G,
|
|
127
|
+
onChange: (e) => K(e.target.value),
|
|
124
128
|
className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all",
|
|
125
|
-
children: [/* @__PURE__ */
|
|
129
|
+
children: [/* @__PURE__ */ v("option", {
|
|
126
130
|
value: "CSV",
|
|
127
131
|
children: "CSV"
|
|
128
|
-
}), /* @__PURE__ */
|
|
132
|
+
}), /* @__PURE__ */ v("option", {
|
|
129
133
|
value: "JSON",
|
|
130
134
|
children: "JSON"
|
|
131
135
|
})]
|
|
132
136
|
})]
|
|
133
137
|
})]
|
|
134
138
|
}),
|
|
135
|
-
/* @__PURE__ */
|
|
139
|
+
/* @__PURE__ */ y("div", {
|
|
136
140
|
className: "space-y-1",
|
|
137
|
-
children: [/* @__PURE__ */
|
|
141
|
+
children: [/* @__PURE__ */ v("label", {
|
|
138
142
|
className: "text-sm font-medium",
|
|
139
143
|
children: O("export.templates.form.fields.description", "Description")
|
|
140
|
-
}), /* @__PURE__ */
|
|
144
|
+
}), /* @__PURE__ */ v("input", {
|
|
141
145
|
type: "text",
|
|
142
146
|
"aria-label": O("export.templates.form.fields.description", "Description"),
|
|
143
147
|
placeholder: O("export.templates.form.fields.descriptionPlaceholder", "Optional description"),
|
|
144
|
-
value:
|
|
145
|
-
onChange: (e) =>
|
|
148
|
+
value: V,
|
|
149
|
+
onChange: (e) => H(e.target.value),
|
|
146
150
|
className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all"
|
|
147
151
|
})]
|
|
148
152
|
}),
|
|
149
|
-
/* @__PURE__ */
|
|
153
|
+
/* @__PURE__ */ y("div", {
|
|
150
154
|
className: "space-y-2",
|
|
151
|
-
children: [/* @__PURE__ */
|
|
155
|
+
children: [/* @__PURE__ */ y("label", {
|
|
152
156
|
className: "text-sm font-medium",
|
|
153
157
|
children: [
|
|
154
158
|
O("export.templates.form.fields.entityTypes", "Entity Types"),
|
|
155
159
|
" *",
|
|
156
160
|
" ",
|
|
157
|
-
/* @__PURE__ */
|
|
161
|
+
/* @__PURE__ */ v("span", {
|
|
158
162
|
className: "text-text-secondary font-normal",
|
|
159
163
|
children: O("export.templates.form.fields.entityTypesHint", "(select at least one)")
|
|
160
164
|
})
|
|
161
165
|
]
|
|
162
|
-
}), /* @__PURE__ */
|
|
166
|
+
}), /* @__PURE__ */ v("div", {
|
|
163
167
|
className: "flex flex-wrap gap-2",
|
|
164
|
-
children: P.map((e) => /* @__PURE__ */
|
|
168
|
+
children: P.map((e) => /* @__PURE__ */ v("button", {
|
|
165
169
|
type: "button",
|
|
166
|
-
onClick: () =>
|
|
167
|
-
className: a("px-3 py-1.5 rounded-lg text-sm font-medium border-2 transition-all",
|
|
170
|
+
onClick: () => re(e.entityType),
|
|
171
|
+
className: a("px-3 py-1.5 rounded-lg text-sm font-medium border-2 transition-all", U.includes(e.entityType) ? "border-primary bg-action-primary-bg/10 text-primary" : "border-border-subtle bg-bg-surface hover:border-primary/50"),
|
|
168
172
|
children: e.displayName
|
|
169
173
|
}, e.entityType))
|
|
170
174
|
})]
|
|
171
175
|
}),
|
|
172
|
-
/* @__PURE__ */
|
|
176
|
+
/* @__PURE__ */ y("div", {
|
|
173
177
|
className: "flex gap-3 pt-2",
|
|
174
|
-
children: [/* @__PURE__ */
|
|
178
|
+
children: [/* @__PURE__ */ y("button", {
|
|
175
179
|
type: "button",
|
|
176
|
-
onClick:
|
|
180
|
+
onClick: ne,
|
|
177
181
|
disabled: N,
|
|
178
182
|
className: "inline-flex items-center gap-2 px-5 py-2.5 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium disabled:opacity-50",
|
|
179
|
-
children: [N ? /* @__PURE__ */
|
|
183
|
+
children: [N ? /* @__PURE__ */ v(p, {
|
|
180
184
|
size: 16,
|
|
181
185
|
className: "animate-spin"
|
|
182
|
-
}) : /* @__PURE__ */
|
|
183
|
-
}), /* @__PURE__ */
|
|
186
|
+
}) : /* @__PURE__ */ v(m, { size: 16 }), O("export.templates.form.actions.create", "Create Template")]
|
|
187
|
+
}), /* @__PURE__ */ v("button", {
|
|
184
188
|
type: "button",
|
|
185
|
-
onClick: () =>
|
|
189
|
+
onClick: () => R(!1),
|
|
186
190
|
className: "px-5 py-2.5 border-2 border-border-subtle rounded-lg hover:bg-accent transition-colors font-medium",
|
|
187
191
|
children: O("export.templates.form.actions.cancel", "Cancel")
|
|
188
192
|
})]
|
|
189
193
|
})
|
|
190
194
|
]
|
|
191
195
|
}),
|
|
192
|
-
/* @__PURE__ */
|
|
196
|
+
/* @__PURE__ */ y("div", {
|
|
193
197
|
className: "relative group",
|
|
194
198
|
children: [
|
|
195
|
-
/* @__PURE__ */
|
|
199
|
+
/* @__PURE__ */ v(h, {
|
|
196
200
|
size: 18,
|
|
197
201
|
className: "absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary group-focus-within:text-primary transition-colors"
|
|
198
202
|
}),
|
|
199
|
-
/* @__PURE__ */
|
|
203
|
+
/* @__PURE__ */ v("input", {
|
|
200
204
|
type: "text",
|
|
201
205
|
"aria-label": O("export.templates.search.placeholder", "Search templates"),
|
|
202
206
|
placeholder: O("export.templates.search.placeholder", "Search templates..."),
|
|
@@ -204,36 +208,36 @@ function T() {
|
|
|
204
208
|
onChange: (e) => I(e.target.value),
|
|
205
209
|
className: "w-full pl-[3.25rem] pr-4 py-2.5 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all duration-200"
|
|
206
210
|
}),
|
|
207
|
-
F && /* @__PURE__ */
|
|
211
|
+
F && /* @__PURE__ */ v("button", {
|
|
208
212
|
type: "button",
|
|
209
213
|
onClick: () => I(""),
|
|
210
214
|
className: "absolute right-3 top-1/2 -translate-y-1/2 p-1 rounded-md text-text-secondary hover:text-text-primary hover:bg-accent",
|
|
211
|
-
children: /* @__PURE__ */
|
|
215
|
+
children: /* @__PURE__ */ v(_, { size: 16 })
|
|
212
216
|
})
|
|
213
217
|
]
|
|
214
218
|
}),
|
|
215
|
-
A && /* @__PURE__ */
|
|
219
|
+
A && /* @__PURE__ */ v("div", {
|
|
216
220
|
className: "grid grid-cols-1 lg:grid-cols-2 gap-4",
|
|
217
221
|
children: [...[
|
|
218
222
|
,
|
|
219
223
|
,
|
|
220
224
|
,
|
|
221
225
|
,
|
|
222
|
-
]].map((e, t) => /* @__PURE__ */
|
|
223
|
-
className: "border border-border-seam rounded-xl p-5
|
|
226
|
+
]].map((e, t) => /* @__PURE__ */ y("div", {
|
|
227
|
+
className: "border border-border-seam rounded-xl p-5",
|
|
224
228
|
children: [
|
|
225
|
-
/* @__PURE__ */
|
|
226
|
-
/* @__PURE__ */
|
|
227
|
-
/* @__PURE__ */
|
|
229
|
+
/* @__PURE__ */ v("div", { className: "h-5 w-40 bg-bg-sunken rounded mb-3 animate-vc-shimmer" }),
|
|
230
|
+
/* @__PURE__ */ v("div", { className: "h-3 w-full bg-bg-sunken rounded mb-2 animate-vc-shimmer" }),
|
|
231
|
+
/* @__PURE__ */ y("div", {
|
|
228
232
|
className: "flex gap-2 mt-4",
|
|
229
|
-
children: [/* @__PURE__ */
|
|
233
|
+
children: [/* @__PURE__ */ v("div", { className: "h-5 w-16 bg-bg-sunken rounded-full animate-vc-shimmer" }), /* @__PURE__ */ v("div", { className: "h-5 w-16 bg-bg-sunken rounded-full animate-vc-shimmer" })]
|
|
230
234
|
})
|
|
231
235
|
]
|
|
232
236
|
}, t))
|
|
233
237
|
}),
|
|
234
|
-
A ? null : Q.length > 0 ? /* @__PURE__ */
|
|
238
|
+
A ? null : Q.length > 0 ? /* @__PURE__ */ v("div", {
|
|
235
239
|
className: "grid grid-cols-1 lg:grid-cols-2 gap-4",
|
|
236
|
-
children: Q.map((e, t) => /* @__PURE__ */
|
|
240
|
+
children: Q.map((e, t) => /* @__PURE__ */ y("div", {
|
|
237
241
|
role: "button",
|
|
238
242
|
tabIndex: 0,
|
|
239
243
|
onClick: () => $(e.id),
|
|
@@ -242,102 +246,102 @@ function T() {
|
|
|
242
246
|
},
|
|
243
247
|
className: a("text-left group cursor-pointer border border-border-seam rounded-xl overflow-hidden bg-bg-surface", "hover:border-primary/40 shadow-[var(--shadow-pop)] transition-colors duration-200", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40", "animate-in fade-in-0"),
|
|
244
248
|
style: { animationDelay: `${t * 50}ms` },
|
|
245
|
-
children: [/* @__PURE__ */
|
|
249
|
+
children: [/* @__PURE__ */ v("div", {
|
|
246
250
|
className: "p-4 border-b bg-bg-sunken/30",
|
|
247
|
-
children: /* @__PURE__ */
|
|
251
|
+
children: /* @__PURE__ */ y("div", {
|
|
248
252
|
className: "flex items-start justify-between",
|
|
249
|
-
children: [/* @__PURE__ */
|
|
253
|
+
children: [/* @__PURE__ */ y("div", {
|
|
250
254
|
className: "flex items-center gap-3",
|
|
251
|
-
children: [/* @__PURE__ */
|
|
255
|
+
children: [/* @__PURE__ */ v("div", {
|
|
252
256
|
className: "p-2 rounded-lg bg-action-primary-bg/10",
|
|
253
|
-
children: /* @__PURE__ */
|
|
257
|
+
children: /* @__PURE__ */ v(f, {
|
|
254
258
|
size: 20,
|
|
255
259
|
className: "text-primary"
|
|
256
260
|
})
|
|
257
|
-
}), /* @__PURE__ */
|
|
261
|
+
}), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h3", {
|
|
258
262
|
className: "font-semibold",
|
|
259
263
|
children: e.name
|
|
260
|
-
}), /* @__PURE__ */
|
|
264
|
+
}), /* @__PURE__ */ y("p", {
|
|
261
265
|
className: "text-xs text-text-secondary",
|
|
262
|
-
children: [
|
|
266
|
+
children: [ee(e.format), e.isDefault && /* @__PURE__ */ v("span", {
|
|
263
267
|
className: "ml-1.5 inline-flex items-center px-1.5 py-0.5 rounded text-xs bg-action-primary-bg/10 text-primary font-medium",
|
|
264
268
|
children: O("export.templates.card.default", "Default")
|
|
265
269
|
})]
|
|
266
270
|
})] })]
|
|
267
|
-
}), /* @__PURE__ */
|
|
271
|
+
}), /* @__PURE__ */ y("div", {
|
|
268
272
|
className: "flex items-center gap-2",
|
|
269
|
-
children: [/* @__PURE__ */
|
|
273
|
+
children: [/* @__PURE__ */ v("button", {
|
|
270
274
|
type: "button",
|
|
271
|
-
onClick: (t) =>
|
|
275
|
+
onClick: (t) => ie(t, e.id),
|
|
272
276
|
className: "p-1.5 rounded-md text-text-secondary hover:text-status-error-text hover:bg-status-error-bg/10 transition-colors",
|
|
273
277
|
"aria-label": O("export.templates.card.deleteAriaLabel", "Delete {name}", { name: e.name }),
|
|
274
|
-
children: /* @__PURE__ */
|
|
275
|
-
}), /* @__PURE__ */
|
|
278
|
+
children: /* @__PURE__ */ v(_, { size: 14 })
|
|
279
|
+
}), /* @__PURE__ */ v(d, {
|
|
276
280
|
size: 18,
|
|
277
281
|
className: "text-text-secondary group-hover:text-primary transition-colors mt-1"
|
|
278
282
|
})]
|
|
279
283
|
})]
|
|
280
284
|
})
|
|
281
|
-
}), /* @__PURE__ */
|
|
285
|
+
}), /* @__PURE__ */ y("div", {
|
|
282
286
|
className: "p-4",
|
|
283
|
-
children: [e.description && /* @__PURE__ */
|
|
287
|
+
children: [e.description && /* @__PURE__ */ v("p", {
|
|
284
288
|
className: "text-sm text-text-secondary mb-3 line-clamp-2",
|
|
285
289
|
children: e.description
|
|
286
|
-
}), /* @__PURE__ */
|
|
290
|
+
}), /* @__PURE__ */ y("div", {
|
|
287
291
|
className: "flex flex-wrap gap-1.5 overflow-hidden",
|
|
288
|
-
children: [e.entityTypes.slice(0, 6).map((e) => /* @__PURE__ */
|
|
292
|
+
children: [e.entityTypes.slice(0, 6).map((e) => /* @__PURE__ */ v("span", {
|
|
289
293
|
className: "inline-flex px-2 py-0.5 rounded text-xs font-medium bg-bg-sunken text-text-secondary max-w-full truncate",
|
|
290
294
|
children: e
|
|
291
|
-
}, e)), e.entityTypes.length > 6 && /* @__PURE__ */
|
|
295
|
+
}, e)), e.entityTypes.length > 6 && /* @__PURE__ */ v("span", {
|
|
292
296
|
className: "inline-flex px-2 py-0.5 rounded text-xs font-medium bg-bg-sunken text-text-secondary",
|
|
293
297
|
children: O("export.templates.card.moreEntities", "+{count} more", { count: e.entityTypes.length - 6 })
|
|
294
298
|
})]
|
|
295
299
|
})]
|
|
296
300
|
})]
|
|
297
301
|
}, e.id))
|
|
298
|
-
}) : /* @__PURE__ */
|
|
299
|
-
|
|
302
|
+
}) : /* @__PURE__ */ v(S, {
|
|
303
|
+
illustration: F ? "empty-search" : "empty-data",
|
|
300
304
|
title: F ? O("export.templates.empty.filtered.title", "No matching templates") : O("export.templates.empty.title", "No templates yet"),
|
|
301
305
|
description: F ? O("export.templates.empty.filtered.description", "Try adjusting your search to find what you are looking for.") : O("export.templates.empty.description", "Create a template to save reusable export configurations."),
|
|
302
|
-
action: F ? /* @__PURE__ */
|
|
306
|
+
action: F ? /* @__PURE__ */ y("button", {
|
|
303
307
|
type: "button",
|
|
304
308
|
onClick: () => I(""),
|
|
305
309
|
className: "inline-flex items-center gap-2 px-4 py-2 border-2 border-border-subtle rounded-lg hover:bg-accent transition-colors font-medium",
|
|
306
|
-
children: [/* @__PURE__ */
|
|
307
|
-
}) : /* @__PURE__ */
|
|
310
|
+
children: [/* @__PURE__ */ v(_, { size: 16 }), O("export.templates.empty.filtered.actions.clear", "Clear Search")]
|
|
311
|
+
}) : /* @__PURE__ */ y("button", {
|
|
308
312
|
type: "button",
|
|
309
|
-
onClick:
|
|
313
|
+
onClick: Z,
|
|
310
314
|
className: "inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium",
|
|
311
|
-
children: [/* @__PURE__ */
|
|
315
|
+
children: [/* @__PURE__ */ v(m, { size: 16 }), O("export.templates.empty.actions.newTemplate", "New Template")]
|
|
312
316
|
})
|
|
313
317
|
}),
|
|
314
|
-
|
|
318
|
+
Y && /* @__PURE__ */ v("div", {
|
|
315
319
|
role: "dialog",
|
|
316
320
|
"aria-modal": "true",
|
|
317
321
|
"aria-labelledby": "template-list-delete-title",
|
|
318
322
|
className: "fixed inset-0 z-50 flex items-center justify-center bg-bg-surface/80 backdrop-blur-sm p-4",
|
|
319
|
-
children: /* @__PURE__ */
|
|
323
|
+
children: /* @__PURE__ */ y("div", {
|
|
320
324
|
className: "w-full max-w-md rounded-xl border-2 border-border-subtle bg-bg-surface p-6 shadow-xl",
|
|
321
325
|
children: [
|
|
322
|
-
/* @__PURE__ */
|
|
326
|
+
/* @__PURE__ */ v("h2", {
|
|
323
327
|
id: "template-list-delete-title",
|
|
324
328
|
className: "text-lg font-bold",
|
|
325
329
|
children: O("export.templates.delete.title", "Delete template?")
|
|
326
330
|
}),
|
|
327
|
-
/* @__PURE__ */
|
|
331
|
+
/* @__PURE__ */ v("p", {
|
|
328
332
|
className: "mt-2 text-sm text-text-secondary",
|
|
329
333
|
children: O("export.templates.delete.description", "This removes the saved export configuration. Existing export jobs are not deleted.")
|
|
330
334
|
}),
|
|
331
|
-
/* @__PURE__ */
|
|
335
|
+
/* @__PURE__ */ y("div", {
|
|
332
336
|
className: "mt-6 flex flex-col-reverse sm:flex-row sm:justify-end gap-3",
|
|
333
|
-
children: [/* @__PURE__ */
|
|
337
|
+
children: [/* @__PURE__ */ v("button", {
|
|
334
338
|
type: "button",
|
|
335
|
-
onClick: () =>
|
|
339
|
+
onClick: () => X(null),
|
|
336
340
|
className: "px-4 py-2 rounded-lg border-2 border-border-subtle hover:bg-accent transition-colors font-medium",
|
|
337
341
|
children: O("export.templates.delete.cancel", "Keep Template")
|
|
338
|
-
}), /* @__PURE__ */
|
|
342
|
+
}), /* @__PURE__ */ v("button", {
|
|
339
343
|
type: "button",
|
|
340
|
-
onClick:
|
|
344
|
+
onClick: ae,
|
|
341
345
|
className: "px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors font-medium",
|
|
342
346
|
children: O("export.templates.delete.confirmAction", "Delete Template")
|
|
343
347
|
})]
|