@ifc-lite/viewer 1.17.4 → 1.18.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/.turbo/turbo-build.log +20 -17
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +630 -0
- package/DESKTOP_CONTRACT_VERSION +1 -1
- package/dist/assets/{basketViewActivator-BmnNtVfZ.js → basketViewActivator-Cm1QEk_R.js} +1 -1
- package/dist/assets/drawing-2d-DoxKMqbO.js +257 -0
- package/dist/assets/{exporters-ChAtBmlj.js → exporters-B_OBqIyD.js} +3479 -2845
- package/dist/assets/{geometry.worker-BQ0rzNo-.js → geometry.worker-xHHy-9DV.js} +1 -1
- package/dist/assets/ids-DQ5jY0E8.js +1 -0
- package/dist/assets/ifc-lite_bg-ADjKXSms.wasm +0 -0
- package/dist/assets/{index-Co8E2-FE.js → index-BKq-M3Mk.js} +55873 -40593
- package/dist/assets/index-COnQRuqY.css +1 -0
- package/dist/assets/{native-bridge-BRvbckFQ.js → native-bridge-SHXiQwFW.js} +104 -104
- package/dist/assets/sandbox-jez21HtV.js +9627 -0
- package/dist/assets/{server-client-BV8zHZ7Y.js → server-client-ncOQVNso.js} +1 -1
- package/dist/assets/{wasm-bridge-g01g7T9b.js → wasm-bridge-DyfBSB8z.js} +1 -1
- package/dist/index.html +8 -7
- package/index.html +1 -0
- package/package.json +13 -13
- package/src/App.tsx +16 -2
- package/src/apache-arrow.d.ts +30 -0
- package/src/components/viewer/AddElementPanel.tsx +758 -0
- package/src/components/viewer/BulkPropertyEditor.tsx +7 -0
- package/src/components/viewer/CesiumOverlay.tsx +62 -19
- package/src/components/viewer/ChatPanel.tsx +259 -93
- package/src/components/viewer/CommandPalette.tsx +56 -7
- package/src/components/viewer/EntityContextMenu.tsx +168 -4
- package/src/components/viewer/ExportChangesButton.tsx +25 -5
- package/src/components/viewer/ExportDialog.tsx +19 -1
- package/src/components/viewer/MainToolbar.tsx +73 -13
- package/src/components/viewer/PropertiesPanel.tsx +237 -23
- package/src/components/viewer/SearchInline.tsx +669 -0
- package/src/components/viewer/SearchModal.filter.builder.tsx +766 -0
- package/src/components/viewer/SearchModal.filter.tsx +514 -0
- package/src/components/viewer/SearchModal.text.tsx +388 -0
- package/src/components/viewer/SearchModal.tsx +235 -0
- package/src/components/viewer/SettingsPage.tsx +252 -101
- package/src/components/viewer/ThemeSwitch.tsx +63 -7
- package/src/components/viewer/ToolOverlays.tsx +5 -0
- package/src/components/viewer/ViewerLayout.tsx +25 -4
- package/src/components/viewer/Viewport.tsx +25 -3
- package/src/components/viewer/ViewportContainer.tsx +51 -64
- package/src/components/viewer/ViewportOverlays.tsx +5 -2
- package/src/components/viewer/annotations/AnnotationDropInput.tsx +203 -0
- package/src/components/viewer/annotations/AnnotationLayer.tsx +287 -0
- package/src/components/viewer/annotations/AnnotationPin.tsx +90 -0
- package/src/components/viewer/annotations/AnnotationPopover.tsx +296 -0
- package/src/components/viewer/bcf/BCFTopicDetail.tsx +4 -4
- package/src/components/viewer/chat/ModelSelector.tsx +90 -54
- package/src/components/viewer/lists/ListPanel.tsx +14 -21
- package/src/components/viewer/properties/GeoreferencingPanel.tsx +113 -51
- package/src/components/viewer/properties/LocationMap.tsx +9 -7
- package/src/components/viewer/properties/ModelMetadataPanel.tsx +1 -1
- package/src/components/viewer/properties/RawStepCard.tsx +332 -0
- package/src/components/viewer/properties/RawStepRow.tsx +261 -0
- package/src/components/viewer/properties/ScheduleCard.tsx +224 -0
- package/src/components/viewer/properties/TaskEditCard.tsx +510 -0
- package/src/components/viewer/properties/raw-step-format.ts +193 -0
- package/src/components/viewer/schedule/AnimationSettingsPopover.tsx +542 -0
- package/src/components/viewer/schedule/GanttDependencyArrows.tsx +89 -0
- package/src/components/viewer/schedule/GanttDragTooltip.tsx +48 -0
- package/src/components/viewer/schedule/GanttEmptyState.tsx +97 -0
- package/src/components/viewer/schedule/GanttPanel.tsx +295 -0
- package/src/components/viewer/schedule/GanttTaskBar.tsx +199 -0
- package/src/components/viewer/schedule/GanttTaskTree.tsx +250 -0
- package/src/components/viewer/schedule/GanttTimeline.tsx +305 -0
- package/src/components/viewer/schedule/GanttToolbar.tsx +406 -0
- package/src/components/viewer/schedule/GenerateAdvancedPanel.tsx +147 -0
- package/src/components/viewer/schedule/GenerateScheduleDialog.tsx +392 -0
- package/src/components/viewer/schedule/HeightStrategyPanel.tsx +120 -0
- package/src/components/viewer/schedule/generate-schedule.test.ts +439 -0
- package/src/components/viewer/schedule/generate-schedule.ts +648 -0
- package/src/components/viewer/schedule/schedule-animator.test.ts +452 -0
- package/src/components/viewer/schedule/schedule-animator.ts +488 -0
- package/src/components/viewer/schedule/schedule-selection.test.ts +148 -0
- package/src/components/viewer/schedule/schedule-selection.ts +163 -0
- package/src/components/viewer/schedule/schedule-utils.ts +223 -0
- package/src/components/viewer/schedule/useConstructionSequence.ts +156 -0
- package/src/components/viewer/schedule/useGanttBarDrag.test.ts +90 -0
- package/src/components/viewer/schedule/useGanttBarDrag.ts +305 -0
- package/src/components/viewer/schedule/useGanttSelection3DHighlight.ts +152 -0
- package/src/components/viewer/schedule/useOverlayCompositor.ts +108 -0
- package/src/components/viewer/selectionHandlers.ts +446 -0
- package/src/components/viewer/tools/AddElementOverlay.tsx +540 -0
- package/src/components/viewer/tools/SectionCapControls.tsx +237 -0
- package/src/components/viewer/tools/SectionPanel.tsx +39 -18
- package/src/components/viewer/useAnimationLoop.ts +9 -1
- package/src/components/viewer/useDuplicateShortcut.ts +77 -0
- package/src/components/viewer/useMouseControls.ts +9 -1
- package/src/components/viewer/useRenderUpdates.ts +1 -1
- package/src/hooks/ids/idsDataAccessor.ts +60 -24
- package/src/hooks/ingest/viewerModelIngest.ts +7 -2
- package/src/hooks/useIfcFederation.ts +326 -71
- package/src/hooks/useIfcLoader.ts +23 -10
- package/src/hooks/useKeyboardShortcuts.ts +25 -0
- package/src/hooks/useSandbox.ts +1 -1
- package/src/hooks/useSearchIndex.ts +125 -0
- package/src/hooks/useViewControls.ts +13 -5
- package/src/index.css +550 -10
- package/src/lib/desktop-entitlement.ts +2 -4
- package/src/lib/geo/cesium-bridge.ts +15 -7
- package/src/lib/geo/effective-georef.test.ts +73 -0
- package/src/lib/geo/effective-georef.ts +111 -0
- package/src/lib/geo/reproject.ts +105 -19
- package/src/lib/llm/byok-guard.test.ts +77 -0
- package/src/lib/llm/byok-guard.ts +39 -0
- package/src/lib/llm/free-models.test.ts +0 -6
- package/src/lib/llm/models.ts +104 -42
- package/src/lib/llm/stream-client.ts +74 -110
- package/src/lib/llm/stream-direct.test.ts +130 -0
- package/src/lib/llm/stream-direct.ts +316 -0
- package/src/lib/llm/system-prompt.test.ts +14 -0
- package/src/lib/llm/system-prompt.ts +102 -1
- package/src/lib/llm/types.ts +20 -2
- package/src/lib/recent-files.ts +38 -4
- package/src/lib/scripts/templates/bim-globals.d.ts +136 -114
- package/src/lib/scripts/templates/construction-schedule.ts +223 -0
- package/src/lib/scripts/templates.ts +7 -0
- package/src/lib/search/common-ifc-types.ts +36 -0
- package/src/lib/search/filter-evaluate.test.ts +537 -0
- package/src/lib/search/filter-evaluate.ts +610 -0
- package/src/lib/search/filter-rules.test.ts +119 -0
- package/src/lib/search/filter-rules.ts +198 -0
- package/src/lib/search/filter-schema.test.ts +233 -0
- package/src/lib/search/filter-schema.ts +146 -0
- package/src/lib/search/recent-searches.test.ts +116 -0
- package/src/lib/search/recent-searches.ts +93 -0
- package/src/lib/search/result-export.test.ts +101 -0
- package/src/lib/search/result-export.ts +104 -0
- package/src/lib/search/saved-filters.test.ts +118 -0
- package/src/lib/search/saved-filters.ts +154 -0
- package/src/lib/search/tier0-scan.test.ts +196 -0
- package/src/lib/search/tier0-scan.ts +237 -0
- package/src/lib/search/tier1-index.test.ts +242 -0
- package/src/lib/search/tier1-index.ts +448 -0
- package/src/main.tsx +1 -10
- package/src/sdk/adapters/export-adapter.test.ts +434 -1
- package/src/sdk/adapters/export-adapter.ts +404 -1
- package/src/sdk/adapters/export-schedule-splice.test.ts +127 -0
- package/src/sdk/adapters/export-schedule-splice.ts +87 -0
- package/src/sdk/adapters/model-compat.ts +8 -2
- package/src/sdk/adapters/schedule-adapter.ts +73 -0
- package/src/sdk/adapters/store-adapter.ts +201 -0
- package/src/sdk/adapters/visibility-adapter.ts +3 -0
- package/src/sdk/local-backend.ts +16 -8
- package/src/services/api-keys.ts +73 -0
- package/src/services/desktop-export.ts +3 -1
- package/src/services/desktop-native-metadata.ts +41 -18
- package/src/services/file-dialog.ts +4 -1
- package/src/services/tauri-modules.d.ts +25 -0
- package/src/store/basketVisibleSet.ts +3 -0
- package/src/store/constants.ts +20 -2
- package/src/store/globalId.ts +4 -1
- package/src/store/index.ts +82 -6
- package/src/store/slices/addElementMeshes.ts +365 -0
- package/src/store/slices/addElementSlice.ts +275 -0
- package/src/store/slices/annotationsSlice.test.ts +133 -0
- package/src/store/slices/annotationsSlice.ts +251 -0
- package/src/store/slices/cesiumSlice.ts +5 -0
- package/src/store/slices/chatSlice.test.ts +6 -76
- package/src/store/slices/chatSlice.ts +17 -58
- package/src/store/slices/dataSlice.test.ts +23 -4
- package/src/store/slices/dataSlice.ts +1 -1
- package/src/store/slices/modelSlice.test.ts +67 -9
- package/src/store/slices/modelSlice.ts +39 -7
- package/src/store/slices/mutationSlice.ts +964 -3
- package/src/store/slices/overlayCompositor.test.ts +164 -0
- package/src/store/slices/overlaySlice.test.ts +93 -0
- package/src/store/slices/overlaySlice.ts +151 -0
- package/src/store/slices/pinboardSlice.test.ts +6 -1
- package/src/store/slices/playbackSlice.ts +128 -0
- package/src/store/slices/schedule-edit-helpers.test.ts +97 -0
- package/src/store/slices/schedule-edit-helpers.ts +179 -0
- package/src/store/slices/scheduleSlice.test.ts +694 -0
- package/src/store/slices/scheduleSlice.ts +1330 -0
- package/src/store/slices/searchSlice.test.ts +342 -0
- package/src/store/slices/searchSlice.ts +341 -0
- package/src/store/slices/sectionSlice.test.ts +87 -7
- package/src/store/slices/sectionSlice.ts +151 -5
- package/src/store/slices/selectionSlice.test.ts +46 -0
- package/src/store/slices/selectionSlice.ts +20 -0
- package/src/store/slices/uiSlice.ts +28 -5
- package/src/store/types.ts +26 -0
- package/src/store.ts +14 -0
- package/src/utils/nativeSpatialDataStore.ts +4 -1
- package/src/utils/viewportUtils.ts +7 -2
- package/src/vite-env.d.ts +0 -4
- package/dist/assets/drawing-2d-gWfpdfYe.js +0 -257
- package/dist/assets/ids-B4jTqB1O.js +0 -1
- package/dist/assets/ifc-lite_bg-BX4E7TX8.wasm +0 -0
- package/dist/assets/index-DckuDqlv.css +0 -1
- package/dist/assets/sandbox-DZiNLNMk.js +0 -5933
- package/src/components/viewer/UpgradePage.tsx +0 -71
- package/src/lib/desktop/ClerkDesktopEntitlementSync.tsx +0 -175
- package/src/lib/llm/ClerkChatSync.tsx +0 -74
- package/src/lib/llm/clerk-auth.ts +0 -62
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ScheduleCard — surface 4D / construction-schedule data in the Inspector.
|
|
7
|
+
*
|
|
8
|
+
* Two complementary views, picked automatically based on the selection:
|
|
9
|
+
* • Selected entity is a *product* controlled by one or more IfcTasks →
|
|
10
|
+
* "Construction Schedule" card listing each controlling task with its
|
|
11
|
+
* start/finish/duration and parent work-schedule name.
|
|
12
|
+
* • Selected entity is itself an IfcTask / IfcWorkSchedule (rare in
|
|
13
|
+
* practice — these typically aren't pickable in the 3D view) → show
|
|
14
|
+
* its time data directly.
|
|
15
|
+
*
|
|
16
|
+
* The card pulls from the viewer's `scheduleSlice` (which holds both parsed
|
|
17
|
+
* and locally-generated schedules), so it lights up automatically the moment
|
|
18
|
+
* the user generates a schedule via the Gantt panel — no separate fetch.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { useMemo } from 'react';
|
|
22
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
|
23
|
+
import { CalendarClock, Diamond, Flag } from 'lucide-react';
|
|
24
|
+
import type { ScheduleExtraction, ScheduleTaskInfo } from '@ifc-lite/parser';
|
|
25
|
+
|
|
26
|
+
interface ScheduleCardProps {
|
|
27
|
+
/** Schedule data from the viewer's slice (parsed or generated). */
|
|
28
|
+
scheduleData: ScheduleExtraction | null;
|
|
29
|
+
/** Selected entity's local express ID. */
|
|
30
|
+
selectedExpressId: number | null;
|
|
31
|
+
/** Selected entity's globalId (used as a fallback when expressId === 0). */
|
|
32
|
+
selectedGlobalId?: string | null;
|
|
33
|
+
/**
|
|
34
|
+
* When true, the schedule was created via the Gantt panel's "Generate
|
|
35
|
+
* from storeys" dialog and isn't yet baked into the source IFC. We render
|
|
36
|
+
* a small "Generated locally" badge so users know the schedule will be
|
|
37
|
+
* spliced in on the next IFC export.
|
|
38
|
+
*/
|
|
39
|
+
isGenerated: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function ScheduleCard({
|
|
43
|
+
scheduleData,
|
|
44
|
+
selectedExpressId,
|
|
45
|
+
selectedGlobalId,
|
|
46
|
+
isGenerated,
|
|
47
|
+
}: ScheduleCardProps) {
|
|
48
|
+
const tasks = useMemo(
|
|
49
|
+
() => findControllingTasks(scheduleData, selectedExpressId, selectedGlobalId),
|
|
50
|
+
[scheduleData, selectedExpressId, selectedGlobalId],
|
|
51
|
+
);
|
|
52
|
+
const scheduleNames = useMemo(
|
|
53
|
+
() => buildScheduleNameLookup(scheduleData),
|
|
54
|
+
[scheduleData],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (tasks.length === 0) return null;
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Collapsible
|
|
61
|
+
defaultOpen
|
|
62
|
+
className="border-2 border-sky-200 dark:border-sky-800 bg-sky-50/20 dark:bg-sky-950/20 w-full max-w-full overflow-hidden"
|
|
63
|
+
>
|
|
64
|
+
<CollapsibleTrigger className="flex items-center gap-2 w-full p-2.5 hover:bg-sky-50 dark:hover:bg-sky-900/30 text-left transition-colors overflow-hidden">
|
|
65
|
+
<CalendarClock className="h-3.5 w-3.5 text-sky-600 dark:text-sky-400 shrink-0" />
|
|
66
|
+
<span className="font-bold text-xs text-sky-700 dark:text-sky-400 truncate flex-1 min-w-0">
|
|
67
|
+
Construction Schedule
|
|
68
|
+
</span>
|
|
69
|
+
{isGenerated && (
|
|
70
|
+
<span
|
|
71
|
+
className="flex items-center gap-1 text-[10px] font-medium bg-amber-100 dark:bg-amber-900/40 px-1.5 py-0.5 border border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-300 shrink-0"
|
|
72
|
+
title="Pending schedule edits — included on IFC export"
|
|
73
|
+
>
|
|
74
|
+
<span className="h-1.5 w-1.5 rounded-full bg-amber-500" aria-hidden />
|
|
75
|
+
Pending
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
<span className="text-[10px] font-mono bg-sky-100 dark:bg-sky-900/50 px-1.5 py-0.5 border border-sky-200 dark:border-sky-800 text-sky-700 dark:text-sky-300 shrink-0">
|
|
79
|
+
{tasks.length} {tasks.length === 1 ? 'task' : 'tasks'}
|
|
80
|
+
</span>
|
|
81
|
+
</CollapsibleTrigger>
|
|
82
|
+
<CollapsibleContent>
|
|
83
|
+
<div className="border-t-2 border-sky-200 dark:border-sky-800">
|
|
84
|
+
{isGenerated && (
|
|
85
|
+
<div className="px-3 py-1.5 text-[10px] text-amber-700 dark:text-amber-300 bg-amber-50/80 dark:bg-amber-900/20 border-b border-amber-200/60 dark:border-amber-800/50">
|
|
86
|
+
Generated locally — will be spliced into the next IFC export.
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
<div className="divide-y divide-sky-100 dark:divide-sky-900/30">
|
|
90
|
+
{tasks.map((task) => (
|
|
91
|
+
<TaskRow key={task.globalId} task={task} scheduleNames={scheduleNames} />
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</CollapsibleContent>
|
|
96
|
+
</Collapsible>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface TaskRowProps {
|
|
101
|
+
task: ScheduleTaskInfo;
|
|
102
|
+
scheduleNames: Map<string, string>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function TaskRow({ task, scheduleNames }: TaskRowProps) {
|
|
106
|
+
const start = formatDate(task.taskTime?.scheduleStart);
|
|
107
|
+
const finish = formatDate(task.taskTime?.scheduleFinish);
|
|
108
|
+
const duration = task.taskTime?.scheduleDuration;
|
|
109
|
+
const completion = task.taskTime?.completion;
|
|
110
|
+
const isCritical = task.taskTime?.isCritical === true;
|
|
111
|
+
const scheduleLabels = task.controllingScheduleGlobalIds
|
|
112
|
+
.map(gid => scheduleNames.get(gid))
|
|
113
|
+
.filter((s): s is string => Boolean(s));
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className="px-3 py-2 text-xs hover:bg-sky-50/50 dark:hover:bg-sky-900/20">
|
|
117
|
+
<div className="flex items-center gap-1.5 mb-1">
|
|
118
|
+
{task.isMilestone ? (
|
|
119
|
+
<Diamond className="h-3 w-3 text-amber-500 fill-amber-500 shrink-0" />
|
|
120
|
+
) : isCritical ? (
|
|
121
|
+
<Flag className="h-3 w-3 text-red-500 fill-red-500 shrink-0" />
|
|
122
|
+
) : null}
|
|
123
|
+
<span
|
|
124
|
+
className={
|
|
125
|
+
'font-medium text-foreground truncate ' + (isCritical ? 'text-red-600 dark:text-red-400' : '')
|
|
126
|
+
}
|
|
127
|
+
title={task.name}
|
|
128
|
+
>
|
|
129
|
+
{task.name || task.identification || task.globalId.slice(0, 12)}
|
|
130
|
+
</span>
|
|
131
|
+
{task.predefinedType && (
|
|
132
|
+
<span className="text-[9px] font-mono bg-sky-100 dark:bg-sky-900/50 px-1 py-0.5 border border-sky-200 dark:border-sky-800 text-sky-600 dark:text-sky-300 shrink-0">
|
|
133
|
+
{task.predefinedType}
|
|
134
|
+
</span>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
<div className="grid grid-cols-[minmax(60px,auto)_1fr] gap-x-2 gap-y-0.5 ml-1 text-[11px]">
|
|
138
|
+
{start && (
|
|
139
|
+
<>
|
|
140
|
+
<span className="text-muted-foreground">Start</span>
|
|
141
|
+
<span className="font-mono text-foreground/90">{start}</span>
|
|
142
|
+
</>
|
|
143
|
+
)}
|
|
144
|
+
{finish && (
|
|
145
|
+
<>
|
|
146
|
+
<span className="text-muted-foreground">Finish</span>
|
|
147
|
+
<span className="font-mono text-foreground/90">{finish}</span>
|
|
148
|
+
</>
|
|
149
|
+
)}
|
|
150
|
+
{duration && (
|
|
151
|
+
<>
|
|
152
|
+
<span className="text-muted-foreground">Duration</span>
|
|
153
|
+
<span className="font-mono text-foreground/90">{duration}</span>
|
|
154
|
+
</>
|
|
155
|
+
)}
|
|
156
|
+
{completion !== undefined && (
|
|
157
|
+
<>
|
|
158
|
+
<span className="text-muted-foreground">Complete</span>
|
|
159
|
+
<span className="font-mono text-foreground/90">{Math.round(completion)}%</span>
|
|
160
|
+
</>
|
|
161
|
+
)}
|
|
162
|
+
{scheduleLabels.length > 0 && (
|
|
163
|
+
<>
|
|
164
|
+
<span className="text-muted-foreground">Schedule</span>
|
|
165
|
+
<span className="text-foreground/90 truncate" title={scheduleLabels.join(', ')}>
|
|
166
|
+
{scheduleLabels.join(', ')}
|
|
167
|
+
</span>
|
|
168
|
+
</>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Find all tasks whose products include the selected entity.
|
|
177
|
+
*
|
|
178
|
+
* Federation-aware: prefer `productGlobalIds` whenever we know the entity's
|
|
179
|
+
* globalId (and the task carries globalIds of its own) — local expressIds can
|
|
180
|
+
* collide across federated models, so matching by globalId first is the safe
|
|
181
|
+
* default. Fall back to `productExpressIds` only for schedules that never
|
|
182
|
+
* recorded globalIds (legacy / headless extraction paths).
|
|
183
|
+
*/
|
|
184
|
+
function findControllingTasks(
|
|
185
|
+
data: ScheduleExtraction | null,
|
|
186
|
+
selectedExpressId: number | null,
|
|
187
|
+
selectedGlobalId: string | null | undefined,
|
|
188
|
+
): ScheduleTaskInfo[] {
|
|
189
|
+
if (!data || data.tasks.length === 0) return [];
|
|
190
|
+
if (selectedExpressId === null && !selectedGlobalId) return [];
|
|
191
|
+
const out: ScheduleTaskInfo[] = [];
|
|
192
|
+
for (const task of data.tasks) {
|
|
193
|
+
const taskHasGlobalIds = task.productGlobalIds.some(Boolean);
|
|
194
|
+
if (selectedGlobalId && taskHasGlobalIds) {
|
|
195
|
+
if (task.productGlobalIds.includes(selectedGlobalId)) out.push(task);
|
|
196
|
+
// When globalIds are the authoritative side, do NOT also match on
|
|
197
|
+
// expressId — a collision across models would produce a false positive.
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (selectedExpressId !== null && selectedExpressId > 0
|
|
201
|
+
&& task.productExpressIds.includes(selectedExpressId)) {
|
|
202
|
+
out.push(task);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function buildScheduleNameLookup(data: ScheduleExtraction | null): Map<string, string> {
|
|
209
|
+
const map = new Map<string, string>();
|
|
210
|
+
if (!data) return map;
|
|
211
|
+
for (const ws of data.workSchedules) {
|
|
212
|
+
if (ws.globalId && ws.name) map.set(ws.globalId, ws.name);
|
|
213
|
+
}
|
|
214
|
+
return map;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function formatDate(iso: string | undefined): string | undefined {
|
|
218
|
+
if (!iso) return undefined;
|
|
219
|
+
const t = Date.parse(iso);
|
|
220
|
+
if (Number.isNaN(t)) return iso;
|
|
221
|
+
return new Date(t).toLocaleDateString(undefined, {
|
|
222
|
+
year: 'numeric', month: 'short', day: 'numeric',
|
|
223
|
+
});
|
|
224
|
+
}
|