@open-mercato/core 0.6.6-develop.6300.1.08ff40c897 → 0.6.6-develop.6304.1.4cf2b975cb

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/data_sync/backend/data-sync/runs/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { usePathname, useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { LogList, type LogListEntry } from '@open-mercato/ui/backend/LogList'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { RotateCcw, XCircle } from 'lucide-react'\nimport { getSyncRunStatusVariant } from '../../../../lib/syncRunStatus'\n\ntype SyncRunDetail = {\n id: string\n integrationId: string\n entityType: string\n direction: 'import' | 'export'\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'paused'\n createdCount: number\n updatedCount: number\n skippedCount: number\n failedCount: number\n batchesCompleted: number\n lastError: string | null\n progressJobId: string | null\n progressJob: {\n id: string\n status: string\n progressPercent: number\n processedCount: number\n totalCount: number | null\n etaSeconds: number | null\n meta?: Record<string, unknown> | null\n } | null\n triggeredBy: string | null\n createdAt: string\n updatedAt: string\n}\n\ntype ProgressEventPayload = {\n jobId?: string\n status?: string\n progressPercent?: number\n processedCount?: number\n totalCount?: number | null\n etaSeconds?: number | null\n meta?: Record<string, unknown> | null\n}\n\ntype LogEntry = {\n id: string\n level: 'info' | 'warn' | 'error'\n message: string\n createdAt: string\n payload?: Record<string, unknown> | null\n}\n\nfunction formatEtaSeconds(seconds: number): string {\n if (seconds < 60) return `${seconds}s`\n if (seconds < 3600) return `${Math.ceil(seconds / 60)}m`\n const hours = Math.floor(seconds / 3600)\n const minutes = Math.ceil((seconds % 3600) / 60)\n return `${hours}h ${minutes}m`\n}\n\ntype SyncRunDetailPageProps = {\n params?: {\n id?: string | string[]\n }\n}\n\nfunction resolveRouteId(value: string | string[] | undefined): string | undefined {\n if (Array.isArray(value)) return value[0]\n return value\n}\n\nfunction resolvePathnameId(pathname: string): string | undefined {\n const parts = pathname.split('/').filter(Boolean)\n const runId = parts.at(-1)\n if (!runId || runId === 'runs' || runId === 'data-sync') return undefined\n return decodeURIComponent(runId)\n}\n\nexport default function SyncRunDetailPage({ params }: SyncRunDetailPageProps) {\n const pathname = usePathname()\n const router = useRouter()\n const runId = resolveRouteId(params?.id) ?? resolvePathnameId(pathname)\n const t = useT()\n\n const [run, setRun] = React.useState<SyncRunDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [logs, setLogs] = React.useState<LogEntry[]>([])\n const [isLoadingLogs, setIsLoadingLogs] = React.useState(false)\n\n const resolveCurrentRunId = React.useCallback(() => {\n return runId ?? (\n typeof window !== 'undefined'\n ? resolvePathnameId(window.location.pathname)\n : undefined\n )\n }, [runId])\n\n const loadRun = React.useCallback(async () => {\n const currentRunId = resolveCurrentRunId()\n if (!currentRunId) {\n setError(t('data_sync.runs.detail.loadError'))\n setIsLoading(false)\n return\n }\n setIsNotFound(false)\n const call = await apiCall<SyncRunDetail>(\n `/api/data_sync/runs/${encodeURIComponent(currentRunId)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n if (call.status === 404) {\n setIsNotFound(true)\n } else {\n setError(t('data_sync.runs.detail.loadError'))\n }\n setIsLoading(false)\n return\n }\n setRun(call.result)\n setIsLoading(false)\n }, [resolveCurrentRunId, t])\n\n const loadLogs = React.useCallback(async () => {\n const currentRunId = resolveCurrentRunId()\n if (!currentRunId) return\n setIsLoadingLogs(true)\n const params = new URLSearchParams({ runId: currentRunId, pageSize: '50' })\n const call = await apiCall<{ items: LogEntry[] }>(\n `/api/integrations/logs?${params.toString()}`,\n undefined,\n { fallback: { items: [] } },\n )\n if (call.ok && call.result) {\n setLogs(call.result.items)\n }\n setIsLoadingLogs(false)\n }, [resolveCurrentRunId])\n\n React.useEffect(() => {\n void loadRun()\n void loadLogs()\n }, [loadRun, loadLogs])\n\n const handleProgressEvent = React.useCallback((payload: ProgressEventPayload) => {\n const eventJobId = typeof payload.jobId === 'string' ? payload.jobId : null\n if (!eventJobId) return\n\n setRun((current) => {\n if (!current?.progressJobId || current.progressJobId !== eventJobId) return current\n return {\n ...current,\n status: (payload.status as SyncRunDetail['status']) ?? current.status,\n progressJob: {\n id: eventJobId,\n status: payload.status ?? current.progressJob?.status ?? current.status,\n progressPercent: payload.progressPercent ?? current.progressJob?.progressPercent ?? 0,\n processedCount: payload.processedCount ?? current.progressJob?.processedCount ?? 0,\n totalCount: payload.totalCount ?? current.progressJob?.totalCount ?? null,\n etaSeconds: payload.etaSeconds ?? current.progressJob?.etaSeconds ?? null,\n meta: payload.meta ?? current.progressJob?.meta ?? null,\n },\n }\n })\n }, [])\n\n useAppEvent('progress.job.updated', (event) => {\n handleProgressEvent(event.payload as ProgressEventPayload)\n }, [handleProgressEvent])\n\n useAppEvent('progress.job.started', (event) => {\n handleProgressEvent(event.payload as ProgressEventPayload)\n }, [handleProgressEvent])\n\n useAppEvent('progress.job.completed', (event) => {\n handleProgressEvent(event.payload as ProgressEventPayload)\n void loadRun()\n void loadLogs()\n }, [handleProgressEvent, loadLogs, loadRun])\n\n useAppEvent('progress.job.failed', (event) => {\n handleProgressEvent(event.payload as ProgressEventPayload)\n void loadRun()\n void loadLogs()\n }, [handleProgressEvent, loadLogs, loadRun])\n\n useAppEvent('progress.job.cancelled', (event) => {\n handleProgressEvent(event.payload as ProgressEventPayload)\n void loadRun()\n void loadLogs()\n }, [handleProgressEvent, loadLogs, loadRun])\n\n useAppEvent('om:bridge:reconnected', () => {\n void loadRun()\n void loadLogs()\n }, [loadLogs, loadRun])\n\n const handleCancel = React.useCallback(async () => {\n const currentRunId = resolveCurrentRunId()\n if (!currentRunId) return\n const call = await apiCall(`/api/data_sync/runs/${encodeURIComponent(currentRunId)}/cancel`, {\n method: 'POST',\n }, { fallback: null })\n if (call.ok) {\n flash(t('data_sync.runs.detail.cancelSuccess'), 'success')\n void loadRun()\n } else {\n flash(t('data_sync.runs.detail.cancelError'), 'error')\n }\n }, [resolveCurrentRunId, t, loadRun])\n\n const handleRetry = React.useCallback(async () => {\n const currentRunId = resolveCurrentRunId()\n if (!currentRunId) return\n const call = await apiCall<{ id: string }>(`/api/data_sync/runs/${encodeURIComponent(currentRunId)}/retry`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ fromBeginning: false }),\n }, { fallback: null })\n if (call.ok && call.result) {\n flash(t('data_sync.runs.detail.retrySuccess'), 'success')\n router.push(`/backend/data-sync/runs/${encodeURIComponent(call.result.id)}`)\n } else {\n flash(t('data_sync.runs.detail.retryError'), 'error')\n }\n }, [resolveCurrentRunId, router, t])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('data_sync.runs.detail.title')} /></PageBody></Page>\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('data_sync.runs.detail.notFound', 'Sync run not found.')}\n backHref=\"/backend/data-sync\"\n backLabel={t('data_sync.runs.detail.back')}\n />\n </PageBody>\n </Page>\n )\n }\n if (error || !run) return <Page><PageBody><ErrorMessage label={error ?? t('data_sync.runs.detail.loadError')} /></PageBody></Page>\n\n const totalProcessed = run.createdCount + run.updatedCount + run.skippedCount + run.failedCount\n const progressPercent = run.progressJob?.progressPercent ?? (run.status === 'completed' ? 100 : 0)\n const progressStatus = run.progressJob?.status ?? run.status\n const processedCount = run.progressJob?.processedCount ?? totalProcessed\n const hasProgressTotal = typeof run.progressJob?.totalCount === 'number' && run.progressJob.totalCount > 0\n const etaLabel = run.progressJob?.etaSeconds && run.progressJob.etaSeconds > 0\n ? formatEtaSeconds(run.progressJob.etaSeconds)\n : null\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/data-sync\"\n backLabel={t('data_sync.runs.detail.back')}\n entityTypeLabel={t('data_sync.runs.detail.title')}\n title={`${run.integrationId} \u2014 ${run.entityType}`}\n statusBadge={(\n <div className=\"mt-2 flex flex-wrap gap-2\">\n <Badge variant=\"outline\">{t(`data_sync.dashboard.direction.${run.direction}`)}</Badge>\n <StatusBadge variant={getSyncRunStatusVariant(run.status)}>\n {t(`data_sync.dashboard.status.${run.status}`)}\n </StatusBadge>\n {run.triggeredBy ? <Badge variant=\"outline\">{run.triggeredBy}</Badge> : null}\n </div>\n )}\n actionsContent={(\n <>\n {(run.status === 'running' || run.status === 'pending') ? (\n <Button type=\"button\" variant=\"destructive\" size=\"sm\" onClick={() => void handleCancel()}>\n <XCircle className=\"mr-2 h-4 w-4\" />\n {t('data_sync.runs.detail.cancel')}\n </Button>\n ) : null}\n {run.status === 'failed' ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleRetry()}>\n <RotateCcw className=\"mr-2 h-4 w-4\" />\n {t('data_sync.runs.detail.retry')}\n </Button>\n ) : null}\n </>\n )}\n />\n\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between gap-3\">\n <CardTitle>{t('data_sync.runs.detail.progress')}</CardTitle>\n <StatusBadge variant={getSyncRunStatusVariant(progressStatus)}>\n {t(`data_sync.dashboard.status.${progressStatus}`)}\n </StatusBadge>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <span className=\"font-medium\">\n {hasProgressTotal\n ? t('data_sync.runs.detail.progress.percent', { percent: progressPercent })\n : t('data_sync.runs.detail.progress.itemsProcessed', { count: processedCount })}\n </span>\n {etaLabel ? (\n <span className=\"text-muted-foreground\">\n {t('data_sync.runs.detail.progress.eta', { eta: etaLabel })}\n </span>\n ) : null}\n </div>\n {hasProgressTotal ? (\n <Progress value={progressPercent} className=\"h-3\" />\n ) : (\n <div className=\"relative h-3 w-full overflow-hidden rounded-full bg-secondary\">\n <div className=\"absolute inset-y-0 left-0 w-1/2 animate-pulse rounded-full bg-primary/80\" />\n <div className=\"absolute inset-y-0 right-0 w-1/3 rounded-full bg-primary/10\" />\n </div>\n )}\n <div className=\"flex flex-wrap items-center justify-between gap-2 text-sm text-muted-foreground\">\n <span>\n {hasProgressTotal\n ? t('data_sync.runs.detail.progress.itemsProcessedTotal', {\n processed: processedCount,\n total: run.progressJob?.totalCount ?? 0,\n })\n : t('data_sync.runs.detail.progress.itemsProcessed', { count: processedCount })}\n </span>\n <span>{t('data_sync.runs.detail.progress.batches', { count: run.batchesCompleted })}</span>\n </div>\n </CardContent>\n </Card>\n\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-6 text-center\">\n <div className=\"text-2xl font-bold text-status-success-text\">{run.createdCount}</div>\n <p className=\"text-sm text-muted-foreground\">{t('data_sync.runs.detail.counters.created')}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-6 text-center\">\n <div className=\"text-2xl font-bold text-status-info-text\">{run.updatedCount}</div>\n <p className=\"text-sm text-muted-foreground\">{t('data_sync.runs.detail.counters.updated')}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-6 text-center\">\n <div className=\"text-2xl font-bold text-muted-foreground\">{run.skippedCount}</div>\n <p className=\"text-sm text-muted-foreground\">{t('data_sync.runs.detail.counters.skipped')}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-6 text-center\">\n <div className=\"text-2xl font-bold text-status-error-text\">{run.failedCount}</div>\n <p className=\"text-sm text-muted-foreground\">{t('data_sync.runs.detail.counters.failed')}</p>\n </CardContent>\n </Card>\n </div>\n\n {run.lastError && (\n <Card className=\"border-status-error-border bg-status-error-bg\">\n <CardHeader>\n <CardTitle className=\"text-status-error-text\">{t('data_sync.runs.detail.error')}</CardTitle>\n </CardHeader>\n <CardContent>\n <pre className=\"text-sm text-status-error-text whitespace-pre-wrap\">{run.lastError}</pre>\n </CardContent>\n </Card>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>{t('data_sync.runs.detail.logs')}</CardTitle>\n </CardHeader>\n <CardContent>\n {isLoadingLogs ? (\n <div className=\"flex justify-center py-4\"><Spinner /></div>\n ) : (\n <LogList\n entries={logs.map<LogListEntry>((log) => ({\n id: log.id,\n time: new Date(log.createdAt).toLocaleString(),\n level: log.level,\n message: log.message,\n body: log.payload ? (\n <pre className=\"overflow-x-auto whitespace-pre-wrap rounded-md border bg-card p-3 text-xs\">\n {JSON.stringify(log.payload, null, 2)}\n </pre>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {t('data_sync.runs.detail.logs.noPayload', 'No payload recorded for this log entry.')}\n </p>\n ),\n }))}\n emptyMessage={t('data_sync.runs.detail.noLogs')}\n />\n )}\n </CardContent>\n </Card>\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { usePathname, useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { LogList, type LogListEntry } from '@open-mercato/ui/backend/LogList'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { RotateCcw, XCircle } from 'lucide-react'\nimport { getSyncRunStatusVariant } from '../../../../lib/syncRunStatus'\n\ntype SyncRunDetail = {\n id: string\n integrationId: string\n entityType: string\n direction: 'import' | 'export'\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'paused'\n createdCount: number\n updatedCount: number\n skippedCount: number\n failedCount: number\n batchesCompleted: number\n lastError: string | null\n progressJobId: string | null\n progressJob: {\n id: string\n status: string\n progressPercent: number\n processedCount: number\n totalCount: number | null\n etaSeconds: number | null\n meta?: Record<string, unknown> | null\n } | null\n triggeredBy: string | null\n createdAt: string\n updatedAt: string\n}\n\ntype ProgressEventPayload = {\n jobId?: string\n status?: string\n progressPercent?: number\n processedCount?: number\n totalCount?: number | null\n etaSeconds?: number | null\n meta?: Record<string, unknown> | null\n}\n\ntype LogEntry = {\n id: string\n level: 'info' | 'warn' | 'error'\n message: string\n createdAt: string\n payload?: Record<string, unknown> | null\n}\n\nfunction formatEtaSeconds(seconds: number): string {\n if (seconds < 60) return `${seconds}s`\n if (seconds < 3600) return `${Math.ceil(seconds / 60)}m`\n const hours = Math.floor(seconds / 3600)\n const minutes = Math.ceil((seconds % 3600) / 60)\n return `${hours}h ${minutes}m`\n}\n\ntype SyncRunDetailPageProps = {\n params?: {\n id?: string | string[]\n }\n}\n\nfunction resolveRouteId(value: string | string[] | undefined): string | undefined {\n if (Array.isArray(value)) return value[0]\n return value\n}\n\nfunction resolvePathnameId(pathname: string): string | undefined {\n const parts = pathname.split('/').filter(Boolean)\n const runId = parts.at(-1)\n if (!runId || runId === 'runs' || runId === 'data-sync') return undefined\n return decodeURIComponent(runId)\n}\n\nexport default function SyncRunDetailPage({ params }: SyncRunDetailPageProps) {\n const pathname = usePathname()\n const router = useRouter()\n const runId = resolveRouteId(params?.id) ?? resolvePathnameId(pathname)\n const t = useT()\n const { runMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'data_sync.runDetail',\n })\n\n const [run, setRun] = React.useState<SyncRunDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [logs, setLogs] = React.useState<LogEntry[]>([])\n const [isLoadingLogs, setIsLoadingLogs] = React.useState(false)\n\n const resolveCurrentRunId = React.useCallback(() => {\n return runId ?? (\n typeof window !== 'undefined'\n ? resolvePathnameId(window.location.pathname)\n : undefined\n )\n }, [runId])\n\n const loadRun = React.useCallback(async () => {\n const currentRunId = resolveCurrentRunId()\n if (!currentRunId) {\n setError(t('data_sync.runs.detail.loadError'))\n setIsLoading(false)\n return\n }\n setIsNotFound(false)\n const call = await apiCall<SyncRunDetail>(\n `/api/data_sync/runs/${encodeURIComponent(currentRunId)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n if (call.status === 404) {\n setIsNotFound(true)\n } else {\n setError(t('data_sync.runs.detail.loadError'))\n }\n setIsLoading(false)\n return\n }\n setRun(call.result)\n setIsLoading(false)\n }, [resolveCurrentRunId, t])\n\n const loadLogs = React.useCallback(async () => {\n const currentRunId = resolveCurrentRunId()\n if (!currentRunId) return\n setIsLoadingLogs(true)\n const params = new URLSearchParams({ runId: currentRunId, pageSize: '50' })\n const call = await apiCall<{ items: LogEntry[] }>(\n `/api/integrations/logs?${params.toString()}`,\n undefined,\n { fallback: { items: [] } },\n )\n if (call.ok && call.result) {\n setLogs(call.result.items)\n }\n setIsLoadingLogs(false)\n }, [resolveCurrentRunId])\n\n React.useEffect(() => {\n void loadRun()\n void loadLogs()\n }, [loadRun, loadLogs])\n\n const handleProgressEvent = React.useCallback((payload: ProgressEventPayload) => {\n const eventJobId = typeof payload.jobId === 'string' ? payload.jobId : null\n if (!eventJobId) return\n\n setRun((current) => {\n if (!current?.progressJobId || current.progressJobId !== eventJobId) return current\n return {\n ...current,\n status: (payload.status as SyncRunDetail['status']) ?? current.status,\n progressJob: {\n id: eventJobId,\n status: payload.status ?? current.progressJob?.status ?? current.status,\n progressPercent: payload.progressPercent ?? current.progressJob?.progressPercent ?? 0,\n processedCount: payload.processedCount ?? current.progressJob?.processedCount ?? 0,\n totalCount: payload.totalCount ?? current.progressJob?.totalCount ?? null,\n etaSeconds: payload.etaSeconds ?? current.progressJob?.etaSeconds ?? null,\n meta: payload.meta ?? current.progressJob?.meta ?? null,\n },\n }\n })\n }, [])\n\n useAppEvent('progress.job.updated', (event) => {\n handleProgressEvent(event.payload as ProgressEventPayload)\n }, [handleProgressEvent])\n\n useAppEvent('progress.job.started', (event) => {\n handleProgressEvent(event.payload as ProgressEventPayload)\n }, [handleProgressEvent])\n\n useAppEvent('progress.job.completed', (event) => {\n handleProgressEvent(event.payload as ProgressEventPayload)\n void loadRun()\n void loadLogs()\n }, [handleProgressEvent, loadLogs, loadRun])\n\n useAppEvent('progress.job.failed', (event) => {\n handleProgressEvent(event.payload as ProgressEventPayload)\n void loadRun()\n void loadLogs()\n }, [handleProgressEvent, loadLogs, loadRun])\n\n useAppEvent('progress.job.cancelled', (event) => {\n handleProgressEvent(event.payload as ProgressEventPayload)\n void loadRun()\n void loadLogs()\n }, [handleProgressEvent, loadLogs, loadRun])\n\n useAppEvent('om:bridge:reconnected', () => {\n void loadRun()\n void loadLogs()\n }, [loadLogs, loadRun])\n\n const handleCancel = React.useCallback(async () => {\n const currentRunId = resolveCurrentRunId()\n if (!currentRunId) return\n const call = await runMutation({\n // optimistic-lock-exempt: run lifecycle action endpoint (cancel), not a concurrent record edit\n operation: () => apiCall(`/api/data_sync/runs/${encodeURIComponent(currentRunId)}/cancel`, {\n method: 'POST',\n }, { fallback: null }),\n mutationPayload: { runId: currentRunId },\n context: {\n operation: 'update',\n actionId: 'cancel-sync-run',\n runId: currentRunId,\n },\n })\n if (call.ok) {\n flash(t('data_sync.runs.detail.cancelSuccess'), 'success')\n void loadRun()\n } else {\n flash(t('data_sync.runs.detail.cancelError'), 'error')\n }\n }, [resolveCurrentRunId, runMutation, t, loadRun])\n\n const handleRetry = React.useCallback(async () => {\n const currentRunId = resolveCurrentRunId()\n if (!currentRunId) return\n const call = await runMutation({\n // optimistic-lock-exempt: starts a new retry run (create), not a concurrent record edit\n operation: () => apiCall<{ id: string }>(`/api/data_sync/runs/${encodeURIComponent(currentRunId)}/retry`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ fromBeginning: false }),\n }, { fallback: null }),\n mutationPayload: { runId: currentRunId, fromBeginning: false },\n context: {\n operation: 'create',\n actionId: 'retry-sync-run',\n runId: currentRunId,\n },\n })\n if (call.ok && call.result) {\n flash(t('data_sync.runs.detail.retrySuccess'), 'success')\n router.push(`/backend/data-sync/runs/${encodeURIComponent(call.result.id)}`)\n } else {\n flash(t('data_sync.runs.detail.retryError'), 'error')\n }\n }, [resolveCurrentRunId, router, runMutation, t])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('data_sync.runs.detail.title')} /></PageBody></Page>\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('data_sync.runs.detail.notFound', 'Sync run not found.')}\n backHref=\"/backend/data-sync\"\n backLabel={t('data_sync.runs.detail.back')}\n />\n </PageBody>\n </Page>\n )\n }\n if (error || !run) return <Page><PageBody><ErrorMessage label={error ?? t('data_sync.runs.detail.loadError')} /></PageBody></Page>\n\n const totalProcessed = run.createdCount + run.updatedCount + run.skippedCount + run.failedCount\n const progressPercent = run.progressJob?.progressPercent ?? (run.status === 'completed' ? 100 : 0)\n const progressStatus = run.progressJob?.status ?? run.status\n const processedCount = run.progressJob?.processedCount ?? totalProcessed\n const hasProgressTotal = typeof run.progressJob?.totalCount === 'number' && run.progressJob.totalCount > 0\n const etaLabel = run.progressJob?.etaSeconds && run.progressJob.etaSeconds > 0\n ? formatEtaSeconds(run.progressJob.etaSeconds)\n : null\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/data-sync\"\n backLabel={t('data_sync.runs.detail.back')}\n entityTypeLabel={t('data_sync.runs.detail.title')}\n title={`${run.integrationId} \u2014 ${run.entityType}`}\n statusBadge={(\n <div className=\"mt-2 flex flex-wrap gap-2\">\n <Badge variant=\"outline\">{t(`data_sync.dashboard.direction.${run.direction}`)}</Badge>\n <StatusBadge variant={getSyncRunStatusVariant(run.status)}>\n {t(`data_sync.dashboard.status.${run.status}`)}\n </StatusBadge>\n {run.triggeredBy ? <Badge variant=\"outline\">{run.triggeredBy}</Badge> : null}\n </div>\n )}\n actionsContent={(\n <>\n {(run.status === 'running' || run.status === 'pending') ? (\n <Button type=\"button\" variant=\"destructive\" size=\"sm\" onClick={() => void handleCancel()}>\n <XCircle className=\"mr-2 h-4 w-4\" />\n {t('data_sync.runs.detail.cancel')}\n </Button>\n ) : null}\n {run.status === 'failed' ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleRetry()}>\n <RotateCcw className=\"mr-2 h-4 w-4\" />\n {t('data_sync.runs.detail.retry')}\n </Button>\n ) : null}\n </>\n )}\n />\n\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between gap-3\">\n <CardTitle>{t('data_sync.runs.detail.progress')}</CardTitle>\n <StatusBadge variant={getSyncRunStatusVariant(progressStatus)}>\n {t(`data_sync.dashboard.status.${progressStatus}`)}\n </StatusBadge>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <span className=\"font-medium\">\n {hasProgressTotal\n ? t('data_sync.runs.detail.progress.percent', { percent: progressPercent })\n : t('data_sync.runs.detail.progress.itemsProcessed', { count: processedCount })}\n </span>\n {etaLabel ? (\n <span className=\"text-muted-foreground\">\n {t('data_sync.runs.detail.progress.eta', { eta: etaLabel })}\n </span>\n ) : null}\n </div>\n {hasProgressTotal ? (\n <Progress value={progressPercent} className=\"h-3\" />\n ) : (\n <div className=\"relative h-3 w-full overflow-hidden rounded-full bg-secondary\">\n <div className=\"absolute inset-y-0 left-0 w-1/2 animate-pulse rounded-full bg-primary/80\" />\n <div className=\"absolute inset-y-0 right-0 w-1/3 rounded-full bg-primary/10\" />\n </div>\n )}\n <div className=\"flex flex-wrap items-center justify-between gap-2 text-sm text-muted-foreground\">\n <span>\n {hasProgressTotal\n ? t('data_sync.runs.detail.progress.itemsProcessedTotal', {\n processed: processedCount,\n total: run.progressJob?.totalCount ?? 0,\n })\n : t('data_sync.runs.detail.progress.itemsProcessed', { count: processedCount })}\n </span>\n <span>{t('data_sync.runs.detail.progress.batches', { count: run.batchesCompleted })}</span>\n </div>\n </CardContent>\n </Card>\n\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-6 text-center\">\n <div className=\"text-2xl font-bold text-status-success-text\">{run.createdCount}</div>\n <p className=\"text-sm text-muted-foreground\">{t('data_sync.runs.detail.counters.created')}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-6 text-center\">\n <div className=\"text-2xl font-bold text-status-info-text\">{run.updatedCount}</div>\n <p className=\"text-sm text-muted-foreground\">{t('data_sync.runs.detail.counters.updated')}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-6 text-center\">\n <div className=\"text-2xl font-bold text-muted-foreground\">{run.skippedCount}</div>\n <p className=\"text-sm text-muted-foreground\">{t('data_sync.runs.detail.counters.skipped')}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-6 text-center\">\n <div className=\"text-2xl font-bold text-status-error-text\">{run.failedCount}</div>\n <p className=\"text-sm text-muted-foreground\">{t('data_sync.runs.detail.counters.failed')}</p>\n </CardContent>\n </Card>\n </div>\n\n {run.lastError && (\n <Card className=\"border-status-error-border bg-status-error-bg\">\n <CardHeader>\n <CardTitle className=\"text-status-error-text\">{t('data_sync.runs.detail.error')}</CardTitle>\n </CardHeader>\n <CardContent>\n <pre className=\"text-sm text-status-error-text whitespace-pre-wrap\">{run.lastError}</pre>\n </CardContent>\n </Card>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>{t('data_sync.runs.detail.logs')}</CardTitle>\n </CardHeader>\n <CardContent>\n {isLoadingLogs ? (\n <div className=\"flex justify-center py-4\"><Spinner /></div>\n ) : (\n <LogList\n entries={logs.map<LogListEntry>((log) => ({\n id: log.id,\n time: new Date(log.createdAt).toLocaleString(),\n level: log.level,\n message: log.message,\n body: log.payload ? (\n <pre className=\"overflow-x-auto whitespace-pre-wrap rounded-md border bg-card p-3 text-xs\">\n {JSON.stringify(log.payload, null, 2)}\n </pre>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {t('data_sync.runs.detail.logs.noPayload', 'No payload recorded for this log entry.')}\n </p>\n ),\n }))}\n emptyMessage={t('data_sync.runs.detail.noLogs')}\n />\n )}\n </CardContent>\n </Card>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["params"]
7
7
  }
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { apiCall, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
5
5
  import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
6
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
6
7
  import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
7
8
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
8
9
  import { Badge } from "@open-mercato/ui/primitives/badge";
@@ -81,6 +82,9 @@ function buildScheduleEditors(entityTypes, directions, records) {
81
82
  function IntegrationScheduleTab(props) {
82
83
  const t = useT();
83
84
  const scopeVersion = useOrganizationScopeVersion();
85
+ const { runMutation } = useGuardedMutation({
86
+ contextId: "data_sync.integrationTab"
87
+ });
84
88
  const [option, setOption] = React.useState(null);
85
89
  const [schedules, setSchedules] = React.useState({});
86
90
  const [isLoading, setIsLoading] = React.useState(true);
@@ -146,17 +150,32 @@ function IntegrationScheduleTab(props) {
146
150
  setRunningKey(scheduleKey);
147
151
  try {
148
152
  const scheduleState = schedules[scheduleKey] ?? buildDefaultScheduleState(entityType);
149
- const call = await apiCall("/api/data_sync/run", {
150
- method: "POST",
151
- headers: { "content-type": "application/json" },
152
- body: JSON.stringify({
153
+ const call = await runMutation({
154
+ // optimistic-lock-exempt: starts a new sync run (create), not a concurrent record edit
155
+ operation: () => apiCall("/api/data_sync/run", {
156
+ method: "POST",
157
+ headers: { "content-type": "application/json" },
158
+ body: JSON.stringify({
159
+ integrationId: props.integrationId,
160
+ entityType,
161
+ direction,
162
+ fullSync: scheduleState.fullSync,
163
+ batchSize: 100
164
+ })
165
+ }, { fallback: null }),
166
+ mutationPayload: {
153
167
  integrationId: props.integrationId,
154
168
  entityType,
155
169
  direction,
156
170
  fullSync: scheduleState.fullSync,
157
171
  batchSize: 100
158
- })
159
- }, { fallback: null });
172
+ },
173
+ context: {
174
+ operation: "create",
175
+ actionId: "start-sync-run",
176
+ integrationId: props.integrationId
177
+ }
178
+ });
160
179
  if (!call.ok) {
161
180
  throw new Error(call.result?.error ?? "Failed to start sync");
162
181
  }
@@ -167,28 +186,45 @@ function IntegrationScheduleTab(props) {
167
186
  } finally {
168
187
  setRunningKey(null);
169
188
  }
170
- }, [option?.canStartRun, props.hasCredentials, props.integrationId, props.isEnabled, schedules, t]);
189
+ }, [option?.canStartRun, props.hasCredentials, props.integrationId, props.isEnabled, runMutation, schedules, t]);
171
190
  const handleSaveSchedule = React.useCallback(async (entityType, direction, scheduleKey) => {
172
191
  const scheduleState = schedules[scheduleKey] ?? buildDefaultScheduleState(entityType);
173
192
  setSavingKey(scheduleKey);
174
193
  try {
175
- const call = await withScopedApiRequestHeaders(
176
- buildOptimisticLockHeader(scheduleState.updatedAt),
177
- () => apiCall("/api/data_sync/schedules", {
178
- method: "POST",
179
- headers: { "content-type": "application/json" },
180
- body: JSON.stringify({
181
- integrationId: props.integrationId,
182
- entityType,
183
- direction,
184
- scheduleType: scheduleState.scheduleType,
185
- scheduleValue: scheduleState.scheduleValue,
186
- timezone: scheduleState.timezone,
187
- fullSync: scheduleState.fullSync,
188
- isEnabled: scheduleState.isEnabled
189
- })
190
- }, { fallback: null })
191
- );
194
+ const call = await runMutation({
195
+ operation: () => withScopedApiRequestHeaders(
196
+ buildOptimisticLockHeader(scheduleState.updatedAt),
197
+ () => apiCall("/api/data_sync/schedules", {
198
+ method: "POST",
199
+ headers: { "content-type": "application/json" },
200
+ body: JSON.stringify({
201
+ integrationId: props.integrationId,
202
+ entityType,
203
+ direction,
204
+ scheduleType: scheduleState.scheduleType,
205
+ scheduleValue: scheduleState.scheduleValue,
206
+ timezone: scheduleState.timezone,
207
+ fullSync: scheduleState.fullSync,
208
+ isEnabled: scheduleState.isEnabled
209
+ })
210
+ }, { fallback: null })
211
+ ),
212
+ mutationPayload: {
213
+ integrationId: props.integrationId,
214
+ entityType,
215
+ direction,
216
+ scheduleType: scheduleState.scheduleType,
217
+ scheduleValue: scheduleState.scheduleValue,
218
+ timezone: scheduleState.timezone,
219
+ fullSync: scheduleState.fullSync,
220
+ isEnabled: scheduleState.isEnabled
221
+ },
222
+ context: {
223
+ operation: "update",
224
+ actionId: "save-sync-schedule",
225
+ integrationId: props.integrationId
226
+ }
227
+ });
192
228
  if (!call.ok || !call.result) {
193
229
  const conflictError = Object.assign(
194
230
  new Error(call.result?.error ?? "Failed to save schedule"),
@@ -219,7 +255,7 @@ function IntegrationScheduleTab(props) {
219
255
  } finally {
220
256
  setSavingKey(null);
221
257
  }
222
- }, [props.integrationId, schedules, t, updateScheduleEditor]);
258
+ }, [props.integrationId, runMutation, schedules, t, updateScheduleEditor]);
223
259
  const handleDeleteSchedule = React.useCallback(async (entityType, scheduleKey) => {
224
260
  const scheduleState = schedules[scheduleKey];
225
261
  if (!scheduleState?.id) {
@@ -228,12 +264,21 @@ function IntegrationScheduleTab(props) {
228
264
  }
229
265
  setDeletingKey(scheduleKey);
230
266
  try {
231
- const call = await withScopedApiRequestHeaders(
232
- buildOptimisticLockHeader(scheduleState.updatedAt),
233
- () => apiCall(`/api/data_sync/schedules/${encodeURIComponent(scheduleState.id)}`, {
234
- method: "DELETE"
235
- }, { fallback: null })
236
- );
267
+ const call = await runMutation({
268
+ operation: () => withScopedApiRequestHeaders(
269
+ buildOptimisticLockHeader(scheduleState.updatedAt),
270
+ () => apiCall(`/api/data_sync/schedules/${encodeURIComponent(scheduleState.id)}`, {
271
+ method: "DELETE"
272
+ }, { fallback: null })
273
+ ),
274
+ mutationPayload: {
275
+ scheduleId: scheduleState.id
276
+ },
277
+ context: {
278
+ operation: "delete",
279
+ actionId: "delete-sync-schedule"
280
+ }
281
+ });
237
282
  if (!call.ok) {
238
283
  throw new Error(call.result?.error ?? "Failed to delete schedule");
239
284
  }
@@ -248,7 +293,7 @@ function IntegrationScheduleTab(props) {
248
293
  } finally {
249
294
  setDeletingKey(null);
250
295
  }
251
- }, [schedules, t, updateScheduleEditor]);
296
+ }, [runMutation, schedules, t, updateScheduleEditor]);
252
297
  if (isLoading) {
253
298
  return /* @__PURE__ */ jsx("div", { className: "flex min-h-40 items-center justify-center rounded-lg border bg-card", children: /* @__PURE__ */ jsx(Spinner, {}) });
254
299
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/data_sync/components/IntegrationScheduleTab.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getSyncSummaryVariant } from '../lib/syncRunStatus'\nimport {\n CalendarClock,\n Play,\n RefreshCw,\n Save,\n ShieldAlert,\n ShieldCheck,\n Trash2,\n} from 'lucide-react'\n\ntype SyncOption = {\n integrationId: string\n title: string\n direction: 'import' | 'export' | 'bidirectional'\n runMode?: 'generic' | 'provider'\n canStartRun?: boolean\n supportedEntities: string[]\n hasCredentials: boolean\n isEnabled: boolean\n}\n\ntype SyncOptionsResponse = {\n items?: SyncOption[]\n}\n\ntype SyncScheduleRecord = {\n id: string\n integrationId: string\n entityType: string\n direction: 'import' | 'export'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n fullSync: boolean\n isEnabled: boolean\n lastRunAt: string | null\n updatedAt?: string | null\n}\n\ntype SyncSchedulesResponse = {\n items?: SyncScheduleRecord[]\n}\n\ntype SyncScheduleEditorState = {\n id?: string\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n fullSync: boolean\n isEnabled: boolean\n lastRunAt: string | null\n updatedAt?: string | null\n}\n\ntype IntegrationScheduleTabProps = {\n integrationId: string\n hasCredentials: boolean\n isEnabled: boolean\n}\n\nconst DEFAULT_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'\n\nfunction formatEntityTypeLabel(entityType: string): string {\n return entityType\n .replace(/[_-]+/g, ' ')\n .replace(/\\b\\w/g, (letter) => letter.toUpperCase())\n}\n\nfunction buildDefaultScheduleState(entityType: string): SyncScheduleEditorState {\n const normalized = entityType.trim().toLowerCase()\n const longerInterval = normalized === 'categories' || normalized === 'attributes'\n return {\n scheduleType: 'interval',\n scheduleValue: longerInterval ? '6h' : '1h',\n timezone: DEFAULT_TIMEZONE,\n fullSync: normalized !== 'products',\n isEnabled: true,\n lastRunAt: null,\n updatedAt: null,\n }\n}\n\nfunction getSupportedDirections(direction: SyncOption['direction'] | null | undefined): Array<'import' | 'export'> {\n if (direction === 'export') return ['export']\n if (direction === 'bidirectional') return ['import', 'export']\n return ['import']\n}\n\nfunction buildScheduleKey(entityType: string, direction: 'import' | 'export'): string {\n return `${entityType}:${direction}`\n}\n\nfunction buildScheduleEditors(\n entityTypes: string[],\n directions: Array<'import' | 'export'>,\n records: SyncScheduleRecord[],\n): Record<string, SyncScheduleEditorState> {\n const nextEntries: Array<[string, SyncScheduleEditorState]> = []\n for (const entityType of entityTypes) {\n for (const direction of directions) {\n const record = records.find((item) => item.entityType === entityType && item.direction === direction)\n nextEntries.push([\n buildScheduleKey(entityType, direction),\n record\n ? {\n id: record.id,\n scheduleType: record.scheduleType,\n scheduleValue: record.scheduleValue,\n timezone: record.timezone,\n fullSync: record.fullSync,\n isEnabled: record.isEnabled,\n lastRunAt: record.lastRunAt,\n updatedAt: record.updatedAt ?? null,\n }\n : buildDefaultScheduleState(entityType),\n ])\n }\n }\n return Object.fromEntries(nextEntries)\n}\n\nexport function IntegrationScheduleTab(props: IntegrationScheduleTabProps) {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const [option, setOption] = React.useState<SyncOption | null>(null)\n const [schedules, setSchedules] = React.useState<Record<string, SyncScheduleEditorState>>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [runningKey, setRunningKey] = React.useState<string | null>(null)\n const [savingKey, setSavingKey] = React.useState<string | null>(null)\n const [deletingKey, setDeletingKey] = React.useState<string | null>(null)\n\n const load = React.useCallback(async () => {\n setIsLoading(true)\n try {\n const [optionsCall, schedulesCall] = await Promise.all([\n apiCall<SyncOptionsResponse>('/api/data_sync/options', undefined, { fallback: { items: [] } }),\n apiCall<SyncSchedulesResponse>(`/api/data_sync/schedules?integrationId=${encodeURIComponent(props.integrationId)}&page=1&pageSize=100`, undefined, { fallback: { items: [] } }),\n ])\n\n const resolvedOption = (optionsCall.result?.items ?? []).find((item) => item.integrationId === props.integrationId) ?? null\n setOption(resolvedOption)\n\n const supportedEntities = resolvedOption?.supportedEntities ?? []\n const supportedDirections = getSupportedDirections(resolvedOption?.direction)\n setSchedules(buildScheduleEditors(supportedEntities, supportedDirections, schedulesCall.result?.items ?? []))\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.integrationTab.loadError', 'Failed to load sync schedules.')\n flash(message, 'error')\n } finally {\n setIsLoading(false)\n }\n }, [props.integrationId, t])\n\n React.useEffect(() => {\n void load()\n }, [load, scopeVersion])\n\n const supportedDirections = React.useMemo(\n () => getSupportedDirections(option?.direction),\n [option?.direction],\n )\n\n const rows = React.useMemo(\n () => (option?.supportedEntities ?? []).flatMap((entityType) => (\n supportedDirections.map((direction) => ({\n entityType,\n direction,\n key: buildScheduleKey(entityType, direction),\n }))\n )),\n [option?.supportedEntities, supportedDirections],\n )\n\n const updateScheduleEditor = React.useCallback((key: string, patch: Partial<SyncScheduleEditorState>, entityType: string) => {\n setSchedules((current) => ({\n ...current,\n [key]: {\n ...(current[key] ?? buildDefaultScheduleState(entityType)),\n ...patch,\n },\n }))\n }, [])\n\n const handleStartSync = React.useCallback(async (entityType: string, direction: 'import' | 'export', scheduleKey: string) => {\n if (!props.isEnabled) {\n flash(t('data_sync.integrationTab.integrationDisabled', 'Enable the integration before starting a sync.'), 'error')\n return\n }\n if (!props.hasCredentials) {\n flash(t('data_sync.integrationTab.credentialsMissing', 'Configure credentials before starting a sync.'), 'error')\n return\n }\n if (option?.canStartRun === false) {\n flash(t('data_sync.integrationTab.providerManaged', 'Start this integration from its provider-specific setup flow.'), 'error')\n return\n }\n\n setRunningKey(scheduleKey)\n try {\n const scheduleState = schedules[scheduleKey] ?? buildDefaultScheduleState(entityType)\n // optimistic-lock-exempt: starts a new sync run (create), not a concurrent record edit\n const call = await apiCall<{ id: string }>('/api/data_sync/run', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n integrationId: props.integrationId,\n entityType,\n direction,\n fullSync: scheduleState.fullSync,\n batchSize: 100,\n }),\n }, { fallback: null })\n\n if (!call.ok) {\n throw new Error((call.result as { error?: string } | null)?.error ?? 'Failed to start sync')\n }\n\n flash(t('data_sync.integrationTab.runStarted', 'Sync run started.'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.integrationTab.runError', 'Failed to start sync.')\n flash(message, 'error')\n } finally {\n setRunningKey(null)\n }\n }, [option?.canStartRun, props.hasCredentials, props.integrationId, props.isEnabled, schedules, t])\n\n const handleSaveSchedule = React.useCallback(async (entityType: string, direction: 'import' | 'export', scheduleKey: string) => {\n const scheduleState = schedules[scheduleKey] ?? buildDefaultScheduleState(entityType)\n setSavingKey(scheduleKey)\n try {\n // Keyed upsert (POST). When the server resolves an existing row the save\n // version-checks against this schedule's loaded `updatedAt`; for a brand\n // new row the header is empty so the create path is unaffected.\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(scheduleState.updatedAt),\n () => apiCall<SyncScheduleRecord>('/api/data_sync/schedules', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n integrationId: props.integrationId,\n entityType,\n direction,\n scheduleType: scheduleState.scheduleType,\n scheduleValue: scheduleState.scheduleValue,\n timezone: scheduleState.timezone,\n fullSync: scheduleState.fullSync,\n isEnabled: scheduleState.isEnabled,\n }),\n }, { fallback: null }),\n )\n\n if (!call.ok || !call.result) {\n const conflictError = Object.assign(\n new Error((call.result as { error?: string } | null)?.error ?? 'Failed to save schedule'),\n {\n status: call.status,\n ...(call.result && typeof call.result === 'object' ? call.result : {}),\n },\n )\n if (surfaceRecordConflict(conflictError, t)) {\n return\n }\n throw conflictError\n }\n\n updateScheduleEditor(scheduleKey, {\n id: call.result.id,\n scheduleType: call.result.scheduleType,\n scheduleValue: call.result.scheduleValue,\n timezone: call.result.timezone,\n fullSync: call.result.fullSync,\n isEnabled: call.result.isEnabled,\n lastRunAt: call.result.lastRunAt,\n updatedAt: call.result.updatedAt ?? null,\n }, entityType)\n flash(t('data_sync.dashboard.schedule.success', 'Recurring schedule saved'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.dashboard.schedule.error', 'Failed to save recurring schedule')\n flash(message, 'error')\n } finally {\n setSavingKey(null)\n }\n }, [props.integrationId, schedules, t, updateScheduleEditor])\n\n const handleDeleteSchedule = React.useCallback(async (entityType: string, scheduleKey: string) => {\n const scheduleState = schedules[scheduleKey]\n if (!scheduleState?.id) {\n updateScheduleEditor(scheduleKey, buildDefaultScheduleState(entityType), entityType)\n return\n }\n\n setDeletingKey(scheduleKey)\n try {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(scheduleState.updatedAt),\n () => apiCall(`/api/data_sync/schedules/${encodeURIComponent(scheduleState.id as string)}`, {\n method: 'DELETE',\n }, { fallback: null }),\n )\n\n if (!call.ok) {\n throw new Error((call.result as { error?: string } | null)?.error ?? 'Failed to delete schedule')\n }\n\n setSchedules((current) => ({\n ...current,\n [scheduleKey]: buildDefaultScheduleState(entityType),\n }))\n flash(t('data_sync.dashboard.schedule.deleteSuccess', 'Recurring schedule removed'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.dashboard.schedule.deleteError', 'Failed to remove recurring schedule')\n flash(message, 'error')\n } finally {\n setDeletingKey(null)\n }\n }, [schedules, t, updateScheduleEditor])\n\n if (isLoading) {\n return (\n <div className=\"flex min-h-40 items-center justify-center rounded-lg border bg-card\">\n <Spinner />\n </div>\n )\n }\n\n if (!option) {\n return (\n <Alert variant=\"warning\">\n <AlertDescription>\n {t('data_sync.integrationTab.notAvailable', 'This integration is not registered as a data sync provider.')}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <section className=\"space-y-4 rounded-lg border bg-card p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant={getSyncSummaryVariant(props.isEnabled ? 'enabled' : 'disabled')}>\n {props.isEnabled ? <ShieldCheck className=\"mr-2 h-3.5 w-3.5\" /> : <ShieldAlert className=\"mr-2 h-3.5 w-3.5\" />}\n {props.isEnabled\n ? t('data_sync.dashboard.start.status.enabled', 'Integration enabled')\n : t('data_sync.dashboard.start.status.disabled', 'Integration disabled')}\n </Badge>\n <Badge variant={getSyncSummaryVariant(props.hasCredentials ? 'ready' : 'missing')}>\n <CalendarClock className=\"mr-2 h-3.5 w-3.5\" />\n {props.hasCredentials\n ? t('data_sync.dashboard.start.status.credentialsReady', 'Credentials ready')\n : t('data_sync.dashboard.start.status.credentialsMissing', 'Credentials missing')}\n </Badge>\n </div>\n <div>\n <h3 className=\"text-base font-semibold\">{t('data_sync.integrationTab.title', 'Sync schedules')}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {t('data_sync.integrationTab.description', 'Run one-off syncs or save recurring schedules for every supported entity directly from the integration detail page.')}\n </p>\n </div>\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={() => void load()} disabled={isLoading}>\n <RefreshCw className=\"mr-2 h-4 w-4\" />\n {t('data_sync.integrationTab.refresh', 'Refresh')}\n </Button>\n </div>\n\n {!props.isEnabled ? (\n <Alert variant=\"warning\">\n <AlertDescription>\n {t('data_sync.integrationTab.integrationDisabledNotice', 'The integration is disabled. You can save schedules now, but runs will stay blocked until the integration is enabled.')}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {!props.hasCredentials ? (\n <Alert variant=\"warning\">\n <AlertDescription>\n {t('data_sync.integrationTab.credentialsMissingNotice', 'Credentials are still missing. Save schedules first if you want, but manual and scheduled runs will fail until credentials are configured.')}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {option.canStartRun === false ? (\n <Alert variant=\"info\">\n <AlertDescription>\n {t('data_sync.integrationTab.providerManagedNotice', 'This provider needs its own setup flow before a run can start. Use the provider tab on this page instead of generic schedules.')}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {rows.length === 0 ? (\n <Alert variant=\"info\">\n <AlertDescription>\n {t('data_sync.integrationTab.empty', 'This provider does not expose any schedulable sync entities yet.')}\n </AlertDescription>\n </Alert>\n ) : (\n <div className=\"overflow-x-auto rounded-lg border\">\n <table className=\"w-full min-w-[1080px] text-sm\">\n <thead className=\"bg-muted/50 text-left\">\n <tr>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.columns.entityType', 'Entity Type')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.columns.direction', 'Direction')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.schedule.type', 'Schedule type')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.integrationTab.columns.value', 'Value')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.schedule.timezone', 'Timezone')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.start.fullSync', 'Run as full sync')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.schedule.enabled', 'Schedule enabled')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.integrationTab.columns.lastRun', 'Last run')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.integrationTab.columns.actions', 'Actions')}</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => {\n const scheduleState = schedules[row.key] ?? buildDefaultScheduleState(row.entityType)\n const isRunning = runningKey === row.key\n const isSaving = savingKey === row.key\n const isDeleting = deletingKey === row.key\n const controlsDisabled = isRunning || isSaving || isDeleting\n return (\n <tr key={row.key} className=\"border-t align-top\">\n <td className=\"px-3 py-3 font-medium\">{formatEntityTypeLabel(row.entityType)}</td>\n <td className=\"px-3 py-3\">{t(`data_sync.dashboard.direction.${row.direction}`, row.direction === 'import' ? 'Import' : 'Export')}</td>\n <td className=\"px-3 py-3\">\n <Select\n value={scheduleState.scheduleType}\n onValueChange={(value) => updateScheduleEditor(row.key, {\n scheduleType: value === 'cron' ? 'cron' : 'interval',\n }, row.entityType)}\n disabled={controlsDisabled}\n >\n <SelectTrigger size=\"lg\" className=\"min-w-32\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"interval\">{t('data_sync.dashboard.schedule.interval', 'Interval')}</SelectItem>\n <SelectItem value=\"cron\">{t('data_sync.dashboard.schedule.cron', 'Cron')}</SelectItem>\n </SelectContent>\n </Select>\n </td>\n <td className=\"px-3 py-3\">\n <Input\n value={scheduleState.scheduleValue}\n onChange={(event) => updateScheduleEditor(row.key, { scheduleValue: event.target.value }, row.entityType)}\n disabled={controlsDisabled}\n placeholder={scheduleState.scheduleType === 'cron' ? '0 * * * *' : '1h'}\n />\n </td>\n <td className=\"px-3 py-3\">\n <Input\n value={scheduleState.timezone}\n onChange={(event) => updateScheduleEditor(row.key, { timezone: event.target.value }, row.entityType)}\n disabled={controlsDisabled}\n />\n </td>\n <td className=\"px-3 py-3\">\n <label className=\"flex min-h-10 items-center gap-2 text-sm\">\n <Switch\n checked={scheduleState.fullSync}\n onCheckedChange={(checked) => updateScheduleEditor(row.key, { fullSync: checked }, row.entityType)}\n disabled={controlsDisabled}\n aria-label={t('data_sync.dashboard.start.fullSync', 'Run as full sync')}\n />\n <span>{t('data_sync.integrationTab.fullSyncShort', 'Full')}</span>\n </label>\n </td>\n <td className=\"px-3 py-3\">\n <label className=\"flex min-h-10 items-center gap-2 text-sm\">\n <Switch\n checked={scheduleState.isEnabled}\n onCheckedChange={(checked) => updateScheduleEditor(row.key, { isEnabled: checked }, row.entityType)}\n disabled={controlsDisabled}\n aria-label={t('data_sync.dashboard.schedule.enabled', 'Schedule enabled')}\n />\n <span>{scheduleState.isEnabled ? t('data_sync.dashboard.schedule.status.shortEnabled', 'Scheduled') : t('data_sync.dashboard.schedule.status.shortDisabled', 'Paused')}</span>\n </label>\n </td>\n <td className=\"px-3 py-3 text-muted-foreground\">\n {scheduleState.lastRunAt\n ? new Date(scheduleState.lastRunAt).toLocaleString()\n : scheduleState.id\n ? t('data_sync.dashboard.schedule.neverRun', 'Saved, but no scheduled execution has completed yet.')\n : t('data_sync.dashboard.schedule.none', 'No recurring schedule saved for this target yet.')}\n </td>\n <td className=\"px-3 py-3\">\n <div className=\"flex flex-wrap gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => void handleStartSync(row.entityType, row.direction, row.key)}\n disabled={controlsDisabled || !props.isEnabled || !props.hasCredentials || option.canStartRun === false}\n >\n {isRunning ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Play className=\"mr-2 h-4 w-4\" />}\n {isRunning\n ? t('data_sync.integrationTab.starting', 'Starting...')\n : t('data_sync.integrationTab.start', 'Run now')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void handleSaveSchedule(row.entityType, row.direction, row.key)}\n disabled={controlsDisabled || option.canStartRun === false}\n >\n {isSaving ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Save className=\"mr-2 h-4 w-4\" />}\n {isSaving\n ? t('data_sync.dashboard.schedule.saving', 'Saving...')\n : t('data_sync.dashboard.schedule.save', 'Save recurring schedule')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void handleDeleteSchedule(row.entityType, row.key)}\n disabled={controlsDisabled || !scheduleState.id}\n >\n {isDeleting ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Trash2 className=\"mr-2 h-4 w-4\" />}\n {isDeleting\n ? t('data_sync.dashboard.schedule.deleting', 'Removing...')\n : t('data_sync.dashboard.schedule.delete', 'Remove schedule')}\n </Button>\n </div>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n )}\n\n <div className=\"grid gap-3 rounded-lg border border-dashed p-4 text-sm text-muted-foreground md:grid-cols-3\">\n <div className=\"space-y-1\">\n <Label>{t('data_sync.dashboard.schedule.interval', 'Interval')}</Label>\n <p>{t('data_sync.dashboard.schedule.intervalHelp', 'Example: `1h`, `6h`, or `24h` for repeating intervals.')}</p>\n </div>\n <div className=\"space-y-1\">\n <Label>{t('data_sync.dashboard.schedule.cron', 'Cron')}</Label>\n <p>{t('data_sync.dashboard.schedule.cronHelp', 'Example: `0 * * * *` runs at the start of every hour.')}</p>\n </div>\n <div className=\"space-y-1\">\n <Label>{t('data_sync.integrationTab.runNowTitle', 'Manual runs')}</Label>\n <p>{t('data_sync.integrationTab.runNowHelp', 'Run now uses the full-sync checkbox from the same row and starts progress tracking immediately in Data Sync.')}</p>\n </div>\n </div>\n </section>\n )\n}\n\nexport default IntegrationScheduleTab\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getSyncSummaryVariant } from '../lib/syncRunStatus'\nimport {\n CalendarClock,\n Play,\n RefreshCw,\n Save,\n ShieldAlert,\n ShieldCheck,\n Trash2,\n} from 'lucide-react'\n\ntype SyncOption = {\n integrationId: string\n title: string\n direction: 'import' | 'export' | 'bidirectional'\n runMode?: 'generic' | 'provider'\n canStartRun?: boolean\n supportedEntities: string[]\n hasCredentials: boolean\n isEnabled: boolean\n}\n\ntype SyncOptionsResponse = {\n items?: SyncOption[]\n}\n\ntype SyncScheduleRecord = {\n id: string\n integrationId: string\n entityType: string\n direction: 'import' | 'export'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n fullSync: boolean\n isEnabled: boolean\n lastRunAt: string | null\n updatedAt?: string | null\n}\n\ntype SyncSchedulesResponse = {\n items?: SyncScheduleRecord[]\n}\n\ntype SyncScheduleEditorState = {\n id?: string\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n fullSync: boolean\n isEnabled: boolean\n lastRunAt: string | null\n updatedAt?: string | null\n}\n\ntype IntegrationScheduleTabProps = {\n integrationId: string\n hasCredentials: boolean\n isEnabled: boolean\n}\n\nconst DEFAULT_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'\n\nfunction formatEntityTypeLabel(entityType: string): string {\n return entityType\n .replace(/[_-]+/g, ' ')\n .replace(/\\b\\w/g, (letter) => letter.toUpperCase())\n}\n\nfunction buildDefaultScheduleState(entityType: string): SyncScheduleEditorState {\n const normalized = entityType.trim().toLowerCase()\n const longerInterval = normalized === 'categories' || normalized === 'attributes'\n return {\n scheduleType: 'interval',\n scheduleValue: longerInterval ? '6h' : '1h',\n timezone: DEFAULT_TIMEZONE,\n fullSync: normalized !== 'products',\n isEnabled: true,\n lastRunAt: null,\n updatedAt: null,\n }\n}\n\nfunction getSupportedDirections(direction: SyncOption['direction'] | null | undefined): Array<'import' | 'export'> {\n if (direction === 'export') return ['export']\n if (direction === 'bidirectional') return ['import', 'export']\n return ['import']\n}\n\nfunction buildScheduleKey(entityType: string, direction: 'import' | 'export'): string {\n return `${entityType}:${direction}`\n}\n\nfunction buildScheduleEditors(\n entityTypes: string[],\n directions: Array<'import' | 'export'>,\n records: SyncScheduleRecord[],\n): Record<string, SyncScheduleEditorState> {\n const nextEntries: Array<[string, SyncScheduleEditorState]> = []\n for (const entityType of entityTypes) {\n for (const direction of directions) {\n const record = records.find((item) => item.entityType === entityType && item.direction === direction)\n nextEntries.push([\n buildScheduleKey(entityType, direction),\n record\n ? {\n id: record.id,\n scheduleType: record.scheduleType,\n scheduleValue: record.scheduleValue,\n timezone: record.timezone,\n fullSync: record.fullSync,\n isEnabled: record.isEnabled,\n lastRunAt: record.lastRunAt,\n updatedAt: record.updatedAt ?? null,\n }\n : buildDefaultScheduleState(entityType),\n ])\n }\n }\n return Object.fromEntries(nextEntries)\n}\n\nexport function IntegrationScheduleTab(props: IntegrationScheduleTabProps) {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { runMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'data_sync.integrationTab',\n })\n const [option, setOption] = React.useState<SyncOption | null>(null)\n const [schedules, setSchedules] = React.useState<Record<string, SyncScheduleEditorState>>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [runningKey, setRunningKey] = React.useState<string | null>(null)\n const [savingKey, setSavingKey] = React.useState<string | null>(null)\n const [deletingKey, setDeletingKey] = React.useState<string | null>(null)\n\n const load = React.useCallback(async () => {\n setIsLoading(true)\n try {\n const [optionsCall, schedulesCall] = await Promise.all([\n apiCall<SyncOptionsResponse>('/api/data_sync/options', undefined, { fallback: { items: [] } }),\n apiCall<SyncSchedulesResponse>(`/api/data_sync/schedules?integrationId=${encodeURIComponent(props.integrationId)}&page=1&pageSize=100`, undefined, { fallback: { items: [] } }),\n ])\n\n const resolvedOption = (optionsCall.result?.items ?? []).find((item) => item.integrationId === props.integrationId) ?? null\n setOption(resolvedOption)\n\n const supportedEntities = resolvedOption?.supportedEntities ?? []\n const supportedDirections = getSupportedDirections(resolvedOption?.direction)\n setSchedules(buildScheduleEditors(supportedEntities, supportedDirections, schedulesCall.result?.items ?? []))\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.integrationTab.loadError', 'Failed to load sync schedules.')\n flash(message, 'error')\n } finally {\n setIsLoading(false)\n }\n }, [props.integrationId, t])\n\n React.useEffect(() => {\n void load()\n }, [load, scopeVersion])\n\n const supportedDirections = React.useMemo(\n () => getSupportedDirections(option?.direction),\n [option?.direction],\n )\n\n const rows = React.useMemo(\n () => (option?.supportedEntities ?? []).flatMap((entityType) => (\n supportedDirections.map((direction) => ({\n entityType,\n direction,\n key: buildScheduleKey(entityType, direction),\n }))\n )),\n [option?.supportedEntities, supportedDirections],\n )\n\n const updateScheduleEditor = React.useCallback((key: string, patch: Partial<SyncScheduleEditorState>, entityType: string) => {\n setSchedules((current) => ({\n ...current,\n [key]: {\n ...(current[key] ?? buildDefaultScheduleState(entityType)),\n ...patch,\n },\n }))\n }, [])\n\n const handleStartSync = React.useCallback(async (entityType: string, direction: 'import' | 'export', scheduleKey: string) => {\n if (!props.isEnabled) {\n flash(t('data_sync.integrationTab.integrationDisabled', 'Enable the integration before starting a sync.'), 'error')\n return\n }\n if (!props.hasCredentials) {\n flash(t('data_sync.integrationTab.credentialsMissing', 'Configure credentials before starting a sync.'), 'error')\n return\n }\n if (option?.canStartRun === false) {\n flash(t('data_sync.integrationTab.providerManaged', 'Start this integration from its provider-specific setup flow.'), 'error')\n return\n }\n\n setRunningKey(scheduleKey)\n try {\n const scheduleState = schedules[scheduleKey] ?? buildDefaultScheduleState(entityType)\n const call = await runMutation({\n // optimistic-lock-exempt: starts a new sync run (create), not a concurrent record edit\n operation: () => apiCall<{ id: string }>('/api/data_sync/run', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n integrationId: props.integrationId,\n entityType,\n direction,\n fullSync: scheduleState.fullSync,\n batchSize: 100,\n }),\n }, { fallback: null }),\n mutationPayload: {\n integrationId: props.integrationId,\n entityType,\n direction,\n fullSync: scheduleState.fullSync,\n batchSize: 100,\n },\n context: {\n operation: 'create',\n actionId: 'start-sync-run',\n integrationId: props.integrationId,\n },\n })\n\n if (!call.ok) {\n throw new Error((call.result as { error?: string } | null)?.error ?? 'Failed to start sync')\n }\n\n flash(t('data_sync.integrationTab.runStarted', 'Sync run started.'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.integrationTab.runError', 'Failed to start sync.')\n flash(message, 'error')\n } finally {\n setRunningKey(null)\n }\n }, [option?.canStartRun, props.hasCredentials, props.integrationId, props.isEnabled, runMutation, schedules, t])\n\n const handleSaveSchedule = React.useCallback(async (entityType: string, direction: 'import' | 'export', scheduleKey: string) => {\n const scheduleState = schedules[scheduleKey] ?? buildDefaultScheduleState(entityType)\n setSavingKey(scheduleKey)\n try {\n // Keyed upsert (POST). When the server resolves an existing row the save\n // version-checks against this schedule's loaded `updatedAt`; for a brand\n // new row the header is empty so the create path is unaffected.\n const call = await runMutation({\n operation: () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(scheduleState.updatedAt),\n () => apiCall<SyncScheduleRecord>('/api/data_sync/schedules', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n integrationId: props.integrationId,\n entityType,\n direction,\n scheduleType: scheduleState.scheduleType,\n scheduleValue: scheduleState.scheduleValue,\n timezone: scheduleState.timezone,\n fullSync: scheduleState.fullSync,\n isEnabled: scheduleState.isEnabled,\n }),\n }, { fallback: null }),\n ),\n mutationPayload: {\n integrationId: props.integrationId,\n entityType,\n direction,\n scheduleType: scheduleState.scheduleType,\n scheduleValue: scheduleState.scheduleValue,\n timezone: scheduleState.timezone,\n fullSync: scheduleState.fullSync,\n isEnabled: scheduleState.isEnabled,\n },\n context: {\n operation: 'update',\n actionId: 'save-sync-schedule',\n integrationId: props.integrationId,\n },\n })\n\n if (!call.ok || !call.result) {\n const conflictError = Object.assign(\n new Error((call.result as { error?: string } | null)?.error ?? 'Failed to save schedule'),\n {\n status: call.status,\n ...(call.result && typeof call.result === 'object' ? call.result : {}),\n },\n )\n if (surfaceRecordConflict(conflictError, t)) {\n return\n }\n throw conflictError\n }\n\n updateScheduleEditor(scheduleKey, {\n id: call.result.id,\n scheduleType: call.result.scheduleType,\n scheduleValue: call.result.scheduleValue,\n timezone: call.result.timezone,\n fullSync: call.result.fullSync,\n isEnabled: call.result.isEnabled,\n lastRunAt: call.result.lastRunAt,\n updatedAt: call.result.updatedAt ?? null,\n }, entityType)\n flash(t('data_sync.dashboard.schedule.success', 'Recurring schedule saved'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.dashboard.schedule.error', 'Failed to save recurring schedule')\n flash(message, 'error')\n } finally {\n setSavingKey(null)\n }\n }, [props.integrationId, runMutation, schedules, t, updateScheduleEditor])\n\n const handleDeleteSchedule = React.useCallback(async (entityType: string, scheduleKey: string) => {\n const scheduleState = schedules[scheduleKey]\n if (!scheduleState?.id) {\n updateScheduleEditor(scheduleKey, buildDefaultScheduleState(entityType), entityType)\n return\n }\n\n setDeletingKey(scheduleKey)\n try {\n const call = await runMutation({\n operation: () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(scheduleState.updatedAt),\n () => apiCall(`/api/data_sync/schedules/${encodeURIComponent(scheduleState.id as string)}`, {\n method: 'DELETE',\n }, { fallback: null }),\n ),\n mutationPayload: {\n scheduleId: scheduleState.id,\n },\n context: {\n operation: 'delete',\n actionId: 'delete-sync-schedule',\n },\n })\n\n if (!call.ok) {\n throw new Error((call.result as { error?: string } | null)?.error ?? 'Failed to delete schedule')\n }\n\n setSchedules((current) => ({\n ...current,\n [scheduleKey]: buildDefaultScheduleState(entityType),\n }))\n flash(t('data_sync.dashboard.schedule.deleteSuccess', 'Recurring schedule removed'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.dashboard.schedule.deleteError', 'Failed to remove recurring schedule')\n flash(message, 'error')\n } finally {\n setDeletingKey(null)\n }\n }, [runMutation, schedules, t, updateScheduleEditor])\n\n if (isLoading) {\n return (\n <div className=\"flex min-h-40 items-center justify-center rounded-lg border bg-card\">\n <Spinner />\n </div>\n )\n }\n\n if (!option) {\n return (\n <Alert variant=\"warning\">\n <AlertDescription>\n {t('data_sync.integrationTab.notAvailable', 'This integration is not registered as a data sync provider.')}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <section className=\"space-y-4 rounded-lg border bg-card p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant={getSyncSummaryVariant(props.isEnabled ? 'enabled' : 'disabled')}>\n {props.isEnabled ? <ShieldCheck className=\"mr-2 h-3.5 w-3.5\" /> : <ShieldAlert className=\"mr-2 h-3.5 w-3.5\" />}\n {props.isEnabled\n ? t('data_sync.dashboard.start.status.enabled', 'Integration enabled')\n : t('data_sync.dashboard.start.status.disabled', 'Integration disabled')}\n </Badge>\n <Badge variant={getSyncSummaryVariant(props.hasCredentials ? 'ready' : 'missing')}>\n <CalendarClock className=\"mr-2 h-3.5 w-3.5\" />\n {props.hasCredentials\n ? t('data_sync.dashboard.start.status.credentialsReady', 'Credentials ready')\n : t('data_sync.dashboard.start.status.credentialsMissing', 'Credentials missing')}\n </Badge>\n </div>\n <div>\n <h3 className=\"text-base font-semibold\">{t('data_sync.integrationTab.title', 'Sync schedules')}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {t('data_sync.integrationTab.description', 'Run one-off syncs or save recurring schedules for every supported entity directly from the integration detail page.')}\n </p>\n </div>\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={() => void load()} disabled={isLoading}>\n <RefreshCw className=\"mr-2 h-4 w-4\" />\n {t('data_sync.integrationTab.refresh', 'Refresh')}\n </Button>\n </div>\n\n {!props.isEnabled ? (\n <Alert variant=\"warning\">\n <AlertDescription>\n {t('data_sync.integrationTab.integrationDisabledNotice', 'The integration is disabled. You can save schedules now, but runs will stay blocked until the integration is enabled.')}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {!props.hasCredentials ? (\n <Alert variant=\"warning\">\n <AlertDescription>\n {t('data_sync.integrationTab.credentialsMissingNotice', 'Credentials are still missing. Save schedules first if you want, but manual and scheduled runs will fail until credentials are configured.')}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {option.canStartRun === false ? (\n <Alert variant=\"info\">\n <AlertDescription>\n {t('data_sync.integrationTab.providerManagedNotice', 'This provider needs its own setup flow before a run can start. Use the provider tab on this page instead of generic schedules.')}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {rows.length === 0 ? (\n <Alert variant=\"info\">\n <AlertDescription>\n {t('data_sync.integrationTab.empty', 'This provider does not expose any schedulable sync entities yet.')}\n </AlertDescription>\n </Alert>\n ) : (\n <div className=\"overflow-x-auto rounded-lg border\">\n <table className=\"w-full min-w-[1080px] text-sm\">\n <thead className=\"bg-muted/50 text-left\">\n <tr>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.columns.entityType', 'Entity Type')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.columns.direction', 'Direction')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.schedule.type', 'Schedule type')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.integrationTab.columns.value', 'Value')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.schedule.timezone', 'Timezone')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.start.fullSync', 'Run as full sync')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.schedule.enabled', 'Schedule enabled')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.integrationTab.columns.lastRun', 'Last run')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.integrationTab.columns.actions', 'Actions')}</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => {\n const scheduleState = schedules[row.key] ?? buildDefaultScheduleState(row.entityType)\n const isRunning = runningKey === row.key\n const isSaving = savingKey === row.key\n const isDeleting = deletingKey === row.key\n const controlsDisabled = isRunning || isSaving || isDeleting\n return (\n <tr key={row.key} className=\"border-t align-top\">\n <td className=\"px-3 py-3 font-medium\">{formatEntityTypeLabel(row.entityType)}</td>\n <td className=\"px-3 py-3\">{t(`data_sync.dashboard.direction.${row.direction}`, row.direction === 'import' ? 'Import' : 'Export')}</td>\n <td className=\"px-3 py-3\">\n <Select\n value={scheduleState.scheduleType}\n onValueChange={(value) => updateScheduleEditor(row.key, {\n scheduleType: value === 'cron' ? 'cron' : 'interval',\n }, row.entityType)}\n disabled={controlsDisabled}\n >\n <SelectTrigger size=\"lg\" className=\"min-w-32\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"interval\">{t('data_sync.dashboard.schedule.interval', 'Interval')}</SelectItem>\n <SelectItem value=\"cron\">{t('data_sync.dashboard.schedule.cron', 'Cron')}</SelectItem>\n </SelectContent>\n </Select>\n </td>\n <td className=\"px-3 py-3\">\n <Input\n value={scheduleState.scheduleValue}\n onChange={(event) => updateScheduleEditor(row.key, { scheduleValue: event.target.value }, row.entityType)}\n disabled={controlsDisabled}\n placeholder={scheduleState.scheduleType === 'cron' ? '0 * * * *' : '1h'}\n />\n </td>\n <td className=\"px-3 py-3\">\n <Input\n value={scheduleState.timezone}\n onChange={(event) => updateScheduleEditor(row.key, { timezone: event.target.value }, row.entityType)}\n disabled={controlsDisabled}\n />\n </td>\n <td className=\"px-3 py-3\">\n <label className=\"flex min-h-10 items-center gap-2 text-sm\">\n <Switch\n checked={scheduleState.fullSync}\n onCheckedChange={(checked) => updateScheduleEditor(row.key, { fullSync: checked }, row.entityType)}\n disabled={controlsDisabled}\n aria-label={t('data_sync.dashboard.start.fullSync', 'Run as full sync')}\n />\n <span>{t('data_sync.integrationTab.fullSyncShort', 'Full')}</span>\n </label>\n </td>\n <td className=\"px-3 py-3\">\n <label className=\"flex min-h-10 items-center gap-2 text-sm\">\n <Switch\n checked={scheduleState.isEnabled}\n onCheckedChange={(checked) => updateScheduleEditor(row.key, { isEnabled: checked }, row.entityType)}\n disabled={controlsDisabled}\n aria-label={t('data_sync.dashboard.schedule.enabled', 'Schedule enabled')}\n />\n <span>{scheduleState.isEnabled ? t('data_sync.dashboard.schedule.status.shortEnabled', 'Scheduled') : t('data_sync.dashboard.schedule.status.shortDisabled', 'Paused')}</span>\n </label>\n </td>\n <td className=\"px-3 py-3 text-muted-foreground\">\n {scheduleState.lastRunAt\n ? new Date(scheduleState.lastRunAt).toLocaleString()\n : scheduleState.id\n ? t('data_sync.dashboard.schedule.neverRun', 'Saved, but no scheduled execution has completed yet.')\n : t('data_sync.dashboard.schedule.none', 'No recurring schedule saved for this target yet.')}\n </td>\n <td className=\"px-3 py-3\">\n <div className=\"flex flex-wrap gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => void handleStartSync(row.entityType, row.direction, row.key)}\n disabled={controlsDisabled || !props.isEnabled || !props.hasCredentials || option.canStartRun === false}\n >\n {isRunning ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Play className=\"mr-2 h-4 w-4\" />}\n {isRunning\n ? t('data_sync.integrationTab.starting', 'Starting...')\n : t('data_sync.integrationTab.start', 'Run now')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void handleSaveSchedule(row.entityType, row.direction, row.key)}\n disabled={controlsDisabled || option.canStartRun === false}\n >\n {isSaving ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Save className=\"mr-2 h-4 w-4\" />}\n {isSaving\n ? t('data_sync.dashboard.schedule.saving', 'Saving...')\n : t('data_sync.dashboard.schedule.save', 'Save recurring schedule')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void handleDeleteSchedule(row.entityType, row.key)}\n disabled={controlsDisabled || !scheduleState.id}\n >\n {isDeleting ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Trash2 className=\"mr-2 h-4 w-4\" />}\n {isDeleting\n ? t('data_sync.dashboard.schedule.deleting', 'Removing...')\n : t('data_sync.dashboard.schedule.delete', 'Remove schedule')}\n </Button>\n </div>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n )}\n\n <div className=\"grid gap-3 rounded-lg border border-dashed p-4 text-sm text-muted-foreground md:grid-cols-3\">\n <div className=\"space-y-1\">\n <Label>{t('data_sync.dashboard.schedule.interval', 'Interval')}</Label>\n <p>{t('data_sync.dashboard.schedule.intervalHelp', 'Example: `1h`, `6h`, or `24h` for repeating intervals.')}</p>\n </div>\n <div className=\"space-y-1\">\n <Label>{t('data_sync.dashboard.schedule.cron', 'Cron')}</Label>\n <p>{t('data_sync.dashboard.schedule.cronHelp', 'Example: `0 * * * *` runs at the start of every hour.')}</p>\n </div>\n <div className=\"space-y-1\">\n <Label>{t('data_sync.integrationTab.runNowTitle', 'Manual runs')}</Label>\n <p>{t('data_sync.integrationTab.runNowHelp', 'Run now uses the full-sync checkbox from the same row and starts progress tracking immediately in Data Sync.')}</p>\n </div>\n </div>\n </section>\n )\n}\n\nexport default IntegrationScheduleTab\n"],
5
+ "mappings": "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",
6
6
  "names": ["supportedDirections"]
7
7
  }
@@ -166,6 +166,12 @@ async function DELETE(req, ctx) {
166
166
  });
167
167
  const dictionary = await loadDictionary(context, dictionaryId);
168
168
  const entry = await loadEntry(context, dictionary, entryId);
169
+ enforceCommandOptimisticLock({
170
+ resourceKind: "dictionaries.entry",
171
+ resourceId: entry.id,
172
+ current: entry.updatedAt ?? null,
173
+ request: req
174
+ });
169
175
  const guardUserId = resolveDictionaryActorId(context.auth);
170
176
  const guardResult = await validateCrudMutationGuard(context.container, {
171
177
  tenantId: context.tenantId,