@campfire-interactive/volume-intelligence-ui 0.6.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +60 -10
- package/dist/index.js +143 -62
- package/package.json +43 -43
package/dist/index.d.ts
CHANGED
|
@@ -150,12 +150,21 @@ interface SegmentMappingEntry {
|
|
|
150
150
|
segmentId: string;
|
|
151
151
|
transformRule?: string | null;
|
|
152
152
|
}
|
|
153
|
+
type AliasStatus = 'suggested' | 'approved' | 'rejected';
|
|
153
154
|
interface SourceValueAlias {
|
|
154
155
|
id: string;
|
|
155
156
|
sourceId: string;
|
|
156
|
-
level
|
|
157
|
+
/** Segment identity (replaces the old hierarchy-only `level`).
|
|
158
|
+
* targetCategory: 'hierarchy_level' | 'automotive_field' | 'dimension';
|
|
159
|
+
* targetKey: 'level1'..'level6' | 'plant_name'|… | 'dim1'..'dimN'. */
|
|
160
|
+
targetCategory: string;
|
|
161
|
+
targetKey: string;
|
|
157
162
|
rawValue: string;
|
|
158
163
|
canonicalValue: string;
|
|
164
|
+
/** Two-stage lifecycle — only 'approved' aliases are used downstream. */
|
|
165
|
+
status: AliasStatus;
|
|
166
|
+
confidence?: number | null;
|
|
167
|
+
suggestedBy?: string | null;
|
|
159
168
|
createdAt: string;
|
|
160
169
|
updatedAt: string;
|
|
161
170
|
}
|
|
@@ -163,6 +172,13 @@ interface ValueAliasEntry {
|
|
|
163
172
|
rawValue: string;
|
|
164
173
|
canonicalValue: string;
|
|
165
174
|
}
|
|
175
|
+
/** A mapped segment an alias can be defined for (built by the host from the
|
|
176
|
+
* source's segment mappings). */
|
|
177
|
+
interface AliasTarget {
|
|
178
|
+
targetCategory: string;
|
|
179
|
+
targetKey: string;
|
|
180
|
+
label: string;
|
|
181
|
+
}
|
|
166
182
|
interface RelinkResult {
|
|
167
183
|
totalOverlay: number;
|
|
168
184
|
linked: number;
|
|
@@ -300,8 +316,16 @@ interface SourceAdminTransport {
|
|
|
300
316
|
items: SegmentMappingRow[];
|
|
301
317
|
}>;
|
|
302
318
|
listValueAliases(sourceId: string): Promise<SourceValueAlias[]>;
|
|
319
|
+
/** Bulk-replace the APPROVED aliases for a segment. OPTIONAL/newer shape
|
|
320
|
+
* keyed by (targetCategory, targetKey); falls back to replaceLevelValueAliases
|
|
321
|
+
* for hosts still on the hierarchy-level shape. */
|
|
322
|
+
replaceValueAliases?(sourceId: string, targetCategory: string, targetKey: string, entries: ValueAliasEntry[]): Promise<SourceValueAlias[]>;
|
|
303
323
|
replaceLevelValueAliases(sourceId: string, level: number, entries: ValueAliasEntry[]): Promise<SourceValueAlias[]>;
|
|
304
324
|
deleteValueAlias(aliasId: string): Promise<void>;
|
|
325
|
+
/** Approve/reject a suggested alias. OPTIONAL: when omitted the card hides the
|
|
326
|
+
* suggested-review controls (a host that predates the workflow is unaffected). */
|
|
327
|
+
approveValueAlias?(aliasId: string): Promise<SourceValueAlias>;
|
|
328
|
+
rejectValueAlias?(aliasId: string): Promise<SourceValueAlias>;
|
|
305
329
|
relinkOverlaySource(sourceId: string): Promise<RelinkResult>;
|
|
306
330
|
getTenantConfig(): Promise<TenantVolumeConfig | null>;
|
|
307
331
|
setTenantSource(sourceId: string): Promise<TenantVolumeConfig>;
|
|
@@ -369,6 +393,14 @@ interface VolumeSourcesListProps {
|
|
|
369
393
|
* (ADR §6) while the list itself stays host-agnostic.
|
|
370
394
|
*/
|
|
371
395
|
renderActiveSourceExtras?: (source: VolumeSource) => ReactNode;
|
|
396
|
+
/**
|
|
397
|
+
* When true, the list is view-only: the create / add-from-template / set-active
|
|
398
|
+
* affordances are hidden so a non-admin host (e.g. forecast members) can show
|
|
399
|
+
* sources without editing them. Defaults to false — existing embedders are
|
|
400
|
+
* unaffected. Mutations are never reachable in this mode; the host should
|
|
401
|
+
* still enforce the boundary server-side.
|
|
402
|
+
*/
|
|
403
|
+
readOnly?: boolean;
|
|
372
404
|
className?: string;
|
|
373
405
|
}
|
|
374
406
|
/**
|
|
@@ -379,7 +411,7 @@ interface VolumeSourcesListProps {
|
|
|
379
411
|
* The VI-webapp's active-source analytical sub-cards (enrichment toggle,
|
|
380
412
|
* backtests) are intentionally NOT here — per ADR §6 they stay VI-webapp-only.
|
|
381
413
|
*/
|
|
382
|
-
declare function VolumeSourcesList({ transport, onOpenSource, renderActiveSourceExtras, className }: VolumeSourcesListProps): react.JSX.Element;
|
|
414
|
+
declare function VolumeSourcesList({ transport, onOpenSource, renderActiveSourceExtras, readOnly, className }: VolumeSourcesListProps): react.JSX.Element;
|
|
383
415
|
|
|
384
416
|
/**
|
|
385
417
|
* Segment mapper — a catalog-driven, required-first assignment surface built for
|
|
@@ -422,8 +454,11 @@ interface UnifiedSegmentMapperProps {
|
|
|
422
454
|
segmentId: string;
|
|
423
455
|
}>) => Promise<void>;
|
|
424
456
|
saving: boolean;
|
|
457
|
+
/** View-only: assignments can't be changed and Save is disabled. Follows the
|
|
458
|
+
* `locked` convention used by TallLayoutMappings / ValueAliasesCard. */
|
|
459
|
+
locked?: boolean;
|
|
425
460
|
}
|
|
426
|
-
declare function UnifiedSegmentMapper({ segments, mappableDimensions, initialMappings, onSave, saving, }: UnifiedSegmentMapperProps): react.JSX.Element;
|
|
461
|
+
declare function UnifiedSegmentMapper({ segments, mappableDimensions, initialMappings, onSave, saving, locked, }: UnifiedSegmentMapperProps): react.JSX.Element;
|
|
427
462
|
|
|
428
463
|
interface VolumeSourceDetailProps {
|
|
429
464
|
transport: SourceAdminTransport;
|
|
@@ -432,6 +467,14 @@ interface VolumeSourceDetailProps {
|
|
|
432
467
|
onOpenSource: (sourceId: string) => void;
|
|
433
468
|
/** Optional "All sources →" affordance; hidden when omitted. */
|
|
434
469
|
onBack?: () => void;
|
|
470
|
+
/**
|
|
471
|
+
* View-only mode: hides the Danger-zone tab and disables every mutating
|
|
472
|
+
* control (layout, unique key, segment detect/add/delete, mapper save,
|
|
473
|
+
* value aliases). For non-admin hosts (e.g. forecast members). Defaults to
|
|
474
|
+
* false — existing embedders unaffected. The host must still enforce the
|
|
475
|
+
* boundary server-side.
|
|
476
|
+
*/
|
|
477
|
+
readOnly?: boolean;
|
|
435
478
|
className?: string;
|
|
436
479
|
}
|
|
437
480
|
/**
|
|
@@ -440,7 +483,7 @@ interface VolumeSourceDetailProps {
|
|
|
440
483
|
* Ported from forecast's SourceDetailPage; adminApi → injected transport,
|
|
441
484
|
* react-router → onOpenSource/onBack callbacks, shadcn/Tailwind → plain CSS.
|
|
442
485
|
*/
|
|
443
|
-
declare function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, className }: VolumeSourceDetailProps): react.JSX.Element;
|
|
486
|
+
declare function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, readOnly, className }: VolumeSourceDetailProps): react.JSX.Element;
|
|
444
487
|
|
|
445
488
|
interface TallLayoutMappingsProps {
|
|
446
489
|
transport: SourceAdminTransport;
|
|
@@ -450,18 +493,25 @@ interface TallLayoutMappingsProps {
|
|
|
450
493
|
declare function TallLayoutMappings({ transport, source, locked }: TallLayoutMappingsProps): react.JSX.Element;
|
|
451
494
|
|
|
452
495
|
/**
|
|
453
|
-
* Per-source
|
|
454
|
-
*
|
|
455
|
-
* sides of the overlay→base
|
|
456
|
-
*
|
|
496
|
+
* Per-source value alias editor. Aliases normalise a raw source value to a
|
|
497
|
+
* canonical one for a mapped segment (targetCategory/targetKey) — the matcher
|
|
498
|
+
* applies approved hierarchy aliases to both sides of the overlay→base compare;
|
|
499
|
+
* automotive aliases are consumed by downstream repos via the seam API.
|
|
500
|
+
*
|
|
501
|
+
* Two stages: `suggested` aliases (populated externally, e.g. cfi-ai-hub) are
|
|
502
|
+
* shown in a review list with Approve/Reject; `approved` aliases are the
|
|
503
|
+
* editable set. `targets` (mapped segments) comes from the host; when omitted we
|
|
504
|
+
* fall back to hierarchy levels 1..6 so older hosts keep working.
|
|
457
505
|
*/
|
|
458
506
|
interface ValueAliasesCardProps {
|
|
459
507
|
transport: SourceAdminTransport;
|
|
460
508
|
sourceId: string;
|
|
461
509
|
locked: boolean;
|
|
462
510
|
isOverlay: boolean;
|
|
511
|
+
/** Mapped segments aliases can be defined for. Falls back to level1..6. */
|
|
512
|
+
targets?: AliasTarget[];
|
|
463
513
|
}
|
|
464
|
-
declare function ValueAliasesCard({ transport, sourceId, locked, isOverlay }: ValueAliasesCardProps): react.JSX.Element;
|
|
514
|
+
declare function ValueAliasesCard({ transport, sourceId, locked, isOverlay, targets }: ValueAliasesCardProps): react.JSX.Element;
|
|
465
515
|
|
|
466
516
|
declare const HIERARCHY_LEVEL_COUNT = 6;
|
|
467
517
|
/** Display labels for hierarchy levels 1..6 (the mapping of source columns to
|
|
@@ -474,4 +524,4 @@ declare const DEFAULT_LEVEL_NAMES: Record<number, string>;
|
|
|
474
524
|
declare const AUTOMOTIVE_SLOTS: AutomotiveSlot[];
|
|
475
525
|
declare const AUTOMOTIVE_TRANSFORM_LABELS: Record<string, string>;
|
|
476
526
|
|
|
477
|
-
export { AUTOMOTIVE_SLOTS, AUTOMOTIVE_TRANSFORM_LABELS, type AutomotiveSlot, type ClearSourceDataResult, type CreateSegmentInput, type CreateSourceInput, DEFAULT_LEVEL_NAMES, type DetectSegmentsResult, HIERARCHY_LEVEL_COUNT, type ListErrorsResult, type RelinkResult, type SegmentMappingCategory, type SegmentMappingCategoryKeys, type SegmentMappingEntry, type SegmentMappingKeys, type SegmentMappingRow, type SourceAdminTransport, type SourceLayout, type SourceTemplateSummary, type SourceValueAlias, type SourceWithSegments, TallLayoutMappings, type TallLayoutMappingsProps, type TenantVolumeConfig, UnifiedSegmentMapper, type UnifiedSegmentMapperProps, type UpdateSourceInput, type Upload, type UploadError, type UploadErrorRow, type UploadInput, type UploadListParams, type UploadListResponse, type UploadRecord, type UploadStats, type UploadStatus, type ValueAliasEntry, ValueAliasesCard, type ValueAliasesCardProps, VolumeFiles, type VolumeFilesProps, type VolumeFilesTransport, VolumeImport, type VolumeImportProps, type VolumeSource$1 as VolumeImportSource, type VolumeImportTransport, type UploadStatus$1 as VolumeImportUploadStatus, type VolumeSource, VolumeSourceDetail, type VolumeSourceDetailProps, type VolumeSourceKind, type VolumeSourceLayout, type VolumeSourceSegment, VolumeSourcesList, type VolumeSourcesListProps };
|
|
527
|
+
export { AUTOMOTIVE_SLOTS, AUTOMOTIVE_TRANSFORM_LABELS, type AliasStatus, type AliasTarget, type AutomotiveSlot, type ClearSourceDataResult, type CreateSegmentInput, type CreateSourceInput, DEFAULT_LEVEL_NAMES, type DetectSegmentsResult, HIERARCHY_LEVEL_COUNT, type ListErrorsResult, type MappableDimension, type RelinkResult, type RequiredMappingStatus, type SegmentMappingCategory, type SegmentMappingCategoryKeys, type SegmentMappingEntry, type SegmentMappingKeys, type SegmentMappingRow, type SourceAdminTransport, type SourceLayout, type SourceTemplateSummary, type SourceValueAlias, type SourceWithSegments, TallLayoutMappings, type TallLayoutMappingsProps, type TenantVolumeConfig, UnifiedSegmentMapper, type UnifiedSegmentMapperProps, type UpdateSourceInput, type Upload, type UploadError, type UploadErrorRow, type UploadInput, type UploadListParams, type UploadListResponse, type UploadRecord, type UploadStats, type UploadStatus, type ValueAliasEntry, ValueAliasesCard, type ValueAliasesCardProps, VolumeFiles, type VolumeFilesProps, type VolumeFilesTransport, VolumeImport, type VolumeImportProps, type VolumeSource$1 as VolumeImportSource, type VolumeImportTransport, type UploadStatus$1 as VolumeImportUploadStatus, type VolumeSource, VolumeSourceDetail, type VolumeSourceDetailProps, type VolumeSourceKind, type VolumeSourceLayout, type VolumeSourceSegment, VolumeSourcesList, type VolumeSourcesListProps };
|
package/dist/index.js
CHANGED
|
@@ -786,7 +786,7 @@ import { useCallback as useCallback3, useEffect as useEffect3, useState as useSt
|
|
|
786
786
|
import { CheckCircle2, Sparkles } from "lucide-react";
|
|
787
787
|
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
788
788
|
var EMPTY_FORM = { code: "", name: "", description: "", headerRowIndex: 0, volumeHeaderFormat: "MMM yyyy" };
|
|
789
|
-
function VolumeSourcesList({ transport, onOpenSource, renderActiveSourceExtras, className }) {
|
|
789
|
+
function VolumeSourcesList({ transport, onOpenSource, renderActiveSourceExtras, readOnly = false, className }) {
|
|
790
790
|
const [sources, setSources] = useState3([]);
|
|
791
791
|
const [activeSourceId, setActiveSourceId] = useState3(null);
|
|
792
792
|
const [templates, setTemplates] = useState3([]);
|
|
@@ -883,7 +883,7 @@ function VolumeSourcesList({ transport, onOpenSource, renderActiveSourceExtras,
|
|
|
883
883
|
] })
|
|
884
884
|
] })
|
|
885
885
|
] }),
|
|
886
|
-
/* @__PURE__ */ jsxs4("div", { className: "cfi-vi-row", children: [
|
|
886
|
+
!readOnly && /* @__PURE__ */ jsxs4("div", { className: "cfi-vi-row", children: [
|
|
887
887
|
availableTemplates.length > 0 && /* @__PURE__ */ jsxs4(Btn, { variant: "outline", size: "sm", onClick: () => setShowTemplates(true), children: [
|
|
888
888
|
/* @__PURE__ */ jsx4(Sparkles, { size: 14, "aria-hidden": true }),
|
|
889
889
|
" Add from template"
|
|
@@ -906,7 +906,7 @@ function VolumeSourcesList({ transport, onOpenSource, renderActiveSourceExtras,
|
|
|
906
906
|
/* @__PURE__ */ jsxs4("span", { className: "cfi-vi-row", children: [
|
|
907
907
|
/* @__PURE__ */ jsx4(Badge, { variant: "outline", children: src.code }),
|
|
908
908
|
!src.isActive && /* @__PURE__ */ jsx4(Badge, { variant: "secondary", children: "Disabled" }),
|
|
909
|
-
!isActive && src.kind === "base" && /* @__PURE__ */ jsx4(Btn, { variant: "outline", size: "sm", disabled: activating === src.id, onClick: () => handleActivate(src), children: activating === src.id ? "Activating\u2026" : "Set Active" })
|
|
909
|
+
!readOnly && !isActive && src.kind === "base" && /* @__PURE__ */ jsx4(Btn, { variant: "outline", size: "sm", disabled: activating === src.id, onClick: () => handleActivate(src), children: activating === src.id ? "Activating\u2026" : "Set Active" })
|
|
910
910
|
] })
|
|
911
911
|
] }) }),
|
|
912
912
|
/* @__PURE__ */ jsxs4(CardBody, { children: [
|
|
@@ -1092,7 +1092,8 @@ function UnifiedSegmentMapper({
|
|
|
1092
1092
|
mappableDimensions,
|
|
1093
1093
|
initialMappings,
|
|
1094
1094
|
onSave,
|
|
1095
|
-
saving
|
|
1095
|
+
saving,
|
|
1096
|
+
locked = false
|
|
1096
1097
|
}) {
|
|
1097
1098
|
const dims = useMemo3(
|
|
1098
1099
|
() => mappableDimensions?.length ? mappableDimensions : CANONICAL_MAPPABLE,
|
|
@@ -1132,6 +1133,7 @@ function UnifiedSegmentMapper({
|
|
|
1132
1133
|
const dirty = dims.some((d) => (assign[d.key] ?? "") !== (savedAssign[d.key] ?? ""));
|
|
1133
1134
|
const canSave = dirty && missingRequired.length === 0 && !saving;
|
|
1134
1135
|
function setOne(dimKey, segId) {
|
|
1136
|
+
if (locked) return;
|
|
1135
1137
|
setAssign((p) => {
|
|
1136
1138
|
const next = { ...p, [dimKey]: segId };
|
|
1137
1139
|
if (segId) {
|
|
@@ -1147,12 +1149,13 @@ function UnifiedSegmentMapper({
|
|
|
1147
1149
|
});
|
|
1148
1150
|
}
|
|
1149
1151
|
function clickColumn(segId) {
|
|
1150
|
-
if (!activeDim) return;
|
|
1152
|
+
if (!activeDim || locked) return;
|
|
1151
1153
|
setOne(activeDim, segId);
|
|
1152
1154
|
const nextEmpty = [...required, ...optional].find((d) => d.key !== activeDim && !assign[d.key] && d.required);
|
|
1153
1155
|
setActiveDim(nextEmpty?.key ?? null);
|
|
1154
1156
|
}
|
|
1155
1157
|
async function handleSave() {
|
|
1158
|
+
if (locked) return;
|
|
1156
1159
|
const hierarchy = [];
|
|
1157
1160
|
const automotive = [];
|
|
1158
1161
|
const dimensions = [];
|
|
@@ -1194,8 +1197,10 @@ function UnifiedSegmentMapper({
|
|
|
1194
1197
|
"div",
|
|
1195
1198
|
{
|
|
1196
1199
|
className: `cfi-vi-hier-row${isActive ? " cfi-vi-dropzone--active" : ""}`,
|
|
1197
|
-
style: { cursor: "pointer", borderRadius: 6 },
|
|
1198
|
-
onClick: () =>
|
|
1200
|
+
style: { cursor: locked ? "default" : "pointer", borderRadius: 6 },
|
|
1201
|
+
onClick: () => {
|
|
1202
|
+
if (!locked) setActiveDim(isActive ? null : d.key);
|
|
1203
|
+
},
|
|
1199
1204
|
onDragOver: (e) => {
|
|
1200
1205
|
e.preventDefault();
|
|
1201
1206
|
setDropDim(d.key);
|
|
@@ -1264,7 +1269,7 @@ function UnifiedSegmentMapper({
|
|
|
1264
1269
|
/* @__PURE__ */ jsx5("div", { className: "cfi-vi-mapper-box", children: optional.map(renderRow) })
|
|
1265
1270
|
] }),
|
|
1266
1271
|
/* @__PURE__ */ jsxs5("div", { className: "cfi-vi-mapper-save", children: [
|
|
1267
|
-
/* @__PURE__ */ jsx5(Btn, { size: "sm", onClick: handleSave, disabled: !canSave, children: saving ? "Saving\u2026" : "Save Mapping" }),
|
|
1272
|
+
/* @__PURE__ */ jsx5(Btn, { size: "sm", onClick: handleSave, disabled: !canSave || locked, children: saving ? "Saving\u2026" : "Save Mapping" }),
|
|
1268
1273
|
dirty && missingRequired.length === 0 && !saving && /* @__PURE__ */ jsx5("span", { className: "cfi-vi-mapper-dirty", children: "Unsaved changes" }),
|
|
1269
1274
|
!dirty && /* @__PURE__ */ jsx5("span", { className: "cfi-vi-muted", children: "Up to date" })
|
|
1270
1275
|
] })
|
|
@@ -1287,12 +1292,14 @@ function UnifiedSegmentMapper({
|
|
|
1287
1292
|
/* @__PURE__ */ jsx5("div", { className: "cfi-vi-pool", children: pool.length === 0 ? /* @__PURE__ */ jsx5("p", { className: "cfi-vi-pool-empty", children: query ? "No columns match the filter." : "All columns assigned." }) : /* @__PURE__ */ jsx5("div", { className: "cfi-vi-chips", children: pool.map((s) => /* @__PURE__ */ jsxs5(
|
|
1288
1293
|
"div",
|
|
1289
1294
|
{
|
|
1290
|
-
draggable:
|
|
1291
|
-
onDragStart: () =>
|
|
1295
|
+
draggable: !locked,
|
|
1296
|
+
onDragStart: () => {
|
|
1297
|
+
if (!locked) setDragging(s.id);
|
|
1298
|
+
},
|
|
1292
1299
|
onDragEnd: () => setDragging(null),
|
|
1293
1300
|
onClick: () => clickColumn(s.id),
|
|
1294
1301
|
className: "cfi-vi-chip",
|
|
1295
|
-
style: { cursor: activeDim ? "pointer" : "grab" },
|
|
1302
|
+
style: { cursor: locked ? "default" : activeDim ? "pointer" : "grab" },
|
|
1296
1303
|
title: activeDim ? "Click to assign" : "Drag onto a dimension, or select a dimension first",
|
|
1297
1304
|
children: [
|
|
1298
1305
|
/* @__PURE__ */ jsx5("div", { className: "cfi-vi-chip-dot" }),
|
|
@@ -1306,7 +1313,7 @@ function UnifiedSegmentMapper({
|
|
|
1306
1313
|
}
|
|
1307
1314
|
|
|
1308
1315
|
// src/VolumeSourceDetail.tsx
|
|
1309
|
-
import { useEffect as useEffect7, useRef as
|
|
1316
|
+
import { useEffect as useEffect7, useMemo as useMemo6, useRef as useRef4, useState as useState7 } from "react";
|
|
1310
1317
|
import { KeyRound, Lock, Rows, Columns, AlertTriangle as AlertTriangle3, Layers, SlidersHorizontal, Tag } from "lucide-react";
|
|
1311
1318
|
|
|
1312
1319
|
// src/TallLayoutMappings.tsx
|
|
@@ -1410,18 +1417,32 @@ function TallLayoutMappings({ transport, source, locked }) {
|
|
|
1410
1417
|
}
|
|
1411
1418
|
|
|
1412
1419
|
// src/ValueAliasesCard.tsx
|
|
1413
|
-
import { useEffect as useEffect6, useMemo as useMemo5, useState as useState6 } from "react";
|
|
1414
|
-
import { Plus, Trash2, ArrowRight, Save, RotateCw } from "lucide-react";
|
|
1420
|
+
import { useEffect as useEffect6, useMemo as useMemo5, useRef as useRef3, useState as useState6 } from "react";
|
|
1421
|
+
import { Plus, Trash2, ArrowRight, Save, RotateCw, Check, X as X3 } from "lucide-react";
|
|
1415
1422
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1416
|
-
|
|
1417
|
-
|
|
1423
|
+
var HIERARCHY_FALLBACK = [1, 2, 3, 4, 5, 6].map((n) => ({
|
|
1424
|
+
targetCategory: "hierarchy_level",
|
|
1425
|
+
targetKey: `level${n}`,
|
|
1426
|
+
label: `L${n} \u2014 ${DEFAULT_LEVEL_NAMES[n]}`
|
|
1427
|
+
}));
|
|
1428
|
+
var keyOf = (t) => `${t.targetCategory}:${t.targetKey}`;
|
|
1429
|
+
function ValueAliasesCard({ transport, sourceId, locked, isOverlay, targets }) {
|
|
1430
|
+
const targetList = targets && targets.length > 0 ? targets : HIERARCHY_FALLBACK;
|
|
1431
|
+
const [selectedKey, setSelectedKey] = useState6(keyOf(targetList[0]));
|
|
1418
1432
|
const [aliases, setAliases] = useState6([]);
|
|
1419
1433
|
const [draft, setDraft] = useState6([]);
|
|
1420
1434
|
const [saving, setSaving] = useState6(false);
|
|
1435
|
+
const [busyId, setBusyId] = useState6(null);
|
|
1421
1436
|
const [error, setError] = useState6(null);
|
|
1422
1437
|
const [loading, setLoading] = useState6(true);
|
|
1423
1438
|
const [relinking, setRelinking] = useState6(false);
|
|
1424
1439
|
const [relinkResult, setRelinkResult] = useState6(null);
|
|
1440
|
+
const editedRef = useRef3(false);
|
|
1441
|
+
const selected = useMemo5(
|
|
1442
|
+
() => targetList.find((t) => keyOf(t) === selectedKey) ?? targetList[0],
|
|
1443
|
+
[targetList, selectedKey]
|
|
1444
|
+
);
|
|
1445
|
+
const matchesSelected = (a) => a.targetCategory === selected.targetCategory && a.targetKey === selected.targetKey;
|
|
1425
1446
|
useEffect6(() => {
|
|
1426
1447
|
let cancelled = false;
|
|
1427
1448
|
setLoading(true);
|
|
@@ -1439,21 +1460,39 @@ function ValueAliasesCard({ transport, sourceId, locked, isOverlay }) {
|
|
|
1439
1460
|
cancelled = true;
|
|
1440
1461
|
};
|
|
1441
1462
|
}, [sourceId, transport]);
|
|
1463
|
+
const approved = useMemo5(() => aliases.filter((a) => matchesSelected(a) && a.status === "approved"), [aliases, selected]);
|
|
1464
|
+
const suggested = useMemo5(() => aliases.filter((a) => matchesSelected(a) && a.status === "suggested"), [aliases, selected]);
|
|
1442
1465
|
useEffect6(() => {
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1466
|
+
editedRef.current = false;
|
|
1467
|
+
}, [selectedKey]);
|
|
1468
|
+
useEffect6(() => {
|
|
1469
|
+
if (!editedRef.current) {
|
|
1470
|
+
setDraft(approved.map((a) => ({ id: a.id, rawValue: a.rawValue, canonicalValue: a.canonicalValue })));
|
|
1471
|
+
}
|
|
1472
|
+
}, [approved]);
|
|
1473
|
+
useEffect6(() => {
|
|
1474
|
+
if (!targetList.some((t) => keyOf(t) === selectedKey)) {
|
|
1475
|
+
setSelectedKey(keyOf(targetList[0]));
|
|
1476
|
+
}
|
|
1477
|
+
}, [targetList, selectedKey]);
|
|
1447
1478
|
const isDirty = useMemo5(() => {
|
|
1448
|
-
const saved =
|
|
1479
|
+
const saved = approved.map((a) => `${a.rawValue.trim().toUpperCase()}${a.canonicalValue.trim().toUpperCase()}`).sort();
|
|
1449
1480
|
const current = draft.filter((d) => d.rawValue.trim() && d.canonicalValue.trim()).map((d) => `${d.rawValue.trim().toUpperCase()}${d.canonicalValue.trim().toUpperCase()}`).sort();
|
|
1450
1481
|
if (saved.length !== current.length) return true;
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1482
|
+
return saved.some((s, i) => s !== current[i]);
|
|
1483
|
+
}, [approved, draft]);
|
|
1484
|
+
const addRow = () => {
|
|
1485
|
+
editedRef.current = true;
|
|
1486
|
+
setDraft((p) => [...p, { id: null, rawValue: "", canonicalValue: "" }]);
|
|
1487
|
+
};
|
|
1488
|
+
const updateRow = (i, patch) => {
|
|
1489
|
+
editedRef.current = true;
|
|
1490
|
+
setDraft((p) => p.map((r, idx) => idx === i ? { ...r, ...patch } : r));
|
|
1491
|
+
};
|
|
1492
|
+
const removeRow = (i) => {
|
|
1493
|
+
editedRef.current = true;
|
|
1494
|
+
setDraft((p) => p.filter((_, idx) => idx !== i));
|
|
1495
|
+
};
|
|
1457
1496
|
async function handleRelink() {
|
|
1458
1497
|
setRelinking(true);
|
|
1459
1498
|
setError(null);
|
|
@@ -1472,42 +1511,79 @@ function ValueAliasesCard({ transport, sourceId, locked, isOverlay }) {
|
|
|
1472
1511
|
setError(null);
|
|
1473
1512
|
try {
|
|
1474
1513
|
const entries = draft.filter((d) => d.rawValue.trim() && d.canonicalValue.trim()).map((d) => ({ rawValue: d.rawValue.trim().toUpperCase(), canonicalValue: d.canonicalValue.trim().toUpperCase() }));
|
|
1475
|
-
|
|
1476
|
-
|
|
1514
|
+
let updated;
|
|
1515
|
+
if (transport.replaceValueAliases) {
|
|
1516
|
+
updated = await transport.replaceValueAliases(sourceId, selected.targetCategory, selected.targetKey, entries);
|
|
1517
|
+
} else {
|
|
1518
|
+
const level = parseInt(selected.targetKey.replace("level", ""), 10);
|
|
1519
|
+
updated = await transport.replaceLevelValueAliases(sourceId, level, entries);
|
|
1520
|
+
}
|
|
1521
|
+
setAliases((prev) => [...prev.filter((a) => !(matchesSelected(a) && a.status === "approved")), ...updated.filter((a) => a.status === "approved")]);
|
|
1522
|
+
editedRef.current = false;
|
|
1477
1523
|
} catch (e) {
|
|
1478
1524
|
setError(e instanceof Error ? e.message : "Failed to save aliases");
|
|
1479
1525
|
} finally {
|
|
1480
1526
|
setSaving(false);
|
|
1481
1527
|
}
|
|
1482
1528
|
}
|
|
1529
|
+
async function review(aliasId, action) {
|
|
1530
|
+
const fn = action === "approve" ? transport.approveValueAlias : transport.rejectValueAlias;
|
|
1531
|
+
if (!fn) return;
|
|
1532
|
+
setBusyId(aliasId);
|
|
1533
|
+
setError(null);
|
|
1534
|
+
try {
|
|
1535
|
+
const updated = await fn(aliasId);
|
|
1536
|
+
setAliases((prev) => prev.map((a) => a.id === aliasId ? updated : a));
|
|
1537
|
+
} catch (e) {
|
|
1538
|
+
setError(e instanceof Error ? e.message : `Failed to ${action}`);
|
|
1539
|
+
} finally {
|
|
1540
|
+
setBusyId(null);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
const canReview = !!transport.approveValueAlias && !!transport.rejectValueAlias;
|
|
1483
1544
|
if (loading) return /* @__PURE__ */ jsx7("p", { className: "cfi-vi-muted", children: "Loading aliases\u2026" });
|
|
1484
1545
|
return /* @__PURE__ */ jsxs7("div", { className: "cfi-vi-aliases", children: [
|
|
1485
1546
|
/* @__PURE__ */ jsxs7("div", { className: "cfi-vi-row cfi-vi-row--between", children: [
|
|
1486
1547
|
/* @__PURE__ */ jsxs7("div", { children: [
|
|
1487
1548
|
/* @__PURE__ */ jsx7("h2", { className: "cfi-vi-h2", children: "Value Aliases" }),
|
|
1488
1549
|
/* @__PURE__ */ jsxs7("p", { className: "cfi-vi-muted", style: { fontSize: "var(--cfi-font-size-xs, 11px)" }, children: [
|
|
1489
|
-
"Normalise
|
|
1550
|
+
"Normalise segment values when this source uses different vocabulary than what it joins to (e.g. ",
|
|
1490
1551
|
/* @__PURE__ */ jsx7("span", { className: "cfi-vi-mono", children: '"MICHIGAN" \u2192 "MICHIGAN ASSEMBLY"' }),
|
|
1491
|
-
").
|
|
1552
|
+
"). Only approved aliases take effect."
|
|
1492
1553
|
] })
|
|
1493
1554
|
] }),
|
|
1494
|
-
/* @__PURE__ */ jsxs7("div", { className: "cfi-vi-field", style: { minWidth:
|
|
1495
|
-
/* @__PURE__ */ jsx7("label", { className: "cfi-vi-label", children: "
|
|
1496
|
-
/* @__PURE__ */ jsx7("select", { className: "cfi-vi-input", value:
|
|
1497
|
-
"L",
|
|
1498
|
-
n,
|
|
1499
|
-
" \u2014 ",
|
|
1500
|
-
DEFAULT_LEVEL_NAMES[n]
|
|
1501
|
-
] }, n)) })
|
|
1555
|
+
/* @__PURE__ */ jsxs7("div", { className: "cfi-vi-field", style: { minWidth: 200 }, children: [
|
|
1556
|
+
/* @__PURE__ */ jsx7("label", { className: "cfi-vi-label", children: "Segment" }),
|
|
1557
|
+
/* @__PURE__ */ jsx7("select", { className: "cfi-vi-input", value: selectedKey, onChange: (e) => setSelectedKey(e.target.value), children: targetList.map((t) => /* @__PURE__ */ jsx7("option", { value: keyOf(t), children: t.label }, keyOf(t))) })
|
|
1502
1558
|
] })
|
|
1503
1559
|
] }),
|
|
1560
|
+
canReview && suggested.length > 0 && /* @__PURE__ */ jsxs7("div", { className: "cfi-vi-panel", style: { borderColor: "var(--cfi-color-warning, #f59e0b)" }, children: [
|
|
1561
|
+
/* @__PURE__ */ jsxs7("h3", { className: "cfi-vi-mapper-h3", style: { margin: 0 }, children: [
|
|
1562
|
+
"Suggested (",
|
|
1563
|
+
suggested.length,
|
|
1564
|
+
")"
|
|
1565
|
+
] }),
|
|
1566
|
+
/* @__PURE__ */ jsx7("p", { className: "cfi-vi-muted", style: { fontSize: "var(--cfi-font-size-xs, 11px)" }, children: "Populated by an upstream system. Approve to make them take effect, or reject to dismiss." }),
|
|
1567
|
+
/* @__PURE__ */ jsx7("div", { className: "cfi-vi-alias-rows", children: suggested.map((s) => /* @__PURE__ */ jsxs7("div", { className: "cfi-vi-row", children: [
|
|
1568
|
+
/* @__PURE__ */ jsx7("span", { className: "cfi-vi-mono", children: s.rawValue }),
|
|
1569
|
+
/* @__PURE__ */ jsx7(ArrowRight, { size: 14, className: "cfi-vi-muted", "aria-hidden": true }),
|
|
1570
|
+
/* @__PURE__ */ jsx7("span", { className: "cfi-vi-mono", style: { flex: 1 }, children: s.canonicalValue }),
|
|
1571
|
+
s.confidence != null && /* @__PURE__ */ jsxs7("span", { className: "cfi-vi-muted", style: { fontSize: "var(--cfi-font-size-xs, 11px)" }, children: [
|
|
1572
|
+
Math.round(s.confidence * 100),
|
|
1573
|
+
"%"
|
|
1574
|
+
] }),
|
|
1575
|
+
/* @__PURE__ */ jsxs7(Btn, { variant: "outline", size: "sm", onClick: () => void review(s.id, "approve"), disabled: locked || busyId === s.id, children: [
|
|
1576
|
+
/* @__PURE__ */ jsx7(Check, { size: 14, "aria-hidden": true }),
|
|
1577
|
+
" Approve"
|
|
1578
|
+
] }),
|
|
1579
|
+
/* @__PURE__ */ jsx7("button", { type: "button", className: "cfi-vi-icon-btn", onClick: () => void review(s.id, "reject"), disabled: locked || busyId === s.id, title: "Reject", children: /* @__PURE__ */ jsx7(X3, { size: 14, "aria-hidden": true }) })
|
|
1580
|
+
] }, s.id)) })
|
|
1581
|
+
] }),
|
|
1504
1582
|
/* @__PURE__ */ jsxs7("div", { className: "cfi-vi-panel", children: [
|
|
1505
1583
|
draft.length === 0 ? /* @__PURE__ */ jsxs7("p", { className: "cfi-vi-muted", style: { fontStyle: "italic", fontSize: "var(--cfi-font-size-xs, 12px)" }, children: [
|
|
1506
|
-
"No aliases
|
|
1507
|
-
|
|
1508
|
-
"
|
|
1509
|
-
DEFAULT_LEVEL_NAMES[selectedLevel],
|
|
1510
|
-
"). Click ",
|
|
1584
|
+
"No approved aliases for ",
|
|
1585
|
+
/* @__PURE__ */ jsx7("strong", { children: selected.label }),
|
|
1586
|
+
". Click ",
|
|
1511
1587
|
/* @__PURE__ */ jsx7("strong", { children: "+ Add alias" }),
|
|
1512
1588
|
" to create one."
|
|
1513
1589
|
] }) : /* @__PURE__ */ jsx7("div", { className: "cfi-vi-alias-rows", children: draft.map((row, i) => /* @__PURE__ */ jsxs7("div", { className: "cfi-vi-row", children: [
|
|
@@ -1554,7 +1630,7 @@ function ValueAliasesCard({ transport, sourceId, locked, isOverlay }) {
|
|
|
1554
1630
|
variant: "outline",
|
|
1555
1631
|
size: "sm",
|
|
1556
1632
|
onClick: () => void handleRelink(),
|
|
1557
|
-
disabled: relinking || isDirty,
|
|
1633
|
+
disabled: relinking || isDirty || locked,
|
|
1558
1634
|
title: isDirty ? "Save aliases first, then re-link" : "Re-run overlay \u2192 base resolution against current aliases",
|
|
1559
1635
|
children: [
|
|
1560
1636
|
/* @__PURE__ */ jsx7(RotateCw, { size: 14, className: relinking ? "cfi-vi-spin" : "", "aria-hidden": true }),
|
|
@@ -1572,7 +1648,7 @@ function ValueAliasesCard({ transport, sourceId, locked, isOverlay }) {
|
|
|
1572
1648
|
|
|
1573
1649
|
// src/VolumeSourceDetail.tsx
|
|
1574
1650
|
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1575
|
-
function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, className }) {
|
|
1651
|
+
function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, readOnly = false, className }) {
|
|
1576
1652
|
const id = sourceId;
|
|
1577
1653
|
const [source, setSource] = useState7(null);
|
|
1578
1654
|
const [allSources, setAllSources] = useState7([]);
|
|
@@ -1584,7 +1660,7 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
1584
1660
|
const [savingKey, setSavingKey] = useState7(false);
|
|
1585
1661
|
const [selectedKeyId, setSelectedKeyId] = useState7("__none__");
|
|
1586
1662
|
const [hasData, setHasData] = useState7(false);
|
|
1587
|
-
const fileRef =
|
|
1663
|
+
const fileRef = useRef4(null);
|
|
1588
1664
|
const [savingLayout, setSavingLayout] = useState7(false);
|
|
1589
1665
|
const [editLayout, setEditLayout] = useState7("wide");
|
|
1590
1666
|
const [editValueDateFormat, setEditValueDateFormat] = useState7("yyyy-MM-dd");
|
|
@@ -1750,12 +1826,16 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
1750
1826
|
if (!source) return /* @__PURE__ */ jsx8(Root, { className, children: /* @__PURE__ */ jsx8("p", { className: "cfi-vi-error", children: "Source not found" }) });
|
|
1751
1827
|
const keySegment = source.segments.find((s) => s.id === source.uniqueKeySegmentId);
|
|
1752
1828
|
const isOverlay = source.kind === "overlay";
|
|
1829
|
+
const aliasTargets = useMemo6(
|
|
1830
|
+
() => mappingRows.filter((m) => m.targetCategory === "hierarchy_level" || m.targetCategory === "automotive_field" || m.targetCategory === "dimension").map((m) => ({ targetCategory: m.targetCategory, targetKey: m.targetKey, label: `${m.segmentLabel} \xB7 ${m.targetKey}` })),
|
|
1831
|
+
[mappingRows]
|
|
1832
|
+
);
|
|
1753
1833
|
const TABS = [
|
|
1754
1834
|
{ id: "structure", label: "Structure", Icon: SlidersHorizontal },
|
|
1755
1835
|
{ id: "mapping", label: "Mapping", Icon: Layers },
|
|
1756
1836
|
{ id: "aliases", label: "Aliases", Icon: Tag },
|
|
1757
1837
|
{ id: "danger", label: "Danger zone", Icon: AlertTriangle3 }
|
|
1758
|
-
];
|
|
1838
|
+
].filter((t) => !readOnly || t.id !== "danger");
|
|
1759
1839
|
return /* @__PURE__ */ jsx8(Root, { className, children: /* @__PURE__ */ jsxs8("div", { className: "cfi-vi-detail", children: [
|
|
1760
1840
|
/* @__PURE__ */ jsxs8("div", { children: [
|
|
1761
1841
|
/* @__PURE__ */ jsxs8("div", { className: "cfi-vi-row", style: { gap: 8 }, children: [
|
|
@@ -1845,7 +1925,7 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
1845
1925
|
{
|
|
1846
1926
|
className: "cfi-vi-input",
|
|
1847
1927
|
value: editLayout,
|
|
1848
|
-
disabled: hasData || savingLayout,
|
|
1928
|
+
disabled: hasData || savingLayout || readOnly,
|
|
1849
1929
|
onChange: (e) => setEditLayout(e.target.value),
|
|
1850
1930
|
children: [
|
|
1851
1931
|
/* @__PURE__ */ jsx8("option", { value: "wide", children: "wide" }),
|
|
@@ -1862,13 +1942,13 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
1862
1942
|
className: "cfi-vi-mono",
|
|
1863
1943
|
value: editValueDateFormat,
|
|
1864
1944
|
placeholder: "yyyy-MM-dd",
|
|
1865
|
-
disabled: hasData || savingLayout,
|
|
1945
|
+
disabled: hasData || savingLayout || readOnly,
|
|
1866
1946
|
onChange: (e) => setEditValueDateFormat(e.target.value)
|
|
1867
1947
|
}
|
|
1868
1948
|
)
|
|
1869
1949
|
] })
|
|
1870
1950
|
] }),
|
|
1871
|
-
/* @__PURE__ */ jsx8(Btn, { size: "sm", onClick: () => void saveLayoutAndRouting(), disabled: hasData || savingLayout, children: savingLayout ? "Saving\u2026" : "Save layout" })
|
|
1951
|
+
/* @__PURE__ */ jsx8(Btn, { size: "sm", onClick: () => void saveLayoutAndRouting(), disabled: hasData || savingLayout || readOnly, children: savingLayout ? "Saving\u2026" : "Save layout" })
|
|
1872
1952
|
] }),
|
|
1873
1953
|
/* @__PURE__ */ jsxs8("div", { className: "cfi-vi-panel", children: [
|
|
1874
1954
|
/* @__PURE__ */ jsxs8("div", { className: "cfi-vi-row", children: [
|
|
@@ -1884,7 +1964,7 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
1884
1964
|
{
|
|
1885
1965
|
className: "cfi-vi-input",
|
|
1886
1966
|
value: selectedKeyId,
|
|
1887
|
-
disabled: hasData,
|
|
1967
|
+
disabled: hasData || readOnly,
|
|
1888
1968
|
onChange: (e) => setSelectedKeyId(e.target.value),
|
|
1889
1969
|
children: [
|
|
1890
1970
|
/* @__PURE__ */ jsx8("option", { value: "__none__", children: "None \u2014 use display name" }),
|
|
@@ -1893,7 +1973,7 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
1893
1973
|
}
|
|
1894
1974
|
)
|
|
1895
1975
|
] }),
|
|
1896
|
-
/* @__PURE__ */ jsx8(Btn, { size: "sm", onClick: saveUniqueKey, disabled: savingKey || hasData, children: savingKey ? "Saving\u2026" : "Save" })
|
|
1976
|
+
/* @__PURE__ */ jsx8(Btn, { size: "sm", onClick: saveUniqueKey, disabled: savingKey || hasData || readOnly, children: savingKey ? "Saving\u2026" : "Save" })
|
|
1897
1977
|
] })
|
|
1898
1978
|
] })
|
|
1899
1979
|
] }),
|
|
@@ -1904,7 +1984,7 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
1904
1984
|
source.segments.length,
|
|
1905
1985
|
")"
|
|
1906
1986
|
] }),
|
|
1907
|
-
/* @__PURE__ */ jsx8(Btn, { variant: "outline", size: "sm", onClick: () => fileRef.current?.click(), disabled: detecting || hasData, children: detecting ? "Detecting\u2026" : "Detect from file" }),
|
|
1987
|
+
/* @__PURE__ */ jsx8(Btn, { variant: "outline", size: "sm", onClick: () => fileRef.current?.click(), disabled: detecting || hasData || readOnly, children: detecting ? "Detecting\u2026" : "Detect from file" }),
|
|
1908
1988
|
/* @__PURE__ */ jsx8(
|
|
1909
1989
|
"input",
|
|
1910
1990
|
{
|
|
@@ -1932,7 +2012,7 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
1932
2012
|
className: "cfi-vi-mono",
|
|
1933
2013
|
value: newSegmentLabel,
|
|
1934
2014
|
placeholder: "e.g. Color",
|
|
1935
|
-
disabled: hasData || addingSegment,
|
|
2015
|
+
disabled: hasData || addingSegment || readOnly,
|
|
1936
2016
|
onChange: (e) => setNewSegmentLabel(e.target.value),
|
|
1937
2017
|
onKeyDown: (e) => {
|
|
1938
2018
|
if (e.key === "Enter" && newSegmentLabel.trim()) void handleAddSegment();
|
|
@@ -1947,7 +2027,7 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
1947
2027
|
{
|
|
1948
2028
|
className: "cfi-vi-input",
|
|
1949
2029
|
value: newSegmentDataType,
|
|
1950
|
-
disabled: hasData || addingSegment,
|
|
2030
|
+
disabled: hasData || addingSegment || readOnly,
|
|
1951
2031
|
onChange: (e) => setNewSegmentDataType(e.target.value),
|
|
1952
2032
|
children: [
|
|
1953
2033
|
/* @__PURE__ */ jsx8("option", { value: "string", children: "string" }),
|
|
@@ -1957,7 +2037,7 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
1957
2037
|
}
|
|
1958
2038
|
)
|
|
1959
2039
|
] }),
|
|
1960
|
-
/* @__PURE__ */ jsx8(Btn, { size: "sm", onClick: () => void handleAddSegment(), disabled: hasData || addingSegment || !newSegmentLabel.trim(), children: addingSegment ? "Adding\u2026" : "Add segment" })
|
|
2040
|
+
/* @__PURE__ */ jsx8(Btn, { size: "sm", onClick: () => void handleAddSegment(), disabled: hasData || addingSegment || readOnly || !newSegmentLabel.trim(), children: addingSegment ? "Adding\u2026" : "Add segment" })
|
|
1961
2041
|
] })
|
|
1962
2042
|
] }),
|
|
1963
2043
|
source.segments.length === 0 ? /* @__PURE__ */ jsx8("p", { className: "cfi-vi-muted", children: "No segments yet. Upload a sample file to auto-detect." }) : /* @__PURE__ */ jsxs8("table", { className: "cfi-vi-table", children: [
|
|
@@ -1983,7 +2063,7 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
1983
2063
|
className: "cfi-vi-icon-btn",
|
|
1984
2064
|
style: { color: "var(--cfi-color-error, #dc2626)" },
|
|
1985
2065
|
onClick: () => void handleDelete(seg.id),
|
|
1986
|
-
disabled: hasData,
|
|
2066
|
+
disabled: hasData || readOnly,
|
|
1987
2067
|
title: "Delete segment",
|
|
1988
2068
|
children: "\xD7"
|
|
1989
2069
|
}
|
|
@@ -1993,7 +2073,7 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
1993
2073
|
] })
|
|
1994
2074
|
] }),
|
|
1995
2075
|
tab === "mapping" && /* @__PURE__ */ jsxs8("div", { className: "cfi-vi-tabpanel", children: [
|
|
1996
|
-
source.layout === "tall" && /* @__PURE__ */ jsx8(TallLayoutMappings, { transport, source, locked: hasData }),
|
|
2076
|
+
source.layout === "tall" && /* @__PURE__ */ jsx8(TallLayoutMappings, { transport, source, locked: hasData || readOnly }),
|
|
1997
2077
|
/* @__PURE__ */ jsxs8("div", { children: [
|
|
1998
2078
|
/* @__PURE__ */ jsxs8("div", { className: "cfi-vi-row", children: [
|
|
1999
2079
|
/* @__PURE__ */ jsx8(Layers, { size: 16, className: "cfi-vi-muted", "aria-hidden": true }),
|
|
@@ -2035,13 +2115,14 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, classNa
|
|
|
2035
2115
|
mappableDimensions,
|
|
2036
2116
|
initialMappings: mappingRows,
|
|
2037
2117
|
onSave: handleSaveMapping,
|
|
2038
|
-
saving: savingMapping
|
|
2118
|
+
saving: savingMapping,
|
|
2119
|
+
locked: readOnly
|
|
2039
2120
|
}
|
|
2040
2121
|
)
|
|
2041
2122
|
] })
|
|
2042
2123
|
] }),
|
|
2043
|
-
tab === "aliases" && /* @__PURE__ */ jsx8("div", { className: "cfi-vi-tabpanel", children: /* @__PURE__ */ jsx8(ValueAliasesCard, { transport, sourceId: source.id, locked:
|
|
2044
|
-
tab === "danger" && /* @__PURE__ */ jsx8("div", { className: "cfi-vi-tabpanel", children: /* @__PURE__ */ jsxs8("div", { className: "cfi-vi-danger", children: [
|
|
2124
|
+
tab === "aliases" && /* @__PURE__ */ jsx8("div", { className: "cfi-vi-tabpanel", children: /* @__PURE__ */ jsx8(ValueAliasesCard, { transport, sourceId: source.id, locked: readOnly, isOverlay, targets: aliasTargets }) }),
|
|
2125
|
+
tab === "danger" && !readOnly && /* @__PURE__ */ jsx8("div", { className: "cfi-vi-tabpanel", children: /* @__PURE__ */ jsxs8("div", { className: "cfi-vi-danger", children: [
|
|
2045
2126
|
/* @__PURE__ */ jsxs8("div", { className: "cfi-vi-row", style: { color: "var(--cfi-color-error, #dc2626)" }, children: [
|
|
2046
2127
|
/* @__PURE__ */ jsx8(AlertTriangle3, { size: 16, "aria-hidden": true }),
|
|
2047
2128
|
/* @__PURE__ */ jsx8("h2", { className: "cfi-vi-h2", children: "Danger zone \u2014 clear source data" })
|
package/package.json
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@campfire-interactive/volume-intelligence-ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared Volume Intelligence UI components (volume import) for the Campfire Suite — embedded by the VI webapp and by consuming apps (OMSF) so there is one import surface, not two that drift.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.js"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"files": [
|
|
15
|
-
"dist"
|
|
16
|
-
],
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "tsup",
|
|
19
|
-
"dev": "tsup --watch"
|
|
20
|
-
},
|
|
21
|
-
"peerDependencies": {
|
|
22
|
-
"react": "^18.0.0 || ^19.0.0",
|
|
23
|
-
"react-dom": "^18.0.0 || ^19.0.0"
|
|
24
|
-
},
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"@campfire-interactive/design-tokens": "*",
|
|
27
|
-
"lucide-react": "^0.487.0"
|
|
28
|
-
},
|
|
29
|
-
"devDependencies": {
|
|
30
|
-
"@types/react": "^18.2.0",
|
|
31
|
-
"react": "^18.2.0",
|
|
32
|
-
"react-dom": "^18.2.0",
|
|
33
|
-
"tsup": "^8.0.0",
|
|
34
|
-
"typescript": "^5.3.3"
|
|
35
|
-
},
|
|
36
|
-
"publishConfig": {
|
|
37
|
-
"registry": "https://registry.npmjs.org",
|
|
38
|
-
"access": "public"
|
|
39
|
-
},
|
|
40
|
-
"engines": {
|
|
41
|
-
"node": ">=18.0.0"
|
|
42
|
-
}
|
|
43
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@campfire-interactive/volume-intelligence-ui",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Shared Volume Intelligence UI components (volume import) for the Campfire Suite — embedded by the VI webapp and by consuming apps (OMSF) so there is one import surface, not two that drift.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup",
|
|
19
|
+
"dev": "tsup --watch"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
23
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@campfire-interactive/design-tokens": "*",
|
|
27
|
+
"lucide-react": "^0.487.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/react": "^18.2.0",
|
|
31
|
+
"react": "^18.2.0",
|
|
32
|
+
"react-dom": "^18.2.0",
|
|
33
|
+
"tsup": "^8.0.0",
|
|
34
|
+
"typescript": "^5.3.3"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"registry": "https://registry.npmjs.org",
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|