@burdenoff/microfe-cosmicintersection 2026.805.1 → 2026.830.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/cosmicintersection/api/cosmicApi.d.ts +84 -2
- package/dist/cosmicintersection/api/cosmicApi.d.ts.map +1 -1
- package/dist/cosmicintersection/api/cosmicApi.js +135 -79
- package/dist/cosmicintersection/api/cosmicApi.js.map +1 -1
- package/dist/cosmicintersection/components/ActionMenu.d.ts +1 -1
- package/dist/cosmicintersection/components/ActionMenu.d.ts.map +1 -1
- package/dist/cosmicintersection/components/ActionMenu.js +51 -54
- package/dist/cosmicintersection/components/ActionMenu.js.map +1 -1
- package/dist/cosmicintersection/components/EntityFormShell.d.ts +1 -1
- package/dist/cosmicintersection/components/EntityFormShell.d.ts.map +1 -1
- package/dist/cosmicintersection/components/EntityFormShell.js +29 -27
- package/dist/cosmicintersection/components/EntityFormShell.js.map +1 -1
- package/dist/cosmicintersection/components/ErrorState.d.ts +1 -1
- package/dist/cosmicintersection/components/ErrorState.d.ts.map +1 -1
- package/dist/cosmicintersection/components/ErrorState.js +19 -17
- package/dist/cosmicintersection/components/ErrorState.js.map +1 -1
- package/dist/cosmicintersection/components/FileUploadField.d.ts.map +1 -1
- package/dist/cosmicintersection/components/FileUploadField.js +61 -60
- package/dist/cosmicintersection/components/FileUploadField.js.map +1 -1
- package/dist/cosmicintersection/components/LineageGraph.d.ts +6 -1
- package/dist/cosmicintersection/components/LineageGraph.d.ts.map +1 -1
- package/dist/cosmicintersection/components/LineageGraph.js.map +1 -1
- package/dist/cosmicintersection/components/LoadingState.d.ts +1 -1
- package/dist/cosmicintersection/components/LoadingState.d.ts.map +1 -1
- package/dist/cosmicintersection/components/LoadingState.js +15 -13
- package/dist/cosmicintersection/components/LoadingState.js.map +1 -1
- package/dist/cosmicintersection/components/PageLayout.d.ts.map +1 -1
- package/dist/cosmicintersection/components/PageLayout.js +28 -27
- package/dist/cosmicintersection/components/PageLayout.js.map +1 -1
- package/dist/cosmicintersection/components/Pagination.d.ts.map +1 -1
- package/dist/cosmicintersection/components/Pagination.js +38 -35
- package/dist/cosmicintersection/components/Pagination.js.map +1 -1
- package/dist/cosmicintersection/components/StatusBadge.d.ts.map +1 -1
- package/dist/cosmicintersection/components/StatusBadge.js +30 -9
- package/dist/cosmicintersection/components/StatusBadge.js.map +1 -1
- package/dist/cosmicintersection/components/StepDAG.d.ts.map +1 -1
- package/dist/cosmicintersection/components/StepDAG.js +22 -18
- package/dist/cosmicintersection/components/StepDAG.js.map +1 -1
- package/dist/cosmicintersection/pages/access/AccessPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/access/AccessPage.js +147 -146
- package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -1
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.js +260 -259
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionsPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionsPage.js +80 -65
- package/dist/cosmicintersection/pages/collections/CollectionsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.js +132 -131
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js +212 -164
- package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetsPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetsPage.js +178 -177
- package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/discovery/DiscoveryPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js +138 -145
- package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js.map +1 -1
- package/dist/cosmicintersection/pages/lineage/LineagePage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/lineage/LineagePage.js +78 -88
- package/dist/cosmicintersection/pages/lineage/LineagePage.js.map +1 -1
- package/dist/cosmicintersection/pages/notebooks/NotebooksPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js +125 -124
- package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelinesPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js +152 -151
- package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/releases/ReleasesPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/releases/ReleasesPage.js +175 -174
- package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunDetailPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunDetailPage.js +252 -118
- package/dist/cosmicintersection/pages/runs/RunDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunsPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunsPage.js +197 -188
- package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourcesPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourcesPage.js +65 -64
- package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -1
- package/dist/dev/main.d.ts.map +1 -1
- package/dist/shared/hooks/useTr.d.ts +12 -0
- package/dist/shared/hooks/useTr.d.ts.map +1 -0
- package/dist/shared/hooks/useTr.js +16 -0
- package/dist/shared/hooks/useTr.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RunsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/runs/RunsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Activity, Eye } from 'lucide-react';\nimport {\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport { useCosPipelinesQuery, useCosRunsQuery, type CosRunStatus } from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\n\nconst STATUS_OPTIONS: CosRunStatus[] = ['QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED', 'CANCELLED'];\nconst ALL = 'ALL';\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();\n}\n\nfunction formatDuration(ms?: number | null): string {\n if (ms === null || ms === undefined) {\n return '—';\n }\n if (ms < 1000) {\n return `${ms} ms`;\n }\n const seconds = ms / 1000;\n if (seconds < 60) {\n return `${seconds.toFixed(1)} s`;\n }\n const minutes = Math.floor(seconds / 60);\n const rest = Math.round(seconds % 60);\n return `${minutes}m ${rest}s`;\n}\n\nexport default function RunsPage() {\n const { navigate } = useCosmicIntersection();\n const [status, setStatus] = useState<CosRunStatus | typeof ALL>(ALL);\n const [pipelineId, setPipelineId] = useState<string | typeof ALL>(ALL);\n const pagination = usePagination({ resetKey: `${status}:${pipelineId}` });\n\n // Load pipelines to populate the filter and to resolve run -> pipeline names.\n const { data: pipelinesData } = useCosPipelinesQuery({ pagination: { limit: 100, offset: 0 } });\n const pipelines = useMemo(() => pipelinesData?.cosListPipelines.items ?? [], [pipelinesData]);\n const pipelineNameById = useMemo(() => {\n const map = new Map<string, string>();\n pipelines.forEach((pipeline) => map.set(pipeline.id, pipeline.name));\n return map;\n }, [pipelines]);\n\n const { data, loading, error, refetch } = useCosRunsQuery({\n status: status === ALL ? undefined : status,\n pipelineId: pipelineId === ALL ? undefined : pipelineId,\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const runs = useMemo(() => data?.cosListRuns.items ?? [], [data]);\n const pageInfo = data?.cosListRuns.pageInfo;\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading runs…\" />;\n }\n if (error) {\n return (\n <ErrorState\n message=\"We could not load pipeline runs. Please try again.\"\n onRetry={() => void refetch()}\n />\n );\n }\n if (runs.length === 0) {\n return (\n <EmptyState\n icon={Activity}\n title={\n status === ALL && pipelineId === ALL ? 'No runs yet' : 'No runs match these filters'\n }\n description={\n status === ALL && pipelineId === ALL\n ? 'Runs appear here once you start a pipeline. Each run tracks its steps, timings and metrics.'\n : 'Try a different status or pipeline filter.'\n }\n />\n );\n }\n\n return (\n <>\n {/* Desktop: table. */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Run</TableHead>\n <TableHead>Pipeline</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Started</TableHead>\n <TableHead>Finished</TableHead>\n <TableHead>Duration</TableHead>\n <TableHead>Triggered by</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {runs.map((run) => (\n <TableRow\n key={run.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`runs/${run.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">#{run.runNumber}</TableCell>\n <TableCell className=\"text-text-secondary\">\n <span className=\"truncate\">\n {pipelineNameById.get(run.pipelineId) ?? (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </span>\n </TableCell>\n <TableCell>\n <StatusBadge status={run.status} />\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.startedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.finishedAt)}\n </TableCell>\n <TableCell className=\"tabular-nums text-text-secondary\">\n {formatDuration(run.durationMs)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{run.triggeredBy || '—'}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile: stacked cards. */}\n <ul className=\"space-y-3 md:hidden\">\n {runs.map((run) => (\n <li key={run.id}>\n <button\n type=\"button\"\n onClick={() => navigate(`runs/${run.id}`)}\n className=\"flex w-full flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 text-left transition-colors hover:border-border-strong\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-medium text-text-primary\">#{run.runNumber}</span>\n <StatusBadge status={run.status} />\n </div>\n <p className=\"truncate text-sm text-text-secondary\">\n {pipelineNameById.get(run.pipelineId) ?? (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </p>\n <dl className=\"grid grid-cols-2 gap-x-3 gap-y-1 text-xs\">\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Started</dt>\n <dd className=\"text-text-primary\">{formatDate(run.startedAt)}</dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Duration</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatDuration(run.durationMs)}\n </dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Finished</dt>\n <dd className=\"text-text-primary\">{formatDate(run.finishedAt)}</dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Triggered by</dt>\n <dd className=\"text-text-primary\">{run.triggeredBy || '—'}</dd>\n </div>\n </dl>\n </button>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={runs.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"runs\"\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Runs\"\n description=\"Every pipeline execution, with status, timings and outcomes.\"\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('pipelines')}>\n <Eye className=\"size-4\" />\n View pipelines\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Status</span>\n <Select\n value={status}\n onValueChange={(value) => setStatus(value as CosRunStatus | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by status\" className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option} value={option}>\n {formatEnumLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Pipeline</span>\n <Select\n value={pipelineId}\n onValueChange={(value) => setPipelineId(value as string | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by pipeline\" className=\"w-56\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All pipelines</SelectItem>\n {pipelines.map((pipeline) => (\n <SelectItem key={pipeline.id} value={pipeline.id}>\n {pipeline.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {renderBody()}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RunsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/runs/RunsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Activity, Eye } from 'lucide-react';\nimport {\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport { useCosPipelinesQuery, useCosRunsQuery, type CosRunStatus } from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { useTr } from '../../../shared/hooks/useTr';\n\nconst STATUS_OPTIONS: CosRunStatus[] = ['QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED', 'CANCELLED'];\nconst ALL = 'ALL';\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();\n}\n\nfunction formatDuration(ms?: number | null): string {\n if (ms === null || ms === undefined) {\n return '—';\n }\n if (ms < 1000) {\n return `${ms} ms`;\n }\n const seconds = ms / 1000;\n if (seconds < 60) {\n return `${seconds.toFixed(1)} s`;\n }\n const minutes = Math.floor(seconds / 60);\n const rest = Math.round(seconds % 60);\n return `${minutes}m ${rest}s`;\n}\n\nexport default function RunsPage() {\n const { navigate } = useCosmicIntersection();\n const tr = useTr();\n const [status, setStatus] = useState<CosRunStatus | typeof ALL>(ALL);\n const [pipelineId, setPipelineId] = useState<string | typeof ALL>(ALL);\n const pagination = usePagination({ resetKey: `${status}:${pipelineId}` });\n\n // Load pipelines to populate the filter and to resolve run -> pipeline names.\n const { data: pipelinesData } = useCosPipelinesQuery({ pagination: { limit: 100, offset: 0 } });\n const pipelines = useMemo(() => pipelinesData?.cosListPipelines.items ?? [], [pipelinesData]);\n const pipelineNameById = useMemo(() => {\n const map = new Map<string, string>();\n pipelines.forEach((pipeline) => map.set(pipeline.id, pipeline.name));\n return map;\n }, [pipelines]);\n\n const { data, loading, error, refetch } = useCosRunsQuery({\n status: status === ALL ? undefined : status,\n pipelineId: pipelineId === ALL ? undefined : pipelineId,\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const runs = useMemo(() => data?.cosListRuns.items ?? [], [data]);\n const pageInfo = data?.cosListRuns.pageInfo;\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label={tr('cosmicintersection.pages.runs.loading', 'Loading runs…')} />;\n }\n if (error) {\n return (\n <ErrorState\n message={tr('cosmicintersection.pages.runs.error', 'We could not load pipeline runs. Please try again.')}\n onRetry={() => void refetch()}\n />\n );\n }\n if (runs.length === 0) {\n const noFilters = status === ALL && pipelineId === ALL;\n return (\n <EmptyState\n icon={Activity}\n title={\n noFilters\n ? tr('cosmicintersection.pages.runs.emptyTitle', 'No runs yet')\n : tr('cosmicintersection.pages.runs.emptyFilterTitle', 'No runs match these filters')\n }\n description={\n noFilters\n ? tr(\n 'cosmicintersection.pages.runs.emptyDescription',\n 'Runs appear here once you start a pipeline. Each run tracks its steps, timings and metrics.',\n )\n : tr(\n 'cosmicintersection.pages.runs.emptyFilterDescription',\n 'Try a different status or pipeline filter.',\n )\n }\n />\n );\n }\n\n return (\n <>\n {/* Desktop: table. */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Run</TableHead>\n <TableHead>Pipeline</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Started</TableHead>\n <TableHead>Finished</TableHead>\n <TableHead>Duration</TableHead>\n <TableHead>Triggered by</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {runs.map((run) => (\n <TableRow\n key={run.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`runs/${run.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">#{run.runNumber}</TableCell>\n <TableCell className=\"text-text-secondary\">\n <span className=\"truncate\">\n {pipelineNameById.get(run.pipelineId) ?? (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </span>\n </TableCell>\n <TableCell>\n <StatusBadge status={run.status} />\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.startedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.finishedAt)}\n </TableCell>\n <TableCell className=\"tabular-nums text-text-secondary\">\n {formatDuration(run.durationMs)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{run.triggeredBy || '—'}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile: stacked cards. */}\n <ul className=\"space-y-3 md:hidden\">\n {runs.map((run) => (\n <li key={run.id}>\n <button\n type=\"button\"\n onClick={() => navigate(`runs/${run.id}`)}\n className=\"flex w-full flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 text-left transition-colors hover:border-border-strong\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-medium text-text-primary\">#{run.runNumber}</span>\n <StatusBadge status={run.status} />\n </div>\n <p className=\"truncate text-sm text-text-secondary\">\n {pipelineNameById.get(run.pipelineId) ?? (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </p>\n <dl className=\"grid grid-cols-2 gap-x-3 gap-y-1 text-xs\">\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Started</dt>\n <dd className=\"text-text-primary\">{formatDate(run.startedAt)}</dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Duration</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatDuration(run.durationMs)}\n </dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Finished</dt>\n <dd className=\"text-text-primary\">{formatDate(run.finishedAt)}</dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Triggered by</dt>\n <dd className=\"text-text-primary\">{run.triggeredBy || '—'}</dd>\n </div>\n </dl>\n </button>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={runs.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel={tr('cosmicintersection.pages.runs.itemLabel', 'runs')}\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title={tr('cosmicintersection.pages.runs.title', 'Runs')}\n description={tr(\n 'cosmicintersection.pages.runs.description',\n 'Every pipeline execution, with status, timings and outcomes.',\n )}\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('pipelines')}>\n <Eye className=\"size-4\" />\n {tr('cosmicintersection.pages.runs.viewPipelines', 'View pipelines')}\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Status</span>\n <Select\n value={status}\n onValueChange={(value) => setStatus(value as CosRunStatus | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by status\" className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option} value={option}>\n {formatEnumLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Pipeline</span>\n <Select\n value={pipelineId}\n onValueChange={(value) => setPipelineId(value as string | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by pipeline\" className=\"w-56\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All pipelines</SelectItem>\n {pipelines.map((pipeline) => (\n <SelectItem key={pipeline.id} value={pipeline.id}>\n {pipeline.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {renderBody()}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SourcesPage.d.ts","sourceRoot":"","sources":["../../../../src/cosmicintersection/pages/sources/SourcesPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SourcesPage.d.ts","sourceRoot":"","sources":["../../../../src/cosmicintersection/pages/sources/SourcesPage.tsx"],"names":[],"mappings":"AAmGA,MAAM,CAAC,OAAO,UAAU,WAAW,4CAkWlC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useCosmicIntersection as e } from "../../context/CosmicIntersectionContext.js";
|
|
2
2
|
import { notify as t } from "../../lib/toast.js";
|
|
3
3
|
import { useCosArchiveSourceMutation as n, useCosMarkSourceSyncedMutation as r, useCosSourcesQuery as ee } from "../../api/cosmicApi.js";
|
|
4
|
-
import i from "
|
|
5
|
-
import a from "../../components/
|
|
6
|
-
import o from "../../components/
|
|
4
|
+
import { useTr as i } from "../../../shared/hooks/useTr.js";
|
|
5
|
+
import a from "../../components/ActionMenu.js";
|
|
6
|
+
import o from "../../components/EmptyState.js";
|
|
7
|
+
import te from "../../components/ErrorState.js";
|
|
7
8
|
import s from "../../components/LoadingState.js";
|
|
8
9
|
import c from "../../components/SafeLink.js";
|
|
9
10
|
import l from "../../components/PageLayout.js";
|
|
@@ -13,7 +14,7 @@ import "../../components/index.js";
|
|
|
13
14
|
import { useMemo as m, useState as h } from "react";
|
|
14
15
|
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
15
16
|
import { Archive as y, FileInput as b, FlaskConical as x, Plus as S, Radio as C, RadioTower as w, RefreshCw as T, Satellite as E, Telescope as D, Webhook as O } from "lucide-react";
|
|
16
|
-
import { Badge as k, Button as A, Select as j, SelectContent as M, SelectItem as N, SelectTrigger as P, SelectValue as F, Table as
|
|
17
|
+
import { Badge as k, Button as A, Select as j, SelectContent as M, SelectItem as N, SelectTrigger as P, SelectValue as F, Table as ne, TableBody as re, TableCell as I, TableHead as L, TableHeader as R, TableRow as z } from "@burdenoff/fe-libs/ui";
|
|
17
18
|
//#region src/cosmicintersection/pages/sources/SourcesPage.tsx
|
|
18
19
|
var B = "ALL", V = [
|
|
19
20
|
"TELESCOPE",
|
|
@@ -51,31 +52,31 @@ function W(e) {
|
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
54
|
function G() {
|
|
54
|
-
let { navigate: y } = e(),
|
|
55
|
+
let { navigate: y } = e(), b = i(), [x, w] = h(B), [E, D] = h(B), O = d({
|
|
55
56
|
pageSize: 20,
|
|
56
|
-
resetKey: `${
|
|
57
|
-
}), { data:
|
|
58
|
-
type:
|
|
59
|
-
status:
|
|
57
|
+
resetKey: `${x}|${E}`
|
|
58
|
+
}), { data: G, loading: K, error: q, refetch: J } = ee({
|
|
59
|
+
type: x === B ? void 0 : x,
|
|
60
|
+
status: E === B ? void 0 : E,
|
|
60
61
|
pagination: {
|
|
61
|
-
limit:
|
|
62
|
-
offset:
|
|
62
|
+
limit: O.limit,
|
|
63
|
+
offset: O.offset
|
|
63
64
|
}
|
|
64
|
-
}), [
|
|
65
|
-
(await
|
|
65
|
+
}), [Y] = r(), [ie] = n(), X = m(() => G?.cosListSources.items ?? [], [G]), ae = G?.cosListSources.pageInfo, Z = async (e) => {
|
|
66
|
+
(await Y({ variables: { id: e.id } })).data?.cosMarkSourceSynced && (t.success(b("cosmicintersection.pages.sources.syncSuccess", "Source marked synced"), b("cosmicintersection.pages.sources.syncSuccessDescription", "“{{name}}” is now up to date.", { name: e.name })), await J());
|
|
66
67
|
}, Q = async (e) => {
|
|
67
|
-
let n = await
|
|
68
|
+
let n = await ie({ variables: { id: e.id } });
|
|
68
69
|
if (n.error) throw Error(n.error.message);
|
|
69
|
-
if (!n.data?.cosArchiveSource) throw t.error("Could not archive source", "The server did not confirm the change."), Error("Archive not confirmed");
|
|
70
|
-
t.success("Source archived"), await
|
|
70
|
+
if (!n.data?.cosArchiveSource) throw t.error(b("cosmicintersection.pages.sources.archiveError", "Could not archive source"), b("cosmicintersection.pages.sources.archiveErrorDescription", "The server did not confirm the change.")), Error("Archive not confirmed");
|
|
71
|
+
t.success(b("cosmicintersection.pages.sources.archiveSuccess", "Source archived")), await J();
|
|
71
72
|
}, $ = /* @__PURE__ */ v(A, {
|
|
72
73
|
type: "button",
|
|
73
74
|
onClick: () => y("sources/create"),
|
|
74
|
-
children: [/* @__PURE__ */ _(S, { className: "size-4" }), "New source"]
|
|
75
|
+
children: [/* @__PURE__ */ _(S, { className: "size-4" }), b("cosmicintersection.pages.sources.create", "New source")]
|
|
75
76
|
});
|
|
76
77
|
return /* @__PURE__ */ _(l, {
|
|
77
|
-
title: "Data sources",
|
|
78
|
-
description: "Instruments, archives and feeds that supply raw data into your federation.",
|
|
78
|
+
title: b("cosmicintersection.pages.sources.title", "Data sources"),
|
|
79
|
+
description: b("cosmicintersection.pages.sources.description", "Instruments, archives and feeds that supply raw data into your federation."),
|
|
79
80
|
actions: $,
|
|
80
81
|
children: /* @__PURE__ */ v("div", {
|
|
81
82
|
className: "space-y-4",
|
|
@@ -84,14 +85,14 @@ function G() {
|
|
|
84
85
|
children: [/* @__PURE__ */ _("div", {
|
|
85
86
|
className: "w-full max-w-[14rem]",
|
|
86
87
|
children: /* @__PURE__ */ v(j, {
|
|
87
|
-
value:
|
|
88
|
-
onValueChange:
|
|
88
|
+
value: x,
|
|
89
|
+
onValueChange: w,
|
|
89
90
|
children: [/* @__PURE__ */ _(P, {
|
|
90
|
-
"aria-label": "Filter by type",
|
|
91
|
-
children: /* @__PURE__ */ _(F, { placeholder: "All types" })
|
|
91
|
+
"aria-label": b("cosmicintersection.common.filterByType", "Filter by type"),
|
|
92
|
+
children: /* @__PURE__ */ _(F, { placeholder: b("cosmicintersection.common.allTypes", "All types") })
|
|
92
93
|
}), /* @__PURE__ */ v(M, { children: [/* @__PURE__ */ _(N, {
|
|
93
94
|
value: B,
|
|
94
|
-
children: "All types"
|
|
95
|
+
children: b("cosmicintersection.common.allTypes", "All types")
|
|
95
96
|
}), V.map((e) => /* @__PURE__ */ _(N, {
|
|
96
97
|
value: e,
|
|
97
98
|
children: p(e)
|
|
@@ -100,14 +101,14 @@ function G() {
|
|
|
100
101
|
}), /* @__PURE__ */ _("div", {
|
|
101
102
|
className: "w-full max-w-[14rem]",
|
|
102
103
|
children: /* @__PURE__ */ v(j, {
|
|
103
|
-
value:
|
|
104
|
-
onValueChange:
|
|
104
|
+
value: E,
|
|
105
|
+
onValueChange: D,
|
|
105
106
|
children: [/* @__PURE__ */ _(P, {
|
|
106
|
-
"aria-label": "Filter by status",
|
|
107
|
-
children: /* @__PURE__ */ _(F, { placeholder: "All statuses" })
|
|
107
|
+
"aria-label": b("cosmicintersection.common.filterByStatus", "Filter by status"),
|
|
108
|
+
children: /* @__PURE__ */ _(F, { placeholder: b("cosmicintersection.common.allStatuses", "All statuses") })
|
|
108
109
|
}), /* @__PURE__ */ v(M, { children: [/* @__PURE__ */ _(N, {
|
|
109
110
|
value: B,
|
|
110
|
-
children: "All statuses"
|
|
111
|
+
children: b("cosmicintersection.common.allStatuses", "All statuses")
|
|
111
112
|
}), H.map((e) => /* @__PURE__ */ _(N, {
|
|
112
113
|
value: e,
|
|
113
114
|
children: p(e)
|
|
@@ -115,39 +116,39 @@ function G() {
|
|
|
115
116
|
})
|
|
116
117
|
})]
|
|
117
118
|
}), (() => {
|
|
118
|
-
if (
|
|
119
|
+
if (K) return /* @__PURE__ */ _(s, {
|
|
119
120
|
variant: "rows",
|
|
120
121
|
rows: 6,
|
|
121
|
-
label: "Loading sources…"
|
|
122
|
+
label: b("cosmicintersection.pages.sources.loading", "Loading sources…")
|
|
122
123
|
});
|
|
123
|
-
if (
|
|
124
|
-
message:
|
|
125
|
-
onRetry: () => void
|
|
124
|
+
if (q) return /* @__PURE__ */ _(te, {
|
|
125
|
+
message: q.message,
|
|
126
|
+
onRetry: () => void J()
|
|
126
127
|
});
|
|
127
128
|
if (X.length === 0) {
|
|
128
|
-
let e =
|
|
129
|
-
return /* @__PURE__ */ _(
|
|
129
|
+
let e = x !== B || E !== B;
|
|
130
|
+
return /* @__PURE__ */ _(o, {
|
|
130
131
|
icon: C,
|
|
131
|
-
title: e ? "No sources match these filters" : "No data sources yet",
|
|
132
|
-
description: e ? "Try clearing the type or status filter to see more sources." : "Connect a telescope, satellite feed, archive or API to start ingesting scientific data.",
|
|
132
|
+
title: e ? b("cosmicintersection.pages.sources.emptyFilterTitle", "No sources match these filters") : b("cosmicintersection.pages.sources.emptyTitle", "No data sources yet"),
|
|
133
|
+
description: e ? b("cosmicintersection.pages.sources.emptyFilterDescription", "Try clearing the type or status filter to see more sources.") : b("cosmicintersection.pages.sources.emptyDescription", "Connect a telescope, satellite feed, archive or API to start ingesting scientific data."),
|
|
133
134
|
action: e ? void 0 : $
|
|
134
135
|
});
|
|
135
136
|
}
|
|
136
137
|
return /* @__PURE__ */ v(g, { children: [
|
|
137
138
|
/* @__PURE__ */ _("div", {
|
|
138
139
|
className: "hidden overflow-hidden rounded-card border border-border-default bg-bg-elevated md:block",
|
|
139
|
-
children: /* @__PURE__ */ v(
|
|
140
|
-
/* @__PURE__ */ _(L, { children: "Name" }),
|
|
141
|
-
/* @__PURE__ */ _(L, { children: "Type" }),
|
|
142
|
-
/* @__PURE__ */ _(L, { children: "Status" }),
|
|
143
|
-
/* @__PURE__ */ _(L, { children: "Endpoint" }),
|
|
144
|
-
/* @__PURE__ */ _(L, { children: "Last synced" }),
|
|
145
|
-
/* @__PURE__ */ _(L, { children: "Tags" }),
|
|
140
|
+
children: /* @__PURE__ */ v(ne, { children: [/* @__PURE__ */ _(R, { children: /* @__PURE__ */ v(z, { children: [
|
|
141
|
+
/* @__PURE__ */ _(L, { children: b("cosmicintersection.common.name", "Name") }),
|
|
142
|
+
/* @__PURE__ */ _(L, { children: b("cosmicintersection.common.type", "Type") }),
|
|
143
|
+
/* @__PURE__ */ _(L, { children: b("cosmicintersection.common.status", "Status") }),
|
|
144
|
+
/* @__PURE__ */ _(L, { children: b("cosmicintersection.common.endpoint", "Endpoint") }),
|
|
145
|
+
/* @__PURE__ */ _(L, { children: b("cosmicintersection.common.lastSynced", "Last synced") }),
|
|
146
|
+
/* @__PURE__ */ _(L, { children: b("cosmicintersection.common.tags", "Tags") }),
|
|
146
147
|
/* @__PURE__ */ _(L, {
|
|
147
148
|
className: "text-right",
|
|
148
|
-
children: "Actions"
|
|
149
|
+
children: b("cosmicintersection.common.actions", "Actions")
|
|
149
150
|
})
|
|
150
|
-
] }) }), /* @__PURE__ */ _(
|
|
151
|
+
] }) }), /* @__PURE__ */ _(re, { children: X.map((e) => /* @__PURE__ */ v(z, {
|
|
151
152
|
className: "cursor-pointer",
|
|
152
153
|
onClick: () => y(`sources/${e.id}`),
|
|
153
154
|
children: [
|
|
@@ -208,16 +209,16 @@ function G() {
|
|
|
208
209
|
}) }),
|
|
209
210
|
/* @__PURE__ */ _(I, {
|
|
210
211
|
className: "text-right",
|
|
211
|
-
children: /* @__PURE__ */ _(
|
|
212
|
-
ariaLabel:
|
|
212
|
+
children: /* @__PURE__ */ _(a, {
|
|
213
|
+
ariaLabel: b("cosmicintersection.common.actionsFor", "Actions for {{name}}", { name: e.name }),
|
|
213
214
|
entityName: e.name,
|
|
214
215
|
onView: () => y(`sources/${e.id}`),
|
|
215
216
|
onEdit: () => y(`sources/${e.id}/edit`),
|
|
216
217
|
onDelete: () => Q(e),
|
|
217
|
-
deleteLabel: "Archive source",
|
|
218
|
+
deleteLabel: b("cosmicintersection.pages.sources.archive", "Archive source"),
|
|
218
219
|
extraItems: [{
|
|
219
220
|
key: "mark-synced",
|
|
220
|
-
label: "Mark synced",
|
|
221
|
+
label: b("cosmicintersection.pages.sources.markSynced", "Mark synced"),
|
|
221
222
|
icon: T,
|
|
222
223
|
onSelect: () => void Z(e)
|
|
223
224
|
}]
|
|
@@ -233,7 +234,7 @@ function G() {
|
|
|
233
234
|
return /* @__PURE__ */ _("li", { children: /* @__PURE__ */ v("div", {
|
|
234
235
|
role: "button",
|
|
235
236
|
tabIndex: 0,
|
|
236
|
-
"aria-label":
|
|
237
|
+
"aria-label": b("cosmicintersection.common.open", "Open {{name}}", { name: e.name }),
|
|
237
238
|
onClick: () => y(`sources/${e.id}`),
|
|
238
239
|
onKeyDown: (t) => {
|
|
239
240
|
(t.key === "Enter" || t.key === " ") && (t.preventDefault(), y(`sources/${e.id}`));
|
|
@@ -262,16 +263,16 @@ function G() {
|
|
|
262
263
|
})]
|
|
263
264
|
}), /* @__PURE__ */ _("div", {
|
|
264
265
|
onClick: (e) => e.stopPropagation(),
|
|
265
|
-
children: /* @__PURE__ */ _(
|
|
266
|
-
ariaLabel:
|
|
266
|
+
children: /* @__PURE__ */ _(a, {
|
|
267
|
+
ariaLabel: b("cosmicintersection.common.actionsFor", "Actions for {{name}}", { name: e.name }),
|
|
267
268
|
entityName: e.name,
|
|
268
269
|
onView: () => y(`sources/${e.id}`),
|
|
269
270
|
onEdit: () => y(`sources/${e.id}/edit`),
|
|
270
271
|
onDelete: () => Q(e),
|
|
271
|
-
deleteLabel: "Archive source",
|
|
272
|
+
deleteLabel: b("cosmicintersection.pages.sources.archive", "Archive source"),
|
|
272
273
|
extraItems: [{
|
|
273
274
|
key: "mark-synced",
|
|
274
|
-
label: "Mark synced",
|
|
275
|
+
label: b("cosmicintersection.pages.sources.markSynced", "Mark synced"),
|
|
275
276
|
icon: T,
|
|
276
277
|
onSelect: () => void Z(e)
|
|
277
278
|
}]
|
|
@@ -296,7 +297,7 @@ function G() {
|
|
|
296
297
|
}) : null,
|
|
297
298
|
/* @__PURE__ */ v("div", {
|
|
298
299
|
className: "flex items-center justify-between gap-2 text-xs text-text-secondary",
|
|
299
|
-
children: [/* @__PURE__ */ _("span", { children: "Last synced" }), /* @__PURE__ */ _("span", { children: W(e.lastSyncedAt) })]
|
|
300
|
+
children: [/* @__PURE__ */ _("span", { children: b("cosmicintersection.common.lastSynced", "Last synced") }), /* @__PURE__ */ _("span", { children: W(e.lastSyncedAt) })]
|
|
300
301
|
}),
|
|
301
302
|
e.tags.length > 0 ? /* @__PURE__ */ v("div", {
|
|
302
303
|
className: "flex flex-wrap gap-1",
|
|
@@ -314,14 +315,14 @@ function G() {
|
|
|
314
315
|
})
|
|
315
316
|
}),
|
|
316
317
|
/* @__PURE__ */ _(u, {
|
|
317
|
-
offset:
|
|
318
|
-
limit:
|
|
318
|
+
offset: O.offset,
|
|
319
|
+
limit: O.limit,
|
|
319
320
|
loadedCount: X.length,
|
|
320
|
-
hasMore:
|
|
321
|
-
onPrev:
|
|
322
|
-
onNext:
|
|
323
|
-
itemLabel: "sources",
|
|
324
|
-
loading:
|
|
321
|
+
hasMore: ae?.hasMore ?? !1,
|
|
322
|
+
onPrev: O.prevPage,
|
|
323
|
+
onNext: O.nextPage,
|
|
324
|
+
itemLabel: b("cosmicintersection.pages.sources.itemLabel", "sources"),
|
|
325
|
+
loading: K
|
|
325
326
|
})
|
|
326
327
|
] });
|
|
327
328
|
})()]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SourcesPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/sources/SourcesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Archive,\n FileInput,\n FlaskConical,\n Plus,\n Radio,\n RadioTower,\n RefreshCw,\n Satellite,\n Telescope,\n Webhook,\n type LucideIcon,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosArchiveSourceMutation,\n useCosMarkSourceSyncedMutation,\n useCosSourcesQuery,\n type CosSource,\n type CosSourceStatus,\n type CosSourceType,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n SafeLink,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst ALL = 'ALL';\n\nconst SOURCE_TYPES: CosSourceType[] = [\n 'TELESCOPE',\n 'SATELLITE',\n 'GROUND_SENSOR',\n 'ARCHIVE',\n 'API',\n 'STREAM',\n 'FILE_DROP',\n 'SIMULATION',\n];\n\nconst SOURCE_STATUSES: CosSourceStatus[] = [\n 'CONFIGURING',\n 'CONNECTED',\n 'SYNCING',\n 'DEGRADED',\n 'ERROR',\n 'DISABLED',\n];\n\n/** Per-type glyph to aid scannability (mockup 06). */\nconst SOURCE_TYPE_ICON: Record<CosSourceType, LucideIcon> = {\n TELESCOPE: Telescope,\n SATELLITE: Satellite,\n GROUND_SENSOR: RadioTower,\n ARCHIVE: Archive,\n API: Webhook,\n STREAM: Radio,\n FILE_DROP: FileInput,\n SIMULATION: FlaskConical,\n};\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return '—';\n }\n return parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nexport default function SourcesPage() {\n const { navigate } = useCosmicIntersection();\n const [typeFilter, setTypeFilter] = useState<string>(ALL);\n const [statusFilter, setStatusFilter] = useState<string>(ALL);\n const pagination = usePagination({ pageSize: 20, resetKey: `${typeFilter}|${statusFilter}` });\n\n const { data, loading, error, refetch } = useCosSourcesQuery({\n type: typeFilter === ALL ? undefined : (typeFilter as CosSourceType),\n status: statusFilter === ALL ? undefined : (statusFilter as CosSourceStatus),\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const [markSynced] = useCosMarkSourceSyncedMutation();\n const [archiveSource] = useCosArchiveSourceMutation();\n\n const sources = useMemo(() => data?.cosListSources.items ?? [], [data]);\n const pageInfo = data?.cosListSources.pageInfo;\n\n const handleMarkSynced = async (source: CosSource) => {\n const response = await markSynced({ variables: { id: source.id } });\n if (response.data?.cosMarkSourceSynced) {\n notify.success('Source marked synced', `“${source.name}” is now up to date.`);\n await refetch();\n }\n };\n\n const handleArchive = async (source: CosSource) => {\n const response = await archiveSource({ variables: { id: source.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveSource) {\n notify.error('Could not archive source', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n notify.success('Source archived');\n await refetch();\n };\n\n const newButton = (\n <Button type=\"button\" onClick={() => navigate('sources/create')}>\n <Plus className=\"size-4\" />\n New source\n </Button>\n );\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading sources…\" />;\n }\n if (error) {\n return <ErrorState message={error.message} onRetry={() => void refetch()} />;\n }\n if (sources.length === 0) {\n const filtered = typeFilter !== ALL || statusFilter !== ALL;\n return (\n <EmptyState\n icon={Radio}\n title={filtered ? 'No sources match these filters' : 'No data sources yet'}\n description={\n filtered\n ? 'Try clearing the type or status filter to see more sources.'\n : 'Connect a telescope, satellite feed, archive or API to start ingesting scientific data.'\n }\n action={filtered ? undefined : newButton}\n />\n );\n }\n\n return (\n <>\n {/* Desktop / tablet: table */}\n <div className=\"hidden overflow-hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Type</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Endpoint</TableHead>\n <TableHead>Last synced</TableHead>\n <TableHead>Tags</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {sources.map((source) => {\n const TypeIcon = SOURCE_TYPE_ICON[source.type] ?? Radio;\n return (\n <TableRow\n key={source.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`sources/${source.id}`)}\n >\n <TableCell>\n <div className=\"flex items-start gap-2.5\">\n <span className=\"mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-button bg-bg-sunken text-text-secondary\">\n <TypeIcon className=\"size-4\" aria-hidden=\"true\" />\n </span>\n <div className=\"min-w-0\">\n <div className=\"font-medium text-text-primary\">{source.name}</div>\n {source.description ? (\n <div className=\"mt-0.5 line-clamp-1 max-w-xs text-xs text-text-secondary\">\n {source.description}\n </div>\n ) : null}\n </div>\n </div>\n </TableCell>\n <TableCell>\n <Badge variant=\"secondary\">{formatEnumLabel(source.type)}</Badge>\n </TableCell>\n <TableCell>\n <StatusBadge status={source.status} />\n </TableCell>\n <TableCell\n className=\"max-w-[16rem]\"\n onClick={(event) => event.stopPropagation()}\n >\n {source.endpoint ? (\n <SafeLink\n href={source.endpoint}\n className=\"block truncate text-sm text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"block truncate text-sm text-text-secondary\"\n >\n {source.endpoint}\n </SafeLink>\n ) : (\n <span className=\"text-sm text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(source.lastSyncedAt)}\n </TableCell>\n <TableCell>\n <div className=\"flex max-w-[12rem] flex-wrap gap-1\">\n {source.tags.length === 0 ? (\n <span className=\"text-xs text-text-secondary\">—</span>\n ) : (\n source.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))\n )}\n {source.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{source.tags.length - 3}\n </span>\n ) : null}\n </div>\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${source.name}`}\n entityName={source.name}\n onView={() => navigate(`sources/${source.id}`)}\n onEdit={() => navigate(`sources/${source.id}/edit`)}\n onDelete={() => handleArchive(source)}\n deleteLabel=\"Archive source\"\n extraItems={[\n {\n key: 'mark-synced',\n label: 'Mark synced',\n icon: RefreshCw,\n onSelect: () => void handleMarkSynced(source),\n },\n ]}\n />\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile: stacked cards */}\n <ul className=\"space-y-3 md:hidden\">\n {sources.map((source) => {\n const TypeIcon = SOURCE_TYPE_ICON[source.type] ?? Radio;\n return (\n <li key={source.id}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-label={`Open ${source.name}`}\n onClick={() => navigate(`sources/${source.id}`)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n navigate(`sources/${source.id}`);\n }\n }}\n className=\"flex flex-col gap-3 rounded-card border border-border-default bg-bg-elevated p-4 outline-none transition-colors hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span className=\"mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-button bg-bg-sunken text-text-secondary\">\n <TypeIcon className=\"size-4\" aria-hidden=\"true\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary\">{source.name}</p>\n {source.description ? (\n <p className=\"mt-0.5 line-clamp-2 text-xs text-text-secondary\">\n {source.description}\n </p>\n ) : null}\n </div>\n </div>\n <div onClick={(event) => event.stopPropagation()}>\n <ActionMenu\n ariaLabel={`Actions for ${source.name}`}\n entityName={source.name}\n onView={() => navigate(`sources/${source.id}`)}\n onEdit={() => navigate(`sources/${source.id}/edit`)}\n onDelete={() => handleArchive(source)}\n deleteLabel=\"Archive source\"\n extraItems={[\n {\n key: 'mark-synced',\n label: 'Mark synced',\n icon: RefreshCw,\n onSelect: () => void handleMarkSynced(source),\n },\n ]}\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <Badge variant=\"secondary\">{formatEnumLabel(source.type)}</Badge>\n <StatusBadge status={source.status} />\n </div>\n\n {source.endpoint ? (\n <div onClick={(event) => event.stopPropagation()}>\n <SafeLink\n href={source.endpoint}\n className=\"block truncate text-sm text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"block truncate text-sm text-text-secondary\"\n >\n {source.endpoint}\n </SafeLink>\n </div>\n ) : null}\n\n <div className=\"flex items-center justify-between gap-2 text-xs text-text-secondary\">\n <span>Last synced</span>\n <span>{formatDate(source.lastSyncedAt)}</span>\n </div>\n\n {source.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {source.tags.slice(0, 4).map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {source.tags.length > 4 ? (\n <span className=\"text-xs text-text-secondary\">\n +{source.tags.length - 4}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </li>\n );\n })}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={sources.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"sources\"\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Data sources\"\n description=\"Instruments, archives and feeds that supply raw data into your federation.\"\n actions={newButton}\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"w-full max-w-[14rem]\">\n <Select value={typeFilter} onValueChange={setTypeFilter}>\n <SelectTrigger aria-label=\"Filter by type\">\n <SelectValue placeholder=\"All types\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All types</SelectItem>\n {SOURCE_TYPES.map((type) => (\n <SelectItem key={type} value={type}>\n {formatEnumLabel(type)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"w-full max-w-[14rem]\">\n <Select value={statusFilter} onValueChange={setStatusFilter}>\n <SelectTrigger aria-label=\"Filter by status\">\n <SelectValue placeholder=\"All statuses\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {SOURCE_STATUSES.map((status) => (\n <SelectItem key={status} value={status}>\n {formatEnumLabel(status)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n {renderBody()}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SourcesPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/sources/SourcesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Archive,\n FileInput,\n FlaskConical,\n Plus,\n Radio,\n RadioTower,\n RefreshCw,\n Satellite,\n Telescope,\n Webhook,\n type LucideIcon,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosArchiveSourceMutation,\n useCosMarkSourceSyncedMutation,\n useCosSourcesQuery,\n type CosSource,\n type CosSourceStatus,\n type CosSourceType,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n SafeLink,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\nimport { useTr } from '../../../shared/hooks/useTr';\n\nconst ALL = 'ALL';\n\nconst SOURCE_TYPES: CosSourceType[] = [\n 'TELESCOPE',\n 'SATELLITE',\n 'GROUND_SENSOR',\n 'ARCHIVE',\n 'API',\n 'STREAM',\n 'FILE_DROP',\n 'SIMULATION',\n];\n\nconst SOURCE_STATUSES: CosSourceStatus[] = [\n 'CONFIGURING',\n 'CONNECTED',\n 'SYNCING',\n 'DEGRADED',\n 'ERROR',\n 'DISABLED',\n];\n\n/** Per-type glyph to aid scannability (mockup 06). */\nconst SOURCE_TYPE_ICON: Record<CosSourceType, LucideIcon> = {\n TELESCOPE: Telescope,\n SATELLITE: Satellite,\n GROUND_SENSOR: RadioTower,\n ARCHIVE: Archive,\n API: Webhook,\n STREAM: Radio,\n FILE_DROP: FileInput,\n SIMULATION: FlaskConical,\n};\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return '—';\n }\n return parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nexport default function SourcesPage() {\n const { navigate } = useCosmicIntersection();\n const tr = useTr();\n const [typeFilter, setTypeFilter] = useState<string>(ALL);\n const [statusFilter, setStatusFilter] = useState<string>(ALL);\n const pagination = usePagination({ pageSize: 20, resetKey: `${typeFilter}|${statusFilter}` });\n\n const { data, loading, error, refetch } = useCosSourcesQuery({\n type: typeFilter === ALL ? undefined : (typeFilter as CosSourceType),\n status: statusFilter === ALL ? undefined : (statusFilter as CosSourceStatus),\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const [markSynced] = useCosMarkSourceSyncedMutation();\n const [archiveSource] = useCosArchiveSourceMutation();\n\n const sources = useMemo(() => data?.cosListSources.items ?? [], [data]);\n const pageInfo = data?.cosListSources.pageInfo;\n\n const handleMarkSynced = async (source: CosSource) => {\n const response = await markSynced({ variables: { id: source.id } });\n if (response.data?.cosMarkSourceSynced) {\n notify.success(\n tr('cosmicintersection.pages.sources.syncSuccess', 'Source marked synced'),\n tr(\n 'cosmicintersection.pages.sources.syncSuccessDescription',\n '“{{name}}” is now up to date.',\n { name: source.name },\n ),\n );\n await refetch();\n }\n };\n\n const handleArchive = async (source: CosSource) => {\n const response = await archiveSource({ variables: { id: source.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveSource) {\n notify.error(\n tr('cosmicintersection.pages.sources.archiveError', 'Could not archive source'),\n tr('cosmicintersection.pages.sources.archiveErrorDescription', 'The server did not confirm the change.'),\n );\n throw new Error('Archive not confirmed');\n }\n notify.success(tr('cosmicintersection.pages.sources.archiveSuccess', 'Source archived'));\n await refetch();\n };\n\n const newButton = (\n <Button type=\"button\" onClick={() => navigate('sources/create')}>\n <Plus className=\"size-4\" />\n {tr('cosmicintersection.pages.sources.create', 'New source')}\n </Button>\n );\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label={tr('cosmicintersection.pages.sources.loading', 'Loading sources…')} />;\n }\n if (error) {\n return <ErrorState message={error.message} onRetry={() => void refetch()} />;\n }\n if (sources.length === 0) {\n const filtered = typeFilter !== ALL || statusFilter !== ALL;\n return (\n <EmptyState\n icon={Radio}\n title={\n filtered\n ? tr('cosmicintersection.pages.sources.emptyFilterTitle', 'No sources match these filters')\n : tr('cosmicintersection.pages.sources.emptyTitle', 'No data sources yet')\n }\n description={\n filtered\n ? tr(\n 'cosmicintersection.pages.sources.emptyFilterDescription',\n 'Try clearing the type or status filter to see more sources.',\n )\n : tr(\n 'cosmicintersection.pages.sources.emptyDescription',\n 'Connect a telescope, satellite feed, archive or API to start ingesting scientific data.',\n )\n }\n action={filtered ? undefined : newButton}\n />\n );\n }\n\n return (\n <>\n {/* Desktop / tablet: table */}\n <div className=\"hidden overflow-hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('cosmicintersection.common.name', 'Name')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.type', 'Type')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.status', 'Status')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.endpoint', 'Endpoint')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.lastSynced', 'Last synced')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.tags', 'Tags')}</TableHead>\n <TableHead className=\"text-right\">{tr('cosmicintersection.common.actions', 'Actions')}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {sources.map((source) => {\n const TypeIcon = SOURCE_TYPE_ICON[source.type] ?? Radio;\n return (\n <TableRow\n key={source.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`sources/${source.id}`)}\n >\n <TableCell>\n <div className=\"flex items-start gap-2.5\">\n <span className=\"mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-button bg-bg-sunken text-text-secondary\">\n <TypeIcon className=\"size-4\" aria-hidden=\"true\" />\n </span>\n <div className=\"min-w-0\">\n <div className=\"font-medium text-text-primary\">{source.name}</div>\n {source.description ? (\n <div className=\"mt-0.5 line-clamp-1 max-w-xs text-xs text-text-secondary\">\n {source.description}\n </div>\n ) : null}\n </div>\n </div>\n </TableCell>\n <TableCell>\n <Badge variant=\"secondary\">{formatEnumLabel(source.type)}</Badge>\n </TableCell>\n <TableCell>\n <StatusBadge status={source.status} />\n </TableCell>\n <TableCell\n className=\"max-w-[16rem]\"\n onClick={(event) => event.stopPropagation()}\n >\n {source.endpoint ? (\n <SafeLink\n href={source.endpoint}\n className=\"block truncate text-sm text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"block truncate text-sm text-text-secondary\"\n >\n {source.endpoint}\n </SafeLink>\n ) : (\n <span className=\"text-sm text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(source.lastSyncedAt)}\n </TableCell>\n <TableCell>\n <div className=\"flex max-w-[12rem] flex-wrap gap-1\">\n {source.tags.length === 0 ? (\n <span className=\"text-xs text-text-secondary\">—</span>\n ) : (\n source.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))\n )}\n {source.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{source.tags.length - 3}\n </span>\n ) : null}\n </div>\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={tr('cosmicintersection.common.actionsFor', 'Actions for {{name}}', { name: source.name })}\n entityName={source.name}\n onView={() => navigate(`sources/${source.id}`)}\n onEdit={() => navigate(`sources/${source.id}/edit`)}\n onDelete={() => handleArchive(source)}\n deleteLabel={tr('cosmicintersection.pages.sources.archive', 'Archive source')}\n extraItems={[\n {\n key: 'mark-synced',\n label: tr('cosmicintersection.pages.sources.markSynced', 'Mark synced'),\n icon: RefreshCw,\n onSelect: () => void handleMarkSynced(source),\n },\n ]}\n />\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile: stacked cards */}\n <ul className=\"space-y-3 md:hidden\">\n {sources.map((source) => {\n const TypeIcon = SOURCE_TYPE_ICON[source.type] ?? Radio;\n return (\n <li key={source.id}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-label={tr('cosmicintersection.common.open', 'Open {{name}}', { name: source.name })}\n onClick={() => navigate(`sources/${source.id}`)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n navigate(`sources/${source.id}`);\n }\n }}\n className=\"flex flex-col gap-3 rounded-card border border-border-default bg-bg-elevated p-4 outline-none transition-colors hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span className=\"mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-button bg-bg-sunken text-text-secondary\">\n <TypeIcon className=\"size-4\" aria-hidden=\"true\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary\">{source.name}</p>\n {source.description ? (\n <p className=\"mt-0.5 line-clamp-2 text-xs text-text-secondary\">\n {source.description}\n </p>\n ) : null}\n </div>\n </div>\n <div onClick={(event) => event.stopPropagation()}>\n <ActionMenu\n ariaLabel={tr('cosmicintersection.common.actionsFor', 'Actions for {{name}}', { name: source.name })}\n entityName={source.name}\n onView={() => navigate(`sources/${source.id}`)}\n onEdit={() => navigate(`sources/${source.id}/edit`)}\n onDelete={() => handleArchive(source)}\n deleteLabel={tr('cosmicintersection.pages.sources.archive', 'Archive source')}\n extraItems={[\n {\n key: 'mark-synced',\n label: tr('cosmicintersection.pages.sources.markSynced', 'Mark synced'),\n icon: RefreshCw,\n onSelect: () => void handleMarkSynced(source),\n },\n ]}\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <Badge variant=\"secondary\">{formatEnumLabel(source.type)}</Badge>\n <StatusBadge status={source.status} />\n </div>\n\n {source.endpoint ? (\n <div onClick={(event) => event.stopPropagation()}>\n <SafeLink\n href={source.endpoint}\n className=\"block truncate text-sm text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"block truncate text-sm text-text-secondary\"\n >\n {source.endpoint}\n </SafeLink>\n </div>\n ) : null}\n\n <div className=\"flex items-center justify-between gap-2 text-xs text-text-secondary\">\n <span>{tr('cosmicintersection.common.lastSynced', 'Last synced')}</span>\n <span>{formatDate(source.lastSyncedAt)}</span>\n </div>\n\n {source.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {source.tags.slice(0, 4).map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {source.tags.length > 4 ? (\n <span className=\"text-xs text-text-secondary\">\n +{source.tags.length - 4}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </li>\n );\n })}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={sources.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel={tr('cosmicintersection.pages.sources.itemLabel', 'sources')}\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title={tr('cosmicintersection.pages.sources.title', 'Data sources')}\n description={tr(\n 'cosmicintersection.pages.sources.description',\n 'Instruments, archives and feeds that supply raw data into your federation.',\n )}\n actions={newButton}\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"w-full max-w-[14rem]\">\n <Select value={typeFilter} onValueChange={setTypeFilter}>\n <SelectTrigger aria-label={tr('cosmicintersection.common.filterByType', 'Filter by type')}>\n <SelectValue placeholder={tr('cosmicintersection.common.allTypes', 'All types')} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>{tr('cosmicintersection.common.allTypes', 'All types')}</SelectItem>\n {SOURCE_TYPES.map((type) => (\n <SelectItem key={type} value={type}>\n {formatEnumLabel(type)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"w-full max-w-[14rem]\">\n <Select value={statusFilter} onValueChange={setStatusFilter}>\n <SelectTrigger aria-label={tr('cosmicintersection.common.filterByStatus', 'Filter by status')}>\n <SelectValue placeholder={tr('cosmicintersection.common.allStatuses', 'All statuses')} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>{tr('cosmicintersection.common.allStatuses', 'All statuses')}</SelectItem>\n {SOURCE_STATUSES.map((status) => (\n <SelectItem key={status} value={status}>\n {formatEnumLabel(status)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n {renderBody()}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
package/dist/dev/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/dev/main.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/dev/main.tsx"],"names":[],"mappings":"AAKA,OAAO,cAAc,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CosmicIntersection MFE translation hook.
|
|
3
|
+
*
|
|
4
|
+
* The app shell owns the real I18nProvider and translation bundles.
|
|
5
|
+
* This hook just wraps `useI18n` so callers can safely fall back to English
|
|
6
|
+
* when a key is missing. The local dev harness wraps the MFE root with a
|
|
7
|
+
* minimal I18nProvider so this hook never throws during isolated development.
|
|
8
|
+
*/
|
|
9
|
+
type TranslationParams = Record<string, string | number>;
|
|
10
|
+
export declare function useTr(): (key: string, fallback: string, params?: TranslationParams) => string;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=useTr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTr.d.ts","sourceRoot":"","sources":["../../../src/shared/hooks/useTr.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,KAAK,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAEzD,wBAAgB,KAAK,KAIjB,KAAK,MAAM,EACX,UAAU,MAAM,EAChB,SAAS,iBAAiB,KACzB,MAAM,CAaV"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useI18n as e } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
2
|
+
//#region src/shared/hooks/useTr.ts
|
|
3
|
+
function t() {
|
|
4
|
+
let { t } = e();
|
|
5
|
+
return (e, n, r) => {
|
|
6
|
+
let i = t(e, r);
|
|
7
|
+
return i === e ? r ? n.replace(/\{\{\s*(\w+)\s*\}\}/g, (e, t) => {
|
|
8
|
+
let n = r[t];
|
|
9
|
+
return n === void 0 ? `{{${t}}}` : String(n);
|
|
10
|
+
}) : n : i;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { t as useTr };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=useTr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTr.js","names":[],"sources":["../../../src/shared/hooks/useTr.ts"],"sourcesContent":["import { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\n\n/**\n * CosmicIntersection MFE translation hook.\n *\n * The app shell owns the real I18nProvider and translation bundles.\n * This hook just wraps `useI18n` so callers can safely fall back to English\n * when a key is missing. The local dev harness wraps the MFE root with a\n * minimal I18nProvider so this hook never throws during isolated development.\n */\ntype TranslationParams = Record<string, string | number>;\n\nexport function useTr() {\n const { t } = useI18n();\n\n return (\n key: string,\n fallback: string,\n params?: TranslationParams,\n ): string => {\n const translated = t(key, params);\n // When the key is missing, the provider returns the key itself.\n if (translated === key) {\n // Apply interpolation to the fallback so callers still get params filled.\n if (!params) return fallback;\n return fallback.replace(/\\{\\{\\s*(\\w+)\\s*\\}\\}/g, (_, paramKey) => {\n const value = params[paramKey];\n return value === undefined ? `{{${paramKey}}}` : String(value);\n });\n }\n return translated;\n };\n}\n"],"mappings":";;AAYA,SAAgB,IAAQ;CACtB,IAAM,EAAE,MAAM,EAAQ;CAEtB,QACE,GACA,GACA,MACW;EACX,IAAM,IAAa,EAAE,GAAK,CAAM;EAUhC,OARI,MAAe,IAEZ,IACE,EAAS,QAAQ,yBAAyB,GAAG,MAAa;GAC/D,IAAM,IAAQ,EAAO;GACrB,OAAO,MAAU,KAAA,IAAY,KAAK,EAAS,MAAM,OAAO,CAAK;EAC/D,CAAC,IAJmB,IAMf;CACT;AACF"}
|