@elaraai/east-ui 1.0.85 → 1.0.87
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/src/buttons/button/index.d.ts +4 -0
- package/dist/src/buttons/button/index.d.ts.map +1 -1
- package/dist/src/buttons/button/index.js +8 -1
- package/dist/src/buttons/button/index.js.map +1 -1
- package/dist/src/buttons/button/types.d.ts +6 -5
- package/dist/src/buttons/button/types.d.ts.map +1 -1
- package/dist/src/buttons/button/types.js.map +1 -1
- package/dist/src/buttons/icon-button/index.d.ts +5 -5
- package/dist/src/buttons/icon-button/index.d.ts.map +1 -1
- package/dist/src/buttons/icon-button/index.js +8 -2
- package/dist/src/buttons/icon-button/index.js.map +1 -1
- package/dist/src/buttons/icon-button/types.d.ts +8 -12
- package/dist/src/buttons/icon-button/types.d.ts.map +1 -1
- package/dist/src/buttons/icon-button/types.js +2 -2
- package/dist/src/buttons/icon-button/types.js.map +1 -1
- package/dist/src/buttons/toggle/index.d.ts +4 -0
- package/dist/src/buttons/toggle/index.d.ts.map +1 -1
- package/dist/src/buttons/toggle/index.js +6 -1
- package/dist/src/buttons/toggle/index.js.map +1 -1
- package/dist/src/buttons/toggle/types.d.ts +3 -2
- package/dist/src/buttons/toggle/types.d.ts.map +1 -1
- package/dist/src/buttons/toggle/types.js.map +1 -1
- package/dist/src/charts/chart/index.d.ts.map +1 -1
- package/dist/src/charts/chart/index.js +3 -3
- package/dist/src/charts/chart/index.js.map +1 -1
- package/dist/src/charts/spec/index.d.ts.map +1 -1
- package/dist/src/charts/spec/index.js +5 -2
- package/dist/src/charts/spec/index.js.map +1 -1
- package/dist/src/collections/deck/index.d.ts.map +1 -1
- package/dist/src/collections/deck/index.js +2 -2
- package/dist/src/collections/deck/index.js.map +1 -1
- package/dist/src/collections/index.d.ts +0 -6
- package/dist/src/collections/index.d.ts.map +1 -1
- package/dist/src/collections/index.js +0 -6
- package/dist/src/collections/index.js.map +1 -1
- package/dist/src/collections/library/index.d.ts +1 -1
- package/dist/src/collections/library/index.d.ts.map +1 -1
- package/dist/src/collections/library/index.js +2 -2
- package/dist/src/collections/library/index.js.map +1 -1
- package/dist/src/collections/schematic/index.d.ts.map +1 -1
- package/dist/src/collections/schematic/index.js +2 -2
- package/dist/src/collections/schematic/index.js.map +1 -1
- package/dist/src/collections/table/index.d.ts +1 -1
- package/dist/src/collections/table/index.d.ts.map +1 -1
- package/dist/src/collections/table/index.js +2 -2
- package/dist/src/collections/table/index.js.map +1 -1
- package/dist/src/collections/tree-view/index.d.ts +11 -32
- package/dist/src/collections/tree-view/index.d.ts.map +1 -1
- package/dist/src/collections/tree-view/index.js +15 -10
- package/dist/src/collections/tree-view/index.js.map +1 -1
- package/dist/src/component.d.ts +673 -9344
- package/dist/src/component.d.ts.map +1 -1
- package/dist/src/component.js +5 -259
- package/dist/src/component.js.map +1 -1
- package/dist/src/contracts/approval.d.ts +6 -8
- package/dist/src/contracts/approval.d.ts.map +1 -1
- package/dist/src/contracts/approval.js +6 -8
- package/dist/src/contracts/approval.js.map +1 -1
- package/dist/src/contracts/drag.d.ts +1 -1
- package/dist/src/contracts/drag.d.ts.map +1 -1
- package/dist/src/contracts/drag.js +4 -5
- package/dist/src/contracts/drag.js.map +1 -1
- package/dist/src/contracts/editing.d.ts +19 -19
- package/dist/src/contracts/editing.js +14 -14
- package/dist/src/contracts/fields.d.ts +471 -0
- package/dist/src/contracts/fields.d.ts.map +1 -0
- package/dist/src/contracts/fields.js +492 -0
- package/dist/src/contracts/fields.js.map +1 -0
- package/dist/src/contracts/index.d.ts +1 -0
- package/dist/src/contracts/index.d.ts.map +1 -1
- package/dist/src/contracts/index.js +1 -0
- package/dist/src/contracts/index.js.map +1 -1
- package/dist/src/contracts/indexed-source.d.ts +118 -0
- package/dist/src/contracts/indexed-source.d.ts.map +1 -0
- package/dist/src/contracts/indexed-source.js +89 -0
- package/dist/src/contracts/indexed-source.js.map +1 -0
- package/dist/src/contracts/pick.d.ts +0 -16
- package/dist/src/contracts/pick.d.ts.map +1 -1
- package/dist/src/contracts/review.d.ts +4 -6
- package/dist/src/contracts/review.d.ts.map +1 -1
- package/dist/src/contracts/review.js +4 -6
- package/dist/src/contracts/review.js.map +1 -1
- package/dist/src/contracts/source.d.ts +2 -2
- package/dist/src/contracts/source.d.ts.map +1 -1
- package/dist/src/contracts/source.js.map +1 -1
- package/dist/src/contracts/states.d.ts +1 -3
- package/dist/src/contracts/states.d.ts.map +1 -1
- package/dist/src/contracts/states.js +1 -3
- package/dist/src/contracts/states.js.map +1 -1
- package/dist/src/display/editable-chip/index.d.ts +2 -0
- package/dist/src/display/editable-chip/index.d.ts.map +1 -1
- package/dist/src/display/editable-chip/index.js +4 -1
- package/dist/src/display/editable-chip/index.js.map +1 -1
- package/dist/src/display/icon/index.d.ts +14 -31
- package/dist/src/display/icon/index.d.ts.map +1 -1
- package/dist/src/display/icon/index.js +15 -19
- package/dist/src/display/icon/index.js.map +1 -1
- package/dist/src/display/icon/solid.d.ts +24 -0
- package/dist/src/display/icon/solid.d.ts.map +1 -0
- package/dist/src/display/icon/solid.js +39 -0
- package/dist/src/display/icon/solid.js.map +1 -0
- package/dist/src/display/icon/types.d.ts +14 -38
- package/dist/src/display/icon/types.d.ts.map +1 -1
- package/dist/src/display/icon/types.js +6 -15
- package/dist/src/display/icon/types.js.map +1 -1
- package/dist/src/display/metric-chip/index.d.ts +2 -0
- package/dist/src/display/metric-chip/index.d.ts.map +1 -1
- package/dist/src/display/metric-chip/index.js +4 -1
- package/dist/src/display/metric-chip/index.js.map +1 -1
- package/dist/src/display/stat/index.d.ts +2 -0
- package/dist/src/display/stat/index.d.ts.map +1 -1
- package/dist/src/display/stat/index.js +4 -1
- package/dist/src/display/stat/index.js.map +1 -1
- package/dist/src/display/stat/types.d.ts +3 -5
- package/dist/src/display/stat/types.d.ts.map +1 -1
- package/dist/src/display/stat/types.js +5 -2
- package/dist/src/display/stat/types.js.map +1 -1
- package/dist/src/feedback/banner/index.d.ts +5 -3
- package/dist/src/feedback/banner/index.d.ts.map +1 -1
- package/dist/src/feedback/banner/index.js +4 -1
- package/dist/src/feedback/banner/index.js.map +1 -1
- package/dist/src/feedback/empty-state/index.d.ts +16 -19
- package/dist/src/feedback/empty-state/index.d.ts.map +1 -1
- package/dist/src/feedback/empty-state/index.js +17 -10
- package/dist/src/feedback/empty-state/index.js.map +1 -1
- package/dist/src/feedback/status/index.d.ts +5 -3
- package/dist/src/feedback/status/index.d.ts.map +1 -1
- package/dist/src/feedback/status/index.js +4 -1
- package/dist/src/feedback/status/index.js.map +1 -1
- package/dist/src/index.d.ts +5 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +12 -11
- package/dist/src/index.js.map +1 -1
- package/dist/src/internal.d.ts +23 -16
- package/dist/src/internal.d.ts.map +1 -1
- package/dist/src/internal.js +29 -13
- package/dist/src/internal.js.map +1 -1
- package/dist/src/navigation/index.d.ts +0 -2
- package/dist/src/navigation/index.d.ts.map +1 -1
- package/dist/src/navigation/index.js +0 -2
- package/dist/src/navigation/index.js.map +1 -1
- package/dist/src/navigation/nav-list/index.d.ts +2 -0
- package/dist/src/navigation/nav-list/index.d.ts.map +1 -1
- package/dist/src/navigation/nav-list/index.js +4 -1
- package/dist/src/navigation/nav-list/index.js.map +1 -1
- package/dist/src/navigation/nav-list/types.d.ts +3 -14
- package/dist/src/navigation/nav-list/types.d.ts.map +1 -1
- package/dist/src/navigation/nav-list/types.js.map +1 -1
- package/dist/src/overlays/command-palette/index.d.ts +2 -0
- package/dist/src/overlays/command-palette/index.d.ts.map +1 -1
- package/dist/src/overlays/command-palette/index.js +5 -0
- package/dist/src/overlays/command-palette/index.js.map +1 -1
- package/dist/src/overlays/command-palette/types.d.ts +0 -8
- package/dist/src/overlays/command-palette/types.d.ts.map +1 -1
- package/dist/src/pick/index.d.ts +1 -17
- package/dist/src/pick/index.d.ts.map +1 -1
- package/dist/src/pick/index.js +1 -1
- package/dist/src/platform/slice/index.d.ts +1324 -2039
- package/dist/src/platform/slice/index.d.ts.map +1 -1
- package/dist/src/platform/slice/index.js +55 -74
- package/dist/src/platform/slice/index.js.map +1 -1
- package/dist/src/platform/slice/local.d.ts +743 -0
- package/dist/src/platform/slice/local.d.ts.map +1 -0
- package/dist/src/platform/slice/local.js +133 -0
- package/dist/src/platform/slice/local.js.map +1 -0
- package/dist/src/platform/slice/record-chrome.d.ts +211 -0
- package/dist/src/platform/slice/record-chrome.d.ts.map +1 -0
- package/dist/src/platform/slice/record-chrome.js +52 -0
- package/dist/src/platform/slice/record-chrome.js.map +1 -0
- package/dist/src/platform/slice/record-interval.d.ts +24 -0
- package/dist/src/platform/slice/record-interval.d.ts.map +1 -0
- package/dist/src/platform/slice/record-interval.js +61 -0
- package/dist/src/platform/slice/record-interval.js.map +1 -0
- package/dist/src/platform/slice/record-plan.d.ts +54 -0
- package/dist/src/platform/slice/record-plan.d.ts.map +1 -0
- package/dist/src/platform/slice/record-plan.js +139 -0
- package/dist/src/platform/slice/record-plan.js.map +1 -0
- package/dist/src/platform/slice/record-types.d.ts +2739 -0
- package/dist/src/platform/slice/record-types.d.ts.map +1 -0
- package/dist/src/platform/slice/record-types.js +209 -0
- package/dist/src/platform/slice/record-types.js.map +1 -0
- package/dist/src/platform/slice/record.d.ts +109 -0
- package/dist/src/platform/slice/record.d.ts.map +1 -0
- package/dist/src/platform/slice/record.js +210 -0
- package/dist/src/platform/slice/record.js.map +1 -0
- package/dist/src/reactive/match.d.ts +4 -6
- package/dist/src/reactive/match.d.ts.map +1 -1
- package/dist/src/reactive/match.js +4 -6
- package/dist/src/reactive/match.js.map +1 -1
- package/dist/src/runtime/collections/deck.d.ts.map +1 -1
- package/dist/src/runtime/collections/index.d.ts +0 -6
- package/dist/src/runtime/collections/index.d.ts.map +1 -1
- package/dist/src/runtime/collections/index.js +0 -6
- package/dist/src/runtime/collections/index.js.map +1 -1
- package/dist/src/runtime/display/icon.d.ts +6 -5
- package/dist/src/runtime/display/icon.d.ts.map +1 -1
- package/dist/src/runtime/display/icon.js +6 -5
- package/dist/src/runtime/display/icon.js.map +1 -1
- package/dist/src/runtime/feedback/empty-state.d.ts +5 -5
- package/dist/src/runtime/feedback/empty-state.js +5 -5
- package/dist/src/runtime/layout/dock.d.ts +2 -1
- package/dist/src/runtime/layout/dock.d.ts.map +1 -1
- package/dist/src/runtime/layout/dock.js +2 -1
- package/dist/src/runtime/layout/dock.js.map +1 -1
- package/dist/src/runtime/navigation/index.d.ts +0 -3
- package/dist/src/runtime/navigation/index.d.ts.map +1 -1
- package/dist/src/runtime/navigation/index.js +0 -3
- package/dist/src/runtime/navigation/index.js.map +1 -1
- package/dist/src/runtime/reactive/match.d.ts +2 -3
- package/dist/src/runtime/reactive/match.d.ts.map +1 -1
- package/dist/src/runtime/reactive/match.js +2 -3
- package/dist/src/runtime/reactive/match.js.map +1 -1
- package/dist/src/runtime/record-slice-source.d.ts +78 -0
- package/dist/src/runtime/record-slice-source.d.ts.map +1 -0
- package/dist/src/runtime/record-slice-source.js +201 -0
- package/dist/src/runtime/record-slice-source.js.map +1 -0
- package/dist/src/runtime/record-slice.d.ts +110 -0
- package/dist/src/runtime/record-slice.d.ts.map +1 -0
- package/dist/src/runtime/record-slice.js +374 -0
- package/dist/src/runtime/record-slice.js.map +1 -0
- package/dist/src/runtime/slice.d.ts +11 -13
- package/dist/src/runtime/slice.d.ts.map +1 -1
- package/dist/src/runtime/slice.js +41 -93
- package/dist/src/runtime/slice.js.map +1 -1
- package/dist/src/shared/plot-gutter.d.ts +1 -1
- package/dist/src/shared/plot-gutter.js +1 -1
- package/dist/src/shared/resolve-tag.d.ts +23 -0
- package/dist/src/shared/resolve-tag.d.ts.map +1 -0
- package/dist/src/shared/resolve-tag.js +26 -0
- package/dist/src/shared/resolve-tag.js.map +1 -0
- package/dist/src/shared/tree.d.ts +1 -1
- package/dist/src/shared/tree.js +1 -1
- package/dist/src/slice/breakdown/index.d.ts +1 -1
- package/dist/src/slice/breakdown/index.d.ts.map +1 -1
- package/dist/src/slice/breakdown/index.js +3 -3
- package/dist/src/slice/breakdown/index.js.map +1 -1
- package/dist/src/slice/cohort/index.d.ts +4 -5
- package/dist/src/slice/cohort/index.d.ts.map +1 -1
- package/dist/src/slice/cohort/index.js +6 -7
- package/dist/src/slice/cohort/index.js.map +1 -1
- package/dist/src/slice/filter/index.d.ts +1 -1
- package/dist/src/slice/filter/index.d.ts.map +1 -1
- package/dist/src/slice/filter/index.js +3 -3
- package/dist/src/slice/filter/index.js.map +1 -1
- package/dist/src/slice/index.d.ts +20 -2230
- package/dist/src/slice/index.d.ts.map +1 -1
- package/dist/src/slice/legend/index.d.ts +2 -2
- package/dist/src/slice/legend/index.d.ts.map +1 -1
- package/dist/src/slice/legend/index.js +4 -4
- package/dist/src/slice/legend/index.js.map +1 -1
- package/dist/src/slice/rail/index.d.ts +7 -4
- package/dist/src/slice/rail/index.d.ts.map +1 -1
- package/dist/src/slice/rail/index.js +10 -5
- package/dist/src/slice/rail/index.js.map +1 -1
- package/dist/src/slice/range/index.d.ts +1 -1
- package/dist/src/slice/range/index.d.ts.map +1 -1
- package/dist/src/slice/range/index.js +3 -3
- package/dist/src/slice/range/index.js.map +1 -1
- package/dist/src/slice/record/index.d.ts +171 -0
- package/dist/src/slice/record/index.d.ts.map +1 -0
- package/dist/src/slice/record/index.js +10 -0
- package/dist/src/slice/record/index.js.map +1 -0
- package/dist/src/slice/search/index.d.ts +6 -6
- package/dist/src/slice/search/index.d.ts.map +1 -1
- package/dist/src/slice/search/index.js +7 -7
- package/dist/src/slice/search/index.js.map +1 -1
- package/dist/src/slice/summary/index.d.ts +1 -1
- package/dist/src/slice/summary/index.d.ts.map +1 -1
- package/dist/src/slice/summary/index.js +3 -3
- package/dist/src/slice/summary/index.js.map +1 -1
- package/dist/src/typography/list/index.d.ts +2 -8
- package/dist/src/typography/list/index.d.ts.map +1 -1
- package/dist/src/typography/list/index.js +4 -1
- package/dist/src/typography/list/index.js.map +1 -1
- package/dist/src/typography/list/types.d.ts +0 -8
- package/dist/src/typography/list/types.d.ts.map +1 -1
- package/dist/test/collections/deck.examples.d.ts.map +1 -1
- package/dist/test/collections/deck.examples.js +6 -7
- package/dist/test/collections/deck.examples.js.map +1 -1
- package/dist/test/collections/index.d.ts +0 -1
- package/dist/test/collections/index.d.ts.map +1 -1
- package/dist/test/collections/index.js +0 -1
- package/dist/test/collections/index.js.map +1 -1
- package/dist/test/collections/library.examples.d.ts.map +1 -1
- package/dist/test/collections/library.examples.js +6 -7
- package/dist/test/collections/library.examples.js.map +1 -1
- package/dist/test/collections/schematic.examples.d.ts.map +1 -1
- package/dist/test/collections/schematic.examples.js +11 -19
- package/dist/test/collections/schematic.examples.js.map +1 -1
- package/dist/test/collections/tree-view.examples.js +2 -2
- package/dist/test/contracts/editing.examples.d.ts +1 -0
- package/dist/test/contracts/editing.examples.d.ts.map +1 -1
- package/dist/test/contracts/editing.examples.js +37 -6
- package/dist/test/contracts/editing.examples.js.map +1 -1
- package/dist/test/contracts/fields.examples.d.ts +7 -0
- package/dist/test/contracts/fields.examples.d.ts.map +1 -0
- package/dist/test/contracts/fields.examples.js +42 -0
- package/dist/test/contracts/fields.examples.js.map +1 -0
- package/dist/test/display/avatar.examples.d.ts.map +1 -1
- package/dist/test/display/avatar.examples.js +3 -3
- package/dist/test/display/avatar.examples.js.map +1 -1
- package/dist/test/display/icon.examples.d.ts.map +1 -1
- package/dist/test/display/icon.examples.js +12 -11
- package/dist/test/display/icon.examples.js.map +1 -1
- package/dist/test/feedback/empty-state.examples.js +9 -9
- package/dist/test/feedback/empty-state.examples.js.map +1 -1
- package/dist/test/layout/dock.examples.d.ts +0 -8
- package/dist/test/layout/dock.examples.d.ts.map +1 -1
- package/dist/test/layout/dock.examples.js +2 -42
- package/dist/test/layout/dock.examples.js.map +1 -1
- package/dist/test/layout/snap-grid.examples.d.ts.map +1 -1
- package/dist/test/layout/snap-grid.examples.js +18 -28
- package/dist/test/layout/snap-grid.examples.js.map +1 -1
- package/dist/test/navigation/index.d.ts +0 -2
- package/dist/test/navigation/index.d.ts.map +1 -1
- package/dist/test/navigation/index.js +0 -2
- package/dist/test/navigation/index.js.map +1 -1
- package/dist/test/navigation/nav-list.examples.js.map +1 -1
- package/dist/test/platform/index.d.ts +1 -0
- package/dist/test/platform/index.d.ts.map +1 -1
- package/dist/test/platform/index.js +1 -0
- package/dist/test/platform/index.js.map +1 -1
- package/dist/test/platform/slice-local.examples.d.ts +2 -0
- package/dist/test/platform/slice-local.examples.d.ts.map +1 -0
- package/dist/test/platform/slice-local.examples.js +31 -0
- package/dist/test/platform/slice-local.examples.js.map +1 -0
- package/dist/test/platform/slice.examples.js +61 -61
- package/dist/test/platform/slice.examples.js.map +1 -1
- package/dist/test/slice/slice.examples.d.ts +0 -1
- package/dist/test/slice/slice.examples.d.ts.map +1 -1
- package/dist/test/slice/slice.examples.js +67 -181
- package/dist/test/slice/slice.examples.js.map +1 -1
- package/package.json +4 -4
- package/dist/src/collections/board/index.d.ts +0 -855
- package/dist/src/collections/board/index.d.ts.map +0 -1
- package/dist/src/collections/board/index.js +0 -416
- package/dist/src/collections/board/index.js.map +0 -1
- package/dist/src/collections/board/types.d.ts +0 -111
- package/dist/src/collections/board/types.d.ts.map +0 -1
- package/dist/src/collections/board/types.js +0 -89
- package/dist/src/collections/board/types.js.map +0 -1
- package/dist/src/collections/calendar/index.d.ts +0 -446
- package/dist/src/collections/calendar/index.d.ts.map +0 -1
- package/dist/src/collections/calendar/index.js +0 -287
- package/dist/src/collections/calendar/index.js.map +0 -1
- package/dist/src/collections/calendar/types.d.ts +0 -338
- package/dist/src/collections/calendar/types.d.ts.map +0 -1
- package/dist/src/collections/calendar/types.js +0 -206
- package/dist/src/collections/calendar/types.js.map +0 -1
- package/dist/src/collections/flowchart/index.d.ts +0 -430
- package/dist/src/collections/flowchart/index.d.ts.map +0 -1
- package/dist/src/collections/flowchart/index.js +0 -292
- package/dist/src/collections/flowchart/index.js.map +0 -1
- package/dist/src/collections/flowchart/types.d.ts +0 -333
- package/dist/src/collections/flowchart/types.d.ts.map +0 -1
- package/dist/src/collections/flowchart/types.js +0 -263
- package/dist/src/collections/flowchart/types.js.map +0 -1
- package/dist/src/collections/plan/assemble.d.ts +0 -2274
- package/dist/src/collections/plan/assemble.d.ts.map +0 -1
- package/dist/src/collections/plan/assemble.js +0 -210
- package/dist/src/collections/plan/assemble.js.map +0 -1
- package/dist/src/collections/plan/builders.d.ts +0 -765
- package/dist/src/collections/plan/builders.d.ts.map +0 -1
- package/dist/src/collections/plan/builders.js +0 -673
- package/dist/src/collections/plan/builders.js.map +0 -1
- package/dist/src/collections/plan/factories.d.ts +0 -512
- package/dist/src/collections/plan/factories.d.ts.map +0 -1
- package/dist/src/collections/plan/factories.js +0 -539
- package/dist/src/collections/plan/factories.js.map +0 -1
- package/dist/src/collections/plan/index.d.ts +0 -324
- package/dist/src/collections/plan/index.d.ts.map +0 -1
- package/dist/src/collections/plan/index.js +0 -193
- package/dist/src/collections/plan/index.js.map +0 -1
- package/dist/src/collections/plan/ir.d.ts +0 -2490
- package/dist/src/collections/plan/ir.d.ts.map +0 -1
- package/dist/src/collections/plan/ir.js +0 -107
- package/dist/src/collections/plan/ir.js.map +0 -1
- package/dist/src/collections/plan/pick.d.ts +0 -133
- package/dist/src/collections/plan/pick.d.ts.map +0 -1
- package/dist/src/collections/plan/pick.js +0 -208
- package/dist/src/collections/plan/pick.js.map +0 -1
- package/dist/src/collections/plan/root.d.ts +0 -297
- package/dist/src/collections/plan/root.d.ts.map +0 -1
- package/dist/src/collections/plan/root.js +0 -427
- package/dist/src/collections/plan/root.js.map +0 -1
- package/dist/src/collections/plan/series.d.ts +0 -13887
- package/dist/src/collections/plan/series.d.ts.map +0 -1
- package/dist/src/collections/plan/series.js +0 -1710
- package/dist/src/collections/plan/series.js.map +0 -1
- package/dist/src/collections/plan/types.d.ts +0 -12612
- package/dist/src/collections/plan/types.d.ts.map +0 -1
- package/dist/src/collections/plan/types.js +0 -1345
- package/dist/src/collections/plan/types.js.map +0 -1
- package/dist/src/collections/roster/index.d.ts +0 -787
- package/dist/src/collections/roster/index.d.ts.map +0 -1
- package/dist/src/collections/roster/index.js +0 -325
- package/dist/src/collections/roster/index.js.map +0 -1
- package/dist/src/collections/roster/types.d.ts +0 -97
- package/dist/src/collections/roster/types.d.ts.map +0 -1
- package/dist/src/collections/roster/types.js +0 -72
- package/dist/src/collections/roster/types.js.map +0 -1
- package/dist/src/collections/sheet/bridge.d.ts +0 -376
- package/dist/src/collections/sheet/bridge.d.ts.map +0 -1
- package/dist/src/collections/sheet/bridge.js +0 -773
- package/dist/src/collections/sheet/bridge.js.map +0 -1
- package/dist/src/collections/sheet/columns.d.ts +0 -410
- package/dist/src/collections/sheet/columns.d.ts.map +0 -1
- package/dist/src/collections/sheet/columns.js +0 -99
- package/dist/src/collections/sheet/columns.js.map +0 -1
- package/dist/src/collections/sheet/context-bridge.d.ts +0 -48
- package/dist/src/collections/sheet/context-bridge.d.ts.map +0 -1
- package/dist/src/collections/sheet/context-bridge.js +0 -216
- package/dist/src/collections/sheet/context-bridge.js.map +0 -1
- package/dist/src/collections/sheet/draft-bridge.d.ts +0 -37
- package/dist/src/collections/sheet/draft-bridge.d.ts.map +0 -1
- package/dist/src/collections/sheet/draft-bridge.js +0 -153
- package/dist/src/collections/sheet/draft-bridge.js.map +0 -1
- package/dist/src/collections/sheet/drafts.d.ts +0 -14
- package/dist/src/collections/sheet/drafts.d.ts.map +0 -1
- package/dist/src/collections/sheet/drafts.js +0 -14
- package/dist/src/collections/sheet/drafts.js.map +0 -1
- package/dist/src/collections/sheet/editing-bridge.d.ts +0 -36
- package/dist/src/collections/sheet/editing-bridge.d.ts.map +0 -1
- package/dist/src/collections/sheet/editing-bridge.js +0 -196
- package/dist/src/collections/sheet/editing-bridge.js.map +0 -1
- package/dist/src/collections/sheet/editing-types.d.ts +0 -268
- package/dist/src/collections/sheet/editing-types.d.ts.map +0 -1
- package/dist/src/collections/sheet/editing-types.js +0 -34
- package/dist/src/collections/sheet/editing-types.js.map +0 -1
- package/dist/src/collections/sheet/edits.d.ts +0 -37
- package/dist/src/collections/sheet/edits.d.ts.map +0 -1
- package/dist/src/collections/sheet/edits.js +0 -31
- package/dist/src/collections/sheet/edits.js.map +0 -1
- package/dist/src/collections/sheet/group.d.ts +0 -165
- package/dist/src/collections/sheet/group.d.ts.map +0 -1
- package/dist/src/collections/sheet/group.js +0 -52
- package/dist/src/collections/sheet/group.js.map +0 -1
- package/dist/src/collections/sheet/index.d.ts +0 -351
- package/dist/src/collections/sheet/index.d.ts.map +0 -1
- package/dist/src/collections/sheet/index.js +0 -208
- package/dist/src/collections/sheet/index.js.map +0 -1
- package/dist/src/collections/sheet/link.d.ts +0 -398
- package/dist/src/collections/sheet/link.d.ts.map +0 -1
- package/dist/src/collections/sheet/link.js +0 -228
- package/dist/src/collections/sheet/link.js.map +0 -1
- package/dist/src/collections/sheet/registers.d.ts +0 -278
- package/dist/src/collections/sheet/registers.d.ts.map +0 -1
- package/dist/src/collections/sheet/registers.js +0 -268
- package/dist/src/collections/sheet/registers.js.map +0 -1
- package/dist/src/collections/sheet/root.d.ts +0 -328
- package/dist/src/collections/sheet/root.d.ts.map +0 -1
- package/dist/src/collections/sheet/root.js +0 -432
- package/dist/src/collections/sheet/root.js.map +0 -1
- package/dist/src/collections/sheet/seed-bridge.d.ts +0 -14
- package/dist/src/collections/sheet/seed-bridge.d.ts.map +0 -1
- package/dist/src/collections/sheet/seed-bridge.js +0 -51
- package/dist/src/collections/sheet/seed-bridge.js.map +0 -1
- package/dist/src/collections/sheet/sub-rows.d.ts +0 -249
- package/dist/src/collections/sheet/sub-rows.d.ts.map +0 -1
- package/dist/src/collections/sheet/sub-rows.js +0 -222
- package/dist/src/collections/sheet/sub-rows.js.map +0 -1
- package/dist/src/collections/sheet/transactions.d.ts +0 -76
- package/dist/src/collections/sheet/transactions.d.ts.map +0 -1
- package/dist/src/collections/sheet/transactions.js +0 -40
- package/dist/src/collections/sheet/transactions.js.map +0 -1
- package/dist/src/collections/sheet/types.d.ts +0 -14992
- package/dist/src/collections/sheet/types.d.ts.map +0 -1
- package/dist/src/collections/sheet/types.js +0 -1061
- package/dist/src/collections/sheet/types.js.map +0 -1
- package/dist/src/navigation/app/index.d.ts +0 -150
- package/dist/src/navigation/app/index.d.ts.map +0 -1
- package/dist/src/navigation/app/index.js +0 -191
- package/dist/src/navigation/app/index.js.map +0 -1
- package/dist/src/navigation/pages/index.d.ts +0 -165
- package/dist/src/navigation/pages/index.d.ts.map +0 -1
- package/dist/src/navigation/pages/index.js +0 -152
- package/dist/src/navigation/pages/index.js.map +0 -1
- package/dist/src/navigation/pages/types.d.ts +0 -111
- package/dist/src/navigation/pages/types.d.ts.map +0 -1
- package/dist/src/navigation/pages/types.js +0 -101
- package/dist/src/navigation/pages/types.js.map +0 -1
- package/dist/src/runtime/collections/board.d.ts +0 -73
- package/dist/src/runtime/collections/board.d.ts.map +0 -1
- package/dist/src/runtime/collections/board.js +0 -68
- package/dist/src/runtime/collections/board.js.map +0 -1
- package/dist/src/runtime/collections/calendar.d.ts +0 -54
- package/dist/src/runtime/collections/calendar.d.ts.map +0 -1
- package/dist/src/runtime/collections/calendar.js +0 -54
- package/dist/src/runtime/collections/calendar.js.map +0 -1
- package/dist/src/runtime/collections/flowchart.d.ts +0 -58
- package/dist/src/runtime/collections/flowchart.d.ts.map +0 -1
- package/dist/src/runtime/collections/flowchart.js +0 -54
- package/dist/src/runtime/collections/flowchart.js.map +0 -1
- package/dist/src/runtime/collections/plan.d.ts +0 -181
- package/dist/src/runtime/collections/plan.d.ts.map +0 -1
- package/dist/src/runtime/collections/plan.js +0 -186
- package/dist/src/runtime/collections/plan.js.map +0 -1
- package/dist/src/runtime/collections/roster.d.ts +0 -60
- package/dist/src/runtime/collections/roster.d.ts.map +0 -1
- package/dist/src/runtime/collections/roster.js +0 -59
- package/dist/src/runtime/collections/roster.js.map +0 -1
- package/dist/src/runtime/collections/sheet.d.ts +0 -122
- package/dist/src/runtime/collections/sheet.d.ts.map +0 -1
- package/dist/src/runtime/collections/sheet.js +0 -73
- package/dist/src/runtime/collections/sheet.js.map +0 -1
- package/dist/src/runtime/navigation/app.d.ts +0 -52
- package/dist/src/runtime/navigation/app.d.ts.map +0 -1
- package/dist/src/runtime/navigation/app.js +0 -53
- package/dist/src/runtime/navigation/app.js.map +0 -1
- package/dist/src/runtime/navigation/pages.d.ts +0 -50
- package/dist/src/runtime/navigation/pages.d.ts.map +0 -1
- package/dist/src/runtime/navigation/pages.js +0 -52
- package/dist/src/runtime/navigation/pages.js.map +0 -1
- package/dist/src/runtime/navigation/route.d.ts +0 -46
- package/dist/src/runtime/navigation/route.d.ts.map +0 -1
- package/dist/src/runtime/navigation/route.js +0 -47
- package/dist/src/runtime/navigation/route.js.map +0 -1
- package/dist/test/collections/board.examples.d.ts +0 -18
- package/dist/test/collections/board.examples.d.ts.map +0 -1
- package/dist/test/collections/board.examples.js +0 -297
- package/dist/test/collections/board.examples.js.map +0 -1
- package/dist/test/collections/calendar.examples.d.ts +0 -5
- package/dist/test/collections/calendar.examples.d.ts.map +0 -1
- package/dist/test/collections/calendar.examples.js +0 -147
- package/dist/test/collections/calendar.examples.js.map +0 -1
- package/dist/test/collections/flowchart.examples.d.ts +0 -17
- package/dist/test/collections/flowchart.examples.d.ts.map +0 -1
- package/dist/test/collections/flowchart.examples.js +0 -243
- package/dist/test/collections/flowchart.examples.js.map +0 -1
- package/dist/test/collections/plan.examples.d.ts +0 -200
- package/dist/test/collections/plan.examples.d.ts.map +0 -1
- package/dist/test/collections/plan.examples.js +0 -3665
- package/dist/test/collections/plan.examples.js.map +0 -1
- package/dist/test/collections/roster.examples.d.ts +0 -26
- package/dist/test/collections/roster.examples.d.ts.map +0 -1
- package/dist/test/collections/roster.examples.js +0 -237
- package/dist/test/collections/roster.examples.js.map +0 -1
- package/dist/test/collections/sheet-link.examples.d.ts +0 -7
- package/dist/test/collections/sheet-link.examples.d.ts.map +0 -1
- package/dist/test/collections/sheet-link.examples.js +0 -41
- package/dist/test/collections/sheet-link.examples.js.map +0 -1
- package/dist/test/collections/sheet-transactions.examples.d.ts +0 -7
- package/dist/test/collections/sheet-transactions.examples.d.ts.map +0 -1
- package/dist/test/collections/sheet-transactions.examples.js +0 -58
- package/dist/test/collections/sheet-transactions.examples.js.map +0 -1
- package/dist/test/collections/sheet.examples.d.ts +0 -96
- package/dist/test/collections/sheet.examples.d.ts.map +0 -1
- package/dist/test/collections/sheet.examples.js +0 -1040
- package/dist/test/collections/sheet.examples.js.map +0 -1
- package/dist/test/navigation/app.examples.d.ts +0 -22
- package/dist/test/navigation/app.examples.d.ts.map +0 -1
- package/dist/test/navigation/app.examples.js +0 -136
- package/dist/test/navigation/app.examples.js.map +0 -1
- package/dist/test/navigation/pages.examples.d.ts +0 -13
- package/dist/test/navigation/pages.examples.d.ts.map +0 -1
- package/dist/test/navigation/pages.examples.js +0 -113
- package/dist/test/navigation/pages.examples.js.map +0 -1
- package/dist/test/navigation/route.examples.d.ts +0 -13
- package/dist/test/navigation/route.examples.d.ts.map +0 -1
- package/dist/test/navigation/route.examples.js +0 -69
- package/dist/test/navigation/route.examples.js.map +0 -1
|
@@ -1,3665 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "@elaraai/east-ui/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
4
|
-
* Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
|
|
5
|
-
*/
|
|
6
|
-
/** @jsxImportSource @elaraai/east-ui */
|
|
7
|
-
import { ArrayType, BooleanType, DateTimeType, DictType, East, FloatType, IntegerType, NullType, OptionType, RecursiveType, StringType, StructType, VariantType, example, none, some, variant, } from "@elaraai/east";
|
|
8
|
-
import { ApprovalStateType, DragEventType, Editing, EventStateType, State, StatusValueType, Style, UIComponentType } from "@elaraai/east-ui";
|
|
9
|
-
import { Badge, Box, Button, Chart, Configurator, Format, HStack, Library, Plan, Progress, Reactive, SegmentGroup, Select, Slice, Sparkline, Text, VStack, deriveApproval } from "@elaraai/east-ui";
|
|
10
|
-
// The corpus — every canvas is DEFINED the one way (`Plan Data Interface.md`
|
|
11
|
-
// §3.5): `data` (RAW domain rows — batches, tonnes, lifecycle states; row
|
|
12
|
-
// series discriminated by a field; no factory-built values in the data) +
|
|
13
|
-
// `series` (one `Plan.series.*` value per row series, `$.const`-bound and typed
|
|
14
|
-
// by the `Plan.Types.Series(Row)` constructor — its accessors DERIVE the
|
|
15
|
-
// canvas vocabulary from the raw fields client-side: labels, quantity
|
|
16
|
-
// displays, element values via the `Plan.run`/`chip`/`event` expression
|
|
17
|
-
// builders) + the root RESOLVER functions (`popover` / `hover` /
|
|
18
|
-
// `expandRender`). The series list IS the layout — one block per series, top
|
|
19
|
-
// to bottom — and hierarchy comes only from the data's own nesting (#822): a
|
|
20
|
-
// series' `children` walk what an entry holds, and a flat source is grouped in
|
|
21
|
-
// a data step first (`groupToDicts`). A row's id is its series and the path of
|
|
22
|
-
// entry keys to it (`Plan.ref`). The kind factories are subtree vocabulary only
|
|
23
|
-
// — the one-off chrome a `Plan.series.rows` entry places (`planLiteralRows`).
|
|
24
|
-
// Fixtures are defined INLINE in each example's fn body (the automated docs
|
|
25
|
-
// extract only the fn body) and DERIVED with East expressions — a `week(n)`
|
|
26
|
-
// ISO-week function plus `East.Array.generate` — never hand-written per
|
|
27
|
-
// element; only individually-meaningful mock records stay literal, and
|
|
28
|
-
// lifecycle states are plain `EventStateType` variants (the shared contracts
|
|
29
|
-
// vocabulary a plan dataset stores).
|
|
30
|
-
// ============================================================================
|
|
31
|
-
// planTargetState — the §1 flagship (every row kind, ONE source, series)
|
|
32
|
-
// ============================================================================
|
|
33
|
-
export const planTargetState = example({
|
|
34
|
-
keywords: [
|
|
35
|
-
"Plan", "canvas", "data", "series", "match", "axis", "window", "resolution", "now",
|
|
36
|
-
"span", "run", "group", "section", "heat", "buckets", "cards", "chip", "events", "mark", "chart",
|
|
37
|
-
"layers", "rollup", "bands", "review", "footer", "milestone",
|
|
38
|
-
"decision", "exception", "pinned", "port", "hovercard", "popover",
|
|
39
|
-
"slice", "brush", "horizon", "toolbar", "affordances", "expand",
|
|
40
|
-
"expandRender", "resolver", "data-driven", "accessor", "raw", "target state",
|
|
41
|
-
"layout", "row id", "Plan.ref", "links", "quantity", "Plan.quantity", "onElementClick",
|
|
42
|
-
],
|
|
43
|
-
description: "Every row kind on one axis from a single raw ops source, with slice chrome, expand, review and a status footer",
|
|
44
|
-
fn: East.function([], UIComponentType, (_$) => {
|
|
45
|
-
// The horizon fixture — the despatch orders the slice narrows (the
|
|
46
|
-
// canvas rows are the ops source; the slice is chrome over THESE):
|
|
47
|
-
// when, which line, whether the order is at risk of running late,
|
|
48
|
-
// and the tonnage booked so far (0 = nothing yet).
|
|
49
|
-
const HorizonRow = StructType({ key: StringType, at: DateTimeType, line: StringType, risk: StringType, tonnes: FloatType });
|
|
50
|
-
const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
|
|
51
|
-
// The RAW job record — what an ops dataset stores: phase + optional
|
|
52
|
-
// batch, the window, optional tonnage, the lifecycle state, an alert.
|
|
53
|
-
const JobRow = StructType({
|
|
54
|
-
key: StringType, phase: StringType, batch: OptionType(StringType),
|
|
55
|
-
start: DateTimeType, end: DateTimeType,
|
|
56
|
-
tonnes: OptionType(FloatType), state: EventStateType,
|
|
57
|
-
alert: OptionType(StatusValueType),
|
|
58
|
-
});
|
|
59
|
-
const ShiftRow = StructType({
|
|
60
|
-
key: StringType, from: DateTimeType, to: DateTimeType,
|
|
61
|
-
hours: FloatType, state: EventStateType,
|
|
62
|
-
});
|
|
63
|
-
const AllocRow = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
|
|
64
|
-
// ONE source — row series discriminated by the kind variant (the
|
|
65
|
-
// natural ops-dataset shape; the same rows page from a dataset). The
|
|
66
|
-
// arms carry RAW fields; presence (status / expand declaration) is
|
|
67
|
-
// per-row Option DATA the accessors pass through.
|
|
68
|
-
const OpsRow = StructType({
|
|
69
|
-
kind: VariantType({
|
|
70
|
-
kpi: StructType({ name: StringType, headline: StringType, pinned: BooleanType,
|
|
71
|
-
points: ArrayType(MeasureRow) }),
|
|
72
|
-
machine: StructType({ cap: FloatType,
|
|
73
|
-
status: OptionType(StatusValueType),
|
|
74
|
-
detail: OptionType(Plan.Types.Expand),
|
|
75
|
-
jobs: ArrayType(JobRow),
|
|
76
|
-
decisions: ArrayType(Plan.Types.DecisionMark),
|
|
77
|
-
ports: ArrayType(Plan.Types.Port) }),
|
|
78
|
-
load: StructType({ name: StringType, sub: StringType, cells: ArrayType(Plan.Types.HeatCell) }),
|
|
79
|
-
dock: StructType({ name: StringType, allocations: ArrayType(AllocRow),
|
|
80
|
-
markers: ArrayType(Plan.Types.CellMarker) }),
|
|
81
|
-
crew: StructType({ name: StringType, hours: StringType, shifts: ArrayType(ShiftRow) }),
|
|
82
|
-
stream: StructType({ name: StringType, marks: ArrayType(Plan.Types.EventMark) }),
|
|
83
|
-
}),
|
|
84
|
-
});
|
|
85
|
-
const cfg = Slice.config(HorizonRow, {
|
|
86
|
-
fields: {
|
|
87
|
-
at: { label: "Despatched", format: { date: "MMM D" } },
|
|
88
|
-
line: { label: "Line" },
|
|
89
|
-
risk: { label: "Risk", hints: ["late", "on-time"] },
|
|
90
|
-
tonnes: { label: "Tonnes" },
|
|
91
|
-
},
|
|
92
|
-
rangeFieldId: "at",
|
|
93
|
-
searchFieldIds: ["line"],
|
|
94
|
-
});
|
|
95
|
-
return (_jsx(Reactive, { children: $ => {
|
|
96
|
-
// Monday of ISO week n, 2026 (W1 Monday = 2025-12-29). The §1
|
|
97
|
-
// window is W27–W38 (half-open at W39); now = W31.
|
|
98
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
99
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
100
|
-
return w1.addWeeks(n.subtract(1n));
|
|
101
|
-
}));
|
|
102
|
-
const covPcts = $.const([96.1, 96.4, 96.8, 97.0, 96.2, 95.1, 93.4, 91.0, 88.9, 91.4, 93.8, 94.2], ArrayType(FloatType));
|
|
103
|
-
const loadPcts = $.const([46.0, 52.0, 58.0, 61.0, 66.0, 72.0, 78.0, 84.0, 90.0, 96.0, 98.0, 92.0], ArrayType(FloatType));
|
|
104
|
-
const coverage = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: covPcts.get(i) })));
|
|
105
|
-
const loadCells = $.let(East.Array.generate(12n, Plan.Types.HeatCell, (_$, i) => ({
|
|
106
|
-
at: Plan.at.time(week(i.add(27n))), value: some(loadPcts.get(i)),
|
|
107
|
-
label: some(East.Float.printFixed(loadPcts.get(i), 0n)),
|
|
108
|
-
})));
|
|
109
|
-
// 36 despatch orders spread over W21–W47 — the horizon fixture.
|
|
110
|
-
// Every third order is at risk of running late (the §1 `Late
|
|
111
|
-
// risk` cohort counts 12); every seventh has no tonnage booked
|
|
112
|
-
// yet (the `Empty` cohort).
|
|
113
|
-
const horizon = $.let(East.Array.generate(36n, HorizonRow, (_$, i) => ({
|
|
114
|
-
key: East.str `h${East.print(i.add(1n))}`,
|
|
115
|
-
at: week(i.multiply(27n).divide(36n).add(21n)),
|
|
116
|
-
line: i.remainder(2n).equal(0n).ifElse(() => "Line 1", () => "Line 2"),
|
|
117
|
-
risk: i.remainder(3n).equal(0n).ifElse(() => "late", () => "on-time"),
|
|
118
|
-
tonnes: i.remainder(7n).equal(6n).ifElse(() => 0.0, () => i.multiply(11n).remainder(40n).toFloat().add(20.0)),
|
|
119
|
-
})));
|
|
120
|
-
// Raw jobs → runs: ONE bound mapping function — the bar label and
|
|
121
|
-
// the quantity derive CLIENT-SIDE from the raw phase/batch/tonnes
|
|
122
|
-
// fields (the series-make application). A quantity is ONE value —
|
|
123
|
-
// the number, its unit and how it prints (#824) — so the bar's
|
|
124
|
-
// caption and a rollup's sum can never disagree. The run is written
|
|
125
|
-
// as a RECORD, so its instants are spelled with `Plan.at.time` —
|
|
126
|
-
// the `Plan.run` builder would wrap a DateTime field by itself.
|
|
127
|
-
const tonnesFormat = $.const(Format.Number({ maximumFractionDigits: 0n }));
|
|
128
|
-
const jobRuns = $.const(East.function([ArrayType(JobRow)], ArrayType(Plan.Types.Run), (_$, jobs) => jobs.map(($, j) => {
|
|
129
|
-
const noQuantity = $.const(none, OptionType(Plan.Types.Quantity));
|
|
130
|
-
const quantity = $.let(j.tonnes.match({
|
|
131
|
-
some: (_$, t) => East.value(some(Plan.quantity(t, { unit: "t", format: tonnesFormat })), OptionType(Plan.Types.Quantity)),
|
|
132
|
-
none: (_$) => noQuantity,
|
|
133
|
-
}), OptionType(Plan.Types.Quantity));
|
|
134
|
-
const label = $.let(j.batch.match({
|
|
135
|
-
some: (_$, b) => East.str `${j.phase} · ${b}`,
|
|
136
|
-
none: (_$) => j.phase,
|
|
137
|
-
}), StringType);
|
|
138
|
-
const run = $.let({
|
|
139
|
-
key: j.key, start: Plan.at.time(j.start), end: Plan.at.time(j.end), label,
|
|
140
|
-
quantity, state: j.state, status: j.alert,
|
|
141
|
-
moved: none, icon: none,
|
|
142
|
-
}, Plan.Types.Run);
|
|
143
|
-
return run;
|
|
144
|
-
})));
|
|
145
|
-
// Raw shifts → chips: hours print as the chip label, an ADDED
|
|
146
|
-
// proposal wearing the `+` prefix — display derives from the
|
|
147
|
-
// lifecycle, down to the proposal's flavour (a `removed` shift is
|
|
148
|
-
// a proposal too, and `+` would read as its opposite).
|
|
149
|
-
const shiftChips = $.const(East.function([ArrayType(ShiftRow)], ArrayType(Plan.Types.Chip), (_$, shifts) => shifts.map(($, s) => {
|
|
150
|
-
const hrs = $.let(East.Float.printFixed(s.hours, 0n), StringType);
|
|
151
|
-
const label = $.let(s.state.match({
|
|
152
|
-
proposed: (_$, p) => p.hasTag("removed").ifElse(() => East.str `${hrs}h`, () => East.str `+${hrs}h`),
|
|
153
|
-
}, _$ => East.str `${hrs}h`), StringType);
|
|
154
|
-
return Plan.chip({ key: s.key, from: s.from, to: s.to, label, state: s.state });
|
|
155
|
-
})));
|
|
156
|
-
// The ONE ops source — every series' rows in one KEYED collection,
|
|
157
|
-
// RAW: no display strings the accessors can derive, no built
|
|
158
|
-
// elements. A key is its entry's identity, never its place: a row's
|
|
159
|
-
// id is the series that made it and this key
|
|
160
|
-
// (`Plan.ref("machines", "L1-M03")`), which is what `links`,
|
|
161
|
-
// `popover` and `onSelect` speak — the §1 layout is the series list.
|
|
162
|
-
const ops = $.const(new Map([
|
|
163
|
-
["coverage", { kind: variant("kpi", { name: "Coverage", headline: "94.2%", pinned: true, points: coverage }) }],
|
|
164
|
-
["L1-M03", { kind: variant("machine", { cap: 120.0, status: some(variant("success", null)), detail: none,
|
|
165
|
-
jobs: [
|
|
166
|
-
{ key: "set", phase: "SET", batch: none, start: week(27n), end: week(28n), tonnes: none, state: variant("actual", null), alert: none },
|
|
167
|
-
{ key: "b214", phase: "RUN", batch: some("B-214"), start: week(28n), end: week(31n), tonnes: some(96.0), state: variant("in-progress", null), alert: none },
|
|
168
|
-
{ key: "cln", phase: "CLN", batch: none, start: week(31n), end: week(32n), tonnes: none, state: variant("confirmed", null), alert: none },
|
|
169
|
-
{ key: "b221", phase: "RUN", batch: some("B-221"), start: week(32n), end: week(35n), tonnes: some(88.0), state: variant("proposed", variant("recommended", null)), alert: none },
|
|
170
|
-
],
|
|
171
|
-
decisions: [{ key: "d1", at: Plan.at.time(week(32n)), applied: false }],
|
|
172
|
-
ports: [{ at: Plan.at.time(week(31n)), label: some("−24 t") }] }) }],
|
|
173
|
-
["L1-M04", { kind: variant("machine", { cap: 120.0, status: none,
|
|
174
|
-
// The expand declaration — a stored plain-data record
|
|
175
|
-
// (§3.2); presence is a per-row fact and the ROOT's
|
|
176
|
-
// expandRender mounts the body.
|
|
177
|
-
detail: some({ height: some("152px"), axis: variant("keep", null) }),
|
|
178
|
-
jobs: [
|
|
179
|
-
{ key: "b208", phase: "RUN", batch: some("B-208"), start: week(27n), end: week(30n), tonnes: some(112.0), state: variant("actual", null), alert: none },
|
|
180
|
-
{ key: "hld", phase: "HLD", batch: none, start: week(30n), end: week(31n), tonnes: none, state: variant("confirmed", null), alert: none },
|
|
181
|
-
{ key: "qc", phase: "QC", batch: none, start: week(31n), end: week(33n), tonnes: none, state: variant("confirmed", null), alert: none },
|
|
182
|
-
{ key: "b231", phase: "RUN", batch: some("B-231"), start: week(33n), end: week(41n), tonnes: some(104.0), state: variant("proposed", variant("recommended", null)), alert: none },
|
|
183
|
-
],
|
|
184
|
-
decisions: [], ports: [] }) }],
|
|
185
|
-
["L1-M07", { kind: variant("machine", { cap: 80.0, status: some(variant("warning", null)), detail: none,
|
|
186
|
-
jobs: [
|
|
187
|
-
{ key: "b197", phase: "HLD", batch: some("B-197"), start: week(27n), end: week(31n), tonnes: none, state: variant("actual", null), alert: some(variant("warning", null)) },
|
|
188
|
-
{ key: "cln", phase: "CLN", batch: none, start: week(34n), end: week(36n), tonnes: none, state: variant("proposed", variant("recommended", null)), alert: none },
|
|
189
|
-
],
|
|
190
|
-
decisions: [], ports: [] }) }],
|
|
191
|
-
["l2-load", { kind: variant("load", { name: "L2 load", sub: "%/wk", cells: loadCells }) }],
|
|
192
|
-
["dock2", { kind: variant("dock", { name: "Dock 2",
|
|
193
|
-
allocations: [
|
|
194
|
-
{ key: "a1", at: week(27n), state: variant("confirmed", null) },
|
|
195
|
-
{ key: "a2", at: week(28n), state: variant("confirmed", null) },
|
|
196
|
-
{ key: "a3", at: week(29n), state: variant("confirmed", null) },
|
|
197
|
-
{ key: "a4", at: week(30n), state: variant("confirmed", null) },
|
|
198
|
-
{ key: "a5", at: week(31n), state: variant("proposed", variant("recommended", null)) },
|
|
199
|
-
{ key: "a6", at: week(33n), state: variant("proposed", variant("recommended", null)) },
|
|
200
|
-
{ key: "a7", at: week(35n), state: variant("confirmed", null) },
|
|
201
|
-
{ key: "a8", at: week(35n), state: variant("proposed", variant("recommended", null)) },
|
|
202
|
-
],
|
|
203
|
-
markers: [{ at: Plan.at.time(week(35n)), lane: none, status: variant("warning", null), message: "capacity breach — 2 allocations" }] }) }],
|
|
204
|
-
["crewA", { kind: variant("crew", { name: "Crew A", hours: "152h → 168h", shifts: [
|
|
205
|
-
{ key: "s1", from: week(27n), to: week(29n), hours: 80.0, state: variant("confirmed", null) },
|
|
206
|
-
{ key: "s2", from: week(29n), to: week(31n), hours: 72.0, state: variant("confirmed", null) },
|
|
207
|
-
{ key: "s3", from: week(31n), to: week(33n), hours: 64.0, state: variant("proposed", variant("recommended", null)) },
|
|
208
|
-
{ key: "s4", from: week(34n), to: week(35n), hours: 48.0, state: variant("estimated", null) },
|
|
209
|
-
{ key: "s5", from: week(36n), to: week(38n), hours: 56.0, state: variant("proposed", variant("recommended", null)) },
|
|
210
|
-
] }) }],
|
|
211
|
-
["milestones", { kind: variant("stream", { name: "Milestones", marks: [
|
|
212
|
-
{ key: "kick", at: Plan.at.time(week(28n)), kind: variant("milestone", null), icon: none, label: some("KICKOFF") },
|
|
213
|
-
{ key: "d1", at: Plan.at.time(week(31n)), kind: variant("decision", { applied: true }), icon: none, label: none },
|
|
214
|
-
{ key: "rel", at: Plan.at.time(week(33n)), kind: variant("milestone", null), icon: none, label: some("REL 2.4") },
|
|
215
|
-
{ key: "audit", at: Plan.at.time(week(35n)), kind: variant("exception", null), icon: none, label: some("AUDIT") },
|
|
216
|
-
{ key: "d2", at: Plan.at.time(week(37n)), kind: variant("decision", { applied: false }), icon: none, label: some("×3") },
|
|
217
|
-
] }) }],
|
|
218
|
-
]), DictType(StringType, OpsRow));
|
|
219
|
-
// The series — the list IS the layout (#822): one block per
|
|
220
|
-
// series, top to bottom in this order, a section titling the
|
|
221
|
-
// blocks beneath it. The whole list is an East value typed by the
|
|
222
|
-
// constructor.
|
|
223
|
-
const series = $.const([
|
|
224
|
-
Plan.series.chart(OpsRow, {
|
|
225
|
-
key: "coverage", title: "Coverage",
|
|
226
|
-
match: r => r.kind.hasTag("kpi"),
|
|
227
|
-
label: r => r.kind.unwrap("kpi").name, id: true,
|
|
228
|
-
pinned: r => r.kind.unwrap("kpi").pinned,
|
|
229
|
-
value: r => some(r.kind.unwrap("kpi").headline),
|
|
230
|
-
status: _r => some(variant("warning", null)),
|
|
231
|
-
height: "spark", expandable: true,
|
|
232
|
-
layers: r => [
|
|
233
|
-
Plan.layer(Chart.Line(r.kind.unwrap("kpi").points, { x: p => p.week, y: p => p.pct }), { breach: { below: 92 } }),
|
|
234
|
-
Chart.refLine({ y: 100, label: "TARGET 100" }),
|
|
235
|
-
],
|
|
236
|
-
}),
|
|
237
|
-
Plan.series.section(OpsRow, { key: "line1", title: "Line 1", meta: "3 rows · 82%" }, [
|
|
238
|
-
Plan.series.span(OpsRow, {
|
|
239
|
-
key: "machines", title: "Machines",
|
|
240
|
-
match: r => r.kind.hasTag("machine"),
|
|
241
|
-
label: (_r, k) => k, id: true,
|
|
242
|
-
value: r => some(East.str `${East.Float.printFixed(r.kind.unwrap("machine").cap, 0n)} t`),
|
|
243
|
-
status: r => r.kind.unwrap("machine").status,
|
|
244
|
-
expand: r => r.kind.unwrap("machine").detail,
|
|
245
|
-
runs: r => jobRuns(r.kind.unwrap("machine").jobs),
|
|
246
|
-
decisions: r => r.kind.unwrap("machine").decisions,
|
|
247
|
-
ports: r => r.kind.unwrap("machine").ports,
|
|
248
|
-
}),
|
|
249
|
-
]),
|
|
250
|
-
Plan.series.section(OpsRow, { key: "line2", title: "Line 2", value: "98%", status: "warning", collapsed: true, summaryAggregate: "mean" }, [
|
|
251
|
-
Plan.series.heat(OpsRow, {
|
|
252
|
-
key: "load", title: "Load",
|
|
253
|
-
match: r => r.kind.hasTag("load"),
|
|
254
|
-
label: r => r.kind.unwrap("load").name,
|
|
255
|
-
sub: r => some(r.kind.unwrap("load").sub),
|
|
256
|
-
cells: r => Plan.heatCells(r.kind.unwrap("load").cells, { min: 0, max: 100, warnAt: 95 }),
|
|
257
|
-
}),
|
|
258
|
-
]),
|
|
259
|
-
Plan.series.section(OpsRow, { key: "docks-in", title: "Docks · In", meta: "1 row" }, [
|
|
260
|
-
Plan.series.buckets(OpsRow, {
|
|
261
|
-
key: "docks", title: "Docks",
|
|
262
|
-
match: r => r.kind.hasTag("dock"),
|
|
263
|
-
label: r => r.kind.unwrap("dock").name,
|
|
264
|
-
sub: _r => some("load/wk"),
|
|
265
|
-
events: r => r.kind.unwrap("dock").allocations.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
|
|
266
|
-
markers: r => r.kind.unwrap("dock").markers,
|
|
267
|
-
}),
|
|
268
|
-
]),
|
|
269
|
-
Plan.series.cards(OpsRow, {
|
|
270
|
-
key: "crews", title: "Crews",
|
|
271
|
-
match: r => r.kind.hasTag("crew"),
|
|
272
|
-
label: r => r.kind.unwrap("crew").name, stacked: true,
|
|
273
|
-
sub: r => some(r.kind.unwrap("crew").hours),
|
|
274
|
-
chips: r => shiftChips(r.kind.unwrap("crew").shifts),
|
|
275
|
-
}),
|
|
276
|
-
Plan.series.events(OpsRow, {
|
|
277
|
-
key: "milestones", title: "Milestones",
|
|
278
|
-
match: r => r.kind.hasTag("stream"),
|
|
279
|
-
label: r => r.kind.unwrap("stream").name, id: true,
|
|
280
|
-
value: r => some(East.print(r.kind.unwrap("stream").marks.length())),
|
|
281
|
-
marks: r => r.kind.unwrap("stream").marks,
|
|
282
|
-
}),
|
|
283
|
-
], ArrayType(Plan.Types.Series(OpsRow)));
|
|
284
|
-
const axis = $.const(Plan.axis({
|
|
285
|
-
window: { min: week(27n), max: week(39n) },
|
|
286
|
-
resolution: "week", resolutions: ["month", "week", "day"], now: week(31n),
|
|
287
|
-
}));
|
|
288
|
-
// The §1 toolbar is SEEDED slice state: the applied window is the
|
|
289
|
-
// range chip (`JUN 29 – SEP 20 · 84d`), and two saved cohorts sit
|
|
290
|
-
// beside the filter builder with `Late risk` active — their
|
|
291
|
-
// counts are live over the horizon fixture, never printed. A slice
|
|
292
|
-
// range is CLOSED — both ends inclusive — so the twelve weeks
|
|
293
|
-
// W27–W38 end the millisecond before W39.
|
|
294
|
-
const slice = $.let(Slice.bind([HorizonRow], "ex.plan.target", cfg, Slice.state({
|
|
295
|
-
range: some(variant("datetime", { from: week(27n), to: week(39n).addMilliseconds(-1n) })),
|
|
296
|
-
cohorts: [
|
|
297
|
-
{ id: "late", name: "Late risk", filters: [variant("string", { fieldId: "risk", op: variant("eq", "late") })] },
|
|
298
|
-
{ id: "empty", name: "Empty", filters: [variant("float", { fieldId: "tonnes", op: variant("lte", 0.0) })] },
|
|
299
|
-
],
|
|
300
|
-
activeCohorts: new Set(["late"]),
|
|
301
|
-
}), horizon, none));
|
|
302
|
-
// The R2 developer render — the ROOT's resolver, called with the
|
|
303
|
-
// focused row's id; ONE function serves every row whose `expand`
|
|
304
|
-
// accessor returned some(...). The machine series declares it.
|
|
305
|
-
const util = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: i.multiply(17n).remainder(45n).toFloat().add(52.0) })));
|
|
306
|
-
const expandRender = $.const(East.function([Plan.Types.RowId], UIComponentType, (_$, _id) => (_jsx(Chart, { layers: [Chart.Line(util, { x: r => r.week, y: r => r.pct })], height: 120, grid: false }))));
|
|
307
|
-
// The generalized element resolvers — ONE stored popover / hover
|
|
308
|
-
// function each over Plan.Types.ElementRef (every arm carries the
|
|
309
|
-
// row's id); rich bodies build lazily at interaction time, and a
|
|
310
|
-
// none result opens no surface.
|
|
311
|
-
const popover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
|
|
312
|
-
const noBody = $.const(none, OptionType(UIComponentType));
|
|
313
|
-
const m03 = $.const(Plan.ref("machines", "L1-M03"));
|
|
314
|
-
return ref.match({
|
|
315
|
-
run: (_$, ev) => ev.run.equal("b221").ifElse(() => some(_jsx(Text, { children: "Proposed by run 412 \u2014 fills the W32 idle window." })), () => noBody),
|
|
316
|
-
mark: (_$, ev) => East.equal(ev.row, m03).and(() => ev.mark.equal("d1")).ifElse(() => some(_jsx(Text, { children: "Schedule B-221" })), () => noBody),
|
|
317
|
-
}, _$ => noBody);
|
|
318
|
-
}));
|
|
319
|
-
const hover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
|
|
320
|
-
const noBody = $.const(none, OptionType(UIComponentType));
|
|
321
|
-
return ref.match({
|
|
322
|
-
run: (_$, ev) => ev.run.equal("b197").ifElse(() => some(_jsx(Text, { children: "Waiting on QC gate 4 \u2014 2.6\u00D7 median dwell." })), () => noBody),
|
|
323
|
-
}, _$ => noBody);
|
|
324
|
-
}));
|
|
325
|
-
// Behavior props — bound once so memoized renderers keep identity.
|
|
326
|
-
const onRow = $.const(East.function([Plan.Types.RowId], NullType, (_$, _id) => null));
|
|
327
|
-
// ONE element callback (#824) — a run, tile, mark, chip, cell or
|
|
328
|
-
// link ribbon, by the same ref the popover resolver receives.
|
|
329
|
-
const onElementClick = $.const(East.function([Plan.Types.ElementRef], NullType, (_$, _ref) => null));
|
|
330
|
-
const onGroupToggle = $.const(East.function([Plan.Types.GroupToggleEvent], NullType, (_$, _e) => null));
|
|
331
|
-
const onBatch = $.const(East.function([], NullType, (_$) => null));
|
|
332
|
-
return (_jsx(Plan, { slice: { slice, affordances: ["cohort", "filter", "search", "range", "resolution", "brush", "summary"] }, axis: axis,
|
|
333
|
-
// The link graph (R1) — the W31 −24 t transfer, its ends
|
|
334
|
-
// named by row id: hover a linked machine for the
|
|
335
|
-
// links-focus control. Its quantity weighs the ribbon, and
|
|
336
|
-
// `text` says it the author's way.
|
|
337
|
-
links: [
|
|
338
|
-
Plan.link({
|
|
339
|
-
key: "t-w31",
|
|
340
|
-
from: Plan.ref("machines", "L1-M03"), fromRun: "b214",
|
|
341
|
-
to: Plan.ref("machines", "L1-M04"), toRun: "qc",
|
|
342
|
-
quantity: Plan.quantity(24, { unit: "t", text: "−24 t" }),
|
|
343
|
-
}),
|
|
344
|
-
], data: ops, series: series,
|
|
345
|
-
// The review chrome — its foot and Rerun. A verdict is a
|
|
346
|
-
// draft of an editing session (`planReview`, `planEditing`).
|
|
347
|
-
review: {
|
|
348
|
-
summary: _jsx(Text, { children: "4 JOBS \u00B7 2 FLAGGED NEED A CALL \u00B7 +6H FLOAT" }),
|
|
349
|
-
onRerun: onBatch,
|
|
350
|
-
}, expandRender: expandRender, popover: popover, hover: hover, onSelect: onRow, onElementClick: onElementClick, onGroupToggle: onGroupToggle, footer: [
|
|
351
|
-
{ text: "512 RESOURCES · 12 GROUPS · 3 IN VIEW" },
|
|
352
|
-
{ text: "318 OBSERVED · 966 PLANNED" },
|
|
353
|
-
{ text: "3 EXCEPTIONS", tone: "warning" },
|
|
354
|
-
{ text: "RUN 412 · W27–W38", end: true },
|
|
355
|
-
] }));
|
|
356
|
-
} }));
|
|
357
|
-
}),
|
|
358
|
-
inputs: [],
|
|
359
|
-
});
|
|
360
|
-
// ============================================================================
|
|
361
|
-
// planVariants — THE Plan configurator (#571): axis presets × style sweeps
|
|
362
|
-
// ============================================================================
|
|
363
|
-
/**
|
|
364
|
-
* THE Plan configurator — the slot-2 variant-space surface the retired Gantt /
|
|
365
|
-
* Planner configurators held (#571). ONE live canvas; every axis feeds it as
|
|
366
|
-
* an expression. The window presets are whole `Plan.axis` VALUES riding a
|
|
367
|
-
* typed preset struct: the ops week window, the year roadmap at month
|
|
368
|
-
* resolution, and the 14-day sprint at day resolution with `ddd DD` labels
|
|
369
|
-
* (the #309 pinned-day-columns contract the Planner's `day` preset and the
|
|
370
|
-
* AlignedStack date-axis panel guarded). The style sweeps ride `style` —
|
|
371
|
-
* density rhythm and gutter width — over a canvas holding every row kind, so
|
|
372
|
-
* one click re-rhythms them all; every callback (select / element click /
|
|
373
|
-
* group toggle / grain change) logs to the aside, the retired configurators'
|
|
374
|
-
* pattern. Fill sizing stays `planFill`'s; the per-kind visual grammars stay
|
|
375
|
-
* the static per-kind panels.
|
|
376
|
-
*/
|
|
377
|
-
export const planVariants = example({
|
|
378
|
-
keywords: [
|
|
379
|
-
"Plan", "configurator", "Configurator", "variants", "preset", "axis", "window",
|
|
380
|
-
"resolution", "month", "week", "day", "roadmap", "sprint", "ops", "format",
|
|
381
|
-
"ddd", "#309", "density", "condensed", "compact", "comfortable", "gutterWidth",
|
|
382
|
-
"gutter", "style", "onSelect", "onElementClick", "ElementRef", "onGroupToggle", "onGrainChange",
|
|
383
|
-
"callback", "aside", "Reactive", "State", "SegmentGroup", "Select", "getTag",
|
|
384
|
-
"every row kind", "span", "chart", "expandable", "heat", "buckets", "table",
|
|
385
|
-
"tableSeries", "vertical", "cards", "events", "group", "section", "row id", "print",
|
|
386
|
-
],
|
|
387
|
-
description: "Plan configurator — axis window presets (ops week / year roadmap / 14-day sprint) with density and gutter-width sweeps over every row kind on one live canvas; every callback logs to the aside",
|
|
388
|
-
fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
|
|
389
|
-
// Monday of ISO week n, 2026 — the ops window is W27–W38
|
|
390
|
-
// (half-open at W39), now = W31; the roadmap preset walks the
|
|
391
|
-
// whole ISO year and the sprint preset a 14-day slice of it.
|
|
392
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
393
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
394
|
-
return w1.addWeeks(n.subtract(1n));
|
|
395
|
-
}));
|
|
396
|
-
const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
|
|
397
|
-
const JobRow = StructType({
|
|
398
|
-
key: StringType, label: StringType,
|
|
399
|
-
start: DateTimeType, end: DateTimeType, state: EventStateType,
|
|
400
|
-
});
|
|
401
|
-
const AllocRow = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
|
|
402
|
-
const ShiftRow = StructType({
|
|
403
|
-
key: StringType, from: DateTimeType, to: DateTimeType, label: StringType, state: EventStateType,
|
|
404
|
-
});
|
|
405
|
-
const RawCell = StructType({ at: DateTimeType, value: OptionType(FloatType) });
|
|
406
|
-
// ONE flat source (the `planExpand` shape): `series` names the
|
|
407
|
-
// series that claims the row, and every channel a row's kind does
|
|
408
|
-
// not read stays empty.
|
|
409
|
-
const OpsRow = StructType({
|
|
410
|
-
series: StringType, label: StringType,
|
|
411
|
-
sub: OptionType(StringType),
|
|
412
|
-
jobs: ArrayType(JobRow),
|
|
413
|
-
points: ArrayType(MeasureRow),
|
|
414
|
-
cells: ArrayType(Plan.Types.HeatCell),
|
|
415
|
-
allocs: ArrayType(AllocRow),
|
|
416
|
-
act: ArrayType(RawCell),
|
|
417
|
-
plan: ArrayType(RawCell),
|
|
418
|
-
shifts: ArrayType(ShiftRow),
|
|
419
|
-
marks: ArrayType(Plan.Types.EventMark),
|
|
420
|
-
});
|
|
421
|
-
const noJobs = $.const([], ArrayType(JobRow));
|
|
422
|
-
const noPoints = $.const([], ArrayType(MeasureRow));
|
|
423
|
-
const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
|
|
424
|
-
const noAllocs = $.const([], ArrayType(AllocRow));
|
|
425
|
-
const noRaw = $.const([], ArrayType(RawCell));
|
|
426
|
-
const noShifts = $.const([], ArrayType(ShiftRow));
|
|
427
|
-
const noMarks = $.const([], ArrayType(Plan.Types.EventMark));
|
|
428
|
-
const pcts = $.const([46.0, 58.0, 66.0, 72.0, 84.0, 96.0], ArrayType(FloatType));
|
|
429
|
-
const points = $.let(East.Array.generate(6n, MeasureRow, (_$, i) => ({ week: week(i.multiply(2n).add(27n)), pct: pcts.get(i) })));
|
|
430
|
-
const cells = $.let(East.Array.generate(6n, Plan.Types.HeatCell, (_$, i) => ({
|
|
431
|
-
at: Plan.at.time(week(i.multiply(2n).add(27n))),
|
|
432
|
-
value: some(pcts.get(i)),
|
|
433
|
-
label: some(East.Float.printFixed(pcts.get(i), 0n)),
|
|
434
|
-
})));
|
|
435
|
-
// Despatched tonnes and their signed Δ against plan, fortnightly —
|
|
436
|
-
// the table row's two positions.
|
|
437
|
-
const act = $.let(East.Array.generate(6n, RawCell, (_$, i) => ({
|
|
438
|
-
at: week(i.multiply(2n).add(27n)), value: some(pcts.get(i).multiply(1.5)),
|
|
439
|
-
})));
|
|
440
|
-
const delta = $.let(East.Array.generate(6n, RawCell, (_$, i) => ({
|
|
441
|
-
at: week(i.multiply(2n).add(27n)), value: some(pcts.get(i).subtract(70.0)),
|
|
442
|
-
})));
|
|
443
|
-
const base = {
|
|
444
|
-
sub: none, jobs: noJobs, points: noPoints, cells: noCells,
|
|
445
|
-
allocs: noAllocs, act: noRaw, plan: noRaw, shifts: noShifts, marks: noMarks,
|
|
446
|
-
};
|
|
447
|
-
// Every row kind on purpose: the density sweep re-rhythms them all
|
|
448
|
-
// at once — compact tightens the shared row and the bars in it,
|
|
449
|
-
// while a two-line gutter keeps its floor and a stacked table a
|
|
450
|
-
// line per position — and the gutter sweep re-widths every label.
|
|
451
|
-
const ops = $.const(new Map([
|
|
452
|
-
["util", { ...base, series: "util", label: "Util %", points }],
|
|
453
|
-
["m03", { ...base, series: "mach", label: "L1-M03", sub: some("cap 120 t"),
|
|
454
|
-
jobs: [
|
|
455
|
-
{ key: "b214", label: "RUN · B-214", start: week(28n), end: week(31n), state: variant("in-progress", null) },
|
|
456
|
-
{ key: "b221", label: "RUN · B-221", start: week(32n), end: week(35n), state: variant("proposed", variant("recommended", null)) },
|
|
457
|
-
] }],
|
|
458
|
-
["m04", { ...base, series: "mach", label: "L1-M04",
|
|
459
|
-
jobs: [
|
|
460
|
-
{ key: "b208", label: "RUN · B-208", start: week(27n), end: week(30n), state: variant("actual", null) },
|
|
461
|
-
] }],
|
|
462
|
-
["load", { ...base, series: "load", label: "L2 load", sub: some("%/wk"), cells }],
|
|
463
|
-
["dock2", { ...base, series: "dock", label: "Dock 2",
|
|
464
|
-
allocs: [
|
|
465
|
-
{ key: "a1", at: week(28n), state: variant("confirmed", null) },
|
|
466
|
-
{ key: "a2", at: week(31n), state: variant("proposed", variant("recommended", null)) },
|
|
467
|
-
{ key: "a3", at: week(33n), state: variant("confirmed", null) },
|
|
468
|
-
] }],
|
|
469
|
-
["desp", { ...base, series: "desp", label: "Despatch t", act, plan: delta }],
|
|
470
|
-
["crewA", { ...base, series: "crew", label: "Crew A",
|
|
471
|
-
shifts: [
|
|
472
|
-
{ key: "s1", from: week(27n), to: week(29n), label: "80h", state: variant("confirmed", null) },
|
|
473
|
-
{ key: "s2", from: week(31n), to: week(33n), label: "+64h", state: variant("proposed", variant("recommended", null)) },
|
|
474
|
-
] }],
|
|
475
|
-
["ms", { ...base, series: "ms", label: "Milestones",
|
|
476
|
-
marks: [
|
|
477
|
-
{ key: "k", at: Plan.at.time(week(29n)), kind: variant("milestone", null), icon: none, label: some("KICKOFF") },
|
|
478
|
-
{ key: "a", at: Plan.at.time(week(34n)), kind: variant("exception", null), icon: none, label: some("AUDIT") },
|
|
479
|
-
] }],
|
|
480
|
-
["m11", { ...base, series: "gmach", label: "L3-M11",
|
|
481
|
-
jobs: [
|
|
482
|
-
{ key: "b301", label: "RUN · B-301", start: week(29n), end: week(33n), state: variant("confirmed", null) },
|
|
483
|
-
] }],
|
|
484
|
-
]), DictType(StringType, OpsRow));
|
|
485
|
-
const series = $.const([
|
|
486
|
-
// The spark's gutter opens it to the expanded chart, at any density.
|
|
487
|
-
Plan.series.chart(OpsRow, {
|
|
488
|
-
key: "util", title: "Utilisation",
|
|
489
|
-
match: r => r.series.equal("util"),
|
|
490
|
-
label: r => r.label, id: true, height: "spark", expandable: true,
|
|
491
|
-
layers: r => [Chart.Line(r.points, { x: p => p.week, y: p => p.pct })],
|
|
492
|
-
}),
|
|
493
|
-
Plan.series.span(OpsRow, {
|
|
494
|
-
key: "mach", title: "Machine jobs",
|
|
495
|
-
match: r => r.series.equal("mach"),
|
|
496
|
-
label: r => r.label, id: true, sub: r => r.sub,
|
|
497
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
498
|
-
key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
|
|
499
|
-
})),
|
|
500
|
-
}),
|
|
501
|
-
Plan.series.heat(OpsRow, {
|
|
502
|
-
key: "load", title: "Line load",
|
|
503
|
-
match: r => r.series.equal("load"),
|
|
504
|
-
label: r => r.label, sub: r => r.sub,
|
|
505
|
-
cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
|
|
506
|
-
}),
|
|
507
|
-
Plan.series.buckets(OpsRow, {
|
|
508
|
-
key: "dock", title: "Dock allocations",
|
|
509
|
-
match: r => r.series.equal("dock"),
|
|
510
|
-
label: r => r.label,
|
|
511
|
-
events: r => r.allocs.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
|
|
512
|
-
}),
|
|
513
|
-
// The positions STACKED — the row grows a line per position.
|
|
514
|
-
Plan.series.table(OpsRow, {
|
|
515
|
-
key: "desp", title: "Despatch",
|
|
516
|
-
match: r => r.series.equal("desp"),
|
|
517
|
-
label: r => r.label, split: "vertical",
|
|
518
|
-
series: r => [
|
|
519
|
-
Plan.tableSeries({ strong: true, cells: Plan.tableCells(r.act) }),
|
|
520
|
-
Plan.tableSeries({
|
|
521
|
-
tone: "muted",
|
|
522
|
-
format: Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }),
|
|
523
|
-
cells: Plan.tableCells(r.plan),
|
|
524
|
-
}),
|
|
525
|
-
],
|
|
526
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
527
|
-
}),
|
|
528
|
-
Plan.series.cards(OpsRow, {
|
|
529
|
-
key: "crew", title: "Crew shifts",
|
|
530
|
-
match: r => r.series.equal("crew"),
|
|
531
|
-
label: r => r.label,
|
|
532
|
-
chips: r => r.shifts.map((_$, s) => Plan.chip({ key: s.key, from: s.from, to: s.to, label: s.label, state: s.state })),
|
|
533
|
-
}),
|
|
534
|
-
Plan.series.events(OpsRow, {
|
|
535
|
-
key: "ms", title: "Milestones",
|
|
536
|
-
match: r => r.series.equal("ms"),
|
|
537
|
-
label: r => r.label, id: true,
|
|
538
|
-
marks: r => r.marks,
|
|
539
|
-
}),
|
|
540
|
-
// A section header, so the group-toggle callback has something to fire on.
|
|
541
|
-
Plan.series.section(OpsRow, { key: "line3", title: "Line 3", meta: "1 row" }, [
|
|
542
|
-
Plan.series.span(OpsRow, {
|
|
543
|
-
key: "gmach", title: "Grouped jobs",
|
|
544
|
-
match: r => r.series.equal("gmach"),
|
|
545
|
-
label: r => r.label, id: true,
|
|
546
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
547
|
-
key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
|
|
548
|
-
})),
|
|
549
|
-
}),
|
|
550
|
-
]),
|
|
551
|
-
], ArrayType(Plan.Types.Series(OpsRow)));
|
|
552
|
-
// Every preset is DATA — a whole axis value in a typed struct, so
|
|
553
|
-
// switching presets swaps window, resolution, format and now
|
|
554
|
-
// through ONE expression-fed prop.
|
|
555
|
-
const presets = $.const([
|
|
556
|
-
{ key: "ops", axis: Plan.axis({
|
|
557
|
-
window: { min: week(27n), max: week(39n) },
|
|
558
|
-
resolution: "week", resolutions: ["month", "week", "day"], now: week(31n),
|
|
559
|
-
}) },
|
|
560
|
-
{ key: "roadmap", axis: Plan.axis({
|
|
561
|
-
window: { min: week(1n), max: week(53n) },
|
|
562
|
-
resolution: "month", format: "MMM", now: week(31n),
|
|
563
|
-
}) },
|
|
564
|
-
{ key: "sprint", axis: Plan.axis({
|
|
565
|
-
window: { min: week(30n), max: week(32n) },
|
|
566
|
-
resolution: "day", format: "ddd DD", now: week(31n),
|
|
567
|
-
}) },
|
|
568
|
-
], ArrayType(StructType({ key: StringType, axis: Plan.Types.Axis })));
|
|
569
|
-
const presetKeys = $.const(["ops", "roadmap", "sprint"], ArrayType(StringType));
|
|
570
|
-
const densities = $.const([
|
|
571
|
-
variant("condensed", null), variant("compact", null), variant("comfortable", null),
|
|
572
|
-
], ArrayType(Style.Types.Density));
|
|
573
|
-
const gutters = $.const(["168px", "200px", "240px"], ArrayType(StringType));
|
|
574
|
-
const presetBind = $.let(State.bind([StringType], "plan_variants_preset", "ops"));
|
|
575
|
-
const densityBind = $.let(State.bind([StringType], "plan_variants_density", "compact"));
|
|
576
|
-
const gutterBind = $.let(State.bind([StringType], "plan_variants_gutter", "168px"));
|
|
577
|
-
const lastEventBind = $.let(State.bind([StringType], "plan_variants_last_event", ""));
|
|
578
|
-
const pKey = $.let(presetBind.read());
|
|
579
|
-
const dKey = $.let(densityBind.read());
|
|
580
|
-
const gKey = $.let(gutterBind.read());
|
|
581
|
-
const lastEvent = $.let(lastEventBind.read());
|
|
582
|
-
const onPreset = $.const(East.function([StringType], NullType, ($, next) => { $(presetBind.write(next)); }));
|
|
583
|
-
const onDensity = $.const(East.function([StringType], NullType, ($, next) => { $(densityBind.write(next)); }));
|
|
584
|
-
const onGutter = $.const(East.function([StringType], NullType, ($, next) => { $(gutterBind.write(next)); }));
|
|
585
|
-
// The interactive surface — every callback writes the aside line,
|
|
586
|
-
// naming the row by its id (the series that made it and the path
|
|
587
|
-
// of keys to it) in its `.east` text.
|
|
588
|
-
const onSelect = $.const(East.function([Plan.Types.RowId], NullType, ($, id) => {
|
|
589
|
-
$(lastEventBind.write(East.str `onSelect · ${East.print(id)}`));
|
|
590
|
-
}));
|
|
591
|
-
// ONE callback for every element (#824) — the ref's arm says which
|
|
592
|
-
// kind was clicked, and each arm names its row and element.
|
|
593
|
-
const onElementClick = $.const(East.function([Plan.Types.ElementRef], NullType, ($, ref) => {
|
|
594
|
-
$(lastEventBind.write(East.str `onElementClick · ${ref.getTag()} · ${East.print(ref)}`));
|
|
595
|
-
}));
|
|
596
|
-
const onGroupToggle = $.const(East.function([Plan.Types.GroupToggleEvent], NullType, ($, ev) => {
|
|
597
|
-
const state = $.let(ev.expanded.ifElse(() => "expanded", () => "collapsed"), StringType);
|
|
598
|
-
$(lastEventBind.write(East.str `onGroupToggle · ${East.print(ev.row)} → ${state}`));
|
|
599
|
-
}));
|
|
600
|
-
const onGrainChange = $.const(East.function([Plan.Types.Grain], NullType, ($, grain) => {
|
|
601
|
-
$(lastEventBind.write(East.str `onGrainChange · ${grain.getTag()}`));
|
|
602
|
-
}));
|
|
603
|
-
const sel = $.let(presets.filter((_$, o) => o.key.equal(pKey)).get(0n, _$ => presets.get(0n)));
|
|
604
|
-
const densitySel = $.let(densities.filter((_$, v) => v.getTag().equal(dKey)).get(0n));
|
|
605
|
-
return (_jsx(Configurator, { controls: [
|
|
606
|
-
Configurator.Control("Preset", pKey, _jsx(Select, { value: pKey, onChange: onPreset, size: "sm", items: presetKeys.map((_$, s) => Select.Item(s, s)) })),
|
|
607
|
-
Configurator.Control("Density", dKey, _jsx(SegmentGroup, { value: dKey, onChange: onDensity, size: "sm", items: densities.map((_$, v) => SegmentGroup.Item(v.getTag(), _jsx(Text, { children: v.getTag().upperCase() }))) })),
|
|
608
|
-
Configurator.Control("Gutter", gKey, _jsx(SegmentGroup, { value: gKey, onChange: onGutter, size: "sm", items: gutters.map((_$, g) => SegmentGroup.Item(g, _jsx(Text, { children: g }))) })),
|
|
609
|
-
], preview: _jsx(Plan, { axis: sel.axis, data: ops, series: series, onSelect: onSelect, onElementClick: onElementClick, onGroupToggle: onGroupToggle, onGrainChange: onGrainChange, style: { density: densitySel, gutterWidth: gKey } }), live: true, aside: {
|
|
610
|
-
label: "Events · Reactive",
|
|
611
|
-
body: (_jsx(Badge, { colorPalette: "brand", variant: "outline", children: East.equal(lastEvent.length(), 0n).ifElse(_$ => "Interact with the canvas", _$ => lastEvent) })),
|
|
612
|
-
}, spec: [
|
|
613
|
-
Configurator.Spec("Resolution", sel.axis.unwrap("time").resolution.getTag()),
|
|
614
|
-
Configurator.Spec("Rows", East.print(ops.size())),
|
|
615
|
-
] }));
|
|
616
|
-
} }))),
|
|
617
|
-
inputs: [],
|
|
618
|
-
});
|
|
619
|
-
// ============================================================================
|
|
620
|
-
// Per-kind examples — one canvas per row kind; the config sweep is DATA
|
|
621
|
-
// ============================================================================
|
|
622
|
-
export const planSpanRows = example({
|
|
623
|
-
keywords: ["Plan", "data", "series", "span", "run", "state", "estimated", "removed", "rejected", "decision", "port", "rollup", "union", "byStatus", "children", "nested", "recursive", "RecursiveType", "bands", "section", "stacked", "gutter", "links", "link", "Plan.ref", "row id", "focus", "expand", "expandRender", "match", "raw", "quantity", "Plan.quantity", "unit", "sum per unit"],
|
|
624
|
-
description: "Span rows over one raw machine source — proposal flavours, decision diamonds and ports, programs rolling their machines up into bands, and a link graph",
|
|
625
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
626
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
627
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
628
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
629
|
-
return w1.addWeeks(n.subtract(1n));
|
|
630
|
-
}));
|
|
631
|
-
const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
|
|
632
|
-
// The RAW job record; `series` picks the series, everything else —
|
|
633
|
-
// including the expand declaration — is per-row data.
|
|
634
|
-
const JobRow = StructType({
|
|
635
|
-
key: StringType, phase: StringType, batch: OptionType(StringType),
|
|
636
|
-
start: DateTimeType, end: DateTimeType,
|
|
637
|
-
tonnes: OptionType(FloatType), state: EventStateType,
|
|
638
|
-
});
|
|
639
|
-
// A PROGRAM is a record too, holding its machines (`machines`): the
|
|
640
|
-
// hierarchy is the data's own (#822), so a program's row nests them —
|
|
641
|
-
// and the entry type is recursive, to whatever depth the data has.
|
|
642
|
-
const MachineRow = RecursiveType((self) => StructType({
|
|
643
|
-
series: StringType,
|
|
644
|
-
sub: OptionType(StringType), value: OptionType(StringType),
|
|
645
|
-
expand: OptionType(Plan.Types.Expand),
|
|
646
|
-
jobs: ArrayType(JobRow),
|
|
647
|
-
decisions: ArrayType(Plan.Types.DecisionMark),
|
|
648
|
-
ports: ArrayType(Plan.Types.Port),
|
|
649
|
-
machines: DictType(StringType, self),
|
|
650
|
-
}));
|
|
651
|
-
const noMachines = $.const(new Map(), DictType(StringType, MachineRow));
|
|
652
|
-
const machines = $.const(new Map([
|
|
653
|
-
// Proposal flavours: forecast ghost · proposed cut · declined.
|
|
654
|
-
["L1-M07", { series: "flavours", sub: none, value: some("80 t"), expand: none,
|
|
655
|
-
jobs: [
|
|
656
|
-
{ key: "run", phase: "RUN", batch: some("B-197"), start: week(27n), end: week(30n), tonnes: some(64.0), state: variant("in-progress", null) },
|
|
657
|
-
{ key: "gho", phase: "FORECAST", batch: none, start: week(30n), end: week(32n), tonnes: none, state: variant("estimated", null) },
|
|
658
|
-
{ key: "rem", phase: "CUT", batch: none, start: week(33n), end: week(35n), tonnes: none, state: variant("proposed", variant("removed", null)) },
|
|
659
|
-
{ key: "rej", phase: "DECLINED", batch: none, start: week(36n), end: week(38n), tonnes: none, state: variant("rejected", null) },
|
|
660
|
-
], decisions: [], ports: [], machines: noMachines }],
|
|
661
|
-
// Tonnage + an applied decision + a port on a stacked two-line
|
|
662
|
-
// gutter; the EXPAND DECLARATION is row data (R2) — the render is
|
|
663
|
-
// the root's expandRender resolver.
|
|
664
|
-
["L1-M09", { series: "detail", sub: some("cap 120 t"), value: none,
|
|
665
|
-
expand: some({ height: some("152px"), axis: variant("dim", null) }),
|
|
666
|
-
jobs: [
|
|
667
|
-
{ key: "a", phase: "RUN", batch: some("B-208"), start: week(27n), end: week(31n), tonnes: some(112.0), state: variant("actual", null) },
|
|
668
|
-
{ key: "b", phase: "RUN", batch: some("B-231"), start: week(31n), end: week(34n), tonnes: some(104.0), state: variant("proposed", variant("recommended", null)) },
|
|
669
|
-
],
|
|
670
|
-
decisions: [{ key: "d1", at: Plan.at.time(week(31n)), applied: true }],
|
|
671
|
-
ports: [{ at: Plan.at.time(week(31n)), label: some("−24 t") }], machines: noMachines }],
|
|
672
|
-
// A program and its machines — the program's row rolls their runs
|
|
673
|
-
// up into union bands (renderer-derived).
|
|
674
|
-
["Program A", { series: "rollup", sub: none, value: none, expand: none, jobs: [], decisions: [], ports: [],
|
|
675
|
-
machines: new Map([
|
|
676
|
-
["L1-M03", { series: "rollup", sub: none, value: none, expand: none,
|
|
677
|
-
jobs: [
|
|
678
|
-
{ key: "b214", phase: "RUN", batch: some("B-214"), start: week(28n), end: week(31n), tonnes: some(96.0), state: variant("actual", null) },
|
|
679
|
-
{ key: "b221", phase: "RUN", batch: some("B-221"), start: week(32n), end: week(35n), tonnes: some(88.0), state: variant("proposed", variant("recommended", null)) },
|
|
680
|
-
], decisions: [], ports: [], machines: noMachines }],
|
|
681
|
-
["L2-M11", { series: "rollup", sub: none, value: none, expand: none,
|
|
682
|
-
jobs: [{ key: "b241", phase: "RUN", batch: some("B-241"), start: week(29n), end: week(33n), tonnes: some(92.0), state: variant("confirmed", null) }],
|
|
683
|
-
decisions: [], ports: [], machines: noMachines }],
|
|
684
|
-
]) }],
|
|
685
|
-
// Linked despatch whose run starts BEYOND the window — in links
|
|
686
|
-
// focus its landing renders as the edge fade.
|
|
687
|
-
["dsp", { series: "despatch", sub: none, value: none, expand: none,
|
|
688
|
-
jobs: [{ key: "d1", phase: "DSP", batch: none, start: week(39n), end: week(42n), tonnes: some(91.0), state: variant("proposed", variant("recommended", null)) }],
|
|
689
|
-
decisions: [], ports: [], machines: noMachines }],
|
|
690
|
-
// The program under the Line 2 section, rolled up byStatus.
|
|
691
|
-
["Program B", { series: "line2", sub: none, value: none, expand: none, jobs: [], decisions: [], ports: [],
|
|
692
|
-
machines: new Map([
|
|
693
|
-
["L2-M12", { series: "line2", sub: none, value: none, expand: none,
|
|
694
|
-
jobs: [
|
|
695
|
-
{ key: "r1", phase: "RUN", batch: some("B-198"), start: week(28n), end: week(32n), tonnes: some(64.0), state: variant("actual", null) },
|
|
696
|
-
{ key: "r2", phase: "RUN", batch: some("B-202"), start: week(30n), end: week(34n), tonnes: some(40.0), state: variant("proposed", variant("recommended", null)) },
|
|
697
|
-
], decisions: [], ports: [], machines: noMachines }],
|
|
698
|
-
]) }],
|
|
699
|
-
]), DictType(StringType, MachineRow));
|
|
700
|
-
// Raw jobs → runs, once — every span series shares the mapping. A
|
|
701
|
-
// job's tonnage is its run's QUANTITY: the bar prints it, and a
|
|
702
|
-
// program's bands sum its machines' tonnes unit by unit (#824).
|
|
703
|
-
const tonnesFormat = $.const(Format.Number({ maximumFractionDigits: 0n }));
|
|
704
|
-
const jobRuns = $.const(East.function([ArrayType(JobRow)], ArrayType(Plan.Types.Run), (_$, jobs) => jobs.map(($, j) => {
|
|
705
|
-
const noQuantity = $.const(none, OptionType(Plan.Types.Quantity));
|
|
706
|
-
const quantity = $.let(j.tonnes.match({
|
|
707
|
-
some: (_$, t) => East.value(some(Plan.quantity(t, { unit: "t", format: tonnesFormat })), OptionType(Plan.Types.Quantity)),
|
|
708
|
-
none: (_$) => noQuantity,
|
|
709
|
-
}), OptionType(Plan.Types.Quantity));
|
|
710
|
-
const label = $.let(j.batch.match({
|
|
711
|
-
some: (_$, b) => East.str `${j.phase} · ${b}`,
|
|
712
|
-
none: (_$) => j.phase,
|
|
713
|
-
}), StringType);
|
|
714
|
-
const run = $.let({
|
|
715
|
-
key: j.key, start: Plan.at.time(j.start), end: Plan.at.time(j.end), label,
|
|
716
|
-
quantity, state: j.state,
|
|
717
|
-
status: none, moved: none, icon: none,
|
|
718
|
-
}, Plan.Types.Run);
|
|
719
|
-
return run;
|
|
720
|
-
})));
|
|
721
|
-
const series = $.const([
|
|
722
|
-
Plan.series.span(MachineRow, {
|
|
723
|
-
key: "flavours", title: "Flavours",
|
|
724
|
-
match: r => r.series.equal("flavours"),
|
|
725
|
-
label: (_r, k) => k, id: true,
|
|
726
|
-
value: r => r.value,
|
|
727
|
-
runs: r => jobRuns(r.jobs),
|
|
728
|
-
}),
|
|
729
|
-
Plan.series.span(MachineRow, {
|
|
730
|
-
key: "detail", title: "Detail",
|
|
731
|
-
match: r => r.series.equal("detail"),
|
|
732
|
-
label: (_r, k) => k, id: true, stacked: true,
|
|
733
|
-
sub: r => r.sub, expand: r => r.expand,
|
|
734
|
-
runs: r => jobRuns(r.jobs), decisions: r => r.decisions, ports: r => r.ports,
|
|
735
|
-
}),
|
|
736
|
-
// A program's machines nest under it (`children` — more of this
|
|
737
|
-
// series, to any depth), and its row rolls their runs up.
|
|
738
|
-
Plan.series.span(MachineRow, {
|
|
739
|
-
key: "rollup", title: "Rollup",
|
|
740
|
-
match: r => r.series.equal("rollup"),
|
|
741
|
-
label: (_r, k) => k, id: true,
|
|
742
|
-
runs: r => jobRuns(r.jobs),
|
|
743
|
-
children: r => r.machines, rollup: "union",
|
|
744
|
-
}),
|
|
745
|
-
Plan.series.span(MachineRow, {
|
|
746
|
-
key: "despatch", title: "Despatch",
|
|
747
|
-
match: r => r.series.equal("despatch"),
|
|
748
|
-
label: (_r, k) => k, id: true,
|
|
749
|
-
runs: r => jobRuns(r.jobs),
|
|
750
|
-
}),
|
|
751
|
-
Plan.series.section(MachineRow, { key: "line2", title: "Line 2", meta: "1 row" }, [
|
|
752
|
-
Plan.series.span(MachineRow, {
|
|
753
|
-
key: "programs", title: "Programs",
|
|
754
|
-
match: r => r.series.equal("line2"),
|
|
755
|
-
label: (_r, k) => k, id: true,
|
|
756
|
-
runs: r => jobRuns(r.jobs),
|
|
757
|
-
children: r => r.machines, rollup: "byStatus",
|
|
758
|
-
}),
|
|
759
|
-
]),
|
|
760
|
-
], ArrayType(Plan.Types.Series(MachineRow)));
|
|
761
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
762
|
-
// The R2 developer render — the ROOT's resolver, called with the
|
|
763
|
-
// focused row's id; ONE function serves every declaring row.
|
|
764
|
-
const util = $.let(East.Array.generate(8n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: i.multiply(13n).remainder(40n).toFloat().add(55.0) })));
|
|
765
|
-
const expandRender = $.const(East.function([Plan.Types.RowId], UIComponentType, (_$, _id) => (_jsx(Chart, { layers: [Chart.Column(util, { x: r => r.week, y: r => r.pct })], height: 100, grid: false }))));
|
|
766
|
-
// The generalized popover resolver — decision diamonds ride the mark
|
|
767
|
-
// arm of the element ref.
|
|
768
|
-
const popover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
|
|
769
|
-
const noBody = $.const(none, OptionType(UIComponentType));
|
|
770
|
-
return ref.match({
|
|
771
|
-
mark: (_$, ev) => ev.mark.equal("d1").ifElse(() => some(_jsx(Text, { children: "Approved by run 411." })), () => noBody),
|
|
772
|
-
}, _$ => noBody);
|
|
773
|
-
}));
|
|
774
|
-
// The linked rows, by id — the series that made each and the path of
|
|
775
|
-
// entry keys to it (a program's machine sits under the program).
|
|
776
|
-
const m07 = $.const(Plan.ref("flavours", "L1-M07"));
|
|
777
|
-
const m09 = $.const(Plan.ref("detail", "L1-M09"));
|
|
778
|
-
const m03 = $.const(Plan.ref("rollup", "Program A", "L1-M03"));
|
|
779
|
-
const m11 = $.const(Plan.ref("rollup", "Program A", "L2-M11"));
|
|
780
|
-
const dsp = $.const(Plan.ref("despatch", "dsp"));
|
|
781
|
-
return (_jsx(Plan, { expandRender: expandRender, popover: popover,
|
|
782
|
-
// A denser gutter (value + carets) — widen it (the shared
|
|
783
|
-
// CSS-px height/width vocabulary).
|
|
784
|
-
style: { gutterWidth: "200px" }, axis: axis,
|
|
785
|
-
// The link graph (R1) — hover a linked row for the ⌁ control.
|
|
786
|
-
// The edges deliberately cover the routing permutations:
|
|
787
|
-
// forward, a same-row seam feed, loopbacks, a rising loop,
|
|
788
|
-
// and an off-window landing. Each moves a QUANTITY (#824):
|
|
789
|
-
// its value weighs the ribbon and its caption prints on it.
|
|
790
|
-
links: [
|
|
791
|
-
Plan.link({ key: "l1", from: m07, fromRun: "run", to: m09, toRun: "a", quantity: Plan.quantity(24, { unit: "t" }) }),
|
|
792
|
-
Plan.link({ key: "l2", from: m09, fromRun: "a", to: m09, toRun: "b", quantity: Plan.quantity(40, { unit: "t" }) }),
|
|
793
|
-
Plan.link({ key: "l3", from: m09, fromRun: "b", to: m03, toRun: "b221", quantity: Plan.quantity(88, { unit: "t" }) }),
|
|
794
|
-
Plan.link({ key: "l4", from: m03, fromRun: "b214", to: m11, toRun: "b241", quantity: Plan.quantity(32, { unit: "t" }) }),
|
|
795
|
-
Plan.link({ key: "l5", from: m11, fromRun: "b241", to: m09, toRun: "b", quantity: Plan.quantity(18, { unit: "t" }) }),
|
|
796
|
-
Plan.link({ key: "l6", from: m09, fromRun: "b", to: dsp, toRun: "d1", quantity: Plan.quantity(91, { unit: "t" }) }),
|
|
797
|
-
], data: machines, series: series }));
|
|
798
|
-
}),
|
|
799
|
-
inputs: [],
|
|
800
|
-
});
|
|
801
|
-
export const planBucketRows = example({
|
|
802
|
-
keywords: ["Plan", "data", "series", "buckets", "Planner", "lane", "lanes", "AM", "PM", "event", "tile", "marker", "tone", "color", "colorPalette", "stretch", "pulse", "icon", "hovercard", "popover", "mixed", "unbucketed", "section", "match", "gutter", "raw"],
|
|
803
|
-
description: "Bucket rows over one dock source — tiles derived in the accessor, and stored tile records with lanes, tones, colours and markers",
|
|
804
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
805
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
806
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
807
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
808
|
-
return w1.addWeeks(n.subtract(1n));
|
|
809
|
-
}));
|
|
810
|
-
const AllocRow = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
|
|
811
|
-
const DockRow = StructType({
|
|
812
|
-
series: StringType, label: StringType,
|
|
813
|
-
sub: OptionType(StringType),
|
|
814
|
-
lanes: ArrayType(Plan.Types.Lane),
|
|
815
|
-
allocations: ArrayType(AllocRow),
|
|
816
|
-
tiles: ArrayType(Plan.Types.BucketEvent),
|
|
817
|
-
markers: ArrayType(Plan.Types.CellMarker),
|
|
818
|
-
});
|
|
819
|
-
// Raw weekly allocations; every third one is a proposal.
|
|
820
|
-
const inbound = $.let(East.Array.generate(6n, AllocRow, ($, i) => {
|
|
821
|
-
const confirmed = $.const(variant("confirmed", null), EventStateType);
|
|
822
|
-
const recommended = $.const(variant("proposed", variant("recommended", null)), EventStateType);
|
|
823
|
-
return {
|
|
824
|
-
key: East.str `a${East.print(i)}`,
|
|
825
|
-
at: week(i.add(27n)),
|
|
826
|
-
state: i.remainder(3n).equal(2n).ifElse(() => recommended, () => confirmed),
|
|
827
|
-
};
|
|
828
|
-
}));
|
|
829
|
-
const docks = $.const(new Map([
|
|
830
|
-
["dock2", { series: "inbound", label: "Dock 2", sub: some("load/wk"),
|
|
831
|
-
lanes: [], allocations: inbound, tiles: [],
|
|
832
|
-
markers: [{ at: Plan.at.time(week(29n)), lane: none, status: variant("warning", null), message: "capacity 90%" }] }],
|
|
833
|
-
// The grammar showcase — tiles stored IN the element vocabulary
|
|
834
|
-
// (plain `PlanBucketEventType` records; no builders in data).
|
|
835
|
-
["dock5", { series: "outbound", label: "Dock 5", sub: some("day · am/pm"),
|
|
836
|
-
lanes: [{ key: "am", label: some("AM") }, { key: "pm", label: some("PM") }],
|
|
837
|
-
allocations: [],
|
|
838
|
-
tiles: [
|
|
839
|
-
{ key: "m1", at: Plan.at.time(week(27n)), lane: some("am"), label: none, icon: none, state: variant("confirmed", null),
|
|
840
|
-
tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: none },
|
|
841
|
-
{ key: "m2", at: Plan.at.time(week(27n)), lane: some("pm"), label: none, icon: none, state: variant("confirmed", null),
|
|
842
|
-
tone: some(variant("warning", null)), color: none, colorPalette: none, stretch: none, content: none, animation: none },
|
|
843
|
-
{ key: "m3", at: Plan.at.time(week(28n)), lane: some("am"), label: none, icon: none, state: variant("proposed", variant("recommended", null)),
|
|
844
|
-
tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: some(variant("pulse", null)) },
|
|
845
|
-
{ key: "m4", at: Plan.at.time(week(29n)), lane: none, label: some("MIXED"), icon: none, state: variant("confirmed", null),
|
|
846
|
-
tone: none, color: none, colorPalette: none, stretch: some(variant("horizontal", null)),
|
|
847
|
-
content: some({ horizontal: some(variant("center", null)), vertical: none }), animation: none },
|
|
848
|
-
{ key: "m5", at: Plan.at.time(week(30n)), lane: some("pm"), label: none,
|
|
849
|
-
icon: some({ prefix: "fas", name: "truck", label: none, style: none }),
|
|
850
|
-
state: variant("proposed", variant("recommended", null)),
|
|
851
|
-
tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: none },
|
|
852
|
-
{ key: "m6", at: Plan.at.time(week(31n)), lane: some("am"), label: some("QC"), icon: none, state: variant("estimated", null),
|
|
853
|
-
tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: none },
|
|
854
|
-
// The colour channels (#571, from the Planner's colors
|
|
855
|
-
// preset): `color` is a raw token override, `colorPalette`
|
|
856
|
-
// recolours the whole lifecycle treatment.
|
|
857
|
-
{ key: "m7", at: Plan.at.time(week(32n)), lane: some("am"), label: some("S-A"), icon: none, state: variant("confirmed", null),
|
|
858
|
-
tone: none, color: some("teal.solid"), colorPalette: none, stretch: none, content: none, animation: none },
|
|
859
|
-
{ key: "m8", at: Plan.at.time(week(32n)), lane: some("pm"), label: some("S-B"), icon: none, state: variant("confirmed", null),
|
|
860
|
-
tone: none, color: none, colorPalette: some(variant("brand", null)), stretch: none, content: none, animation: none },
|
|
861
|
-
],
|
|
862
|
-
markers: [{ at: Plan.at.time(week(29n)), lane: none, status: variant("danger", null), message: "capacity breach" }] }],
|
|
863
|
-
]), DictType(StringType, DockRow));
|
|
864
|
-
const series = $.const([
|
|
865
|
-
// Raw allocations → resting tiles, in the accessor.
|
|
866
|
-
Plan.series.buckets(DockRow, {
|
|
867
|
-
key: "inbound", title: "Inbound",
|
|
868
|
-
match: r => r.series.equal("inbound"),
|
|
869
|
-
label: r => r.label,
|
|
870
|
-
sub: r => r.sub,
|
|
871
|
-
events: r => r.allocations.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
|
|
872
|
-
markers: r => r.markers,
|
|
873
|
-
}),
|
|
874
|
-
Plan.series.section(DockRow, { key: "docks-out", title: "Docks · Out", meta: "1 row" }, [
|
|
875
|
-
// Stored vocabulary records pass straight through.
|
|
876
|
-
Plan.series.buckets(DockRow, {
|
|
877
|
-
key: "outbound", title: "Outbound",
|
|
878
|
-
match: r => r.series.equal("outbound"),
|
|
879
|
-
label: r => r.label,
|
|
880
|
-
sub: r => r.sub,
|
|
881
|
-
lanes: r => r.lanes, events: r => r.tiles, markers: r => r.markers,
|
|
882
|
-
}),
|
|
883
|
-
]),
|
|
884
|
-
], ArrayType(Plan.Types.Series(DockRow)));
|
|
885
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
886
|
-
// The generalized resolvers — tiles ride the event arm of the ref.
|
|
887
|
-
const popover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
|
|
888
|
-
const noBody = $.const(none, OptionType(UIComponentType));
|
|
889
|
-
return ref.match({
|
|
890
|
-
event: (_$, ev) => ev.event.equal("m5").ifElse(() => some(_jsx(Text, { children: "Load 41 \u00B7 8 pallets" })), () => noBody),
|
|
891
|
-
}, _$ => noBody);
|
|
892
|
-
}));
|
|
893
|
-
const hover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
|
|
894
|
-
const noBody = $.const(none, OptionType(UIComponentType));
|
|
895
|
-
return ref.match({
|
|
896
|
-
event: (_$, ev) => ev.event.equal("m3").ifElse(() => some(_jsx(Text, { children: "Urgent \u2014 overtime window" })), () => noBody),
|
|
897
|
-
}, _$ => noBody);
|
|
898
|
-
}));
|
|
899
|
-
return (_jsx(Plan, { popover: popover, hover: hover, axis: axis, data: docks, series: series }));
|
|
900
|
-
}),
|
|
901
|
-
inputs: [],
|
|
902
|
-
});
|
|
903
|
-
export const planChartRows = example({
|
|
904
|
-
keywords: ["Plan", "data", "series", "chart", "layers", "spark", "expanded", "fixed", "refLine", "refBand", "refDot", "breach", "stacked", "dual-axis", "swatches", "Area", "Band", "Scatter", "Column", "Line", "domain", "tickValues", "section", "match", "gutter", "raw"],
|
|
905
|
-
description: "Chart rows over one measure source — one series per mark kind, plus annotations and a fixed dual-axis composition",
|
|
906
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
907
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
908
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
909
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
910
|
-
return w1.addWeeks(n.subtract(1n));
|
|
911
|
-
}));
|
|
912
|
-
const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
|
|
913
|
-
const BandRow = StructType({ week: DateTimeType, lo: FloatType, hi: FloatType });
|
|
914
|
-
// ONE raw source — each series's points ride the row (`extra` carries
|
|
915
|
-
// the second set for stacked / dual compositions).
|
|
916
|
-
const ChartRow = StructType({
|
|
917
|
-
series: StringType, label: StringType,
|
|
918
|
-
sub: OptionType(StringType), value: OptionType(StringType),
|
|
919
|
-
points: ArrayType(MeasureRow),
|
|
920
|
-
extra: ArrayType(MeasureRow),
|
|
921
|
-
band: ArrayType(BandRow),
|
|
922
|
-
});
|
|
923
|
-
const pcts = $.const([96.1, 96.4, 96.8, 97.0, 96.2, 95.1, 93.4, 91.0, 88.9, 91.4, 93.8, 94.2], ArrayType(FloatType));
|
|
924
|
-
const coverage = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: pcts.get(i) })));
|
|
925
|
-
const cum = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: i.toFloat().multiply(14.0).add(40.0) })));
|
|
926
|
-
const out1 = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: i.multiply(23n).remainder(17n).toFloat().add(28.0) })));
|
|
927
|
-
const out2 = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: i.multiply(31n).remainder(13n).toFloat().add(14.0) })));
|
|
928
|
-
const ppm = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: i.multiply(37n).remainder(60n).toFloat().add(120.0) })));
|
|
929
|
-
const band = $.let(East.Array.generate(12n, BandRow, (_$, i) => ({ week: week(i.add(27n)), lo: pcts.get(i).subtract(3.0), hi: pcts.get(i).add(3.0) })));
|
|
930
|
-
const measures = $.const(new Map([
|
|
931
|
-
["spark", { series: "spark", label: "Coverage", sub: none, value: some("94.2%"), points: coverage, extra: [], band: [] }],
|
|
932
|
-
["cum", { series: "cum", label: "Cumulative · t", sub: none, value: some("194 t"), points: cum, extra: [], band: [] }],
|
|
933
|
-
["stacked", { series: "stacked", label: "Output · t", sub: some("t/wk"), value: none, points: out1, extra: out2, band: [] }],
|
|
934
|
-
["ppm", { series: "ppm", label: "Defects · ppm", sub: none, value: some("161"), points: ppm, extra: [], band: [] }],
|
|
935
|
-
["refs", { series: "refs", label: "Coverage + refs", sub: none, value: none, points: coverage, extra: [], band: [] }],
|
|
936
|
-
["dual", { series: "dual", label: "Out + coverage", sub: none, value: none, points: out1, extra: coverage, band }],
|
|
937
|
-
]), DictType(StringType, ChartRow));
|
|
938
|
-
const series = $.const([
|
|
939
|
-
// Line — the KPI spark with a breach threshold; the caret opens
|
|
940
|
-
// it to a custom 120px (expandedHeight, default 88).
|
|
941
|
-
Plan.series.chart(ChartRow, {
|
|
942
|
-
key: "spark", title: "Spark",
|
|
943
|
-
match: r => r.series.equal("spark"),
|
|
944
|
-
label: r => r.label, id: true,
|
|
945
|
-
value: r => r.value, status: _r => some(variant("warning", null)),
|
|
946
|
-
height: "spark", expandable: true, expandedHeight: "120px",
|
|
947
|
-
layers: r => [Plan.layer(Chart.Line(r.points, { x: p => p.week, y: p => p.pct }), { breach: { below: 92 } })],
|
|
948
|
-
}),
|
|
949
|
-
// Area — the cumulative fill.
|
|
950
|
-
Plan.series.chart(ChartRow, {
|
|
951
|
-
key: "cum", title: "Cumulative",
|
|
952
|
-
match: r => r.series.equal("cum"),
|
|
953
|
-
label: r => r.label, id: true, value: r => r.value,
|
|
954
|
-
layers: r => [Chart.Area(r.points, { x: p => p.week, y: p => p.pct })],
|
|
955
|
-
}),
|
|
956
|
-
// Columns — the row's two point sets stacked by one series id,
|
|
957
|
-
// on a two-line gutter (label over sub).
|
|
958
|
-
Plan.series.chart(ChartRow, {
|
|
959
|
-
key: "stacked", title: "Stacked",
|
|
960
|
-
match: r => r.series.equal("stacked"),
|
|
961
|
-
label: r => r.label, id: true, stacked: true, sub: r => r.sub,
|
|
962
|
-
layers: r => [
|
|
963
|
-
Plan.layer(Chart.Column(r.points, { x: p => p.week, y: p => p.pct }), { series: "L1" }),
|
|
964
|
-
Plan.layer(Chart.Column(r.extra, { x: p => p.week, y: p => p.pct }), { series: "L2" }),
|
|
965
|
-
],
|
|
966
|
-
}),
|
|
967
|
-
// Scatter — the defect cloud.
|
|
968
|
-
Plan.series.chart(ChartRow, {
|
|
969
|
-
key: "ppm", title: "Ppm",
|
|
970
|
-
match: r => r.series.equal("ppm"),
|
|
971
|
-
label: r => r.label, id: true, value: r => r.value,
|
|
972
|
-
layers: r => [Chart.Scatter(r.points, { x: p => p.week, y: p => p.pct })],
|
|
973
|
-
}),
|
|
974
|
-
// Line + every annotation kind, at expanded density.
|
|
975
|
-
Plan.series.chart(ChartRow, {
|
|
976
|
-
key: "refs", title: "Refs",
|
|
977
|
-
match: r => r.series.equal("refs"),
|
|
978
|
-
label: r => r.label, id: true,
|
|
979
|
-
height: "expanded",
|
|
980
|
-
layers: r => [
|
|
981
|
-
Plan.layer(Chart.Line(r.points, { x: p => p.week, y: p => p.pct }), { breach: { below: 92 } }),
|
|
982
|
-
Chart.refLine({ y: 100, label: "TARGET 100" }),
|
|
983
|
-
Chart.refBand({ x: [week(34n), week(36n)], label: "CRUNCH" }),
|
|
984
|
-
Chart.refDot({ x: week(36n), y: 91.4, label: "LOW" }),
|
|
985
|
-
],
|
|
986
|
-
}),
|
|
987
|
-
// The composed dual-axis chart under a section header; axes take
|
|
988
|
-
// Chart.Root's vocabulary — domain / tickValues. Output columns
|
|
989
|
-
// scale left; the coverage line + its band scale right.
|
|
990
|
-
Plan.series.section(ChartRow, { key: "quality", title: "Quality", meta: "1 row" }, [
|
|
991
|
-
Plan.series.chart(ChartRow, {
|
|
992
|
-
key: "dual", title: "Dual",
|
|
993
|
-
match: r => r.series.equal("dual"),
|
|
994
|
-
label: r => r.label, id: true,
|
|
995
|
-
height: Plan.fixed("120px"),
|
|
996
|
-
left: { domain: [0, 60], tickValues: [0, 25, 50] },
|
|
997
|
-
right: { domain: [80, 105], tickValues: [85, 95, 105] },
|
|
998
|
-
swatches: [{ color: "ink.3", label: "out" }, { color: "brand.d", label: "cov · rh" }],
|
|
999
|
-
layers: r => [
|
|
1000
|
-
Chart.Column(r.points, { x: p => p.week, y: p => p.pct }),
|
|
1001
|
-
Plan.layer(Chart.Line(r.extra, { x: p => p.week, y: p => p.pct }), { axis: "right" }),
|
|
1002
|
-
Plan.layer(Chart.Band(r.band, { x: p => p.week, low: p => p.lo, high: p => p.hi }), { axis: "right" }),
|
|
1003
|
-
],
|
|
1004
|
-
}),
|
|
1005
|
-
]),
|
|
1006
|
-
], ArrayType(Plan.Types.Series(ChartRow)));
|
|
1007
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
1008
|
-
return (_jsx(Plan, { axis: axis, data: measures, series: series }));
|
|
1009
|
-
}),
|
|
1010
|
-
inputs: [],
|
|
1011
|
-
});
|
|
1012
|
-
export const planHeatRows = example({
|
|
1013
|
-
keywords: ["Plan", "data", "series", "heat", "Matrix", "cells", "depth", "aggregate", "mean", "children", "nested", "recursive", "RecursiveType", "scale", "warnAt", "weightCells", "segmentCells", "segment", "no-data", "hatch", "section", "match", "gutter", "raw"],
|
|
1014
|
-
description: "Heat rows over one line source — colour-depth cells under a line that averages them, weight bars, and status segments",
|
|
1015
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
1016
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
1017
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
1018
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
1019
|
-
return w1.addWeeks(n.subtract(1n));
|
|
1020
|
-
}));
|
|
1021
|
-
// A LINE holds its machines' load rows (`children`): the hierarchy is
|
|
1022
|
-
// the data's (#822), so the line's row derives its own cells from
|
|
1023
|
-
// theirs.
|
|
1024
|
-
const HeatRow = RecursiveType((self) => StructType({
|
|
1025
|
-
series: StringType, label: StringType,
|
|
1026
|
-
sub: OptionType(StringType),
|
|
1027
|
-
cells: ArrayType(Plan.Types.HeatCell),
|
|
1028
|
-
weights: ArrayType(Plan.Types.WeightCell),
|
|
1029
|
-
segs: ArrayType(Plan.Types.SegmentCell),
|
|
1030
|
-
children: DictType(StringType, self),
|
|
1031
|
-
}));
|
|
1032
|
-
const noChildren = $.const(new Map(), DictType(StringType, HeatRow));
|
|
1033
|
-
const pcts = $.const([46.0, 52.0, 58.0, 61.0, 66.0, 72.0, 78.0, 84.0, 90.0, 96.0, 98.0, 92.0], ArrayType(FloatType));
|
|
1034
|
-
const cells = $.let(East.Array.generate(12n, Plan.Types.HeatCell, (_$, i) => ({
|
|
1035
|
-
at: Plan.at.time(week(i.add(27n))),
|
|
1036
|
-
value: i.equal(4n).ifElse(() => none, () => some(pcts.get(i))), // W31 = no data
|
|
1037
|
-
label: i.equal(4n).ifElse(() => none, () => some(East.Float.printFixed(pcts.get(i), 0n))),
|
|
1038
|
-
})));
|
|
1039
|
-
// Booked-vs-free fractions; the back half is the planned pale tail.
|
|
1040
|
-
const weights = $.let(East.Array.generate(6n, Plan.Types.WeightCell, (_$, i) => ({
|
|
1041
|
-
at: Plan.at.time(week(i.multiply(2n).add(27n))),
|
|
1042
|
-
fraction: i.toFloat().multiply(-0.11).add(0.9),
|
|
1043
|
-
planned: i.greaterEqual(3n),
|
|
1044
|
-
})));
|
|
1045
|
-
const lines = $.const(new Map([
|
|
1046
|
-
// A line with no cells of its own — its row shows the per-bucket
|
|
1047
|
-
// mean of its machines' (the series declares `aggregate`).
|
|
1048
|
-
["line1", { series: "depth", label: "Line 1", sub: none, cells: [], weights: [], segs: [],
|
|
1049
|
-
children: new Map([
|
|
1050
|
-
["m03h", { series: "depth", label: "L1-M03", sub: none, cells, weights: [], segs: [], children: noChildren }],
|
|
1051
|
-
["m04h", { series: "depth", label: "L1-M04", sub: none, cells, weights: [], segs: [], children: noChildren }],
|
|
1052
|
-
]) }],
|
|
1053
|
-
["booked", { series: "booked", label: "Crew A", sub: some("booked h"), cells: [], weights, segs: [], children: noChildren }],
|
|
1054
|
-
// Segment compositions — plain `{ fill, weight, label }` records.
|
|
1055
|
-
["pack", { series: "segments", label: "Pack line", sub: some("capacity"), cells: [], weights: [], children: noChildren,
|
|
1056
|
-
segs: [
|
|
1057
|
-
{ at: Plan.at.time(week(27n)), segments: [
|
|
1058
|
-
{ fill: variant("success", null), weight: 60.0, label: some("60%") },
|
|
1059
|
-
{ fill: variant("warning", null), weight: 25.0, label: some("25%") },
|
|
1060
|
-
{ fill: variant("slack", null), weight: 15.0, label: none },
|
|
1061
|
-
] },
|
|
1062
|
-
{ at: Plan.at.time(week(28n)), segments: [
|
|
1063
|
-
{ fill: variant("success", null), weight: 70.0, label: some("70%") },
|
|
1064
|
-
{ fill: variant("slack", null), weight: 30.0, label: none },
|
|
1065
|
-
] },
|
|
1066
|
-
{ at: Plan.at.time(week(29n)), segments: [
|
|
1067
|
-
{ fill: variant("danger", null), weight: 40.0, label: some("40%") },
|
|
1068
|
-
{ fill: variant("free", null), weight: 60.0, label: none },
|
|
1069
|
-
] },
|
|
1070
|
-
] }],
|
|
1071
|
-
]), DictType(StringType, HeatRow));
|
|
1072
|
-
const series = $.const([
|
|
1073
|
-
// A line's machines nest under it, and its row is their per-bucket
|
|
1074
|
-
// mean — painted on `scale`, the scale a parent's DERIVED cells
|
|
1075
|
-
// take (#824; a mean of rows on 0–100 would inherit it anyway).
|
|
1076
|
-
Plan.series.heat(HeatRow, {
|
|
1077
|
-
key: "depth", title: "Depth",
|
|
1078
|
-
match: r => r.series.equal("depth"),
|
|
1079
|
-
label: r => r.label, id: true,
|
|
1080
|
-
cells: r => Plan.heatCells(r.cells, { min: 0, max: 100, warnAt: 95 }),
|
|
1081
|
-
children: r => r.children, aggregate: "mean",
|
|
1082
|
-
scale: { min: 0, max: 100, warnAt: 95 },
|
|
1083
|
-
}),
|
|
1084
|
-
Plan.series.heat(HeatRow, {
|
|
1085
|
-
key: "booked", title: "Booked",
|
|
1086
|
-
match: r => r.series.equal("booked"),
|
|
1087
|
-
label: r => r.label,
|
|
1088
|
-
sub: r => r.sub,
|
|
1089
|
-
cells: r => Plan.weightCells(r.weights),
|
|
1090
|
-
}),
|
|
1091
|
-
Plan.series.section(HeatRow, { key: "packing", title: "Packing", meta: "1 row" }, [
|
|
1092
|
-
Plan.series.heat(HeatRow, {
|
|
1093
|
-
key: "segments", title: "Segments",
|
|
1094
|
-
match: r => r.series.equal("segments"),
|
|
1095
|
-
label: r => r.label,
|
|
1096
|
-
sub: r => r.sub,
|
|
1097
|
-
cells: r => Plan.segmentCells(r.segs),
|
|
1098
|
-
}),
|
|
1099
|
-
]),
|
|
1100
|
-
], ArrayType(Plan.Types.Series(HeatRow)));
|
|
1101
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
1102
|
-
return (_jsx(Plan, { axis: axis, data: lines, series: series }));
|
|
1103
|
-
}),
|
|
1104
|
-
inputs: [],
|
|
1105
|
-
});
|
|
1106
|
-
export const planTableRows = example({
|
|
1107
|
-
keywords: ["Plan", "data", "series", "table", "cells", "tableCells", "subtotal", "aggregate", "sum", "format", "emphasis", "footer", "children", "nested", "recursive", "RecursiveType", "depth", "em-dash", "neg", "match", "gutter", "tableSeries", "split", "horizontal", "vertical", "multi-value", "multi-cell", "stacked", "two-line", "strong", "muted", "rollup", "mirror", "position", "raw"],
|
|
1108
|
-
description: "Table rows over one order source — subtotals at every level of the data's nesting, footer emphasis, and every split × gutter combination",
|
|
1109
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
1110
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
1111
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
1112
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
1113
|
-
return w1.addWeeks(n.subtract(1n));
|
|
1114
|
-
}));
|
|
1115
|
-
const RawCell = StructType({ at: DateTimeType, value: OptionType(FloatType) });
|
|
1116
|
-
// The RAW order record — actuals and the plan Δ as per-bucket value
|
|
1117
|
-
// arrays; every display decision lives in the series configs. Orders
|
|
1118
|
-
// nest (`children`): a top holds its programs, a program its orders —
|
|
1119
|
-
// the hierarchy is the data's own (#822), to whatever depth it has.
|
|
1120
|
-
const OrderRow = RecursiveType((self) => StructType({
|
|
1121
|
-
series: StringType, name: StringType,
|
|
1122
|
-
sub: OptionType(StringType),
|
|
1123
|
-
act: ArrayType(RawCell),
|
|
1124
|
-
plan: ArrayType(RawCell),
|
|
1125
|
-
children: DictType(StringType, self),
|
|
1126
|
-
}));
|
|
1127
|
-
const noChildren = $.const(new Map(), DictType(StringType, OrderRow));
|
|
1128
|
-
const act = $.let(East.Array.generate(12n, RawCell, (_$, i) => ({
|
|
1129
|
-
at: week(i.add(27n)),
|
|
1130
|
-
value: i.equal(9n).ifElse(() => none, () => some(i.toFloat().multiply(7.0).add(40.0))),
|
|
1131
|
-
})));
|
|
1132
|
-
const deltas = $.let(East.Array.generate(12n, RawCell, (_$, i) => ({
|
|
1133
|
-
at: week(i.add(27n)),
|
|
1134
|
-
value: i.remainder(4n).equal(3n).ifElse(() => none, () => some(i.toFloat().multiply(1.5).subtract(8.0))),
|
|
1135
|
-
})));
|
|
1136
|
-
const outflow = $.let(East.Array.generate(12n, RawCell, (_$, i) => ({
|
|
1137
|
-
at: week(i.add(27n)),
|
|
1138
|
-
value: some(i.toFloat().multiply(-3.0).subtract(12.0)),
|
|
1139
|
-
})));
|
|
1140
|
-
const orders = $.const(new Map([
|
|
1141
|
-
// Two levels of nesting — a top holds its programs, a program its
|
|
1142
|
-
// orders; every level with no values of its own is a subtotal.
|
|
1143
|
-
["despatches", { series: "orders", name: "Despatches", sub: none, act: [], plan: [],
|
|
1144
|
-
children: new Map([
|
|
1145
|
-
["program-a", { series: "orders", name: "Program A", sub: none, act: [], plan: [],
|
|
1146
|
-
children: new Map([
|
|
1147
|
-
["or-1188", { series: "orders", name: "OR-1188", sub: none, act, plan: [], children: noChildren }],
|
|
1148
|
-
["or-1204", { series: "orders", name: "OR-1204", sub: none, act, plan: [], children: noChildren }],
|
|
1149
|
-
]) }],
|
|
1150
|
-
["program-b", { series: "orders", name: "Program B", sub: none, act: [], plan: [],
|
|
1151
|
-
children: new Map([
|
|
1152
|
-
["or-1219", { series: "orders", name: "OR-1219", sub: none, act, plan: [], children: noChildren }],
|
|
1153
|
-
]) }],
|
|
1154
|
-
]) }],
|
|
1155
|
-
["returns", { series: "orders", name: "Returns", sub: none, act: [], plan: [],
|
|
1156
|
-
children: new Map([
|
|
1157
|
-
["program-b", { series: "orders", name: "Program B", sub: none, act: [], plan: [],
|
|
1158
|
-
children: new Map([
|
|
1159
|
-
["rt-0031", { series: "orders", name: "RT-0031", sub: none, act, plan: [], children: noChildren }],
|
|
1160
|
-
]) }],
|
|
1161
|
-
]) }],
|
|
1162
|
-
// Footer emphasis + negative tone + the muted em-dash.
|
|
1163
|
-
["net", { series: "net", name: "Net flow", sub: none, plan: [], children: noChildren,
|
|
1164
|
-
act: [
|
|
1165
|
-
{ at: week(27n), value: some(22.0) }, { at: week(28n), value: some(-26.0) },
|
|
1166
|
-
{ at: week(29n), value: none },
|
|
1167
|
-
] }],
|
|
1168
|
-
// Multi-value series — raw act + plan arrays per row. The SPLIT
|
|
1169
|
-
// (how the positions sit against each other) and the GUTTER (one
|
|
1170
|
-
// line or two) are independent choices, so all four combinations
|
|
1171
|
-
// are here: the pair that reads well depends on the numbers, not
|
|
1172
|
-
// on the split.
|
|
1173
|
-
["actplan", { series: "actplan", name: "Act · Δ plan", sub: some("t/wk"), act, plan: deltas, children: noChildren }],
|
|
1174
|
-
["inout", { series: "inout", name: "In / out", sub: none, act, plan: outflow, children: noChildren }],
|
|
1175
|
-
// Horizontal, on a ONE-line gutter — the pair reads as a single
|
|
1176
|
-
// fact ("booked beside free"), so a sub label would only repeat it.
|
|
1177
|
-
["sidebyside", { series: "sidebyside", name: "Booked · free", sub: none, act, plan: outflow, children: noChildren }],
|
|
1178
|
-
// Vertical, on a TWO-line gutter — the stack needs the unit spelled
|
|
1179
|
-
// out, because the positions are the same measure at two times.
|
|
1180
|
-
["overunder", { series: "overunder", name: "Act / plan", sub: some("t/wk"), act, plan: deltas, children: noChildren }],
|
|
1181
|
-
// NESTED and multi-value: the subtotal parent mirrors its members,
|
|
1182
|
-
// an act subtotal beside a Δ subtotal.
|
|
1183
|
-
["flows", { series: "flow", name: "Flows", sub: none, act: [], plan: [],
|
|
1184
|
-
children: new Map([
|
|
1185
|
-
["fl-1", { series: "flow", name: "FL-2201", sub: none, act, plan: deltas, children: noChildren }],
|
|
1186
|
-
["fl-2", { series: "flow", name: "FL-2202", sub: none, act, plan: deltas, children: noChildren }],
|
|
1187
|
-
]) }],
|
|
1188
|
-
// The same, STACKED: members and their subtotal both put the two
|
|
1189
|
-
// positions on their own lines, so the parent has to grow too.
|
|
1190
|
-
["stacks", { series: "stack", name: "Stacks", sub: none, act: [], plan: [],
|
|
1191
|
-
children: new Map([
|
|
1192
|
-
["st-1", { series: "stack", name: "ST-3301", sub: none, act, plan: outflow, children: noChildren }],
|
|
1193
|
-
["st-2", { series: "stack", name: "ST-3302", sub: none, act, plan: outflow, children: noChildren }],
|
|
1194
|
-
]) }],
|
|
1195
|
-
]), DictType(StringType, OrderRow));
|
|
1196
|
-
const series = $.const([
|
|
1197
|
-
// Each order nests under its program, each program under its top
|
|
1198
|
-
// (`children`, to any depth), and every parent sums its children.
|
|
1199
|
-
Plan.series.table(OrderRow, {
|
|
1200
|
-
key: "orders", title: "Orders",
|
|
1201
|
-
match: r => r.series.equal("orders"),
|
|
1202
|
-
label: r => r.name,
|
|
1203
|
-
cells: r => Plan.tableCells(r.act),
|
|
1204
|
-
children: r => r.children, aggregate: "sum",
|
|
1205
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
1206
|
-
}),
|
|
1207
|
-
Plan.series.table(OrderRow, {
|
|
1208
|
-
key: "net", title: "Net",
|
|
1209
|
-
match: r => r.series.equal("net"),
|
|
1210
|
-
label: r => r.name, emphasis: "footer",
|
|
1211
|
-
cells: r => Plan.tableCells(r.act),
|
|
1212
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
1213
|
-
}),
|
|
1214
|
-
// Per-POSITION style declared ONCE, in the CONFIG — a strong
|
|
1215
|
-
// rolled-up actual beside its muted, always-signed plan Δ.
|
|
1216
|
-
Plan.series.table(OrderRow, {
|
|
1217
|
-
key: "actplan", title: "Actual vs plan",
|
|
1218
|
-
match: r => r.series.equal("actplan"),
|
|
1219
|
-
label: r => r.name, stacked: true, sub: r => r.sub,
|
|
1220
|
-
series: r => [
|
|
1221
|
-
Plan.tableSeries({ strong: true, rollup: true, cells: Plan.tableCells(r.act) }),
|
|
1222
|
-
Plan.tableSeries({
|
|
1223
|
-
tone: "muted",
|
|
1224
|
-
format: Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }),
|
|
1225
|
-
cells: Plan.tableCells(r.plan),
|
|
1226
|
-
}),
|
|
1227
|
-
],
|
|
1228
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
1229
|
-
}),
|
|
1230
|
-
// The VERTICAL split stacks the positions; the row grows.
|
|
1231
|
-
Plan.series.table(OrderRow, {
|
|
1232
|
-
key: "inout", title: "Inout",
|
|
1233
|
-
match: r => r.series.equal("inout"),
|
|
1234
|
-
label: r => r.name, split: "vertical",
|
|
1235
|
-
series: r => [
|
|
1236
|
-
Plan.tableSeries({ cells: Plan.tableCells(r.act) }),
|
|
1237
|
-
Plan.tableSeries({ tone: "muted", cells: Plan.tableCells(r.plan) }),
|
|
1238
|
-
],
|
|
1239
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
1240
|
-
}),
|
|
1241
|
-
// HORIZONTAL on a ONE-line gutter — the other half of the pair
|
|
1242
|
-
// above: the split is a cell-layout choice and the gutter a label
|
|
1243
|
-
// choice, so neither implies the other.
|
|
1244
|
-
Plan.series.table(OrderRow, {
|
|
1245
|
-
key: "sidebyside", title: "Side by side",
|
|
1246
|
-
match: r => r.series.equal("sidebyside"),
|
|
1247
|
-
label: r => r.name, split: "horizontal",
|
|
1248
|
-
series: r => [
|
|
1249
|
-
Plan.tableSeries({ strong: true, cells: Plan.tableCells(r.act) }),
|
|
1250
|
-
Plan.tableSeries({ tone: "muted", cells: Plan.tableCells(r.plan) }),
|
|
1251
|
-
],
|
|
1252
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
1253
|
-
}),
|
|
1254
|
-
// A MULTI-VALUE series under a subtotal parent — every position
|
|
1255
|
-
// rolls up, so the parent shows an act subtotal beside a Δ subtotal
|
|
1256
|
-
// instead of collapsing to one number and looking complete. Flag a
|
|
1257
|
-
// position `rollup: true` to narrow it back to that one.
|
|
1258
|
-
Plan.series.table(OrderRow, {
|
|
1259
|
-
key: "flow", title: "Flow",
|
|
1260
|
-
match: r => r.series.equal("flow"),
|
|
1261
|
-
label: r => r.name,
|
|
1262
|
-
series: r => [
|
|
1263
|
-
Plan.tableSeries({ strong: true, cells: Plan.tableCells(r.act) }),
|
|
1264
|
-
Plan.tableSeries({
|
|
1265
|
-
tone: "muted",
|
|
1266
|
-
format: Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }),
|
|
1267
|
-
cells: Plan.tableCells(r.plan),
|
|
1268
|
-
}),
|
|
1269
|
-
],
|
|
1270
|
-
children: r => r.children, aggregate: "sum",
|
|
1271
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
1272
|
-
}),
|
|
1273
|
-
// NESTED and VERTICAL — the subtotal stacks its positions the way
|
|
1274
|
-
// its members do. The parent's positions carry no values of their
|
|
1275
|
-
// own (they are derived), so its height comes from its members'
|
|
1276
|
-
// count: a parent that read its own empty cells as one line would
|
|
1277
|
-
// render as two.
|
|
1278
|
-
Plan.series.table(OrderRow, {
|
|
1279
|
-
key: "stack", title: "Stack",
|
|
1280
|
-
match: r => r.series.equal("stack"),
|
|
1281
|
-
label: r => r.name, split: "vertical",
|
|
1282
|
-
series: r => [
|
|
1283
|
-
Plan.tableSeries({ strong: true, cells: Plan.tableCells(r.act) }),
|
|
1284
|
-
Plan.tableSeries({ tone: "muted", cells: Plan.tableCells(r.plan) }),
|
|
1285
|
-
],
|
|
1286
|
-
children: r => r.children, aggregate: "sum",
|
|
1287
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
1288
|
-
}),
|
|
1289
|
-
// VERTICAL on a TWO-line gutter — the remaining combination, and
|
|
1290
|
-
// the one that grows the row in BOTH directions at once.
|
|
1291
|
-
Plan.series.table(OrderRow, {
|
|
1292
|
-
key: "overunder", title: "Over / under",
|
|
1293
|
-
match: r => r.series.equal("overunder"),
|
|
1294
|
-
label: r => r.name, split: "vertical", stacked: true, sub: r => r.sub,
|
|
1295
|
-
series: r => [
|
|
1296
|
-
Plan.tableSeries({ strong: true, rollup: true, cells: Plan.tableCells(r.act) }),
|
|
1297
|
-
Plan.tableSeries({
|
|
1298
|
-
tone: "muted",
|
|
1299
|
-
format: Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }),
|
|
1300
|
-
cells: Plan.tableCells(r.plan),
|
|
1301
|
-
}),
|
|
1302
|
-
],
|
|
1303
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
1304
|
-
}),
|
|
1305
|
-
], ArrayType(Plan.Types.Series(OrderRow)));
|
|
1306
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
1307
|
-
return (_jsx(Plan, { axis: axis, data: orders, series: series }));
|
|
1308
|
-
}),
|
|
1309
|
-
inputs: [],
|
|
1310
|
-
});
|
|
1311
|
-
// ============================================================================
|
|
1312
|
-
// planFold — a coarser resolution FOLDS each bucket's values (#824)
|
|
1313
|
-
// ============================================================================
|
|
1314
|
-
/**
|
|
1315
|
-
* Temporal fold (#824). The data is weekly; the canvas shows it at whatever
|
|
1316
|
-
* resolution the axis states, and at MONTH every row shows ONE value per
|
|
1317
|
-
* month — the fold of its weeks there — where it used to stack four or five
|
|
1318
|
-
* on top of each other. Each cell builder and chart layer declares its fold,
|
|
1319
|
-
* defaulting to what its values mean: a heat level and a line fold by `mean`,
|
|
1320
|
-
* a table numeral and a column by `sum`, a weight fraction by `mean`. A row
|
|
1321
|
-
* overrides it where the meaning differs — peak load by `max`, closing stock
|
|
1322
|
-
* by `last`. The switch is the resolution; nothing in the data changes, and a
|
|
1323
|
-
* row whose weeks were never folded (WEEK) shows them as they are.
|
|
1324
|
-
*/
|
|
1325
|
-
export const planFold = example({
|
|
1326
|
-
keywords: [
|
|
1327
|
-
"Plan", "fold", "temporal fold", "resolution", "week", "month", "rebucket", "bucket",
|
|
1328
|
-
"sum", "mean", "max", "last", "count", "default", "override", "heatCells", "weightCells",
|
|
1329
|
-
"tableCells", "tableSeries", "layer", "Plan.layer", "column", "line", "format",
|
|
1330
|
-
"Reactive", "State", "SegmentGroup", "#824",
|
|
1331
|
-
],
|
|
1332
|
-
description: "Temporal fold — weekly data at MONTH resolution shows one folded cell per month per row: heat and lines by mean, tables and columns by sum, with per-row overrides (peak load by max, closing stock by last)",
|
|
1333
|
-
fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
|
|
1334
|
-
// Monday of ISO week n, 2026 — twelve weeks, W27–W38.
|
|
1335
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
1336
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
1337
|
-
return w1.addWeeks(n.subtract(1n));
|
|
1338
|
-
}));
|
|
1339
|
-
const Weekly = StructType({ at: DateTimeType, value: OptionType(FloatType) });
|
|
1340
|
-
const MeasureRow = StructType({ week: DateTimeType, v: FloatType });
|
|
1341
|
-
// ONE flat weekly source; `series` picks the row's series.
|
|
1342
|
-
const OpsRow = StructType({
|
|
1343
|
-
series: StringType, label: StringType,
|
|
1344
|
-
heat: ArrayType(Plan.Types.HeatCell),
|
|
1345
|
-
weights: ArrayType(Plan.Types.WeightCell),
|
|
1346
|
-
weekly: ArrayType(Weekly),
|
|
1347
|
-
points: ArrayType(MeasureRow),
|
|
1348
|
-
});
|
|
1349
|
-
const noHeat = $.const([], ArrayType(Plan.Types.HeatCell));
|
|
1350
|
-
const noWeights = $.const([], ArrayType(Plan.Types.WeightCell));
|
|
1351
|
-
const noWeekly = $.const([], ArrayType(Weekly));
|
|
1352
|
-
const noPoints = $.const([], ArrayType(MeasureRow));
|
|
1353
|
-
// Twelve weeks of each measure, derived — never hand-written.
|
|
1354
|
-
const load = $.let(East.Array.generate(12n, Plan.Types.HeatCell, (_$, i) => ({
|
|
1355
|
-
at: Plan.at.time(week(i.add(27n))),
|
|
1356
|
-
value: some(i.multiply(17n).remainder(50n).toFloat().add(45.0)),
|
|
1357
|
-
label: none,
|
|
1358
|
-
})));
|
|
1359
|
-
const booked = $.let(East.Array.generate(12n, Plan.Types.WeightCell, (_$, i) => ({
|
|
1360
|
-
at: Plan.at.time(week(i.add(27n))),
|
|
1361
|
-
fraction: i.multiply(7n).remainder(10n).toFloat().multiply(0.06).add(0.4),
|
|
1362
|
-
planned: i.greaterEqual(5n),
|
|
1363
|
-
})));
|
|
1364
|
-
const tonnes = $.let(East.Array.generate(12n, Weekly, (_$, i) => ({
|
|
1365
|
-
at: week(i.add(27n)), value: some(i.multiply(29n).remainder(60n).toFloat().add(80.0)),
|
|
1366
|
-
})));
|
|
1367
|
-
const stock = $.let(East.Array.generate(12n, Weekly, (_$, i) => ({
|
|
1368
|
-
at: week(i.add(27n)), value: some(i.toFloat().multiply(-22.0).add(420.0)),
|
|
1369
|
-
})));
|
|
1370
|
-
const output = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({
|
|
1371
|
-
week: week(i.add(27n)), v: i.multiply(23n).remainder(40n).toFloat().add(60.0),
|
|
1372
|
-
})));
|
|
1373
|
-
const coverage = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({
|
|
1374
|
-
week: week(i.add(27n)), v: i.multiply(13n).remainder(9n).toFloat().add(88.0),
|
|
1375
|
-
})));
|
|
1376
|
-
const base = { heat: noHeat, weights: noWeights, weekly: noWeekly, points: noPoints };
|
|
1377
|
-
const ops = $.const(new Map([
|
|
1378
|
-
["load", { ...base, series: "load", label: "Line load %", heat: load }],
|
|
1379
|
-
["peak", { ...base, series: "peak", label: "Peak load %", heat: load }],
|
|
1380
|
-
["book", { ...base, series: "book", label: "Booked", weights: booked }],
|
|
1381
|
-
["desp", { ...base, series: "desp", label: "Despatch t", weekly: tonnes }],
|
|
1382
|
-
["stock", { ...base, series: "stock", label: "Closing stock t", weekly: stock }],
|
|
1383
|
-
["out", { ...base, series: "out", label: "Output · t", points: output }],
|
|
1384
|
-
["cov", { ...base, series: "cov", label: "Coverage %", points: coverage }],
|
|
1385
|
-
]), DictType(StringType, OpsRow));
|
|
1386
|
-
const whole = $.const(Format.Number({ maximumFractionDigits: 0n }));
|
|
1387
|
-
const series = $.const([
|
|
1388
|
-
// A level — a month shows its weeks' MEAN (the default). The
|
|
1389
|
-
// declared format prints the folded values.
|
|
1390
|
-
Plan.series.heat(OpsRow, {
|
|
1391
|
-
key: "load", title: "Line load",
|
|
1392
|
-
match: r => r.series.equal("load"), label: r => r.label,
|
|
1393
|
-
cells: r => Plan.heatCells(r.heat, { min: 0, max: 100, format: whole }),
|
|
1394
|
-
}),
|
|
1395
|
-
// The same weeks, folded by their MAX — the peak a month hit.
|
|
1396
|
-
Plan.series.heat(OpsRow, {
|
|
1397
|
-
key: "peak", title: "Peak load",
|
|
1398
|
-
match: r => r.series.equal("peak"), label: r => r.label,
|
|
1399
|
-
cells: r => Plan.heatCells(r.heat, { min: 0, max: 100, fold: "max", format: whole }),
|
|
1400
|
-
}),
|
|
1401
|
-
// A fraction of each bucket booked — a month's is its weeks' mean.
|
|
1402
|
-
Plan.series.heat(OpsRow, {
|
|
1403
|
-
key: "book", title: "Booked",
|
|
1404
|
-
match: r => r.series.equal("book"), label: r => r.label,
|
|
1405
|
-
cells: r => Plan.weightCells(r.weights),
|
|
1406
|
-
}),
|
|
1407
|
-
// An amount — a month shows its weeks' SUM (the default).
|
|
1408
|
-
Plan.series.table(OpsRow, {
|
|
1409
|
-
key: "desp", title: "Despatch",
|
|
1410
|
-
match: r => r.series.equal("desp"), label: r => r.label,
|
|
1411
|
-
cells: r => Plan.tableCells(r.weekly), format: whole,
|
|
1412
|
-
}),
|
|
1413
|
-
// A balance — a month shows where it CLOSED, its last week.
|
|
1414
|
-
Plan.series.table(OpsRow, {
|
|
1415
|
-
key: "stock", title: "Closing stock",
|
|
1416
|
-
match: r => r.series.equal("stock"), label: r => r.label,
|
|
1417
|
-
cells: r => Plan.tableCells(r.weekly), fold: "last", format: whole,
|
|
1418
|
-
}),
|
|
1419
|
-
// Columns sum; a line averages.
|
|
1420
|
-
Plan.series.chart(OpsRow, {
|
|
1421
|
-
key: "out", title: "Output",
|
|
1422
|
-
match: r => r.series.equal("out"), label: r => r.label, id: true,
|
|
1423
|
-
layers: r => [Chart.Column(r.points, { x: p => p.week, y: p => p.v })],
|
|
1424
|
-
}),
|
|
1425
|
-
Plan.series.chart(OpsRow, {
|
|
1426
|
-
key: "cov", title: "Coverage",
|
|
1427
|
-
match: r => r.series.equal("cov"), label: r => r.label, id: true,
|
|
1428
|
-
layers: r => [Plan.layer(Chart.Line(r.points, { x: p => p.week, y: p => p.v }), { fold: "mean" })],
|
|
1429
|
-
}),
|
|
1430
|
-
], ArrayType(Plan.Types.Series(OpsRow)));
|
|
1431
|
-
// The resolution is the switch — two whole axis values, picked by key.
|
|
1432
|
-
const axes = $.const([
|
|
1433
|
-
{ key: "month", axis: Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "month", now: week(31n) }) },
|
|
1434
|
-
{ key: "week", axis: Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }) },
|
|
1435
|
-
], ArrayType(StructType({ key: StringType, axis: Plan.Types.Axis })));
|
|
1436
|
-
const resBind = $.let(State.bind([StringType], "plan_fold_resolution", "month"));
|
|
1437
|
-
const resKey = $.let(resBind.read());
|
|
1438
|
-
const onRes = $.const(East.function([StringType], NullType, ($, next) => { $(resBind.write(next)); }));
|
|
1439
|
-
const sel = $.let(axes.filter((_$, a) => a.key.equal(resKey)).get(0n, _$ => axes.get(0n)));
|
|
1440
|
-
return (_jsxs(VStack, { gap: "2", align: "stretch", children: [_jsx(SegmentGroup, { value: resKey, onChange: onRes, size: "sm", items: [SegmentGroup.Item("month", _jsx(Text, { children: "MONTH" })), SegmentGroup.Item("week", _jsx(Text, { children: "WEEK" }))] }), _jsx(Plan, { axis: sel.axis, data: ops, series: series })] }));
|
|
1441
|
-
} }))),
|
|
1442
|
-
inputs: [],
|
|
1443
|
-
});
|
|
1444
|
-
export const planCardRows = example({
|
|
1445
|
-
keywords: ["Plan", "data", "series", "cards", "Roster", "chip", "lifecycle", "confirmed", "recommended", "removed", "estimated", "icon", "popover", "stacked", "format", "axis", "section", "match", "gutter", "raw"],
|
|
1446
|
-
description: "Cards rows over one crew source — chip labels derived from hours × lifecycle, plus stored chip records",
|
|
1447
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
1448
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
1449
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
1450
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
1451
|
-
return w1.addWeeks(n.subtract(1n));
|
|
1452
|
-
}));
|
|
1453
|
-
const ShiftRow = StructType({
|
|
1454
|
-
key: StringType, from: DateTimeType, to: DateTimeType,
|
|
1455
|
-
hours: FloatType, state: EventStateType,
|
|
1456
|
-
});
|
|
1457
|
-
const CrewRow = StructType({
|
|
1458
|
-
series: StringType, name: StringType,
|
|
1459
|
-
sub: OptionType(StringType), value: OptionType(StringType),
|
|
1460
|
-
shifts: ArrayType(ShiftRow),
|
|
1461
|
-
chips: ArrayType(Plan.Types.Chip),
|
|
1462
|
-
});
|
|
1463
|
-
const crews = $.const(new Map([
|
|
1464
|
-
// RAW shifts — hours + lifecycle; chip labels derive client-side.
|
|
1465
|
-
["crewA", { series: "main", name: "Crew A", sub: some("152h → 168h"), value: none, chips: [], shifts: [
|
|
1466
|
-
{ key: "s1", from: week(27n), to: week(29n), hours: 80.0, state: variant("confirmed", null) },
|
|
1467
|
-
{ key: "s2", from: week(29n), to: week(31n), hours: 56.0, state: variant("proposed", variant("removed", null)) },
|
|
1468
|
-
{ key: "s3", from: week(31n), to: week(33n), hours: 64.0, state: variant("proposed", variant("recommended", null)) },
|
|
1469
|
-
{ key: "s4", from: week(34n), to: week(35n), hours: 48.0, state: variant("estimated", null) },
|
|
1470
|
-
] }],
|
|
1471
|
-
// STORED vocabulary — plain chip records (the §3.2 element
|
|
1472
|
-
// shapes), here carrying the shift-type icon.
|
|
1473
|
-
["crewB", { series: "pool", name: "Crew B", sub: none, value: some("128h"), shifts: [], chips: [
|
|
1474
|
-
{ key: "b1", from: Plan.at.time(week(28n)), to: Plan.at.time(week(31n)), label: "96h", state: variant("confirmed", null),
|
|
1475
|
-
icon: some({ prefix: "fas", name: "user-group", label: none, style: none }) },
|
|
1476
|
-
{ key: "b2", from: Plan.at.time(week(33n)), to: Plan.at.time(week(36n)), label: "+32h", state: variant("proposed", variant("recommended", null)), icon: none },
|
|
1477
|
-
] }],
|
|
1478
|
-
]), DictType(StringType, CrewRow));
|
|
1479
|
-
const series = $.const([
|
|
1480
|
-
Plan.series.cards(CrewRow, {
|
|
1481
|
-
key: "main", title: "Main",
|
|
1482
|
-
match: r => r.series.equal("main"),
|
|
1483
|
-
label: r => r.name, stacked: true,
|
|
1484
|
-
sub: r => r.sub,
|
|
1485
|
-
chips: r => r.shifts.map(($, s) => {
|
|
1486
|
-
const hrs = $.let(East.Float.printFixed(s.hours, 0n), StringType);
|
|
1487
|
-
// The `+` means ADDED hours, so it rides the proposal's
|
|
1488
|
-
// flavour, not the mere fact of being a proposal — a
|
|
1489
|
-
// `removed` shift is a proposal too, and prefixing it `+`
|
|
1490
|
-
// would read as the opposite of what it does.
|
|
1491
|
-
const label = $.let(s.state.match({
|
|
1492
|
-
proposed: (_$, p) => p.hasTag("removed").ifElse(() => East.str `${hrs}h`, () => East.str `+${hrs}h`),
|
|
1493
|
-
}, _$ => East.str `${hrs}h`), StringType);
|
|
1494
|
-
return Plan.chip({ key: s.key, from: s.from, to: s.to, label, state: s.state });
|
|
1495
|
-
}),
|
|
1496
|
-
}),
|
|
1497
|
-
Plan.series.section(CrewRow, { key: "relief", title: "Relief pool", meta: "1 row" }, [
|
|
1498
|
-
Plan.series.cards(CrewRow, {
|
|
1499
|
-
key: "pool", title: "Pool",
|
|
1500
|
-
match: r => r.series.equal("pool"),
|
|
1501
|
-
label: r => r.name,
|
|
1502
|
-
value: r => r.value,
|
|
1503
|
-
chips: r => r.chips,
|
|
1504
|
-
}),
|
|
1505
|
-
]),
|
|
1506
|
-
], ArrayType(Plan.Types.Series(CrewRow)));
|
|
1507
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n), format: "D MMM" }));
|
|
1508
|
-
// The generalized popover resolver — chips ride the chip arm.
|
|
1509
|
-
const popover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
|
|
1510
|
-
const noBody = $.const(none, OptionType(UIComponentType));
|
|
1511
|
-
return ref.match({
|
|
1512
|
-
chip: (_$, ev) => ev.chip.equal("s3").ifElse(() => some(_jsx(Text, { children: "Overtime proposal." })), () => noBody),
|
|
1513
|
-
}, _$ => noBody);
|
|
1514
|
-
}));
|
|
1515
|
-
return (_jsx(Plan, { popover: popover, axis: axis, data: crews, series: series }));
|
|
1516
|
-
}),
|
|
1517
|
-
inputs: [],
|
|
1518
|
-
});
|
|
1519
|
-
export const planEventRows = example({
|
|
1520
|
-
keywords: ["Plan", "data", "series", "events", "mark", "milestone", "decision", "exception", "markKind", "applied", "icon", "label", "popover", "section", "match", "stacked", "gutter", "raw"],
|
|
1521
|
-
description: "Event rows over one stream source — milestone dots, decision diamonds, an exception, and a custom glyph",
|
|
1522
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
1523
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
1524
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
1525
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
1526
|
-
return w1.addWeeks(n.subtract(1n));
|
|
1527
|
-
}));
|
|
1528
|
-
const StreamRow = StructType({
|
|
1529
|
-
series: StringType, name: StringType,
|
|
1530
|
-
sub: OptionType(StringType), value: OptionType(StringType),
|
|
1531
|
-
marks: ArrayType(Plan.Types.EventMark),
|
|
1532
|
-
});
|
|
1533
|
-
const streams = $.const(new Map([
|
|
1534
|
-
["ms", { series: "main", name: "Milestones", sub: none, value: some("5"), marks: [
|
|
1535
|
-
{ key: "kick", at: Plan.at.time(week(28n)), kind: variant("milestone", null), icon: none, label: some("KICKOFF") },
|
|
1536
|
-
{ key: "d1", at: Plan.at.time(week(31n)), kind: variant("decision", { applied: true }), icon: none, label: none },
|
|
1537
|
-
{ key: "rel", at: Plan.at.time(week(33n)), kind: variant("milestone", null),
|
|
1538
|
-
icon: some({ prefix: "fas", name: "rocket", label: none, style: none }), label: some("REL 2.4") },
|
|
1539
|
-
{ key: "audit", at: Plan.at.time(week(35n)), kind: variant("exception", null), icon: none, label: some("AUDIT") },
|
|
1540
|
-
{ key: "d2", at: Plan.at.time(week(37n)), kind: variant("decision", { applied: false }), icon: none, label: some("×3") },
|
|
1541
|
-
] }],
|
|
1542
|
-
["release", { series: "programs", name: "Releases", sub: some("6-wk cadence"), value: none, marks: [
|
|
1543
|
-
{ key: "r1", at: Plan.at.time(week(29n)), kind: variant("milestone", null), icon: none, label: some("2.3") },
|
|
1544
|
-
{ key: "r2", at: Plan.at.time(week(36n)), kind: variant("milestone", null), icon: none, label: some("2.4") },
|
|
1545
|
-
] }],
|
|
1546
|
-
]), DictType(StringType, StreamRow));
|
|
1547
|
-
const series = $.const([
|
|
1548
|
-
Plan.series.events(StreamRow, {
|
|
1549
|
-
key: "milestones", title: "Milestones",
|
|
1550
|
-
match: r => r.series.equal("main"),
|
|
1551
|
-
label: r => r.name, id: true,
|
|
1552
|
-
value: r => r.value,
|
|
1553
|
-
marks: r => r.marks,
|
|
1554
|
-
}),
|
|
1555
|
-
Plan.series.section(StreamRow, { key: "programs", title: "Programs", meta: "1 row" }, [
|
|
1556
|
-
Plan.series.events(StreamRow, {
|
|
1557
|
-
key: "releases", title: "Releases",
|
|
1558
|
-
match: r => r.series.equal("programs"),
|
|
1559
|
-
label: r => r.name, id: true, stacked: true,
|
|
1560
|
-
sub: r => r.sub,
|
|
1561
|
-
marks: r => r.marks,
|
|
1562
|
-
}),
|
|
1563
|
-
]),
|
|
1564
|
-
], ArrayType(Plan.Types.Series(StreamRow)));
|
|
1565
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
1566
|
-
// The generalized popover resolver — event marks ride the mark arm.
|
|
1567
|
-
const popover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
|
|
1568
|
-
const noBody = $.const(none, OptionType(UIComponentType));
|
|
1569
|
-
return ref.match({
|
|
1570
|
-
mark: (_$, ev) => ev.mark.equal("rel").ifElse(() => some(_jsx(Text, { children: "Go/no-go review." })), () => noBody),
|
|
1571
|
-
}, _$ => noBody);
|
|
1572
|
-
}));
|
|
1573
|
-
return (_jsx(Plan, { popover: popover, axis: axis, data: streams, series: series }));
|
|
1574
|
-
}),
|
|
1575
|
-
inputs: [],
|
|
1576
|
-
});
|
|
1577
|
-
export const planGroupedRows = example({
|
|
1578
|
-
keywords: ["Plan", "data", "series", "group", "groups", "strip", "summary", "summaryAggregate", "collapsed", "groupToDicts", "grouping", "data step", "children", "Plan.children", "step down", "member count", "heterogeneous", "match", "nesting", "raw"],
|
|
1579
|
-
description: "Group strips over grouped data — one `groupToDicts` makes each line an entry holding its rows, and each line's strip nests them, collapsed strips resting as their mean",
|
|
1580
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
1581
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
1582
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
1583
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
1584
|
-
return w1.addWeeks(n.subtract(1n));
|
|
1585
|
-
}));
|
|
1586
|
-
const JobRow = StructType({
|
|
1587
|
-
key: StringType, batch: StringType,
|
|
1588
|
-
start: DateTimeType, end: DateTimeType, state: EventStateType,
|
|
1589
|
-
});
|
|
1590
|
-
// The RAW rows, flat — each names its line, and carries jobs, load
|
|
1591
|
-
// cells, or both.
|
|
1592
|
-
const LineRow = StructType({
|
|
1593
|
-
line: StringType, label: StringType,
|
|
1594
|
-
jobs: ArrayType(JobRow),
|
|
1595
|
-
cells: ArrayType(Plan.Types.HeatCell),
|
|
1596
|
-
});
|
|
1597
|
-
const pcts = $.const([46.0, 52.0, 58.0, 61.0, 66.0, 72.0, 78.0, 84.0, 90.0, 96.0, 98.0, 92.0], ArrayType(FloatType));
|
|
1598
|
-
const cells = $.let(East.Array.generate(12n, Plan.Types.HeatCell, (_$, i) => ({
|
|
1599
|
-
at: Plan.at.time(week(i.add(27n))),
|
|
1600
|
-
value: some(pcts.get(i)),
|
|
1601
|
-
label: some(East.Float.printFixed(pcts.get(i), 0n)),
|
|
1602
|
-
})));
|
|
1603
|
-
const rows = $.const(new Map([
|
|
1604
|
-
// Line 1 — mixed kinds: a machine's jobs and its load.
|
|
1605
|
-
["m03", { line: "Line 1", label: "L1-M03",
|
|
1606
|
-
jobs: [{ key: "r", batch: "B-214", start: week(28n), end: week(31n), state: variant("in-progress", null) }],
|
|
1607
|
-
cells: [] }],
|
|
1608
|
-
["m03h", { line: "Line 1", label: "L1-M03 load", jobs: [], cells }],
|
|
1609
|
-
["l2", { line: "Line 2", label: "L2 load", jobs: [], cells }],
|
|
1610
|
-
["m21", { line: "Line 3", label: "L3-M21", jobs: [], cells }],
|
|
1611
|
-
["m22", { line: "Line 3", label: "L3-M22", jobs: [], cells }],
|
|
1612
|
-
["m31", { line: "Line 4", label: "L4-M31", jobs: [], cells }],
|
|
1613
|
-
]), DictType(StringType, LineRow));
|
|
1614
|
-
// Grouping is a DATA step (#822): one `groupToDicts` makes each line an
|
|
1615
|
-
// entry holding its rows, keyed as the source keys them. A strip nests
|
|
1616
|
-
// exactly what its entry holds, so it reads the same inline or paged.
|
|
1617
|
-
const lines = $.let(rows.groupToDicts(($, r) => r.line, ($, _r, k) => k));
|
|
1618
|
-
const LineGroup = DictType(StringType, LineRow);
|
|
1619
|
-
const series = $.const([
|
|
1620
|
-
// One strip PER LINE, its members the line's rows — stepped down
|
|
1621
|
-
// into (`Plan.children`) and laid out like a top-level list: the
|
|
1622
|
-
// jobs block, then the load block. Line 1 rests open; the others
|
|
1623
|
-
// rest as their DECLARED mean strip, wearing their member count.
|
|
1624
|
-
Plan.series.group(LineGroup, {
|
|
1625
|
-
key: "lines", title: "Lines",
|
|
1626
|
-
label: (_g, line) => line,
|
|
1627
|
-
collapsed: (_g, line) => line.equal("Line 1").not(),
|
|
1628
|
-
summaryAggregate: "mean",
|
|
1629
|
-
children: Plan.children((g) => g, [
|
|
1630
|
-
Plan.series.span(LineRow, {
|
|
1631
|
-
key: "line-jobs", title: "Jobs",
|
|
1632
|
-
match: r => r.jobs.size().greater(0n),
|
|
1633
|
-
label: r => r.label, id: true,
|
|
1634
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
1635
|
-
key: j.key, start: j.start, end: j.end,
|
|
1636
|
-
label: East.str `RUN · ${j.batch}`, state: j.state,
|
|
1637
|
-
})),
|
|
1638
|
-
}),
|
|
1639
|
-
Plan.series.heat(LineRow, {
|
|
1640
|
-
key: "line-load", title: "Load",
|
|
1641
|
-
match: r => r.cells.size().greater(0n),
|
|
1642
|
-
label: r => r.label,
|
|
1643
|
-
cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
|
|
1644
|
-
}),
|
|
1645
|
-
]),
|
|
1646
|
-
}),
|
|
1647
|
-
], ArrayType(Plan.Types.Series(LineGroup)));
|
|
1648
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
1649
|
-
return (_jsx(Plan, { axis: axis, data: lines, series: series }));
|
|
1650
|
-
}),
|
|
1651
|
-
inputs: [],
|
|
1652
|
-
});
|
|
1653
|
-
// ============================================================================
|
|
1654
|
-
// planSeriesData — the minimal data + series introduction
|
|
1655
|
-
// ============================================================================
|
|
1656
|
-
export const planSeriesData = example({
|
|
1657
|
-
keywords: ["Plan", "data", "series", "match", "variant", "span", "cards", "group", "rows", "groupToDicts", "grouping", "data step", "children", "Plan.children", "step down", "nesting", "rollup", "Series", "data-driven", "accessor", "raw", "one source", "layout"],
|
|
1658
|
-
description: "The data + series canvas, minimally — one raw source grouped into blocks in one data step, and one `Plan.series.*` entry per block, the list the layout",
|
|
1659
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
1660
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
1661
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
1662
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
1663
|
-
return w1.addWeeks(n.subtract(1n));
|
|
1664
|
-
}));
|
|
1665
|
-
// The RAW domain shape — series discriminated by a variant field
|
|
1666
|
-
// (the natural ops-dataset form; the same rows page from a dataset).
|
|
1667
|
-
const JobRow = StructType({
|
|
1668
|
-
batch: StringType, start: DateTimeType, end: DateTimeType,
|
|
1669
|
-
tonnes: FloatType, state: EventStateType,
|
|
1670
|
-
});
|
|
1671
|
-
const ShiftRow = StructType({
|
|
1672
|
-
key: StringType, from: DateTimeType, to: DateTimeType,
|
|
1673
|
-
hours: FloatType, state: EventStateType,
|
|
1674
|
-
});
|
|
1675
|
-
const OpsRow = StructType({
|
|
1676
|
-
line: StringType,
|
|
1677
|
-
kind: VariantType({
|
|
1678
|
-
machine: StructType({ jobs: ArrayType(JobRow) }),
|
|
1679
|
-
crew: StructType({ shifts: ArrayType(ShiftRow) }),
|
|
1680
|
-
}),
|
|
1681
|
-
});
|
|
1682
|
-
const ops = $.const(new Map([
|
|
1683
|
-
["L1-M03", { line: "Line 1", kind: variant("machine", { jobs: [
|
|
1684
|
-
{ batch: "B-214", start: week(28n), end: week(31n), tonnes: 96.0, state: variant("in-progress", null) },
|
|
1685
|
-
{ batch: "B-221", start: week(32n), end: week(35n), tonnes: 88.0, state: variant("proposed", variant("recommended", null)) },
|
|
1686
|
-
] }) }],
|
|
1687
|
-
["L1-M04", { line: "Line 1", kind: variant("machine", { jobs: [
|
|
1688
|
-
{ batch: "B-208", start: week(27n), end: week(30n), tonnes: 112.0, state: variant("actual", null) },
|
|
1689
|
-
] }) }],
|
|
1690
|
-
["L2-M11", { line: "Line 2", kind: variant("machine", { jobs: [
|
|
1691
|
-
{ batch: "B-241", start: week(29n), end: week(33n), tonnes: 92.0, state: variant("confirmed", null) },
|
|
1692
|
-
] }) }],
|
|
1693
|
-
["crewA", { line: "Line 1", kind: variant("crew", { shifts: [
|
|
1694
|
-
{ key: "s1", from: week(27n), to: week(29n), hours: 80.0, state: variant("confirmed", null) },
|
|
1695
|
-
{ key: "s2", from: week(31n), to: week(33n), hours: 64.0, state: variant("proposed", variant("recommended", null)) },
|
|
1696
|
-
] }) }],
|
|
1697
|
-
]), DictType(StringType, OpsRow));
|
|
1698
|
-
// Hierarchy is the DATA's (#822): one `groupToDicts` groups the rows
|
|
1699
|
-
// into the canvas's blocks — each machine under its line, the crews
|
|
1700
|
-
// under one "Crews" block. An entry of the result holds its rows.
|
|
1701
|
-
const blocks = $.let(ops.groupToDicts(($, r) => r.kind.hasTag("crew").ifElse(() => "Crews", () => r.line), ($, _r, k) => k));
|
|
1702
|
-
const Block = DictType(StringType, OpsRow);
|
|
1703
|
-
// The series — real East values bound in the body, typed by the
|
|
1704
|
-
// constructor. The list IS the layout: one block per series, top to
|
|
1705
|
-
// bottom. The accessors are where raw fields become canvas vocabulary:
|
|
1706
|
-
// labels, quantity displays and chip text all derive CLIENT-SIDE,
|
|
1707
|
-
// inside each series' `derive`.
|
|
1708
|
-
const series = $.const([
|
|
1709
|
-
// One row per line, its machines stepped down into
|
|
1710
|
-
// (`Plan.children`) and their runs rolled up into its bands —
|
|
1711
|
-
// which sum the runs' quantities, unit by unit.
|
|
1712
|
-
Plan.series.span(Block, {
|
|
1713
|
-
key: "lines", title: "Lines",
|
|
1714
|
-
match: (_b, name) => name.equal("Crews").not(),
|
|
1715
|
-
label: (_b, name) => name,
|
|
1716
|
-
runs: _b => [],
|
|
1717
|
-
rollup: "union",
|
|
1718
|
-
children: Plan.children((b) => b, [
|
|
1719
|
-
Plan.series.span(OpsRow, {
|
|
1720
|
-
key: "machines", title: "Machines",
|
|
1721
|
-
match: r => r.kind.hasTag("machine"),
|
|
1722
|
-
label: (_r, k) => k, id: true,
|
|
1723
|
-
runs: r => r.kind.unwrap("machine").jobs.map((_$, j) => Plan.run({
|
|
1724
|
-
key: j.batch, start: j.start, end: j.end,
|
|
1725
|
-
label: East.str `RUN · ${j.batch}`,
|
|
1726
|
-
// A quantity is one value: the bar prints `96 t`,
|
|
1727
|
-
// and the line's band sums the tonnes.
|
|
1728
|
-
quantity: Plan.quantity(j.tonnes, { unit: "t", format: Format.Number({ maximumFractionDigits: 0n }) }),
|
|
1729
|
-
state: j.state,
|
|
1730
|
-
})),
|
|
1731
|
-
}),
|
|
1732
|
-
]),
|
|
1733
|
-
}),
|
|
1734
|
-
// One strip per matching block — here the one "Crews" block,
|
|
1735
|
-
// wearing its member count.
|
|
1736
|
-
Plan.series.group(Block, {
|
|
1737
|
-
key: "crews", title: "Crews",
|
|
1738
|
-
match: (_b, name) => name.equal("Crews"),
|
|
1739
|
-
label: (_b, name) => name,
|
|
1740
|
-
children: Plan.children((b) => b, [
|
|
1741
|
-
Plan.series.cards(OpsRow, {
|
|
1742
|
-
key: "crew-shifts", title: "Crew shifts",
|
|
1743
|
-
match: r => r.kind.hasTag("crew"),
|
|
1744
|
-
label: (_r, k) => k,
|
|
1745
|
-
chips: r => r.kind.unwrap("crew").shifts.map(($, s) => {
|
|
1746
|
-
const hrs = $.let(East.Float.printFixed(s.hours, 0n), StringType);
|
|
1747
|
-
// `+` marks ADDED hours — a removed proposal keeps the
|
|
1748
|
-
// plain figure (see planCardRows for the full ladder).
|
|
1749
|
-
const label = $.let(s.state.match({
|
|
1750
|
-
proposed: (_$, p) => p.hasTag("removed").ifElse(() => East.str `${hrs}h`, () => East.str `+${hrs}h`),
|
|
1751
|
-
}, _$ => East.str `${hrs}h`), StringType);
|
|
1752
|
-
return Plan.chip({ key: s.key, from: s.from, to: s.to, label, state: s.state });
|
|
1753
|
-
}),
|
|
1754
|
-
}),
|
|
1755
|
-
]),
|
|
1756
|
-
}),
|
|
1757
|
-
Plan.series.rows(Block, { key: "chrome", title: "Milestones", subtitle: "one-off chrome" }, [Plan.events({ key: "ms", label: "Milestones", id: true, marks: [
|
|
1758
|
-
Plan.mark({ key: "kick", at: week(28n), kind: "milestone", label: "KICKOFF" }),
|
|
1759
|
-
Plan.mark({ key: "rel", at: week(33n), kind: "milestone", label: "REL 2.4" }),
|
|
1760
|
-
] })]),
|
|
1761
|
-
], ArrayType(Plan.Types.Series(Block)));
|
|
1762
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
1763
|
-
return (_jsx(Plan, { axis: axis, data: blocks, series: series }));
|
|
1764
|
-
}),
|
|
1765
|
-
inputs: [],
|
|
1766
|
-
});
|
|
1767
|
-
// ============================================================================
|
|
1768
|
-
// planLiteralRows — the kind factories: rows no dataset holds
|
|
1769
|
-
// ============================================================================
|
|
1770
|
-
export const planLiteralRows = example({
|
|
1771
|
-
keywords: [
|
|
1772
|
-
"Plan", "span", "Plan.span", "literal", "rows", "series.rows", "chrome", "one-off",
|
|
1773
|
-
"kind factory", "subtree", "nested", "parent", "rollup", "union", "bands", "run", "layout",
|
|
1774
|
-
],
|
|
1775
|
-
description: "Literal rows — `Plan.span` builds a one-off subtree (a shutdown parent rolling up two trades' runs) that `Plan.series.rows` places beside the data-driven series",
|
|
1776
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
1777
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
1778
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
1779
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
1780
|
-
return w1.addWeeks(n.subtract(1n));
|
|
1781
|
-
}));
|
|
1782
|
-
const JobRow = StructType({
|
|
1783
|
-
batch: StringType, start: DateTimeType, end: DateTimeType, state: EventStateType,
|
|
1784
|
-
});
|
|
1785
|
-
const MachineRow = StructType({ jobs: ArrayType(JobRow) });
|
|
1786
|
-
const machines = $.const(new Map([
|
|
1787
|
-
["L1-M03", { jobs: [{ batch: "B-214", start: week(28n), end: week(31n), state: variant("in-progress", null) }] }],
|
|
1788
|
-
["L1-M04", { jobs: [{ batch: "B-208", start: week(27n), end: week(30n), state: variant("actual", null) }] }],
|
|
1789
|
-
]), DictType(StringType, MachineRow));
|
|
1790
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
1791
|
-
return (_jsx(Plan, { axis: axis, data: machines, series: [
|
|
1792
|
-
Plan.series.span(MachineRow, {
|
|
1793
|
-
key: "machines", title: "Machines",
|
|
1794
|
-
label: (_r, k) => k, id: true,
|
|
1795
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
1796
|
-
key: j.batch, start: j.start, end: j.end,
|
|
1797
|
-
label: East.str `RUN · ${j.batch}`, state: j.state,
|
|
1798
|
-
})),
|
|
1799
|
-
}),
|
|
1800
|
-
// Rows no dataset holds — the planned shutdown, written out once.
|
|
1801
|
-
// `Plan.span` nests: the parent DECLARES its rollup and the canvas
|
|
1802
|
-
// derives the band from its two rows' runs. The series list is the
|
|
1803
|
-
// layout, so this block sits below the machines.
|
|
1804
|
-
Plan.series.rows(MachineRow, { key: "works", title: "Planned works", subtitle: "literal rows" }, [
|
|
1805
|
-
Plan.span({
|
|
1806
|
-
key: "shutdown", label: "Shutdown", rollup: "union", rows: [
|
|
1807
|
-
Plan.span({ key: "elec", label: "Electrical", runs: [
|
|
1808
|
-
Plan.run({ key: "iso", start: week(33n), end: week(34n), label: "ISOLATE", state: "confirmed" }),
|
|
1809
|
-
] }),
|
|
1810
|
-
Plan.span({ key: "mech", label: "Mechanical", runs: [
|
|
1811
|
-
Plan.run({ key: "reline", start: week(34n), end: week(36n), label: "RELINE", state: "recommended" }),
|
|
1812
|
-
] }),
|
|
1813
|
-
],
|
|
1814
|
-
}),
|
|
1815
|
-
]),
|
|
1816
|
-
] }));
|
|
1817
|
-
}),
|
|
1818
|
-
inputs: [],
|
|
1819
|
-
});
|
|
1820
|
-
// ============================================================================
|
|
1821
|
-
// planPick — the series library, minimally (#590)
|
|
1822
|
-
// ============================================================================
|
|
1823
|
-
export const planPick = example({
|
|
1824
|
-
keywords: [
|
|
1825
|
-
"Plan", "pick", "Plan.pick", "Pick", "library", "panel", "series", "hidden",
|
|
1826
|
-
"toggle", "eye", "show", "hide", "choose", "persisted", "Reactive", "State", "#590",
|
|
1827
|
-
],
|
|
1828
|
-
description: "The series library, minimally — `Plan.pick` binds which series show, and `<Plan pick>` mounts the library beside the canvas",
|
|
1829
|
-
fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
|
|
1830
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
1831
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
1832
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
1833
|
-
return w1.addWeeks(n.subtract(1n));
|
|
1834
|
-
}));
|
|
1835
|
-
const JobRow = StructType({
|
|
1836
|
-
batch: StringType, start: DateTimeType, end: DateTimeType, state: EventStateType,
|
|
1837
|
-
});
|
|
1838
|
-
const OpsRow = StructType({
|
|
1839
|
-
series: StringType, jobs: ArrayType(JobRow), cells: ArrayType(Plan.Types.HeatCell),
|
|
1840
|
-
});
|
|
1841
|
-
const noJobs = $.const([], ArrayType(JobRow));
|
|
1842
|
-
const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
|
|
1843
|
-
const pcts = $.const([46.0, 58.0, 66.0, 72.0, 84.0, 96.0], ArrayType(FloatType));
|
|
1844
|
-
const cells = $.let(East.Array.generate(6n, Plan.Types.HeatCell, (_$, i) => ({
|
|
1845
|
-
at: Plan.at.time(week(i.multiply(2n).add(27n))), value: some(pcts.get(i)), label: none,
|
|
1846
|
-
})));
|
|
1847
|
-
const ops = $.const(new Map([
|
|
1848
|
-
["L1-M03", { series: "machines", cells: noCells,
|
|
1849
|
-
jobs: [{ batch: "B-214", start: week(28n), end: week(31n), state: variant("in-progress", null) }] }],
|
|
1850
|
-
["L1-M04", { series: "machines", cells: noCells,
|
|
1851
|
-
jobs: [{ batch: "B-208", start: week(27n), end: week(30n), state: variant("actual", null) }] }],
|
|
1852
|
-
["L2-load", { series: "load", cells, jobs: noJobs }],
|
|
1853
|
-
]), DictType(StringType, OpsRow));
|
|
1854
|
-
// Every series that COULD show — the library lists these, and the
|
|
1855
|
-
// canvas shows the ones switched on in this order.
|
|
1856
|
-
const all = $.const([
|
|
1857
|
-
Plan.series.span(OpsRow, {
|
|
1858
|
-
key: "machines", title: "Machine jobs", subtitle: "one row per machine",
|
|
1859
|
-
match: r => r.series.equal("machines"),
|
|
1860
|
-
label: (_r, k) => k, id: true,
|
|
1861
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
1862
|
-
key: j.batch, start: j.start, end: j.end,
|
|
1863
|
-
label: East.str `RUN · ${j.batch}`, state: j.state,
|
|
1864
|
-
})),
|
|
1865
|
-
}),
|
|
1866
|
-
Plan.series.heat(OpsRow, {
|
|
1867
|
-
key: "load", title: "Line load", subtitle: "% per fortnight",
|
|
1868
|
-
match: r => r.series.equal("load"),
|
|
1869
|
-
label: (_r, k) => k,
|
|
1870
|
-
cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
|
|
1871
|
-
}),
|
|
1872
|
-
], ArrayType(Plan.Types.Series(OpsRow)));
|
|
1873
|
-
// The handle is STATE — which series are switched off, persisted
|
|
1874
|
-
// under its key — so it lives inside the Reactive. "load" starts off.
|
|
1875
|
-
const shown = $.let(Plan.pick("ex.plan.pick", all, { hidden: ["load"] }));
|
|
1876
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
1877
|
-
// `pick` REPLACES `series`: the canvas shows the picked series and
|
|
1878
|
-
// mounts the library itself, so nothing else is wired.
|
|
1879
|
-
return (_jsx(Plan, { axis: axis, data: ops, pick: shown }));
|
|
1880
|
-
} }))),
|
|
1881
|
-
inputs: [],
|
|
1882
|
-
});
|
|
1883
|
-
// ============================================================================
|
|
1884
|
-
// planLibraryDnd — the pickable series library over every row kind
|
|
1885
|
-
// ============================================================================
|
|
1886
|
-
export const planLibraryDnd = example({
|
|
1887
|
-
keywords: [
|
|
1888
|
-
"Plan", "data", "series", "library", "Pick", "pick", "Panel", "pickItems", "hidden",
|
|
1889
|
-
"toggle", "eye", "kind icon", "group", "section", "views", "nested", "children", "rows",
|
|
1890
|
-
"chrome", "span", "buckets", "chart", "heat", "table", "cards", "events", "duplicate",
|
|
1891
|
-
"same entity", "multiple views", "adjacent", "seek", "layout", "order",
|
|
1892
|
-
"Reactive", "State", "#590",
|
|
1893
|
-
],
|
|
1894
|
-
description: "The series library across every row kind — duplicate kinds, sections, a group per entry, and a `views` series showing one asset three ways",
|
|
1895
|
-
fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
|
|
1896
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
1897
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
1898
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
1899
|
-
return w1.addWeeks(n.subtract(1n));
|
|
1900
|
-
}));
|
|
1901
|
-
const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
|
|
1902
|
-
const JobRow = StructType({
|
|
1903
|
-
key: StringType, label: StringType,
|
|
1904
|
-
start: DateTimeType, end: DateTimeType, state: EventStateType,
|
|
1905
|
-
});
|
|
1906
|
-
const ShiftRow = StructType({
|
|
1907
|
-
key: StringType, from: DateTimeType, to: DateTimeType, label: StringType, state: EventStateType,
|
|
1908
|
-
});
|
|
1909
|
-
const AllocRow = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
|
|
1910
|
-
// ONE flat source (the `planExpand` shape): `pick` names the series
|
|
1911
|
-
// that claims the row, and every other channel is empty for the
|
|
1912
|
-
// series that do not use it. A program holds its members
|
|
1913
|
-
// (`members`), so the entry type is recursive.
|
|
1914
|
-
const OpsRow = RecursiveType((self) => StructType({
|
|
1915
|
-
pick: StringType, label: StringType,
|
|
1916
|
-
jobs: ArrayType(JobRow),
|
|
1917
|
-
points: ArrayType(MeasureRow),
|
|
1918
|
-
cells: ArrayType(Plan.Types.HeatCell),
|
|
1919
|
-
allocs: ArrayType(AllocRow),
|
|
1920
|
-
nums: ArrayType(Plan.Types.TableCell),
|
|
1921
|
-
shifts: ArrayType(ShiftRow),
|
|
1922
|
-
marks: ArrayType(Plan.Types.EventMark),
|
|
1923
|
-
members: DictType(StringType, self),
|
|
1924
|
-
}));
|
|
1925
|
-
const noJobs = $.const([], ArrayType(JobRow));
|
|
1926
|
-
const noPoints = $.const([], ArrayType(MeasureRow));
|
|
1927
|
-
const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
|
|
1928
|
-
const noAllocs = $.const([], ArrayType(AllocRow));
|
|
1929
|
-
const noNums = $.const([], ArrayType(Plan.Types.TableCell));
|
|
1930
|
-
const noShifts = $.const([], ArrayType(ShiftRow));
|
|
1931
|
-
const noMarks = $.const([], ArrayType(Plan.Types.EventMark));
|
|
1932
|
-
const noMembers = $.const(new Map(), DictType(StringType, OpsRow));
|
|
1933
|
-
const pcts = $.const([46.0, 58.0, 66.0, 72.0, 84.0, 96.0], ArrayType(FloatType));
|
|
1934
|
-
const points = $.let(East.Array.generate(6n, MeasureRow, (_$, i) => ({ week: week(i.multiply(2n).add(27n)), pct: pcts.get(i) })));
|
|
1935
|
-
const cells = $.let(East.Array.generate(6n, Plan.Types.HeatCell, (_$, i) => ({
|
|
1936
|
-
at: Plan.at.time(week(i.multiply(2n).add(27n))),
|
|
1937
|
-
value: some(pcts.get(i)),
|
|
1938
|
-
label: some(East.Float.printFixed(pcts.get(i), 0n)),
|
|
1939
|
-
})));
|
|
1940
|
-
// The lifecycle states as consts, so each tag stays a LITERAL —
|
|
1941
|
-
// `variant(someString, null)` widens to `variant<string, null>` and
|
|
1942
|
-
// stops satisfying `EventStateType`.
|
|
1943
|
-
const ACTUAL = variant("actual", null);
|
|
1944
|
-
const CONFIRMED = variant("confirmed", null);
|
|
1945
|
-
const RUNNING = variant("in-progress", null);
|
|
1946
|
-
const PROPOSED = variant("proposed", variant("recommended", null));
|
|
1947
|
-
const base = {
|
|
1948
|
-
jobs: noJobs, points: noPoints, cells: noCells,
|
|
1949
|
-
allocs: noAllocs, nums: noNums, shifts: noShifts, marks: noMarks, members: noMembers,
|
|
1950
|
-
};
|
|
1951
|
-
// The keys are the entries' identities; the LAYOUT is the series
|
|
1952
|
-
// list below (#822).
|
|
1953
|
-
const ops = $.const(new Map([
|
|
1954
|
-
["util", { ...base, pick: "util", label: "Util %", points }],
|
|
1955
|
-
// ONE asset, THREE views — the `views` series below gives each
|
|
1956
|
-
// machine a jobs row, a utilisation chart and a tonnes table,
|
|
1957
|
-
// from these channels; nothing about the row is duplicated.
|
|
1958
|
-
["m03", { ...base, pick: "machines", label: "L1-M03",
|
|
1959
|
-
jobs: [{ key: "b214", label: "RUN · B-214", start: week(28n), end: week(31n), state: RUNNING }],
|
|
1960
|
-
points,
|
|
1961
|
-
nums: [
|
|
1962
|
-
{ at: Plan.at.time(week(28n)), value: some(96.0), text: none, tone: none },
|
|
1963
|
-
{ at: Plan.at.time(week(31n)), value: some(88.0), text: none, tone: none },
|
|
1964
|
-
] }],
|
|
1965
|
-
["m04", { ...base, pick: "machines", label: "L1-M04",
|
|
1966
|
-
jobs: [{ key: "b208", label: "RUN · B-208", start: week(27n), end: week(30n), state: ACTUAL }],
|
|
1967
|
-
points,
|
|
1968
|
-
nums: [
|
|
1969
|
-
{ at: Plan.at.time(week(28n)), value: some(112.0), text: none, tone: none },
|
|
1970
|
-
{ at: Plan.at.time(week(31n)), value: some(-24.0), text: none, tone: none },
|
|
1971
|
-
] }],
|
|
1972
|
-
// SAME KIND as the machines' jobs, different entry — a kind is
|
|
1973
|
-
// not an identity, which is why the library keys on `key`.
|
|
1974
|
-
["c01", { ...base, pick: "contract", label: "CON-01",
|
|
1975
|
-
jobs: [{ key: "c1", label: "RUN · C-1", start: week(30n), end: week(34n), state: CONFIRMED }] }],
|
|
1976
|
-
["load", { ...base, pick: "load", label: "L2 load", cells }],
|
|
1977
|
-
["qual", { ...base, pick: "quality", label: "Quality", cells }],
|
|
1978
|
-
["dock2", { ...base, pick: "docks", label: "Dock 2",
|
|
1979
|
-
allocs: [
|
|
1980
|
-
{ key: "a1", at: week(28n), state: variant("confirmed", null) },
|
|
1981
|
-
{ key: "a2", at: week(31n), state: variant("proposed", variant("recommended", null)) },
|
|
1982
|
-
] }],
|
|
1983
|
-
["desp", { ...base, pick: "table", label: "Despatch t",
|
|
1984
|
-
nums: [
|
|
1985
|
-
{ at: Plan.at.time(week(28n)), value: some(128.0), text: none, tone: none },
|
|
1986
|
-
{ at: Plan.at.time(week(30n)), value: some(-96.0), text: none, tone: none },
|
|
1987
|
-
] }],
|
|
1988
|
-
["crewA", { ...base, pick: "cards", label: "Crew A",
|
|
1989
|
-
shifts: [
|
|
1990
|
-
{ key: "s1", from: week(27n), to: week(29n), label: "80h", state: variant("confirmed", null) },
|
|
1991
|
-
{ key: "s2", from: week(31n), to: week(33n), label: "+64h", state: variant("proposed", variant("recommended", null)) },
|
|
1992
|
-
] }],
|
|
1993
|
-
["ms", { ...base, pick: "events", label: "Milestones",
|
|
1994
|
-
marks: [
|
|
1995
|
-
{ key: "k", at: Plan.at.time(week(29n)), kind: variant("milestone", null), icon: none, label: some("KICKOFF") },
|
|
1996
|
-
{ key: "a", at: Plan.at.time(week(34n)), kind: variant("exception", null), icon: none, label: some("AUDIT") },
|
|
1997
|
-
] }],
|
|
1998
|
-
// Members of the three sections — each section lays its series
|
|
1999
|
-
// out under its header.
|
|
2000
|
-
["m11", { ...base, pick: "gspan", label: "L3-M11",
|
|
2001
|
-
jobs: [{ key: "b301", label: "RUN · B-301", start: week(29n), end: week(33n), state: CONFIRMED }] }],
|
|
2002
|
-
["m12", { ...base, pick: "gspan", label: "L3-M12",
|
|
2003
|
-
jobs: [{ key: "b302", label: "RUN · B-302", start: week(31n), end: week(36n), state: ACTUAL }] }],
|
|
2004
|
-
["l4", { ...base, pick: "gheat", label: "L4 load", cells }],
|
|
2005
|
-
["d5", { ...base, pick: "gbuckets", label: "Dock 5",
|
|
2006
|
-
allocs: [{ key: "a3", at: week(33n), state: variant("confirmed", null) }] }],
|
|
2007
|
-
// Two PROGRAMS, each holding its runs — one strip per program,
|
|
2008
|
-
// its members nested in it.
|
|
2009
|
-
["Program A", { ...base, pick: "programs", label: "Program A",
|
|
2010
|
-
members: new Map([
|
|
2011
|
-
["p1", { ...base, pick: "programs", label: "PR-A1",
|
|
2012
|
-
jobs: [{ key: "p1", label: "RUN · P-1", start: week(28n), end: week(32n), state: CONFIRMED }] }],
|
|
2013
|
-
["p2", { ...base, pick: "programs", label: "PR-A2",
|
|
2014
|
-
jobs: [{ key: "p2", label: "RUN · P-2", start: week(33n), end: week(37n), state: PROPOSED }] }],
|
|
2015
|
-
]) }],
|
|
2016
|
-
["Program B", { ...base, pick: "programs", label: "Program B",
|
|
2017
|
-
members: new Map([
|
|
2018
|
-
["p3", { ...base, pick: "programs", label: "PR-B1",
|
|
2019
|
-
jobs: [{ key: "p3", label: "RUN · P-3", start: week(30n), end: week(35n), state: ACTUAL }] }],
|
|
2020
|
-
]) }],
|
|
2021
|
-
]), DictType(StringType, OpsRow));
|
|
2022
|
-
// The whole library, in layout order: every kind once, two kinds
|
|
2023
|
-
// TWICE, a views series, three sections each wrapping a different
|
|
2024
|
-
// kind, a group per entry, and literal chrome. Fourteen entries,
|
|
2025
|
-
// eleven distinct arms.
|
|
2026
|
-
const all = $.const([
|
|
2027
|
-
// Literal one-off chrome — it names itself, so it can be
|
|
2028
|
-
// switched off like anything else.
|
|
2029
|
-
Plan.series.rows(OpsRow, { key: "chrome", title: "Section header", subtitle: "literal chrome" }, [Plan.events({ key: "hdr", label: "Plan", id: true })]),
|
|
2030
|
-
Plan.series.chart(OpsRow, {
|
|
2031
|
-
key: "util", title: "Utilisation", subtitle: "% per fortnight",
|
|
2032
|
-
match: r => r.pick.equal("util"),
|
|
2033
|
-
label: r => r.label, id: true, height: "spark",
|
|
2034
|
-
layers: r => [Chart.Column(r.points, { x: p => p.week, y: p => p.pct })],
|
|
2035
|
-
}),
|
|
2036
|
-
// ── THE SAME ASSET, SEEN THREE WAYS ───────────────────────
|
|
2037
|
-
// A `views` series gives each machine one row per member
|
|
2038
|
-
// series, ADJACENT and in this order — its jobs, its
|
|
2039
|
-
// utilisation, its tonnes — and a seek on a machine lands on
|
|
2040
|
-
// its first view row. Each row's id is its member series and
|
|
2041
|
-
// the machine's key, so all three are addressable apart.
|
|
2042
|
-
Plan.series.views(OpsRow, {
|
|
2043
|
-
key: "machines", title: "Machines", subtitle: "one asset, three views",
|
|
2044
|
-
match: r => r.pick.equal("machines"),
|
|
2045
|
-
}, [
|
|
2046
|
-
Plan.series.span(OpsRow, {
|
|
2047
|
-
key: "machine-jobs", title: "Machine jobs",
|
|
2048
|
-
label: r => r.label, id: true,
|
|
2049
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
2050
|
-
key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
|
|
2051
|
-
})),
|
|
2052
|
-
}),
|
|
2053
|
-
// The label stays the ASSET — all three rows are the same
|
|
2054
|
-
// machine, and pretending otherwise would hide that. What
|
|
2055
|
-
// distinguishes them is the VIEW, which is what the gutter
|
|
2056
|
-
// sub-line is for.
|
|
2057
|
-
Plan.series.chart(OpsRow, {
|
|
2058
|
-
key: "machine-util", title: "Machine · utilisation",
|
|
2059
|
-
label: r => r.label, stacked: true, sub: _r => some("utilisation %"), height: "spark",
|
|
2060
|
-
layers: r => [Chart.Line(r.points, { x: p => p.week, y: p => p.pct })],
|
|
2061
|
-
}),
|
|
2062
|
-
Plan.series.table(OpsRow, {
|
|
2063
|
-
key: "machine-tonnes", title: "Machine · tonnes",
|
|
2064
|
-
label: r => r.label, stacked: true, sub: _r => some("tonnes · plan Δ"),
|
|
2065
|
-
cells: r => r.nums,
|
|
2066
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
2067
|
-
}),
|
|
2068
|
-
]),
|
|
2069
|
-
Plan.series.span(OpsRow, {
|
|
2070
|
-
key: "contract", title: "Contractor jobs", subtitle: "same KIND, own entry",
|
|
2071
|
-
match: r => r.pick.equal("contract"),
|
|
2072
|
-
label: r => r.label, id: true,
|
|
2073
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
2074
|
-
key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
|
|
2075
|
-
})),
|
|
2076
|
-
}),
|
|
2077
|
-
Plan.series.heat(OpsRow, {
|
|
2078
|
-
key: "load", title: "Line load", subtitle: "% per fortnight",
|
|
2079
|
-
match: r => r.pick.equal("load"),
|
|
2080
|
-
label: r => r.label,
|
|
2081
|
-
cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
|
|
2082
|
-
}),
|
|
2083
|
-
Plan.series.heat(OpsRow, {
|
|
2084
|
-
key: "quality", title: "Quality index", subtitle: "same KIND, own entry",
|
|
2085
|
-
match: r => r.pick.equal("quality"),
|
|
2086
|
-
label: r => r.label,
|
|
2087
|
-
cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
|
|
2088
|
-
}),
|
|
2089
|
-
Plan.series.buckets(OpsRow, {
|
|
2090
|
-
key: "docks", title: "Dock allocations", subtitle: "tiles per bucket",
|
|
2091
|
-
match: r => r.pick.equal("docks"),
|
|
2092
|
-
label: r => r.label,
|
|
2093
|
-
events: r => r.allocs.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
|
|
2094
|
-
}),
|
|
2095
|
-
Plan.series.table(OpsRow, {
|
|
2096
|
-
key: "table", title: "Despatch tonnes", subtitle: "per bucket",
|
|
2097
|
-
match: r => r.pick.equal("table"),
|
|
2098
|
-
label: r => r.label,
|
|
2099
|
-
cells: r => r.nums,
|
|
2100
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
2101
|
-
}),
|
|
2102
|
-
Plan.series.cards(OpsRow, {
|
|
2103
|
-
key: "cards", title: "Crew shifts", subtitle: "assignments",
|
|
2104
|
-
match: r => r.pick.equal("cards"),
|
|
2105
|
-
label: r => r.label,
|
|
2106
|
-
chips: r => r.shifts.map((_$, s) => Plan.chip({ key: s.key, from: s.from, to: s.to, label: s.label, state: s.state })),
|
|
2107
|
-
}),
|
|
2108
|
-
Plan.series.events(OpsRow, {
|
|
2109
|
-
key: "events", title: "Milestones", subtitle: "instant marks",
|
|
2110
|
-
match: r => r.pick.equal("events"),
|
|
2111
|
-
label: r => r.label, id: true,
|
|
2112
|
-
marks: r => r.marks,
|
|
2113
|
-
}),
|
|
2114
|
-
// SECTIONS — each wrapping a DIFFERENT kind. A section is the
|
|
2115
|
-
// unit a person picks — "Line 3" — and switching it off takes
|
|
2116
|
-
// its whole block with it, because its members are built by its
|
|
2117
|
-
// own `derive`.
|
|
2118
|
-
//
|
|
2119
|
-
// The metas are terse because the gutter is 168px and truncates
|
|
2120
|
-
// the title if the meta crowds it.
|
|
2121
|
-
Plan.series.section(OpsRow, { key: "line3", title: "Line 3", subtitle: "a section of spans", meta: "span" }, [
|
|
2122
|
-
Plan.series.span(OpsRow, {
|
|
2123
|
-
key: "gspan", title: "Line 3 jobs", subtitle: "member",
|
|
2124
|
-
match: r => r.pick.equal("gspan"),
|
|
2125
|
-
label: r => r.label, id: true,
|
|
2126
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
2127
|
-
key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
|
|
2128
|
-
})),
|
|
2129
|
-
}),
|
|
2130
|
-
]),
|
|
2131
|
-
Plan.series.section(OpsRow, { key: "loads", title: "Load", subtitle: "a section of heat", meta: "heat" }, [
|
|
2132
|
-
Plan.series.heat(OpsRow, {
|
|
2133
|
-
key: "gheat", title: "Load rows", subtitle: "member",
|
|
2134
|
-
match: r => r.pick.equal("gheat"),
|
|
2135
|
-
label: r => r.label,
|
|
2136
|
-
cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
|
|
2137
|
-
}),
|
|
2138
|
-
]),
|
|
2139
|
-
Plan.series.section(OpsRow, { key: "dock-group", title: "Docks", subtitle: "a section of buckets", meta: "buckets" }, [
|
|
2140
|
-
Plan.series.buckets(OpsRow, {
|
|
2141
|
-
key: "gbuckets", title: "Dock rows", subtitle: "member",
|
|
2142
|
-
match: r => r.pick.equal("gbuckets"),
|
|
2143
|
-
label: r => r.label,
|
|
2144
|
-
events: r => r.allocs.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
|
|
2145
|
-
}),
|
|
2146
|
-
]),
|
|
2147
|
-
// One strip PER PROGRAM, its runs nested in the program's entry
|
|
2148
|
-
// — and ONE library entry for all of them.
|
|
2149
|
-
Plan.series.group(OpsRow, {
|
|
2150
|
-
key: "programs", title: "Programs", subtitle: "one strip per program",
|
|
2151
|
-
match: r => r.pick.equal("programs"),
|
|
2152
|
-
label: r => r.label,
|
|
2153
|
-
children: Plan.children((r) => r.members, [
|
|
2154
|
-
Plan.series.span(OpsRow, {
|
|
2155
|
-
key: "program-runs", title: "Program runs", subtitle: "member",
|
|
2156
|
-
label: r => r.label, id: true,
|
|
2157
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
2158
|
-
key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
|
|
2159
|
-
})),
|
|
2160
|
-
}),
|
|
2161
|
-
]),
|
|
2162
|
-
}),
|
|
2163
|
-
], ArrayType(Plan.Types.Series(OpsRow)));
|
|
2164
|
-
// The library lists every series by title, subtitle and kind; the
|
|
2165
|
-
// only option is which start switched off. There are no row counts
|
|
2166
|
-
// (#822): a count means something only when every entry is in hand.
|
|
2167
|
-
const shown = $.let(Plan.pick("ex.plan.library", all, {
|
|
2168
|
-
hidden: ["quality", "cards", "dock-group"],
|
|
2169
|
-
}));
|
|
2170
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
2171
|
-
// `pick` REPLACES `series`: the handle already carries the list, so
|
|
2172
|
-
// the canvas feeds itself the picked ones and mounts the library.
|
|
2173
|
-
// Nothing here wires the panel to the canvas.
|
|
2174
|
-
return (_jsx(Plan, { axis: axis, data: ops, pick: shown, style: { height: "620px" } }));
|
|
2175
|
-
} }))),
|
|
2176
|
-
inputs: [],
|
|
2177
|
-
});
|
|
2178
|
-
// ============================================================================
|
|
2179
|
-
// planRowDrop — the canvas as a drag TARGET, and which rows can receive
|
|
2180
|
-
// ============================================================================
|
|
2181
|
-
/**
|
|
2182
|
-
* A Plan is a drag target, and a heterogeneous one — which is what makes it
|
|
2183
|
-
* different from every other target in the grammar.
|
|
2184
|
-
*
|
|
2185
|
-
* Roster, Board and Blend have ONE kind of cell, so "can you drop here" is a
|
|
2186
|
-
* question about the cell's contents. A Plan's rows are nine different things,
|
|
2187
|
-
* so the question is answered at three levels:
|
|
2188
|
-
*
|
|
2189
|
-
* 1. **Structurally, by series.** A card lands only on a row whose series
|
|
2190
|
-
* declares `edit` — WHERE it lands (`items`, the entry's list the row's
|
|
2191
|
-
* elements come from) and HOW it becomes one (`create`, from the drop: the
|
|
2192
|
-
* card, the row and the bucket's instant). Only the kinds holding discrete
|
|
2193
|
-
* scheduled objects can — `span` (runs), `buckets` (tiles), `events`
|
|
2194
|
-
* (marks), `cards` (chips). A `chart` / `heat` / `table` row renders
|
|
2195
|
-
* DERIVED values, and section headers and group strips are wayfinding:
|
|
2196
|
-
* they register no cell and never light up during a drag.
|
|
2197
|
-
* 2. **By policy, with `canDrop`.** Of the rows that can receive, this canvas
|
|
2198
|
-
* admits only the matching FAMILY: a job goes on a machine, a delivery on
|
|
2199
|
-
* a dock, a shift on a crew, a milestone on a stream. The `PALLET` card
|
|
2200
|
-
* belongs to no family and is therefore refused everywhere — the ⊘ stage
|
|
2201
|
-
* on every row, which is what a card with nowhere to go should look like.
|
|
2202
|
-
* 3. **As a draft (#880).** A drop is a gesture of the editing session: the
|
|
2203
|
-
* entry is drafted with the new item in its list and its rows derived
|
|
2204
|
-
* again — drawn at once with the pending mark, undone with ⌘Z — and Apply
|
|
2205
|
-
* writes every draft as ONE checked batch, here through the live handle's
|
|
2206
|
-
* inline adapter (`editing.onUpdate`).
|
|
2207
|
-
*
|
|
2208
|
-
* The canvas is bounded shorter than its rows, so the Line 3 machine starts
|
|
2209
|
-
* below the fold: a card held at the canvas's bottom edge scrolls it there
|
|
2210
|
-
* (#608). A card is also a keyboard control — Space picks it up, the arrows
|
|
2211
|
-
* carry it from row to row and along a row's buckets, Space drops it and
|
|
2212
|
-
* Escape cancels — and every step is said to a screen reader.
|
|
2213
|
-
*/
|
|
2214
|
-
export const planRowDrop = example({
|
|
2215
|
-
keywords: [
|
|
2216
|
-
"Plan", "Library", "DnD", "drag", "drop", "canDrop", "sources", "id", "edit", "items", "create",
|
|
2217
|
-
"add", "target", "surface", "cell", "slot", "row kind", "selective", "veto",
|
|
2218
|
-
"invalid", "span", "buckets", "events", "cards", "chart", "heat", "table", "section", "row id", "row text",
|
|
2219
|
-
"droppable", "inert", "bucket instant", "editing", "onUpdate", "onPatch", "draft", "Apply", "undo",
|
|
2220
|
-
"Plan.Types.PatchEvent", "Reactive", "State", "live handle", "re-derive", "#880",
|
|
2221
|
-
"auto-scroll", "keyboard", "screen reader", "announcements", "#608",
|
|
2222
|
-
],
|
|
2223
|
-
description: "Library + Plan DnD — a card lands only on a series that declares `edit`, `canDrop` admits only the matching family, and every drop is a draft applied as one checked batch",
|
|
2224
|
-
fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
|
|
2225
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
2226
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
2227
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
2228
|
-
return w1.addWeeks(n.subtract(1n));
|
|
2229
|
-
}));
|
|
2230
|
-
const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
|
|
2231
|
-
const JobRow = StructType({
|
|
2232
|
-
key: StringType, label: StringType,
|
|
2233
|
-
start: DateTimeType, end: DateTimeType, state: EventStateType,
|
|
2234
|
-
});
|
|
2235
|
-
const ShiftRow = StructType({
|
|
2236
|
-
key: StringType, from: DateTimeType, to: DateTimeType, label: StringType, state: EventStateType,
|
|
2237
|
-
});
|
|
2238
|
-
const AllocRow = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
|
|
2239
|
-
const OpsRow = StructType({
|
|
2240
|
-
series: StringType, label: StringType,
|
|
2241
|
-
jobs: ArrayType(JobRow),
|
|
2242
|
-
points: ArrayType(MeasureRow),
|
|
2243
|
-
cells: ArrayType(Plan.Types.HeatCell),
|
|
2244
|
-
allocs: ArrayType(AllocRow),
|
|
2245
|
-
nums: ArrayType(Plan.Types.TableCell),
|
|
2246
|
-
shifts: ArrayType(ShiftRow),
|
|
2247
|
-
marks: ArrayType(Plan.Types.EventMark),
|
|
2248
|
-
});
|
|
2249
|
-
const noJobs = $.const([], ArrayType(JobRow));
|
|
2250
|
-
const noPoints = $.const([], ArrayType(MeasureRow));
|
|
2251
|
-
const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
|
|
2252
|
-
const noAllocs = $.const([], ArrayType(AllocRow));
|
|
2253
|
-
const noNums = $.const([], ArrayType(Plan.Types.TableCell));
|
|
2254
|
-
const noShifts = $.const([], ArrayType(ShiftRow));
|
|
2255
|
-
const noMarks = $.const([], ArrayType(Plan.Types.EventMark));
|
|
2256
|
-
const pcts = $.const([46.0, 58.0, 66.0, 72.0, 84.0, 96.0], ArrayType(FloatType));
|
|
2257
|
-
const points = $.let(East.Array.generate(6n, MeasureRow, (_$, i) => ({ week: week(i.multiply(2n).add(27n)), pct: pcts.get(i) })));
|
|
2258
|
-
const cells = $.let(East.Array.generate(6n, Plan.Types.HeatCell, (_$, i) => ({
|
|
2259
|
-
at: Plan.at.time(week(i.multiply(2n).add(27n))),
|
|
2260
|
-
value: some(pcts.get(i)),
|
|
2261
|
-
label: some(East.Float.printFixed(pcts.get(i), 0n)),
|
|
2262
|
-
})));
|
|
2263
|
-
const CONFIRMED = variant("confirmed", null);
|
|
2264
|
-
const RUNNING = variant("in-progress", null);
|
|
2265
|
-
// Everything a drop creates is a PROPOSAL — it is a suggestion the
|
|
2266
|
-
// host has not committed, and the lifecycle is how the canvas says so.
|
|
2267
|
-
const ADDED = variant("proposed", variant("added", null));
|
|
2268
|
-
const base = {
|
|
2269
|
-
jobs: noJobs, points: noPoints, cells: noCells,
|
|
2270
|
-
allocs: noAllocs, nums: noNums, shifts: noShifts, marks: noMarks,
|
|
2271
|
-
};
|
|
2272
|
-
// The series list below orders the canvas, interleaving the
|
|
2273
|
-
// droppable and inert kinds — a solid block of receiving rows would
|
|
2274
|
-
// not show that the line is drawn per kind. The source is a LIVE
|
|
2275
|
-
// handle: the canvas reads it, and Apply writes the drafts back.
|
|
2276
|
-
const ops = $.let(State.bind([DictType(StringType, OpsRow)], "ex.plan.ops", new Map([
|
|
2277
|
-
["util", { ...base, series: "util", label: "Util %", points }],
|
|
2278
|
-
["m03", { ...base, series: "mach", label: "L1-M03",
|
|
2279
|
-
jobs: [{ key: "b214", label: "RUN · B-214", start: week(28n), end: week(31n), state: RUNNING }] }],
|
|
2280
|
-
["m04", { ...base, series: "mach", label: "L1-M04",
|
|
2281
|
-
jobs: [{ key: "b208", label: "RUN · B-208", start: week(27n), end: week(30n), state: CONFIRMED }] }],
|
|
2282
|
-
["load", { ...base, series: "load", label: "L2 load", cells }],
|
|
2283
|
-
["dock2", { ...base, series: "dock", label: "Dock 2",
|
|
2284
|
-
allocs: [{ key: "a1", at: week(29n), state: CONFIRMED }] }],
|
|
2285
|
-
["desp", { ...base, series: "table", label: "Despatch t",
|
|
2286
|
-
nums: [
|
|
2287
|
-
{ at: Plan.at.time(week(28n)), value: some(128.0), text: none, tone: none },
|
|
2288
|
-
{ at: Plan.at.time(week(31n)), value: some(-96.0), text: none, tone: none },
|
|
2289
|
-
] }],
|
|
2290
|
-
["crewA", { ...base, series: "crew", label: "Crew A",
|
|
2291
|
-
shifts: [{ key: "s1", from: week(27n), to: week(29n), label: "80h", state: CONFIRMED }] }],
|
|
2292
|
-
["ms", { ...base, series: "strm", label: "Milestones",
|
|
2293
|
-
marks: [{ key: "k", at: Plan.at.time(week(29n)), kind: variant("milestone", null), icon: none, label: some("KICKOFF") }] }],
|
|
2294
|
-
// A section MEMBER — the header itself takes no drops, but the
|
|
2295
|
-
// span row under it receives like any other span row.
|
|
2296
|
-
["m11", { ...base, series: "gmach", label: "L3-M11",
|
|
2297
|
-
jobs: [{ key: "b301", label: "RUN · B-301", start: week(30n), end: week(34n), state: CONFIRMED }] }],
|
|
2298
|
-
])));
|
|
2299
|
-
// ── The two policy tables the host owns ───────────────────────
|
|
2300
|
-
// Which FAMILY of card each row will take. A drop cell names its
|
|
2301
|
-
// row by the canonical TEXT of the row's id — the series that made
|
|
2302
|
-
// it and the path of keys to it — so the table is keyed by that
|
|
2303
|
-
// text. A row absent from this map takes nothing — which is how
|
|
2304
|
-
// the inert kinds would behave even if they did register a cell.
|
|
2305
|
-
const rowAccepts = $.const(new Map([
|
|
2306
|
-
[East.print(Plan.ref("mach", "m03")), "job"],
|
|
2307
|
-
[East.print(Plan.ref("mach", "m04")), "job"],
|
|
2308
|
-
[East.print(Plan.ref("gmach", "m11")), "job"],
|
|
2309
|
-
[East.print(Plan.ref("dock", "dock2")), "delivery"],
|
|
2310
|
-
[East.print(Plan.ref("crew", "crewA")), "shift"],
|
|
2311
|
-
[East.print(Plan.ref("strm", "ms")), "milestone"],
|
|
2312
|
-
]), DictType(StringType, StringType));
|
|
2313
|
-
const CardRow = StructType({
|
|
2314
|
-
key: StringType, name: StringType, family: StringType, note: StringType, icon: StringType,
|
|
2315
|
-
});
|
|
2316
|
-
const cards = $.const([
|
|
2317
|
-
{ key: "job-weld", name: "Weld cell", family: "job", note: "job · machines", icon: "gear" },
|
|
2318
|
-
{ key: "job-cure", name: "Cure oven", family: "job", note: "job · machines", icon: "fire" },
|
|
2319
|
-
{ key: "dlv-truck", name: "Truck 12", family: "delivery", note: "delivery · docks", icon: "truck" },
|
|
2320
|
-
{ key: "shf-night", name: "Night shift", family: "shift", note: "shift · crews", icon: "moon" },
|
|
2321
|
-
{ key: "mst-audit", name: "Audit gate", family: "milestone", note: "milestone · streams", icon: "flag" },
|
|
2322
|
-
// Belongs to no family, so no row accepts it — the ⊘ stage
|
|
2323
|
-
// everywhere, which is what a card with nowhere to go looks like.
|
|
2324
|
-
{ key: "pallet", name: "PALLET", family: "none", note: "fits nowhere", icon: "box" },
|
|
2325
|
-
], ArrayType(CardRow));
|
|
2326
|
-
const cardFamily = $.const(cards.toDict((_$, c) => c.key, (_$, c) => c.family));
|
|
2327
|
-
const cardName = $.const(cards.toDict((_$, c) => c.key, (_$, c) => c.name));
|
|
2328
|
-
// ── The drop veto ────────────────────────────────────────────
|
|
2329
|
-
// Consulted with the candidate event the pointer's CURRENT bucket
|
|
2330
|
-
// would produce, so the ⊘ appears while dragging rather than after,
|
|
2331
|
-
// and once more before the drop becomes a draft. Only `add` reaches
|
|
2332
|
-
// a Plan from a library, and refusing the rest says so.
|
|
2333
|
-
const canDrop = $.const(East.function([DragEventType], BooleanType, ($, event) => {
|
|
2334
|
-
const no = $.const(false, BooleanType);
|
|
2335
|
-
return event.match({
|
|
2336
|
-
add: ($, add) => {
|
|
2337
|
-
const row = $.let(add.into.row);
|
|
2338
|
-
const card = $.let(add.from.key);
|
|
2339
|
-
return rowAccepts.has(row)
|
|
2340
|
-
.and(_$ => cardFamily.has(card))
|
|
2341
|
-
.and(_$ => rowAccepts.get(row).equal(cardFamily.get(card)));
|
|
2342
|
-
},
|
|
2343
|
-
}, _$ => no);
|
|
2344
|
-
}));
|
|
2345
|
-
// ── The session ──────────────────────────────────────────────
|
|
2346
|
-
// Every drop is a DRAFT of the entry it landed on: the canvas
|
|
2347
|
-
// draws the new item at once, marked pending, and the history bar
|
|
2348
|
-
// undoes, redoes, discards and applies it. Apply writes every draft
|
|
2349
|
-
// through the live handle as one checked batch; `onPatch` hears each
|
|
2350
|
-
// gesture as it is made.
|
|
2351
|
-
const lastBind = $.let(State.bind([StringType], "ex.plan.lastdrop", "none yet"));
|
|
2352
|
-
const onPatch = $.const(East.function([Plan.Types.PatchEvent(OpsRow)], NullType, ($, event) => {
|
|
2353
|
-
$(lastBind.write(East.str `${event.origin.getTag()} · ${event.label}`));
|
|
2354
|
-
}));
|
|
2355
|
-
const last = $.let(lastBind.read());
|
|
2356
|
-
const axis = $.const(Plan.axis({
|
|
2357
|
-
window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n),
|
|
2358
|
-
}));
|
|
2359
|
-
return (_jsxs(VStack, { gap: "4", align: "stretch", children: [_jsx(Library, { id: "plan-library", data: cards, item: c => ({ key: c.key, label: c.name, sublabel: c.note, icon: c.icon }) }), _jsx(Plan, { axis: axis, data: ops,
|
|
2360
|
-
// The DnD target role: `id` names this surface in every
|
|
2361
|
-
// cell ref, `sources` says which palettes it will take
|
|
2362
|
-
// from, and `canDrop` is the policy. A drop becomes a
|
|
2363
|
-
// draft of the session — without `editing` no card
|
|
2364
|
-
// lands, since nothing could hold it.
|
|
2365
|
-
id: "ops-plan", sources: ["plan-library"], canDrop: canDrop, editing: { onUpdate: ops.write, onPatch }, series: [
|
|
2366
|
-
// INERT — a chart plots a derived series, so there
|
|
2367
|
-
// is nothing a card could become here.
|
|
2368
|
-
Plan.series.chart(OpsRow, {
|
|
2369
|
-
key: "util", title: "Utilisation",
|
|
2370
|
-
match: r => r.series.equal("util"),
|
|
2371
|
-
label: r => r.label, id: true, height: "spark",
|
|
2372
|
-
layers: r => [Chart.Line(r.points, { x: p => p.week, y: p => p.pct })],
|
|
2373
|
-
}),
|
|
2374
|
-
// RECEIVES — runs are discrete scheduled objects. A
|
|
2375
|
-
// dropped job joins the machine's `jobs`: a
|
|
2376
|
-
// fortnight long, from the bucket the pointer named.
|
|
2377
|
-
Plan.series.span(OpsRow, {
|
|
2378
|
-
key: "mach", title: "Machine jobs",
|
|
2379
|
-
match: r => r.series.equal("mach"),
|
|
2380
|
-
label: r => r.label, id: true,
|
|
2381
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
2382
|
-
key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
|
|
2383
|
-
})),
|
|
2384
|
-
edit: {
|
|
2385
|
-
items: "jobs",
|
|
2386
|
-
create: (drop, r) => ({
|
|
2387
|
-
key: East.str `drop-${drop.from.key}-${East.print(r.jobs.size())}`,
|
|
2388
|
-
label: cardName.get(drop.from.key),
|
|
2389
|
-
start: drop.at.unwrap("time"), end: drop.at.unwrap("time").addWeeks(2n),
|
|
2390
|
-
state: ADDED,
|
|
2391
|
-
}),
|
|
2392
|
-
},
|
|
2393
|
-
}),
|
|
2394
|
-
// INERT — an intensity field has no object to add to.
|
|
2395
|
-
Plan.series.heat(OpsRow, {
|
|
2396
|
-
key: "load", title: "Line load",
|
|
2397
|
-
match: r => r.series.equal("load"),
|
|
2398
|
-
label: r => r.label,
|
|
2399
|
-
cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
|
|
2400
|
-
}),
|
|
2401
|
-
// RECEIVES — a dropped delivery becomes a tile in
|
|
2402
|
-
// the bucket under the pointer.
|
|
2403
|
-
Plan.series.buckets(OpsRow, {
|
|
2404
|
-
key: "dock", title: "Dock allocations",
|
|
2405
|
-
match: r => r.series.equal("dock"),
|
|
2406
|
-
label: r => r.label,
|
|
2407
|
-
events: r => r.allocs.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
|
|
2408
|
-
edit: {
|
|
2409
|
-
items: "allocs",
|
|
2410
|
-
create: (drop, r) => ({
|
|
2411
|
-
key: East.str `drop-${drop.from.key}-${East.print(r.allocs.size())}`,
|
|
2412
|
-
at: drop.at.unwrap("time"), state: ADDED,
|
|
2413
|
-
}),
|
|
2414
|
-
},
|
|
2415
|
-
}),
|
|
2416
|
-
// INERT — the cells are computed numbers.
|
|
2417
|
-
Plan.series.table(OpsRow, {
|
|
2418
|
-
key: "table", title: "Despatch tonnes",
|
|
2419
|
-
match: r => r.series.equal("table"),
|
|
2420
|
-
label: r => r.label,
|
|
2421
|
-
cells: r => r.nums,
|
|
2422
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
2423
|
-
}),
|
|
2424
|
-
// RECEIVES — a dropped shift becomes a chip.
|
|
2425
|
-
Plan.series.cards(OpsRow, {
|
|
2426
|
-
key: "crew", title: "Crew shifts",
|
|
2427
|
-
match: r => r.series.equal("crew"),
|
|
2428
|
-
label: r => r.label,
|
|
2429
|
-
chips: r => r.shifts.map((_$, s) => Plan.chip({
|
|
2430
|
-
key: s.key, from: s.from, to: s.to, label: s.label, state: s.state,
|
|
2431
|
-
})),
|
|
2432
|
-
edit: {
|
|
2433
|
-
items: "shifts",
|
|
2434
|
-
create: (drop, r) => ({
|
|
2435
|
-
key: East.str `drop-${drop.from.key}-${East.print(r.shifts.size())}`,
|
|
2436
|
-
from: drop.at.unwrap("time"), to: drop.at.unwrap("time").addWeeks(2n),
|
|
2437
|
-
label: cardName.get(drop.from.key), state: ADDED,
|
|
2438
|
-
}),
|
|
2439
|
-
},
|
|
2440
|
-
}),
|
|
2441
|
-
// RECEIVES — a dropped milestone becomes a mark at
|
|
2442
|
-
// the instant, the one kind with no duration.
|
|
2443
|
-
Plan.series.events(OpsRow, {
|
|
2444
|
-
key: "strm", title: "Milestones",
|
|
2445
|
-
match: r => r.series.equal("strm"),
|
|
2446
|
-
label: r => r.label, id: true,
|
|
2447
|
-
marks: r => r.marks,
|
|
2448
|
-
edit: {
|
|
2449
|
-
items: "marks",
|
|
2450
|
-
create: (drop, r) => ({
|
|
2451
|
-
key: East.str `drop-${drop.from.key}-${East.print(r.marks.size())}`,
|
|
2452
|
-
at: drop.at, kind: variant("milestone", null), icon: none,
|
|
2453
|
-
label: some(cardName.get(drop.from.key)),
|
|
2454
|
-
}),
|
|
2455
|
-
},
|
|
2456
|
-
}),
|
|
2457
|
-
// The HEADER is inert; the span row under it is not.
|
|
2458
|
-
Plan.series.section(OpsRow, { key: "line3", title: "Line 3", meta: "span" }, [
|
|
2459
|
-
Plan.series.span(OpsRow, {
|
|
2460
|
-
key: "gmach", title: "Line 3 machine jobs",
|
|
2461
|
-
match: r => r.series.equal("gmach"),
|
|
2462
|
-
label: r => r.label, id: true,
|
|
2463
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
2464
|
-
key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
|
|
2465
|
-
})),
|
|
2466
|
-
edit: {
|
|
2467
|
-
items: "jobs",
|
|
2468
|
-
create: (drop, r) => ({
|
|
2469
|
-
key: East.str `drop-${drop.from.key}-${East.print(r.jobs.size())}`,
|
|
2470
|
-
label: cardName.get(drop.from.key),
|
|
2471
|
-
start: drop.at.unwrap("time"), end: drop.at.unwrap("time").addWeeks(2n),
|
|
2472
|
-
state: ADDED,
|
|
2473
|
-
}),
|
|
2474
|
-
},
|
|
2475
|
-
}),
|
|
2476
|
-
]),
|
|
2477
|
-
],
|
|
2478
|
-
// Shorter than its rows: the last ones start below the
|
|
2479
|
-
// fold, and a card held at the bottom edge scrolls there.
|
|
2480
|
-
style: { height: "300px" } }), _jsx(Text.MonoLabel, { children: East.str `LAST GESTURE · ${last}` })] }));
|
|
2481
|
-
} }))),
|
|
2482
|
-
inputs: [],
|
|
2483
|
-
});
|
|
2484
|
-
// ============================================================================
|
|
2485
|
-
// planFill — the bounded sizing isolate (#320 / #567 D1)
|
|
2486
|
-
// ============================================================================
|
|
2487
|
-
/** Fill (#320) — `height="fill"` resolves against the bounded Box and
|
|
2488
|
-
* virtualizes 200 rows of mixed kinds under 8 line strips. The bound must land
|
|
2489
|
-
* on the canvas WRAPPER: a percentage passed inward resolves against an
|
|
2490
|
-
* auto-height parent, computes to `auto`, and silently unbinds — the frame
|
|
2491
|
-
* reports bounded, renders its spacer, and never scrolls (#567 D1).
|
|
2492
|
-
*
|
|
2493
|
-
* The grouping is here for what it does to VIRTUALIZATION, not for the
|
|
2494
|
-
* chrome. Collapsing a strip removes its 25 children from the virtualizer's
|
|
2495
|
-
* item list, so `count` and the total size change while the scroll offset does
|
|
2496
|
-
* not — the case where an estimate that disagrees with the rendered height
|
|
2497
|
-
* shows up as drift or a jumping scrollbar. The strips also give the list
|
|
2498
|
-
* another row height again, so the `estimateSize` path is exercised rather
|
|
2499
|
-
* than a single constant. */
|
|
2500
|
-
export const planFill = example({
|
|
2501
|
-
keywords: ["Plan", "fill", "height", "maxHeight", "#320", "virtual", "virtualization", "bounded", "Box", "scroll", "sizing", "data", "series", "span", "chart", "heat", "spark", "sub", "two-line", "mixed", "variable", "estimateSize", "group", "groupToDicts", "grouping", "data step", "views", "interleave", "Plan.children", "collapse", "parent"],
|
|
2502
|
-
description: "Fill sizing over variable row heights — 200 virtualized rows of mixed kinds under 8 line strips",
|
|
2503
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
2504
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open).
|
|
2505
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
2506
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
2507
|
-
return w1.addWeeks(n.subtract(1n));
|
|
2508
|
-
}));
|
|
2509
|
-
const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
|
|
2510
|
-
const UnitRow = StructType({
|
|
2511
|
-
line: StringType, series: StringType,
|
|
2512
|
-
sub: OptionType(StringType),
|
|
2513
|
-
start: DateTimeType, end: DateTimeType, tonnes: FloatType,
|
|
2514
|
-
points: ArrayType(MeasureRow),
|
|
2515
|
-
cells: ArrayType(Plan.Types.HeatCell),
|
|
2516
|
-
});
|
|
2517
|
-
// 200 raw rows, generated in East — the row count is the point. The
|
|
2518
|
-
// KEYS sort as written (`UNIT-1000` … `UNIT-1199`) rather than
|
|
2519
|
-
// lexicographically (`UNIT-1`, `UNIT-10`, `UNIT-100`, …), and a line's
|
|
2520
|
-
// rows keep that order.
|
|
2521
|
-
//
|
|
2522
|
-
// `line` is the grouping level: 8 strips of 25, so a single collapse
|
|
2523
|
-
// takes an eighth of the list out of the virtualizer at once.
|
|
2524
|
-
//
|
|
2525
|
-
// Everything else here exists to make the row heights DISAGREE, which
|
|
2526
|
-
// is the case a virtualizer gets wrong. `series` cycles span / chart /
|
|
2527
|
-
// heat (32 / 32 / 28px), `sub` alternates so every other row floors at
|
|
2528
|
-
// the 42px two-line gutter, and the strips add another height again —
|
|
2529
|
-
// so consecutive estimates differ in both directions and no constant
|
|
2530
|
-
// can stand in for `estimateSize`.
|
|
2531
|
-
const units = $.const(East.Array.range(0n, 200n).toDict((_$, i) => East.str `UNIT-${East.print(i.add(1000n))}`, ($, i) => {
|
|
2532
|
-
const m = $.let(i.modulo(10n), IntegerType);
|
|
2533
|
-
return {
|
|
2534
|
-
line: East.str `LINE ${East.print(i.modulo(8n).add(1n))}`,
|
|
2535
|
-
// The kind cycles with the KEY, so every stretch of the canvas
|
|
2536
|
-
// holds the same mix — clustering the tall rows at one end
|
|
2537
|
-
// would leave most of the scroll a single height again.
|
|
2538
|
-
series: m.equal(4n).ifElse(() => "heat", () => m.equal(5n).ifElse(() => "spark", () => m.equal(6n).ifElse(() => "chartM", () => m.equal(7n).ifElse(() => "chartL", () => m.equal(8n).ifElse(() => "chartXL", () => "span"))))),
|
|
2539
|
-
// Alternating one-line / two-line gutters.
|
|
2540
|
-
sub: i.modulo(2n).equal(0n).ifElse(() => some(East.str `cap ${East.print(i.modulo(9n).add(40n))} t`), () => $.const(none, OptionType(StringType))),
|
|
2541
|
-
start: week(i.modulo(9n).add(27n)),
|
|
2542
|
-
end: week(i.modulo(9n).add(30n)),
|
|
2543
|
-
tonnes: i.toFloat().multiply(1.5).add(40.0),
|
|
2544
|
-
points: East.Array.generate(6n, MeasureRow, (_$, j) => ({
|
|
2545
|
-
week: week(j.multiply(2n).add(27n)),
|
|
2546
|
-
pct: j.multiply(17n).add(i).remainder(60n).toFloat().add(40.0),
|
|
2547
|
-
})),
|
|
2548
|
-
cells: East.Array.generate(6n, Plan.Types.HeatCell, (_$, j) => ({
|
|
2549
|
-
at: Plan.at.time(week(j.multiply(2n).add(27n))),
|
|
2550
|
-
value: some(j.multiply(13n).add(i).remainder(100n).toFloat()),
|
|
2551
|
-
label: none,
|
|
2552
|
-
})),
|
|
2553
|
-
};
|
|
2554
|
-
}), DictType(StringType, UnitRow));
|
|
2555
|
-
// Grouping is a DATA step (#822): one `groupToDicts` makes each line an
|
|
2556
|
-
// entry holding its 25 units.
|
|
2557
|
-
const lines = $.let(units.groupToDicts(($, u) => u.line, ($, _u, k) => k));
|
|
2558
|
-
const LineGroup = DictType(StringType, UnitRow);
|
|
2559
|
-
// ONE strip per line, its units stepped down into. A `views` series
|
|
2560
|
-
// gives each unit the ONE row its kind's member draws, so within a
|
|
2561
|
-
// strip the kinds interleave in the units' order — a block per kind
|
|
2562
|
-
// would bank the tall rows together again.
|
|
2563
|
-
const series = $.const([
|
|
2564
|
-
Plan.series.group(LineGroup, {
|
|
2565
|
-
key: "lines", title: "Lines",
|
|
2566
|
-
label: (_g, line) => line,
|
|
2567
|
-
children: Plan.children((g) => g, [
|
|
2568
|
-
Plan.series.views(UnitRow, { key: "units", title: "Units" }, [
|
|
2569
|
-
Plan.series.span(UnitRow, {
|
|
2570
|
-
key: "unit-span", title: "Span",
|
|
2571
|
-
match: r => r.series.equal("span"),
|
|
2572
|
-
label: (_r, k) => k, id: true, sub: r => r.sub,
|
|
2573
|
-
runs: (r, k) => [Plan.run({
|
|
2574
|
-
key: k, start: r.start, end: r.end,
|
|
2575
|
-
label: East.str `RUN · ${k}`,
|
|
2576
|
-
quantity: Plan.quantity(r.tonnes, { unit: "t", format: Format.Number({ maximumFractionDigits: 0n }) }),
|
|
2577
|
-
state: variant("confirmed", null),
|
|
2578
|
-
})],
|
|
2579
|
-
}),
|
|
2580
|
-
// Heat rows are 28px — shorter than everything around them.
|
|
2581
|
-
Plan.series.heat(UnitRow, {
|
|
2582
|
-
key: "unit-heat", title: "Heat",
|
|
2583
|
-
match: r => r.series.equal("heat"),
|
|
2584
|
-
label: (_r, k) => k, id: true, sub: r => r.sub,
|
|
2585
|
-
cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
|
|
2586
|
-
}),
|
|
2587
|
-
// Four chart heights. `height` is a SERIES declaration,
|
|
2588
|
-
// not a per-row accessor, so distinct heights mean
|
|
2589
|
-
// distinct members — which is the point here: a spark,
|
|
2590
|
-
// then three EXPANDED rows several times taller,
|
|
2591
|
-
// scattered through the same unit order.
|
|
2592
|
-
Plan.series.chart(UnitRow, {
|
|
2593
|
-
key: "unit-spark", title: "Spark",
|
|
2594
|
-
match: r => r.series.equal("spark"),
|
|
2595
|
-
label: (_r, k) => k, id: true, sub: r => r.sub,
|
|
2596
|
-
height: "spark", expandable: true,
|
|
2597
|
-
layers: r => [Chart.Line(r.points, { x: p => p.week, y: p => p.pct })],
|
|
2598
|
-
}),
|
|
2599
|
-
Plan.series.chart(UnitRow, {
|
|
2600
|
-
key: "unit-chart-m", title: "Chart · medium",
|
|
2601
|
-
match: r => r.series.equal("chartM"),
|
|
2602
|
-
label: (_r, k) => k, id: true, sub: r => r.sub,
|
|
2603
|
-
height: Plan.fixed("72px"),
|
|
2604
|
-
layers: r => [Chart.Line(r.points, { x: p => p.week, y: p => p.pct })],
|
|
2605
|
-
}),
|
|
2606
|
-
Plan.series.chart(UnitRow, {
|
|
2607
|
-
key: "unit-chart-l", title: "Chart · large",
|
|
2608
|
-
match: r => r.series.equal("chartL"),
|
|
2609
|
-
label: (_r, k) => k, id: true, sub: r => r.sub,
|
|
2610
|
-
height: "expanded",
|
|
2611
|
-
layers: r => [Chart.Area(r.points, { x: p => p.week, y: p => p.pct })],
|
|
2612
|
-
}),
|
|
2613
|
-
Plan.series.chart(UnitRow, {
|
|
2614
|
-
key: "unit-chart-xl", title: "Chart · x-large",
|
|
2615
|
-
match: r => r.series.equal("chartXL"),
|
|
2616
|
-
label: (_r, k) => k, id: true, sub: r => r.sub,
|
|
2617
|
-
height: "expanded", expandedHeight: "140px",
|
|
2618
|
-
layers: r => [Chart.Column(r.points, { x: p => p.week, y: p => p.pct })],
|
|
2619
|
-
}),
|
|
2620
|
-
]),
|
|
2621
|
-
]),
|
|
2622
|
-
}),
|
|
2623
|
-
], ArrayType(Plan.Types.Series(LineGroup)));
|
|
2624
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
2625
|
-
return (_jsx(Box, { height: "240px", children: _jsx(Plan, { axis: axis, data: lines, series: series, style: { height: "fill" } }) }));
|
|
2626
|
-
}),
|
|
2627
|
-
inputs: [],
|
|
2628
|
-
});
|
|
2629
|
-
// ============================================================================
|
|
2630
|
-
// planReview — the review chrome, and what a verdict is FOR (#569)
|
|
2631
|
-
// ============================================================================
|
|
2632
|
-
/**
|
|
2633
|
-
* Review as drafts (#880): a verdict is a FIELD of the record the reviewer
|
|
2634
|
-
* decides about — `approval`, an `ApprovalStateType` — and the series whose
|
|
2635
|
-
* rows are reviewed names it (`review: { verdict: "approval" }`). Approve or
|
|
2636
|
-
* Reject on a row, and Approve all / Reject all at the foot, draft the entries
|
|
2637
|
-
* with the field changed: the canvas derives the drafted rows again at once —
|
|
2638
|
-
* buttons, bar, dot and run — marked pending, the history bar undoes, redoes
|
|
2639
|
-
* and discards them, and Apply writes them back through the live handle as one
|
|
2640
|
-
* checked batch. The foot's summary is the HOST's, over what it holds: the
|
|
2641
|
-
* saved state, which moves only when Apply lands.
|
|
2642
|
-
*/
|
|
2643
|
-
export const planReview = example({
|
|
2644
|
-
keywords: [
|
|
2645
|
-
"Plan", "review", "approval", "approve", "reject", "verdict", "decision", "ApprovalStateType",
|
|
2646
|
-
"deriveApproval", "flagged", "chrome", "batch", "foot", "Approve all", "Reject all", "onRerun",
|
|
2647
|
-
"editing", "onUpdate", "draft", "pending", "Apply", "undo", "redo", "discard", "Reactive", "State", "bind",
|
|
2648
|
-
"live handle", "derived", "accessor", "data", "row id", "#880",
|
|
2649
|
-
],
|
|
2650
|
-
description: "Review as drafts — a verdict drafts the entry's `approval` field, the canvas re-derives buttons, bar and dot from the draft, and Apply writes the batch back through the live handle",
|
|
2651
|
-
fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
|
|
2652
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
2653
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
2654
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
2655
|
-
return w1.addWeeks(n.subtract(1n));
|
|
2656
|
-
}));
|
|
2657
|
-
// The verdict IS a field of the job — what the reviewer decides
|
|
2658
|
-
// about it — so a verdict drafts the job with `approval` changed,
|
|
2659
|
-
// and Apply writes the job back. Nothing is held anywhere else.
|
|
2660
|
-
const JobRow = StructType({
|
|
2661
|
-
start: DateTimeType, end: DateTimeType, tonnes: FloatType, flagged: BooleanType,
|
|
2662
|
-
approval: ApprovalStateType,
|
|
2663
|
-
});
|
|
2664
|
-
// A LIVE handle: the canvas reads it, and Apply writes it.
|
|
2665
|
-
const jobs = $.let(State.bind([DictType(StringType, JobRow)], "plan_review_jobs", new Map([
|
|
2666
|
-
["L1-M03", { start: week(28n), end: week(31n), tonnes: 96.0, flagged: true, approval: variant("pending", null) }],
|
|
2667
|
-
["L1-M04", { start: week(29n), end: week(33n), tonnes: 112.0, flagged: true, approval: variant("approved", null) }],
|
|
2668
|
-
["L1-M07", { start: week(30n), end: week(34n), tonnes: 64.0, flagged: true, approval: variant("rejected", null) }],
|
|
2669
|
-
["L2-M11", { start: week(27n), end: week(30n), tonnes: 88.0, flagged: false, approval: variant("approved", null) }],
|
|
2670
|
-
])));
|
|
2671
|
-
// Rerun is not a verdict — it asks the host for a fresh proposal,
|
|
2672
|
-
// so it stays a callback and writes the SOURCE: here every job
|
|
2673
|
-
// returns to what its flag derives ("clean rests pre-approved,
|
|
2674
|
-
// flagged awaits an explicit call"). Drafts made before it sit over
|
|
2675
|
-
// a source that moved, and say so.
|
|
2676
|
-
const onRerun = $.const(East.function([], NullType, ($) => {
|
|
2677
|
-
const fresh = $.let(jobs.read().map((_$, j) => ({
|
|
2678
|
-
start: j.start, end: j.end, tonnes: j.tonnes, flagged: j.flagged,
|
|
2679
|
-
approval: deriveApproval(j.flagged).unwrap("some"),
|
|
2680
|
-
})));
|
|
2681
|
-
$(jobs.write(fresh));
|
|
2682
|
-
}));
|
|
2683
|
-
const series = $.const([
|
|
2684
|
-
Plan.series.span(JobRow, {
|
|
2685
|
-
key: "jobs", title: "Jobs",
|
|
2686
|
-
label: (_r, k) => k, id: true,
|
|
2687
|
-
value: r => some(East.str `${East.Float.printFixed(r.tonnes, 0n)} t`),
|
|
2688
|
-
// The verdict's FIELD: the decision buttons show it, and a
|
|
2689
|
-
// verdict drafts the job with it changed.
|
|
2690
|
-
review: { verdict: "approval" },
|
|
2691
|
-
// ...and the SAME field drives the bar and the dot, because
|
|
2692
|
-
// appearance is derived like everything else. Click Approve
|
|
2693
|
-
// and the draft repaints — the chrome never touched it.
|
|
2694
|
-
status: r => r.approval.hasTag("rejected").ifElse(() => some(variant("danger", null)), () => r.flagged.and(_$ => r.approval.hasTag("pending")).ifElse(() => some(variant("warning", null)), () => none)),
|
|
2695
|
-
runs: (r, k) => [Plan.run({
|
|
2696
|
-
key: k, start: r.start, end: r.end,
|
|
2697
|
-
label: East.str `RUN · ${k}`,
|
|
2698
|
-
quantity: Plan.quantity(r.tonnes, { unit: "t", format: Format.Number({ maximumFractionDigits: 0n }) }),
|
|
2699
|
-
state: r.approval.hasTag("approved").ifElse(() => variant("confirmed", null), () => r.approval.hasTag("rejected").ifElse(() => variant("rejected", null), () => variant("proposed", variant("recommended", null)))),
|
|
2700
|
-
})],
|
|
2701
|
-
}),
|
|
2702
|
-
], ArrayType(Plan.Types.Series(JobRow)));
|
|
2703
|
-
const axis = $.const(Plan.axis({
|
|
2704
|
-
window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n),
|
|
2705
|
-
}));
|
|
2706
|
-
// The host's summary reads what it holds — the saved jobs.
|
|
2707
|
-
const saved = $.let(jobs.read());
|
|
2708
|
-
const pending = $.let(saved.filter((_$, j) => j.approval.hasTag("pending")).size());
|
|
2709
|
-
const rejected = $.let(saved.filter((_$, j) => j.approval.hasTag("rejected")).size());
|
|
2710
|
-
return (_jsx(Plan, { axis: axis, data: jobs, series: series, review: {
|
|
2711
|
-
summary: _jsx(Text, { children: East.str `SAVED · ${East.Float.printFixed(pending.toFloat(), 0n)} PENDING · ${East.Float.printFixed(rejected.toFloat(), 0n)} REJECTED` }),
|
|
2712
|
-
onRerun,
|
|
2713
|
-
}, editing: { onUpdate: jobs.write } }));
|
|
2714
|
-
} }))),
|
|
2715
|
-
inputs: [],
|
|
2716
|
-
});
|
|
2717
|
-
// ============================================================================
|
|
2718
|
-
// planEditing — every change a draft, one checked Apply (#880, #825)
|
|
2719
|
-
// ============================================================================
|
|
2720
|
-
/**
|
|
2721
|
-
* The Plan's editing session (#880) — the Sheet's, over the canvas's entries.
|
|
2722
|
-
*
|
|
2723
|
-
* The source holds LINES, each holding its machines. A machine is reviewed
|
|
2724
|
-
* (its `approval` field), receives dropped jobs (its `jobs` list) and has its
|
|
2725
|
-
* jobs moved and resized, and every gesture lands on the MACHINE rows, one
|
|
2726
|
-
* level down — yet each drafts the LINE, the source's top-level entry, which
|
|
2727
|
-
* the whole subtree rides in:
|
|
2728
|
-
*
|
|
2729
|
-
* - Approve / Reject on a machine, and Approve all / Reject all at the foot,
|
|
2730
|
-
* draft its `approval` (`review: { verdict: "approval" }` on the machines).
|
|
2731
|
-
* - A job card dropped on a machine drafts a new job in its `jobs` at the
|
|
2732
|
-
* bucket's instant (`edit: { items: "jobs", create }`).
|
|
2733
|
-
* - A job's run moves along its machine or onto another machine, and resizes
|
|
2734
|
-
* by either end (#825): the series names the job's key and the instant
|
|
2735
|
-
* fields a move writes (`key`, `start`, `end`). It moves in whole weeks, or
|
|
2736
|
-
* in days with Shift held. A move onto a machine of the other line takes
|
|
2737
|
-
* the job out of one line and puts it into the other, as one gesture over
|
|
2738
|
-
* both. From the keyboard, Space on a focused run picks it up: ←/→ move it
|
|
2739
|
-
* a week, Shift+←/→ its end, Alt+←/→ its start, ↑/↓ carry it to another
|
|
2740
|
-
* machine, and Space drops it.
|
|
2741
|
-
*
|
|
2742
|
-
* Every gesture is one transaction, drawn at once with the pending mark; the
|
|
2743
|
-
* toolbar's history bar undoes, redoes and discards it (so do ⌘Z and ⌘⇧Z).
|
|
2744
|
-
* `ready` is the author's check over a drafted line: a machine holding more
|
|
2745
|
-
* than four jobs is refused, by name, and Apply waits until it is fixed — M11
|
|
2746
|
-
* already holds four, so a job moved onto it holds Apply until one leaves.
|
|
2747
|
-
* Apply writes the batch back through the live handle (`onUpdate`), and
|
|
2748
|
-
* `onPatch` hears each gesture as it is made.
|
|
2749
|
-
*/
|
|
2750
|
-
export const planEditing = example({
|
|
2751
|
-
keywords: [
|
|
2752
|
-
"Plan", "editing", "session", "draft", "drafts", "transaction", "onUpdate", "onPatch", "ready",
|
|
2753
|
-
"Readiness", "Editing.Types.Readiness", "invalid", "review", "verdict", "approval", "ApprovalStateType",
|
|
2754
|
-
"Approve all", "Reject all", "edit", "items", "create", "drop", "Library", "DnD", "id", "sources",
|
|
2755
|
-
"undo", "redo", "discard", "Apply", "history bar", "pending", "nested", "children", "Plan.children",
|
|
2756
|
-
"top-level entry", "live handle", "Plan.Types.PatchEvent", "Reactive", "State", "bind", "#880",
|
|
2757
|
-
"move", "resize", "drag", "run", "key", "start", "end", "Shift", "snap", "keyboard", "Space",
|
|
2758
|
-
"cross-row", "Plan.Types.Move", "#825",
|
|
2759
|
-
],
|
|
2760
|
-
description: "The Plan's editing session — verdicts, dropped jobs and moved or resized runs on nested machine rows draft their line (both lines when a job changes line), the history bar undoes and applies them, and `ready` refuses a crowded machine",
|
|
2761
|
-
fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
|
|
2762
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
2763
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
2764
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
2765
|
-
return w1.addWeeks(n.subtract(1n));
|
|
2766
|
-
}));
|
|
2767
|
-
const Job = StructType({
|
|
2768
|
-
key: StringType, label: StringType, start: DateTimeType, end: DateTimeType, state: EventStateType,
|
|
2769
|
-
});
|
|
2770
|
-
const Machine = StructType({ approval: ApprovalStateType, jobs: ArrayType(Job) });
|
|
2771
|
-
const Line = StructType({ name: StringType, machines: DictType(StringType, Machine) });
|
|
2772
|
-
const CONFIRMED = variant("confirmed", null);
|
|
2773
|
-
const RECOMMENDED = variant("proposed", variant("recommended", null));
|
|
2774
|
-
// What a drop creates is a proposal the host has not committed.
|
|
2775
|
-
const ADDED = variant("proposed", variant("added", null));
|
|
2776
|
-
// A LIVE handle over the lines: the canvas reads it, and Apply
|
|
2777
|
-
// writes it. M11 already holds four jobs, so one more is refused.
|
|
2778
|
-
const lines = $.let(State.bind([DictType(StringType, Line)], "ex.plan.editing.lines", new Map([
|
|
2779
|
-
["L1", { name: "Line 1", machines: new Map([
|
|
2780
|
-
["M03", { approval: variant("pending", null), jobs: [
|
|
2781
|
-
{ key: "b214", label: "B-214", start: week(28n), end: week(31n), state: RECOMMENDED },
|
|
2782
|
-
] }],
|
|
2783
|
-
["M04", { approval: variant("approved", null), jobs: [
|
|
2784
|
-
{ key: "b208", label: "B-208", start: week(27n), end: week(30n), state: CONFIRMED },
|
|
2785
|
-
{ key: "b219", label: "B-219", start: week(31n), end: week(34n), state: RECOMMENDED },
|
|
2786
|
-
] }],
|
|
2787
|
-
]) }],
|
|
2788
|
-
["L2", { name: "Line 2", machines: new Map([
|
|
2789
|
-
["M11", { approval: variant("pending", null), jobs: [
|
|
2790
|
-
{ key: "b241", label: "B-241", start: week(27n), end: week(29n), state: CONFIRMED },
|
|
2791
|
-
{ key: "b244", label: "B-244", start: week(29n), end: week(31n), state: RECOMMENDED },
|
|
2792
|
-
{ key: "b247", label: "B-247", start: week(31n), end: week(33n), state: RECOMMENDED },
|
|
2793
|
-
{ key: "b250", label: "B-250", start: week(33n), end: week(35n), state: RECOMMENDED },
|
|
2794
|
-
] }],
|
|
2795
|
-
]) }],
|
|
2796
|
-
])));
|
|
2797
|
-
// The palette the jobs come from.
|
|
2798
|
-
const CardRow = StructType({ key: StringType, name: StringType, note: StringType, icon: StringType });
|
|
2799
|
-
const cards = $.const([
|
|
2800
|
-
{ key: "weld", name: "Weld run", note: "two weeks", icon: "gear" },
|
|
2801
|
-
{ key: "cure", name: "Cure run", note: "two weeks", icon: "fire" },
|
|
2802
|
-
], ArrayType(CardRow));
|
|
2803
|
-
const cardName = $.const(cards.toDict((_$, c) => c.key, (_$, c) => c.name));
|
|
2804
|
-
// The author's check over one drafted LINE — every check of the
|
|
2805
|
-
// batch runs in one call. A refusal names the machine.
|
|
2806
|
-
const ready = $.const(East.function([Line, StringType], Editing.Types.Readiness, ($, line, _key) => {
|
|
2807
|
-
const crowded = $.let(line.machines.filter((_$, m) => m.jobs.size().greater(4n)));
|
|
2808
|
-
const result = $.let(variant("ready", null), Editing.Types.Readiness);
|
|
2809
|
-
$.if(crowded.size().greater(0n), ($) => {
|
|
2810
|
-
$.assign(result, variant("invalid", crowded.toArray((_$, m, k) => ({
|
|
2811
|
-
field: "jobs", message: East.str `${k} holds ${East.print(m.jobs.size())} jobs — at most 4`,
|
|
2812
|
-
}))));
|
|
2813
|
-
});
|
|
2814
|
-
return result;
|
|
2815
|
-
}));
|
|
2816
|
-
// Every gesture, as it is made — a verdict, a drop, an undo.
|
|
2817
|
-
const lastBind = $.let(State.bind([StringType], "ex.plan.editing.last", "none yet"));
|
|
2818
|
-
const onPatch = $.const(East.function([Plan.Types.PatchEvent(Line)], NullType, ($, event) => {
|
|
2819
|
-
$(lastBind.write(East.str `${event.origin.getTag()} · ${event.label}`));
|
|
2820
|
-
}));
|
|
2821
|
-
const last = $.let(lastBind.read());
|
|
2822
|
-
// The host's summary reads what it holds — the saved lines.
|
|
2823
|
-
const saved = $.let(lines.read());
|
|
2824
|
-
const pending = $.let(saved.toArray((_$, l) => l.machines.filter((_$, m) => m.approval.hasTag("pending")).size()).sum());
|
|
2825
|
-
const axis = $.const(Plan.axis({
|
|
2826
|
-
window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n),
|
|
2827
|
-
}));
|
|
2828
|
-
return (_jsxs(VStack, { gap: "4", align: "stretch", children: [_jsx(Library, { id: "plan-editing-jobs", data: cards, item: c => ({ key: c.key, label: c.name, sublabel: c.note, icon: c.icon }) }), _jsx(Plan, { axis: axis, data: lines, id: "plan-editing", sources: ["plan-editing-jobs"], series: [
|
|
2829
|
-
// One row per line, its machines stepped down into
|
|
2830
|
-
// through a plain field — which is what lets a
|
|
2831
|
-
// gesture on a machine write back into its line.
|
|
2832
|
-
Plan.series.span(Line, {
|
|
2833
|
-
key: "lines", title: "Lines",
|
|
2834
|
-
label: l => l.name,
|
|
2835
|
-
runs: _l => [],
|
|
2836
|
-
rollup: "union",
|
|
2837
|
-
children: Plan.children(l => l.machines, [
|
|
2838
|
-
Plan.series.span(Machine, {
|
|
2839
|
-
key: "machines", title: "Machines",
|
|
2840
|
-
label: (_m, k) => k, id: true,
|
|
2841
|
-
review: { verdict: "approval" },
|
|
2842
|
-
status: m => m.approval.hasTag("rejected").ifElse(() => some(variant("danger", null)), () => none),
|
|
2843
|
-
runs: m => m.jobs.map((_$, j) => Plan.run({
|
|
2844
|
-
key: j.key, start: j.start, end: j.end,
|
|
2845
|
-
label: East.str `RUN · ${j.label}`, state: j.state,
|
|
2846
|
-
})),
|
|
2847
|
-
edit: {
|
|
2848
|
-
items: "jobs",
|
|
2849
|
-
// A run moves and resizes: the job is
|
|
2850
|
-
// found by `key` — the run's own key —
|
|
2851
|
-
// and a move writes `start` and `end`.
|
|
2852
|
-
// Keys stay unique on a machine: a drop
|
|
2853
|
-
// or a move that would repeat one is
|
|
2854
|
-
// refused, so the new job's key names
|
|
2855
|
-
// its card, bucket and place.
|
|
2856
|
-
key: "key", start: "start", end: "end",
|
|
2857
|
-
create: (drop, m) => ({
|
|
2858
|
-
key: East.str `${drop.from.key}-${East.print(drop.at.unwrap("time"))}-${East.print(m.jobs.size())}`,
|
|
2859
|
-
label: cardName.get(drop.from.key),
|
|
2860
|
-
start: drop.at.unwrap("time"), end: drop.at.unwrap("time").addWeeks(2n),
|
|
2861
|
-
state: ADDED,
|
|
2862
|
-
}),
|
|
2863
|
-
},
|
|
2864
|
-
}),
|
|
2865
|
-
]),
|
|
2866
|
-
}),
|
|
2867
|
-
], review: {
|
|
2868
|
-
summary: _jsx(Text, { children: East.str `SAVED · ${East.print(pending)} PENDING` }),
|
|
2869
|
-
}, editing: { onUpdate: lines.write, onPatch, ready }, style: { height: "360px" } }), _jsx(Text.MonoLabel, { children: East.str `LAST GESTURE · ${last}` })] }));
|
|
2870
|
-
} }))),
|
|
2871
|
-
inputs: [],
|
|
2872
|
-
});
|
|
2873
|
-
// ============================================================================
|
|
2874
|
-
// planUiState — the interaction state, held by the host (#824)
|
|
2875
|
-
// ============================================================================
|
|
2876
|
-
/**
|
|
2877
|
-
* A bound `ui` state (#824): the canvas's selection, the rows folded or opened
|
|
2878
|
-
* against what they declare, the expanded charts, and a row to bring into
|
|
2879
|
-
* view — held by the HOST, in `State.bind` at `Plan.Types.UiState`, seeded by
|
|
2880
|
-
* `Plan.uiState(…)`. The canvas reads it and writes the user's actions back;
|
|
2881
|
-
* anything else may write it too. Here a picker beside the canvas brings a
|
|
2882
|
-
* machine into view (`focus` — the canvas opens the line it sits in, scrolls
|
|
2883
|
-
* to it and spends the request) and selects it, three buttons fold, open and
|
|
2884
|
-
* expand from outside, and a readout says what the state holds — including
|
|
2885
|
-
* whatever the user clicked on the canvas itself.
|
|
2886
|
-
*/
|
|
2887
|
-
export const planUiState = example({
|
|
2888
|
-
keywords: [
|
|
2889
|
-
"Plan", "ui", "UiState", "Plan.uiState", "state", "bound", "State", "bind", "controlled",
|
|
2890
|
-
"selected", "selection", "collapsed", "expanded", "fold", "open", "charts", "focus",
|
|
2891
|
-
"scroll to row", "deep link", "external", "write back", "Reactive", "Select", "Button", "#824",
|
|
2892
|
-
],
|
|
2893
|
-
description: "A bound ui state — the host selects a machine and brings it into view, folds and opens lines and expands a chart from outside, and reads back what the user did on the canvas",
|
|
2894
|
-
fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
|
|
2895
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
2896
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
2897
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
2898
|
-
return w1.addWeeks(n.subtract(1n));
|
|
2899
|
-
}));
|
|
2900
|
-
const JobRow = StructType({
|
|
2901
|
-
key: StringType, start: DateTimeType, end: DateTimeType, state: EventStateType,
|
|
2902
|
-
});
|
|
2903
|
-
const MachineRow = StructType({ line: StringType, jobs: ArrayType(JobRow) });
|
|
2904
|
-
const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
|
|
2905
|
-
const machines = $.const(new Map([
|
|
2906
|
-
["L1-M03", { line: "Line 1", jobs: [{ key: "b214", start: week(28n), end: week(31n), state: variant("in-progress", null) }] }],
|
|
2907
|
-
["L1-M04", { line: "Line 1", jobs: [{ key: "b208", start: week(27n), end: week(30n), state: variant("actual", null) }] }],
|
|
2908
|
-
["L2-M11", { line: "Line 2", jobs: [{ key: "b241", start: week(29n), end: week(33n), state: variant("confirmed", null) }] }],
|
|
2909
|
-
["L2-M12", { line: "Line 2", jobs: [{ key: "b198", start: week(30n), end: week(34n), state: variant("proposed", variant("recommended", null)) }] }],
|
|
2910
|
-
["L3-M21", { line: "Line 3", jobs: [{ key: "b301", start: week(31n), end: week(35n), state: variant("confirmed", null) }] }],
|
|
2911
|
-
["L3-M22", { line: "Line 3", jobs: [{ key: "b302", start: week(33n), end: week(37n), state: variant("proposed", variant("recommended", null)) }] }],
|
|
2912
|
-
]), DictType(StringType, MachineRow));
|
|
2913
|
-
const lines = $.let(machines.groupToDicts(($, m) => m.line, ($, _m, k) => k));
|
|
2914
|
-
const LineGroup = DictType(StringType, MachineRow);
|
|
2915
|
-
const coverage = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({
|
|
2916
|
-
week: week(i.add(27n)), pct: i.multiply(13n).remainder(9n).toFloat().add(88.0),
|
|
2917
|
-
})));
|
|
2918
|
-
const series = $.const([
|
|
2919
|
-
Plan.series.rows(LineGroup, { key: "kpi", title: "Coverage" }, [
|
|
2920
|
-
Plan.chart({
|
|
2921
|
-
key: "coverage", label: "Coverage", id: true, height: "spark", expandable: true,
|
|
2922
|
-
layers: [Chart.Line(coverage, { x: p => p.week, y: p => p.pct })],
|
|
2923
|
-
}),
|
|
2924
|
-
]),
|
|
2925
|
-
Plan.series.group(LineGroup, {
|
|
2926
|
-
key: "lines", title: "Lines",
|
|
2927
|
-
label: (_g, line) => line,
|
|
2928
|
-
children: Plan.children((g) => g, [
|
|
2929
|
-
Plan.series.span(MachineRow, {
|
|
2930
|
-
key: "machines", title: "Machines",
|
|
2931
|
-
label: (_m, k) => k, id: true,
|
|
2932
|
-
runs: m => m.jobs.map((_$, j) => Plan.run({
|
|
2933
|
-
key: j.key, start: j.start, end: j.end, label: East.str `RUN · ${j.key}`, state: j.state,
|
|
2934
|
-
})),
|
|
2935
|
-
}),
|
|
2936
|
-
]),
|
|
2937
|
-
}),
|
|
2938
|
-
], ArrayType(Plan.Types.Series(LineGroup)));
|
|
2939
|
-
// The STATE — Line 3 starts folded. Every row is named by its id:
|
|
2940
|
-
// a line is `Plan.ref("lines", line)`, a machine
|
|
2941
|
-
// `Plan.ref("machines", line, machine)`.
|
|
2942
|
-
const ui = $.let(State.bind([Plan.Types.UiState], "ex.plan.ui", Plan.uiState({ collapsed: [Plan.ref("lines", "Line 3")] })));
|
|
2943
|
-
const now = $.let(ui.read());
|
|
2944
|
-
const lineIds = $.const([Plan.ref("lines", "Line 1"), Plan.ref("lines", "Line 2"), Plan.ref("lines", "Line 3")], ArrayType(Plan.Types.RowId));
|
|
2945
|
-
const kpi = $.const(Plan.ref("kpi", "coverage"));
|
|
2946
|
-
// Bring a machine into view: select it, and REQUEST its focus — the
|
|
2947
|
-
// canvas opens its line if it is folded, scrolls to it, and clears
|
|
2948
|
-
// the request once it has.
|
|
2949
|
-
const goTo = $.const(East.function([StringType], NullType, ($, key) => {
|
|
2950
|
-
const s = $.let(ui.read());
|
|
2951
|
-
const id = $.let(Plan.ref("machines", machines.get(key).line, key));
|
|
2952
|
-
$(ui.write(East.value({
|
|
2953
|
-
selected: some(id), collapsed: s.collapsed, expanded: s.expanded, charts: s.charts, focus: some(id),
|
|
2954
|
-
}, Plan.Types.UiState)));
|
|
2955
|
-
}));
|
|
2956
|
-
const foldAll = $.const(East.function([], NullType, ($) => {
|
|
2957
|
-
const s = $.let(ui.read());
|
|
2958
|
-
$(ui.write(East.value({
|
|
2959
|
-
selected: s.selected, collapsed: lineIds, expanded: [], charts: s.charts, focus: s.focus,
|
|
2960
|
-
}, Plan.Types.UiState)));
|
|
2961
|
-
}));
|
|
2962
|
-
const openAll = $.const(East.function([], NullType, ($) => {
|
|
2963
|
-
const s = $.let(ui.read());
|
|
2964
|
-
$(ui.write(East.value({
|
|
2965
|
-
selected: s.selected, collapsed: [], expanded: lineIds, charts: s.charts, focus: s.focus,
|
|
2966
|
-
}, Plan.Types.UiState)));
|
|
2967
|
-
}));
|
|
2968
|
-
const noCharts = $.const([], ArrayType(Plan.Types.RowId));
|
|
2969
|
-
const kpiChart = $.const([kpi], ArrayType(Plan.Types.RowId));
|
|
2970
|
-
const chart = $.const(East.function([], NullType, ($) => {
|
|
2971
|
-
const s = $.let(ui.read());
|
|
2972
|
-
const charts = $.let(s.charts.size().greater(0n).ifElse(() => noCharts, () => kpiChart));
|
|
2973
|
-
$(ui.write(East.value({
|
|
2974
|
-
selected: s.selected, collapsed: s.collapsed, expanded: s.expanded, charts, focus: s.focus,
|
|
2975
|
-
}, Plan.Types.UiState)));
|
|
2976
|
-
}));
|
|
2977
|
-
const keys = $.const(["L1-M03", "L1-M04", "L2-M11", "L2-M12", "L3-M21", "L3-M22"], ArrayType(StringType));
|
|
2978
|
-
const picked = $.let(now.selected.match({
|
|
2979
|
-
some: (_$, id) => East.print(id),
|
|
2980
|
-
none: (_$) => "nothing",
|
|
2981
|
-
}), StringType);
|
|
2982
|
-
const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
|
|
2983
|
-
return (_jsxs(VStack, { gap: "2", align: "stretch", children: [_jsxs(HStack, { gap: "2", children: [_jsx(Select, { value: "L3-M22", onChange: goTo, size: "sm", items: keys.map((_$, k) => Select.Item(k, East.str `Go to ${k}`)) }), _jsx(Button, { size: "xs", onClick: foldAll, children: "Fold lines" }), _jsx(Button, { size: "xs", onClick: openAll, children: "Open lines" }), _jsx(Button, { size: "xs", onClick: chart, children: "Coverage chart" })] }), _jsx(Plan, { axis: axis, data: lines, series: series, ui: ui, style: { height: "300px" } }), _jsx(Text.MonoLabel, { children: East.str `SELECTED · ${picked} · ${East.print(now.collapsed.size())} FOLDED · ${East.print(now.expanded.size())} OPENED` })] }));
|
|
2984
|
-
} }))),
|
|
2985
|
-
inputs: [],
|
|
2986
|
-
});
|
|
2987
|
-
/**
|
|
2988
|
-
* R2 expand-in-place — the one example that shows the whole gesture.
|
|
2989
|
-
*
|
|
2990
|
-
* Expand is two halves and needs BOTH: a per-row `expand` declaration (data,
|
|
2991
|
-
* so it flows through an accessor like every other envelope field) and the
|
|
2992
|
-
* ROOT's `expandRender` resolver, which builds the mounted body from the row's
|
|
2993
|
-
* id. Neither alone shows the control.
|
|
2994
|
-
*
|
|
2995
|
-
* The rows here are deliberately heterogeneous — span, chart, heat, table,
|
|
2996
|
-
* events — because focusing one is what makes the other five collapse, and
|
|
2997
|
-
* each row KIND collapses differently (#591): geometry shrinks, values
|
|
2998
|
-
* re-encode as a tone strip, shapes keep their silhouette.
|
|
2999
|
-
*/
|
|
3000
|
-
export const planExpand = example({
|
|
3001
|
-
keywords: ["Plan", "expand", "expandRender", "expandGutter", "axis", "keep", "dim", "off", "focus", "R2", "collapse", "context strip", "row", "row id", "data", "series", "chart", "heat", "table", "events", "span", "raw"],
|
|
3002
|
-
description: "Expand-in-place — a row declares `expand`, the root renders plot and gutter, and unfocused rows collapse to strips",
|
|
3003
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
3004
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
3005
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
3006
|
-
return w1.addWeeks(n.subtract(1n));
|
|
3007
|
-
}));
|
|
3008
|
-
// ONE raw source; `series` picks the series and `expand` is per-row
|
|
3009
|
-
// DATA — presence is what grows the ⤢ control on that row.
|
|
3010
|
-
const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
|
|
3011
|
-
const OpsRow = StructType({
|
|
3012
|
-
series: StringType,
|
|
3013
|
-
label: StringType,
|
|
3014
|
-
expand: OptionType(Plan.Types.Expand),
|
|
3015
|
-
jobs: ArrayType(StructType({
|
|
3016
|
-
key: StringType, label: StringType,
|
|
3017
|
-
start: DateTimeType, end: DateTimeType, state: EventStateType,
|
|
3018
|
-
})),
|
|
3019
|
-
points: ArrayType(MeasureRow),
|
|
3020
|
-
cells: ArrayType(Plan.Types.HeatCell),
|
|
3021
|
-
nums: ArrayType(Plan.Types.TableCell),
|
|
3022
|
-
marks: ArrayType(Plan.Types.EventMark),
|
|
3023
|
-
});
|
|
3024
|
-
const noJobs = $.const([], ArrayType(StructType({
|
|
3025
|
-
key: StringType, label: StringType,
|
|
3026
|
-
start: DateTimeType, end: DateTimeType, state: EventStateType,
|
|
3027
|
-
})));
|
|
3028
|
-
const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
|
|
3029
|
-
const noNums = $.const([], ArrayType(Plan.Types.TableCell));
|
|
3030
|
-
const noMarks = $.const([], ArrayType(Plan.Types.EventMark));
|
|
3031
|
-
const noPoints = $.const([], ArrayType(MeasureRow));
|
|
3032
|
-
const covPcts = $.const([96.1, 96.4, 96.8, 97.0, 96.2, 95.1, 93.4, 91.0, 88.9, 91.4, 93.8, 94.2], ArrayType(FloatType));
|
|
3033
|
-
const coverage = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: covPcts.get(i) })));
|
|
3034
|
-
const ops = $.const(new Map([
|
|
3035
|
-
// A CHART row — the one kind whose marks are a VALUE scale. Its
|
|
3036
|
-
// plot, its gutter ticks and its ref-label gate answer to the band
|
|
3037
|
-
// the marks keep at the top, not to the grown row (#591).
|
|
3038
|
-
["COVERAGE", { series: "chart", label: "Coverage",
|
|
3039
|
-
expand: some({ height: some("150px"), axis: variant("keep", null) }),
|
|
3040
|
-
jobs: noJobs, points: coverage, cells: noCells, nums: noNums, marks: noMarks }],
|
|
3041
|
-
// axis: keep — the grid and now-line run THROUGH the render.
|
|
3042
|
-
["L1-M03", { series: "span", label: "L1-M03",
|
|
3043
|
-
expand: some({ height: some("168px"), axis: variant("keep", null) }),
|
|
3044
|
-
jobs: [
|
|
3045
|
-
{ key: "b208", label: "RUN · B-208", start: week(27n), end: week(30n), state: variant("actual", null) },
|
|
3046
|
-
{ key: "qc", label: "QC", start: week(30n), end: week(32n), state: variant("confirmed", null) },
|
|
3047
|
-
{ key: "b231", label: "RUN · B-231", start: week(33n), end: week(38n), state: variant("proposed", variant("recommended", null)) },
|
|
3048
|
-
], points: noPoints, cells: noCells, nums: noNums, marks: noMarks }],
|
|
3049
|
-
// axis: dim — washed to 40% behind a dense render.
|
|
3050
|
-
["L1-M04", { series: "span", label: "L1-M04",
|
|
3051
|
-
expand: some({ height: some("140px"), axis: variant("dim", null) }),
|
|
3052
|
-
jobs: [
|
|
3053
|
-
{ key: "b214", label: "RUN · B-214", start: week(28n), end: week(33n), state: variant("in-progress", null) },
|
|
3054
|
-
], points: noPoints, cells: noCells, nums: noNums, marks: noMarks }],
|
|
3055
|
-
// No declaration — no control. The contrast is the point: one row
|
|
3056
|
-
// that cannot be expanded beside five that can.
|
|
3057
|
-
["L1-M07", { series: "span", label: "L1-M07", expand: none,
|
|
3058
|
-
jobs: [
|
|
3059
|
-
{ key: "hld", label: "HLD · B-197", start: week(27n), end: week(31n), state: variant("actual", null) },
|
|
3060
|
-
], points: noPoints, cells: noCells, nums: noNums, marks: noMarks }],
|
|
3061
|
-
// The kinds that COLLAPSE differently — heat keeps its ramp, the
|
|
3062
|
-
// table re-encodes its numerals, the marks keep their silhouettes.
|
|
3063
|
-
["LOAD", { series: "heat", label: "Line load",
|
|
3064
|
-
expand: some({ height: some("132px"), axis: variant("keep", null) }),
|
|
3065
|
-
jobs: noJobs, points: noPoints, nums: noNums, marks: noMarks,
|
|
3066
|
-
cells: [
|
|
3067
|
-
{ at: Plan.at.time(week(27n)), value: some(46.0), label: some("46") },
|
|
3068
|
-
{ at: Plan.at.time(week(28n)), value: some(58.0), label: some("58") },
|
|
3069
|
-
{ at: Plan.at.time(week(29n)), value: some(66.0), label: some("66") },
|
|
3070
|
-
{ at: Plan.at.time(week(30n)), value: some(72.0), label: some("72") },
|
|
3071
|
-
{ at: Plan.at.time(week(31n)), value: some(84.0), label: some("84") },
|
|
3072
|
-
{ at: Plan.at.time(week(32n)), value: some(90.0), label: some("90") },
|
|
3073
|
-
{ at: Plan.at.time(week(33n)), value: some(96.0), label: some("96") },
|
|
3074
|
-
{ at: Plan.at.time(week(34n)), value: none, label: none },
|
|
3075
|
-
{ at: Plan.at.time(week(35n)), value: some(92.0), label: some("92") },
|
|
3076
|
-
] }],
|
|
3077
|
-
// axis: off — the render draws its own canvas, so the shared lines
|
|
3078
|
-
// are suppressed INSIDE this row only (the ruler never moves).
|
|
3079
|
-
["DESPATCH", { series: "table", label: "Despatch t",
|
|
3080
|
-
expand: some({ height: some("120px"), axis: variant("off", null) }),
|
|
3081
|
-
jobs: noJobs, points: noPoints, cells: noCells, marks: noMarks,
|
|
3082
|
-
nums: [
|
|
3083
|
-
{ at: Plan.at.time(week(27n)), value: some(128.0), text: none, tone: none },
|
|
3084
|
-
{ at: Plan.at.time(week(28n)), value: some(134.0), text: none, tone: none },
|
|
3085
|
-
{ at: Plan.at.time(week(29n)), value: some(119.0), text: none, tone: none },
|
|
3086
|
-
{ at: Plan.at.time(week(30n)), value: some(-96.0), text: none, tone: none },
|
|
3087
|
-
{ at: Plan.at.time(week(31n)), value: some(-88.0), text: none, tone: none },
|
|
3088
|
-
{ at: Plan.at.time(week(32n)), value: none, text: none, tone: none },
|
|
3089
|
-
{ at: Plan.at.time(week(33n)), value: some(151.0), text: none, tone: none },
|
|
3090
|
-
{ at: Plan.at.time(week(34n)), value: some(162.0), text: none, tone: none },
|
|
3091
|
-
{ at: Plan.at.time(week(35n)), value: some(144.0), text: none, tone: none },
|
|
3092
|
-
] }],
|
|
3093
|
-
["MILESTONES", { series: "events", label: "Milestones",
|
|
3094
|
-
expand: some({ height: some("112px"), axis: variant("dim", null) }),
|
|
3095
|
-
jobs: noJobs, points: noPoints, cells: noCells, nums: noNums,
|
|
3096
|
-
marks: [
|
|
3097
|
-
{ key: "k", at: Plan.at.time(week(28n)), kind: variant("milestone", null), icon: none, label: some("KICKOFF") },
|
|
3098
|
-
{ key: "d", at: Plan.at.time(week(31n)), kind: variant("decision", { applied: true }), icon: none, label: none },
|
|
3099
|
-
{ key: "a", at: Plan.at.time(week(34n)), kind: variant("exception", null), icon: none, label: some("AUDIT") },
|
|
3100
|
-
] }],
|
|
3101
|
-
]), DictType(StringType, OpsRow));
|
|
3102
|
-
// ONE resolver serves every declaring row, called with the row's id at
|
|
3103
|
-
// interaction time. The canvas hands it the PLOT column with the
|
|
3104
|
-
// shared grid + now-line drawn behind it — so a component that fills
|
|
3105
|
-
// that column edge to edge shares the canvas's x-space and lines up
|
|
3106
|
-
// with the buckets above. `Sparkline` is the axis-free chart, which is
|
|
3107
|
-
// what makes the alignment visible; a `Chart` would draw its own axes
|
|
3108
|
-
// and margins inside the column and align to those instead.
|
|
3109
|
-
const util = $.let(East.Array.generate(12n, FloatType, (_$, i) => i.multiply(19n).remainder(48n).toFloat().add(50.0)));
|
|
3110
|
-
const expandRender = $.const(East.function([Plan.Types.RowId], UIComponentType, (_$, _id) => (_jsx(Sparkline, { data: util, type: "area", color: "link", width: "100%", height: "100%" }))));
|
|
3111
|
-
// The GUTTER half. An expanded row's gutter cell grows with the row,
|
|
3112
|
-
// and what fills the space it opens up is the author's — the identity
|
|
3113
|
-
// and measures that only earn their place once the row has the canvas.
|
|
3114
|
-
// Same row id as `expandRender`, so it can differ per row.
|
|
3115
|
-
// The old spec's drilled-row card, which is what the grown gutter is
|
|
3116
|
-
// for: identity lines then a fill meter. The lines need no styling —
|
|
3117
|
-
// the gutter body already carries the sub-line vocabulary — so the
|
|
3118
|
-
// author writes content, not typography.
|
|
3119
|
-
const GutterFacts = StructType({ a: StringType, b: StringType, fill: FloatType });
|
|
3120
|
-
const expandGutter = $.const(East.function([Plan.Types.RowId], UIComponentType, ($, id) => {
|
|
3121
|
-
const facts = $.const(new Map([
|
|
3122
|
-
["COVERAGE", { a: "TARGET 100 · MIN 92", b: "BREACH W34–W36 · 3 wk", fill: 0.94 }],
|
|
3123
|
-
["L1-M03", { a: "120 t · FILL", b: "B-208 · 88 t · 73%", fill: 0.73 }],
|
|
3124
|
-
["L1-M04", { a: "120 t · FILL", b: "B-214 · 89 t · 74%", fill: 0.74 }],
|
|
3125
|
-
["LOAD", { a: "MEAN 74 · PEAK 96", b: "BREACH W33 · 1 wk", fill: 0.96 }],
|
|
3126
|
-
["DESPATCH", { a: "NET 1 629 t", b: "2 SHORT WEEKS", fill: 0.55 }],
|
|
3127
|
-
["MILESTONES", { a: "5 MARKS", b: "1 EXCEPTION · W34", fill: 0.2 }],
|
|
3128
|
-
]), DictType(StringType, GutterFacts));
|
|
3129
|
-
// A row's id is its series and the path of keys to it — here the
|
|
3130
|
-
// entry's one key, which is what the facts are keyed by.
|
|
3131
|
-
const f = $.let(facts.get(id.unwrap("entry").path.get(0n)));
|
|
3132
|
-
return (_jsxs(Box, { children: [_jsx(Text, { children: f.a }), _jsx(Text, { children: f.b }), _jsx(Box, { width: "108px", children: _jsx(Progress, { value: f.fill.multiply(100.0), size: "xs", tone: "brand" }) })] }));
|
|
3133
|
-
}));
|
|
3134
|
-
return (_jsx(Plan, { axis: Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }), data: ops, series: [
|
|
3135
|
-
Plan.series.span(OpsRow, {
|
|
3136
|
-
key: "machines", title: "Machines",
|
|
3137
|
-
match: r => r.series.equal("span"),
|
|
3138
|
-
label: r => r.label, id: true, expand: r => r.expand,
|
|
3139
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
3140
|
-
key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
|
|
3141
|
-
})),
|
|
3142
|
-
}),
|
|
3143
|
-
Plan.series.chart(OpsRow, {
|
|
3144
|
-
key: "coverage", title: "Coverage",
|
|
3145
|
-
match: r => r.series.equal("chart"),
|
|
3146
|
-
label: r => r.label, id: true, expand: r => r.expand,
|
|
3147
|
-
height: "spark",
|
|
3148
|
-
left: { domain: [80, 110], tickValues: [80, 100] },
|
|
3149
|
-
layers: r => [
|
|
3150
|
-
Plan.layer(Chart.Line(r.points, { x: p => p.week, y: p => p.pct }), { breach: { below: 92 } }),
|
|
3151
|
-
Chart.refLine({ y: 100, label: "TARGET 100" }),
|
|
3152
|
-
],
|
|
3153
|
-
}),
|
|
3154
|
-
Plan.series.heat(OpsRow, {
|
|
3155
|
-
key: "load", title: "Line load",
|
|
3156
|
-
match: r => r.series.equal("heat"),
|
|
3157
|
-
label: r => r.label, expand: r => r.expand,
|
|
3158
|
-
cells: r => Plan.heatCells(r.cells, { min: 40.0, max: 100.0 }),
|
|
3159
|
-
}),
|
|
3160
|
-
Plan.series.table(OpsRow, {
|
|
3161
|
-
key: "despatch", title: "Despatch",
|
|
3162
|
-
match: r => r.series.equal("table"),
|
|
3163
|
-
label: r => r.label, expand: r => r.expand,
|
|
3164
|
-
cells: r => r.nums,
|
|
3165
|
-
}),
|
|
3166
|
-
Plan.series.events(OpsRow, {
|
|
3167
|
-
key: "milestones", title: "Milestones",
|
|
3168
|
-
match: r => r.series.equal("events"),
|
|
3169
|
-
label: r => r.label, id: true, expand: r => r.expand,
|
|
3170
|
-
marks: r => r.marks,
|
|
3171
|
-
}),
|
|
3172
|
-
], expandRender: expandRender, expandGutter: expandGutter, style: { height: "460px" } }));
|
|
3173
|
-
}),
|
|
3174
|
-
inputs: [],
|
|
3175
|
-
});
|
|
3176
|
-
// ============================================================================
|
|
3177
|
-
// planNarrow — the §10 narrow layout: a phone-width box makes the Plan a
|
|
3178
|
-
// review tool, not a canvas (#570)
|
|
3179
|
-
// ============================================================================
|
|
3180
|
-
/**
|
|
3181
|
-
* Below 480px of CONTAINER width — not the viewport: this example is a 360px
|
|
3182
|
-
* `<Box>` on a desktop page — the Plan reflows to the §10 layout: three tabs
|
|
3183
|
-
* over ONE slice (Groups · Rows · Measures), the group grain as hottest-first
|
|
3184
|
-
* strip cards, one group's rows as cards whose head is the row's gutter
|
|
3185
|
-
* identity and whose body is its plot on the shared window, chart rows
|
|
3186
|
-
* full-width at expanded density. A row declaring `expand` drills in place on
|
|
3187
|
-
* a second tap; horizontal pan is two-finger. The DEFINITION is the desktop
|
|
3188
|
-
* one — the same `data` + `series`, the same slice — only the box changed.
|
|
3189
|
-
*/
|
|
3190
|
-
export const planNarrow = example({
|
|
3191
|
-
keywords: ["Plan", "narrow", "mobile", "phone", "responsive", "compact", "container", "breakpoint", "tabs", "Groups", "Rows", "Measures", "cards", "strip", "hottest", "two-finger", "pan", "review", "cohort", "slice", "§10", "groupToDicts", "raw"],
|
|
3192
|
-
description: "The narrow layout — a phone-width box turns the same canvas into a review tool: Groups · Rows · Measures tabs, hottest-first strip cards, rows as cards, charts at expanded density",
|
|
3193
|
-
fn: East.function([], UIComponentType, (_$) => {
|
|
3194
|
-
const HorizonRow = StructType({ key: StringType, at: DateTimeType, risk: StringType });
|
|
3195
|
-
const cfg = Slice.config(HorizonRow, {
|
|
3196
|
-
fields: { at: { label: "Despatched", format: { date: "MMM D" } }, risk: { label: "Risk", hints: ["late", "on-time"] } },
|
|
3197
|
-
rangeFieldId: "at",
|
|
3198
|
-
});
|
|
3199
|
-
return (_jsx(Reactive, { children: $ => {
|
|
3200
|
-
// Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
|
|
3201
|
-
const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
|
|
3202
|
-
const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
|
|
3203
|
-
return w1.addWeeks(n.subtract(1n));
|
|
3204
|
-
}));
|
|
3205
|
-
const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
|
|
3206
|
-
const JobRow = StructType({
|
|
3207
|
-
key: StringType, label: StringType,
|
|
3208
|
-
start: DateTimeType, end: DateTimeType, state: EventStateType,
|
|
3209
|
-
});
|
|
3210
|
-
// The raw rows: `series` picks the series, `line` the line a row
|
|
3211
|
-
// belongs to, and every envelope field is per-row DATA.
|
|
3212
|
-
const OpsRow = StructType({
|
|
3213
|
-
series: StringType, line: StringType, label: StringType,
|
|
3214
|
-
value: OptionType(StringType), status: OptionType(StatusValueType),
|
|
3215
|
-
expand: OptionType(Plan.Types.Expand),
|
|
3216
|
-
jobs: ArrayType(JobRow),
|
|
3217
|
-
cells: ArrayType(Plan.Types.HeatCell),
|
|
3218
|
-
});
|
|
3219
|
-
const noJobs = $.const([], ArrayType(JobRow));
|
|
3220
|
-
const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
|
|
3221
|
-
const loadPcts = $.const([46.0, 52.0, 58.0, 61.0, 66.0, 72.0, 78.0, 84.0, 90.0, 96.0, 98.0, 92.0], ArrayType(FloatType));
|
|
3222
|
-
// Line 2 runs hotter than Line 1 — the Groups tab sorts it first.
|
|
3223
|
-
const loadA = $.let(East.Array.generate(12n, Plan.Types.HeatCell, (_$, i) => ({
|
|
3224
|
-
at: Plan.at.time(week(i.add(27n))), value: some(loadPcts.get(i).multiply(0.85)),
|
|
3225
|
-
label: some(East.Float.printFixed(loadPcts.get(i).multiply(0.85), 0n)),
|
|
3226
|
-
})));
|
|
3227
|
-
const loadB = $.let(East.Array.generate(12n, Plan.Types.HeatCell, (_$, i) => ({
|
|
3228
|
-
at: Plan.at.time(week(i.add(27n))), value: some(loadPcts.get(i)),
|
|
3229
|
-
label: some(East.Float.printFixed(loadPcts.get(i), 0n)),
|
|
3230
|
-
})));
|
|
3231
|
-
const covPcts = $.const([96.1, 96.4, 96.8, 97.0, 96.2, 95.1, 93.4, 91.0, 88.9, 91.4, 93.8, 94.2], ArrayType(FloatType));
|
|
3232
|
-
const coverage = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: covPcts.get(i) })));
|
|
3233
|
-
const ops = $.const(new Map([
|
|
3234
|
-
["L1-M03", { series: "mach", line: "Line 1 · Fill", label: "L1-M03",
|
|
3235
|
-
value: some("120 t"), status: some(variant("success", null)),
|
|
3236
|
-
expand: some({ height: some("140px"), axis: variant("keep", null) }),
|
|
3237
|
-
jobs: [
|
|
3238
|
-
{ key: "b208", label: "RUN · B-208", start: week(27n), end: week(30n), state: variant("actual", null) },
|
|
3239
|
-
{ key: "qc", label: "QC", start: week(30n), end: week(32n), state: variant("confirmed", null) },
|
|
3240
|
-
{ key: "b231", label: "RUN · B-231", start: week(33n), end: week(38n), state: variant("proposed", variant("recommended", null)) },
|
|
3241
|
-
], cells: noCells }],
|
|
3242
|
-
["L1-M04", { series: "mach", line: "Line 1 · Fill", label: "L1-M04",
|
|
3243
|
-
value: some("120 t"), status: some(variant("warning", null)), expand: none,
|
|
3244
|
-
jobs: [
|
|
3245
|
-
{ key: "b214", label: "RUN · B-214", start: week(28n), end: week(33n), state: variant("in-progress", null) },
|
|
3246
|
-
], cells: noCells }],
|
|
3247
|
-
["l1-load", { series: "load", line: "Line 1 · Fill", label: "Line load",
|
|
3248
|
-
value: none, status: none, expand: none,
|
|
3249
|
-
jobs: noJobs, cells: loadA }],
|
|
3250
|
-
["L2-M11", { series: "mach", line: "Line 2 · Assy", label: "L2-M11",
|
|
3251
|
-
value: some("80 t"), status: none, expand: none,
|
|
3252
|
-
jobs: [
|
|
3253
|
-
{ key: "b241", label: "RUN · B-241", start: week(29n), end: week(34n), state: variant("confirmed", null) },
|
|
3254
|
-
], cells: noCells }],
|
|
3255
|
-
["l2-load", { series: "load", line: "Line 2 · Assy", label: "Line load",
|
|
3256
|
-
value: none, status: some(variant("warning", null)), expand: none,
|
|
3257
|
-
jobs: noJobs, cells: loadB }],
|
|
3258
|
-
]), DictType(StringType, OpsRow));
|
|
3259
|
-
// Grouping is a DATA step (#822): one `groupToDicts` makes each
|
|
3260
|
-
// line an entry holding its rows — the strips nest exactly those.
|
|
3261
|
-
const lines = $.let(ops.groupToDicts(($, r) => r.line, ($, _r, k) => k));
|
|
3262
|
-
const LineGroup = DictType(StringType, OpsRow);
|
|
3263
|
-
// The slice's own rows — despatch orders with a late-risk cohort
|
|
3264
|
-
// seeded active, so the chips row has something to say.
|
|
3265
|
-
const horizon = $.let(East.Array.generate(24n, HorizonRow, (_$, i) => ({
|
|
3266
|
-
key: East.str `h${East.print(i.add(1n))}`,
|
|
3267
|
-
at: week(i.divide(2n).add(27n)),
|
|
3268
|
-
risk: i.remainder(3n).equal(0n).ifElse(() => "late", () => "on-time"),
|
|
3269
|
-
})));
|
|
3270
|
-
// A slice range is CLOSED — both ends inclusive — so the twelve
|
|
3271
|
-
// weeks W27–W38 end the millisecond before W39.
|
|
3272
|
-
const slice = $.let(Slice.bind([HorizonRow], "ex.plan.narrow", cfg, Slice.state({
|
|
3273
|
-
range: some(variant("datetime", { from: week(27n), to: week(39n).addMilliseconds(-1n) })),
|
|
3274
|
-
cohorts: [{ id: "late", name: "Late risk", filters: [variant("string", { fieldId: "risk", op: variant("eq", "late") })] }],
|
|
3275
|
-
activeCohorts: new Set(["late"]),
|
|
3276
|
-
}), horizon, none));
|
|
3277
|
-
const series = $.const([
|
|
3278
|
-
// The KPI no line holds — a hand-built chart row over the
|
|
3279
|
-
// coverage points: it rides the Measures tab and an "Other
|
|
3280
|
-
// rows" card.
|
|
3281
|
-
Plan.series.rows(LineGroup, { key: "kpi", title: "Coverage" }, [
|
|
3282
|
-
Plan.chart({
|
|
3283
|
-
key: "coverage", label: "Coverage", id: true, value: "94.2%",
|
|
3284
|
-
height: "spark",
|
|
3285
|
-
left: { domain: [80, 110], tickValues: [80, 100] },
|
|
3286
|
-
layers: [
|
|
3287
|
-
Plan.layer(Chart.Line(coverage, { x: p => p.week, y: p => p.pct }), { breach: { below: 92 } }),
|
|
3288
|
-
Chart.refLine({ y: 100, label: "TARGET 100" }),
|
|
3289
|
-
],
|
|
3290
|
-
}),
|
|
3291
|
-
]),
|
|
3292
|
-
// One strip per line, its rows stepped down into; its summary
|
|
3293
|
-
// strip is the max of its heat rows — what "hottest first" reads.
|
|
3294
|
-
Plan.series.group(LineGroup, {
|
|
3295
|
-
key: "lines", title: "Lines",
|
|
3296
|
-
label: (_g, line) => line,
|
|
3297
|
-
summaryAggregate: "max",
|
|
3298
|
-
children: Plan.children((g) => g, [
|
|
3299
|
-
Plan.series.span(OpsRow, {
|
|
3300
|
-
key: "mach", title: "Machines",
|
|
3301
|
-
match: r => r.series.equal("mach"),
|
|
3302
|
-
label: r => r.label, id: true,
|
|
3303
|
-
value: r => r.value, status: r => r.status, expand: r => r.expand,
|
|
3304
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({
|
|
3305
|
-
key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
|
|
3306
|
-
})),
|
|
3307
|
-
}),
|
|
3308
|
-
Plan.series.heat(OpsRow, {
|
|
3309
|
-
key: "load", title: "Line load",
|
|
3310
|
-
match: r => r.series.equal("load"),
|
|
3311
|
-
label: r => r.label, status: r => r.status,
|
|
3312
|
-
cells: r => Plan.heatCells(r.cells, { min: 0, max: 100, warnAt: 95 }),
|
|
3313
|
-
}),
|
|
3314
|
-
]),
|
|
3315
|
-
}),
|
|
3316
|
-
], ArrayType(Plan.Types.Series(LineGroup)));
|
|
3317
|
-
const util = $.let(East.Array.generate(12n, FloatType, (_$, i) => i.multiply(19n).remainder(48n).toFloat().add(50.0)));
|
|
3318
|
-
const expandRender = $.const(East.function([Plan.Types.RowId], UIComponentType, (_$, _id) => (_jsx(Sparkline, { data: util, type: "area", color: "link", width: "100%", height: "100%" }))));
|
|
3319
|
-
const axis = $.const(Plan.axis({
|
|
3320
|
-
window: { min: week(27n), max: week(39n) },
|
|
3321
|
-
resolution: "week", resolutions: ["month", "week", "day"], now: week(31n),
|
|
3322
|
-
}));
|
|
3323
|
-
// The 360px box is the whole point: the reflow is a property of
|
|
3324
|
-
// the CONTAINER, so a phone, a splitter pane and this box agree.
|
|
3325
|
-
return (_jsx(Box, { width: "360px", children: _jsx(Plan, { axis: axis, data: lines, series: series, slice: { slice, affordances: ["cohort", "filter", "range", "resolution", "summary"] }, expandRender: expandRender, footer: [
|
|
3326
|
-
{ text: "6 ROWS · 2 LINES" },
|
|
3327
|
-
{ text: "RUN 412 · W27–W38", end: true },
|
|
3328
|
-
], style: { height: "560px" } }) }));
|
|
3329
|
-
} }));
|
|
3330
|
-
}),
|
|
3331
|
-
inputs: [],
|
|
3332
|
-
});
|
|
3333
|
-
// ============================================================================
|
|
3334
|
-
// planNumberAxis — the `number` axis (#631): day 1..8 with AM/PM lanes
|
|
3335
|
-
// ============================================================================
|
|
3336
|
-
/**
|
|
3337
|
-
* The `number` axis — the retired Planner's `plannerPoint` canvas on the
|
|
3338
|
-
* Plan: eight days at step 1, AM/PM lanes in the bucket rows, `now` at day
|
|
3339
|
-
* 5. Every kind positions on the one numeric scale, and every instant in the
|
|
3340
|
-
* raw data is a plain number: a `FloatType` field wraps to the `number` arm
|
|
3341
|
-
* through the element builders (`Plan.run({ start: j.start })`), a chart
|
|
3342
|
-
* layer's numeric x accessor lands its columns on the same arm, `Plan.tableCells`
|
|
3343
|
-
* reads a numeric `at`, and the heat cells stored as RECORDS spell it out with
|
|
3344
|
-
* `Plan.at.number`. The slice's range field is an integer day, so the horizon
|
|
3345
|
-
* brush and the range chip ride the slice's `integer` arm exactly as they ride
|
|
3346
|
-
* `datetime` on a time axis — a closed range, so the eight days `[1, 9)` read
|
|
3347
|
-
* `1–8`; there is no resolution segment — `step` is the declaration.
|
|
3348
|
-
*/
|
|
3349
|
-
export const planNumberAxis = example({
|
|
3350
|
-
keywords: [
|
|
3351
|
-
"Plan", "axis", "number", "numeric", "step", "Plan.axis.number", "Plan.at", "instant",
|
|
3352
|
-
"day", "AM", "PM", "lanes", "buckets", "Planner", "plannerPoint", "brush", "integer", "range", "inclusive",
|
|
3353
|
-
"format", "now", "typed axis", "#631", "raw",
|
|
3354
|
-
],
|
|
3355
|
-
description: "The number axis — day 1..8 at step 1 with AM/PM lanes (the retired Planner's plannerPoint), every row kind on the numeric scale, the horizon brush over the slice's integer range, read inclusive",
|
|
3356
|
-
fn: East.function([], UIComponentType, (_$) => {
|
|
3357
|
-
const HorizonRow = StructType({ key: StringType, day: IntegerType, line: StringType });
|
|
3358
|
-
const cfg = Slice.config(HorizonRow, {
|
|
3359
|
-
fields: { day: { label: "Day" }, line: { label: "Line" } },
|
|
3360
|
-
rangeFieldId: "day",
|
|
3361
|
-
});
|
|
3362
|
-
return (_jsx(Reactive, { children: $ => {
|
|
3363
|
-
// RAW rows — every instant is a plain number (a day index).
|
|
3364
|
-
const AllocRow = StructType({ key: StringType, day: FloatType, lane: StringType, state: EventStateType });
|
|
3365
|
-
const JobRow = StructType({ key: StringType, label: StringType, start: FloatType, end: FloatType, state: EventStateType });
|
|
3366
|
-
const ShiftRow = StructType({ key: StringType, from: FloatType, to: FloatType, hours: FloatType, state: EventStateType });
|
|
3367
|
-
const MarkRow = StructType({ key: StringType, day: FloatType, label: StringType });
|
|
3368
|
-
const PointRow = StructType({ day: FloatType, t: FloatType });
|
|
3369
|
-
const RawCell = StructType({ at: FloatType, value: OptionType(FloatType) });
|
|
3370
|
-
const OpsRow = StructType({
|
|
3371
|
-
series: StringType, label: StringType, value: OptionType(StringType), sub: OptionType(StringType),
|
|
3372
|
-
allocations: ArrayType(AllocRow), jobs: ArrayType(JobRow), shifts: ArrayType(ShiftRow),
|
|
3373
|
-
marks: ArrayType(MarkRow), points: ArrayType(PointRow), tonnes: ArrayType(RawCell),
|
|
3374
|
-
cells: ArrayType(Plan.Types.HeatCell),
|
|
3375
|
-
});
|
|
3376
|
-
const noAllocs = $.const([], ArrayType(AllocRow));
|
|
3377
|
-
const noJobs = $.const([], ArrayType(JobRow));
|
|
3378
|
-
const noShifts = $.const([], ArrayType(ShiftRow));
|
|
3379
|
-
const noMarks = $.const([], ArrayType(MarkRow));
|
|
3380
|
-
const noPoints = $.const([], ArrayType(PointRow));
|
|
3381
|
-
const noTonnes = $.const([], ArrayType(RawCell));
|
|
3382
|
-
const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
|
|
3383
|
-
const CONFIRMED = variant("confirmed", null);
|
|
3384
|
-
const ACTUAL = variant("actual", null);
|
|
3385
|
-
const RUNNING = variant("in-progress", null);
|
|
3386
|
-
const PROPOSED = variant("proposed", variant("recommended", null));
|
|
3387
|
-
// Per-day series over days 1..8 — derived, never hand-written.
|
|
3388
|
-
const points = $.let(East.Array.generate(8n, PointRow, (_$, i) => ({ day: i.toFloat().add(1.0), t: i.multiply(17n).remainder(40n).toFloat().add(60.0) })));
|
|
3389
|
-
const tonnes = $.let(East.Array.generate(8n, RawCell, (_$, i) => ({ at: i.toFloat().add(1.0), value: some(i.multiply(23n).remainder(70n).toFloat().add(80.0)) })));
|
|
3390
|
-
// Heat cells as STORED records — the instant spelled explicitly.
|
|
3391
|
-
const cells = $.let(East.Array.generate(8n, Plan.Types.HeatCell, ($, i) => {
|
|
3392
|
-
const load = $.let(i.multiply(29n).remainder(55n).toFloat().add(40.0), FloatType);
|
|
3393
|
-
return { at: Plan.at.number(i.toFloat().add(1.0)), value: some(load), label: some(East.Float.printFixed(load, 0n)) };
|
|
3394
|
-
}));
|
|
3395
|
-
const base = {
|
|
3396
|
-
value: none, sub: none, allocations: noAllocs, jobs: noJobs, shifts: noShifts,
|
|
3397
|
-
marks: noMarks, points: noPoints, tonnes: noTonnes, cells: noCells,
|
|
3398
|
-
};
|
|
3399
|
-
// The keys are the entries' identities; the series list below is
|
|
3400
|
-
// the layout.
|
|
3401
|
-
const ops = $.const(new Map([
|
|
3402
|
-
["dock2", { ...base, series: "dock", label: "Dock 2", sub: some("load/day"), allocations: [
|
|
3403
|
-
{ key: "a1", day: 1.0, lane: "am", state: CONFIRMED },
|
|
3404
|
-
{ key: "a2", day: 1.0, lane: "pm", state: CONFIRMED },
|
|
3405
|
-
{ key: "a3", day: 2.0, lane: "am", state: CONFIRMED },
|
|
3406
|
-
{ key: "a4", day: 3.0, lane: "pm", state: PROPOSED },
|
|
3407
|
-
{ key: "a5", day: 5.0, lane: "am", state: PROPOSED },
|
|
3408
|
-
{ key: "a6", day: 6.0, lane: "pm", state: CONFIRMED },
|
|
3409
|
-
{ key: "a7", day: 8.0, lane: "am", state: PROPOSED },
|
|
3410
|
-
] }],
|
|
3411
|
-
["dock5", { ...base, series: "dock", label: "Dock 5", sub: some("load/day"), allocations: [
|
|
3412
|
-
{ key: "b1", day: 2.0, lane: "pm", state: CONFIRMED },
|
|
3413
|
-
{ key: "b2", day: 4.0, lane: "am", state: CONFIRMED },
|
|
3414
|
-
{ key: "b3", day: 4.0, lane: "pm", state: PROPOSED },
|
|
3415
|
-
{ key: "b4", day: 7.0, lane: "am", state: PROPOSED },
|
|
3416
|
-
] }],
|
|
3417
|
-
["m03", { ...base, series: "mach", label: "L1-M03", value: some("120 t"), jobs: [
|
|
3418
|
-
{ key: "set", label: "SET", start: 1.0, end: 2.0, state: ACTUAL },
|
|
3419
|
-
{ key: "b214", label: "RUN · B-214", start: 2.0, end: 5.0, state: RUNNING },
|
|
3420
|
-
{ key: "b221", label: "RUN · B-221", start: 6.0, end: 8.0, state: PROPOSED },
|
|
3421
|
-
] }],
|
|
3422
|
-
["m04", { ...base, series: "mach", label: "L1-M04", value: some("80 t"), jobs: [
|
|
3423
|
-
{ key: "b208", label: "RUN · B-208", start: 1.0, end: 4.0, state: ACTUAL },
|
|
3424
|
-
{ key: "qc", label: "QC", start: 4.0, end: 5.0, state: CONFIRMED },
|
|
3425
|
-
{ key: "b231", label: "RUN · B-231", start: 5.0, end: 9.0, state: PROPOSED },
|
|
3426
|
-
] }],
|
|
3427
|
-
["load", { ...base, series: "load", label: "Line load", cells }],
|
|
3428
|
-
["out", { ...base, series: "out", label: "Output · t", value: some("612 t"), points }],
|
|
3429
|
-
["desp", { ...base, series: "table", label: "Despatch t", tonnes }],
|
|
3430
|
-
["crewA", { ...base, series: "crew", label: "Crew A", shifts: [
|
|
3431
|
-
{ key: "s1", from: 1.0, to: 3.0, hours: 24.0, state: CONFIRMED },
|
|
3432
|
-
{ key: "s2", from: 3.0, to: 6.0, hours: 36.0, state: CONFIRMED },
|
|
3433
|
-
{ key: "s3", from: 6.0, to: 8.0, hours: 24.0, state: PROPOSED },
|
|
3434
|
-
] }],
|
|
3435
|
-
["ms", { ...base, series: "ms", label: "Milestones", marks: [
|
|
3436
|
-
{ key: "kick", day: 2.0, label: "KICKOFF" },
|
|
3437
|
-
{ key: "rel", day: 6.0, label: "REL" },
|
|
3438
|
-
] }],
|
|
3439
|
-
]), DictType(StringType, OpsRow));
|
|
3440
|
-
const series = $.const([
|
|
3441
|
-
Plan.series.section(OpsRow, { key: "docks-in", title: "Docks · In", meta: "2 rows" }, [
|
|
3442
|
-
Plan.series.buckets(OpsRow, {
|
|
3443
|
-
key: "dock", title: "Docks",
|
|
3444
|
-
match: r => r.series.equal("dock"),
|
|
3445
|
-
label: r => r.label, sub: r => r.sub,
|
|
3446
|
-
// The Planner's AM/PM lanes — sub-slots of each day column.
|
|
3447
|
-
lanes: _r => [Plan.lane({ key: "am", label: "AM" }), Plan.lane({ key: "pm", label: "PM" })],
|
|
3448
|
-
// `a.day` is a FloatType field — the builder wraps it to the number arm.
|
|
3449
|
-
events: r => r.allocations.map((_$, a) => Plan.event({ key: a.key, at: a.day, lane: a.lane, state: a.state })),
|
|
3450
|
-
}),
|
|
3451
|
-
]),
|
|
3452
|
-
Plan.series.span(OpsRow, {
|
|
3453
|
-
key: "mach", title: "Machines",
|
|
3454
|
-
match: r => r.series.equal("mach"),
|
|
3455
|
-
label: r => r.label, id: true, value: r => r.value,
|
|
3456
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({ key: j.key, start: j.start, end: j.end, label: j.label, state: j.state })),
|
|
3457
|
-
}),
|
|
3458
|
-
Plan.series.heat(OpsRow, {
|
|
3459
|
-
key: "load", title: "Line load",
|
|
3460
|
-
match: r => r.series.equal("load"),
|
|
3461
|
-
label: r => r.label,
|
|
3462
|
-
cells: r => Plan.heatCells(r.cells, { min: 0, max: 100, warnAt: 90 }),
|
|
3463
|
-
}),
|
|
3464
|
-
Plan.series.chart(OpsRow, {
|
|
3465
|
-
key: "out", title: "Output",
|
|
3466
|
-
match: r => r.series.equal("out"),
|
|
3467
|
-
label: r => r.label, id: true, value: r => r.value, height: "expanded",
|
|
3468
|
-
// A numeric x accessor lands the columns on the number arm.
|
|
3469
|
-
layers: r => [Chart.Column(r.points, { x: p => p.day, y: p => p.t })],
|
|
3470
|
-
}),
|
|
3471
|
-
Plan.series.table(OpsRow, {
|
|
3472
|
-
key: "table", title: "Despatch",
|
|
3473
|
-
match: r => r.series.equal("table"),
|
|
3474
|
-
label: r => r.label,
|
|
3475
|
-
// A `{ at: FloatType, value }` record wraps by its field type.
|
|
3476
|
-
cells: r => Plan.tableCells(r.tonnes),
|
|
3477
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
3478
|
-
}),
|
|
3479
|
-
Plan.series.cards(OpsRow, {
|
|
3480
|
-
key: "crew", title: "Crews",
|
|
3481
|
-
match: r => r.series.equal("crew"),
|
|
3482
|
-
label: r => r.label,
|
|
3483
|
-
chips: r => r.shifts.map((_$, s) => Plan.chip({
|
|
3484
|
-
key: s.key, from: s.from, to: s.to,
|
|
3485
|
-
label: East.str `${East.Float.printFixed(s.hours, 0n)}h`, state: s.state,
|
|
3486
|
-
})),
|
|
3487
|
-
}),
|
|
3488
|
-
Plan.series.events(OpsRow, {
|
|
3489
|
-
key: "ms", title: "Milestones",
|
|
3490
|
-
match: r => r.series.equal("ms"),
|
|
3491
|
-
label: r => r.label, id: true,
|
|
3492
|
-
marks: r => r.marks.map((_$, m) => Plan.mark({ key: m.key, at: m.day, kind: "milestone", label: m.label })),
|
|
3493
|
-
}),
|
|
3494
|
-
], ArrayType(Plan.Types.Series(OpsRow)));
|
|
3495
|
-
// The slice's horizon — twelve days of orders behind an eight-day
|
|
3496
|
-
// window. Its range field is an INTEGER day, so the brush, the
|
|
3497
|
-
// range chip and the window keys write the slice's `integer` arm:
|
|
3498
|
-
// a closed range, both ends inclusive — the eight days `[1, 9)`
|
|
3499
|
-
// are `1–8`.
|
|
3500
|
-
const horizon = $.let(East.Array.generate(24n, HorizonRow, (_$, i) => ({
|
|
3501
|
-
key: East.str `o${East.print(i.add(1n))}`,
|
|
3502
|
-
day: i.divide(2n).add(1n),
|
|
3503
|
-
line: i.remainder(2n).equal(0n).ifElse(() => "Line 1", () => "Line 2"),
|
|
3504
|
-
})));
|
|
3505
|
-
const slice = $.let(Slice.bind([HorizonRow], "ex.plan.number", cfg, Slice.state({
|
|
3506
|
-
range: some(variant("integer", { from: 1n, to: 8n })),
|
|
3507
|
-
}), horizon, none));
|
|
3508
|
-
// The declaration: `[1, 9)` ÷ 1 = eight day columns, the divider at 5.
|
|
3509
|
-
const axis = $.const(Plan.axis.number({
|
|
3510
|
-
window: { min: 1, max: 9 }, step: 1, now: 5, format: Chart.format.number(),
|
|
3511
|
-
}));
|
|
3512
|
-
return (_jsx(Plan, { axis: axis, data: ops, series: series, slice: { slice, affordances: ["filter", "range", "brush", "summary"] }, footer: [{ text: "8 STEPS · NOW 5" }] }));
|
|
3513
|
-
} }));
|
|
3514
|
-
}),
|
|
3515
|
-
inputs: [],
|
|
3516
|
-
});
|
|
3517
|
-
// ============================================================================
|
|
3518
|
-
// planOrdinalAxis — the `ordinal` axis (#631): workflow phases
|
|
3519
|
-
// ============================================================================
|
|
3520
|
-
/**
|
|
3521
|
-
* The `ordinal` axis — a workflow of six phases, each a bucket, in declared
|
|
3522
|
-
* order. Instants are the phase VALUES: a run's `start` / `end` are phase
|
|
3523
|
-
* names (an interval covers `[start, end]` in phase order — on an ordinal
|
|
3524
|
-
* axis the end names the LAST bucket covered, since values are buckets, not
|
|
3525
|
-
* edges), a tile sits in a phase, a chart's string x accessor lands its
|
|
3526
|
-
* columns on the phase arm, `Plan.tableCells` reads a string `at`, and
|
|
3527
|
-
* `Plan.at.ordinal` spells one out where a record is written as data. There
|
|
3528
|
-
* is no slice range for an ordinal axis — the list IS the window, so the
|
|
3529
|
-
* horizon brush does not mount and the window keys idle; `now` names a phase.
|
|
3530
|
-
*/
|
|
3531
|
-
export const planOrdinalAxis = example({
|
|
3532
|
-
keywords: [
|
|
3533
|
-
"Plan", "axis", "ordinal", "phase", "phases", "workflow", "stage", "Plan.axis.ordinal",
|
|
3534
|
-
"Plan.at", "instant", "values", "list", "span", "buckets", "heat", "chart", "table",
|
|
3535
|
-
"cards", "events", "Planner", "typed axis", "#631", "raw",
|
|
3536
|
-
],
|
|
3537
|
-
description: "The ordinal axis — six workflow phases as the buckets, in declared order; orders span phases, tiles and cells sit in them, a string x accessor lands a chart on them",
|
|
3538
|
-
fn: East.function([], UIComponentType, ($) => {
|
|
3539
|
-
const PHASES = $.const(["INTAKE", "PREP", "BUILD", "QC", "PACK", "SHIP"], ArrayType(StringType));
|
|
3540
|
-
// RAW rows — every instant is a phase NAME.
|
|
3541
|
-
const JobRow = StructType({ key: StringType, label: StringType, start: StringType, end: StringType, state: EventStateType });
|
|
3542
|
-
const AllocRow = StructType({ key: StringType, phase: StringType, state: EventStateType });
|
|
3543
|
-
const ShiftRow = StructType({ key: StringType, from: StringType, to: StringType, label: StringType, state: EventStateType });
|
|
3544
|
-
const MarkRow = StructType({ key: StringType, phase: StringType, label: StringType, exception: BooleanType });
|
|
3545
|
-
const PointRow = StructType({ phase: StringType, n: FloatType });
|
|
3546
|
-
const RawCell = StructType({ at: StringType, value: OptionType(FloatType) });
|
|
3547
|
-
const OrderRow = StructType({
|
|
3548
|
-
series: StringType, label: StringType, value: OptionType(StringType), sub: OptionType(StringType),
|
|
3549
|
-
jobs: ArrayType(JobRow), allocations: ArrayType(AllocRow), shifts: ArrayType(ShiftRow),
|
|
3550
|
-
marks: ArrayType(MarkRow), points: ArrayType(PointRow), counts: ArrayType(RawCell),
|
|
3551
|
-
cells: ArrayType(Plan.Types.HeatCell),
|
|
3552
|
-
});
|
|
3553
|
-
const noJobs = $.const([], ArrayType(JobRow));
|
|
3554
|
-
const noAllocs = $.const([], ArrayType(AllocRow));
|
|
3555
|
-
const noShifts = $.const([], ArrayType(ShiftRow));
|
|
3556
|
-
const noMarks = $.const([], ArrayType(MarkRow));
|
|
3557
|
-
const noPoints = $.const([], ArrayType(PointRow));
|
|
3558
|
-
const noCounts = $.const([], ArrayType(RawCell));
|
|
3559
|
-
const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
|
|
3560
|
-
const CONFIRMED = variant("confirmed", null);
|
|
3561
|
-
const ACTUAL = variant("actual", null);
|
|
3562
|
-
const RUNNING = variant("in-progress", null);
|
|
3563
|
-
const PROPOSED = variant("proposed", variant("recommended", null));
|
|
3564
|
-
// Per-phase series derived over the declared list — the index picks the value.
|
|
3565
|
-
const points = $.let(East.Array.generate(6n, PointRow, (_$, i) => ({ phase: PHASES.get(i), n: i.multiply(7n).remainder(20n).toFloat().add(4.0) })));
|
|
3566
|
-
const counts = $.let(East.Array.generate(6n, RawCell, (_$, i) => ({
|
|
3567
|
-
at: PHASES.get(i),
|
|
3568
|
-
value: i.equal(4n).ifElse(() => none, () => some(i.multiply(11n).remainder(30n).toFloat().add(12.0))),
|
|
3569
|
-
})));
|
|
3570
|
-
// Heat cells as STORED records — the phase spelled with `Plan.at.ordinal`.
|
|
3571
|
-
const cells = $.let(East.Array.generate(6n, Plan.Types.HeatCell, ($, i) => {
|
|
3572
|
-
const load = $.let(i.multiply(31n).remainder(60n).toFloat().add(35.0), FloatType);
|
|
3573
|
-
return { at: Plan.at.ordinal(PHASES.get(i)), value: some(load), label: some(East.Float.printFixed(load, 0n)) };
|
|
3574
|
-
}));
|
|
3575
|
-
const base = {
|
|
3576
|
-
value: none, sub: none, jobs: noJobs, allocations: noAllocs, shifts: noShifts,
|
|
3577
|
-
marks: noMarks, points: noPoints, counts: noCounts, cells: noCells,
|
|
3578
|
-
};
|
|
3579
|
-
const orders = $.const(new Map([
|
|
3580
|
-
["or-1188", { ...base, series: "order", label: "OR-1188", value: some("96 t"), jobs: [
|
|
3581
|
-
{ key: "prep", label: "PREP", start: "INTAKE", end: "PREP", state: ACTUAL },
|
|
3582
|
-
{ key: "build", label: "BUILD · B-214", start: "BUILD", end: "QC", state: RUNNING },
|
|
3583
|
-
{ key: "ship", label: "PACK + SHIP", start: "PACK", end: "SHIP", state: PROPOSED },
|
|
3584
|
-
] }],
|
|
3585
|
-
["or-1204", { ...base, series: "order", label: "OR-1204", value: some("54 t"), jobs: [
|
|
3586
|
-
{ key: "intake", label: "INTAKE", start: "INTAKE", end: "INTAKE", state: ACTUAL },
|
|
3587
|
-
{ key: "build", label: "BUILD · B-221", start: "PREP", end: "PACK", state: PROPOSED },
|
|
3588
|
-
] }],
|
|
3589
|
-
["bench", { ...base, series: "bench", label: "Bench 2", sub: some("slots"), allocations: [
|
|
3590
|
-
{ key: "a1", phase: "PREP", state: CONFIRMED }, { key: "a2", phase: "BUILD", state: CONFIRMED },
|
|
3591
|
-
{ key: "a3", phase: "BUILD", state: PROPOSED }, { key: "a4", phase: "PACK", state: PROPOSED },
|
|
3592
|
-
] }],
|
|
3593
|
-
["load", { ...base, series: "load", label: "Phase load", cells }],
|
|
3594
|
-
["wip", { ...base, series: "wip", label: "WIP · orders", value: some("31"), points }],
|
|
3595
|
-
["count", { ...base, series: "count", label: "Orders in phase", counts }],
|
|
3596
|
-
["crew", { ...base, series: "crew", label: "Crew B", shifts: [
|
|
3597
|
-
{ key: "s1", from: "INTAKE", to: "PREP", label: "prep crew", state: CONFIRMED },
|
|
3598
|
-
{ key: "s2", from: "BUILD", to: "SHIP", label: "+ finish crew", state: PROPOSED },
|
|
3599
|
-
] }],
|
|
3600
|
-
["gates", { ...base, series: "gates", label: "Gates", marks: [
|
|
3601
|
-
{ key: "g1", phase: "QC", label: "HOLD", exception: true },
|
|
3602
|
-
{ key: "g2", phase: "SHIP", label: "RELEASE", exception: false },
|
|
3603
|
-
] }],
|
|
3604
|
-
]), DictType(StringType, OrderRow));
|
|
3605
|
-
const EXCEPTION = $.const(variant("exception", null), Plan.Types.EventMarkKind);
|
|
3606
|
-
const MILESTONE = $.const(variant("milestone", null), Plan.Types.EventMarkKind);
|
|
3607
|
-
const series = $.const([
|
|
3608
|
-
Plan.series.section(OrderRow, { key: "order-block", title: "Orders", meta: "2 rows" }, [
|
|
3609
|
-
Plan.series.span(OrderRow, {
|
|
3610
|
-
key: "order", title: "Orders",
|
|
3611
|
-
match: r => r.series.equal("order"),
|
|
3612
|
-
label: r => r.label, id: true, value: r => r.value,
|
|
3613
|
-
// `j.start` / `j.end` are StringType fields — the builder wraps them to the ordinal arm.
|
|
3614
|
-
runs: r => r.jobs.map((_$, j) => Plan.run({ key: j.key, start: j.start, end: j.end, label: j.label, state: j.state })),
|
|
3615
|
-
}),
|
|
3616
|
-
]),
|
|
3617
|
-
Plan.series.buckets(OrderRow, {
|
|
3618
|
-
key: "bench", title: "Benches",
|
|
3619
|
-
match: r => r.series.equal("bench"),
|
|
3620
|
-
label: r => r.label, sub: r => r.sub,
|
|
3621
|
-
events: r => r.allocations.map((_$, a) => Plan.event({ key: a.key, at: a.phase, state: a.state })),
|
|
3622
|
-
}),
|
|
3623
|
-
Plan.series.heat(OrderRow, {
|
|
3624
|
-
key: "load", title: "Phase load",
|
|
3625
|
-
match: r => r.series.equal("load"),
|
|
3626
|
-
label: r => r.label,
|
|
3627
|
-
cells: r => Plan.heatCells(r.cells, { min: 0, max: 100, warnAt: 90 }),
|
|
3628
|
-
}),
|
|
3629
|
-
Plan.series.chart(OrderRow, {
|
|
3630
|
-
key: "wip", title: "WIP",
|
|
3631
|
-
match: r => r.series.equal("wip"),
|
|
3632
|
-
label: r => r.label, id: true, value: r => r.value, height: "expanded",
|
|
3633
|
-
// A string x accessor lands the columns on the ordinal arm.
|
|
3634
|
-
layers: r => [Chart.Column(r.points, { x: p => p.phase, y: p => p.n })],
|
|
3635
|
-
}),
|
|
3636
|
-
Plan.series.table(OrderRow, {
|
|
3637
|
-
key: "count", title: "Counts",
|
|
3638
|
-
match: r => r.series.equal("count"),
|
|
3639
|
-
label: r => r.label,
|
|
3640
|
-
cells: r => Plan.tableCells(r.counts),
|
|
3641
|
-
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
3642
|
-
}),
|
|
3643
|
-
Plan.series.cards(OrderRow, {
|
|
3644
|
-
key: "crew", title: "Crews",
|
|
3645
|
-
match: r => r.series.equal("crew"),
|
|
3646
|
-
label: r => r.label,
|
|
3647
|
-
chips: r => r.shifts.map((_$, s) => Plan.chip({ key: s.key, from: s.from, to: s.to, label: s.label, state: s.state })),
|
|
3648
|
-
}),
|
|
3649
|
-
Plan.series.events(OrderRow, {
|
|
3650
|
-
key: "gates", title: "Gates",
|
|
3651
|
-
match: r => r.series.equal("gates"),
|
|
3652
|
-
label: r => r.label, id: true,
|
|
3653
|
-
marks: r => r.marks.map((_$, m) => Plan.mark({
|
|
3654
|
-
key: m.key, at: m.phase, label: m.label,
|
|
3655
|
-
kind: m.exception.ifElse(() => EXCEPTION, () => MILESTONE),
|
|
3656
|
-
})),
|
|
3657
|
-
}),
|
|
3658
|
-
], ArrayType(Plan.Types.Series(OrderRow)));
|
|
3659
|
-
// The declaration: the list IS the axis — one bucket per phase, `now` at BUILD.
|
|
3660
|
-
const axis = $.const(Plan.axis.ordinal({ values: PHASES, now: "BUILD" }));
|
|
3661
|
-
return (_jsx(Plan, { axis: axis, data: orders, series: series, footer: [{ text: "6 PHASES · NOW BUILD" }] }));
|
|
3662
|
-
}),
|
|
3663
|
-
inputs: [],
|
|
3664
|
-
});
|
|
3665
|
-
//# sourceMappingURL=plan.examples.js.map
|