@open-mercato/core 0.6.6-develop.5876.1.6a45c370ac → 0.6.6-develop.5900.1.b3fb52925e
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.
|
@@ -10,6 +10,7 @@ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
|
10
10
|
import { useCustomFieldDefs } from "@open-mercato/ui/backend/utils/customFieldDefs";
|
|
11
11
|
import { Alert, AlertDescription, AlertTitle } from "@open-mercato/ui/primitives/alert";
|
|
12
12
|
import { Badge } from "@open-mercato/ui/primitives/badge";
|
|
13
|
+
import { StatusBadge } from "@open-mercato/ui/primitives/status-badge";
|
|
13
14
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
14
15
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@open-mercato/ui/primitives/card";
|
|
15
16
|
import { Input } from "@open-mercato/ui/primitives/input";
|
|
@@ -898,37 +899,48 @@ function MetricCard({ label, value }) {
|
|
|
898
899
|
/* @__PURE__ */ jsx("div", { className: "mt-2 text-lg font-semibold text-foreground", children: value })
|
|
899
900
|
] });
|
|
900
901
|
}
|
|
902
|
+
const RUN_STATUS_BADGE_VARIANTS = {
|
|
903
|
+
completed: "success",
|
|
904
|
+
failed: "error",
|
|
905
|
+
cancelled: "neutral",
|
|
906
|
+
pending: "info",
|
|
907
|
+
running: "info",
|
|
908
|
+
idle: "neutral"
|
|
909
|
+
};
|
|
901
910
|
function RunStatusBadge({
|
|
902
911
|
status,
|
|
903
912
|
t
|
|
904
913
|
}) {
|
|
914
|
+
const variant = RUN_STATUS_BADGE_VARIANTS[status] ?? "neutral";
|
|
905
915
|
if (status === "completed") {
|
|
906
|
-
return /* @__PURE__ */ jsxs(
|
|
916
|
+
return /* @__PURE__ */ jsxs(StatusBadge, { variant, className: "gap-1.5", children: [
|
|
907
917
|
/* @__PURE__ */ jsx(CheckCircle2, { className: "size-3.5" }),
|
|
908
918
|
t("sync_excel.widget.run.status.completed", "Completed")
|
|
909
919
|
] });
|
|
910
920
|
}
|
|
911
921
|
if (status === "failed") {
|
|
912
|
-
return /* @__PURE__ */ jsxs(
|
|
922
|
+
return /* @__PURE__ */ jsxs(StatusBadge, { variant, className: "gap-1.5", children: [
|
|
913
923
|
/* @__PURE__ */ jsx(AlertTriangle, { className: "size-3.5" }),
|
|
914
924
|
t("sync_excel.widget.run.status.failed", "Failed")
|
|
915
925
|
] });
|
|
916
926
|
}
|
|
917
927
|
if (status === "cancelled") {
|
|
918
|
-
return /* @__PURE__ */ jsxs(
|
|
928
|
+
return /* @__PURE__ */ jsxs(StatusBadge, { variant, className: "gap-1.5", children: [
|
|
919
929
|
/* @__PURE__ */ jsx(XCircle, { className: "size-3.5" }),
|
|
920
930
|
t("sync_excel.widget.run.status.cancelled", "Cancelled")
|
|
921
931
|
] });
|
|
922
932
|
}
|
|
923
933
|
if (status === "pending" || status === "running") {
|
|
924
|
-
return /* @__PURE__ */ jsxs(
|
|
934
|
+
return /* @__PURE__ */ jsxs(StatusBadge, { variant, className: "gap-1.5", children: [
|
|
925
935
|
/* @__PURE__ */ jsx(RefreshCw, { className: cn("size-3.5", status === "running" ? "animate-spin" : "") }),
|
|
926
936
|
status === "pending" ? t("sync_excel.widget.run.status.pending", "Pending") : t("sync_excel.widget.run.status.running", "Running")
|
|
927
937
|
] });
|
|
928
938
|
}
|
|
929
|
-
return /* @__PURE__ */ jsx(
|
|
939
|
+
return /* @__PURE__ */ jsx(StatusBadge, { variant, children: t("sync_excel.widget.run.status.idle", "Idle") });
|
|
930
940
|
}
|
|
931
941
|
export {
|
|
942
|
+
RUN_STATUS_BADGE_VARIANTS,
|
|
943
|
+
RunStatusBadge,
|
|
932
944
|
SyncExcelUploadConfigWidget as default
|
|
933
945
|
};
|
|
934
946
|
//# sourceMappingURL=widget.client.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/sync_excel/widgets/injection/upload-config/widget.client.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport type { FieldMapping } from '../../../../data_sync/lib/adapter'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useCustomFieldDefs } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@open-mercato/ui/primitives/table'\nimport { AlertTriangle, CheckCircle2, Play, RefreshCw, Upload, XCircle } from 'lucide-react'\nimport {\n buildPeopleSuggestedMapping,\n buildPeopleTargetOptions,\n buildSuggestedMappingSignature,\n findMappingTargetOption,\n SYNC_EXCEL_PEOPLE_CUSTOM_FIELD_ENTITY_IDS,\n type MappingTargetOption,\n type SuggestedMapping,\n} from './target-options'\n\ntype SyncExcelIntegrationContext = {\n formId?: string\n integrationDetailWidgetSpotId?: string\n integrationId?: string\n activeTab?: string\n state?: {\n isEnabled?: boolean\n } | null\n refreshDetail?: () => Promise<void>\n refreshLogs?: () => Promise<void>\n refreshHealthSnapshot?: () => Promise<void>\n}\n\ntype SyncExcelIntegrationData = {\n state?: {\n isEnabled?: boolean\n } | null\n}\n\ntype PreviewRow = Record<string, string | null>\n\ntype UploadResponse = {\n uploadId: string\n filename: string\n mimeType: string\n fileSize: number\n entityType: 'customers.person'\n headers: string[]\n sampleRows: PreviewRow[]\n totalRows: number\n suggestedMapping: SuggestedMapping\n}\n\ntype ImportResponse = {\n runId: string\n progressJobId: string | null\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'\n}\n\ntype SyncRunDetail = {\n id: string\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'paused'\n createdCount: number\n updatedCount: number\n skippedCount: number\n failedCount: number\n lastError: string | null\n progressJobId: string | null\n progressJob: {\n id: string\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'\n progressPercent: number\n processedCount: number\n totalCount: number | null\n etaSeconds: number | null\n } | null\n}\n\ntype MappingRowState = {\n sourceColumn: string\n targetField: string\n}\n\ntype MappingDiagnostics = {\n duplicateTargets: string[]\n hasNameTarget: boolean\n hasIdentityTarget: boolean\n mappedCount: number\n unmappedCount: number\n}\n\ntype PersistedSessionSnapshot = {\n uploadId: string\n filename: string\n preview: UploadResponse | null\n mappingRows: MappingRowState[]\n matchStrategy: SuggestedMapping['matchStrategy']\n runId: string | null\n progressJobId: string | null\n}\n\nconst ENTITY_TYPE = 'customers.person' as const\nconst SESSION_STORAGE_PREFIX = 'om:sync_excel:session'\nconst RUN_POLL_INTERVAL_MS = 4_000\n\nconst SELECT_CLASS_NAME = 'flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'\n\nfunction buildMappingRows(headers: string[], suggestedMapping: SuggestedMapping): MappingRowState[] {\n const mappedFields = new Map(suggestedMapping.fields.map((field) => [field.externalField, field.localField]))\n return headers.map((header) => ({\n sourceColumn: header,\n targetField: mappedFields.get(header) ?? '',\n }))\n}\n\nfunction buildFieldMapping(sourceColumn: string, targetField: string, targetOptions: MappingTargetOption[]): FieldMapping {\n const matchedOption = findMappingTargetOption(targetOptions, targetField)\n const mappingKind = matchedOption?.mappingKind ?? (targetField.startsWith('cf:') ? 'custom_field' : 'core')\n const dedupeRole = matchedOption?.dedupeRole\n\n return {\n externalField: sourceColumn,\n localField: targetField,\n mappingKind,\n ...(dedupeRole ? { dedupeRole } : {}),\n }\n}\n\nfunction inferMatchStrategy(rows: MappingRowState[]): SuggestedMapping['matchStrategy'] {\n if (rows.some((row) => row.targetField === 'person.externalId')) return 'externalId'\n if (rows.some((row) => row.targetField === 'person.primaryEmail')) return 'email'\n return 'custom'\n}\n\nfunction buildMapping(\n rows: MappingRowState[],\n matchStrategy: SuggestedMapping['matchStrategy'],\n targetOptions: MappingTargetOption[],\n): SuggestedMapping {\n const mappedRows = rows.filter((row) => row.targetField.trim().length > 0)\n const fields = mappedRows.map((row) => buildFieldMapping(row.sourceColumn, row.targetField, targetOptions))\n const resolvedMatchStrategy = matchStrategy === 'custom' ? inferMatchStrategy(rows) : matchStrategy\n const matchField = resolvedMatchStrategy === 'externalId'\n ? 'person.externalId'\n : resolvedMatchStrategy === 'email'\n ? 'person.primaryEmail'\n : undefined\n\n return {\n entityType: ENTITY_TYPE,\n matchStrategy: resolvedMatchStrategy,\n ...(matchField ? { matchField } : {}),\n fields,\n unmappedColumns: rows.filter((row) => row.targetField.trim().length === 0).map((row) => row.sourceColumn),\n }\n}\n\nfunction buildDiagnostics(rows: MappingRowState[]): MappingDiagnostics {\n const duplicateTargets = Array.from(\n rows\n .filter((row) => row.targetField.trim().length > 0)\n .reduce((accumulator, row) => {\n const nextCount = (accumulator.get(row.targetField) ?? 0) + 1\n accumulator.set(row.targetField, nextCount)\n return accumulator\n }, new Map<string, number>())\n .entries(),\n )\n .filter(([, count]) => count > 1)\n .map(([target]) => target)\n\n const hasNameTarget = rows.some((row) => row.targetField === 'person.lastName' || row.targetField === 'person.displayName')\n const hasIdentityTarget = rows.some((row) => row.targetField === 'person.externalId' || row.targetField === 'person.primaryEmail')\n const mappedCount = rows.filter((row) => row.targetField.trim().length > 0).length\n\n return {\n duplicateTargets,\n hasNameTarget,\n hasIdentityTarget,\n mappedCount,\n unmappedCount: rows.length - mappedCount,\n }\n}\n\nfunction formatTargetLabel(\n targetField: string,\n t: ReturnType<typeof useT>,\n targetOptions: MappingTargetOption[],\n): string {\n const option = findMappingTargetOption(targetOptions, targetField)\n if (!option) return targetField\n return option.labelKey ? t(option.labelKey, option.fallback) : option.fallback\n}\n\nfunction getMatchStrategyLabel(\n matchStrategy: SuggestedMapping['matchStrategy'],\n t: ReturnType<typeof useT>,\n): string {\n if (matchStrategy === 'externalId') {\n return t('sync_excel.widget.matchStrategy.externalId', 'Source record ID (Recommended)')\n }\n if (matchStrategy === 'email') {\n return t('sync_excel.widget.matchStrategy.email', 'Email address')\n }\n return t('sync_excel.widget.matchStrategy.custom', 'Custom matching')\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nfunction readApiErrorMessage(result: Record<string, unknown> | null | undefined, fallback: string): string {\n const value = result?.error\n if (typeof value === 'string' && value.trim().length > 0) return value\n return fallback\n}\n\nfunction normalizeStatus(status: SyncRunDetail['status'] | ImportResponse['status'] | null | undefined): 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'idle' {\n if (status === 'pending' || status === 'running' || status === 'completed' || status === 'failed' || status === 'cancelled') {\n return status\n }\n return 'idle'\n}\n\nfunction getSessionStorageKey(integrationId: string | undefined): string {\n return `${SESSION_STORAGE_PREFIX}:${integrationId ?? 'sync_excel'}`\n}\n\nfunction readPersistedSessionSnapshot(integrationId: string | undefined): PersistedSessionSnapshot | null {\n if (typeof window === 'undefined') return null\n try {\n const raw = window.sessionStorage.getItem(getSessionStorageKey(integrationId))\n if (!raw) return null\n const parsed = JSON.parse(raw) as Partial<PersistedSessionSnapshot> | null\n if (!parsed || typeof parsed !== 'object') return null\n if (typeof parsed.uploadId !== 'string' || parsed.uploadId.trim().length === 0) return null\n if (typeof parsed.filename !== 'string') return null\n if (!Array.isArray(parsed.mappingRows)) return null\n if (parsed.matchStrategy !== 'externalId' && parsed.matchStrategy !== 'email' && parsed.matchStrategy !== 'custom') {\n return null\n }\n const preview = (\n parsed.preview\n && typeof parsed.preview === 'object'\n && typeof parsed.preview.uploadId === 'string'\n && parsed.preview.uploadId.trim().length > 0\n && typeof parsed.preview.filename === 'string'\n && Array.isArray(parsed.preview.headers)\n && Array.isArray(parsed.preview.sampleRows)\n && typeof parsed.preview.totalRows === 'number'\n && typeof parsed.preview.fileSize === 'number'\n && parsed.preview.entityType === ENTITY_TYPE\n && parsed.preview.suggestedMapping\n )\n ? parsed.preview as UploadResponse\n : null\n\n return {\n uploadId: parsed.uploadId,\n filename: parsed.filename,\n preview,\n mappingRows: parsed.mappingRows\n .filter((row): row is MappingRowState => (\n Boolean(row)\n && typeof row.sourceColumn === 'string'\n && typeof row.targetField === 'string'\n )),\n matchStrategy: parsed.matchStrategy,\n runId: typeof parsed.runId === 'string' && parsed.runId.trim().length > 0 ? parsed.runId : null,\n progressJobId: typeof parsed.progressJobId === 'string' && parsed.progressJobId.trim().length > 0 ? parsed.progressJobId : null,\n }\n } catch {\n return null\n }\n}\n\nfunction writePersistedSessionSnapshot(\n integrationId: string | undefined,\n snapshot: PersistedSessionSnapshot | null,\n): void {\n if (typeof window === 'undefined') return\n try {\n const key = getSessionStorageKey(integrationId)\n if (!snapshot) {\n window.sessionStorage.removeItem(key)\n return\n }\n window.sessionStorage.setItem(key, JSON.stringify(snapshot))\n } catch {\n // sessionStorage can be unavailable in private/locked-down browser contexts; persistence is best-effort.\n }\n}\n\nexport default function SyncExcelUploadConfigWidget({\n context,\n data,\n}: InjectionWidgetComponentProps<SyncExcelIntegrationContext, SyncExcelIntegrationData>) {\n const pathname = usePathname()\n const router = useRouter()\n const searchParams = useSearchParams()\n const t = useT()\n const { data: customFieldDefs = [] } = useCustomFieldDefs([...SYNC_EXCEL_PEOPLE_CUSTOM_FIELD_ENTITY_IDS])\n const [selectedFile, setSelectedFile] = React.useState<File | null>(null)\n const [preview, setPreview] = React.useState<UploadResponse | null>(null)\n const [mappingRows, setMappingRows] = React.useState<MappingRowState[]>([])\n const [matchStrategy, setMatchStrategy] = React.useState<SuggestedMapping['matchStrategy']>('custom')\n const [isMappingDirty, setIsMappingDirty] = React.useState(false)\n const [runDetail, setRunDetail] = React.useState<SyncRunDetail | null>(null)\n const [runId, setRunId] = React.useState<string | null>(null)\n const [progressJobId, setProgressJobId] = React.useState<string | null>(null)\n const [isUploading, setIsUploading] = React.useState(false)\n const [isRefreshingPreview, setIsRefreshingPreview] = React.useState(false)\n const [isStartingImport, setIsStartingImport] = React.useState(false)\n const [isCancelling, setIsCancelling] = React.useState(false)\n const [isRestoringSession, setIsRestoringSession] = React.useState(true)\n const mutationContextId = `${context.formId ?? 'sync-excel'}:sync-excel-import`\n const lastAppliedSuggestionSignatureRef = React.useRef<string | null>(null)\n const restoringSnapshotRef = React.useRef<PersistedSessionSnapshot | null>(null)\n const lastRestoreRequestKeyRef = React.useRef<string | null>(null)\n const { runMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: mutationContextId,\n spotId: context.integrationDetailWidgetSpotId,\n })\n\n const targetOptions = React.useMemo(\n () => buildPeopleTargetOptions(customFieldDefs),\n [customFieldDefs],\n )\n const previewSuggestion = React.useMemo(\n () => preview ? buildPeopleSuggestedMapping(preview.headers, preview.suggestedMapping, customFieldDefs) : null,\n [customFieldDefs, preview],\n )\n const resolvedIntegrationEnabled = data?.state?.isEnabled ?? context.state?.isEnabled ?? true\n const diagnostics = React.useMemo(() => buildDiagnostics(mappingRows), [mappingRows])\n const canStartImport = Boolean(preview)\n && diagnostics.duplicateTargets.length === 0\n && diagnostics.hasNameTarget\n && diagnostics.hasIdentityTarget\n && mappingRows.some((row) => row.targetField.trim().length > 0)\n const importStatus = normalizeStatus(runDetail?.status ?? (runId ? 'pending' : null))\n const progressValue = runDetail?.progressJob?.progressPercent ?? 0\n const isRunActive = importStatus === 'pending' || importStatus === 'running'\n const searchParamsKey = searchParams?.toString() ?? ''\n const uploadIdFromUrl = searchParams?.get('uploadId') ?? null\n const runIdFromUrl = searchParams?.get('runId') ?? null\n const shouldHideInteractiveContent = isRestoringSession && Boolean(uploadIdFromUrl)\n const hasExistingRunForCurrentUpload = Boolean(preview?.uploadId && runId)\n const hasSafeDedupeStrategy = matchStrategy === 'externalId' || matchStrategy === 'email'\n\n const replaceQueryParams = React.useCallback((updates: Record<string, string | null | undefined>) => {\n const params = new URLSearchParams(searchParamsKey)\n for (const [key, value] of Object.entries(updates)) {\n if (typeof value === 'string' && value.trim().length > 0) {\n params.set(key, value)\n } else {\n params.delete(key)\n }\n }\n const query = params.toString()\n if (query === searchParamsKey) return\n router.replace(query ? `${pathname}?${query}` : pathname)\n }, [pathname, router, searchParamsKey])\n\n const clearPersistedSession = React.useCallback(() => {\n writePersistedSessionSnapshot(context.integrationId, null)\n replaceQueryParams({ uploadId: null, runId: null })\n }, [context.integrationId, replaceQueryParams])\n\n const persistCurrentSession = React.useCallback((next?: Partial<PersistedSessionSnapshot>) => {\n const persistedPreview = next?.preview ?? preview ?? null\n const uploadId = next?.uploadId ?? persistedPreview?.uploadId ?? null\n const filename = next?.filename ?? persistedPreview?.filename ?? null\n if (!uploadId || !filename) {\n writePersistedSessionSnapshot(context.integrationId, null)\n return\n }\n\n writePersistedSessionSnapshot(context.integrationId, {\n uploadId,\n filename,\n preview: persistedPreview,\n mappingRows: next?.mappingRows ?? mappingRows,\n matchStrategy: next?.matchStrategy ?? matchStrategy,\n runId: next?.runId ?? runId,\n progressJobId: next?.progressJobId ?? progressJobId,\n })\n }, [context.integrationId, mappingRows, matchStrategy, preview, progressJobId, runId])\n\n const syncPreviewState = React.useCallback((nextPreview: UploadResponse, options?: {\n preserveManualState?: boolean\n restoredSnapshot?: PersistedSessionSnapshot | null\n }) => {\n const nextSuggestedMapping = buildPeopleSuggestedMapping(nextPreview.headers, nextPreview.suggestedMapping, customFieldDefs)\n setPreview(nextPreview)\n\n const restoredSnapshot = options?.restoredSnapshot\n const shouldRestoreSnapshot = Boolean(\n restoredSnapshot\n && restoredSnapshot.uploadId === nextPreview.uploadId\n && restoredSnapshot.mappingRows.length > 0,\n )\n\n if (shouldRestoreSnapshot) {\n setMappingRows(restoredSnapshot!.mappingRows)\n setMatchStrategy(restoredSnapshot!.matchStrategy)\n setRunId(restoredSnapshot!.runId)\n setProgressJobId(restoredSnapshot!.progressJobId)\n setIsMappingDirty(true)\n lastAppliedSuggestionSignatureRef.current = null\n persistCurrentSession({\n uploadId: nextPreview.uploadId,\n filename: nextPreview.filename,\n preview: nextPreview,\n mappingRows: restoredSnapshot!.mappingRows,\n matchStrategy: restoredSnapshot!.matchStrategy,\n runId: restoredSnapshot!.runId,\n progressJobId: restoredSnapshot!.progressJobId,\n })\n replaceQueryParams({\n uploadId: nextPreview.uploadId,\n runId: restoredSnapshot!.runId,\n })\n return\n }\n\n if (options?.preserveManualState && preview?.uploadId === nextPreview.uploadId) {\n persistCurrentSession({\n uploadId: nextPreview.uploadId,\n filename: nextPreview.filename,\n preview: nextPreview,\n })\n replaceQueryParams({ uploadId: nextPreview.uploadId })\n return\n }\n\n const nextRows = buildMappingRows(nextPreview.headers, nextSuggestedMapping)\n setMappingRows(nextRows)\n setMatchStrategy(nextSuggestedMapping.matchStrategy)\n setIsMappingDirty(false)\n lastAppliedSuggestionSignatureRef.current = buildSuggestedMappingSignature(nextPreview.headers, nextSuggestedMapping)\n persistCurrentSession({\n uploadId: nextPreview.uploadId,\n filename: nextPreview.filename,\n preview: nextPreview,\n mappingRows: nextRows,\n matchStrategy: nextSuggestedMapping.matchStrategy,\n runId: null,\n progressJobId: null,\n })\n replaceQueryParams({\n uploadId: nextPreview.uploadId,\n runId: null,\n })\n }, [customFieldDefs, persistCurrentSession, preview?.uploadId, replaceQueryParams])\n\n React.useEffect(() => {\n if (!preview || !previewSuggestion) return\n if (isMappingDirty) return\n const nextSignature = buildSuggestedMappingSignature(preview.headers, previewSuggestion)\n if (lastAppliedSuggestionSignatureRef.current === nextSignature) return\n setMappingRows(buildMappingRows(preview.headers, previewSuggestion))\n setMatchStrategy(previewSuggestion.matchStrategy)\n lastAppliedSuggestionSignatureRef.current = nextSignature\n }, [isMappingDirty, preview, previewSuggestion])\n\n const refreshRunDetail = React.useCallback(async (currentRunId: string) => {\n const call = await apiCall<SyncRunDetail>(`/api/data_sync/runs/${encodeURIComponent(currentRunId)}`, undefined, { fallback: null })\n if (call.ok && call.result) {\n setRunDetail(call.result)\n setProgressJobId(call.result.progressJobId ?? null)\n persistCurrentSession({\n runId: currentRunId,\n progressJobId: call.result.progressJobId ?? null,\n })\n return call.result\n }\n return null\n }, [persistCurrentSession])\n\n React.useEffect(() => {\n if (!preview?.uploadId) return\n persistCurrentSession()\n }, [mappingRows, matchStrategy, persistCurrentSession, preview?.uploadId, progressJobId, runId])\n\n React.useEffect(() => {\n const persistedSnapshot = readPersistedSessionSnapshot(context.integrationId)\n const resolvedUploadId = uploadIdFromUrl ?? persistedSnapshot?.uploadId ?? null\n const resolvedRunId = runIdFromUrl ?? persistedSnapshot?.runId ?? null\n\n if (!resolvedUploadId) {\n restoringSnapshotRef.current = null\n lastRestoreRequestKeyRef.current = null\n setIsRestoringSession(false)\n return\n }\n\n if (!uploadIdFromUrl && persistedSnapshot?.uploadId === resolvedUploadId) {\n replaceQueryParams({\n uploadId: resolvedUploadId,\n runId: resolvedRunId,\n })\n }\n\n if (preview?.uploadId === resolvedUploadId && mappingRows.length > 0) {\n if (resolvedRunId && runId !== resolvedRunId) {\n setRunId(resolvedRunId)\n }\n setIsRestoringSession(false)\n return\n }\n\n const restoreRequestKey = `${resolvedUploadId}:${resolvedRunId ?? ''}`\n if (lastRestoreRequestKeyRef.current === restoreRequestKey) {\n setIsRestoringSession(false)\n return\n }\n\n lastRestoreRequestKeyRef.current = restoreRequestKey\n\n let cancelled = false\n restoringSnapshotRef.current = persistedSnapshot\n const persistedPreview = restoringSnapshotRef.current?.preview?.uploadId === resolvedUploadId\n ? restoringSnapshotRef.current.preview\n : null\n\n if (persistedPreview) {\n syncPreviewState(persistedPreview, {\n restoredSnapshot: restoringSnapshotRef.current,\n })\n setIsRestoringSession(false)\n } else {\n setIsRestoringSession(true)\n }\n\n const restoreSession = async () => {\n const call = await apiCall<UploadResponse>(\n `/api/sync_excel/preview?uploadId=${encodeURIComponent(resolvedUploadId)}&entityType=${encodeURIComponent(ENTITY_TYPE)}`,\n undefined,\n { fallback: null },\n )\n\n if (cancelled) return\n\n if (!call.ok || !call.result) {\n setPreview(null)\n setMappingRows([])\n setMatchStrategy('custom')\n setRunId(null)\n setRunDetail(null)\n setProgressJobId(null)\n clearPersistedSession()\n lastRestoreRequestKeyRef.current = null\n setIsRestoringSession(false)\n return\n }\n\n syncPreviewState(call.result, {\n restoredSnapshot: restoringSnapshotRef.current,\n })\n\n if (resolvedRunId && !(restoringSnapshotRef.current?.runId)) {\n setRunId(resolvedRunId)\n }\n\n setIsRestoringSession(false)\n }\n\n void restoreSession()\n\n return () => {\n cancelled = true\n }\n }, [clearPersistedSession, context.integrationId, mappingRows.length, preview?.uploadId, replaceQueryParams, runId, runIdFromUrl, syncPreviewState, uploadIdFromUrl])\n\n React.useEffect(() => {\n if (!runId) return\n\n let cancelled = false\n let timeoutId: ReturnType<typeof setTimeout> | null = null\n\n const poll = async () => {\n const detail = await refreshRunDetail(runId)\n if (cancelled) return\n if (context.activeTab === 'logs') {\n await context.refreshLogs?.()\n }\n if (context.activeTab === 'health' || normalizeStatus(detail?.status) !== 'idle') {\n await context.refreshHealthSnapshot?.()\n }\n const status = normalizeStatus(detail?.status)\n if (status === 'pending' || status === 'running') {\n timeoutId = setTimeout(() => {\n void poll()\n }, RUN_POLL_INTERVAL_MS)\n }\n }\n\n void poll()\n\n return () => {\n cancelled = true\n if (timeoutId) clearTimeout(timeoutId)\n }\n }, [context.activeTab, context.refreshHealthSnapshot, context.refreshLogs, refreshRunDetail, runId])\n\n const handleUpload = React.useCallback(async () => {\n if (!selectedFile) {\n flash(t('sync_excel.widget.messages.fileRequired', 'Select a CSV file first.'), 'error')\n return\n }\n\n setIsUploading(true)\n try {\n const formData = new FormData()\n formData.set('file', selectedFile)\n formData.set('entityType', ENTITY_TYPE)\n\n const call = await runMutation({\n operation: () => apiCall<UploadResponse>('/api/sync_excel/upload', {\n method: 'POST',\n body: formData,\n }, { fallback: null }),\n mutationPayload: {\n integrationId: context.integrationId ?? 'sync_excel',\n entityType: ENTITY_TYPE,\n fileName: selectedFile.name,\n },\n context: {\n ...context,\n operation: 'create',\n actionId: 'sync-excel-upload',\n },\n })\n\n if (!call.ok || !call.result) {\n flash(\n readApiErrorMessage(call.result as Record<string, unknown> | null, t('sync_excel.widget.messages.uploadError', 'Failed to upload CSV file.')),\n 'error',\n )\n return\n }\n\n syncPreviewState(call.result)\n setSelectedFile(null)\n setRunId(null)\n setRunDetail(null)\n setProgressJobId(null)\n flash(t('sync_excel.widget.messages.uploadSuccess', 'CSV preview is ready.'), 'success')\n } catch {\n flash(t('sync_excel.widget.messages.uploadError', 'Failed to upload CSV file.'), 'error')\n } finally {\n setIsUploading(false)\n }\n }, [context, runMutation, selectedFile, syncPreviewState, t])\n\n const handleRefreshPreview = React.useCallback(async () => {\n if (!preview) return\n setIsRefreshingPreview(true)\n try {\n const call = await apiCall<UploadResponse>(\n `/api/sync_excel/preview?uploadId=${encodeURIComponent(preview.uploadId)}&entityType=${encodeURIComponent(ENTITY_TYPE)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n flash(t('sync_excel.widget.messages.previewError', 'Failed to refresh preview.'), 'error')\n return\n }\n syncPreviewState(call.result, { preserveManualState: true })\n flash(t('sync_excel.widget.messages.previewRefreshed', 'Preview refreshed.'), 'success')\n } catch {\n flash(t('sync_excel.widget.messages.previewError', 'Failed to refresh preview.'), 'error')\n } finally {\n setIsRefreshingPreview(false)\n }\n }, [preview, syncPreviewState, t])\n\n const handleTargetFieldChange = React.useCallback((sourceColumn: string, targetField: string) => {\n setIsMappingDirty(true)\n setMappingRows((current) => current.map((row) => row.sourceColumn === sourceColumn ? { ...row, targetField } : row))\n }, [])\n\n const handleResetToSuggested = React.useCallback(() => {\n if (!preview || !previewSuggestion) return\n setMappingRows(buildMappingRows(preview.headers, previewSuggestion))\n setMatchStrategy(previewSuggestion.matchStrategy)\n setIsMappingDirty(false)\n lastAppliedSuggestionSignatureRef.current = buildSuggestedMappingSignature(preview.headers, previewSuggestion)\n flash(t('sync_excel.widget.messages.mappingReset', 'Suggested mapping restored.'), 'success')\n }, [preview, previewSuggestion, t])\n\n const handleStartImport = React.useCallback(async () => {\n if (!preview) return\n\n const mapping = buildMapping(mappingRows, matchStrategy, targetOptions)\n setIsStartingImport(true)\n\n try {\n const call = await runMutation({\n operation: () => apiCall<ImportResponse>('/api/sync_excel/import', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n uploadId: preview.uploadId,\n entityType: ENTITY_TYPE,\n mapping,\n }),\n }, { fallback: null }),\n mutationPayload: {\n integrationId: context.integrationId ?? 'sync_excel',\n uploadId: preview.uploadId,\n entityType: ENTITY_TYPE,\n mapping,\n },\n context: {\n ...context,\n operation: 'create',\n actionId: 'sync-excel-start-import',\n },\n })\n\n if (!call.ok || !call.result) {\n flash(\n readApiErrorMessage(call.result as Record<string, unknown> | null, t('sync_excel.widget.messages.importError', 'Failed to start import run.')),\n 'error',\n )\n return\n }\n\n setRunId(call.result.runId)\n setProgressJobId(call.result.progressJobId)\n replaceQueryParams({\n uploadId: preview.uploadId,\n runId: call.result.runId,\n })\n persistCurrentSession({\n uploadId: preview.uploadId,\n filename: preview.filename,\n runId: call.result.runId,\n progressJobId: call.result.progressJobId,\n })\n flash(t('sync_excel.widget.messages.importStarted', 'Import run started.'), 'success')\n await refreshRunDetail(call.result.runId)\n await context.refreshLogs?.()\n await context.refreshHealthSnapshot?.()\n } catch {\n flash(t('sync_excel.widget.messages.importError', 'Failed to start import run.'), 'error')\n } finally {\n setIsStartingImport(false)\n }\n }, [context, mappingRows, matchStrategy, persistCurrentSession, preview, refreshRunDetail, replaceQueryParams, runMutation, t, targetOptions])\n\n const handleCancelRun = React.useCallback(async () => {\n if (!runId) return\n\n setIsCancelling(true)\n try {\n const call = await runMutation({\n operation: () => apiCall<{ ok?: boolean }>(`/api/data_sync/runs/${encodeURIComponent(runId)}/cancel`, {\n method: 'POST',\n }, { fallback: null }),\n mutationPayload: {\n integrationId: context.integrationId ?? 'sync_excel',\n runId,\n },\n context: {\n ...context,\n operation: 'update',\n actionId: 'sync-excel-cancel-import',\n },\n })\n\n if (!call.ok) {\n flash(t('sync_excel.widget.messages.cancelError', 'Failed to cancel import run.'), 'error')\n return\n }\n\n flash(t('sync_excel.widget.messages.cancelSuccess', 'Import run cancelled.'), 'success')\n await refreshRunDetail(runId)\n await context.refreshLogs?.()\n await context.refreshHealthSnapshot?.()\n } catch {\n flash(t('sync_excel.widget.messages.cancelError', 'Failed to cancel import run.'), 'error')\n } finally {\n setIsCancelling(false)\n }\n }, [context, refreshRunDetail, runId, runMutation, t])\n\n const handleRefreshAll = React.useCallback(async () => {\n if (!runId) return\n await refreshRunDetail(runId)\n await context.refreshLogs?.()\n await context.refreshHealthSnapshot?.()\n }, [context, refreshRunDetail, runId])\n\n const firstSampleRow = preview?.sampleRows[0] ?? null\n\n return (\n <div className=\"space-y-6\">\n {isRestoringSession ? (\n <Card>\n <CardContent className=\"flex items-center gap-3 py-6\">\n <Spinner className=\"size-4\" />\n <div className=\"text-sm text-muted-foreground\">\n {t('sync_excel.widget.messages.restoringSession', 'Restoring the last CSV session...')}\n </div>\n </CardContent>\n </Card>\n ) : null}\n\n {!resolvedIntegrationEnabled ? (\n <StatusAlert\n variant=\"warning\"\n title={t('sync_excel.widget.disabled.title', 'Integration disabled')}\n message={t('sync_excel.widget.disabled.message', 'Enable this integration to upload CSV files and start imports.')}\n />\n ) : null}\n\n {!shouldHideInteractiveContent ? (\n <Card>\n <CardHeader>\n <CardTitle>{t('sync_excel.widget.upload.title', 'Upload CSV')}</CardTitle>\n <CardDescription>\n {t('sync_excel.widget.upload.description', 'Upload a CSV file, review the suggested people mapping, and start a background import run.')}\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"sync-excel-upload-file\">{t('sync_excel.widget.upload.fileLabel', 'CSV file')}</Label>\n <Input\n id=\"sync-excel-upload-file\"\n type=\"file\"\n accept=\".csv,text/csv\"\n onChange={(event) => setSelectedFile(event.target.files?.[0] ?? null)}\n disabled={isUploading || isRunActive || isRestoringSession}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <Button type=\"button\" onClick={() => void handleUpload()} disabled={!selectedFile || isUploading || isRunActive || isRestoringSession}>\n {isUploading ? <Spinner className=\"mr-2 size-4\" /> : <Upload className=\"mr-2 size-4\" />}\n {isUploading\n ? t('sync_excel.widget.actions.uploadPending', 'Uploading...')\n : t('sync_excel.widget.actions.upload', 'Upload and preview')}\n </Button>\n {preview ? (\n <Button type=\"button\" variant=\"outline\" onClick={() => void handleRefreshPreview()} disabled={isRefreshingPreview || isUploading || isRunActive || isRestoringSession}>\n {isRefreshingPreview ? <Spinner className=\"mr-2 size-4\" /> : <RefreshCw className=\"mr-2 size-4\" />}\n {isRefreshingPreview\n ? t('sync_excel.widget.actions.refreshPending', 'Refreshing...')\n : t('sync_excel.widget.actions.refresh', 'Refresh preview')}\n </Button>\n ) : null}\n </div>\n\n {selectedFile ? (\n <div className=\"text-sm text-muted-foreground\">\n {t('sync_excel.widget.upload.selectedFile', 'Selected file')}: <span className=\"font-medium text-foreground\">{selectedFile.name}</span>\n </div>\n ) : null}\n </CardContent>\n </Card>\n ) : null}\n\n {preview && !shouldHideInteractiveContent ? (\n <Card>\n <CardHeader>\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"space-y-1\">\n <CardTitle>{t('sync_excel.widget.preview.title', 'Preview and mapping')}</CardTitle>\n <CardDescription>\n {t('sync_excel.widget.preview.description', 'Every source column stays visible here. Leave a target empty to keep the column unmapped for now.')}\n </CardDescription>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Badge variant=\"outline\">{preview.filename}</Badge>\n <Badge variant=\"outline\">{t('sync_excel.widget.preview.rows', '{count} rows', { count: preview.totalRows })}</Badge>\n <Badge variant=\"outline\">{formatFileSize(preview.fileSize)}</Badge>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n <MetricCard\n label={t('sync_excel.widget.metrics.mapped', 'Mapped columns')}\n value={String(diagnostics.mappedCount)}\n />\n <MetricCard\n label={t('sync_excel.widget.metrics.unmapped', 'Unmapped columns')}\n value={String(diagnostics.unmappedCount)}\n />\n <MetricCard\n label={t('sync_excel.widget.metrics.identity', 'Identity strategy')}\n value={getMatchStrategyLabel(matchStrategy, t)}\n />\n <MetricCard\n label={t('sync_excel.widget.metrics.entity', 'Target entity')}\n value={ENTITY_TYPE}\n />\n </div>\n\n <div className=\"grid gap-4 lg:grid-cols-[minmax(0,1fr)_280px]\">\n <div className=\"overflow-hidden rounded-lg border\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{t('sync_excel.widget.mapping.sourceColumn', 'Source column')}</TableHead>\n <TableHead>{t('sync_excel.widget.mapping.sampleValue', 'Sample value')}</TableHead>\n <TableHead>{t('sync_excel.widget.mapping.targetField', 'Target field')}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {mappingRows.map((row) => (\n <TableRow key={row.sourceColumn}>\n <TableCell className=\"font-medium\">{row.sourceColumn}</TableCell>\n <TableCell className=\"max-w-[260px] truncate text-muted-foreground\">\n {firstSampleRow?.[row.sourceColumn] ?? '\u2014'}\n </TableCell>\n <TableCell>\n <select\n className={SELECT_CLASS_NAME}\n value={row.targetField}\n onChange={(event) => handleTargetFieldChange(row.sourceColumn, event.target.value)}\n disabled={isStartingImport || isRunActive}\n >\n <option value=\"\">{t('sync_excel.mapping.targets.ignore', 'Leave unmapped')}</option>\n {targetOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.labelKey ? t(option.labelKey, option.fallback) : option.fallback}\n </option>\n ))}\n </select>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"sync-excel-match-strategy\">\n {t('sync_excel.widget.matchStrategy.label', 'How to match existing people')}\n </Label>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'sync_excel.widget.matchStrategy.help',\n 'This decides whether imported rows update existing people or create new ones.',\n )}\n </p>\n <select\n id=\"sync-excel-match-strategy\"\n className={SELECT_CLASS_NAME}\n value={matchStrategy}\n onChange={(event) => {\n setIsMappingDirty(true)\n setMatchStrategy(event.target.value as SuggestedMapping['matchStrategy'])\n }}\n disabled={isStartingImport || isRunActive}\n >\n <option value=\"externalId\">\n {t('sync_excel.widget.matchStrategy.externalId', 'Source record ID (Recommended)')}\n </option>\n <option value=\"email\">{t('sync_excel.widget.matchStrategy.email', 'Email address')}</option>\n <option value=\"custom\">{t('sync_excel.widget.matchStrategy.custom', 'Custom matching')}</option>\n </select>\n <p className=\"text-sm text-muted-foreground\">\n {matchStrategy === 'externalId'\n ? t(\n 'sync_excel.widget.matchStrategy.externalIdDescription',\n 'Best for repeated imports from the same source. Future imports update the same people instead of creating duplicates.',\n )\n : matchStrategy === 'email'\n ? t(\n 'sync_excel.widget.matchStrategy.emailDescription',\n 'Matches people by email. Works best when each person has one reliable, unique email address.',\n )\n : t(\n 'sync_excel.widget.matchStrategy.customDescription',\n 'Advanced option. Use only if you know how existing records should be identified in this import.',\n )}\n </p>\n </div>\n\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleResetToSuggested} disabled={isStartingImport || isRunActive}>\n <RefreshCw className=\"mr-2 size-4\" />\n {t('sync_excel.widget.actions.reset', 'Reset to suggestion')}\n </Button>\n <Button type=\"button\" onClick={() => void handleStartImport()} disabled={!canStartImport || isStartingImport || isRunActive}>\n {isStartingImport ? <Spinner className=\"mr-2 size-4\" /> : <Play className=\"mr-2 size-4\" />}\n {isStartingImport\n ? t('sync_excel.widget.actions.startPending', 'Starting...')\n : t('sync_excel.widget.actions.start', 'Start import')}\n </Button>\n </div>\n\n {matchStrategy === 'custom' ? (\n <StatusAlert\n variant=\"warning\"\n title={t('sync_excel.widget.matchStrategy.customWarningTitle', 'Use custom matching with care')}\n message={t(\n 'sync_excel.widget.matchStrategy.customWarningMessage',\n 'Custom matching may create duplicates if the mapped fields do not uniquely identify a person.',\n )}\n />\n ) : null}\n\n {hasExistingRunForCurrentUpload ? (\n <StatusAlert\n variant=\"warning\"\n title={t('sync_excel.widget.validation.reimportTitle', 'This upload already has a run')}\n message={t(\n 'sync_excel.widget.validation.reimportMessage',\n 'Starting another import from the same uploaded CSV may create duplicates unless your matching strategy reliably identifies existing people.',\n )}\n />\n ) : null}\n\n {!hasSafeDedupeStrategy ? (\n <StatusAlert\n variant=\"warning\"\n title={t('sync_excel.widget.validation.duplicateRiskTitle', 'Duplicate risk')}\n message={t(\n 'sync_excel.widget.validation.duplicateRiskMessage',\n 'This mapping does not use the recommended External ID or Email matching strategy. Re-importing the same dataset may create duplicate people.',\n )}\n />\n ) : null}\n\n {diagnostics.duplicateTargets.length > 0 ? (\n <StatusAlert\n variant=\"error\"\n title={t('sync_excel.widget.validation.duplicateTargetsTitle', 'Duplicate target fields')}\n message={t(\n 'sync_excel.widget.validation.duplicateTargetsMessage',\n 'Each target field can only be mapped once in this foundation slice: {targets}',\n { targets: diagnostics.duplicateTargets.map((target) => formatTargetLabel(target, t, targetOptions)).join(', ') },\n )}\n />\n ) : null}\n\n {!diagnostics.hasIdentityTarget ? (\n <StatusAlert\n variant=\"warning\"\n title={t('sync_excel.widget.validation.identityTitle', 'Map an identity field')}\n message={t('sync_excel.widget.validation.identityMessage', 'Map either External ID or Primary email so reimports can match existing people.')}\n />\n ) : null}\n\n {!diagnostics.hasNameTarget ? (\n <StatusAlert\n variant=\"warning\"\n title={t('sync_excel.widget.validation.nameTitle', 'Map a name field')}\n message={t('sync_excel.widget.validation.nameMessage', 'Map Last name or Display name so the importer can build person records.')}\n />\n ) : null}\n\n {diagnostics.hasIdentityTarget && diagnostics.hasNameTarget && diagnostics.duplicateTargets.length === 0 ? (\n <StatusAlert\n variant=\"info\"\n title={t('sync_excel.widget.validation.readyTitle', 'Ready to import')}\n message={t('sync_excel.widget.validation.readyMessage', 'The current mapping is valid for the first customers.person slice.')}\n />\n ) : null}\n </div>\n </div>\n </CardContent>\n </Card>\n ) : null}\n\n {runId && !shouldHideInteractiveContent ? (\n <Card>\n <CardHeader>\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"space-y-1\">\n <CardTitle>{t('sync_excel.widget.run.title', 'Import run status')}</CardTitle>\n <CardDescription>\n {t('sync_excel.widget.run.description', 'The import runs in the background through Data Sync and updates this view as progress changes.')}\n </CardDescription>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <RunStatusBadge status={importStatus} t={t} />\n {progressJobId ? <Badge variant=\"outline\">{progressJobId}</Badge> : null}\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-muted-foreground\">{t('sync_excel.widget.run.progressLabel', 'Progress')}</span>\n <span className=\"font-medium\">{progressValue}%</span>\n </div>\n <Progress value={progressValue} className=\"h-2.5\" />\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-5\">\n <MetricCard label={t('sync_excel.widget.run.metrics.created', 'Created')} value={String(runDetail?.createdCount ?? 0)} />\n <MetricCard label={t('sync_excel.widget.run.metrics.updated', 'Updated')} value={String(runDetail?.updatedCount ?? 0)} />\n <MetricCard label={t('sync_excel.widget.run.metrics.skipped', 'Skipped')} value={String(runDetail?.skippedCount ?? 0)} />\n <MetricCard label={t('sync_excel.widget.run.metrics.failed', 'Failed')} value={String(runDetail?.failedCount ?? 0)} />\n <MetricCard label={t('sync_excel.widget.run.metrics.processed', 'Processed')} value={String(runDetail?.progressJob?.processedCount ?? 0)} />\n </div>\n\n {runDetail?.lastError ? (\n <StatusAlert\n variant=\"error\"\n title={t('sync_excel.widget.run.errorTitle', 'Run error')}\n message={runDetail.lastError}\n />\n ) : null}\n\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={() => void handleRefreshAll()} disabled={isRefreshingPreview || isRestoringSession}>\n <RefreshCw className=\"mr-2 size-4\" />\n {t('sync_excel.widget.actions.refreshRun', 'Refresh run status')}\n </Button>\n {isRunActive ? (\n <Button type=\"button\" variant=\"destructive\" onClick={() => void handleCancelRun()} disabled={isCancelling}>\n {isCancelling ? <Spinner className=\"mr-2 size-4\" /> : <XCircle className=\"mr-2 size-4\" />}\n {isCancelling\n ? t('sync_excel.widget.actions.cancelPending', 'Cancelling...')\n : t('sync_excel.widget.actions.cancel', 'Cancel run')}\n </Button>\n ) : null}\n </div>\n </CardContent>\n </Card>\n ) : null}\n </div>\n )\n}\n\nfunction StatusAlert({\n variant,\n title,\n message,\n}: {\n variant: 'warning' | 'error' | 'info'\n title: string\n message: string\n}) {\n return (\n <Alert variant={variant === 'error' ? 'destructive' : variant}>\n <AlertTitle>{title}</AlertTitle>\n <AlertDescription>{message}</AlertDescription>\n </Alert>\n )\n}\n\nfunction MetricCard({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"rounded-lg border bg-muted/30 p-4\">\n <div className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">{label}</div>\n <div className=\"mt-2 text-lg font-semibold text-foreground\">{value}</div>\n </div>\n )\n}\n\nfunction RunStatusBadge({\n status,\n t,\n}: {\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'idle'\n t: ReturnType<typeof useT>\n}) {\n if (status === 'completed') {\n return (\n <Badge variant=\"outline\" className=\"gap-1.5 border-emerald-500/30 bg-emerald-500/10 text-emerald-300\">\n <CheckCircle2 className=\"size-3.5\" />\n {t('sync_excel.widget.run.status.completed', 'Completed')}\n </Badge>\n )\n }\n\n if (status === 'failed') {\n return (\n <Badge variant=\"outline\" className=\"gap-1.5 border-red-500/30 bg-red-500/10 text-red-300\">\n <AlertTriangle className=\"size-3.5\" />\n {t('sync_excel.widget.run.status.failed', 'Failed')}\n </Badge>\n )\n }\n\n if (status === 'cancelled') {\n return (\n <Badge variant=\"outline\" className=\"gap-1.5 border-zinc-500/30 bg-zinc-500/10 text-zinc-300\">\n <XCircle className=\"size-3.5\" />\n {t('sync_excel.widget.run.status.cancelled', 'Cancelled')}\n </Badge>\n )\n }\n\n if (status === 'pending' || status === 'running') {\n return (\n <Badge variant=\"outline\" className=\"gap-1.5 border-blue-500/30 bg-blue-500/10 text-blue-300\">\n <RefreshCw className={cn('size-3.5', status === 'running' ? 'animate-spin' : '')} />\n {status === 'pending'\n ? t('sync_excel.widget.run.status.pending', 'Pending')\n : t('sync_excel.widget.run.status.running', 'Running')}\n </Badge>\n )\n }\n\n return (\n <Badge variant=\"outline\" className=\"gap-1.5 border-zinc-500/30 bg-zinc-500/10 text-zinc-300\">\n {t('sync_excel.widget.run.status.idle', 'Idle')}\n </Badge>\n )\n}\n"],
|
|
5
|
-
"mappings": ";AA+yBU,SACE,KADF;AA7yBV,YAAY,WAAW;AACvB,SAAS,aAAa,WAAW,uBAAuB;AAGxD,SAAS,YAAY;AACrB,SAAS,UAAU;AACnB,SAAS,aAAa;AACtB,SAAS,0BAA0B;AACnC,SAAS,eAAe;AACxB,SAAS,0BAA0B;AACnC,SAAS,OAAO,kBAAkB,kBAAkB;AACpD,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,MAAM,aAAa,iBAAiB,YAAY,iBAAiB;AAC1E,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,OAAO,WAAW,WAAW,WAAW,aAAa,gBAAgB;AAC9E,SAAS,eAAe,cAAc,MAAM,WAAW,QAAQ,eAAe;AAC9E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAmFP,MAAM,cAAc;AACpB,MAAM,yBAAyB;AAC/B,MAAM,uBAAuB;AAE7B,MAAM,oBAAoB;AAE1B,SAAS,iBAAiB,SAAmB,kBAAuD;AAClG,QAAM,eAAe,IAAI,IAAI,iBAAiB,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,eAAe,MAAM,UAAU,CAAC,CAAC;AAC5G,SAAO,QAAQ,IAAI,CAAC,YAAY;AAAA,IAC9B,cAAc;AAAA,IACd,aAAa,aAAa,IAAI,MAAM,KAAK;AAAA,EAC3C,EAAE;AACJ;AAEA,SAAS,kBAAkB,cAAsB,aAAqB,eAAoD;AACxH,QAAM,gBAAgB,wBAAwB,eAAe,WAAW;AACxE,QAAM,cAAc,eAAe,gBAAgB,YAAY,WAAW,KAAK,IAAI,iBAAiB;AACpG,QAAM,aAAa,eAAe;AAElC,SAAO;AAAA,IACL,eAAe;AAAA,IACf,YAAY;AAAA,IACZ;AAAA,IACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,EACrC;AACF;AAEA,SAAS,mBAAmB,MAA4D;AACtF,MAAI,KAAK,KAAK,CAAC,QAAQ,IAAI,gBAAgB,mBAAmB,EAAG,QAAO;AACxE,MAAI,KAAK,KAAK,CAAC,QAAQ,IAAI,gBAAgB,qBAAqB,EAAG,QAAO;AAC1E,SAAO;AACT;AAEA,SAAS,aACP,MACA,eACA,eACkB;AAClB,QAAM,aAAa,KAAK,OAAO,CAAC,QAAQ,IAAI,YAAY,KAAK,EAAE,SAAS,CAAC;AACzE,QAAM,SAAS,WAAW,IAAI,CAAC,QAAQ,kBAAkB,IAAI,cAAc,IAAI,aAAa,aAAa,CAAC;AAC1G,QAAM,wBAAwB,kBAAkB,WAAW,mBAAmB,IAAI,IAAI;AACtF,QAAM,aAAa,0BAA0B,eACzC,sBACA,0BAA0B,UACxB,wBACA;AAEN,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,IACnC;AAAA,IACA,iBAAiB,KAAK,OAAO,CAAC,QAAQ,IAAI,YAAY,KAAK,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,QAAQ,IAAI,YAAY;AAAA,EAC1G;AACF;AAEA,SAAS,iBAAiB,MAA6C;AACrE,QAAM,mBAAmB,MAAM;AAAA,IAC7B,KACG,OAAO,CAAC,QAAQ,IAAI,YAAY,KAAK,EAAE,SAAS,CAAC,EACjD,OAAO,CAAC,aAAa,QAAQ;AAC5B,YAAM,aAAa,YAAY,IAAI,IAAI,WAAW,KAAK,KAAK;AAC5D,kBAAY,IAAI,IAAI,aAAa,SAAS;AAC1C,aAAO;AAAA,IACT,GAAG,oBAAI,IAAoB,CAAC,EAC3B,QAAQ;AAAA,EACb,EACG,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,QAAQ,CAAC,EAC/B,IAAI,CAAC,CAAC,MAAM,MAAM,MAAM;AAE3B,QAAM,gBAAgB,KAAK,KAAK,CAAC,QAAQ,IAAI,gBAAgB,qBAAqB,IAAI,gBAAgB,oBAAoB;AAC1H,QAAM,oBAAoB,KAAK,KAAK,CAAC,QAAQ,IAAI,gBAAgB,uBAAuB,IAAI,gBAAgB,qBAAqB;AACjI,QAAM,cAAc,KAAK,OAAO,CAAC,QAAQ,IAAI,YAAY,KAAK,EAAE,SAAS,CAAC,EAAE;AAE5E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,KAAK,SAAS;AAAA,EAC/B;AACF;AAEA,SAAS,kBACP,aACA,GACA,eACQ;AACR,QAAM,SAAS,wBAAwB,eAAe,WAAW;AACjE,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,OAAO,WAAW,EAAE,OAAO,UAAU,OAAO,QAAQ,IAAI,OAAO;AACxE;AAEA,SAAS,sBACP,eACA,GACQ;AACR,MAAI,kBAAkB,cAAc;AAClC,WAAO,EAAE,8CAA8C,gCAAgC;AAAA,EACzF;AACA,MAAI,kBAAkB,SAAS;AAC7B,WAAO,EAAE,yCAAyC,eAAe;AAAA,EACnE;AACA,SAAO,EAAE,0CAA0C,iBAAiB;AACtE;AAEA,SAAS,eAAe,OAAuB;AAC7C,MAAI,QAAQ,KAAM,QAAO,GAAG,KAAK;AACjC,MAAI,QAAQ,OAAO,KAAM,QAAO,IAAI,QAAQ,MAAM,QAAQ,CAAC,CAAC;AAC5D,SAAO,IAAI,SAAS,OAAO,OAAO,QAAQ,CAAC,CAAC;AAC9C;AAEA,SAAS,oBAAoB,QAAoD,UAA0B;AACzG,QAAM,QAAQ,QAAQ;AACtB,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,EAAG,QAAO;AACjE,SAAO;AACT;AAEA,SAAS,gBAAgB,QAAsJ;AAC7K,MAAI,WAAW,aAAa,WAAW,aAAa,WAAW,eAAe,WAAW,YAAY,WAAW,aAAa;AAC3H,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,qBAAqB,eAA2C;AACvE,SAAO,GAAG,sBAAsB,IAAI,iBAAiB,YAAY;AACnE;AAEA,SAAS,6BAA6B,eAAoE;AACxG,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI;AACF,UAAM,MAAM,OAAO,eAAe,QAAQ,qBAAqB,aAAa,CAAC;AAC7E,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAI,OAAO,OAAO,aAAa,YAAY,OAAO,SAAS,KAAK,EAAE,WAAW,EAAG,QAAO;AACvF,QAAI,OAAO,OAAO,aAAa,SAAU,QAAO;AAChD,QAAI,CAAC,MAAM,QAAQ,OAAO,WAAW,EAAG,QAAO;AAC/C,QAAI,OAAO,kBAAkB,gBAAgB,OAAO,kBAAkB,WAAW,OAAO,kBAAkB,UAAU;AAClH,aAAO;AAAA,IACT;AACA,UAAM,UACJ,OAAO,WACJ,OAAO,OAAO,YAAY,YAC1B,OAAO,OAAO,QAAQ,aAAa,YACnC,OAAO,QAAQ,SAAS,KAAK,EAAE,SAAS,KACxC,OAAO,OAAO,QAAQ,aAAa,YACnC,MAAM,QAAQ,OAAO,QAAQ,OAAO,KACpC,MAAM,QAAQ,OAAO,QAAQ,UAAU,KACvC,OAAO,OAAO,QAAQ,cAAc,YACpC,OAAO,OAAO,QAAQ,aAAa,YACnC,OAAO,QAAQ,eAAe,eAC9B,OAAO,QAAQ,mBAEhB,OAAO,UACP;AAEJ,WAAO;AAAA,MACL,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,MACjB;AAAA,MACA,aAAa,OAAO,YACjB,OAAO,CAAC,QACP,QAAQ,GAAG,KACR,OAAO,IAAI,iBAAiB,YAC5B,OAAO,IAAI,gBAAgB,QAC/B;AAAA,MACH,eAAe,OAAO;AAAA,MACtB,OAAO,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,KAAK,EAAE,SAAS,IAAI,OAAO,QAAQ;AAAA,MAC3F,eAAe,OAAO,OAAO,kBAAkB,YAAY,OAAO,cAAc,KAAK,EAAE,SAAS,IAAI,OAAO,gBAAgB;AAAA,IAC7H;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,8BACP,eACA,UACM;AACN,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,UAAM,MAAM,qBAAqB,aAAa;AAC9C,QAAI,CAAC,UAAU;AACb,aAAO,eAAe,WAAW,GAAG;AACpC;AAAA,IACF;AACA,WAAO,eAAe,QAAQ,KAAK,KAAK,UAAU,QAAQ,CAAC;AAAA,EAC7D,QAAQ;AAAA,EAER;AACF;AAEe,SAAR,4BAA6C;AAAA,EAClD;AAAA,EACA;AACF,GAAyF;AACvF,QAAM,WAAW,YAAY;AAC7B,QAAM,SAAS,UAAU;AACzB,QAAM,eAAe,gBAAgB;AACrC,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,MAAM,kBAAkB,CAAC,EAAE,IAAI,mBAAmB,CAAC,GAAG,yCAAyC,CAAC;AACxG,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAsB,IAAI;AACxE,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAgC,IAAI;AACxE,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAA4B,CAAC,CAAC;AAC1E,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAA4C,QAAQ;AACpG,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,KAAK;AAChE,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAA+B,IAAI;AAC3E,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAwB,IAAI;AAC5E,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,KAAK;AAC1D,QAAM,CAAC,qBAAqB,sBAAsB,IAAI,MAAM,SAAS,KAAK;AAC1E,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAS,KAAK;AACpE,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,KAAK;AAC5D,QAAM,CAAC,oBAAoB,qBAAqB,IAAI,MAAM,SAAS,IAAI;AACvE,QAAM,oBAAoB,GAAG,QAAQ,UAAU,YAAY;AAC3D,QAAM,oCAAoC,MAAM,OAAsB,IAAI;AAC1E,QAAM,uBAAuB,MAAM,OAAwC,IAAI;AAC/E,QAAM,2BAA2B,MAAM,OAAsB,IAAI;AACjE,QAAM,EAAE,YAAY,IAAI,mBAA4C;AAAA,IAClE,WAAW;AAAA,IACX,QAAQ,QAAQ;AAAA,EAClB,CAAC;AAED,QAAM,gBAAgB,MAAM;AAAA,IAC1B,MAAM,yBAAyB,eAAe;AAAA,IAC9C,CAAC,eAAe;AAAA,EAClB;AACA,QAAM,oBAAoB,MAAM;AAAA,IAC9B,MAAM,UAAU,4BAA4B,QAAQ,SAAS,QAAQ,kBAAkB,eAAe,IAAI;AAAA,IAC1G,CAAC,iBAAiB,OAAO;AAAA,EAC3B;AACA,QAAM,6BAA6B,MAAM,OAAO,aAAa,QAAQ,OAAO,aAAa;AACzF,QAAM,cAAc,MAAM,QAAQ,MAAM,iBAAiB,WAAW,GAAG,CAAC,WAAW,CAAC;AACpF,QAAM,iBAAiB,QAAQ,OAAO,KACjC,YAAY,iBAAiB,WAAW,KACxC,YAAY,iBACZ,YAAY,qBACZ,YAAY,KAAK,CAAC,QAAQ,IAAI,YAAY,KAAK,EAAE,SAAS,CAAC;AAChE,QAAM,eAAe,gBAAgB,WAAW,WAAW,QAAQ,YAAY,KAAK;AACpF,QAAM,gBAAgB,WAAW,aAAa,mBAAmB;AACjE,QAAM,cAAc,iBAAiB,aAAa,iBAAiB;AACnE,QAAM,kBAAkB,cAAc,SAAS,KAAK;AACpD,QAAM,kBAAkB,cAAc,IAAI,UAAU,KAAK;AACzD,QAAM,eAAe,cAAc,IAAI,OAAO,KAAK;AACnD,QAAM,+BAA+B,sBAAsB,QAAQ,eAAe;AAClF,QAAM,iCAAiC,QAAQ,SAAS,YAAY,KAAK;AACzE,QAAM,wBAAwB,kBAAkB,gBAAgB,kBAAkB;AAElF,QAAM,qBAAqB,MAAM,YAAY,CAAC,YAAuD;AACnG,UAAM,SAAS,IAAI,gBAAgB,eAAe;AAClD,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,UAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG;AACxD,eAAO,IAAI,KAAK,KAAK;AAAA,MACvB,OAAO;AACL,eAAO,OAAO,GAAG;AAAA,MACnB;AAAA,IACF;AACA,UAAM,QAAQ,OAAO,SAAS;AAC9B,QAAI,UAAU,gBAAiB;AAC/B,WAAO,QAAQ,QAAQ,GAAG,QAAQ,IAAI,KAAK,KAAK,QAAQ;AAAA,EAC1D,GAAG,CAAC,UAAU,QAAQ,eAAe,CAAC;AAEtC,QAAM,wBAAwB,MAAM,YAAY,MAAM;AACpD,kCAA8B,QAAQ,eAAe,IAAI;AACzD,uBAAmB,EAAE,UAAU,MAAM,OAAO,KAAK,CAAC;AAAA,EACpD,GAAG,CAAC,QAAQ,eAAe,kBAAkB,CAAC;AAE9C,QAAM,wBAAwB,MAAM,YAAY,CAAC,SAA6C;AAC5F,UAAM,mBAAmB,MAAM,WAAW,WAAW;AACrD,UAAM,WAAW,MAAM,YAAY,kBAAkB,YAAY;AACjE,UAAM,WAAW,MAAM,YAAY,kBAAkB,YAAY;AACjE,QAAI,CAAC,YAAY,CAAC,UAAU;AAC1B,oCAA8B,QAAQ,eAAe,IAAI;AACzD;AAAA,IACF;AAEA,kCAA8B,QAAQ,eAAe;AAAA,MACnD;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,aAAa,MAAM,eAAe;AAAA,MAClC,eAAe,MAAM,iBAAiB;AAAA,MACtC,OAAO,MAAM,SAAS;AAAA,MACtB,eAAe,MAAM,iBAAiB;AAAA,IACxC,CAAC;AAAA,EACH,GAAG,CAAC,QAAQ,eAAe,aAAa,eAAe,SAAS,eAAe,KAAK,CAAC;AAErF,QAAM,mBAAmB,MAAM,YAAY,CAAC,aAA6B,YAGnE;AACJ,UAAM,uBAAuB,4BAA4B,YAAY,SAAS,YAAY,kBAAkB,eAAe;AAC3H,eAAW,WAAW;AAEtB,UAAM,mBAAmB,SAAS;AAClC,UAAM,wBAAwB;AAAA,MAC5B,oBACG,iBAAiB,aAAa,YAAY,YAC1C,iBAAiB,YAAY,SAAS;AAAA,IAC3C;AAEA,QAAI,uBAAuB;AACzB,qBAAe,iBAAkB,WAAW;AAC5C,uBAAiB,iBAAkB,aAAa;AAChD,eAAS,iBAAkB,KAAK;AAChC,uBAAiB,iBAAkB,aAAa;AAChD,wBAAkB,IAAI;AACtB,wCAAkC,UAAU;AAC5C,4BAAsB;AAAA,QACpB,UAAU,YAAY;AAAA,QACtB,UAAU,YAAY;AAAA,QACtB,SAAS;AAAA,QACT,aAAa,iBAAkB;AAAA,QAC/B,eAAe,iBAAkB;AAAA,QACjC,OAAO,iBAAkB;AAAA,QACzB,eAAe,iBAAkB;AAAA,MACnC,CAAC;AACD,yBAAmB;AAAA,QACjB,UAAU,YAAY;AAAA,QACtB,OAAO,iBAAkB;AAAA,MAC3B,CAAC;AACD;AAAA,IACF;AAEA,QAAI,SAAS,uBAAuB,SAAS,aAAa,YAAY,UAAU;AAC9E,4BAAsB;AAAA,QACpB,UAAU,YAAY;AAAA,QACtB,UAAU,YAAY;AAAA,QACtB,SAAS;AAAA,MACX,CAAC;AACD,yBAAmB,EAAE,UAAU,YAAY,SAAS,CAAC;AACrD;AAAA,IACF;AAEA,UAAM,WAAW,iBAAiB,YAAY,SAAS,oBAAoB;AAC3E,mBAAe,QAAQ;AACvB,qBAAiB,qBAAqB,aAAa;AACnD,sBAAkB,KAAK;AACvB,sCAAkC,UAAU,+BAA+B,YAAY,SAAS,oBAAoB;AACpH,0BAAsB;AAAA,MACpB,UAAU,YAAY;AAAA,MACtB,UAAU,YAAY;AAAA,MACtB,SAAS;AAAA,MACT,aAAa;AAAA,MACb,eAAe,qBAAqB;AAAA,MACpC,OAAO;AAAA,MACP,eAAe;AAAA,IACjB,CAAC;AACD,uBAAmB;AAAA,MACjB,UAAU,YAAY;AAAA,MACtB,OAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAC,iBAAiB,uBAAuB,SAAS,UAAU,kBAAkB,CAAC;AAElF,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,WAAW,CAAC,kBAAmB;AACpC,QAAI,eAAgB;AACpB,UAAM,gBAAgB,+BAA+B,QAAQ,SAAS,iBAAiB;AACvF,QAAI,kCAAkC,YAAY,cAAe;AACjE,mBAAe,iBAAiB,QAAQ,SAAS,iBAAiB,CAAC;AACnE,qBAAiB,kBAAkB,aAAa;AAChD,sCAAkC,UAAU;AAAA,EAC9C,GAAG,CAAC,gBAAgB,SAAS,iBAAiB,CAAC;AAE/C,QAAM,mBAAmB,MAAM,YAAY,OAAO,iBAAyB;AACzE,UAAM,OAAO,MAAM,QAAuB,uBAAuB,mBAAmB,YAAY,CAAC,IAAI,QAAW,EAAE,UAAU,KAAK,CAAC;AAClI,QAAI,KAAK,MAAM,KAAK,QAAQ;AAC1B,mBAAa,KAAK,MAAM;AACxB,uBAAiB,KAAK,OAAO,iBAAiB,IAAI;AAClD,4BAAsB;AAAA,QACpB,OAAO;AAAA,QACP,eAAe,KAAK,OAAO,iBAAiB;AAAA,MAC9C,CAAC;AACD,aAAO,KAAK;AAAA,IACd;AACA,WAAO;AAAA,EACT,GAAG,CAAC,qBAAqB,CAAC;AAE1B,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,SAAS,SAAU;AACxB,0BAAsB;AAAA,EACxB,GAAG,CAAC,aAAa,eAAe,uBAAuB,SAAS,UAAU,eAAe,KAAK,CAAC;AAE/F,QAAM,UAAU,MAAM;AACpB,UAAM,oBAAoB,6BAA6B,QAAQ,aAAa;AAC5E,UAAM,mBAAmB,mBAAmB,mBAAmB,YAAY;AAC3E,UAAM,gBAAgB,gBAAgB,mBAAmB,SAAS;AAElE,QAAI,CAAC,kBAAkB;AACrB,2BAAqB,UAAU;AAC/B,+BAAyB,UAAU;AACnC,4BAAsB,KAAK;AAC3B;AAAA,IACF;AAEA,QAAI,CAAC,mBAAmB,mBAAmB,aAAa,kBAAkB;AACxE,yBAAmB;AAAA,QACjB,UAAU;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAEA,QAAI,SAAS,aAAa,oBAAoB,YAAY,SAAS,GAAG;AACpE,UAAI,iBAAiB,UAAU,eAAe;AAC5C,iBAAS,aAAa;AAAA,MACxB;AACA,4BAAsB,KAAK;AAC3B;AAAA,IACF;AAEA,UAAM,oBAAoB,GAAG,gBAAgB,IAAI,iBAAiB,EAAE;AACpE,QAAI,yBAAyB,YAAY,mBAAmB;AAC1D,4BAAsB,KAAK;AAC3B;AAAA,IACF;AAEA,6BAAyB,UAAU;AAEnC,QAAI,YAAY;AAChB,yBAAqB,UAAU;AAC/B,UAAM,mBAAmB,qBAAqB,SAAS,SAAS,aAAa,mBACzE,qBAAqB,QAAQ,UAC7B;AAEJ,QAAI,kBAAkB;AACpB,uBAAiB,kBAAkB;AAAA,QACjC,kBAAkB,qBAAqB;AAAA,MACzC,CAAC;AACD,4BAAsB,KAAK;AAAA,IAC7B,OAAO;AACL,4BAAsB,IAAI;AAAA,IAC5B;AAEA,UAAM,iBAAiB,YAAY;AACjC,YAAM,OAAO,MAAM;AAAA,QACjB,oCAAoC,mBAAmB,gBAAgB,CAAC,eAAe,mBAAmB,WAAW,CAAC;AAAA,QACtH;AAAA,QACA,EAAE,UAAU,KAAK;AAAA,MACnB;AAEA,UAAI,UAAW;AAEf,UAAI,CAAC,KAAK,MAAM,CAAC,KAAK,QAAQ;AAC5B,mBAAW,IAAI;AACf,uBAAe,CAAC,CAAC;AACjB,yBAAiB,QAAQ;AACzB,iBAAS,IAAI;AACb,qBAAa,IAAI;AACjB,yBAAiB,IAAI;AACrB,8BAAsB;AACtB,iCAAyB,UAAU;AACnC,8BAAsB,KAAK;AAC3B;AAAA,MACF;AAEA,uBAAiB,KAAK,QAAQ;AAAA,QAC5B,kBAAkB,qBAAqB;AAAA,MACzC,CAAC;AAED,UAAI,iBAAiB,CAAE,qBAAqB,SAAS,OAAQ;AAC3D,iBAAS,aAAa;AAAA,MACxB;AAEA,4BAAsB,KAAK;AAAA,IAC7B;AAEA,SAAK,eAAe;AAEpB,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,uBAAuB,QAAQ,eAAe,YAAY,QAAQ,SAAS,UAAU,oBAAoB,OAAO,cAAc,kBAAkB,eAAe,CAAC;AAEpK,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,MAAO;AAEZ,QAAI,YAAY;AAChB,QAAI,YAAkD;AAEtD,UAAM,OAAO,YAAY;AACvB,YAAM,SAAS,MAAM,iBAAiB,KAAK;AAC3C,UAAI,UAAW;AACf,UAAI,QAAQ,cAAc,QAAQ;AAChC,cAAM,QAAQ,cAAc;AAAA,MAC9B;AACA,UAAI,QAAQ,cAAc,YAAY,gBAAgB,QAAQ,MAAM,MAAM,QAAQ;AAChF,cAAM,QAAQ,wBAAwB;AAAA,MACxC;AACA,YAAM,SAAS,gBAAgB,QAAQ,MAAM;AAC7C,UAAI,WAAW,aAAa,WAAW,WAAW;AAChD,oBAAY,WAAW,MAAM;AAC3B,eAAK,KAAK;AAAA,QACZ,GAAG,oBAAoB;AAAA,MACzB;AAAA,IACF;AAEA,SAAK,KAAK;AAEV,WAAO,MAAM;AACX,kBAAY;AACZ,UAAI,UAAW,cAAa,SAAS;AAAA,IACvC;AAAA,EACF,GAAG,CAAC,QAAQ,WAAW,QAAQ,uBAAuB,QAAQ,aAAa,kBAAkB,KAAK,CAAC;AAEnG,QAAM,eAAe,MAAM,YAAY,YAAY;AACjD,QAAI,CAAC,cAAc;AACjB,YAAM,EAAE,2CAA2C,0BAA0B,GAAG,OAAO;AACvF;AAAA,IACF;AAEA,mBAAe,IAAI;AACnB,QAAI;AACF,YAAM,WAAW,IAAI,SAAS;AAC9B,eAAS,IAAI,QAAQ,YAAY;AACjC,eAAS,IAAI,cAAc,WAAW;AAEtC,YAAM,OAAO,MAAM,YAAY;AAAA,QAC7B,WAAW,MAAM,QAAwB,0BAA0B;AAAA,UACjE,QAAQ;AAAA,UACR,MAAM;AAAA,QACR,GAAG,EAAE,UAAU,KAAK,CAAC;AAAA,QACrB,iBAAiB;AAAA,UACf,eAAe,QAAQ,iBAAiB;AAAA,UACxC,YAAY;AAAA,UACZ,UAAU,aAAa;AAAA,QACzB;AAAA,QACA,SAAS;AAAA,UACP,GAAG;AAAA,UACH,WAAW;AAAA,UACX,UAAU;AAAA,QACZ;AAAA,MACF,CAAC;AAED,UAAI,CAAC,KAAK,MAAM,CAAC,KAAK,QAAQ;AAC5B;AAAA,UACE,oBAAoB,KAAK,QAA0C,EAAE,0CAA0C,4BAA4B,CAAC;AAAA,UAC5I;AAAA,QACF;AACA;AAAA,MACF;AAEA,uBAAiB,KAAK,MAAM;AAC5B,sBAAgB,IAAI;AACpB,eAAS,IAAI;AACb,mBAAa,IAAI;AACjB,uBAAiB,IAAI;AACrB,YAAM,EAAE,4CAA4C,uBAAuB,GAAG,SAAS;AAAA,IACzF,QAAQ;AACN,YAAM,EAAE,0CAA0C,4BAA4B,GAAG,OAAO;AAAA,IAC1F,UAAE;AACA,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,SAAS,aAAa,cAAc,kBAAkB,CAAC,CAAC;AAE5D,QAAM,uBAAuB,MAAM,YAAY,YAAY;AACzD,QAAI,CAAC,QAAS;AACd,2BAAuB,IAAI;AAC3B,QAAI;AACF,YAAM,OAAO,MAAM;AAAA,QACjB,oCAAoC,mBAAmB,QAAQ,QAAQ,CAAC,eAAe,mBAAmB,WAAW,CAAC;AAAA,QACtH;AAAA,QACA,EAAE,UAAU,KAAK;AAAA,MACnB;AACA,UAAI,CAAC,KAAK,MAAM,CAAC,KAAK,QAAQ;AAC5B,cAAM,EAAE,2CAA2C,4BAA4B,GAAG,OAAO;AACzF;AAAA,MACF;AACA,uBAAiB,KAAK,QAAQ,EAAE,qBAAqB,KAAK,CAAC;AAC3D,YAAM,EAAE,+CAA+C,oBAAoB,GAAG,SAAS;AAAA,IACzF,QAAQ;AACN,YAAM,EAAE,2CAA2C,4BAA4B,GAAG,OAAO;AAAA,IAC3F,UAAE;AACA,6BAAuB,KAAK;AAAA,IAC9B;AAAA,EACF,GAAG,CAAC,SAAS,kBAAkB,CAAC,CAAC;AAEjC,QAAM,0BAA0B,MAAM,YAAY,CAAC,cAAsB,gBAAwB;AAC/F,sBAAkB,IAAI;AACtB,mBAAe,CAAC,YAAY,QAAQ,IAAI,CAAC,QAAQ,IAAI,iBAAiB,eAAe,EAAE,GAAG,KAAK,YAAY,IAAI,GAAG,CAAC;AAAA,EACrH,GAAG,CAAC,CAAC;AAEL,QAAM,yBAAyB,MAAM,YAAY,MAAM;AACrD,QAAI,CAAC,WAAW,CAAC,kBAAmB;AACpC,mBAAe,iBAAiB,QAAQ,SAAS,iBAAiB,CAAC;AACnE,qBAAiB,kBAAkB,aAAa;AAChD,sBAAkB,KAAK;AACvB,sCAAkC,UAAU,+BAA+B,QAAQ,SAAS,iBAAiB;AAC7G,UAAM,EAAE,2CAA2C,6BAA6B,GAAG,SAAS;AAAA,EAC9F,GAAG,CAAC,SAAS,mBAAmB,CAAC,CAAC;AAElC,QAAM,oBAAoB,MAAM,YAAY,YAAY;AACtD,QAAI,CAAC,QAAS;AAEd,UAAM,UAAU,aAAa,aAAa,eAAe,aAAa;AACtE,wBAAoB,IAAI;AAExB,QAAI;AACF,YAAM,OAAO,MAAM,YAAY;AAAA,QAC7B,WAAW,MAAM,QAAwB,0BAA0B;AAAA,UACjE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU;AAAA,YACnB,UAAU,QAAQ;AAAA,YAClB,YAAY;AAAA,YACZ;AAAA,UACF,CAAC;AAAA,QACH,GAAG,EAAE,UAAU,KAAK,CAAC;AAAA,QACrB,iBAAiB;AAAA,UACf,eAAe,QAAQ,iBAAiB;AAAA,UACxC,UAAU,QAAQ;AAAA,UAClB,YAAY;AAAA,UACZ;AAAA,QACF;AAAA,QACA,SAAS;AAAA,UACP,GAAG;AAAA,UACH,WAAW;AAAA,UACX,UAAU;AAAA,QACZ;AAAA,MACF,CAAC;AAED,UAAI,CAAC,KAAK,MAAM,CAAC,KAAK,QAAQ;AAC5B;AAAA,UACE,oBAAoB,KAAK,QAA0C,EAAE,0CAA0C,6BAA6B,CAAC;AAAA,UAC7I;AAAA,QACF;AACA;AAAA,MACF;AAEA,eAAS,KAAK,OAAO,KAAK;AAC1B,uBAAiB,KAAK,OAAO,aAAa;AAC1C,yBAAmB;AAAA,QACjB,UAAU,QAAQ;AAAA,QAClB,OAAO,KAAK,OAAO;AAAA,MACrB,CAAC;AACD,4BAAsB;AAAA,QACpB,UAAU,QAAQ;AAAA,QAClB,UAAU,QAAQ;AAAA,QAClB,OAAO,KAAK,OAAO;AAAA,QACnB,eAAe,KAAK,OAAO;AAAA,MAC7B,CAAC;AACD,YAAM,EAAE,4CAA4C,qBAAqB,GAAG,SAAS;AACrF,YAAM,iBAAiB,KAAK,OAAO,KAAK;AACxC,YAAM,QAAQ,cAAc;AAC5B,YAAM,QAAQ,wBAAwB;AAAA,IACxC,QAAQ;AACN,YAAM,EAAE,0CAA0C,6BAA6B,GAAG,OAAO;AAAA,IAC3F,UAAE;AACA,0BAAoB,KAAK;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,SAAS,aAAa,eAAe,uBAAuB,SAAS,kBAAkB,oBAAoB,aAAa,GAAG,aAAa,CAAC;AAE7I,QAAM,kBAAkB,MAAM,YAAY,YAAY;AACpD,QAAI,CAAC,MAAO;AAEZ,oBAAgB,IAAI;AACpB,QAAI;AACF,YAAM,OAAO,MAAM,YAAY;AAAA,QAC7B,WAAW,MAAM,QAA0B,uBAAuB,mBAAmB,KAAK,CAAC,WAAW;AAAA,UACpG,QAAQ;AAAA,QACV,GAAG,EAAE,UAAU,KAAK,CAAC;AAAA,QACrB,iBAAiB;AAAA,UACf,eAAe,QAAQ,iBAAiB;AAAA,UACxC;AAAA,QACF;AAAA,QACA,SAAS;AAAA,UACP,GAAG;AAAA,UACH,WAAW;AAAA,UACX,UAAU;AAAA,QACZ;AAAA,MACF,CAAC;AAED,UAAI,CAAC,KAAK,IAAI;AACZ,cAAM,EAAE,0CAA0C,8BAA8B,GAAG,OAAO;AAC1F;AAAA,MACF;AAEA,YAAM,EAAE,4CAA4C,uBAAuB,GAAG,SAAS;AACvF,YAAM,iBAAiB,KAAK;AAC5B,YAAM,QAAQ,cAAc;AAC5B,YAAM,QAAQ,wBAAwB;AAAA,IACxC,QAAQ;AACN,YAAM,EAAE,0CAA0C,8BAA8B,GAAG,OAAO;AAAA,IAC5F,UAAE;AACA,sBAAgB,KAAK;AAAA,IACvB;AAAA,EACF,GAAG,CAAC,SAAS,kBAAkB,OAAO,aAAa,CAAC,CAAC;AAErD,QAAM,mBAAmB,MAAM,YAAY,YAAY;AACrD,QAAI,CAAC,MAAO;AACZ,UAAM,iBAAiB,KAAK;AAC5B,UAAM,QAAQ,cAAc;AAC5B,UAAM,QAAQ,wBAAwB;AAAA,EACxC,GAAG,CAAC,SAAS,kBAAkB,KAAK,CAAC;AAErC,QAAM,iBAAiB,SAAS,WAAW,CAAC,KAAK;AAEjD,SACE,qBAAC,SAAI,WAAU,aACZ;AAAA,yBACC,oBAAC,QACC,+BAAC,eAAY,WAAU,gCACrB;AAAA,0BAAC,WAAQ,WAAU,UAAS;AAAA,MAC5B,oBAAC,SAAI,WAAU,iCACZ,YAAE,+CAA+C,mCAAmC,GACvF;AAAA,OACF,GACF,IACE;AAAA,IAEH,CAAC,6BACA;AAAA,MAAC;AAAA;AAAA,QACC,SAAQ;AAAA,QACR,OAAO,EAAE,oCAAoC,sBAAsB;AAAA,QACnE,SAAS,EAAE,sCAAsC,gEAAgE;AAAA;AAAA,IACnH,IACE;AAAA,IAEH,CAAC,+BACF,qBAAC,QACC;AAAA,2BAAC,cACC;AAAA,4BAAC,aAAW,YAAE,kCAAkC,YAAY,GAAE;AAAA,QAC9D,oBAAC,mBACE,YAAE,wCAAwC,4FAA4F,GACzI;AAAA,SACF;AAAA,MACA,qBAAC,eAAY,WAAU,aACrB;AAAA,6BAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,SAAQ,0BAA0B,YAAE,sCAAsC,UAAU,GAAE;AAAA,UAC7F;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,MAAK;AAAA,cACL,QAAO;AAAA,cACP,UAAU,CAAC,UAAU,gBAAgB,MAAM,OAAO,QAAQ,CAAC,KAAK,IAAI;AAAA,cACpE,UAAU,eAAe,eAAe;AAAA;AAAA,UAC1C;AAAA,WACF;AAAA,QAEA,qBAAC,SAAI,WAAU,qCACb;AAAA,+BAAC,UAAO,MAAK,UAAS,SAAS,MAAM,KAAK,aAAa,GAAG,UAAU,CAAC,gBAAgB,eAAe,eAAe,oBAChH;AAAA,0BAAc,oBAAC,WAAQ,WAAU,eAAc,IAAK,oBAAC,UAAO,WAAU,eAAc;AAAA,YACpF,cACG,EAAE,2CAA2C,cAAc,IAC3D,EAAE,oCAAoC,oBAAoB;AAAA,aAChE;AAAA,UACC,UACC,qBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,MAAM,KAAK,qBAAqB,GAAG,UAAU,uBAAuB,eAAe,eAAe,oBAChJ;AAAA,kCAAsB,oBAAC,WAAQ,WAAU,eAAc,IAAK,oBAAC,aAAU,WAAU,eAAc;AAAA,YAC/F,sBACG,EAAE,4CAA4C,eAAe,IAC7D,EAAE,qCAAqC,iBAAiB;AAAA,aAC9D,IACE;AAAA,WACN;AAAA,QAEC,eACC,qBAAC,SAAI,WAAU,iCACZ;AAAA,YAAE,yCAAyC,eAAe;AAAA,UAAE;AAAA,UAAE,oBAAC,UAAK,WAAU,+BAA+B,uBAAa,MAAK;AAAA,WAClI,IACE;AAAA,SACN;AAAA,OACF,IACI;AAAA,IAEH,WAAW,CAAC,+BACX,qBAAC,QACC;AAAA,0BAAC,cACC,+BAAC,SAAI,WAAU,oDACb;AAAA,6BAAC,SAAI,WAAU,aACb;AAAA,8BAAC,aAAW,YAAE,mCAAmC,qBAAqB,GAAE;AAAA,UACxE,oBAAC,mBACE,YAAE,yCAAyC,mGAAmG,GACjJ;AAAA,WACF;AAAA,QACA,qBAAC,SAAI,WAAU,wBACb;AAAA,8BAAC,SAAM,SAAQ,WAAW,kBAAQ,UAAS;AAAA,UAC3C,oBAAC,SAAM,SAAQ,WAAW,YAAE,kCAAkC,gBAAgB,EAAE,OAAO,QAAQ,UAAU,CAAC,GAAE;AAAA,UAC5G,oBAAC,SAAM,SAAQ,WAAW,yBAAe,QAAQ,QAAQ,GAAE;AAAA,WAC7D;AAAA,SACF,GACF;AAAA,MACA,qBAAC,eAAY,WAAU,aACrB;AAAA,6BAAC,SAAI,WAAU,4CACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,EAAE,oCAAoC,gBAAgB;AAAA,cAC7D,OAAO,OAAO,YAAY,WAAW;AAAA;AAAA,UACvC;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,EAAE,sCAAsC,kBAAkB;AAAA,cACjE,OAAO,OAAO,YAAY,aAAa;AAAA;AAAA,UACzC;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,EAAE,sCAAsC,mBAAmB;AAAA,cAClE,OAAO,sBAAsB,eAAe,CAAC;AAAA;AAAA,UAC/C;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,EAAE,oCAAoC,eAAe;AAAA,cAC5D,OAAO;AAAA;AAAA,UACT;AAAA,WACF;AAAA,QAEA,qBAAC,SAAI,WAAU,iDACb;AAAA,8BAAC,SAAI,WAAU,qCACb,+BAAC,SACC;AAAA,gCAAC,eACC,+BAAC,YACC;AAAA,kCAAC,aAAW,YAAE,0CAA0C,eAAe,GAAE;AAAA,cACzE,oBAAC,aAAW,YAAE,yCAAyC,cAAc,GAAE;AAAA,cACvE,oBAAC,aAAW,YAAE,yCAAyC,cAAc,GAAE;AAAA,eACzE,GACF;AAAA,YACA,oBAAC,aACE,sBAAY,IAAI,CAAC,QAChB,qBAAC,YACC;AAAA,kCAAC,aAAU,WAAU,eAAe,cAAI,cAAa;AAAA,cACrD,oBAAC,aAAU,WAAU,gDAClB,2BAAiB,IAAI,YAAY,KAAK,UACzC;AAAA,cACA,oBAAC,aACC;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAW;AAAA,kBACX,OAAO,IAAI;AAAA,kBACX,UAAU,CAAC,UAAU,wBAAwB,IAAI,cAAc,MAAM,OAAO,KAAK;AAAA,kBACjF,UAAU,oBAAoB;AAAA,kBAE9B;AAAA,wCAAC,YAAO,OAAM,IAAI,YAAE,qCAAqC,gBAAgB,GAAE;AAAA,oBAC1E,cAAc,IAAI,CAAC,WAClB,oBAAC,YAA0B,OAAO,OAAO,OACtC,iBAAO,WAAW,EAAE,OAAO,UAAU,OAAO,QAAQ,IAAI,OAAO,YADrD,OAAO,KAEpB,CACD;AAAA;AAAA;AAAA,cACH,GACF;AAAA,iBAnBa,IAAI,YAoBnB,CACD,GACH;AAAA,aACF,GACF;AAAA,UAEA,qBAAC,SAAI,WAAU,aACb;AAAA,iCAAC,SAAI,WAAU,aACb;AAAA,kCAAC,SAAM,SAAQ,6BACZ,YAAE,yCAAyC,8BAA8B,GAC5E;AAAA,cACA,oBAAC,OAAE,WAAU,iCACV;AAAA,gBACC;AAAA,gBACA;AAAA,cACF,GACF;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,IAAG;AAAA,kBACH,WAAW;AAAA,kBACX,OAAO;AAAA,kBACP,UAAU,CAAC,UAAU;AACnB,sCAAkB,IAAI;AACtB,qCAAiB,MAAM,OAAO,KAA0C;AAAA,kBAC1E;AAAA,kBACA,UAAU,oBAAoB;AAAA,kBAE9B;AAAA,wCAAC,YAAO,OAAM,cACX,YAAE,8CAA8C,gCAAgC,GACnF;AAAA,oBACA,oBAAC,YAAO,OAAM,SAAS,YAAE,yCAAyC,eAAe,GAAE;AAAA,oBACnF,oBAAC,YAAO,OAAM,UAAU,YAAE,0CAA0C,iBAAiB,GAAE;AAAA;AAAA;AAAA,cACzF;AAAA,cACA,oBAAC,OAAE,WAAU,iCACV,4BAAkB,eACf;AAAA,gBACE;AAAA,gBACA;AAAA,cACF,IACA,kBAAkB,UAChB;AAAA,gBACE;AAAA,gBACA;AAAA,cACF,IACA;AAAA,gBACE;AAAA,gBACA;AAAA,cACF,GACR;AAAA,eACF;AAAA,YAEA,qBAAC,SAAI,WAAU,wBACb;AAAA,mCAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,wBAAwB,UAAU,oBAAoB,aACrG;AAAA,oCAAC,aAAU,WAAU,eAAc;AAAA,gBAClC,EAAE,mCAAmC,qBAAqB;AAAA,iBAC7D;AAAA,cACA,qBAAC,UAAO,MAAK,UAAS,SAAS,MAAM,KAAK,kBAAkB,GAAG,UAAU,CAAC,kBAAkB,oBAAoB,aAC7G;AAAA,mCAAmB,oBAAC,WAAQ,WAAU,eAAc,IAAK,oBAAC,QAAK,WAAU,eAAc;AAAA,gBACvF,mBACG,EAAE,0CAA0C,aAAa,IACzD,EAAE,mCAAmC,cAAc;AAAA,iBACzD;AAAA,eACF;AAAA,YAEC,kBAAkB,WACjB;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,OAAO,EAAE,sDAAsD,+BAA+B;AAAA,gBAC9F,SAAS;AAAA,kBACP;AAAA,kBACA;AAAA,gBACF;AAAA;AAAA,YACF,IACE;AAAA,YAEH,iCACC;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,OAAO,EAAE,8CAA8C,+BAA+B;AAAA,gBACtF,SAAS;AAAA,kBACP;AAAA,kBACA;AAAA,gBACF;AAAA;AAAA,YACF,IACE;AAAA,YAEH,CAAC,wBACA;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,OAAO,EAAE,mDAAmD,gBAAgB;AAAA,gBAC5E,SAAS;AAAA,kBACP;AAAA,kBACA;AAAA,gBACF;AAAA;AAAA,YACF,IACE;AAAA,YAEH,YAAY,iBAAiB,SAAS,IACrC;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,OAAO,EAAE,sDAAsD,yBAAyB;AAAA,gBACxF,SAAS;AAAA,kBACP;AAAA,kBACA;AAAA,kBACA,EAAE,SAAS,YAAY,iBAAiB,IAAI,CAAC,WAAW,kBAAkB,QAAQ,GAAG,aAAa,CAAC,EAAE,KAAK,IAAI,EAAE;AAAA,gBAClH;AAAA;AAAA,YACF,IACE;AAAA,YAEH,CAAC,YAAY,oBACZ;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,OAAO,EAAE,8CAA8C,uBAAuB;AAAA,gBAC9E,SAAS,EAAE,gDAAgD,iFAAiF;AAAA;AAAA,YAC9I,IACE;AAAA,YAEH,CAAC,YAAY,gBACZ;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,OAAO,EAAE,0CAA0C,kBAAkB;AAAA,gBACrE,SAAS,EAAE,4CAA4C,yEAAyE;AAAA;AAAA,YAClI,IACE;AAAA,YAEH,YAAY,qBAAqB,YAAY,iBAAiB,YAAY,iBAAiB,WAAW,IACrG;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,OAAO,EAAE,2CAA2C,iBAAiB;AAAA,gBACrE,SAAS,EAAE,6CAA6C,oEAAoE;AAAA;AAAA,YAC9H,IACE;AAAA,aACN;AAAA,WACF;AAAA,SACF;AAAA,OACF,IACE;AAAA,IAEH,SAAS,CAAC,+BACT,qBAAC,QACC;AAAA,0BAAC,cACC,+BAAC,SAAI,WAAU,oDACb;AAAA,6BAAC,SAAI,WAAU,aACb;AAAA,8BAAC,aAAW,YAAE,+BAA+B,mBAAmB,GAAE;AAAA,UAClE,oBAAC,mBACE,YAAE,qCAAqC,gGAAgG,GAC1I;AAAA,WACF;AAAA,QACA,qBAAC,SAAI,WAAU,qCACb;AAAA,8BAAC,kBAAe,QAAQ,cAAc,GAAM;AAAA,UAC3C,gBAAgB,oBAAC,SAAM,SAAQ,WAAW,yBAAc,IAAW;AAAA,WACtE;AAAA,SACF,GACF;AAAA,MACA,qBAAC,eAAY,WAAU,aACrB;AAAA,6BAAC,SAAI,WAAU,aACb;AAAA,+BAAC,SAAI,WAAU,6CACb;AAAA,gCAAC,UAAK,WAAU,yBAAyB,YAAE,uCAAuC,UAAU,GAAE;AAAA,YAC9F,qBAAC,UAAK,WAAU,eAAe;AAAA;AAAA,cAAc;AAAA,eAAC;AAAA,aAChD;AAAA,UACA,oBAAC,YAAS,OAAO,eAAe,WAAU,SAAQ;AAAA,WACpD;AAAA,QAEA,qBAAC,SAAI,WAAU,4CACb;AAAA,8BAAC,cAAW,OAAO,EAAE,yCAAyC,SAAS,GAAG,OAAO,OAAO,WAAW,gBAAgB,CAAC,GAAG;AAAA,UACvH,oBAAC,cAAW,OAAO,EAAE,yCAAyC,SAAS,GAAG,OAAO,OAAO,WAAW,gBAAgB,CAAC,GAAG;AAAA,UACvH,oBAAC,cAAW,OAAO,EAAE,yCAAyC,SAAS,GAAG,OAAO,OAAO,WAAW,gBAAgB,CAAC,GAAG;AAAA,UACvH,oBAAC,cAAW,OAAO,EAAE,wCAAwC,QAAQ,GAAG,OAAO,OAAO,WAAW,eAAe,CAAC,GAAG;AAAA,UACpH,oBAAC,cAAW,OAAO,EAAE,2CAA2C,WAAW,GAAG,OAAO,OAAO,WAAW,aAAa,kBAAkB,CAAC,GAAG;AAAA,WAC5I;AAAA,QAEC,WAAW,YACV;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,OAAO,EAAE,oCAAoC,WAAW;AAAA,YACxD,SAAS,UAAU;AAAA;AAAA,QACrB,IACE;AAAA,QAEJ,qBAAC,SAAI,WAAU,wBACb;AAAA,+BAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,MAAM,KAAK,iBAAiB,GAAG,UAAU,uBAAuB,oBAC/G;AAAA,gCAAC,aAAU,WAAU,eAAc;AAAA,YAClC,EAAE,wCAAwC,oBAAoB;AAAA,aACjE;AAAA,UACC,cACC,qBAAC,UAAO,MAAK,UAAS,SAAQ,eAAc,SAAS,MAAM,KAAK,gBAAgB,GAAG,UAAU,cAC1F;AAAA,2BAAe,oBAAC,WAAQ,WAAU,eAAc,IAAK,oBAAC,WAAQ,WAAU,eAAc;AAAA,YACtF,eACG,EAAE,2CAA2C,eAAe,IAC5D,EAAE,oCAAoC,YAAY;AAAA,aACxD,IACE;AAAA,WACN;AAAA,SACF;AAAA,OACF,IACE;AAAA,KACN;AAEJ;AAEA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE,qBAAC,SAAM,SAAS,YAAY,UAAU,gBAAgB,SACpD;AAAA,wBAAC,cAAY,iBAAM;AAAA,IACnB,oBAAC,oBAAkB,mBAAQ;AAAA,KAC7B;AAEJ;AAEA,SAAS,WAAW,EAAE,OAAO,MAAM,GAAqC;AACtE,SACE,qBAAC,SAAI,WAAU,qCACb;AAAA,wBAAC,SAAI,WAAU,qEAAqE,iBAAM;AAAA,IAC1F,oBAAC,SAAI,WAAU,8CAA8C,iBAAM;AAAA,KACrE;AAEJ;AAEA,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AACF,GAGG;AACD,MAAI,WAAW,aAAa;AAC1B,WACE,qBAAC,SAAM,SAAQ,WAAU,WAAU,oEACjC;AAAA,0BAAC,gBAAa,WAAU,YAAW;AAAA,MAClC,EAAE,0CAA0C,WAAW;AAAA,OAC1D;AAAA,EAEJ;AAEA,MAAI,WAAW,UAAU;AACvB,WACE,qBAAC,SAAM,SAAQ,WAAU,WAAU,wDACjC;AAAA,0BAAC,iBAAc,WAAU,YAAW;AAAA,MACnC,EAAE,uCAAuC,QAAQ;AAAA,OACpD;AAAA,EAEJ;AAEA,MAAI,WAAW,aAAa;AAC1B,WACE,qBAAC,SAAM,SAAQ,WAAU,WAAU,2DACjC;AAAA,0BAAC,WAAQ,WAAU,YAAW;AAAA,MAC7B,EAAE,0CAA0C,WAAW;AAAA,OAC1D;AAAA,EAEJ;AAEA,MAAI,WAAW,aAAa,WAAW,WAAW;AAChD,WACE,qBAAC,SAAM,SAAQ,WAAU,WAAU,2DACjC;AAAA,0BAAC,aAAU,WAAW,GAAG,YAAY,WAAW,YAAY,iBAAiB,EAAE,GAAG;AAAA,MACjF,WAAW,YACR,EAAE,wCAAwC,SAAS,IACnD,EAAE,wCAAwC,SAAS;AAAA,OACzD;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAM,SAAQ,WAAU,WAAU,2DAChC,YAAE,qCAAqC,MAAM,GAChD;AAEJ;",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport type { FieldMapping } from '../../../../data_sync/lib/adapter'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useCustomFieldDefs } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { StatusBadge, type StatusMap } from '@open-mercato/ui/primitives/status-badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@open-mercato/ui/primitives/table'\nimport { AlertTriangle, CheckCircle2, Play, RefreshCw, Upload, XCircle } from 'lucide-react'\nimport {\n buildPeopleSuggestedMapping,\n buildPeopleTargetOptions,\n buildSuggestedMappingSignature,\n findMappingTargetOption,\n SYNC_EXCEL_PEOPLE_CUSTOM_FIELD_ENTITY_IDS,\n type MappingTargetOption,\n type SuggestedMapping,\n} from './target-options'\n\ntype SyncExcelIntegrationContext = {\n formId?: string\n integrationDetailWidgetSpotId?: string\n integrationId?: string\n activeTab?: string\n state?: {\n isEnabled?: boolean\n } | null\n refreshDetail?: () => Promise<void>\n refreshLogs?: () => Promise<void>\n refreshHealthSnapshot?: () => Promise<void>\n}\n\ntype SyncExcelIntegrationData = {\n state?: {\n isEnabled?: boolean\n } | null\n}\n\ntype PreviewRow = Record<string, string | null>\n\ntype UploadResponse = {\n uploadId: string\n filename: string\n mimeType: string\n fileSize: number\n entityType: 'customers.person'\n headers: string[]\n sampleRows: PreviewRow[]\n totalRows: number\n suggestedMapping: SuggestedMapping\n}\n\ntype ImportResponse = {\n runId: string\n progressJobId: string | null\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'\n}\n\ntype SyncRunDetail = {\n id: string\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'paused'\n createdCount: number\n updatedCount: number\n skippedCount: number\n failedCount: number\n lastError: string | null\n progressJobId: string | null\n progressJob: {\n id: string\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'\n progressPercent: number\n processedCount: number\n totalCount: number | null\n etaSeconds: number | null\n } | null\n}\n\ntype MappingRowState = {\n sourceColumn: string\n targetField: string\n}\n\ntype MappingDiagnostics = {\n duplicateTargets: string[]\n hasNameTarget: boolean\n hasIdentityTarget: boolean\n mappedCount: number\n unmappedCount: number\n}\n\ntype PersistedSessionSnapshot = {\n uploadId: string\n filename: string\n preview: UploadResponse | null\n mappingRows: MappingRowState[]\n matchStrategy: SuggestedMapping['matchStrategy']\n runId: string | null\n progressJobId: string | null\n}\n\nconst ENTITY_TYPE = 'customers.person' as const\nconst SESSION_STORAGE_PREFIX = 'om:sync_excel:session'\nconst RUN_POLL_INTERVAL_MS = 4_000\n\nconst SELECT_CLASS_NAME = 'flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'\n\nfunction buildMappingRows(headers: string[], suggestedMapping: SuggestedMapping): MappingRowState[] {\n const mappedFields = new Map(suggestedMapping.fields.map((field) => [field.externalField, field.localField]))\n return headers.map((header) => ({\n sourceColumn: header,\n targetField: mappedFields.get(header) ?? '',\n }))\n}\n\nfunction buildFieldMapping(sourceColumn: string, targetField: string, targetOptions: MappingTargetOption[]): FieldMapping {\n const matchedOption = findMappingTargetOption(targetOptions, targetField)\n const mappingKind = matchedOption?.mappingKind ?? (targetField.startsWith('cf:') ? 'custom_field' : 'core')\n const dedupeRole = matchedOption?.dedupeRole\n\n return {\n externalField: sourceColumn,\n localField: targetField,\n mappingKind,\n ...(dedupeRole ? { dedupeRole } : {}),\n }\n}\n\nfunction inferMatchStrategy(rows: MappingRowState[]): SuggestedMapping['matchStrategy'] {\n if (rows.some((row) => row.targetField === 'person.externalId')) return 'externalId'\n if (rows.some((row) => row.targetField === 'person.primaryEmail')) return 'email'\n return 'custom'\n}\n\nfunction buildMapping(\n rows: MappingRowState[],\n matchStrategy: SuggestedMapping['matchStrategy'],\n targetOptions: MappingTargetOption[],\n): SuggestedMapping {\n const mappedRows = rows.filter((row) => row.targetField.trim().length > 0)\n const fields = mappedRows.map((row) => buildFieldMapping(row.sourceColumn, row.targetField, targetOptions))\n const resolvedMatchStrategy = matchStrategy === 'custom' ? inferMatchStrategy(rows) : matchStrategy\n const matchField = resolvedMatchStrategy === 'externalId'\n ? 'person.externalId'\n : resolvedMatchStrategy === 'email'\n ? 'person.primaryEmail'\n : undefined\n\n return {\n entityType: ENTITY_TYPE,\n matchStrategy: resolvedMatchStrategy,\n ...(matchField ? { matchField } : {}),\n fields,\n unmappedColumns: rows.filter((row) => row.targetField.trim().length === 0).map((row) => row.sourceColumn),\n }\n}\n\nfunction buildDiagnostics(rows: MappingRowState[]): MappingDiagnostics {\n const duplicateTargets = Array.from(\n rows\n .filter((row) => row.targetField.trim().length > 0)\n .reduce((accumulator, row) => {\n const nextCount = (accumulator.get(row.targetField) ?? 0) + 1\n accumulator.set(row.targetField, nextCount)\n return accumulator\n }, new Map<string, number>())\n .entries(),\n )\n .filter(([, count]) => count > 1)\n .map(([target]) => target)\n\n const hasNameTarget = rows.some((row) => row.targetField === 'person.lastName' || row.targetField === 'person.displayName')\n const hasIdentityTarget = rows.some((row) => row.targetField === 'person.externalId' || row.targetField === 'person.primaryEmail')\n const mappedCount = rows.filter((row) => row.targetField.trim().length > 0).length\n\n return {\n duplicateTargets,\n hasNameTarget,\n hasIdentityTarget,\n mappedCount,\n unmappedCount: rows.length - mappedCount,\n }\n}\n\nfunction formatTargetLabel(\n targetField: string,\n t: ReturnType<typeof useT>,\n targetOptions: MappingTargetOption[],\n): string {\n const option = findMappingTargetOption(targetOptions, targetField)\n if (!option) return targetField\n return option.labelKey ? t(option.labelKey, option.fallback) : option.fallback\n}\n\nfunction getMatchStrategyLabel(\n matchStrategy: SuggestedMapping['matchStrategy'],\n t: ReturnType<typeof useT>,\n): string {\n if (matchStrategy === 'externalId') {\n return t('sync_excel.widget.matchStrategy.externalId', 'Source record ID (Recommended)')\n }\n if (matchStrategy === 'email') {\n return t('sync_excel.widget.matchStrategy.email', 'Email address')\n }\n return t('sync_excel.widget.matchStrategy.custom', 'Custom matching')\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nfunction readApiErrorMessage(result: Record<string, unknown> | null | undefined, fallback: string): string {\n const value = result?.error\n if (typeof value === 'string' && value.trim().length > 0) return value\n return fallback\n}\n\nfunction normalizeStatus(status: SyncRunDetail['status'] | ImportResponse['status'] | null | undefined): 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'idle' {\n if (status === 'pending' || status === 'running' || status === 'completed' || status === 'failed' || status === 'cancelled') {\n return status\n }\n return 'idle'\n}\n\nfunction getSessionStorageKey(integrationId: string | undefined): string {\n return `${SESSION_STORAGE_PREFIX}:${integrationId ?? 'sync_excel'}`\n}\n\nfunction readPersistedSessionSnapshot(integrationId: string | undefined): PersistedSessionSnapshot | null {\n if (typeof window === 'undefined') return null\n try {\n const raw = window.sessionStorage.getItem(getSessionStorageKey(integrationId))\n if (!raw) return null\n const parsed = JSON.parse(raw) as Partial<PersistedSessionSnapshot> | null\n if (!parsed || typeof parsed !== 'object') return null\n if (typeof parsed.uploadId !== 'string' || parsed.uploadId.trim().length === 0) return null\n if (typeof parsed.filename !== 'string') return null\n if (!Array.isArray(parsed.mappingRows)) return null\n if (parsed.matchStrategy !== 'externalId' && parsed.matchStrategy !== 'email' && parsed.matchStrategy !== 'custom') {\n return null\n }\n const preview = (\n parsed.preview\n && typeof parsed.preview === 'object'\n && typeof parsed.preview.uploadId === 'string'\n && parsed.preview.uploadId.trim().length > 0\n && typeof parsed.preview.filename === 'string'\n && Array.isArray(parsed.preview.headers)\n && Array.isArray(parsed.preview.sampleRows)\n && typeof parsed.preview.totalRows === 'number'\n && typeof parsed.preview.fileSize === 'number'\n && parsed.preview.entityType === ENTITY_TYPE\n && parsed.preview.suggestedMapping\n )\n ? parsed.preview as UploadResponse\n : null\n\n return {\n uploadId: parsed.uploadId,\n filename: parsed.filename,\n preview,\n mappingRows: parsed.mappingRows\n .filter((row): row is MappingRowState => (\n Boolean(row)\n && typeof row.sourceColumn === 'string'\n && typeof row.targetField === 'string'\n )),\n matchStrategy: parsed.matchStrategy,\n runId: typeof parsed.runId === 'string' && parsed.runId.trim().length > 0 ? parsed.runId : null,\n progressJobId: typeof parsed.progressJobId === 'string' && parsed.progressJobId.trim().length > 0 ? parsed.progressJobId : null,\n }\n } catch {\n return null\n }\n}\n\nfunction writePersistedSessionSnapshot(\n integrationId: string | undefined,\n snapshot: PersistedSessionSnapshot | null,\n): void {\n if (typeof window === 'undefined') return\n try {\n const key = getSessionStorageKey(integrationId)\n if (!snapshot) {\n window.sessionStorage.removeItem(key)\n return\n }\n window.sessionStorage.setItem(key, JSON.stringify(snapshot))\n } catch {\n // sessionStorage can be unavailable in private/locked-down browser contexts; persistence is best-effort.\n }\n}\n\nexport default function SyncExcelUploadConfigWidget({\n context,\n data,\n}: InjectionWidgetComponentProps<SyncExcelIntegrationContext, SyncExcelIntegrationData>) {\n const pathname = usePathname()\n const router = useRouter()\n const searchParams = useSearchParams()\n const t = useT()\n const { data: customFieldDefs = [] } = useCustomFieldDefs([...SYNC_EXCEL_PEOPLE_CUSTOM_FIELD_ENTITY_IDS])\n const [selectedFile, setSelectedFile] = React.useState<File | null>(null)\n const [preview, setPreview] = React.useState<UploadResponse | null>(null)\n const [mappingRows, setMappingRows] = React.useState<MappingRowState[]>([])\n const [matchStrategy, setMatchStrategy] = React.useState<SuggestedMapping['matchStrategy']>('custom')\n const [isMappingDirty, setIsMappingDirty] = React.useState(false)\n const [runDetail, setRunDetail] = React.useState<SyncRunDetail | null>(null)\n const [runId, setRunId] = React.useState<string | null>(null)\n const [progressJobId, setProgressJobId] = React.useState<string | null>(null)\n const [isUploading, setIsUploading] = React.useState(false)\n const [isRefreshingPreview, setIsRefreshingPreview] = React.useState(false)\n const [isStartingImport, setIsStartingImport] = React.useState(false)\n const [isCancelling, setIsCancelling] = React.useState(false)\n const [isRestoringSession, setIsRestoringSession] = React.useState(true)\n const mutationContextId = `${context.formId ?? 'sync-excel'}:sync-excel-import`\n const lastAppliedSuggestionSignatureRef = React.useRef<string | null>(null)\n const restoringSnapshotRef = React.useRef<PersistedSessionSnapshot | null>(null)\n const lastRestoreRequestKeyRef = React.useRef<string | null>(null)\n const { runMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: mutationContextId,\n spotId: context.integrationDetailWidgetSpotId,\n })\n\n const targetOptions = React.useMemo(\n () => buildPeopleTargetOptions(customFieldDefs),\n [customFieldDefs],\n )\n const previewSuggestion = React.useMemo(\n () => preview ? buildPeopleSuggestedMapping(preview.headers, preview.suggestedMapping, customFieldDefs) : null,\n [customFieldDefs, preview],\n )\n const resolvedIntegrationEnabled = data?.state?.isEnabled ?? context.state?.isEnabled ?? true\n const diagnostics = React.useMemo(() => buildDiagnostics(mappingRows), [mappingRows])\n const canStartImport = Boolean(preview)\n && diagnostics.duplicateTargets.length === 0\n && diagnostics.hasNameTarget\n && diagnostics.hasIdentityTarget\n && mappingRows.some((row) => row.targetField.trim().length > 0)\n const importStatus = normalizeStatus(runDetail?.status ?? (runId ? 'pending' : null))\n const progressValue = runDetail?.progressJob?.progressPercent ?? 0\n const isRunActive = importStatus === 'pending' || importStatus === 'running'\n const searchParamsKey = searchParams?.toString() ?? ''\n const uploadIdFromUrl = searchParams?.get('uploadId') ?? null\n const runIdFromUrl = searchParams?.get('runId') ?? null\n const shouldHideInteractiveContent = isRestoringSession && Boolean(uploadIdFromUrl)\n const hasExistingRunForCurrentUpload = Boolean(preview?.uploadId && runId)\n const hasSafeDedupeStrategy = matchStrategy === 'externalId' || matchStrategy === 'email'\n\n const replaceQueryParams = React.useCallback((updates: Record<string, string | null | undefined>) => {\n const params = new URLSearchParams(searchParamsKey)\n for (const [key, value] of Object.entries(updates)) {\n if (typeof value === 'string' && value.trim().length > 0) {\n params.set(key, value)\n } else {\n params.delete(key)\n }\n }\n const query = params.toString()\n if (query === searchParamsKey) return\n router.replace(query ? `${pathname}?${query}` : pathname)\n }, [pathname, router, searchParamsKey])\n\n const clearPersistedSession = React.useCallback(() => {\n writePersistedSessionSnapshot(context.integrationId, null)\n replaceQueryParams({ uploadId: null, runId: null })\n }, [context.integrationId, replaceQueryParams])\n\n const persistCurrentSession = React.useCallback((next?: Partial<PersistedSessionSnapshot>) => {\n const persistedPreview = next?.preview ?? preview ?? null\n const uploadId = next?.uploadId ?? persistedPreview?.uploadId ?? null\n const filename = next?.filename ?? persistedPreview?.filename ?? null\n if (!uploadId || !filename) {\n writePersistedSessionSnapshot(context.integrationId, null)\n return\n }\n\n writePersistedSessionSnapshot(context.integrationId, {\n uploadId,\n filename,\n preview: persistedPreview,\n mappingRows: next?.mappingRows ?? mappingRows,\n matchStrategy: next?.matchStrategy ?? matchStrategy,\n runId: next?.runId ?? runId,\n progressJobId: next?.progressJobId ?? progressJobId,\n })\n }, [context.integrationId, mappingRows, matchStrategy, preview, progressJobId, runId])\n\n const syncPreviewState = React.useCallback((nextPreview: UploadResponse, options?: {\n preserveManualState?: boolean\n restoredSnapshot?: PersistedSessionSnapshot | null\n }) => {\n const nextSuggestedMapping = buildPeopleSuggestedMapping(nextPreview.headers, nextPreview.suggestedMapping, customFieldDefs)\n setPreview(nextPreview)\n\n const restoredSnapshot = options?.restoredSnapshot\n const shouldRestoreSnapshot = Boolean(\n restoredSnapshot\n && restoredSnapshot.uploadId === nextPreview.uploadId\n && restoredSnapshot.mappingRows.length > 0,\n )\n\n if (shouldRestoreSnapshot) {\n setMappingRows(restoredSnapshot!.mappingRows)\n setMatchStrategy(restoredSnapshot!.matchStrategy)\n setRunId(restoredSnapshot!.runId)\n setProgressJobId(restoredSnapshot!.progressJobId)\n setIsMappingDirty(true)\n lastAppliedSuggestionSignatureRef.current = null\n persistCurrentSession({\n uploadId: nextPreview.uploadId,\n filename: nextPreview.filename,\n preview: nextPreview,\n mappingRows: restoredSnapshot!.mappingRows,\n matchStrategy: restoredSnapshot!.matchStrategy,\n runId: restoredSnapshot!.runId,\n progressJobId: restoredSnapshot!.progressJobId,\n })\n replaceQueryParams({\n uploadId: nextPreview.uploadId,\n runId: restoredSnapshot!.runId,\n })\n return\n }\n\n if (options?.preserveManualState && preview?.uploadId === nextPreview.uploadId) {\n persistCurrentSession({\n uploadId: nextPreview.uploadId,\n filename: nextPreview.filename,\n preview: nextPreview,\n })\n replaceQueryParams({ uploadId: nextPreview.uploadId })\n return\n }\n\n const nextRows = buildMappingRows(nextPreview.headers, nextSuggestedMapping)\n setMappingRows(nextRows)\n setMatchStrategy(nextSuggestedMapping.matchStrategy)\n setIsMappingDirty(false)\n lastAppliedSuggestionSignatureRef.current = buildSuggestedMappingSignature(nextPreview.headers, nextSuggestedMapping)\n persistCurrentSession({\n uploadId: nextPreview.uploadId,\n filename: nextPreview.filename,\n preview: nextPreview,\n mappingRows: nextRows,\n matchStrategy: nextSuggestedMapping.matchStrategy,\n runId: null,\n progressJobId: null,\n })\n replaceQueryParams({\n uploadId: nextPreview.uploadId,\n runId: null,\n })\n }, [customFieldDefs, persistCurrentSession, preview?.uploadId, replaceQueryParams])\n\n React.useEffect(() => {\n if (!preview || !previewSuggestion) return\n if (isMappingDirty) return\n const nextSignature = buildSuggestedMappingSignature(preview.headers, previewSuggestion)\n if (lastAppliedSuggestionSignatureRef.current === nextSignature) return\n setMappingRows(buildMappingRows(preview.headers, previewSuggestion))\n setMatchStrategy(previewSuggestion.matchStrategy)\n lastAppliedSuggestionSignatureRef.current = nextSignature\n }, [isMappingDirty, preview, previewSuggestion])\n\n const refreshRunDetail = React.useCallback(async (currentRunId: string) => {\n const call = await apiCall<SyncRunDetail>(`/api/data_sync/runs/${encodeURIComponent(currentRunId)}`, undefined, { fallback: null })\n if (call.ok && call.result) {\n setRunDetail(call.result)\n setProgressJobId(call.result.progressJobId ?? null)\n persistCurrentSession({\n runId: currentRunId,\n progressJobId: call.result.progressJobId ?? null,\n })\n return call.result\n }\n return null\n }, [persistCurrentSession])\n\n React.useEffect(() => {\n if (!preview?.uploadId) return\n persistCurrentSession()\n }, [mappingRows, matchStrategy, persistCurrentSession, preview?.uploadId, progressJobId, runId])\n\n React.useEffect(() => {\n const persistedSnapshot = readPersistedSessionSnapshot(context.integrationId)\n const resolvedUploadId = uploadIdFromUrl ?? persistedSnapshot?.uploadId ?? null\n const resolvedRunId = runIdFromUrl ?? persistedSnapshot?.runId ?? null\n\n if (!resolvedUploadId) {\n restoringSnapshotRef.current = null\n lastRestoreRequestKeyRef.current = null\n setIsRestoringSession(false)\n return\n }\n\n if (!uploadIdFromUrl && persistedSnapshot?.uploadId === resolvedUploadId) {\n replaceQueryParams({\n uploadId: resolvedUploadId,\n runId: resolvedRunId,\n })\n }\n\n if (preview?.uploadId === resolvedUploadId && mappingRows.length > 0) {\n if (resolvedRunId && runId !== resolvedRunId) {\n setRunId(resolvedRunId)\n }\n setIsRestoringSession(false)\n return\n }\n\n const restoreRequestKey = `${resolvedUploadId}:${resolvedRunId ?? ''}`\n if (lastRestoreRequestKeyRef.current === restoreRequestKey) {\n setIsRestoringSession(false)\n return\n }\n\n lastRestoreRequestKeyRef.current = restoreRequestKey\n\n let cancelled = false\n restoringSnapshotRef.current = persistedSnapshot\n const persistedPreview = restoringSnapshotRef.current?.preview?.uploadId === resolvedUploadId\n ? restoringSnapshotRef.current.preview\n : null\n\n if (persistedPreview) {\n syncPreviewState(persistedPreview, {\n restoredSnapshot: restoringSnapshotRef.current,\n })\n setIsRestoringSession(false)\n } else {\n setIsRestoringSession(true)\n }\n\n const restoreSession = async () => {\n const call = await apiCall<UploadResponse>(\n `/api/sync_excel/preview?uploadId=${encodeURIComponent(resolvedUploadId)}&entityType=${encodeURIComponent(ENTITY_TYPE)}`,\n undefined,\n { fallback: null },\n )\n\n if (cancelled) return\n\n if (!call.ok || !call.result) {\n setPreview(null)\n setMappingRows([])\n setMatchStrategy('custom')\n setRunId(null)\n setRunDetail(null)\n setProgressJobId(null)\n clearPersistedSession()\n lastRestoreRequestKeyRef.current = null\n setIsRestoringSession(false)\n return\n }\n\n syncPreviewState(call.result, {\n restoredSnapshot: restoringSnapshotRef.current,\n })\n\n if (resolvedRunId && !(restoringSnapshotRef.current?.runId)) {\n setRunId(resolvedRunId)\n }\n\n setIsRestoringSession(false)\n }\n\n void restoreSession()\n\n return () => {\n cancelled = true\n }\n }, [clearPersistedSession, context.integrationId, mappingRows.length, preview?.uploadId, replaceQueryParams, runId, runIdFromUrl, syncPreviewState, uploadIdFromUrl])\n\n React.useEffect(() => {\n if (!runId) return\n\n let cancelled = false\n let timeoutId: ReturnType<typeof setTimeout> | null = null\n\n const poll = async () => {\n const detail = await refreshRunDetail(runId)\n if (cancelled) return\n if (context.activeTab === 'logs') {\n await context.refreshLogs?.()\n }\n if (context.activeTab === 'health' || normalizeStatus(detail?.status) !== 'idle') {\n await context.refreshHealthSnapshot?.()\n }\n const status = normalizeStatus(detail?.status)\n if (status === 'pending' || status === 'running') {\n timeoutId = setTimeout(() => {\n void poll()\n }, RUN_POLL_INTERVAL_MS)\n }\n }\n\n void poll()\n\n return () => {\n cancelled = true\n if (timeoutId) clearTimeout(timeoutId)\n }\n }, [context.activeTab, context.refreshHealthSnapshot, context.refreshLogs, refreshRunDetail, runId])\n\n const handleUpload = React.useCallback(async () => {\n if (!selectedFile) {\n flash(t('sync_excel.widget.messages.fileRequired', 'Select a CSV file first.'), 'error')\n return\n }\n\n setIsUploading(true)\n try {\n const formData = new FormData()\n formData.set('file', selectedFile)\n formData.set('entityType', ENTITY_TYPE)\n\n const call = await runMutation({\n operation: () => apiCall<UploadResponse>('/api/sync_excel/upload', {\n method: 'POST',\n body: formData,\n }, { fallback: null }),\n mutationPayload: {\n integrationId: context.integrationId ?? 'sync_excel',\n entityType: ENTITY_TYPE,\n fileName: selectedFile.name,\n },\n context: {\n ...context,\n operation: 'create',\n actionId: 'sync-excel-upload',\n },\n })\n\n if (!call.ok || !call.result) {\n flash(\n readApiErrorMessage(call.result as Record<string, unknown> | null, t('sync_excel.widget.messages.uploadError', 'Failed to upload CSV file.')),\n 'error',\n )\n return\n }\n\n syncPreviewState(call.result)\n setSelectedFile(null)\n setRunId(null)\n setRunDetail(null)\n setProgressJobId(null)\n flash(t('sync_excel.widget.messages.uploadSuccess', 'CSV preview is ready.'), 'success')\n } catch {\n flash(t('sync_excel.widget.messages.uploadError', 'Failed to upload CSV file.'), 'error')\n } finally {\n setIsUploading(false)\n }\n }, [context, runMutation, selectedFile, syncPreviewState, t])\n\n const handleRefreshPreview = React.useCallback(async () => {\n if (!preview) return\n setIsRefreshingPreview(true)\n try {\n const call = await apiCall<UploadResponse>(\n `/api/sync_excel/preview?uploadId=${encodeURIComponent(preview.uploadId)}&entityType=${encodeURIComponent(ENTITY_TYPE)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n flash(t('sync_excel.widget.messages.previewError', 'Failed to refresh preview.'), 'error')\n return\n }\n syncPreviewState(call.result, { preserveManualState: true })\n flash(t('sync_excel.widget.messages.previewRefreshed', 'Preview refreshed.'), 'success')\n } catch {\n flash(t('sync_excel.widget.messages.previewError', 'Failed to refresh preview.'), 'error')\n } finally {\n setIsRefreshingPreview(false)\n }\n }, [preview, syncPreviewState, t])\n\n const handleTargetFieldChange = React.useCallback((sourceColumn: string, targetField: string) => {\n setIsMappingDirty(true)\n setMappingRows((current) => current.map((row) => row.sourceColumn === sourceColumn ? { ...row, targetField } : row))\n }, [])\n\n const handleResetToSuggested = React.useCallback(() => {\n if (!preview || !previewSuggestion) return\n setMappingRows(buildMappingRows(preview.headers, previewSuggestion))\n setMatchStrategy(previewSuggestion.matchStrategy)\n setIsMappingDirty(false)\n lastAppliedSuggestionSignatureRef.current = buildSuggestedMappingSignature(preview.headers, previewSuggestion)\n flash(t('sync_excel.widget.messages.mappingReset', 'Suggested mapping restored.'), 'success')\n }, [preview, previewSuggestion, t])\n\n const handleStartImport = React.useCallback(async () => {\n if (!preview) return\n\n const mapping = buildMapping(mappingRows, matchStrategy, targetOptions)\n setIsStartingImport(true)\n\n try {\n const call = await runMutation({\n operation: () => apiCall<ImportResponse>('/api/sync_excel/import', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n uploadId: preview.uploadId,\n entityType: ENTITY_TYPE,\n mapping,\n }),\n }, { fallback: null }),\n mutationPayload: {\n integrationId: context.integrationId ?? 'sync_excel',\n uploadId: preview.uploadId,\n entityType: ENTITY_TYPE,\n mapping,\n },\n context: {\n ...context,\n operation: 'create',\n actionId: 'sync-excel-start-import',\n },\n })\n\n if (!call.ok || !call.result) {\n flash(\n readApiErrorMessage(call.result as Record<string, unknown> | null, t('sync_excel.widget.messages.importError', 'Failed to start import run.')),\n 'error',\n )\n return\n }\n\n setRunId(call.result.runId)\n setProgressJobId(call.result.progressJobId)\n replaceQueryParams({\n uploadId: preview.uploadId,\n runId: call.result.runId,\n })\n persistCurrentSession({\n uploadId: preview.uploadId,\n filename: preview.filename,\n runId: call.result.runId,\n progressJobId: call.result.progressJobId,\n })\n flash(t('sync_excel.widget.messages.importStarted', 'Import run started.'), 'success')\n await refreshRunDetail(call.result.runId)\n await context.refreshLogs?.()\n await context.refreshHealthSnapshot?.()\n } catch {\n flash(t('sync_excel.widget.messages.importError', 'Failed to start import run.'), 'error')\n } finally {\n setIsStartingImport(false)\n }\n }, [context, mappingRows, matchStrategy, persistCurrentSession, preview, refreshRunDetail, replaceQueryParams, runMutation, t, targetOptions])\n\n const handleCancelRun = React.useCallback(async () => {\n if (!runId) return\n\n setIsCancelling(true)\n try {\n const call = await runMutation({\n operation: () => apiCall<{ ok?: boolean }>(`/api/data_sync/runs/${encodeURIComponent(runId)}/cancel`, {\n method: 'POST',\n }, { fallback: null }),\n mutationPayload: {\n integrationId: context.integrationId ?? 'sync_excel',\n runId,\n },\n context: {\n ...context,\n operation: 'update',\n actionId: 'sync-excel-cancel-import',\n },\n })\n\n if (!call.ok) {\n flash(t('sync_excel.widget.messages.cancelError', 'Failed to cancel import run.'), 'error')\n return\n }\n\n flash(t('sync_excel.widget.messages.cancelSuccess', 'Import run cancelled.'), 'success')\n await refreshRunDetail(runId)\n await context.refreshLogs?.()\n await context.refreshHealthSnapshot?.()\n } catch {\n flash(t('sync_excel.widget.messages.cancelError', 'Failed to cancel import run.'), 'error')\n } finally {\n setIsCancelling(false)\n }\n }, [context, refreshRunDetail, runId, runMutation, t])\n\n const handleRefreshAll = React.useCallback(async () => {\n if (!runId) return\n await refreshRunDetail(runId)\n await context.refreshLogs?.()\n await context.refreshHealthSnapshot?.()\n }, [context, refreshRunDetail, runId])\n\n const firstSampleRow = preview?.sampleRows[0] ?? null\n\n return (\n <div className=\"space-y-6\">\n {isRestoringSession ? (\n <Card>\n <CardContent className=\"flex items-center gap-3 py-6\">\n <Spinner className=\"size-4\" />\n <div className=\"text-sm text-muted-foreground\">\n {t('sync_excel.widget.messages.restoringSession', 'Restoring the last CSV session...')}\n </div>\n </CardContent>\n </Card>\n ) : null}\n\n {!resolvedIntegrationEnabled ? (\n <StatusAlert\n variant=\"warning\"\n title={t('sync_excel.widget.disabled.title', 'Integration disabled')}\n message={t('sync_excel.widget.disabled.message', 'Enable this integration to upload CSV files and start imports.')}\n />\n ) : null}\n\n {!shouldHideInteractiveContent ? (\n <Card>\n <CardHeader>\n <CardTitle>{t('sync_excel.widget.upload.title', 'Upload CSV')}</CardTitle>\n <CardDescription>\n {t('sync_excel.widget.upload.description', 'Upload a CSV file, review the suggested people mapping, and start a background import run.')}\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"sync-excel-upload-file\">{t('sync_excel.widget.upload.fileLabel', 'CSV file')}</Label>\n <Input\n id=\"sync-excel-upload-file\"\n type=\"file\"\n accept=\".csv,text/csv\"\n onChange={(event) => setSelectedFile(event.target.files?.[0] ?? null)}\n disabled={isUploading || isRunActive || isRestoringSession}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <Button type=\"button\" onClick={() => void handleUpload()} disabled={!selectedFile || isUploading || isRunActive || isRestoringSession}>\n {isUploading ? <Spinner className=\"mr-2 size-4\" /> : <Upload className=\"mr-2 size-4\" />}\n {isUploading\n ? t('sync_excel.widget.actions.uploadPending', 'Uploading...')\n : t('sync_excel.widget.actions.upload', 'Upload and preview')}\n </Button>\n {preview ? (\n <Button type=\"button\" variant=\"outline\" onClick={() => void handleRefreshPreview()} disabled={isRefreshingPreview || isUploading || isRunActive || isRestoringSession}>\n {isRefreshingPreview ? <Spinner className=\"mr-2 size-4\" /> : <RefreshCw className=\"mr-2 size-4\" />}\n {isRefreshingPreview\n ? t('sync_excel.widget.actions.refreshPending', 'Refreshing...')\n : t('sync_excel.widget.actions.refresh', 'Refresh preview')}\n </Button>\n ) : null}\n </div>\n\n {selectedFile ? (\n <div className=\"text-sm text-muted-foreground\">\n {t('sync_excel.widget.upload.selectedFile', 'Selected file')}: <span className=\"font-medium text-foreground\">{selectedFile.name}</span>\n </div>\n ) : null}\n </CardContent>\n </Card>\n ) : null}\n\n {preview && !shouldHideInteractiveContent ? (\n <Card>\n <CardHeader>\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"space-y-1\">\n <CardTitle>{t('sync_excel.widget.preview.title', 'Preview and mapping')}</CardTitle>\n <CardDescription>\n {t('sync_excel.widget.preview.description', 'Every source column stays visible here. Leave a target empty to keep the column unmapped for now.')}\n </CardDescription>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Badge variant=\"outline\">{preview.filename}</Badge>\n <Badge variant=\"outline\">{t('sync_excel.widget.preview.rows', '{count} rows', { count: preview.totalRows })}</Badge>\n <Badge variant=\"outline\">{formatFileSize(preview.fileSize)}</Badge>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n <MetricCard\n label={t('sync_excel.widget.metrics.mapped', 'Mapped columns')}\n value={String(diagnostics.mappedCount)}\n />\n <MetricCard\n label={t('sync_excel.widget.metrics.unmapped', 'Unmapped columns')}\n value={String(diagnostics.unmappedCount)}\n />\n <MetricCard\n label={t('sync_excel.widget.metrics.identity', 'Identity strategy')}\n value={getMatchStrategyLabel(matchStrategy, t)}\n />\n <MetricCard\n label={t('sync_excel.widget.metrics.entity', 'Target entity')}\n value={ENTITY_TYPE}\n />\n </div>\n\n <div className=\"grid gap-4 lg:grid-cols-[minmax(0,1fr)_280px]\">\n <div className=\"overflow-hidden rounded-lg border\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{t('sync_excel.widget.mapping.sourceColumn', 'Source column')}</TableHead>\n <TableHead>{t('sync_excel.widget.mapping.sampleValue', 'Sample value')}</TableHead>\n <TableHead>{t('sync_excel.widget.mapping.targetField', 'Target field')}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {mappingRows.map((row) => (\n <TableRow key={row.sourceColumn}>\n <TableCell className=\"font-medium\">{row.sourceColumn}</TableCell>\n <TableCell className=\"max-w-[260px] truncate text-muted-foreground\">\n {firstSampleRow?.[row.sourceColumn] ?? '\u2014'}\n </TableCell>\n <TableCell>\n <select\n className={SELECT_CLASS_NAME}\n value={row.targetField}\n onChange={(event) => handleTargetFieldChange(row.sourceColumn, event.target.value)}\n disabled={isStartingImport || isRunActive}\n >\n <option value=\"\">{t('sync_excel.mapping.targets.ignore', 'Leave unmapped')}</option>\n {targetOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.labelKey ? t(option.labelKey, option.fallback) : option.fallback}\n </option>\n ))}\n </select>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"sync-excel-match-strategy\">\n {t('sync_excel.widget.matchStrategy.label', 'How to match existing people')}\n </Label>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'sync_excel.widget.matchStrategy.help',\n 'This decides whether imported rows update existing people or create new ones.',\n )}\n </p>\n <select\n id=\"sync-excel-match-strategy\"\n className={SELECT_CLASS_NAME}\n value={matchStrategy}\n onChange={(event) => {\n setIsMappingDirty(true)\n setMatchStrategy(event.target.value as SuggestedMapping['matchStrategy'])\n }}\n disabled={isStartingImport || isRunActive}\n >\n <option value=\"externalId\">\n {t('sync_excel.widget.matchStrategy.externalId', 'Source record ID (Recommended)')}\n </option>\n <option value=\"email\">{t('sync_excel.widget.matchStrategy.email', 'Email address')}</option>\n <option value=\"custom\">{t('sync_excel.widget.matchStrategy.custom', 'Custom matching')}</option>\n </select>\n <p className=\"text-sm text-muted-foreground\">\n {matchStrategy === 'externalId'\n ? t(\n 'sync_excel.widget.matchStrategy.externalIdDescription',\n 'Best for repeated imports from the same source. Future imports update the same people instead of creating duplicates.',\n )\n : matchStrategy === 'email'\n ? t(\n 'sync_excel.widget.matchStrategy.emailDescription',\n 'Matches people by email. Works best when each person has one reliable, unique email address.',\n )\n : t(\n 'sync_excel.widget.matchStrategy.customDescription',\n 'Advanced option. Use only if you know how existing records should be identified in this import.',\n )}\n </p>\n </div>\n\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleResetToSuggested} disabled={isStartingImport || isRunActive}>\n <RefreshCw className=\"mr-2 size-4\" />\n {t('sync_excel.widget.actions.reset', 'Reset to suggestion')}\n </Button>\n <Button type=\"button\" onClick={() => void handleStartImport()} disabled={!canStartImport || isStartingImport || isRunActive}>\n {isStartingImport ? <Spinner className=\"mr-2 size-4\" /> : <Play className=\"mr-2 size-4\" />}\n {isStartingImport\n ? t('sync_excel.widget.actions.startPending', 'Starting...')\n : t('sync_excel.widget.actions.start', 'Start import')}\n </Button>\n </div>\n\n {matchStrategy === 'custom' ? (\n <StatusAlert\n variant=\"warning\"\n title={t('sync_excel.widget.matchStrategy.customWarningTitle', 'Use custom matching with care')}\n message={t(\n 'sync_excel.widget.matchStrategy.customWarningMessage',\n 'Custom matching may create duplicates if the mapped fields do not uniquely identify a person.',\n )}\n />\n ) : null}\n\n {hasExistingRunForCurrentUpload ? (\n <StatusAlert\n variant=\"warning\"\n title={t('sync_excel.widget.validation.reimportTitle', 'This upload already has a run')}\n message={t(\n 'sync_excel.widget.validation.reimportMessage',\n 'Starting another import from the same uploaded CSV may create duplicates unless your matching strategy reliably identifies existing people.',\n )}\n />\n ) : null}\n\n {!hasSafeDedupeStrategy ? (\n <StatusAlert\n variant=\"warning\"\n title={t('sync_excel.widget.validation.duplicateRiskTitle', 'Duplicate risk')}\n message={t(\n 'sync_excel.widget.validation.duplicateRiskMessage',\n 'This mapping does not use the recommended External ID or Email matching strategy. Re-importing the same dataset may create duplicate people.',\n )}\n />\n ) : null}\n\n {diagnostics.duplicateTargets.length > 0 ? (\n <StatusAlert\n variant=\"error\"\n title={t('sync_excel.widget.validation.duplicateTargetsTitle', 'Duplicate target fields')}\n message={t(\n 'sync_excel.widget.validation.duplicateTargetsMessage',\n 'Each target field can only be mapped once in this foundation slice: {targets}',\n { targets: diagnostics.duplicateTargets.map((target) => formatTargetLabel(target, t, targetOptions)).join(', ') },\n )}\n />\n ) : null}\n\n {!diagnostics.hasIdentityTarget ? (\n <StatusAlert\n variant=\"warning\"\n title={t('sync_excel.widget.validation.identityTitle', 'Map an identity field')}\n message={t('sync_excel.widget.validation.identityMessage', 'Map either External ID or Primary email so reimports can match existing people.')}\n />\n ) : null}\n\n {!diagnostics.hasNameTarget ? (\n <StatusAlert\n variant=\"warning\"\n title={t('sync_excel.widget.validation.nameTitle', 'Map a name field')}\n message={t('sync_excel.widget.validation.nameMessage', 'Map Last name or Display name so the importer can build person records.')}\n />\n ) : null}\n\n {diagnostics.hasIdentityTarget && diagnostics.hasNameTarget && diagnostics.duplicateTargets.length === 0 ? (\n <StatusAlert\n variant=\"info\"\n title={t('sync_excel.widget.validation.readyTitle', 'Ready to import')}\n message={t('sync_excel.widget.validation.readyMessage', 'The current mapping is valid for the first customers.person slice.')}\n />\n ) : null}\n </div>\n </div>\n </CardContent>\n </Card>\n ) : null}\n\n {runId && !shouldHideInteractiveContent ? (\n <Card>\n <CardHeader>\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"space-y-1\">\n <CardTitle>{t('sync_excel.widget.run.title', 'Import run status')}</CardTitle>\n <CardDescription>\n {t('sync_excel.widget.run.description', 'The import runs in the background through Data Sync and updates this view as progress changes.')}\n </CardDescription>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <RunStatusBadge status={importStatus} t={t} />\n {progressJobId ? <Badge variant=\"outline\">{progressJobId}</Badge> : null}\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-muted-foreground\">{t('sync_excel.widget.run.progressLabel', 'Progress')}</span>\n <span className=\"font-medium\">{progressValue}%</span>\n </div>\n <Progress value={progressValue} className=\"h-2.5\" />\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-5\">\n <MetricCard label={t('sync_excel.widget.run.metrics.created', 'Created')} value={String(runDetail?.createdCount ?? 0)} />\n <MetricCard label={t('sync_excel.widget.run.metrics.updated', 'Updated')} value={String(runDetail?.updatedCount ?? 0)} />\n <MetricCard label={t('sync_excel.widget.run.metrics.skipped', 'Skipped')} value={String(runDetail?.skippedCount ?? 0)} />\n <MetricCard label={t('sync_excel.widget.run.metrics.failed', 'Failed')} value={String(runDetail?.failedCount ?? 0)} />\n <MetricCard label={t('sync_excel.widget.run.metrics.processed', 'Processed')} value={String(runDetail?.progressJob?.processedCount ?? 0)} />\n </div>\n\n {runDetail?.lastError ? (\n <StatusAlert\n variant=\"error\"\n title={t('sync_excel.widget.run.errorTitle', 'Run error')}\n message={runDetail.lastError}\n />\n ) : null}\n\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={() => void handleRefreshAll()} disabled={isRefreshingPreview || isRestoringSession}>\n <RefreshCw className=\"mr-2 size-4\" />\n {t('sync_excel.widget.actions.refreshRun', 'Refresh run status')}\n </Button>\n {isRunActive ? (\n <Button type=\"button\" variant=\"destructive\" onClick={() => void handleCancelRun()} disabled={isCancelling}>\n {isCancelling ? <Spinner className=\"mr-2 size-4\" /> : <XCircle className=\"mr-2 size-4\" />}\n {isCancelling\n ? t('sync_excel.widget.actions.cancelPending', 'Cancelling...')\n : t('sync_excel.widget.actions.cancel', 'Cancel run')}\n </Button>\n ) : null}\n </div>\n </CardContent>\n </Card>\n ) : null}\n </div>\n )\n}\n\nfunction StatusAlert({\n variant,\n title,\n message,\n}: {\n variant: 'warning' | 'error' | 'info'\n title: string\n message: string\n}) {\n return (\n <Alert variant={variant === 'error' ? 'destructive' : variant}>\n <AlertTitle>{title}</AlertTitle>\n <AlertDescription>{message}</AlertDescription>\n </Alert>\n )\n}\n\nfunction MetricCard({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"rounded-lg border bg-muted/30 p-4\">\n <div className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">{label}</div>\n <div className=\"mt-2 text-lg font-semibold text-foreground\">{value}</div>\n </div>\n )\n}\n\ntype RunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'idle'\n\nexport const RUN_STATUS_BADGE_VARIANTS: StatusMap<RunStatus> = {\n completed: 'success',\n failed: 'error',\n cancelled: 'neutral',\n pending: 'info',\n running: 'info',\n idle: 'neutral',\n}\n\nexport function RunStatusBadge({\n status,\n t,\n}: {\n status: RunStatus\n t: ReturnType<typeof useT>\n}) {\n const variant = RUN_STATUS_BADGE_VARIANTS[status] ?? 'neutral'\n\n if (status === 'completed') {\n return (\n <StatusBadge variant={variant} className=\"gap-1.5\">\n <CheckCircle2 className=\"size-3.5\" />\n {t('sync_excel.widget.run.status.completed', 'Completed')}\n </StatusBadge>\n )\n }\n\n if (status === 'failed') {\n return (\n <StatusBadge variant={variant} className=\"gap-1.5\">\n <AlertTriangle className=\"size-3.5\" />\n {t('sync_excel.widget.run.status.failed', 'Failed')}\n </StatusBadge>\n )\n }\n\n if (status === 'cancelled') {\n return (\n <StatusBadge variant={variant} className=\"gap-1.5\">\n <XCircle className=\"size-3.5\" />\n {t('sync_excel.widget.run.status.cancelled', 'Cancelled')}\n </StatusBadge>\n )\n }\n\n if (status === 'pending' || status === 'running') {\n return (\n <StatusBadge variant={variant} className=\"gap-1.5\">\n <RefreshCw className={cn('size-3.5', status === 'running' ? 'animate-spin' : '')} />\n {status === 'pending'\n ? t('sync_excel.widget.run.status.pending', 'Pending')\n : t('sync_excel.widget.run.status.running', 'Running')}\n </StatusBadge>\n )\n }\n\n return (\n <StatusBadge variant={variant}>\n {t('sync_excel.widget.run.status.idle', 'Idle')}\n </StatusBadge>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.5900.1.b3fb52925e",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -245,16 +245,16 @@
|
|
|
245
245
|
"zod": "^4.4.3"
|
|
246
246
|
},
|
|
247
247
|
"peerDependencies": {
|
|
248
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
249
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
250
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
248
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.5900.1.b3fb52925e",
|
|
249
|
+
"@open-mercato/shared": "0.6.6-develop.5900.1.b3fb52925e",
|
|
250
|
+
"@open-mercato/ui": "0.6.6-develop.5900.1.b3fb52925e",
|
|
251
251
|
"react": "^19.0.0",
|
|
252
252
|
"react-dom": "^19.0.0"
|
|
253
253
|
},
|
|
254
254
|
"devDependencies": {
|
|
255
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
256
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
257
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
255
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.5900.1.b3fb52925e",
|
|
256
|
+
"@open-mercato/shared": "0.6.6-develop.5900.1.b3fb52925e",
|
|
257
|
+
"@open-mercato/ui": "0.6.6-develop.5900.1.b3fb52925e",
|
|
258
258
|
"@testing-library/dom": "^10.4.1",
|
|
259
259
|
"@testing-library/jest-dom": "^6.9.1",
|
|
260
260
|
"@testing-library/react": "^16.3.1",
|
|
@@ -12,6 +12,7 @@ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
|
12
12
|
import { useCustomFieldDefs } from '@open-mercato/ui/backend/utils/customFieldDefs'
|
|
13
13
|
import { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'
|
|
14
14
|
import { Badge } from '@open-mercato/ui/primitives/badge'
|
|
15
|
+
import { StatusBadge, type StatusMap } from '@open-mercato/ui/primitives/status-badge'
|
|
15
16
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
16
17
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'
|
|
17
18
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
@@ -1171,54 +1172,67 @@ function MetricCard({ label, value }: { label: string; value: string }) {
|
|
|
1171
1172
|
)
|
|
1172
1173
|
}
|
|
1173
1174
|
|
|
1174
|
-
|
|
1175
|
+
type RunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'idle'
|
|
1176
|
+
|
|
1177
|
+
export const RUN_STATUS_BADGE_VARIANTS: StatusMap<RunStatus> = {
|
|
1178
|
+
completed: 'success',
|
|
1179
|
+
failed: 'error',
|
|
1180
|
+
cancelled: 'neutral',
|
|
1181
|
+
pending: 'info',
|
|
1182
|
+
running: 'info',
|
|
1183
|
+
idle: 'neutral',
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
export function RunStatusBadge({
|
|
1175
1187
|
status,
|
|
1176
1188
|
t,
|
|
1177
1189
|
}: {
|
|
1178
|
-
status:
|
|
1190
|
+
status: RunStatus
|
|
1179
1191
|
t: ReturnType<typeof useT>
|
|
1180
1192
|
}) {
|
|
1193
|
+
const variant = RUN_STATUS_BADGE_VARIANTS[status] ?? 'neutral'
|
|
1194
|
+
|
|
1181
1195
|
if (status === 'completed') {
|
|
1182
1196
|
return (
|
|
1183
|
-
<
|
|
1197
|
+
<StatusBadge variant={variant} className="gap-1.5">
|
|
1184
1198
|
<CheckCircle2 className="size-3.5" />
|
|
1185
1199
|
{t('sync_excel.widget.run.status.completed', 'Completed')}
|
|
1186
|
-
</
|
|
1200
|
+
</StatusBadge>
|
|
1187
1201
|
)
|
|
1188
1202
|
}
|
|
1189
1203
|
|
|
1190
1204
|
if (status === 'failed') {
|
|
1191
1205
|
return (
|
|
1192
|
-
<
|
|
1206
|
+
<StatusBadge variant={variant} className="gap-1.5">
|
|
1193
1207
|
<AlertTriangle className="size-3.5" />
|
|
1194
1208
|
{t('sync_excel.widget.run.status.failed', 'Failed')}
|
|
1195
|
-
</
|
|
1209
|
+
</StatusBadge>
|
|
1196
1210
|
)
|
|
1197
1211
|
}
|
|
1198
1212
|
|
|
1199
1213
|
if (status === 'cancelled') {
|
|
1200
1214
|
return (
|
|
1201
|
-
<
|
|
1215
|
+
<StatusBadge variant={variant} className="gap-1.5">
|
|
1202
1216
|
<XCircle className="size-3.5" />
|
|
1203
1217
|
{t('sync_excel.widget.run.status.cancelled', 'Cancelled')}
|
|
1204
|
-
</
|
|
1218
|
+
</StatusBadge>
|
|
1205
1219
|
)
|
|
1206
1220
|
}
|
|
1207
1221
|
|
|
1208
1222
|
if (status === 'pending' || status === 'running') {
|
|
1209
1223
|
return (
|
|
1210
|
-
<
|
|
1224
|
+
<StatusBadge variant={variant} className="gap-1.5">
|
|
1211
1225
|
<RefreshCw className={cn('size-3.5', status === 'running' ? 'animate-spin' : '')} />
|
|
1212
1226
|
{status === 'pending'
|
|
1213
1227
|
? t('sync_excel.widget.run.status.pending', 'Pending')
|
|
1214
1228
|
: t('sync_excel.widget.run.status.running', 'Running')}
|
|
1215
|
-
</
|
|
1229
|
+
</StatusBadge>
|
|
1216
1230
|
)
|
|
1217
1231
|
}
|
|
1218
1232
|
|
|
1219
1233
|
return (
|
|
1220
|
-
<
|
|
1234
|
+
<StatusBadge variant={variant}>
|
|
1221
1235
|
{t('sync_excel.widget.run.status.idle', 'Idle')}
|
|
1222
|
-
</
|
|
1236
|
+
</StatusBadge>
|
|
1223
1237
|
)
|
|
1224
1238
|
}
|