@elaraai/east-claude-plugin 1.0.96 → 1.0.98
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/.claude-plugin/plugin.json +1 -1
- package/index.json +804 -474
- package/package.json +1 -1
- package/skills/e3/SKILL.md +113 -12
- package/skills/e3-ui/SKILL.md +904 -100
- package/skills/e3-ui-cli/SKILL.md +2 -2
- package/skills/east-ui/SKILL.md +15 -38
package/index.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 2,
|
|
3
3
|
"stats": {
|
|
4
|
-
"totalEntries":
|
|
5
|
-
"totalFiles":
|
|
4
|
+
"totalEntries": 1273,
|
|
5
|
+
"totalFiles": 217,
|
|
6
6
|
"packages": {
|
|
7
7
|
"east": 570,
|
|
8
8
|
"east-node-std": 55,
|
|
9
9
|
"east-node-io": 63,
|
|
10
10
|
"east-py-datascience": 94,
|
|
11
|
-
"east-ui":
|
|
12
|
-
"e3-ui":
|
|
11
|
+
"east-ui": 307,
|
|
12
|
+
"e3-ui": 152,
|
|
13
13
|
"e3": 18,
|
|
14
14
|
"e3-ui-cli": 8,
|
|
15
15
|
"e3-create": 6
|
|
@@ -31635,400 +31635,6 @@
|
|
|
31635
31635
|
],
|
|
31636
31636
|
"source": "// Active-trigger colour overrides on a static pager\n// inputs: []\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const pageBind = $.let(State.bind([IntegerType], \"pagination_custom_page\", 2n));\n const page = $.let(pageBind.read());\n const onPageChange = $.const(East.function([IntegerType], NullType, ($, next) => { $(pageBind.write(next)); }));\n return (\n <Pagination page={page} pageSize={10n} count={200n} onPageChange={onPageChange}\n activeBackground=\"bg.brand.subtle\" activeColor=\"fg.inverse\" />\n );\n }}</Reactive>\n ))"
|
|
31637
31637
|
},
|
|
31638
|
-
{
|
|
31639
|
-
"id": "east-ui:schematic.examples.tsx:schematicPlant",
|
|
31640
|
-
"skill": "east-ui",
|
|
31641
|
-
"package": "east-ui",
|
|
31642
|
-
"file": "east-ui/packages/east-ui/test/collections/schematic.examples.tsx",
|
|
31643
|
-
"suite": "",
|
|
31644
|
-
"test": "Plant floor — tanks and lines in halls with a walkway band and pipe runs",
|
|
31645
|
-
"keywords": [
|
|
31646
|
-
"Schematic",
|
|
31647
|
-
"canvas",
|
|
31648
|
-
"items",
|
|
31649
|
-
"zones",
|
|
31650
|
-
"links",
|
|
31651
|
-
"meter",
|
|
31652
|
-
"status",
|
|
31653
|
-
"hatch",
|
|
31654
|
-
"label",
|
|
31655
|
-
"metric",
|
|
31656
|
-
"parallel",
|
|
31657
|
-
"fan-out",
|
|
31658
|
-
"flow"
|
|
31659
|
-
],
|
|
31660
|
-
"imports": [
|
|
31661
|
-
"import { East, ArrayType, BooleanType, FloatType, IntegerType, NullType, OptionType, StringType, StructType, example, variant, some, none } from \"@elaraai/east\";",
|
|
31662
|
-
"import { State, StatusTokenType, UIComponentType } from \"@elaraai/east-ui\";",
|
|
31663
|
-
"import { Configurator, HStack, Reactive, Schematic, Select, Separator, Slice, Slider, Sparkline, Switch, Text, VStack } from \"@elaraai/east-ui\";"
|
|
31664
|
-
],
|
|
31665
|
-
"languages": [
|
|
31666
|
-
"tsx"
|
|
31667
|
-
],
|
|
31668
|
-
"source": "// Plant floor — tanks and lines in halls with a walkway band and pipe runs\n// inputs: []\nEast.function([], UIComponentType, ($) => {\n const equipment = $.const([\n { id: \"UNIT-04\", x: 3.0, y: 2.6, kind: \"unit · 40 kL\", fill: 28.8, cap: 40.0, metric: \"28.8 kL\", state: some(variant(\"success\", null)), w: 4.5 },\n { id: \"UNIT-05\", x: 8.0, y: 2.6, kind: \"unit · 40 kL\", fill: 36.8, cap: 40.0, metric: \"36.8 kL\", state: some(variant(\"warning\", null)), w: 4.5 },\n { id: \"UNIT-06\", x: 13.0, y: 2.6, kind: \"unit · 40 kL\", fill: 20.4, cap: 40.0, metric: \"20.4 kL\", state: some(variant(\"success\", null)), w: 4.5 },\n { id: \"UNIT-07\", x: 18.0, y: 2.6, kind: \"unit · 60 kL\", fill: 0.0, cap: 60.0, metric: \"empty\", state: some(variant(\"neutral\", null)), w: 4.5 },\n { id: \"QA-1\", x: 25.0, y: 2.6, kind: \"qa hold · 8h\", fill: 4.0, cap: 8.0, metric: \"4h left\", state: some(variant(\"info\", null)), w: 4.5 },\n { id: \"LINE-2\", x: 8.0, y: 9.8, kind: \"pack · sku-241\", fill: 0.0, cap: 0.0, metric: \"1,800 u/h\", state: some(variant(\"success\", null)), w: 13.0 },\n { id: \"BAY-OUT\", x: 24.0, y: 9.8, kind: \"pallets · 5 slots\", fill: 3.0, cap: 5.0, metric: \"3 / 5\", state: some(variant(\"success\", null)), w: 4.5 },\n ]);\n const areas = $.const([\n { id: \"hall-b\", name: \"Hall B\", x: 0.6, y: 0.6, w: 20.0, h: 4.4, pattern: Schematic.outline() },\n { id: \"qa-cell\", name: \"QA Cell\", x: 22.5, y: 0.6, w: 5.5, h: 4.4, pattern: Schematic.outline() },\n { id: \"aisle-3\", name: \"Aisle 3 · 1.6 m walkway\", x: 0.6, y: 6.2, w: 28.0, h: 1.6, pattern: Schematic.hatch() },\n { id: \"dispatch\", name: \"Dispatch\", x: 20.5, y: 8.4, w: 8.1, h: 3.2, pattern: Schematic.outline() },\n ]);\n // Already the resolved link type — passed through with no `link` mapper.\n const orthogonal = variant(\"orthogonal\", { corner: none });\n // p2 + p2b share the same endpoint pair — the renderer fans them into\n // parallel lanes (#180) so both edges (feed + CIP return) stay readable;\n // labels/metrics render mid-path at label zoom.\n const pipes = $.const([\n { key: \"p1\", from: \"UNIT-04\", to: \"LINE-2\", label: some(\"feed\"), metric: some(\"12 m³/h\"), style: Schematic.solid(), route: orthogonal, via: [], layer: none },\n { key: \"p2\", from: \"UNIT-05\", to: \"LINE-2\", label: some(\"feed\"), metric: some(\"9 m³/h\"), style: Schematic.solid(), route: orthogonal, via: [], layer: none },\n { key: \"p2b\", from: \"UNIT-05\", to: \"LINE-2\", label: some(\"CIP return\"), metric: none, style: Schematic.dashed(), route: orthogonal, via: [], layer: none },\n { key: \"p3\", from: \"UNIT-06\", to: \"LINE-2\", label: none, metric: none, style: Schematic.solid(), route: orthogonal, via: [], layer: none },\n { key: \"p4\", from: \"QA-1\", to: \"BAY-OUT\", label: none, metric: some(\"3 pallets/h\"), style: Schematic.dashed(), route: orthogonal, via: [{ x: 26.5, y: 6.8 }], layer: none },\n ], ArrayType(Schematic.Types.Link));\n return (\n <Schematic\n extent={{ width: 29, height: 12.5 }}\n height=\"440px\"\n items={equipment}\n item={e => ({\n key: e.id, x: e.x, y: e.y, label: e.id,\n sublabel: e.kind, icon: \"database\",\n status: e.state,\n meter: { value: e.fill, max: e.cap },\n metric: e.metric,\n width: e.w,\n })}\n zones={areas}\n zone={z => ({ key: z.id, label: z.name, x: z.x, y: z.y, width: z.w, height: z.h, pattern: z.pattern })}\n links={pipes}\n scaleUnit=\"m\"\n grid={true}\n />\n );\n })"
|
|
31669
|
-
},
|
|
31670
|
-
{
|
|
31671
|
-
"id": "east-ui:schematic.examples.tsx:schematicStress",
|
|
31672
|
-
"skill": "east-ui",
|
|
31673
|
-
"package": "east-ui",
|
|
31674
|
-
"file": "east-ui/packages/east-ui/test/collections/schematic.examples.tsx",
|
|
31675
|
-
"suite": "",
|
|
31676
|
-
"test": "Stress probe — 3,000 generated items exercise the LOD ladder and Slice chrome (rail + ghosting) at scale; the pan/zoom perf budget with slice on",
|
|
31677
|
-
"keywords": [
|
|
31678
|
-
"Schematic",
|
|
31679
|
-
"stress",
|
|
31680
|
-
"performance",
|
|
31681
|
-
"LOD",
|
|
31682
|
-
"semantic zoom",
|
|
31683
|
-
"declutter",
|
|
31684
|
-
"minimap",
|
|
31685
|
-
"large",
|
|
31686
|
-
"many items",
|
|
31687
|
-
"slice",
|
|
31688
|
-
"sliceEffect",
|
|
31689
|
-
"pan",
|
|
31690
|
-
"zoom",
|
|
31691
|
-
"10k"
|
|
31692
|
-
],
|
|
31693
|
-
"imports": [
|
|
31694
|
-
"import { East, ArrayType, BooleanType, FloatType, IntegerType, NullType, OptionType, StringType, StructType, example, variant, some, none } from \"@elaraai/east\";",
|
|
31695
|
-
"import { State, StatusTokenType, UIComponentType } from \"@elaraai/east-ui\";",
|
|
31696
|
-
"import { Configurator, HStack, Reactive, Schematic, Select, Separator, Slice, Slider, Sparkline, Switch, Text, VStack } from \"@elaraai/east-ui\";"
|
|
31697
|
-
],
|
|
31698
|
-
"languages": [
|
|
31699
|
-
"tsx"
|
|
31700
|
-
],
|
|
31701
|
-
"source": "// Stress probe — 3,000 generated items exercise the LOD ladder and Slice chrome (rail + ghosting) at scale; the pan/zoom perf budget with slice on\n// inputs: []\nconst STRESS_KINDS = [\"unit\", \"pack\", \"pallet\", \"tank\"];\n\nconst STRESS_GRID_ITEMS = Array.from({ length: 3000 }, (_, i) => {\n const col = i % 50, row = Math.floor(i / 50);\n return {\n id: `U-${String(i).padStart(4, \"0\")}`,\n x: col * 2.0 + 2.0,\n y: row * 2.4 + 2.0,\n load: (i * 37 % 100) / 100,\n kind: STRESS_KINDS[i % STRESS_KINDS.length]!,\n };\n});\n\nEast.function([], UIComponentType, (_$) => {\n const EquipType = StructType({ id: StringType, x: FloatType, y: FloatType, load: FloatType, kind: StringType });\n return (\n <Reactive>{$ => {\n const data = $.const(STRESS_GRID_ITEMS, ArrayType(EquipType));\n // Seed a filter so ~3/4 of the items are slice-excluded (ghosted) —\n // exercises the slice-effect paint path as well as the rail chrome.\n const slice = $.let(Slice.bind(\"ex.schematic.stress\", data, {\n fields: { id: { label: \"ID\" }, kind: { label: \"Kind\" } }, search: [\"id\", \"kind\"],\n initial: { kind: [\"unit\"] },\n }));\n const tagged = $.let(Slice.partition([EquipType], slice));\n return (\n <Schematic\n extent={{ width: 104, height: 100 }}\n height=\"460px\"\n items={tagged}\n item={t => ({\n key: t.value.id, x: t.value.x, y: t.value.y, label: t.value.id,\n icon: \"microchip\", meter: { value: t.value.load, max: 1.0 },\n excluded: t.matched.not(),\n })}\n slice={slice}\n affordances={[\"search\", \"filter\"]}\n sliceOpacity={0.35}\n sliceDesaturate={true}\n selectionMode=\"multiple\"\n minimap={true}\n scaleUnit=\"m\"\n />\n );\n }}</Reactive>\n );\n })"
|
|
31702
|
-
},
|
|
31703
|
-
{
|
|
31704
|
-
"id": "east-ui:schematic.examples.tsx:schematicLinkEdit",
|
|
31705
|
-
"skill": "east-ui",
|
|
31706
|
-
"package": "east-ui",
|
|
31707
|
-
"file": "east-ui/packages/east-ui/test/collections/schematic.examples.tsx",
|
|
31708
|
-
"suite": "",
|
|
31709
|
-
"test": "Link editing — connect drags item→item (draw adds locally, connect is event-only), Shift+drag grows the session, endpoint handles re-target, Del deletes; canConnect vetoes mixer→shipping",
|
|
31710
|
-
"keywords": [
|
|
31711
|
-
"Schematic",
|
|
31712
|
-
"link",
|
|
31713
|
-
"editing",
|
|
31714
|
-
"connect",
|
|
31715
|
-
"linkMode",
|
|
31716
|
-
"session",
|
|
31717
|
-
"onCreateLink",
|
|
31718
|
-
"onSelectLink",
|
|
31719
|
-
"onEditLink",
|
|
31720
|
-
"onDeleteLink",
|
|
31721
|
-
"readOnlyLinks",
|
|
31722
|
-
"readOnly",
|
|
31723
|
-
"draw",
|
|
31724
|
-
"Reactive",
|
|
31725
|
-
"State",
|
|
31726
|
-
"Switch"
|
|
31727
|
-
],
|
|
31728
|
-
"imports": [
|
|
31729
|
-
"import { East, ArrayType, BooleanType, FloatType, IntegerType, NullType, OptionType, StringType, StructType, example, variant, some, none } from \"@elaraai/east\";",
|
|
31730
|
-
"import { State, StatusTokenType, UIComponentType } from \"@elaraai/east-ui\";",
|
|
31731
|
-
"import { Configurator, HStack, Reactive, Schematic, Select, Separator, Slice, Slider, Sparkline, Switch, Text, VStack } from \"@elaraai/east-ui\";"
|
|
31732
|
-
],
|
|
31733
|
-
"languages": [
|
|
31734
|
-
"tsx"
|
|
31735
|
-
],
|
|
31736
|
-
"source": "// Link editing — connect drags item→item (draw adds locally, connect is event-only), Shift+drag grows the session, endpoint handles re-target, Del deletes; canConnect vetoes mixer→shipping\n// inputs: []\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const log = $.let(State.bind([StringType], \"schematic_link_log\", \"—\"));\n const onCreateLink = $.const(East.function([Schematic.Types.LinkCreateEvent], NullType, ($, ev) => {\n $(log.write(East.str`created ${ev.link.from}→${ev.link.to} · session ${East.print(ev.links.size())} · existing ${East.print(ev.existing.size())}`));\n }));\n const onEditLink = $.const(East.function([Schematic.Types.LinkEditEvent], NullType, ($, ev) => {\n $(log.write(East.str`edited ${ev.key} → ${ev.from}→${ev.to}`));\n }));\n const onDeleteLink = $.const(East.function([StringType], NullType, ($, key) => {\n $(log.write(East.str`deleted ${key}`));\n }));\n // Reactive mode switches: `connect` = event-only (plan an operation,\n // nothing drawn); editable off = readOnlyLinks (tool + handles hidden).\n const connectMode = $.let(State.bind([BooleanType], \"ex.link.connectmode\", false));\n const cOn = $.let(connectMode.read());\n const onC = $.const(East.function([BooleanType], NullType, ($, v) => { $(connectMode.write(v)); }));\n const lm = $.let(\n cOn.ifElse(_$ => variant(\"connect\", null), _$ => variant(\"draw\", null)),\n Schematic.Types.LinkMode,\n );\n const editable = $.let(State.bind([BooleanType], \"ex.link.editable\", true));\n const eOn = $.let(editable.read());\n const onE = $.const(East.function([BooleanType], NullType, ($, v) => { $(editable.write(v)); }));\n const roLinks = $.let(eOn.not(), BooleanType);\n // Connection validator: mixers never connect DIRECTLY to shipping —\n // product must pass through a packer (drag MIX-1 → SHIP: no snap).\n const canConnect = $.const(East.function([StringType, StringType], BooleanType, (_$, from, to) =>\n from.substring(0n, 3n).equal(\"MIX\").and(() => to.equal(\"SHIP\")).not()));\n // The mode READOUT follows the switch — the description must never\n // claim event-only while draw mode is adding locally.\n const modeTxt = $.let(cOn.ifElse(_$ => \"CONNECT — event-only, nothing added\", _$ => \"DRAW — adds locally\"));\n const txt = $.let(log.read());\n return (\n <VStack gap=\"3\" align=\"stretch\">\n <HStack gap=\"4\" align=\"center\">\n <Switch checked={eOn} label=\"Links editable\" onChange={onE} />\n <Switch checked={cOn} label=\"Connect mode\" onChange={onC} />\n <Text.MonoLabel>{East.str`${modeTxt} · ${txt}`}</Text.MonoLabel>\n </HStack>\n <Schematic\n extent={{ width: 22, height: 11 }}\n height=\"420px\"\n items={[\n { id: \"MIX-1\", x: 3.0, y: 3.0 }, { id: \"MIX-2\", x: 3.0, y: 8.0 },\n { id: \"PACK-A\", x: 12.0, y: 3.0 }, { id: \"PACK-B\", x: 12.0, y: 8.0 },\n { id: \"SHIP\", x: 19.0, y: 5.5 },\n ]}\n item={r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: \"industry\" })}\n links={[{ id: \"l1\", a: \"PACK-A\", b: \"SHIP\" }]}\n link={l => ({ key: l.id, from: l.a, to: l.b })}\n linkMode={lm}\n readOnlyLinks={roLinks}\n onCreateLink={onCreateLink}\n onEditLink={onEditLink}\n onDeleteLink={onDeleteLink}\n canConnect={canConnect}\n />\n </VStack>\n );\n }}</Reactive>\n ))"
|
|
31737
|
-
},
|
|
31738
|
-
{
|
|
31739
|
-
"id": "east-ui:schematic.examples.tsx:schematicNets",
|
|
31740
|
-
"skill": "east-ui",
|
|
31741
|
-
"package": "east-ui",
|
|
31742
|
-
"file": "east-ui/packages/east-ui/test/collections/schematic.examples.tsx",
|
|
31743
|
-
"suite": "",
|
|
31744
|
-
"test": "Nets — a manifold as ONE row (header bar + stubs, no pairwise explosion); the connect tool grows nets, stub-level selection edits one leg; canConnect keeps CIP-1 supply-only",
|
|
31745
|
-
"keywords": [
|
|
31746
|
-
"Schematic",
|
|
31747
|
-
"net",
|
|
31748
|
-
"nets",
|
|
31749
|
-
"manifold",
|
|
31750
|
-
"bus",
|
|
31751
|
-
"trunk",
|
|
31752
|
-
"header",
|
|
31753
|
-
"bar",
|
|
31754
|
-
"stubs",
|
|
31755
|
-
"sources",
|
|
31756
|
-
"destinations",
|
|
31757
|
-
"via",
|
|
31758
|
-
"label",
|
|
31759
|
-
"linkMode",
|
|
31760
|
-
"onCreateLink",
|
|
31761
|
-
"session",
|
|
31762
|
-
"Reactive",
|
|
31763
|
-
"State",
|
|
31764
|
-
"Switch"
|
|
31765
|
-
],
|
|
31766
|
-
"imports": [
|
|
31767
|
-
"import { East, ArrayType, BooleanType, FloatType, IntegerType, NullType, OptionType, StringType, StructType, example, variant, some, none } from \"@elaraai/east\";",
|
|
31768
|
-
"import { State, StatusTokenType, UIComponentType } from \"@elaraai/east-ui\";",
|
|
31769
|
-
"import { Configurator, HStack, Reactive, Schematic, Select, Separator, Slice, Slider, Sparkline, Switch, Text, VStack } from \"@elaraai/east-ui\";"
|
|
31770
|
-
],
|
|
31771
|
-
"languages": [
|
|
31772
|
-
"tsx"
|
|
31773
|
-
],
|
|
31774
|
-
"source": "// Nets — a manifold as ONE row (header bar + stubs, no pairwise explosion); the connect tool grows nets, stub-level selection edits one leg; canConnect keeps CIP-1 supply-only\n// inputs: []\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const units = $.const([\n { id: \"CIP-1\", x: 2.0, y: 6.0 },\n { id: \"U-01\", x: 9.0, y: 2.0 }, { id: \"U-02\", x: 9.0, y: 4.5 },\n { id: \"U-03\", x: 9.0, y: 7.0 }, { id: \"U-04\", x: 9.0, y: 9.5 },\n { id: \"PK-A\", x: 17.0, y: 3.5 }, { id: \"PK-B\", x: 17.0, y: 8.0 },\n { id: \"DOCK\", x: 23.0, y: 6.0 },\n ]);\n const manifolds = $.const([\n { id: \"cip\", srcs: [\"CIP-1\"], dsts: [\"U-01\", \"U-02\", \"U-03\", \"U-04\"], name: \"CIP supply\", flow: \"12 m³/h\", path: [] },\n { id: \"header\", srcs: [\"PK-A\", \"PK-B\"], dsts: [\"DOCK\"], name: \"outfeed header\", flow: \"\", path: [{ x: 20.0, y: 6.0 }] },\n ], ArrayType(StructType({\n id: StringType,\n srcs: ArrayType(StringType),\n dsts: ArrayType(StringType),\n name: StringType,\n flow: StringType,\n path: ArrayType(StructType({ x: FloatType, y: FloatType })),\n })));\n const log = $.let(State.bind([StringType], \"schematic_net_log\", \"—\"));\n // A Shift-session commit reports the WHOLE growing net — upsert by\n // `net.key` to materialise it as one manifold row.\n const onCreateLink = $.const(East.function([Schematic.Types.LinkCreateEvent], NullType, ($, ev) => {\n $(log.write(East.str`net ${ev.net.key} · ${East.print(ev.net.sources.size())} src → ${East.print(ev.net.destinations.size())} dst · additive ${East.print(ev.additive)}`));\n }));\n // Click a STUB to select one leg (narrow halo), Del removes just that\n // endpoint — onEditNet reports the net's endpoints AFTER the removal.\n const onEditNet = $.const(East.function([Schematic.Types.NetEndpoints], NullType, ($, ev) => {\n $(log.write(East.str`net ${ev.key} edited · ${East.print(ev.sources.size())} src → ${East.print(ev.destinations.size())} dst`));\n }));\n // Connection validator: CIP-1 is supply-only — the connect draft\n // never snaps onto it (covers links, session/net extensions, and\n // re-targets alike).\n const canConnect = $.const(East.function([StringType, StringType], BooleanType, (_$, _from, to) => to.notEqual(\"CIP-1\")));\n // Reactive mode switches: `connect` = event-only (plan the manifold,\n // nothing drawn); editable off = readOnlyLinks (connect tool hidden).\n const connectMode = $.let(State.bind([BooleanType], \"ex.net.connectmode\", false));\n const cOn = $.let(connectMode.read());\n const onC = $.const(East.function([BooleanType], NullType, ($, v) => { $(connectMode.write(v)); }));\n const lm = $.let(\n cOn.ifElse(_$ => variant(\"connect\", null), _$ => variant(\"draw\", null)),\n Schematic.Types.LinkMode,\n );\n const editable = $.let(State.bind([BooleanType], \"ex.net.editable\", true));\n const eOn = $.let(editable.read());\n const onE = $.const(East.function([BooleanType], NullType, ($, v) => { $(editable.write(v)); }));\n const roLinks = $.let(eOn.not(), BooleanType);\n // The mode READOUT follows the switch — the description must never\n // claim event-only while draw mode is adding locally.\n const modeTxt = $.let(cOn.ifElse(_$ => \"CONNECT — event-only, nothing added\", _$ => \"DRAW — adds locally\"));\n const txt = $.let(log.read());\n return (\n <VStack gap=\"3\" align=\"stretch\">\n <HStack gap=\"4\" align=\"center\">\n <Switch checked={eOn} label=\"Links editable\" onChange={onE} />\n <Switch checked={cOn} label=\"Connect mode\" onChange={onC} />\n <Text.MonoLabel>{East.str`${modeTxt} · ${txt}`}</Text.MonoLabel>\n </HStack>\n <Schematic\n extent={{ width: 26, height: 12 }}\n height=\"420px\"\n items={units}\n item={r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: \"industry\" })}\n nets={manifolds}\n net={m => ({ key: m.id, sources: m.srcs, destinations: m.dsts, label: m.name, via: m.path })}\n linkMode={lm}\n readOnlyLinks={roLinks}\n onCreateLink={onCreateLink}\n onEditNet={onEditNet}\n canConnect={canConnect}\n />\n </VStack>\n );\n }}</Reactive>\n ))"
|
|
31775
|
-
},
|
|
31776
|
-
{
|
|
31777
|
-
"id": "east-ui:schematic.examples.tsx:schematicVariants",
|
|
31778
|
-
"skill": "east-ui",
|
|
31779
|
-
"package": "east-ui",
|
|
31780
|
-
"file": "east-ui/packages/east-ui/test/collections/schematic.examples.tsx",
|
|
31781
|
-
"suite": "",
|
|
31782
|
-
"test": "Schematic canvas configurator — a canvas preset axis (minimal / layers / geometry / color / facility) plus a read-only switch driving one live canvas",
|
|
31783
|
-
"keywords": [
|
|
31784
|
-
"Schematic",
|
|
31785
|
-
"canvas",
|
|
31786
|
-
"minimal",
|
|
31787
|
-
"readOnly",
|
|
31788
|
-
"static",
|
|
31789
|
-
"layers",
|
|
31790
|
-
"layer",
|
|
31791
|
-
"visibility",
|
|
31792
|
-
"solo",
|
|
31793
|
-
"lock",
|
|
31794
|
-
"opacity",
|
|
31795
|
-
"toggle",
|
|
31796
|
-
"legend",
|
|
31797
|
-
"geometry",
|
|
31798
|
-
"polygon",
|
|
31799
|
-
"polyline",
|
|
31800
|
-
"circle",
|
|
31801
|
-
"arc",
|
|
31802
|
-
"bulge",
|
|
31803
|
-
"footprint",
|
|
31804
|
-
"zone",
|
|
31805
|
-
"CAD",
|
|
31806
|
-
"shape",
|
|
31807
|
-
"color",
|
|
31808
|
-
"tone",
|
|
31809
|
-
"bg",
|
|
31810
|
-
"override",
|
|
31811
|
-
"style",
|
|
31812
|
-
"facility",
|
|
31813
|
-
"navigator",
|
|
31814
|
-
"minimap",
|
|
31815
|
-
"zoom",
|
|
31816
|
-
"LOD",
|
|
31817
|
-
"click-to-fly",
|
|
31818
|
-
"large",
|
|
31819
|
-
"generate",
|
|
31820
|
-
"Reactive",
|
|
31821
|
-
"State",
|
|
31822
|
-
"SegmentGroup",
|
|
31823
|
-
"Switch",
|
|
31824
|
-
"Configurator",
|
|
31825
|
-
"getTag",
|
|
31826
|
-
"configurator"
|
|
31827
|
-
],
|
|
31828
|
-
"imports": [
|
|
31829
|
-
"import { East, ArrayType, BooleanType, FloatType, IntegerType, NullType, OptionType, StringType, StructType, example, variant, some, none } from \"@elaraai/east\";",
|
|
31830
|
-
"import { State, StatusTokenType, UIComponentType } from \"@elaraai/east-ui\";",
|
|
31831
|
-
"import { Configurator, HStack, Reactive, Schematic, Select, Separator, Slice, Slider, Sparkline, Switch, Text, VStack } from \"@elaraai/east-ui\";"
|
|
31832
|
-
],
|
|
31833
|
-
"languages": [
|
|
31834
|
-
"tsx"
|
|
31835
|
-
],
|
|
31836
|
-
"source": "// Schematic canvas configurator — a canvas preset axis (minimal / layers / geometry / color / facility) plus a read-only switch driving one live canvas\n// inputs: []\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n // readOnly ON makes the shown canvas a pure static picture — the\n // connect / move edit tools never render, leaving just pan / zoom /\n // selection (the old MINIMAL row's fixed prop, now a live switch).\n const roBind = $.let(State.bind([BooleanType], \"schematic_readonly\", false));\n const roOn = $.let(roBind.read());\n const onRo = $.const(East.function([BooleanType], NullType, ($, v) => { $(roBind.write(v)); }));\n // ---- FACILITY preset (schematicFacility fold-in) — 306 units:\n // navigator rail, minimap, semantic zoom, click-to-fly from the\n // navigator; rows generated with East.Array.generate, each carrying\n // a varied footprint shape/size (circle / square / triangle / diamond).\n const UnitType = StructType({\n key: StringType,\n x: FloatType,\n y: FloatType,\n kind: StringType,\n fill: FloatType,\n cap: FloatType,\n metric: StringType,\n status: OptionType(StatusTokenType),\n // Footprint discriminator + half-size (world units) — the item\n // mapper turns these into a circle / square / triangle / diamond.\n shape: IntegerType,\n size: FloatType,\n });\n const statuses = $.const([\n some(variant(\"success\", null)),\n some(variant(\"success\", null)),\n some(variant(\"success\", null)),\n some(variant(\"warning\", null)),\n some(variant(\"info\", null)),\n some(variant(\"neutral\", null)),\n ], ArrayType(OptionType(StatusTokenType)));\n // One generated grid block per zone — geometry and pseudo-random\n // fill computed with East expressions over the unit index. Blocks\n // mix row spacing (tight ⇢ sparse) and add hash jitter so the\n // semantic-zoom declutter has real density variation to chew on.\n const block = $.const(East.function(\n [StringType, FloatType, FloatType, IntegerType, IntegerType, FloatType, FloatType],\n ArrayType(UnitType),\n (_$, prefix, x0, y0, count, cols, spacing, cap) =>\n East.Array.generate(count, UnitType, ($, i) => {\n const col = $.let(i.remainder(cols));\n const row = $.let(i.subtract(col).divide(cols));\n const fill = $.let(i.add(1n).multiply(7919n).remainder(100n).toFloat().divide(100.0).multiply(cap));\n const jx = $.let(i.multiply(2654435761n).remainder(97n).toFloat().divide(97.0).subtract(0.5).multiply(spacing.multiply(0.3)));\n const jy = $.let(i.multiply(40503n).remainder(89n).toFloat().divide(89.0).subtract(0.5).multiply(spacing.multiply(0.3)));\n // Cycle the four footprint shapes (period 4) and three sizes\n // (period 3) independently, so consecutive cells vary in both.\n const shape = $.let(i.remainder(4n));\n const size = $.let(i.remainder(3n).toFloat().multiply(0.3).add(0.55));\n return {\n key: East.str`${prefix}-${East.print(i.add(1n))}`,\n x: x0.add(col.toFloat().multiply(spacing)).add(jx),\n y: y0.add(row.toFloat().multiply(spacing)).add(jy),\n kind: East.str`${East.Float.printFixed(cap, 0n)} t cell`,\n fill,\n cap,\n metric: East.str`${East.Float.printFixed(fill, 1n)} t`,\n status: statuses.get(i.remainder(6n)),\n shape,\n size,\n };\n }),\n ));\n const units = $.let(\n block(\"IN\", 3.0, 3.5, 16n, 8n, 3.1, 20.0)\n .concat(block(\"PA\", 33.0, 3.5, 84n, 14n, 3.1, 40.0))\n .concat(block(\"PB\", 33.0, 27.5, 84n, 21n, 2.0, 40.0))\n .concat(block(\"ST\", 3.0, 15.5, 80n, 10n, 1.6, 12.0))\n .concat(block(\"QA\", 81.0, 3.5, 30n, 5n, 3.6, 60.0))\n .concat(block(\"OUT\", 81.0, 27.5, 12n, 4n, 4.8, 24.0)));\n const facilityAreas = $.const([\n { id: \"inbound\", name: \"Inbound\", x: 1.0, y: 1.0, w: 28.0, h: 10.0 },\n { id: \"hall-a\", name: \"Process Hall A\", x: 31.0, y: 1.0, w: 46.0, h: 22.0 },\n { id: \"hall-b\", name: \"Process Hall B\", x: 31.0, y: 25.0, w: 46.0, h: 22.0 },\n { id: \"storage\", name: \"Storage\", x: 1.0, y: 13.0, w: 28.0, h: 34.0 },\n { id: \"qa\", name: \"Quality\", x: 79.0, y: 1.0, w: 20.0, h: 22.0 },\n { id: \"outbound\", name: \"Outbound\", x: 79.0, y: 25.0, w: 20.0, h: 22.0 },\n ]);\n return (\n <Configurator\n controls={[\n Configurator.Slot(\"Editing\",\n <HStack gap=\"5\" align=\"center\" wrap=\"wrap\">\n <Switch checked={roOn} label=\"Read-only\" onChange={onRo} />\n </HStack>),\n ]}\n preview={\n <Schematic\n extent={{ width: 100, height: 48 }}\n height=\"440px\"\n items={units}\n item={u => ({\n key: u.key, x: u.x, y: u.y, label: u.key,\n sublabel: u.kind, icon: \"cube\",\n status: u.status,\n meter: { value: u.fill, max: u.cap },\n metric: u.metric,\n // Four footprint shapes of varying size, centred on the\n // unit's (x, y) anchor — polygon vertices are absolute world\n // coords, so they are offset from the anchor by ±size.\n footprint: u.shape.equal(0n).ifElse(\n _$ => Schematic.circle(u.size),\n _$ => u.shape.equal(1n).ifElse(\n _$ => Schematic.polygon([ // square\n { x: u.x.subtract(u.size), y: u.y.subtract(u.size) },\n { x: u.x.add(u.size), y: u.y.subtract(u.size) },\n { x: u.x.add(u.size), y: u.y.add(u.size) },\n { x: u.x.subtract(u.size), y: u.y.add(u.size) },\n ]),\n _$ => u.shape.equal(2n).ifElse(\n _$ => Schematic.polygon([ // triangle\n { x: u.x, y: u.y.subtract(u.size) },\n { x: u.x.add(u.size), y: u.y.add(u.size) },\n { x: u.x.subtract(u.size), y: u.y.add(u.size) },\n ]),\n _$ => Schematic.polygon([ // diamond\n { x: u.x, y: u.y.subtract(u.size) },\n { x: u.x.add(u.size), y: u.y },\n { x: u.x, y: u.y.add(u.size) },\n { x: u.x.subtract(u.size), y: u.y },\n ]),\n ),\n ),\n ),\n })}\n zones={facilityAreas}\n zone={z => ({ key: z.id, label: z.name, x: z.x, y: z.y, width: z.w, height: z.h })}\n scaleUnit=\"m\"\n grid={true}\n navigator={true}\n minimap={true}\n readOnly={roOn}\n />\n }\n spec={[\n Configurator.Spec(\"readOnly\", roOn.ifElse(_$ => \"on\", _$ => \"off\")),\n ]}\n />\n );\n }}</Reactive>\n ))"
|
|
31837
|
-
},
|
|
31838
|
-
{
|
|
31839
|
-
"id": "east-ui:schematic.examples.tsx:schematicLayers",
|
|
31840
|
-
"skill": "east-ui",
|
|
31841
|
-
"package": "east-ui",
|
|
31842
|
-
"file": "east-ui/packages/east-ui/test/collections/schematic.examples.tsx",
|
|
31843
|
-
"suite": "",
|
|
31844
|
-
"test": "Layer chrome — shell locked and dimmed, maintenance shipped hidden; the layer panel shows / hides / solos / locks each",
|
|
31845
|
-
"keywords": [
|
|
31846
|
-
"Schematic",
|
|
31847
|
-
"layers",
|
|
31848
|
-
"layer",
|
|
31849
|
-
"visibility",
|
|
31850
|
-
"solo",
|
|
31851
|
-
"lock",
|
|
31852
|
-
"opacity",
|
|
31853
|
-
"toggle",
|
|
31854
|
-
"legend"
|
|
31855
|
-
],
|
|
31856
|
-
"imports": [
|
|
31857
|
-
"import { East, ArrayType, BooleanType, FloatType, IntegerType, NullType, OptionType, StringType, StructType, example, variant, some, none } from \"@elaraai/east\";",
|
|
31858
|
-
"import { State, StatusTokenType, UIComponentType } from \"@elaraai/east-ui\";",
|
|
31859
|
-
"import { Configurator, HStack, Reactive, Schematic, Select, Separator, Slice, Slider, Sparkline, Switch, Text, VStack } from \"@elaraai/east-ui\";"
|
|
31860
|
-
],
|
|
31861
|
-
"languages": [
|
|
31862
|
-
"tsx"
|
|
31863
|
-
],
|
|
31864
|
-
"source": "// Layer chrome — shell locked and dimmed, maintenance shipped hidden; the layer panel shows / hides / solos / locks each\n// inputs: []\nEast.function([], UIComponentType, (_$) => {\n const SCHEMATIC_LAYERS_DATA = [\n { id: \"GATE\", x: 2.5, y: 6.0, kind: \"entry\", sys: \"shell\" },\n { id: \"PUMP-1\", x: 6.0, y: 4.0, kind: \"pump\", sys: \"process\" },\n { id: \"TANK-2\", x: 12.0, y: 4.0, kind: \"tank\", sys: \"process\" },\n { id: \"VALVE-3\", x: 18.0, y: 7.0, kind: \"valve\", sys: \"utilities\" },\n { id: \"SENS-4\", x: 9.0, y: 9.0, kind: \"sensor\", sys: \"maintenance\" },\n ];\n const SCHEMATIC_LAYERS_ROOMS_DATA = [\n { id: \"hall\", name: \"Hall A\", x: 1.0, y: 1.0, w: 22.0, h: 11.0 },\n ];\n const SCHEMATIC_LAYERS_PIPES_DATA = [\n { id: \"p1\", a: \"PUMP-1\", b: \"TANK-2\" },\n { id: \"p2\", a: \"TANK-2\", b: \"VALVE-3\" },\n ];\n return (\n <Schematic\n extent={{ width: 24, height: 13 }}\n height=\"420px\"\n items={SCHEMATIC_LAYERS_DATA}\n item={e => ({ key: e.id, x: e.x, y: e.y, label: e.id, sublabel: e.kind, icon: \"gear\", layer: e.sys })}\n zones={SCHEMATIC_LAYERS_ROOMS_DATA}\n zone={z => ({ key: z.id, label: z.name, x: z.x, y: z.y, width: z.w, height: z.h, layer: \"shell\" })}\n links={SCHEMATIC_LAYERS_PIPES_DATA}\n link={l => ({ key: l.id, from: l.a, to: l.b, layer: \"utilities\" })}\n layers={[\n { key: \"shell\", label: \"Building shell\", tone: \"muted\", locked: true, opacity: 0.45 },\n { key: \"process\", label: \"Process\", tone: \"brand\" },\n { key: \"utilities\", label: \"Utilities\", tone: \"success\" },\n { key: \"maintenance\", label: \"Maintenance\", tone: \"warning\", visible: false },\n ]}\n scaleUnit=\"m\"\n />\n );\n })"
|
|
31865
|
-
},
|
|
31866
|
-
{
|
|
31867
|
-
"id": "east-ui:schematic.examples.tsx:schematicGeometry",
|
|
31868
|
-
"skill": "east-ui",
|
|
31869
|
-
"package": "east-ui",
|
|
31870
|
-
"file": "east-ui/packages/east-ui/test/collections/schematic.examples.tsx",
|
|
31871
|
-
"suite": "",
|
|
31872
|
-
"test": "CAD geometry — polygon and arc-aware polyline zones with polygon / circle item footprints on the metric grid",
|
|
31873
|
-
"keywords": [
|
|
31874
|
-
"Schematic",
|
|
31875
|
-
"geometry",
|
|
31876
|
-
"polygon",
|
|
31877
|
-
"polyline",
|
|
31878
|
-
"circle",
|
|
31879
|
-
"arc",
|
|
31880
|
-
"bulge",
|
|
31881
|
-
"footprint",
|
|
31882
|
-
"zone",
|
|
31883
|
-
"CAD",
|
|
31884
|
-
"shape",
|
|
31885
|
-
"grid"
|
|
31886
|
-
],
|
|
31887
|
-
"imports": [
|
|
31888
|
-
"import { East, ArrayType, BooleanType, FloatType, IntegerType, NullType, OptionType, StringType, StructType, example, variant, some, none } from \"@elaraai/east\";",
|
|
31889
|
-
"import { State, StatusTokenType, UIComponentType } from \"@elaraai/east-ui\";",
|
|
31890
|
-
"import { Configurator, HStack, Reactive, Schematic, Select, Separator, Slice, Slider, Sparkline, Switch, Text, VStack } from \"@elaraai/east-ui\";"
|
|
31891
|
-
],
|
|
31892
|
-
"languages": [
|
|
31893
|
-
"tsx"
|
|
31894
|
-
],
|
|
31895
|
-
"source": "// CAD geometry — polygon and arc-aware polyline zones with polygon / circle item footprints on the metric grid\n// inputs: []\nEast.function([], UIComponentType, (_$) => {\n const SCHEMATIC_GEOMETRY_DATA = [\n { id: \"PUMP-1\", x: 6.0, y: 4.0, kind: \"transfer pump\", state: some(variant(\"success\", null)), fp: true, round: false, r: 0.0,\n pts: [{ x: 3.5, y: 2.2, bulge: 0.0 }, { x: 8.6, y: 3.0, bulge: 0.0 }, { x: 8.0, y: 6.0, bulge: 0.0 }, { x: 2.9, y: 5.2, bulge: 0.0 }] },\n { id: \"UNIT-9\", x: 16.0, y: 4.5, kind: \"storage tank\", state: some(variant(\"warning\", null)), fp: false, round: true, r: 2.4,\n pts: [{ x: 16.0, y: 4.5, bulge: 0.0 }] },\n { id: \"VALVE-3\", x: 21.6, y: 4.5, kind: \"manifold\", state: some(variant(\"info\", null)), fp: false, round: false, r: 0.0,\n pts: [{ x: 21.6, y: 4.5, bulge: 0.0 }] },\n ];\n const SCHEMATIC_GEOMETRY_AREAS_DATA = [\n { id: \"hall-c\", name: \"Hall C\", x: 1.5, y: 1.2, w: 9.1, h: 6.4, road: false,\n pts: [{ x: 1.8, y: 2.0, bulge: 0.0 }, { x: 10.2, y: 1.3, bulge: 0.0 }, { x: 10.6, y: 7.0, bulge: 0.0 }, { x: 2.2, y: 7.6, bulge: 0.0 }] },\n { id: \"svc-rd\", name: \"Service Rd\", x: 1.0, y: 9.0, w: 23.5, h: 3.4, road: true,\n pts: [{ x: 1.5, y: 11.6, bulge: 0.0 }, { x: 7.0, y: 9.8, bulge: 0.5 }, { x: 13.0, y: 11.4, bulge: -0.5 }, { x: 19.0, y: 9.6, bulge: 0.4 }, { x: 24.0, y: 10.8, bulge: 0.0 }] },\n ];\n return (\n <Schematic\n extent={{ width: 26, height: 14 }}\n height=\"440px\"\n items={SCHEMATIC_GEOMETRY_DATA}\n item={e => ({\n key: e.id, x: e.x, y: e.y, label: e.id,\n sublabel: e.kind, status: e.state, icon: \"industry\",\n footprint: e.round.ifElse(\n _$ => Schematic.circle(e.r),\n _$ => e.fp.ifElse(_$ => Schematic.polygon(e.pts), _$ => Schematic.rect()),\n ),\n })}\n zones={SCHEMATIC_GEOMETRY_AREAS_DATA}\n zone={z => ({\n key: z.id, label: z.name, x: z.x, y: z.y, width: z.w, height: z.h,\n geometry: z.road.ifElse(\n _$ => Schematic.polyline(z.pts, { width: 1.4 }),\n _$ => Schematic.polygon(z.pts),\n ),\n })}\n scaleUnit=\"m\"\n grid={true}\n />\n );\n })"
|
|
31896
|
-
},
|
|
31897
|
-
{
|
|
31898
|
-
"id": "east-ui:schematic.examples.tsx:schematicColorOverrides",
|
|
31899
|
-
"skill": "east-ui",
|
|
31900
|
-
"package": "east-ui",
|
|
31901
|
-
"file": "east-ui/packages/east-ui/test/collections/schematic.examples.tsx",
|
|
31902
|
-
"suite": "",
|
|
31903
|
-
"test": "Colour overrides — raw color/bg per item footprint and a toned filled zone, independent of status",
|
|
31904
|
-
"keywords": [
|
|
31905
|
-
"Schematic",
|
|
31906
|
-
"color",
|
|
31907
|
-
"tone",
|
|
31908
|
-
"bg",
|
|
31909
|
-
"override",
|
|
31910
|
-
"fillOpacity",
|
|
31911
|
-
"palette",
|
|
31912
|
-
"style"
|
|
31913
|
-
],
|
|
31914
|
-
"imports": [
|
|
31915
|
-
"import { East, ArrayType, BooleanType, FloatType, IntegerType, NullType, OptionType, StringType, StructType, example, variant, some, none } from \"@elaraai/east\";",
|
|
31916
|
-
"import { State, StatusTokenType, UIComponentType } from \"@elaraai/east-ui\";",
|
|
31917
|
-
"import { Configurator, HStack, Reactive, Schematic, Select, Separator, Slice, Slider, Sparkline, Switch, Text, VStack } from \"@elaraai/east-ui\";"
|
|
31918
|
-
],
|
|
31919
|
-
"languages": [
|
|
31920
|
-
"tsx"
|
|
31921
|
-
],
|
|
31922
|
-
"source": "// Colour overrides — raw color/bg per item footprint and a toned filled zone, independent of status\n// inputs: []\nEast.function([], UIComponentType, (_$) => {\n const SCHEMATIC_COLOR_OVERRIDE_DATA = [\n { id: \"U-1\", x: 4.0, y: 4.0, r: 1.4, fill: \"bg.brand.subtle\" },\n { id: \"U-2\", x: 8.0, y: 4.0, r: 1.4, fill: \"bg.success.subtle\" },\n { id: \"U-3\", x: 12.0, y: 4.0, r: 1.4, fill: \"bg.subtle\" },\n ];\n const SCHEMATIC_COLOR_OVERRIDE_AREAS_DATA = [\n { id: \"bay\", name: \"Bay\", x: 1.5, y: 1.5, w: 13.0, h: 5.5 },\n ];\n return (\n <Schematic\n extent={{ width: 16, height: 8 }}\n height=\"360px\"\n items={SCHEMATIC_COLOR_OVERRIDE_DATA}\n item={e => ({\n key: e.id, x: e.x, y: e.y, label: e.id,\n footprint: Schematic.circle(e.r),\n color: e.fill,\n bg: e.fill,\n fillOpacity: 0.18,\n })}\n zones={SCHEMATIC_COLOR_OVERRIDE_AREAS_DATA}\n zone={z => ({\n key: z.id, label: z.name, x: z.x, y: z.y, width: z.w, height: z.h,\n tone: \"brand\",\n bg: \"bg.brand.subtle\",\n })}\n scaleUnit=\"m\"\n />\n );\n })"
|
|
31923
|
-
},
|
|
31924
|
-
{
|
|
31925
|
-
"id": "east-ui:schematic.examples.tsx:schematicSlice",
|
|
31926
|
-
"skill": "east-ui",
|
|
31927
|
-
"package": "east-ui",
|
|
31928
|
-
"file": "east-ui/packages/east-ui/test/collections/schematic.examples.tsx",
|
|
31929
|
-
"suite": "",
|
|
31930
|
-
"test": "Slice panel — slice effect (Slice.partition tags rows; excluded ghost/desaturate reactively via slice* props) and select filter (marquee selection writes an in-filter; the rest ghost)",
|
|
31931
|
-
"keywords": [
|
|
31932
|
-
"Schematic",
|
|
31933
|
-
"slice",
|
|
31934
|
-
"sliceEffect",
|
|
31935
|
-
"excluded",
|
|
31936
|
-
"ghost",
|
|
31937
|
-
"desaturate",
|
|
31938
|
-
"pulse",
|
|
31939
|
-
"halo",
|
|
31940
|
-
"frame",
|
|
31941
|
-
"partition",
|
|
31942
|
-
"filter",
|
|
31943
|
-
"reactive",
|
|
31944
|
-
"switch",
|
|
31945
|
-
"State",
|
|
31946
|
-
"selection",
|
|
31947
|
-
"selectionMode",
|
|
31948
|
-
"sliceSelectField",
|
|
31949
|
-
"in",
|
|
31950
|
-
"marquee",
|
|
31951
|
-
"onSelectionChange"
|
|
31952
|
-
],
|
|
31953
|
-
"imports": [
|
|
31954
|
-
"import { East, ArrayType, BooleanType, FloatType, IntegerType, NullType, OptionType, StringType, StructType, example, variant, some, none } from \"@elaraai/east\";",
|
|
31955
|
-
"import { State, StatusTokenType, UIComponentType } from \"@elaraai/east-ui\";",
|
|
31956
|
-
"import { Configurator, HStack, Reactive, Schematic, Select, Separator, Slice, Slider, Sparkline, Switch, Text, VStack } from \"@elaraai/east-ui\";"
|
|
31957
|
-
],
|
|
31958
|
-
"languages": [
|
|
31959
|
-
"tsx"
|
|
31960
|
-
],
|
|
31961
|
-
"source": "// Slice panel — slice effect (Slice.partition tags rows; excluded ghost/desaturate reactively via slice* props) and select filter (marquee selection writes an in-filter; the rest ghost)\n// inputs: []\nEast.function([], UIComponentType, (_$) => {\n const SCHEMATIC_SLICE_EFFECT_DATA = [\n { id: \"UNIT-04\", x: 3.5, y: 2.6, kind: \"unit\", st: some(variant(\"success\", null)) },\n { id: \"UNIT-05\", x: 11.5, y: 2.6, kind: \"unit\", st: some(variant(\"success\", null)) },\n { id: \"LINE-2\", x: 4.5, y: 6.2, kind: \"pack\", st: some(variant(\"warning\", null)) },\n { id: \"BAY-OUT\", x: 12.0, y: 6.2, kind: \"pallets\", st: some(variant(\"info\", null)) },\n ];\n const SCHEMATIC_SELECT_FILTER_DATA = [\n { id: \"A\", x: 3.5, y: 3.0, kind: \"unit\" },\n { id: \"B\", x: 8.0, y: 3.0, kind: \"unit\" },\n { id: \"C\", x: 12.5, y: 3.0, kind: \"pack\" },\n { id: \"D\", x: 5.5, y: 6.4, kind: \"unit\" },\n { id: \"E\", x: 10.0, y: 6.4, kind: \"pack\" },\n ];\n const EffectEquipType = StructType({ id: StringType, x: FloatType, y: FloatType, kind: StringType, st: OptionType(StatusTokenType) });\n const FilterEquipType = StructType({ id: StringType, x: FloatType, y: FloatType, kind: StringType });\n return (\n <VStack gap=\"4\" align=\"stretch\">\n <Separator label=\"SLICE EFFECT\" align=\"start\" />\n <Reactive>{$ => {\n const data = $.const(SCHEMATIC_SLICE_EFFECT_DATA, ArrayType(EffectEquipType));\n // Seed an active filter (kind = \"unit\") so two rows already fail\n // the narrowing — the effect is visible at first render: the\n // \"pack\" / \"pallets\" units ghost out, the \"unit\" survivors pulse.\n const slice = $.let(Slice.bind(\"ex.schematic.effect\", data, {\n fields: { id: { label: \"ID\" }, kind: { label: \"Kind\" } }, search: [\"id\", \"kind\"],\n initial: { kind: [\"unit\"] },\n }));\n // Full set tagged with pass/fail — excluded rows STAY (ghosted),\n // rather than the pre-narrowed `Slice.rows`.\n const tagged = $.let(Slice.partition([EffectEquipType], slice));\n\n // Every effect setting is a flat `slice*` prop (SubtypeExprOrValue),\n // so a State-bound switch drives it reactively — flip a switch, the\n // schematic re-renders in place. No builder, no escape hatch.\n const hide = $.let(State.bind([BooleanType], \"ex.effect.hide\", false));\n const desat = $.let(State.bind([BooleanType], \"ex.effect.desat\", true));\n const pulse = $.let(State.bind([BooleanType], \"ex.effect.pulse\", true));\n const frame = $.let(State.bind([BooleanType], \"ex.effect.frame\", true));\n const opacity = $.let(State.bind([FloatType], \"ex.effect.opacity\", 0.35));\n const hideOn = $.let(hide.read());\n const desatOn = $.let(desat.read());\n const pulseOn = $.let(pulse.read());\n const frameOn = $.let(frame.read());\n const opacityVal = $.let(opacity.read());\n const onHide = $.const(East.function([BooleanType], NullType, ($, v) => { $(hide.write(v)); }));\n const onDesat = $.const(East.function([BooleanType], NullType, ($, v) => { $(desat.write(v)); }));\n const onPulse = $.const(East.function([BooleanType], NullType, ($, v) => { $(pulse.write(v)); }));\n const onFrame = $.const(East.function([BooleanType], NullType, ($, v) => { $(frame.write(v)); }));\n const onOpacity = $.const(East.function([FloatType], NullType, ($, v) => { $(opacity.write(v)); }));\n // Reactive emphasis option — `some(pulse)` on, `none` (ring off) off.\n const emphasis = $.let(\n pulseOn.ifElse(_$ => some(variant(\"pulse\", null)), _$ => none),\n OptionType(Schematic.Types.Emphasis),\n );\n return (\n <VStack gap=\"3\" align=\"stretch\">\n <HStack gap=\"4\">\n <Switch checked={hideOn} label=\"Hide filtered\" onChange={onHide} />\n <Switch checked={desatOn} label=\"Desaturate\" onChange={onDesat} />\n <Switch checked={pulseOn} label=\"Pulse ring\" onChange={onPulse} />\n <Switch checked={frameOn} label=\"Frame\" onChange={onFrame} />\n </HStack>\n <HStack gap=\"3\" align=\"center\">\n <Text.MonoLabel>OPACITY</Text.MonoLabel>\n <Slider value={opacityVal} min={0} max={1} step={0.05} onChange={onOpacity} />\n </HStack>\n <Schematic\n extent={{ width: 16, height: 9 }}\n height=\"400px\"\n items={tagged}\n item={t => ({\n key: t.value.id, x: t.value.x, y: t.value.y, label: t.value.id,\n sublabel: t.value.kind, icon: \"database\", status: t.value.st,\n excluded: t.matched.not(),\n })}\n slice={slice}\n affordances={[\"search\", \"filter\"]}\n sliceOpacity={opacityVal}\n sliceDesaturate={desatOn}\n sliceEmphasis={emphasis}\n sliceFrame={frameOn}\n sliceHidden={hideOn}\n />\n </VStack>\n );\n }}</Reactive>\n <Separator label=\"SELECT FILTER\" align=\"start\" />\n <Reactive>{$ => {\n const data = $.const(SCHEMATIC_SELECT_FILTER_DATA, ArrayType(FilterEquipType));\n // No initial filter — everything matches until a selection writes one.\n const slice = $.let(Slice.bind(\"ex.schematic.selfilter\", data, {\n fields: { id: { label: \"ID\" }, kind: { label: \"Kind\" } }, search: [\"id\", \"kind\"],\n }));\n // Full set tagged pass/fail; a marquee selection writes `in(id, keys)`\n // via sliceSelectField, so the non-selected re-tag excluded → ghost.\n const tagged = $.let(Slice.partition([FilterEquipType], slice));\n return (\n <VStack gap=\"3\" align=\"stretch\">\n <Schematic\n extent={{ width: 16, height: 9 }}\n height=\"400px\"\n items={tagged}\n item={t => ({\n key: t.value.id, x: t.value.x, y: t.value.y, label: t.value.id,\n sublabel: t.value.kind, icon: \"database\", excluded: t.matched.not(),\n })}\n slice={slice}\n affordances={[\"filter\"]}\n selectionMode=\"multiple\"\n sliceSelectField=\"id\"\n sliceOpacity={0.2}\n sliceDesaturate={true}\n />\n <Text.MonoLabel>DRAG A BOX TO SELECT · the rest ghost via the slice</Text.MonoLabel>\n </VStack>\n );\n }}</Reactive>\n </VStack>\n );\n })"
|
|
31962
|
-
},
|
|
31963
|
-
{
|
|
31964
|
-
"id": "east-ui:schematic.examples.tsx:schematicInteractions",
|
|
31965
|
-
"skill": "east-ui",
|
|
31966
|
-
"package": "east-ui",
|
|
31967
|
-
"file": "east-ui/packages/east-ui/test/collections/schematic.examples.tsx",
|
|
31968
|
-
"suite": "",
|
|
31969
|
-
"test": "Schematic interaction configurator — a tool axis (select / marquee / zone / move) plus multi-select, zoom-focus and movable switches; hover cards stay on; the aside logs five event lines",
|
|
31970
|
-
"keywords": [
|
|
31971
|
-
"Schematic",
|
|
31972
|
-
"Reactive",
|
|
31973
|
-
"State",
|
|
31974
|
-
"onSelect",
|
|
31975
|
-
"onItemOpen",
|
|
31976
|
-
"double-click",
|
|
31977
|
-
"open",
|
|
31978
|
-
"drill-in",
|
|
31979
|
-
"interactive",
|
|
31980
|
-
"selection",
|
|
31981
|
-
"selectionMode",
|
|
31982
|
-
"single",
|
|
31983
|
-
"multiple",
|
|
31984
|
-
"marquee",
|
|
31985
|
-
"onSelectionChange",
|
|
31986
|
-
"selectZoomFocus",
|
|
31987
|
-
"focus",
|
|
31988
|
-
"zoom",
|
|
31989
|
-
"multi-select",
|
|
31990
|
-
"Switch",
|
|
31991
|
-
"zone",
|
|
31992
|
-
"area",
|
|
31993
|
-
"onSelectZone",
|
|
31994
|
-
"onZoneSelectionChange",
|
|
31995
|
-
"childItemKeys",
|
|
31996
|
-
"move",
|
|
31997
|
-
"reposition",
|
|
31998
|
-
"drag",
|
|
31999
|
-
"onMoveItem",
|
|
32000
|
-
"readOnlyItems",
|
|
32001
|
-
"editing",
|
|
32002
|
-
"group",
|
|
32003
|
-
"hover",
|
|
32004
|
-
"HoverCard",
|
|
32005
|
-
"itemHover",
|
|
32006
|
-
"zoneHover",
|
|
32007
|
-
"linkHover",
|
|
32008
|
-
"Sparkline",
|
|
32009
|
-
"chart",
|
|
32010
|
-
"inspection",
|
|
32011
|
-
"lazy",
|
|
32012
|
-
"viewport",
|
|
32013
|
-
"onViewportChange",
|
|
32014
|
-
"camera",
|
|
32015
|
-
"bounds",
|
|
32016
|
-
"settle",
|
|
32017
|
-
"SegmentGroup",
|
|
32018
|
-
"Configurator",
|
|
32019
|
-
"getTag",
|
|
32020
|
-
"configurator"
|
|
32021
|
-
],
|
|
32022
|
-
"imports": [
|
|
32023
|
-
"import { East, ArrayType, BooleanType, FloatType, IntegerType, NullType, OptionType, StringType, StructType, example, variant, some, none } from \"@elaraai/east\";",
|
|
32024
|
-
"import { State, StatusTokenType, UIComponentType } from \"@elaraai/east-ui\";",
|
|
32025
|
-
"import { Configurator, HStack, Reactive, Schematic, Select, Separator, Slice, Slider, Sparkline, Switch, Text, VStack } from \"@elaraai/east-ui\";"
|
|
32026
|
-
],
|
|
32027
|
-
"languages": [
|
|
32028
|
-
"tsx"
|
|
32029
|
-
],
|
|
32030
|
-
"source": "// Schematic interaction configurator — a tool axis (select / marquee / zone / move) plus multi-select, zoom-focus and movable switches; hover cards stay on; the aside logs five event lines\n// inputs: []\nEast.function([], UIComponentType, (_$) => {\n const SCHEMATIC_INTERACTIONS_ITEMS = [\n { id: \"CELL-A\", x: 3.0, y: 3.0 },\n { id: \"CELL-B\", x: 9.0, y: 3.0 },\n { id: \"M1\", x: 15.0, y: 3.0 },\n { id: \"P1\", x: 6.0, y: 9.0 }, { id: \"P2\", x: 18.0, y: 9.0 },\n ];\n const SCHEMATIC_INTERACTIONS_ZONES = [\n { id: \"hall-a\", name: \"Hall A\", x: 1.0, y: 1.0, w: 10.0, h: 4.5 },\n { id: \"hall-b\", name: \"Hall B\", x: 12.5, y: 1.0, w: 10.5, h: 4.5 },\n { id: \"dock\", name: \"Dock\", x: 1.0, y: 7.0, w: 22.0, h: 4.0 },\n ];\n return (\n <Reactive>{$ => {\n // The tool axis is a bare label array — the control and the\n // per-tool prop mapping below read the same four keys.\n const tools = $.const([\"select\", \"marquee\", \"zone\", \"move\"], ArrayType(StringType));\n // Tool mode — select / marquee / zone / move.\n const toolBind = $.let(State.bind([StringType], \"schematic_tool\", \"select\"));\n const tool = $.let(toolBind.read());\n const onToolChange = $.const(East.function([StringType], NullType, ($, next) => {\n $(toolBind.write(next));\n }));\n // Event log binds + callbacks (verbatim from schematicInteractive).\n const bind = $.let(State.bind([StringType], \"schematic_selected\", \"none\"));\n const opened = $.let(State.bind([StringType], \"schematic_opened\", \"none\"));\n const onSelect = $.const(East.function([StringType], NullType, ($, key) => {\n $(bind.write(key));\n }));\n const onItemOpen = $.const(East.function([StringType], NullType, ($, key) => {\n $(opened.write(key));\n }));\n const selected = $.let(bind.read());\n const openedKey = $.let(opened.read());\n // The full selected-key set is mirrored into State on every change\n // (verbatim from schematicRangeSelect).\n const keysBind = $.let(State.bind([ArrayType(StringType)], \"schematic_selected_keys\", []));\n const onSelectionChange = $.const(East.function([Schematic.Types.SelectionEvent], NullType, ($, ev) => {\n $(keysBind.write(ev.selectedKeys));\n }));\n const keys = $.let(keysBind.read());\n const count = $.let(keys.size());\n // Reactive toggle for the camera-on-select behaviour (schematicRangeSelect's\n // switch, original State key) — applied under the marquee tool.\n const focus = $.let(State.bind([BooleanType], \"ex.schematic.focus\", true));\n const focusOn = $.let(focus.read());\n const onFocus = $.const(East.function([BooleanType], NullType, ($, v) => { $(focus.write(v)); }));\n // Multi-select switch (schematicRangeSelect's original key; the zone\n // example's ex.zone.multi toggled the same selectionMode variant and\n // folds into this one control).\n const multi = $.let(State.bind([BooleanType], \"ex.schematic.multi\", true));\n const multiOn = $.let(multi.read());\n const onMulti = $.const(East.function([BooleanType], NullType, ($, v) => { $(multi.write(v)); }));\n // Zone-selection log line (verbatim from schematicZoneSelect).\n const readout = $.let(State.bind([StringType], \"schematic_zone_sel\", \"—\"));\n const onZoneSelectionChange = $.const(East.function([Schematic.Types.ZoneSelectionEvent], NullType, ($, ev) => {\n $(readout.write(East.str`${East.print(ev.selectedKeys.size())} zone(s) · ${East.print(ev.childItemKeys.size())} item(s) inside`));\n }));\n const zoneTxt = $.let(readout.read());\n // Move log + movable switch (verbatim from schematicItemMove).\n const log = $.let(State.bind([StringType], \"schematic_move_log\", \"—\"));\n const onMoveItem = $.const(East.function([Schematic.Types.ItemMoveEvent], NullType, ($, ev) => {\n $(log.write(East.str`moved ${East.print(ev.keys.size())} item(s) · ${ev.key} → ${East.Float.printFixed(ev.x, 1n)}, ${East.Float.printFixed(ev.y, 1n)}`));\n }));\n const movable = $.let(State.bind([BooleanType], \"ex.move.on\", true));\n const mOn = $.let(movable.read());\n const onM = $.const(East.function([BooleanType], NullType, ($, v) => { $(movable.write(v)); }));\n const moveTxt = $.let(log.read());\n // Debounced viewport line (verbatim from schematicViewport).\n const viewportBind = $.let(State.bind([StringType], \"schematic_viewport\", \"—\"));\n const onViewportChange = $.const(East.function([Schematic.Types.ViewportEvent], NullType, ($, ev) => {\n $(viewportBind.write(East.str`zoom ${East.Float.printFixed(ev.zoom, 1n)} · x ${East.Float.printFixed(ev.minX, 1n)}..${East.Float.printFixed(ev.maxX, 1n)} · y ${East.Float.printFixed(ev.minY, 1n)}..${East.Float.printFixed(ev.maxY, 1n)}`));\n }));\n const viewportTxt = $.let(viewportBind.read());\n // Hover cards (verbatim from schematicHover) — enabled in ALL modes;\n // hover config is orthogonal to the tool.\n const itemHover = $.const(East.function([StringType], UIComponentType, (_$, key) => (\n <VStack gap=\"1\" align=\"flex-start\">\n <Text.MonoLabel>{East.str`${key} · throughput (7d)`}</Text.MonoLabel>\n <Sparkline data={[3.2, 4.1, 3.8, 5.0, 4.6, 5.4, 5.1]} type=\"area\" width=\"180px\" height=\"44px\" />\n </VStack>\n )));\n const zoneHover = $.const(East.function([StringType], UIComponentType, (_$, key) => (\n <Text.MonoLabel>{East.str`${key} — CIP window 14:00–15:00`}</Text.MonoLabel>\n )));\n const linkHover = $.const(East.function([StringType], UIComponentType, (_$, key) => (\n <Text.MonoLabel>{East.str`${key} · 12.4 m³/h`}</Text.MonoLabel>\n )));\n // Per-tool prop mapping — where a prop differs per tool mode it is\n // derived from the mode value with ifElse.\n const selMode = $.let(\n tool.equal(\"select\").ifElse(\n // Select — single-tap inspection.\n _$ => variant(\"single\", null),\n _$ => tool.equal(\"move\").ifElse(\n // Move — keeps `multiple` so dragging a SELECTED item moves\n // the whole selection rigidly (schematicItemMove).\n _$ => variant(\"multiple\", null),\n // Marquee / zone — honour the Multi-select switch (marquee\n // and Shift-extend need `multiple`).\n _$ => multiOn.ifElse(_$ => variant(\"multiple\", null), _$ => variant(\"single\", null)),\n ),\n ),\n Schematic.Types.SelectionMode,\n );\n // Camera-on-select only under the marquee tool: focus ON flies / fits,\n // OFF leaves the camera put (schematicRangeSelect).\n const zoomFocus = $.let(tool.equal(\"marquee\").ifElse(_$ => focusOn, _$ => false), BooleanType);\n // Items reposition only under the move tool, gated by its switch.\n const roItems = $.let(tool.equal(\"move\").ifElse(_$ => mOn.not(), _$ => true), BooleanType);\n return (\n <Configurator\n controls={[\n Configurator.Control(\"Tool\", tool,\n <Select value={tool} onChange={onToolChange} size=\"sm\"\n items={tools.map((_$, t) => Select.Item(t, t))} />),\n // Slots, not Controls: the switches report through the\n // aside's event log rather than as one value each.\n Configurator.Slot(\"Selection\",\n <HStack gap=\"5\" align=\"center\" wrap=\"wrap\">\n <Switch checked={multiOn} label=\"Multi-select (marquee/zone)\" onChange={onMulti} />\n </HStack>),\n Configurator.Slot(\"Camera\",\n <HStack gap=\"5\" align=\"center\" wrap=\"wrap\">\n <Switch checked={focusOn} label=\"Zoom focus (marquee)\" onChange={onFocus} />\n </HStack>),\n Configurator.Slot(\"Items\",\n <HStack gap=\"5\" align=\"center\" wrap=\"wrap\">\n <Switch checked={mOn} label=\"Movable (move)\" onChange={onM} />\n </HStack>),\n ]}\n preview={\n /* schematicZoneSelect's 24 × 12 extent — the zone bodies (and\n the M1 / P1 / P2 rows inside them) need the wider world rect. */\n <Schematic\n navigator={false}\n extent={{ width: 24, height: 12 }}\n height=\"440px\"\n items={SCHEMATIC_INTERACTIONS_ITEMS}\n item={r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: \"industry\" })}\n zones={SCHEMATIC_INTERACTIONS_ZONES}\n zone={z => ({ key: z.id, label: z.name, x: z.x, y: z.y, width: z.w, height: z.h })}\n onSelect={onSelect}\n onItemOpen={onItemOpen}\n selectionMode={selMode}\n selectZoomFocus={zoomFocus}\n onSelectionChange={onSelectionChange}\n onZoneSelectionChange={onZoneSelectionChange}\n readOnlyItems={roItems}\n onMoveItem={onMoveItem}\n itemHover={itemHover}\n zoneHover={zoneHover}\n linkHover={linkHover}\n onViewportChange={onViewportChange}\n />\n }\n aside={{\n label: \"Event log · Reactive\",\n body: (\n <VStack gap=\"1\" align=\"stretch\">\n <Text fontFamily=\"mono\">{East.str`INSPECTING · ${selected} · OPENED · ${openedKey}`}</Text>\n <Text fontFamily=\"mono\">{East.str`SELECTED · ${East.print(count)}`}</Text>\n <Text fontFamily=\"mono\">{East.str`ZONES · ${zoneTxt}`}</Text>\n <Text fontFamily=\"mono\">{East.str`MOVE · ${moveTxt}`}</Text>\n <Text fontFamily=\"mono\">{East.str`VIEW · ${viewportTxt}`}</Text>\n </VStack>\n ),\n }}\n />\n );\n }}</Reactive>\n );\n })"
|
|
32031
|
-
},
|
|
32032
31638
|
{
|
|
32033
31639
|
"id": "east-ui:table.examples.tsx:tableBasic",
|
|
32034
31640
|
"skill": "east-ui",
|
|
@@ -38813,8 +38419,8 @@
|
|
|
38813
38419
|
"scale"
|
|
38814
38420
|
],
|
|
38815
38421
|
"imports": [
|
|
38816
|
-
"import { East, example } from \"@elaraai/east\";",
|
|
38817
|
-
"import { UIComponentType } from \"@elaraai/east-ui\";",
|
|
38422
|
+
"import { ArrayType, East, example, variant } from \"@elaraai/east\";",
|
|
38423
|
+
"import { ColorTokenType, Style, UIComponentType } from \"@elaraai/east-ui\";",
|
|
38818
38424
|
"import { Box, HStack, Text, VStack } from \"@elaraai/east-ui\";"
|
|
38819
38425
|
],
|
|
38820
38426
|
"languages": [
|
|
@@ -38838,8 +38444,8 @@
|
|
|
38838
38444
|
"density"
|
|
38839
38445
|
],
|
|
38840
38446
|
"imports": [
|
|
38841
|
-
"import { East, example } from \"@elaraai/east\";",
|
|
38842
|
-
"import { UIComponentType } from \"@elaraai/east-ui\";",
|
|
38447
|
+
"import { ArrayType, East, example, variant } from \"@elaraai/east\";",
|
|
38448
|
+
"import { ColorTokenType, Style, UIComponentType } from \"@elaraai/east-ui\";",
|
|
38843
38449
|
"import { Box, HStack, Text, VStack } from \"@elaraai/east-ui\";"
|
|
38844
38450
|
],
|
|
38845
38451
|
"languages": [
|
|
@@ -38866,8 +38472,8 @@
|
|
|
38866
38472
|
"shadow"
|
|
38867
38473
|
],
|
|
38868
38474
|
"imports": [
|
|
38869
|
-
"import { East, example } from \"@elaraai/east\";",
|
|
38870
|
-
"import { UIComponentType } from \"@elaraai/east-ui\";",
|
|
38475
|
+
"import { ArrayType, East, example, variant } from \"@elaraai/east\";",
|
|
38476
|
+
"import { ColorTokenType, Style, UIComponentType } from \"@elaraai/east-ui\";",
|
|
38871
38477
|
"import { Box, HStack, Text, VStack } from \"@elaraai/east-ui\";"
|
|
38872
38478
|
],
|
|
38873
38479
|
"languages": [
|
|
@@ -38893,8 +38499,8 @@
|
|
|
38893
38499
|
"transition"
|
|
38894
38500
|
],
|
|
38895
38501
|
"imports": [
|
|
38896
|
-
"import { East, example } from \"@elaraai/east\";",
|
|
38897
|
-
"import { UIComponentType } from \"@elaraai/east-ui\";",
|
|
38502
|
+
"import { ArrayType, East, example, variant } from \"@elaraai/east\";",
|
|
38503
|
+
"import { ColorTokenType, Style, UIComponentType } from \"@elaraai/east-ui\";",
|
|
38898
38504
|
"import { Box, HStack, Text, VStack } from \"@elaraai/east-ui\";"
|
|
38899
38505
|
],
|
|
38900
38506
|
"languages": [
|
|
@@ -38902,6 +38508,37 @@
|
|
|
38902
38508
|
],
|
|
38903
38509
|
"source": "// Four labelled chips previewing the MotionDurationType tokens\n// inputs: []\nEast.function([], UIComponentType, (_$) => {\n return (\n <HStack gap=\"2\">\n <Box padding=\"2\" background=\"bg.subtle\" borderRadius=\"full\"><Text>instant</Text></Box>\n <Box padding=\"2\" background=\"bg.subtle\" borderRadius=\"full\"><Text>fast</Text></Box>\n <Box padding=\"2\" background=\"bg.subtle\" borderRadius=\"full\"><Text>normal</Text></Box>\n <Box padding=\"2\" background=\"bg.subtle\" borderRadius=\"full\"><Text>slow</Text></Box>\n </HStack>\n );\n })"
|
|
38904
38510
|
},
|
|
38511
|
+
{
|
|
38512
|
+
"id": "east-ui:style.examples.tsx:colorTokenSwatches",
|
|
38513
|
+
"skill": "east-ui",
|
|
38514
|
+
"package": "east-ui",
|
|
38515
|
+
"file": "east-ui/packages/east-ui/test/style.examples.tsx",
|
|
38516
|
+
"suite": "",
|
|
38517
|
+
"test": "A typed colour token per shift — a theme hue at a shade, the value a component's colour accessor returns (a Roster shift's `color`); the renderer resolves it as the theme's {colors.<hue>.<shade>}",
|
|
38518
|
+
"keywords": [
|
|
38519
|
+
"Style",
|
|
38520
|
+
"ColorToken",
|
|
38521
|
+
"ColorTokenType",
|
|
38522
|
+
"ColorShadeType",
|
|
38523
|
+
"hue",
|
|
38524
|
+
"shade",
|
|
38525
|
+
"colour",
|
|
38526
|
+
"color",
|
|
38527
|
+
"palette",
|
|
38528
|
+
"fill",
|
|
38529
|
+
"swatch",
|
|
38530
|
+
"Roster.shifts"
|
|
38531
|
+
],
|
|
38532
|
+
"imports": [
|
|
38533
|
+
"import { ArrayType, East, example, variant } from \"@elaraai/east\";",
|
|
38534
|
+
"import { ColorTokenType, Style, UIComponentType } from \"@elaraai/east-ui\";",
|
|
38535
|
+
"import { Box, HStack, Text, VStack } from \"@elaraai/east-ui\";"
|
|
38536
|
+
],
|
|
38537
|
+
"languages": [
|
|
38538
|
+
"tsx"
|
|
38539
|
+
],
|
|
38540
|
+
"source": "// A typed colour token per shift — a theme hue at a shade, the value a component's colour accessor returns (a Roster shift's `color`); the renderer resolves it as the theme's {colors.<hue>.<shade>}\n// inputs: [] returns: [variant(\"teal\", variant(\"500\", null)), variant(\"orange\", variant(\"300\", null)), variant(\"purple\", variant(\"700\", null))]\nEast.function([], ArrayType(ColorTokenType), ($) => {\n // The warehouse's three shifts, each its own hue: early a teal, late an orange, night a purple.\n const swatches = $.const([\n Style.ColorToken(\"teal\", \"500\"),\n Style.ColorToken(\"orange\", \"300\"),\n Style.ColorToken(\"purple\", \"700\"),\n ], ArrayType(ColorTokenType));\n return swatches;\n })"
|
|
38541
|
+
},
|
|
38905
38542
|
{
|
|
38906
38543
|
"id": "east-ui:style.examples.tsx:statusPalette",
|
|
38907
38544
|
"skill": "east-ui",
|
|
@@ -38922,8 +38559,8 @@
|
|
|
38922
38559
|
"semantic"
|
|
38923
38560
|
],
|
|
38924
38561
|
"imports": [
|
|
38925
|
-
"import { East, example } from \"@elaraai/east\";",
|
|
38926
|
-
"import { UIComponentType } from \"@elaraai/east-ui\";",
|
|
38562
|
+
"import { ArrayType, East, example, variant } from \"@elaraai/east\";",
|
|
38563
|
+
"import { ColorTokenType, Style, UIComponentType } from \"@elaraai/east-ui\";",
|
|
38927
38564
|
"import { Box, HStack, Text, VStack } from \"@elaraai/east-ui\";"
|
|
38928
38565
|
],
|
|
38929
38566
|
"languages": [
|
|
@@ -39763,7 +39400,7 @@
|
|
|
39763
39400
|
"languages": [
|
|
39764
39401
|
"tsx"
|
|
39765
39402
|
],
|
|
39766
|
-
"source": "// The kiln yard's board over its own records: its bins, each with the yard's spec and grade beside the board's roles, read a day at a time through the slice; the days it publishes; a check over the bins' own fields; the rail over the bays, the kilns and the loads — each source with its icon, and each narrowing every place its rows appear, the Library's tabs included; the yard's fields as columns; the Arrivals and Resources library; the inspector; and the yard's greedy fit on e3.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex(\"kiln_bins_by_time\", kilnBins, {
|
|
39403
|
+
"source": "// The kiln yard's board over its own records: its bins, each with the yard's spec and grade beside the board's roles, read a day at a time through the slice; the days it publishes; a check over the bins' own fields; the rail over the bays, the kilns and the loads — each source with its icon, and each narrowing every place its rows appear, the Library's tabs included; the yard's fields as columns; the Arrivals and Resources library; the inspector; and the yard's greedy fit on e3.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex.interval(\"kiln_bins_by_time\", kilnBins, { from: \"at\", to: \"at\" });\n\n/** The days of the yard's plan, what the board publishes: Tue 4 May issued, Tue 11 and Wed 12 drafts. */\nexport const yardDays = e3.record(\"yard_days\", DictType(DateTimeType, YardDay), yardDayRows);\n\nexport const yardDaysPatch = e3.mutation.patch(yardDays);\n\nexport const kilns = e3.record(\"kilns\", DictType(StringType, Kiln), yardKilns);\n\nexport const kilnsByGroupMax = e3.recordIndex(\"kilns_by_group_max\", kilns, { on: [\"group\", \"max\"] });\n\nexport const kilnsByName = e3.recordIndex(\"kilns_by_name\", kilns, { on: [\"name\"] });\n\nexport const arrivals = e3.record(\"arrivals\", DictType(StringType, TimberLoad), yardArrivals);\n\nexport const arrivalsBySpec = e3.recordIndex(\"arrivals_by_spec\", arrivals, { on: [\"spec\"] });\n\nexport const yardBays = e3.record(\"yard_bays\", DictType(StringType, Bay), yardBayRows);\n\nexport const yardBaysByArea = e3.recordIndex(\"yard_bays_by_area\", yardBays, { on: [\"area\"] });\n\nexport const yardBaysByCover = e3.recordIndex(\"yard_bays_by_cover\", yardBays, { on: [\"cover\"] });\n\n/** The yard's fit's body: the built-in rules, then a greedy look-ahead over the day's other loads. */\nexport const yardFitBody = East.function([Allocation.Types.FitRequest(KilnBin)], Allocation.Types.FitResult, ($, req) => {\n const answer = $.const(Allocation.answer(KilnBin));\n const rule = $.const(Allocation.fit);\n const ahead = $.const(Allocation.lookahead(KilnBin));\n return answer(req, East.function([Allocation.Types.Candidate], Allocation.Types.Fit, (_$2, c) => ahead(req, c, rule)),\n \"ranked by load, kind and spare days, and what it leaves for the day's other loads\");\n});\n\nexport const yardFit = e3.function(\"yard_fit\", yardFitBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n // What the board writes: the bins and the days of the plan, each through its patch door.\n const bins = $.let(Record.bind(kilnBins, [kilnBinsPatch]));\n const entries = $.let(Data.bindPaged(kilnBins));\n const byTime = $.let(Data.bindPaged(kilnBins, { indexes: [kilnBinsByTime] }));\n const days = $.let(Record.bind(yardDays, [yardDaysPatch]));\n // Every bay, kiln and load is the calculation scope; the narrowing only chooses which show.\n const yard = $.let(Data.bind(kilns));\n const loads = $.let(Data.bind(arrivals));\n const bays = $.let(Data.bind(yardBays));\n const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: [yardBaysByArea, yardBaysByCover] }));\n const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: [kilnsByGroupMax, kilnsByName] }));\n const loadIndex = $.let(Data.bindPaged(arrivals, { indexes: [arrivalsBySpec] }));\n const slice = $.let(Slice.bindRecords(\"ex.allocationKilnYard\", {\n // The day's bins: the board's navigation writes the range; no chip picks the day.\n bin: { source: byTime, range: \"at\" },\n bays: { source: bayIndex, icon: { prefix: \"fas\", name: \"warehouse\" },\n fields: { area: { label: \"Area\", hints: [\"North\", \"South\"] }, cover: { label: \"Cover\", hints: [\"Covered\", \"Open\"] } } },\n // A max narrows within a group (one index on group, then max); a name is searched by its start.\n kilns: { source: kilnIndex, icon: { prefix: \"fas\", name: \"fire\" },\n fields: { group: { label: \"Kiln group\", hints: [\"Hall A\", \"Hall B\", \"Compact\", \"Standard\", \"Large\", \"Bulk\"] }, max: { label: \"Max m³\" }, name: { label: \"Name\" } } },\n arrivals: { source: loadIndex, icon: { prefix: \"fas\", name: \"truck\" }, fields: { spec: { label: \"Spec\", hints: [\"MC08\", \"MC10\", \"MC12\", \"MC14\", \"MC16\", \"MC18\", \"MC20\", \"MC22\"] } } },\n }, {\n presets: [\n { id: \"halls\", name: \"Halls\", where: { kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n { id: \"small\", name: \"Compact and standard\", where: { kilns: { group: [\"Compact\", \"Standard\"] } } },\n { id: \"large\", name: \"Large and bulk\", where: { kilns: { group: [\"Large\", \"Bulk\"] } } },\n { id: \"north\", name: \"North bays, halls\", where: { bays: { area: [\"North\"] }, kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n ],\n }));\n const fit = $.let(Func.bind(yardFit));\n // The yard's check reads its own fields: a bin measured off its booked spec is flagged, never refused.\n const check = $.const(East.function([Allocation.Types.CheckContext(KilnBin)], ArrayType(Allocation.Types.Issue), ($2, cx) => {\n const issues = $2.let([], ArrayType(Allocation.Types.Issue));\n $2.for(cx.plan.bins, ($3, bin, id) => {\n $3.match(bin.arrival, { some: ($4, code) => {\n $4.if(East.notEqual(bin.row.specBooked, bin.row.specMeasured), ($5) => {\n $5(issues.pushLast({ kind: variant(\"custom\", \"Spec mismatch\"), tone: variant(\"neutral\", null),\n title: East.str`${code} · bin ${id}`,\n detail: East.str`Spec booked ${bin.row.specBooked} · measured ${bin.row.specMeasured} · ${bin.lane} ${bin.at.printFormatted(\"HH:mm\")}`,\n bin: some(id), resource: none, flag: some(\"≠ spec\"), fix: none }));\n });\n } });\n });\n return issues;\n }));\n return <Box width=\"100%\" height=\"900px\"><Allocation id=\"allocationKilnYard\" title=\"Kiln loading\" fit={fit} check={check} slice={{ slice }} inspector\n library={[Allocation.library.arrivals(), Allocation.library.resources()]} view={{ layout: \"timeline\", date: yardDay }}\n events={{ bin: Schedule.events(bins, {\n name: \"Bin\", icon: \"box\", entries,\n at: \"at\", resource: { field: \"bay\", of: \"bays\" }, arrival: \"load\",\n quantity: { field: \"m3\", unit: \"m³\" }, parts: { field: \"kilns\", of: \"kilns\" }, hold: \"dryFor\", kind: \"spec\",\n fields: { specMeasured: Schedule.field.text({ label: \"Spec measured\" }), instruction: Schedule.field.text({ label: \"Loading instruction\" }) },\n }) }}\n resources={{\n bays: Schedule.resources(bays.read(), { name: \"Bays\", icon: \"warehouse\", label: b => b.name, offset: b => b.offset }),\n kilns: Schedule.resources(yard.read(), { name: \"Kilns\", icon: \"fire\", label: k => k.name, group: k => k.group,\n meta: k => some(East.str`${k.group} · ${East.Float.roundHalf(k.max)} m³`), note: k => k.note,\n min: k => k.min, max: k => k.max, hold: k => variant(\"days\", k.days), jobs: k => k.charges }),\n }}\n arrivals={Allocation.arrivals(loads.read(), {\n label: l => l.code, meta: l => some(East.str`${l.mill} · ${l.grade} · spec ${l.spec}`),\n quantity: l => l.m3, hold: l => variant(\"days\", l.dryingDays), kind: l => some(l.spec),\n due: l => some(l.due), window: l => some({ from: l.from, to: l.to }),\n details: l => [{ label: \"Settings\", value: l.schedule }],\n // A booked bin's own fields: the ones no role maps.\n values: l => ({ specBooked: l.spec, specMeasured: l.spec, gradeBooked: l.grade.substring(0n, 1n), gradeMeasured: l.grade, instruction: l.instruction }),\n })}\n periods={Schedule.periods(days, { every: \"day\", status: { field: \"state\", draft: \"draft\", published: \"issued\" } })}\n columns={[\n { key: \"spec\", label: \"Spec\", text: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => b.specMeasured, () => East.str`booked ${b.specBooked} · measured ${b.specMeasured}`),\n tone: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => none, () => some(variant(\"warning\", null))) },\n { key: \"grade\", label: \"Grade\", text: b => b.gradeMeasured.startsWith(b.gradeBooked).ifElse(() => b.gradeMeasured, () => East.str`booked ${b.gradeBooked} · measured ${b.gradeMeasured}`), table: false },\n ]} />\n </Box>;\n }}</Reactive>)"
|
|
39767
39404
|
},
|
|
39768
39405
|
{
|
|
39769
39406
|
"id": "e3-ui:allocation.examples.tsx:allocationTable",
|
|
@@ -39794,7 +39431,7 @@
|
|
|
39794
39431
|
"languages": [
|
|
39795
39432
|
"tsx"
|
|
39796
39433
|
],
|
|
39797
|
-
"source": "// The kiln yard as a Table: one row per bay slot and one per extra bin of a batch, in four foldable time bands, narrowed by the bays' area or cover, the kilns' group, max or name, and the loads' spec; the same drafts, library and inspector as the Timeline.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex(\"kiln_bins_by_time\", kilnBins, {
|
|
39434
|
+
"source": "// The kiln yard as a Table: one row per bay slot and one per extra bin of a batch, in four foldable time bands, narrowed by the bays' area or cover, the kilns' group, max or name, and the loads' spec; the same drafts, library and inspector as the Timeline.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex.interval(\"kiln_bins_by_time\", kilnBins, { from: \"at\", to: \"at\" });\n\n/** The days of the yard's plan, what the board publishes: Tue 4 May issued, Tue 11 and Wed 12 drafts. */\nexport const yardDays = e3.record(\"yard_days\", DictType(DateTimeType, YardDay), yardDayRows);\n\nexport const yardDaysPatch = e3.mutation.patch(yardDays);\n\nexport const kilns = e3.record(\"kilns\", DictType(StringType, Kiln), yardKilns);\n\nexport const kilnsByGroupMax = e3.recordIndex(\"kilns_by_group_max\", kilns, { on: [\"group\", \"max\"] });\n\nexport const kilnsByName = e3.recordIndex(\"kilns_by_name\", kilns, { on: [\"name\"] });\n\nexport const arrivals = e3.record(\"arrivals\", DictType(StringType, TimberLoad), yardArrivals);\n\nexport const arrivalsBySpec = e3.recordIndex(\"arrivals_by_spec\", arrivals, { on: [\"spec\"] });\n\nexport const yardBays = e3.record(\"yard_bays\", DictType(StringType, Bay), yardBayRows);\n\nexport const yardBaysByArea = e3.recordIndex(\"yard_bays_by_area\", yardBays, { on: [\"area\"] });\n\nexport const yardBaysByCover = e3.recordIndex(\"yard_bays_by_cover\", yardBays, { on: [\"cover\"] });\n\n/** The yard's fit's body: the built-in rules, then a greedy look-ahead over the day's other loads. */\nexport const yardFitBody = East.function([Allocation.Types.FitRequest(KilnBin)], Allocation.Types.FitResult, ($, req) => {\n const answer = $.const(Allocation.answer(KilnBin));\n const rule = $.const(Allocation.fit);\n const ahead = $.const(Allocation.lookahead(KilnBin));\n return answer(req, East.function([Allocation.Types.Candidate], Allocation.Types.Fit, (_$2, c) => ahead(req, c, rule)),\n \"ranked by load, kind and spare days, and what it leaves for the day's other loads\");\n});\n\nexport const yardFit = e3.function(\"yard_fit\", yardFitBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const bins = $.let(Record.bind(kilnBins, [kilnBinsPatch]));\n const entries = $.let(Data.bindPaged(kilnBins));\n const byTime = $.let(Data.bindPaged(kilnBins, { indexes: [kilnBinsByTime] }));\n const days = $.let(Record.bind(yardDays, [yardDaysPatch]));\n // Every bay, kiln and load is the calculation scope; the narrowing only chooses which show.\n const yard = $.let(Data.bind(kilns));\n const loads = $.let(Data.bind(arrivals));\n const bays = $.let(Data.bind(yardBays));\n const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: [yardBaysByArea, yardBaysByCover] }));\n const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: [kilnsByGroupMax, kilnsByName] }));\n const loadIndex = $.let(Data.bindPaged(arrivals, { indexes: [arrivalsBySpec] }));\n const slice = $.let(Slice.bindRecords(\"ex.allocationTable\", {\n bin: { source: byTime, range: \"at\" },\n bays: { source: bayIndex, icon: { prefix: \"fas\", name: \"warehouse\" },\n fields: { area: { label: \"Area\", hints: [\"North\", \"South\"] }, cover: { label: \"Cover\", hints: [\"Covered\", \"Open\"] } } },\n // A max narrows within a group (one index on group, then max); a name is searched by its start.\n kilns: { source: kilnIndex, icon: { prefix: \"fas\", name: \"fire\" },\n fields: { group: { label: \"Kiln group\", hints: [\"Hall A\", \"Hall B\", \"Compact\", \"Standard\", \"Large\", \"Bulk\"] }, max: { label: \"Max m³\" }, name: { label: \"Name\" } } },\n arrivals: { source: loadIndex, icon: { prefix: \"fas\", name: \"truck\" }, fields: { spec: { label: \"Spec\", hints: [\"MC08\", \"MC10\", \"MC12\", \"MC14\", \"MC16\", \"MC18\", \"MC20\", \"MC22\"] } } },\n }, {\n presets: [\n { id: \"halls\", name: \"Halls\", where: { kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n { id: \"small\", name: \"Compact and standard\", where: { kilns: { group: [\"Compact\", \"Standard\"] } } },\n { id: \"large\", name: \"Large and bulk\", where: { kilns: { group: [\"Large\", \"Bulk\"] } } },\n { id: \"north\", name: \"North bays, halls\", where: { bays: { area: [\"North\"] }, kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n ],\n }));\n const fit = $.let(Func.bind(yardFit));\n return <Box width=\"100%\" height=\"900px\"><Allocation id=\"allocationTable\" title=\"Kiln loading\" fit={fit} slice={{ slice }} inspector\n library={[Allocation.library.arrivals(), Allocation.library.resources()]} view={{ layout: \"table\", date: yardDay }}\n events={{ bin: Schedule.events(bins, {\n name: \"Bin\", icon: \"box\", entries,\n at: \"at\", resource: { field: \"bay\", of: \"bays\" }, arrival: \"load\",\n quantity: { field: \"m3\", unit: \"m³\" }, parts: { field: \"kilns\", of: \"kilns\" }, hold: \"dryFor\", kind: \"spec\",\n fields: { specMeasured: Schedule.field.text({ label: \"Spec measured\" }), instruction: Schedule.field.text({ label: \"Loading instruction\" }) },\n }) }}\n resources={{\n bays: Schedule.resources(bays.read(), { name: \"Bays\", icon: \"warehouse\", label: b => b.name, offset: b => b.offset }),\n kilns: Schedule.resources(yard.read(), { name: \"Kilns\", icon: \"fire\", label: k => k.name, group: k => k.group,\n meta: k => some(East.str`${k.group} · ${East.Float.roundHalf(k.max)} m³`), note: k => k.note,\n min: k => k.min, max: k => k.max, hold: k => variant(\"days\", k.days), jobs: k => k.charges }),\n }}\n arrivals={Allocation.arrivals(loads.read(), {\n label: l => l.code, meta: l => some(East.str`${l.mill} · ${l.grade} · spec ${l.spec}`),\n quantity: l => l.m3, hold: l => variant(\"days\", l.dryingDays), kind: l => some(l.spec),\n due: l => some(l.due), window: l => some({ from: l.from, to: l.to }),\n details: l => [{ label: \"Settings\", value: l.schedule }],\n values: l => ({ specBooked: l.spec, specMeasured: l.spec, gradeBooked: l.grade.substring(0n, 1n), gradeMeasured: l.grade, instruction: l.instruction }),\n })}\n periods={Schedule.periods(days, { every: \"day\", status: { field: \"state\", draft: \"draft\", published: \"issued\" } })}\n columns={[\n { key: \"spec\", label: \"Spec\", text: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => b.specMeasured, () => East.str`booked ${b.specBooked} · measured ${b.specMeasured}`),\n tone: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => none, () => some(variant(\"warning\", null))) },\n { key: \"grade\", label: \"Grade\", text: b => b.gradeMeasured.startsWith(b.gradeBooked).ifElse(() => b.gradeMeasured, () => East.str`booked ${b.gradeBooked} · measured ${b.gradeMeasured}`), table: false },\n ]} />\n </Box>;\n }}</Reactive>)"
|
|
39798
39435
|
},
|
|
39799
39436
|
{
|
|
39800
39437
|
"id": "e3-ui:allocation.examples.tsx:allocationLabels",
|
|
@@ -39824,7 +39461,7 @@
|
|
|
39824
39461
|
"languages": [
|
|
39825
39462
|
"tsx"
|
|
39826
39463
|
],
|
|
39827
|
-
"source": "// The kiln yard in its own words: labels={{ lane: \"Bay\", arrival: { one: \"Delivery\", other: \"Deliveries\" }, resource: \"Kiln\", quantity: \"Vol\" }} names the board's lanes, arrivals, resources and quantity everywhere it writes them — the Table's heads, the Chooser, the placeholders, the drag captions, the banners and the narrowing's summary — and table={{ bands }} sets the Table's time bands to the yard's shifts.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex(\"kiln_bins_by_time\", kilnBins, {
|
|
39464
|
+
"source": "// The kiln yard in its own words: labels={{ lane: \"Bay\", arrival: { one: \"Delivery\", other: \"Deliveries\" }, resource: \"Kiln\", quantity: \"Vol\" }} names the board's lanes, arrivals, resources and quantity everywhere it writes them — the Table's heads, the Chooser, the placeholders, the drag captions, the banners and the narrowing's summary — and table={{ bands }} sets the Table's time bands to the yard's shifts.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex.interval(\"kiln_bins_by_time\", kilnBins, { from: \"at\", to: \"at\" });\n\n/** The days of the yard's plan, what the board publishes: Tue 4 May issued, Tue 11 and Wed 12 drafts. */\nexport const yardDays = e3.record(\"yard_days\", DictType(DateTimeType, YardDay), yardDayRows);\n\nexport const yardDaysPatch = e3.mutation.patch(yardDays);\n\nexport const kilns = e3.record(\"kilns\", DictType(StringType, Kiln), yardKilns);\n\nexport const kilnsByGroup = e3.recordIndex(\"kilns_by_group\", kilns, { on: [\"group\"] });\n\nexport const arrivals = e3.record(\"arrivals\", DictType(StringType, TimberLoad), yardArrivals);\n\nexport const yardBays = e3.record(\"yard_bays\", DictType(StringType, Bay), yardBayRows);\n\nexport const yardBaysByArea = e3.recordIndex(\"yard_bays_by_area\", yardBays, { on: [\"area\"] });\n\n/** The yard's fit's body: the built-in rules, then a greedy look-ahead over the day's other loads. */\nexport const yardFitBody = East.function([Allocation.Types.FitRequest(KilnBin)], Allocation.Types.FitResult, ($, req) => {\n const answer = $.const(Allocation.answer(KilnBin));\n const rule = $.const(Allocation.fit);\n const ahead = $.const(Allocation.lookahead(KilnBin));\n return answer(req, East.function([Allocation.Types.Candidate], Allocation.Types.Fit, (_$2, c) => ahead(req, c, rule)),\n \"ranked by load, kind and spare days, and what it leaves for the day's other loads\");\n});\n\nexport const yardFit = e3.function(\"yard_fit\", yardFitBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const bins = $.let(Record.bind(kilnBins, [kilnBinsPatch]));\n const entries = $.let(Data.bindPaged(kilnBins));\n const byTime = $.let(Data.bindPaged(kilnBins, { indexes: [kilnBinsByTime] }));\n const days = $.let(Record.bind(yardDays, [yardDaysPatch]));\n // Every bay and kiln is the calculation scope; the narrowing only chooses which show.\n const yard = $.let(Data.bind(kilns));\n const loads = $.let(Data.bind(arrivals));\n const bays = $.let(Data.bind(yardBays));\n const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: [yardBaysByArea] }));\n const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: [kilnsByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.allocationLabels\", {\n bin: { source: byTime, range: \"at\" },\n bays: { source: bayIndex, fields: { area: { label: \"Area\", hints: [\"North\", \"South\"] } } },\n kilns: { source: kilnIndex, fields: { group: { label: \"Kiln group\", hints: [\"Hall A\", \"Hall B\", \"Compact\", \"Standard\", \"Large\", \"Bulk\"] } } },\n }, {\n presets: [\n { id: \"halls\", name: \"Halls\", where: { kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n { id: \"small\", name: \"Compact and standard\", where: { kilns: { group: [\"Compact\", \"Standard\"] } } },\n { id: \"large\", name: \"Large and bulk\", where: { kilns: { group: [\"Large\", \"Bulk\"] } } },\n { id: \"north\", name: \"North bays, halls\", where: { bays: { area: [\"North\"] }, kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n ],\n }));\n const fit = $.let(Func.bind(yardFit));\n return <Box width=\"100%\" height=\"900px\"><Allocation id=\"allocationLabels\" title=\"Kiln loading\" fit={fit} slice={{ slice }} inspector\n library={[Allocation.library.arrivals(), Allocation.library.resources()]} view={{ layout: \"table\", date: yardDay }}\n labels={{ lane: \"Bay\", arrival: { one: \"Delivery\", other: \"Deliveries\" }, resource: \"Kiln\", quantity: \"Vol\" }}\n table={{ bands: [{ from: 0, label: \"Night\" }, { from: 6, label: \"Day shift\" }, { from: 18, label: \"Evening\" }] }}\n events={{ bin: Schedule.events(bins, {\n name: \"Bin\", icon: \"box\", entries,\n at: \"at\", resource: { field: \"bay\", of: \"bays\" }, arrival: \"load\",\n quantity: { field: \"m3\", unit: \"m³\" }, parts: { field: \"kilns\", of: \"kilns\" }, hold: \"dryFor\", kind: \"spec\",\n fields: { specMeasured: Schedule.field.text({ label: \"Spec measured\" }), instruction: Schedule.field.text({ label: \"Loading instruction\" }) },\n }) }}\n resources={{\n bays: Schedule.resources(bays.read(), { name: \"Bays\", icon: \"warehouse\", label: b => b.name, offset: b => b.offset }),\n kilns: Schedule.resources(yard.read(), { name: \"Kilns\", icon: \"fire\", label: k => k.name, group: k => k.group,\n meta: k => some(East.str`${k.group} · ${East.Float.roundHalf(k.max)} m³`), note: k => k.note,\n min: k => k.min, max: k => k.max, hold: k => variant(\"days\", k.days), jobs: k => k.charges }),\n }}\n arrivals={Allocation.arrivals(loads.read(), {\n label: l => l.code, meta: l => some(East.str`${l.mill} · ${l.grade} · spec ${l.spec}`),\n quantity: l => l.m3, hold: l => variant(\"days\", l.dryingDays), kind: l => some(l.spec),\n due: l => some(l.due), window: l => some({ from: l.from, to: l.to }),\n details: l => [{ label: \"Settings\", value: l.schedule }],\n values: l => ({ specBooked: l.spec, specMeasured: l.spec, gradeBooked: l.grade.substring(0n, 1n), gradeMeasured: l.grade, instruction: l.instruction }),\n })}\n periods={Schedule.periods(days, { every: \"day\", status: { field: \"state\", draft: \"draft\", published: \"issued\" } })}\n columns={[\n { key: \"spec\", label: \"Spec\", text: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => b.specMeasured, () => East.str`booked ${b.specBooked} · measured ${b.specMeasured}`),\n tone: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => none, () => some(variant(\"warning\", null))) },\n { key: \"grade\", label: \"Grade\", text: b => b.gradeMeasured.startsWith(b.gradeBooked).ifElse(() => b.gradeMeasured, () => East.str`booked ${b.gradeBooked} · measured ${b.gradeMeasured}`), table: false },\n ]} />\n </Box>;\n }}</Reactive>)"
|
|
39828
39465
|
},
|
|
39829
39466
|
{
|
|
39830
39467
|
"id": "e3-ui:allocation.examples.tsx:allocationPublished",
|
|
@@ -39851,7 +39488,7 @@
|
|
|
39851
39488
|
"languages": [
|
|
39852
39489
|
"tsx"
|
|
39853
39490
|
],
|
|
39854
|
-
"source": "// A published day, Tue 4 May, its status the yard's own day issued: read-only under its banner; navigation, the narrowing, the focus, links and the inspector still work.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex(\"kiln_bins_by_time\", kilnBins, {
|
|
39491
|
+
"source": "// A published day, Tue 4 May, its status the yard's own day issued: read-only under its banner; navigation, the narrowing, the focus, links and the inspector still work.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex.interval(\"kiln_bins_by_time\", kilnBins, { from: \"at\", to: \"at\" });\n\n/** The days of the yard's plan, what the board publishes: Tue 4 May issued, Tue 11 and Wed 12 drafts. */\nexport const yardDays = e3.record(\"yard_days\", DictType(DateTimeType, YardDay), yardDayRows);\n\nexport const yardDaysPatch = e3.mutation.patch(yardDays);\n\nexport const kilns = e3.record(\"kilns\", DictType(StringType, Kiln), yardKilns);\n\nexport const kilnsByGroup = e3.recordIndex(\"kilns_by_group\", kilns, { on: [\"group\"] });\n\nexport const arrivals = e3.record(\"arrivals\", DictType(StringType, TimberLoad), yardArrivals);\n\nexport const yardBays = e3.record(\"yard_bays\", DictType(StringType, Bay), yardBayRows);\n\nexport const yardBaysByArea = e3.recordIndex(\"yard_bays_by_area\", yardBays, { on: [\"area\"] });\n\n/** The yard's fit's body: the built-in rules, then a greedy look-ahead over the day's other loads. */\nexport const yardFitBody = East.function([Allocation.Types.FitRequest(KilnBin)], Allocation.Types.FitResult, ($, req) => {\n const answer = $.const(Allocation.answer(KilnBin));\n const rule = $.const(Allocation.fit);\n const ahead = $.const(Allocation.lookahead(KilnBin));\n return answer(req, East.function([Allocation.Types.Candidate], Allocation.Types.Fit, (_$2, c) => ahead(req, c, rule)),\n \"ranked by load, kind and spare days, and what it leaves for the day's other loads\");\n});\n\nexport const yardFit = e3.function(\"yard_fit\", yardFitBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const bins = $.let(Record.bind(kilnBins, [kilnBinsPatch]));\n const entries = $.let(Data.bindPaged(kilnBins));\n const byTime = $.let(Data.bindPaged(kilnBins, { indexes: [kilnBinsByTime] }));\n const days = $.let(Record.bind(yardDays, [yardDaysPatch]));\n // Every bay and kiln is the calculation scope; the narrowing only chooses which show.\n const yard = $.let(Data.bind(kilns));\n const loads = $.let(Data.bind(arrivals));\n const bays = $.let(Data.bind(yardBays));\n const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: [yardBaysByArea] }));\n const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: [kilnsByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.allocationPublished\", {\n bin: { source: byTime, range: \"at\" },\n bays: { source: bayIndex, fields: { area: { label: \"Area\", hints: [\"North\", \"South\"] } } },\n kilns: { source: kilnIndex, fields: { group: { label: \"Kiln group\", hints: [\"Hall A\", \"Hall B\", \"Compact\", \"Standard\", \"Large\", \"Bulk\"] } } },\n }, {\n presets: [\n { id: \"halls\", name: \"Halls\", where: { kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n { id: \"small\", name: \"Compact and standard\", where: { kilns: { group: [\"Compact\", \"Standard\"] } } },\n { id: \"large\", name: \"Large and bulk\", where: { kilns: { group: [\"Large\", \"Bulk\"] } } },\n { id: \"north\", name: \"North bays, halls\", where: { bays: { area: [\"North\"] }, kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n ],\n }));\n const fit = $.let(Func.bind(yardFit));\n return <Box width=\"100%\" height=\"900px\"><Allocation id=\"allocationPublished\" title=\"Kiln loading\" fit={fit} slice={{ slice }} inspector\n library={[Allocation.library.arrivals(), Allocation.library.resources()]} view={{ layout: \"timeline\", date: yardPublishedDay }}\n events={{ bin: Schedule.events(bins, {\n name: \"Bin\", icon: \"box\", entries,\n at: \"at\", resource: { field: \"bay\", of: \"bays\" }, arrival: \"load\",\n quantity: { field: \"m3\", unit: \"m³\" }, parts: { field: \"kilns\", of: \"kilns\" }, hold: \"dryFor\", kind: \"spec\",\n fields: { specMeasured: Schedule.field.text({ label: \"Spec measured\" }), instruction: Schedule.field.text({ label: \"Loading instruction\" }) },\n }) }}\n resources={{\n bays: Schedule.resources(bays.read(), { name: \"Bays\", icon: \"warehouse\", label: b => b.name, offset: b => b.offset }),\n kilns: Schedule.resources(yard.read(), { name: \"Kilns\", icon: \"fire\", label: k => k.name, group: k => k.group,\n meta: k => some(East.str`${k.group} · ${East.Float.roundHalf(k.max)} m³`), note: k => k.note,\n min: k => k.min, max: k => k.max, hold: k => variant(\"days\", k.days), jobs: k => k.charges }),\n }}\n arrivals={Allocation.arrivals(loads.read(), {\n label: l => l.code, meta: l => some(East.str`${l.mill} · ${l.grade} · spec ${l.spec}`),\n quantity: l => l.m3, hold: l => variant(\"days\", l.dryingDays), kind: l => some(l.spec),\n due: l => some(l.due), window: l => some({ from: l.from, to: l.to }),\n details: l => [{ label: \"Settings\", value: l.schedule }],\n values: l => ({ specBooked: l.spec, specMeasured: l.spec, gradeBooked: l.grade.substring(0n, 1n), gradeMeasured: l.grade, instruction: l.instruction }),\n })}\n periods={Schedule.periods(days, { every: \"day\", status: { field: \"state\", draft: \"draft\", published: \"issued\" } })}\n columns={[\n { key: \"spec\", label: \"Spec\", text: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => b.specMeasured, () => East.str`booked ${b.specBooked} · measured ${b.specMeasured}`),\n tone: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => none, () => some(variant(\"warning\", null))) },\n { key: \"grade\", label: \"Grade\", text: b => b.gradeMeasured.startsWith(b.gradeBooked).ifElse(() => b.gradeMeasured, () => East.str`booked ${b.gradeBooked} · measured ${b.gradeMeasured}`), table: false },\n ]} />\n </Box>;\n }}</Reactive>)"
|
|
39855
39492
|
},
|
|
39856
39493
|
{
|
|
39857
39494
|
"id": "e3-ui:allocation.examples.tsx:allocationNoPublish",
|
|
@@ -39878,7 +39515,7 @@
|
|
|
39878
39515
|
"languages": [
|
|
39879
39516
|
"tsx"
|
|
39880
39517
|
],
|
|
39881
|
-
"source": "// The kiln yard with no `periods`: no status chip and no Publish; Save writes the day's bins as drafts.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex(\"kiln_bins_by_time\", kilnBins, {
|
|
39518
|
+
"source": "// The kiln yard with no `periods`: no status chip and no Publish; Save writes the day's bins as drafts.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex.interval(\"kiln_bins_by_time\", kilnBins, { from: \"at\", to: \"at\" });\n\nexport const kilns = e3.record(\"kilns\", DictType(StringType, Kiln), yardKilns);\n\nexport const kilnsByGroup = e3.recordIndex(\"kilns_by_group\", kilns, { on: [\"group\"] });\n\nexport const arrivals = e3.record(\"arrivals\", DictType(StringType, TimberLoad), yardArrivals);\n\nexport const yardBays = e3.record(\"yard_bays\", DictType(StringType, Bay), yardBayRows);\n\nexport const yardBaysByArea = e3.recordIndex(\"yard_bays_by_area\", yardBays, { on: [\"area\"] });\n\n/** The yard's fit's body: the built-in rules, then a greedy look-ahead over the day's other loads. */\nexport const yardFitBody = East.function([Allocation.Types.FitRequest(KilnBin)], Allocation.Types.FitResult, ($, req) => {\n const answer = $.const(Allocation.answer(KilnBin));\n const rule = $.const(Allocation.fit);\n const ahead = $.const(Allocation.lookahead(KilnBin));\n return answer(req, East.function([Allocation.Types.Candidate], Allocation.Types.Fit, (_$2, c) => ahead(req, c, rule)),\n \"ranked by load, kind and spare days, and what it leaves for the day's other loads\");\n});\n\nexport const yardFit = e3.function(\"yard_fit\", yardFitBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const bins = $.let(Record.bind(kilnBins, [kilnBinsPatch]));\n const entries = $.let(Data.bindPaged(kilnBins));\n const byTime = $.let(Data.bindPaged(kilnBins, { indexes: [kilnBinsByTime] }));\n // Every bay and kiln is the calculation scope; the narrowing only chooses which show.\n const yard = $.let(Data.bind(kilns));\n const loads = $.let(Data.bind(arrivals));\n const bays = $.let(Data.bind(yardBays));\n const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: [yardBaysByArea] }));\n const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: [kilnsByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.allocationNoPublish\", {\n bin: { source: byTime, range: \"at\" },\n bays: { source: bayIndex, fields: { area: { label: \"Area\", hints: [\"North\", \"South\"] } } },\n kilns: { source: kilnIndex, fields: { group: { label: \"Kiln group\", hints: [\"Hall A\", \"Hall B\", \"Compact\", \"Standard\", \"Large\", \"Bulk\"] } } },\n }, {\n presets: [\n { id: \"halls\", name: \"Halls\", where: { kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n { id: \"small\", name: \"Compact and standard\", where: { kilns: { group: [\"Compact\", \"Standard\"] } } },\n { id: \"large\", name: \"Large and bulk\", where: { kilns: { group: [\"Large\", \"Bulk\"] } } },\n { id: \"north\", name: \"North bays, halls\", where: { bays: { area: [\"North\"] }, kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n ],\n }));\n const fit = $.let(Func.bind(yardFit));\n return <Box width=\"100%\" height=\"900px\"><Allocation id=\"allocationNoPublish\" title=\"Kiln loading\" fit={fit} slice={{ slice }} inspector\n library={[Allocation.library.arrivals(), Allocation.library.resources()]} view={{ layout: \"timeline\", date: yardDay }}\n events={{ bin: Schedule.events(bins, {\n name: \"Bin\", icon: \"box\", entries,\n at: \"at\", resource: { field: \"bay\", of: \"bays\" }, arrival: \"load\",\n quantity: { field: \"m3\", unit: \"m³\" }, parts: { field: \"kilns\", of: \"kilns\" }, hold: \"dryFor\", kind: \"spec\",\n fields: { specMeasured: Schedule.field.text({ label: \"Spec measured\" }), instruction: Schedule.field.text({ label: \"Loading instruction\" }) },\n }) }}\n resources={{\n bays: Schedule.resources(bays.read(), { name: \"Bays\", icon: \"warehouse\", label: b => b.name, offset: b => b.offset }),\n kilns: Schedule.resources(yard.read(), { name: \"Kilns\", icon: \"fire\", label: k => k.name, group: k => k.group,\n meta: k => some(East.str`${k.group} · ${East.Float.roundHalf(k.max)} m³`), note: k => k.note,\n min: k => k.min, max: k => k.max, hold: k => variant(\"days\", k.days), jobs: k => k.charges }),\n }}\n arrivals={Allocation.arrivals(loads.read(), {\n label: l => l.code, meta: l => some(East.str`${l.mill} · ${l.grade} · spec ${l.spec}`),\n quantity: l => l.m3, hold: l => variant(\"days\", l.dryingDays), kind: l => some(l.spec),\n due: l => some(l.due), window: l => some({ from: l.from, to: l.to }),\n details: l => [{ label: \"Settings\", value: l.schedule }],\n values: l => ({ specBooked: l.spec, specMeasured: l.spec, gradeBooked: l.grade.substring(0n, 1n), gradeMeasured: l.grade, instruction: l.instruction }),\n })}\n columns={[\n { key: \"spec\", label: \"Spec\", text: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => b.specMeasured, () => East.str`booked ${b.specBooked} · measured ${b.specMeasured}`),\n tone: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => none, () => some(variant(\"warning\", null))) },\n { key: \"grade\", label: \"Grade\", text: b => b.gradeMeasured.startsWith(b.gradeBooked).ifElse(() => b.gradeMeasured, () => East.str`booked ${b.gradeBooked} · measured ${b.gradeMeasured}`), table: false },\n ]} />\n </Box>;\n }}</Reactive>)"
|
|
39882
39519
|
},
|
|
39883
39520
|
{
|
|
39884
39521
|
"id": "e3-ui:allocation.examples.tsx:allocationSplit",
|
|
@@ -39906,7 +39543,7 @@
|
|
|
39906
39543
|
"languages": [
|
|
39907
39544
|
"tsx"
|
|
39908
39545
|
],
|
|
39909
|
-
"source": "// Loads split across kilns on Wed 12 May: each bin's parts on their kilns, one load with 4 m³ unallocated and its Partly allocated issue, and a link to each part.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex(\"kiln_bins_by_time\", kilnBins, {
|
|
39546
|
+
"source": "// Loads split across kilns on Wed 12 May: each bin's parts on their kilns, one load with 4 m³ unallocated and its Partly allocated issue, and a link to each part.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex.interval(\"kiln_bins_by_time\", kilnBins, { from: \"at\", to: \"at\" });\n\n/** The days of the yard's plan, what the board publishes: Tue 4 May issued, Tue 11 and Wed 12 drafts. */\nexport const yardDays = e3.record(\"yard_days\", DictType(DateTimeType, YardDay), yardDayRows);\n\nexport const yardDaysPatch = e3.mutation.patch(yardDays);\n\nexport const kilns = e3.record(\"kilns\", DictType(StringType, Kiln), yardKilns);\n\nexport const kilnsByGroup = e3.recordIndex(\"kilns_by_group\", kilns, { on: [\"group\"] });\n\nexport const arrivals = e3.record(\"arrivals\", DictType(StringType, TimberLoad), yardArrivals);\n\nexport const yardBays = e3.record(\"yard_bays\", DictType(StringType, Bay), yardBayRows);\n\nexport const yardBaysByArea = e3.recordIndex(\"yard_bays_by_area\", yardBays, { on: [\"area\"] });\n\n/** The yard's fit's body: the built-in rules, then a greedy look-ahead over the day's other loads. */\nexport const yardFitBody = East.function([Allocation.Types.FitRequest(KilnBin)], Allocation.Types.FitResult, ($, req) => {\n const answer = $.const(Allocation.answer(KilnBin));\n const rule = $.const(Allocation.fit);\n const ahead = $.const(Allocation.lookahead(KilnBin));\n return answer(req, East.function([Allocation.Types.Candidate], Allocation.Types.Fit, (_$2, c) => ahead(req, c, rule)),\n \"ranked by load, kind and spare days, and what it leaves for the day's other loads\");\n});\n\nexport const yardFit = e3.function(\"yard_fit\", yardFitBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const bins = $.let(Record.bind(kilnBins, [kilnBinsPatch]));\n const entries = $.let(Data.bindPaged(kilnBins));\n const byTime = $.let(Data.bindPaged(kilnBins, { indexes: [kilnBinsByTime] }));\n const days = $.let(Record.bind(yardDays, [yardDaysPatch]));\n // Every bay and kiln is the calculation scope; the narrowing only chooses which show.\n const yard = $.let(Data.bind(kilns));\n const loads = $.let(Data.bind(arrivals));\n const bays = $.let(Data.bind(yardBays));\n const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: [yardBaysByArea] }));\n const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: [kilnsByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.allocationSplit\", {\n bin: { source: byTime, range: \"at\" },\n bays: { source: bayIndex, fields: { area: { label: \"Area\", hints: [\"North\", \"South\"] } } },\n kilns: { source: kilnIndex, fields: { group: { label: \"Kiln group\", hints: [\"Hall A\", \"Hall B\", \"Compact\", \"Standard\", \"Large\", \"Bulk\"] } } },\n }, {\n presets: [\n { id: \"halls\", name: \"Halls\", where: { kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n { id: \"small\", name: \"Compact and standard\", where: { kilns: { group: [\"Compact\", \"Standard\"] } } },\n { id: \"large\", name: \"Large and bulk\", where: { kilns: { group: [\"Large\", \"Bulk\"] } } },\n { id: \"north\", name: \"North bays, halls\", where: { bays: { area: [\"North\"] }, kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n ],\n }));\n const fit = $.let(Func.bind(yardFit));\n return <Box width=\"100%\" height=\"900px\"><Allocation id=\"allocationSplit\" title=\"Kiln loading\" fit={fit} slice={{ slice }} inspector\n library={[Allocation.library.arrivals(), Allocation.library.resources()]} view={{ layout: \"timeline\", date: yardSplitDay }}\n events={{ bin: Schedule.events(bins, {\n name: \"Bin\", icon: \"box\", entries,\n at: \"at\", resource: { field: \"bay\", of: \"bays\" }, arrival: \"load\",\n quantity: { field: \"m3\", unit: \"m³\" }, parts: { field: \"kilns\", of: \"kilns\" }, hold: \"dryFor\", kind: \"spec\",\n fields: { specMeasured: Schedule.field.text({ label: \"Spec measured\" }), instruction: Schedule.field.text({ label: \"Loading instruction\" }) },\n }) }}\n resources={{\n bays: Schedule.resources(bays.read(), { name: \"Bays\", icon: \"warehouse\", label: b => b.name, offset: b => b.offset }),\n kilns: Schedule.resources(yard.read(), { name: \"Kilns\", icon: \"fire\", label: k => k.name, group: k => k.group,\n meta: k => some(East.str`${k.group} · ${East.Float.roundHalf(k.max)} m³`), note: k => k.note,\n min: k => k.min, max: k => k.max, hold: k => variant(\"days\", k.days), jobs: k => k.charges }),\n }}\n arrivals={Allocation.arrivals(loads.read(), {\n label: l => l.code, meta: l => some(East.str`${l.mill} · ${l.grade} · spec ${l.spec}`),\n quantity: l => l.m3, hold: l => variant(\"days\", l.dryingDays), kind: l => some(l.spec),\n due: l => some(l.due), window: l => some({ from: l.from, to: l.to }),\n details: l => [{ label: \"Settings\", value: l.schedule }],\n values: l => ({ specBooked: l.spec, specMeasured: l.spec, gradeBooked: l.grade.substring(0n, 1n), gradeMeasured: l.grade, instruction: l.instruction }),\n })}\n periods={Schedule.periods(days, { every: \"day\", status: { field: \"state\", draft: \"draft\", published: \"issued\" } })}\n columns={[\n { key: \"spec\", label: \"Spec\", text: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => b.specMeasured, () => East.str`booked ${b.specBooked} · measured ${b.specMeasured}`),\n tone: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => none, () => some(variant(\"warning\", null))) },\n { key: \"grade\", label: \"Grade\", text: b => b.gradeMeasured.startsWith(b.gradeBooked).ifElse(() => b.gradeMeasured, () => East.str`booked ${b.gradeBooked} · measured ${b.gradeMeasured}`), table: false },\n ]} />\n </Box>;\n }}</Reactive>)"
|
|
39910
39547
|
},
|
|
39911
39548
|
{
|
|
39912
39549
|
"id": "e3-ui:allocation.examples.tsx:allocationWindowed",
|
|
@@ -39939,7 +39576,7 @@
|
|
|
39939
39576
|
"languages": [
|
|
39940
39577
|
"tsx"
|
|
39941
39578
|
],
|
|
39942
|
-
"source": "// Two years of bins read a day at a time through the slice's bin source — an interval index over their times, its range the board's own navigation — and the plan's days by key: only the active day and the open picker's month are requested, never a record whole.\n// inputs: []\n/** Two years of the yard's bins, for the board read a day at a time through its slice. */\nexport const kilnBinHistory = e3.record(\"kiln_bin_history\", DictType(StringType, KilnBin), yardBinHistoryRows);\n\nexport const kilnBinHistoryPatch = e3.mutation.patch(kilnBinHistory);\n\nexport const kilnBinHistoryByTime = e3.recordIndex(\"kiln_bin_history_by_time\", kilnBinHistory, {
|
|
39579
|
+
"source": "// Two years of bins read a day at a time through the slice's bin source — an interval index over their times, its range the board's own navigation — and the plan's days by key: only the active day and the open picker's month are requested, never a record whole.\n// inputs: []\n/** Two years of the yard's bins, for the board read a day at a time through its slice. */\nexport const kilnBinHistory = e3.record(\"kiln_bin_history\", DictType(StringType, KilnBin), yardBinHistoryRows);\n\nexport const kilnBinHistoryPatch = e3.mutation.patch(kilnBinHistory);\n\nexport const kilnBinHistoryByTime = e3.recordIndex.interval(\"kiln_bin_history_by_time\", kilnBinHistory, { from: \"at\", to: \"at\" });\n\n/** The days of those two years, read by key. */\nexport const yardDayHistory = e3.record(\"yard_day_history\", DictType(DateTimeType, YardDay), yardDayHistoryRows);\n\nexport const yardDayHistoryPatch = e3.mutation.patch(yardDayHistory);\n\nexport const kilns = e3.record(\"kilns\", DictType(StringType, Kiln), yardKilns);\n\nexport const kilnsByGroup = e3.recordIndex(\"kilns_by_group\", kilns, { on: [\"group\"] });\n\nexport const arrivals = e3.record(\"arrivals\", DictType(StringType, TimberLoad), yardArrivals);\n\nexport const yardBays = e3.record(\"yard_bays\", DictType(StringType, Bay), yardBayRows);\n\nexport const yardBaysByArea = e3.recordIndex(\"yard_bays_by_area\", yardBays, { on: [\"area\"] });\n\n/** The yard's fit's body: the built-in rules, then a greedy look-ahead over the day's other loads. */\nexport const yardFitBody = East.function([Allocation.Types.FitRequest(KilnBin)], Allocation.Types.FitResult, ($, req) => {\n const answer = $.const(Allocation.answer(KilnBin));\n const rule = $.const(Allocation.fit);\n const ahead = $.const(Allocation.lookahead(KilnBin));\n return answer(req, East.function([Allocation.Types.Candidate], Allocation.Types.Fit, (_$2, c) => ahead(req, c, rule)),\n \"ranked by load, kind and spare days, and what it leaves for the day's other loads\");\n});\n\nexport const yardFit = e3.function(\"yard_fit\", yardFitBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const bins = $.let(Record.bind(kilnBinHistory, [kilnBinHistoryPatch]));\n const entries = $.let(Data.bindPaged(kilnBinHistory));\n const byTime = $.let(Data.bindPaged(kilnBinHistory, { indexes: [kilnBinHistoryByTime] }));\n // The days of the plan read by key: the day, and the open picker's month.\n const days = $.let(Record.bind(yardDayHistory, [yardDayHistoryPatch]));\n const dayEntries = $.let(Data.bindPaged(yardDayHistory));\n // Every bay and kiln is the calculation scope; the narrowing only chooses which show.\n const yard = $.let(Data.bind(kilns));\n const loads = $.let(Data.bind(arrivals));\n const bays = $.let(Data.bind(yardBays));\n const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: [yardBaysByArea] }));\n const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: [kilnsByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.allocationWindowed\", {\n // The day's bins: the board's navigation writes the range.\n bin: { source: byTime, range: \"at\" },\n bays: { source: bayIndex, fields: { area: { label: \"Area\", hints: [\"North\", \"South\"] } } },\n kilns: { source: kilnIndex, fields: { group: { label: \"Kiln group\", hints: [\"Hall A\", \"Hall B\", \"Compact\", \"Standard\", \"Large\", \"Bulk\"] } } },\n }, {\n presets: [\n { id: \"halls\", name: \"Halls\", where: { kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n { id: \"small\", name: \"Compact and standard\", where: { kilns: { group: [\"Compact\", \"Standard\"] } } },\n { id: \"large\", name: \"Large and bulk\", where: { kilns: { group: [\"Large\", \"Bulk\"] } } },\n { id: \"north\", name: \"North bays, halls\", where: { bays: { area: [\"North\"] }, kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n ],\n }));\n const fit = $.let(Func.bind(yardFit));\n return <Box width=\"100%\" height=\"900px\"><Allocation id=\"allocationWindowed\" title=\"Kiln loading\" fit={fit} slice={{ slice }} inspector\n library={[Allocation.library.arrivals(), Allocation.library.resources()]} view={{ layout: \"timeline\", date: yardDay }}\n events={{ bin: Schedule.events(bins, {\n name: \"Bin\", icon: \"box\", entries,\n at: \"at\", resource: { field: \"bay\", of: \"bays\" }, arrival: \"load\",\n quantity: { field: \"m3\", unit: \"m³\" }, parts: { field: \"kilns\", of: \"kilns\" }, hold: \"dryFor\", kind: \"spec\",\n fields: { specMeasured: Schedule.field.text({ label: \"Spec measured\" }), instruction: Schedule.field.text({ label: \"Loading instruction\" }) },\n }) }}\n resources={{\n bays: Schedule.resources(bays.read(), { name: \"Bays\", icon: \"warehouse\", label: b => b.name, offset: b => b.offset }),\n kilns: Schedule.resources(yard.read(), { name: \"Kilns\", icon: \"fire\", label: k => k.name, group: k => k.group,\n meta: k => some(East.str`${k.group} · ${East.Float.roundHalf(k.max)} m³`), note: k => k.note,\n min: k => k.min, max: k => k.max, hold: k => variant(\"days\", k.days), jobs: k => k.charges }),\n }}\n arrivals={Allocation.arrivals(loads.read(), {\n label: l => l.code, meta: l => some(East.str`${l.mill} · ${l.grade} · spec ${l.spec}`),\n quantity: l => l.m3, hold: l => variant(\"days\", l.dryingDays), kind: l => some(l.spec),\n due: l => some(l.due), window: l => some({ from: l.from, to: l.to }),\n details: l => [{ label: \"Settings\", value: l.schedule }],\n values: l => ({ specBooked: l.spec, specMeasured: l.spec, gradeBooked: l.grade.substring(0n, 1n), gradeMeasured: l.grade, instruction: l.instruction }),\n })}\n periods={Schedule.periods(days, { every: \"day\", status: { field: \"state\", draft: \"draft\", published: \"issued\" }, entries: dayEntries })}\n columns={[\n { key: \"spec\", label: \"Spec\", text: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => b.specMeasured, () => East.str`booked ${b.specBooked} · measured ${b.specMeasured}`),\n tone: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => none, () => some(variant(\"warning\", null))) },\n { key: \"grade\", label: \"Grade\", text: b => b.gradeMeasured.startsWith(b.gradeBooked).ifElse(() => b.gradeMeasured, () => East.str`booked ${b.gradeBooked} · measured ${b.gradeMeasured}`), table: false },\n ]} />\n </Box>;\n }}</Reactive>)"
|
|
39943
39580
|
},
|
|
39944
39581
|
{
|
|
39945
39582
|
"id": "e3-ui:allocation.examples.tsx:allocationMobile",
|
|
@@ -39966,7 +39603,7 @@
|
|
|
39966
39603
|
"languages": [
|
|
39967
39604
|
"tsx"
|
|
39968
39605
|
],
|
|
39969
|
-
"source": "// The kiln yard in a 390px container: a card per kiln (or per slot in the Table) with Book, Reserve, Allocate, Unlink and Back to Arrivals in anchored popovers; no drag source or target.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex(\"kiln_bins_by_time\", kilnBins, {
|
|
39606
|
+
"source": "// The kiln yard in a 390px container: a card per kiln (or per slot in the Table) with Book, Reserve, Allocate, Unlink and Back to Arrivals in anchored popovers; no drag source or target.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex.interval(\"kiln_bins_by_time\", kilnBins, { from: \"at\", to: \"at\" });\n\n/** The days of the yard's plan, what the board publishes: Tue 4 May issued, Tue 11 and Wed 12 drafts. */\nexport const yardDays = e3.record(\"yard_days\", DictType(DateTimeType, YardDay), yardDayRows);\n\nexport const yardDaysPatch = e3.mutation.patch(yardDays);\n\nexport const kilns = e3.record(\"kilns\", DictType(StringType, Kiln), yardKilns);\n\nexport const kilnsByGroup = e3.recordIndex(\"kilns_by_group\", kilns, { on: [\"group\"] });\n\nexport const arrivals = e3.record(\"arrivals\", DictType(StringType, TimberLoad), yardArrivals);\n\nexport const yardBays = e3.record(\"yard_bays\", DictType(StringType, Bay), yardBayRows);\n\nexport const yardBaysByArea = e3.recordIndex(\"yard_bays_by_area\", yardBays, { on: [\"area\"] });\n\n/** The yard's fit's body: the built-in rules, then a greedy look-ahead over the day's other loads. */\nexport const yardFitBody = East.function([Allocation.Types.FitRequest(KilnBin)], Allocation.Types.FitResult, ($, req) => {\n const answer = $.const(Allocation.answer(KilnBin));\n const rule = $.const(Allocation.fit);\n const ahead = $.const(Allocation.lookahead(KilnBin));\n return answer(req, East.function([Allocation.Types.Candidate], Allocation.Types.Fit, (_$2, c) => ahead(req, c, rule)),\n \"ranked by load, kind and spare days, and what it leaves for the day's other loads\");\n});\n\nexport const yardFit = e3.function(\"yard_fit\", yardFitBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const bins = $.let(Record.bind(kilnBins, [kilnBinsPatch]));\n const entries = $.let(Data.bindPaged(kilnBins));\n const byTime = $.let(Data.bindPaged(kilnBins, { indexes: [kilnBinsByTime] }));\n const days = $.let(Record.bind(yardDays, [yardDaysPatch]));\n // Every bay and kiln is the calculation scope; the narrowing only chooses which show.\n const yard = $.let(Data.bind(kilns));\n const loads = $.let(Data.bind(arrivals));\n const bays = $.let(Data.bind(yardBays));\n const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: [yardBaysByArea] }));\n const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: [kilnsByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.allocationMobile\", {\n bin: { source: byTime, range: \"at\" },\n bays: { source: bayIndex, fields: { area: { label: \"Area\", hints: [\"North\", \"South\"] } } },\n kilns: { source: kilnIndex, fields: { group: { label: \"Kiln group\", hints: [\"Hall A\", \"Hall B\", \"Compact\", \"Standard\", \"Large\", \"Bulk\"] } } },\n }, {\n presets: [\n { id: \"halls\", name: \"Halls\", where: { kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n { id: \"small\", name: \"Compact and standard\", where: { kilns: { group: [\"Compact\", \"Standard\"] } } },\n { id: \"large\", name: \"Large and bulk\", where: { kilns: { group: [\"Large\", \"Bulk\"] } } },\n { id: \"north\", name: \"North bays, halls\", where: { bays: { area: [\"North\"] }, kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n ],\n }));\n const fit = $.let(Func.bind(yardFit));\n return <Box width=\"100%\" maxWidth=\"390px\" height=\"760px\"><Allocation id=\"allocationMobile\" title=\"Kiln loading\" fit={fit} slice={{ slice }} inspector\n library={[Allocation.library.arrivals(), Allocation.library.resources()]} view={{ layout: \"timeline\", date: yardDay }}\n events={{ bin: Schedule.events(bins, {\n name: \"Bin\", icon: \"box\", entries,\n at: \"at\", resource: { field: \"bay\", of: \"bays\" }, arrival: \"load\",\n quantity: { field: \"m3\", unit: \"m³\" }, parts: { field: \"kilns\", of: \"kilns\" }, hold: \"dryFor\", kind: \"spec\",\n fields: { specMeasured: Schedule.field.text({ label: \"Spec measured\" }), instruction: Schedule.field.text({ label: \"Loading instruction\" }) },\n }) }}\n resources={{\n bays: Schedule.resources(bays.read(), { name: \"Bays\", icon: \"warehouse\", label: b => b.name, offset: b => b.offset }),\n kilns: Schedule.resources(yard.read(), { name: \"Kilns\", icon: \"fire\", label: k => k.name, group: k => k.group,\n meta: k => some(East.str`${k.group} · ${East.Float.roundHalf(k.max)} m³`), note: k => k.note,\n min: k => k.min, max: k => k.max, hold: k => variant(\"days\", k.days), jobs: k => k.charges }),\n }}\n arrivals={Allocation.arrivals(loads.read(), {\n label: l => l.code, meta: l => some(East.str`${l.mill} · ${l.grade} · spec ${l.spec}`),\n quantity: l => l.m3, hold: l => variant(\"days\", l.dryingDays), kind: l => some(l.spec),\n due: l => some(l.due), window: l => some({ from: l.from, to: l.to }),\n details: l => [{ label: \"Settings\", value: l.schedule }],\n values: l => ({ specBooked: l.spec, specMeasured: l.spec, gradeBooked: l.grade.substring(0n, 1n), gradeMeasured: l.grade, instruction: l.instruction }),\n })}\n periods={Schedule.periods(days, { every: \"day\", status: { field: \"state\", draft: \"draft\", published: \"issued\" } })}\n columns={[\n { key: \"spec\", label: \"Spec\", text: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => b.specMeasured, () => East.str`booked ${b.specBooked} · measured ${b.specMeasured}`),\n tone: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => none, () => some(variant(\"warning\", null))) },\n { key: \"grade\", label: \"Grade\", text: b => b.gradeMeasured.startsWith(b.gradeBooked).ifElse(() => b.gradeMeasured, () => East.str`booked ${b.gradeBooked} · measured ${b.gradeMeasured}`), table: false },\n ]} />\n </Box>;\n }}</Reactive>)"
|
|
39970
39607
|
},
|
|
39971
39608
|
{
|
|
39972
39609
|
"id": "e3-ui:allocation.examples.tsx:allocationCustomBin",
|
|
@@ -39993,7 +39630,7 @@
|
|
|
39993
39630
|
"languages": [
|
|
39994
39631
|
"tsx"
|
|
39995
39632
|
],
|
|
39996
|
-
"source": "// The bin kind's own inspector: the yard's typed bin form — its m³ and its loading instruction, each change the bin written back whole — in place of the built-in quantity field and the row's other fields.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex(\"kiln_bins_by_time\", kilnBins, {
|
|
39633
|
+
"source": "// The bin kind's own inspector: the yard's typed bin form — its m³ and its loading instruction, each change the bin written back whole — in place of the built-in quantity field and the row's other fields.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex.interval(\"kiln_bins_by_time\", kilnBins, { from: \"at\", to: \"at\" });\n\n/** The days of the yard's plan, what the board publishes: Tue 4 May issued, Tue 11 and Wed 12 drafts. */\nexport const yardDays = e3.record(\"yard_days\", DictType(DateTimeType, YardDay), yardDayRows);\n\nexport const yardDaysPatch = e3.mutation.patch(yardDays);\n\nexport const kilns = e3.record(\"kilns\", DictType(StringType, Kiln), yardKilns);\n\nexport const kilnsByGroup = e3.recordIndex(\"kilns_by_group\", kilns, { on: [\"group\"] });\n\nexport const arrivals = e3.record(\"arrivals\", DictType(StringType, TimberLoad), yardArrivals);\n\nexport const yardBays = e3.record(\"yard_bays\", DictType(StringType, Bay), yardBayRows);\n\nexport const yardBaysByArea = e3.recordIndex(\"yard_bays_by_area\", yardBays, { on: [\"area\"] });\n\n/** The yard's fit's body: the built-in rules, then a greedy look-ahead over the day's other loads. */\nexport const yardFitBody = East.function([Allocation.Types.FitRequest(KilnBin)], Allocation.Types.FitResult, ($, req) => {\n const answer = $.const(Allocation.answer(KilnBin));\n const rule = $.const(Allocation.fit);\n const ahead = $.const(Allocation.lookahead(KilnBin));\n return answer(req, East.function([Allocation.Types.Candidate], Allocation.Types.Fit, (_$2, c) => ahead(req, c, rule)),\n \"ranked by load, kind and spare days, and what it leaves for the day's other loads\");\n});\n\nexport const yardFit = e3.function(\"yard_fit\", yardFitBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const bins = $.let(Record.bind(kilnBins, [kilnBinsPatch]));\n const entries = $.let(Data.bindPaged(kilnBins));\n const byTime = $.let(Data.bindPaged(kilnBins, { indexes: [kilnBinsByTime] }));\n const days = $.let(Record.bind(yardDays, [yardDaysPatch]));\n // Every bay and kiln is the calculation scope; the narrowing only chooses which show.\n const yard = $.let(Data.bind(kilns));\n const loads = $.let(Data.bind(arrivals));\n const bays = $.let(Data.bind(yardBays));\n const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: [yardBaysByArea] }));\n const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: [kilnsByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.allocationCustomBin\", {\n bin: { source: byTime, range: \"at\" },\n bays: { source: bayIndex, fields: { area: { label: \"Area\", hints: [\"North\", \"South\"] } } },\n kilns: { source: kilnIndex, fields: { group: { label: \"Kiln group\", hints: [\"Hall A\", \"Hall B\", \"Compact\", \"Standard\", \"Large\", \"Bulk\"] } } },\n }, {\n presets: [\n { id: \"halls\", name: \"Halls\", where: { kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n { id: \"small\", name: \"Compact and standard\", where: { kilns: { group: [\"Compact\", \"Standard\"] } } },\n { id: \"large\", name: \"Large and bulk\", where: { kilns: { group: [\"Large\", \"Bulk\"] } } },\n { id: \"north\", name: \"North bays, halls\", where: { bays: { area: [\"North\"] }, kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n ],\n }));\n const fit = $.let(Func.bind(yardFit));\n // The bin's own form: its m³ and its loading instruction, each change the bin written back whole.\n const binForm = $.const(East.function([KilnBin, FunctionType([KilnBin], NullType)], UIComponentType, (_$2, row, update) =>\n <VStack>\n <Field.FloatInput label=\"Quantity\" value={row.m3} min={0} step={1} onChange={(_$3, m3) => update({ bay: row.bay, at: row.at, load: row.load,\n m3, spec: row.spec, dryFor: row.dryFor, kilns: row.kilns, specBooked: row.specBooked, specMeasured: row.specMeasured,\n gradeBooked: row.gradeBooked, gradeMeasured: row.gradeMeasured, instruction: row.instruction })} />\n <Field.StringInput label=\"Loading instruction\" value={row.instruction.unwrap(\"some\", () => \"\")} onChange={(_$3, text) => update({ bay: row.bay,\n at: row.at, load: row.load, m3: row.m3, spec: row.spec, dryFor: row.dryFor, kilns: row.kilns, specBooked: row.specBooked,\n specMeasured: row.specMeasured, gradeBooked: row.gradeBooked, gradeMeasured: row.gradeMeasured,\n instruction: East.equal(text, \"\").ifElse(() => none, () => some(text)) })} />\n </VStack>));\n return <Box width=\"100%\" height=\"900px\"><Allocation id=\"allocationCustomBin\" title=\"Kiln loading\" fit={fit} slice={{ slice }} inspector\n library={[Allocation.library.arrivals(), Allocation.library.resources()]} view={{ layout: \"timeline\", date: yardDay }}\n events={{ bin: Schedule.events(bins, {\n name: \"Bin\", icon: \"box\", entries,\n at: \"at\", resource: { field: \"bay\", of: \"bays\" }, arrival: \"load\",\n quantity: { field: \"m3\", unit: \"m³\" }, parts: { field: \"kilns\", of: \"kilns\" }, hold: \"dryFor\", kind: \"spec\",\n inspector: binForm,\n }) }}\n resources={{\n bays: Schedule.resources(bays.read(), { name: \"Bays\", icon: \"warehouse\", label: b => b.name, offset: b => b.offset }),\n kilns: Schedule.resources(yard.read(), { name: \"Kilns\", icon: \"fire\", label: k => k.name, group: k => k.group,\n meta: k => some(East.str`${k.group} · ${East.Float.roundHalf(k.max)} m³`), note: k => k.note,\n min: k => k.min, max: k => k.max, hold: k => variant(\"days\", k.days), jobs: k => k.charges }),\n }}\n arrivals={Allocation.arrivals(loads.read(), {\n label: l => l.code, meta: l => some(East.str`${l.mill} · ${l.grade} · spec ${l.spec}`),\n quantity: l => l.m3, hold: l => variant(\"days\", l.dryingDays), kind: l => some(l.spec),\n due: l => some(l.due), window: l => some({ from: l.from, to: l.to }),\n details: l => [{ label: \"Settings\", value: l.schedule }],\n values: l => ({ specBooked: l.spec, specMeasured: l.spec, gradeBooked: l.grade.substring(0n, 1n), gradeMeasured: l.grade, instruction: l.instruction }),\n })}\n periods={Schedule.periods(days, { every: \"day\", status: { field: \"state\", draft: \"draft\", published: \"issued\" } })}\n columns={[\n { key: \"spec\", label: \"Spec\", text: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => b.specMeasured, () => East.str`booked ${b.specBooked} · measured ${b.specMeasured}`),\n tone: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => none, () => some(variant(\"warning\", null))) },\n { key: \"grade\", label: \"Grade\", text: b => b.gradeMeasured.startsWith(b.gradeBooked).ifElse(() => b.gradeMeasured, () => East.str`booked ${b.gradeBooked} · measured ${b.gradeMeasured}`), table: false },\n ]} />\n </Box>;\n }}</Reactive>)"
|
|
39997
39634
|
},
|
|
39998
39635
|
{
|
|
39999
39636
|
"id": "e3-ui:allocation.examples.tsx:allocationFitRule",
|
|
@@ -40021,7 +39658,7 @@
|
|
|
40021
39658
|
"languages": [
|
|
40022
39659
|
"tsx"
|
|
40023
39660
|
],
|
|
40024
|
-
"source": "// The ready-made fit with one yard rule over it, as an e3.function: a bulk kiln takes no load under 20 m³, so LD-2235 (8 m³) fits K-305 alone.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex(\"kiln_bins_by_time\", kilnBins, {
|
|
39661
|
+
"source": "// The ready-made fit with one yard rule over it, as an e3.function: a bulk kiln takes no load under 20 m³, so LD-2235 (8 m³) fits K-305 alone.\n// inputs: []\n/** The bins, every day's, each the yard's own row. */\nexport const kilnBins = e3.record(\"kiln_bins\", DictType(StringType, KilnBin), yardBinRows);\n\nexport const kilnBinsPatch = e3.mutation.patch(kilnBins);\n\n/** The bins by their time: what the board's slice reads a day of them through, its navigation writing the range. */\nexport const kilnBinsByTime = e3.recordIndex.interval(\"kiln_bins_by_time\", kilnBins, { from: \"at\", to: \"at\" });\n\n/** The days of the yard's plan, what the board publishes: Tue 4 May issued, Tue 11 and Wed 12 drafts. */\nexport const yardDays = e3.record(\"yard_days\", DictType(DateTimeType, YardDay), yardDayRows);\n\nexport const yardDaysPatch = e3.mutation.patch(yardDays);\n\nexport const kilns = e3.record(\"kilns\", DictType(StringType, Kiln), yardKilns);\n\nexport const kilnsByGroup = e3.recordIndex(\"kilns_by_group\", kilns, { on: [\"group\"] });\n\nexport const arrivals = e3.record(\"arrivals\", DictType(StringType, TimberLoad), yardArrivals);\n\nexport const yardBays = e3.record(\"yard_bays\", DictType(StringType, Bay), yardBayRows);\n\nexport const yardBaysByArea = e3.recordIndex(\"yard_bays_by_area\", yardBays, { on: [\"area\"] });\n\n/** A yard rule over the ready-made fit: a bulk kiln takes no load under 20 m³. */\nexport const bulkFitBody = East.function([Allocation.Types.FitRequest(KilnBin)], Allocation.Types.FitResult, ($, req) => {\n const answer = $.const(Allocation.answer(KilnBin));\n const rule = $.const(Allocation.fit);\n return answer(req, East.function([Allocation.Types.Candidate], Allocation.Types.Fit, ($2, c) => {\n $2.if(East.equal(c.resource.group, some(\"Bulk\")).and(() => c.reserve.not()).and(() => East.less(c.quantity, 20.0)), ($3) => {\n $3.return(variant(\"refused\", { group: \"Too small\", reason: East.str`bulk kilns take 20 m³ and more`, measure: variant(\"none\", null) }));\n });\n return rule(c);\n }), \"ranked by load, kind and spare days; bulk kilns take 20 m³ and more\");\n});\n\nexport const bulkFit = e3.function(\"yard_bulk_fit\", bulkFitBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const bins = $.let(Record.bind(kilnBins, [kilnBinsPatch]));\n const entries = $.let(Data.bindPaged(kilnBins));\n const byTime = $.let(Data.bindPaged(kilnBins, { indexes: [kilnBinsByTime] }));\n const days = $.let(Record.bind(yardDays, [yardDaysPatch]));\n // Every bay and kiln is the calculation scope; the narrowing only chooses which show.\n const yard = $.let(Data.bind(kilns));\n const loads = $.let(Data.bind(arrivals));\n const bays = $.let(Data.bind(yardBays));\n const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: [yardBaysByArea] }));\n const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: [kilnsByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.allocationFitRule\", {\n bin: { source: byTime, range: \"at\" },\n bays: { source: bayIndex, fields: { area: { label: \"Area\", hints: [\"North\", \"South\"] } } },\n kilns: { source: kilnIndex, fields: { group: { label: \"Kiln group\", hints: [\"Hall A\", \"Hall B\", \"Compact\", \"Standard\", \"Large\", \"Bulk\"] } } },\n }, {\n presets: [\n { id: \"halls\", name: \"Halls\", where: { kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n { id: \"small\", name: \"Compact and standard\", where: { kilns: { group: [\"Compact\", \"Standard\"] } } },\n { id: \"large\", name: \"Large and bulk\", where: { kilns: { group: [\"Large\", \"Bulk\"] } } },\n { id: \"north\", name: \"North bays, halls\", where: { bays: { area: [\"North\"] }, kilns: { group: [\"Hall A\", \"Hall B\"] } } },\n ],\n }));\n const fit = $.let(Func.bind(bulkFit));\n return <Box width=\"100%\" height=\"900px\"><Allocation id=\"allocationFitRule\" title=\"Kiln loading\" fit={fit} slice={{ slice }} inspector\n library={[Allocation.library.arrivals(), Allocation.library.resources()]} view={{ layout: \"timeline\", date: yardDay }}\n events={{ bin: Schedule.events(bins, {\n name: \"Bin\", icon: \"box\", entries,\n at: \"at\", resource: { field: \"bay\", of: \"bays\" }, arrival: \"load\",\n quantity: { field: \"m3\", unit: \"m³\" }, parts: { field: \"kilns\", of: \"kilns\" }, hold: \"dryFor\", kind: \"spec\",\n fields: { specMeasured: Schedule.field.text({ label: \"Spec measured\" }), instruction: Schedule.field.text({ label: \"Loading instruction\" }) },\n }) }}\n resources={{\n bays: Schedule.resources(bays.read(), { name: \"Bays\", icon: \"warehouse\", label: b => b.name, offset: b => b.offset }),\n kilns: Schedule.resources(yard.read(), { name: \"Kilns\", icon: \"fire\", label: k => k.name, group: k => k.group,\n meta: k => some(East.str`${k.group} · ${East.Float.roundHalf(k.max)} m³`), note: k => k.note,\n min: k => k.min, max: k => k.max, hold: k => variant(\"days\", k.days), jobs: k => k.charges }),\n }}\n arrivals={Allocation.arrivals(loads.read(), {\n label: l => l.code, meta: l => some(East.str`${l.mill} · ${l.grade} · spec ${l.spec}`),\n quantity: l => l.m3, hold: l => variant(\"days\", l.dryingDays), kind: l => some(l.spec),\n due: l => some(l.due), window: l => some({ from: l.from, to: l.to }),\n details: l => [{ label: \"Settings\", value: l.schedule }],\n values: l => ({ specBooked: l.spec, specMeasured: l.spec, gradeBooked: l.grade.substring(0n, 1n), gradeMeasured: l.grade, instruction: l.instruction }),\n })}\n periods={Schedule.periods(days, { every: \"day\", status: { field: \"state\", draft: \"draft\", published: \"issued\" } })}\n columns={[\n { key: \"spec\", label: \"Spec\", text: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => b.specMeasured, () => East.str`booked ${b.specBooked} · measured ${b.specMeasured}`),\n tone: b => East.equal(b.specBooked, b.specMeasured).ifElse(() => none, () => some(variant(\"warning\", null))) },\n { key: \"grade\", label: \"Grade\", text: b => b.gradeMeasured.startsWith(b.gradeBooked).ifElse(() => b.gradeMeasured, () => East.str`booked ${b.gradeBooked} · measured ${b.gradeMeasured}`), table: false },\n ]} />\n </Box>;\n }}</Reactive>)"
|
|
40025
39662
|
},
|
|
40026
39663
|
{
|
|
40027
39664
|
"id": "e3-ui:fit.examples.ts:allocationDayLoads",
|
|
@@ -40206,6 +39843,49 @@
|
|
|
40206
39843
|
],
|
|
40207
39844
|
"source": "// Allocation.slots: the day's lane slots, one per lane per hour at its offset, in time then lane order — 48 for two bays — each free, booked or reserved; B-1 06:15 is booked with bin b1\n// inputs: [plan, context] returns: { slots: 48n, held: [{ lane: \"B-1\", at: new Date(\"2027-05-11T06:15:00Z\"), status: variant(\"booked\", null), bins: [\"b1\"] }] }\n/** A bin of a small yard's own: its bay, its time and its load, and the grade measured on arrival, which the board carries but never reads. */\nexport const GradeBin = StructType({ bay: StringType, at: DateTimeType, load: OptionType(StringType), grade: StringType });\n\n/** One day of the yard's bins as the board reads them, and one of its bins. */\nexport const GradeDay = Allocation.Types.Day(GradeBin);\n\nexport const GradeBoardBin = Allocation.Types.Bin(GradeBin);\n\nconst byString = compareFor(StringType);\n\n/** The plan day: Tue 11 May 2027. */\nexport const planDay = new Date(\"2027-05-11T00:00:00Z\");\n\n/** A small yard: two bays, three loads, and three kilns — K-1 with its next charge from Fri 21, K-2 free, K-3 drying until Fri 14. */\nexport const context: ValueTypeOf<typeof Allocation.Types.Context> = {\n lanes: [{ key: \"B-1\", label: \"B-1\", offset: variant(\"minutes\", 15) }, { key: \"B-2\", label: \"B-2\", offset: variant(\"minutes\", 35) }],\n every: variant(\"hours\", 1),\n arrivals: new SortedMap<string, ValueTypeOf<typeof Allocation.Types.Arrival>>([\n [\"L-1\", { key: \"L-1\", label: \"L-1\", meta: none, quantity: 12, hold: variant(\"days\", 8), kind: some(\"MC12\"), due: none, window: none, details: [] }],\n [\"L-2\", { key: \"L-2\", label: \"L-2\", meta: none, quantity: 30, hold: variant(\"days\", 6), kind: some(\"MC14\"), due: none, window: none, details: [] }],\n [\"L-3\", { key: \"L-3\", label: \"L-3\", meta: none, quantity: 8, hold: variant(\"days\", 9), kind: some(\"MC12\"), due: none, window: none, details: [] }],\n ], byString),\n resources: new SortedMap<string, ValueTypeOf<typeof Allocation.Types.Resource>>([\n [\"K-1\", { key: \"K-1\", label: \"K-1\", group: some(\"Hall A\"), meta: none, note: none, min: 10, max: 24, hold: variant(\"days\", 8),\n jobs: [{ label: \"planned\", start: new Date(\"2027-05-21T00:00:00Z\"), end: new Date(\"2027-05-29T00:00:00Z\") }] }],\n [\"K-2\", { key: \"K-2\", label: \"K-2\", group: some(\"Bulk\"), meta: none, note: none, min: 20, max: 32, hold: variant(\"days\", 8), jobs: [] }],\n [\"K-3\", { key: \"K-3\", label: \"K-3\", group: some(\"Hall A\"), meta: none, note: none, min: 10, max: 24, hold: variant(\"days\", 8),\n jobs: [{ label: \"C-1\", start: new Date(\"2027-05-01T00:00:00Z\"), end: new Date(\"2027-05-14T00:00:00Z\") }] }],\n ], byString),\n rules: { under: true }, unit: \"m³\",\n};\n\n/** Tue 11's plan: L-1's 12 m³ in B-1 at 06:15, on K-1. */\nexport const plan: ValueTypeOf<typeof GradeDay> = {\n status: variant(\"draft\", null),\n bins: new SortedMap<string, ValueTypeOf<typeof GradeBoardBin>>([\n [\"b1\", { lane: \"B-1\", at: new Date(\"2027-05-11T06:15:00Z\"), arrival: some(\"L-1\"), quantity: 12, kind: some(\"MC12\"), hold: variant(\"days\", 8),\n parts: new SortedMap<string, number>([[\"K-1\", 12]], byString), row: { bay: \"B-1\", at: new Date(\"2027-05-11T06:15:00Z\"), load: some(\"L-1\"), grade: \"R1\" } }],\n ], byString),\n};\n\nEast.function([GradeDay, Allocation.Types.Context], StructType({ slots: IntegerType, held: ArrayType(Allocation.Types.Slot) }), ($, plan, context) => {\n const slots = $.const(Allocation.slots(GradeBin));\n const all = $.const(slots(planDay, plan, context));\n return { slots: all.size(), held: all.filter(($2, slot) => slot.status.hasTag(\"free\").not()) };\n })"
|
|
40208
39845
|
},
|
|
39846
|
+
{
|
|
39847
|
+
"id": "e3-ui:optimise.examples.tsx:allocationOptimise",
|
|
39848
|
+
"skill": "e3-ui",
|
|
39849
|
+
"package": "e3-ui",
|
|
39850
|
+
"file": "east-ui/packages/e3-ui/test/allocation/optimise.examples.tsx",
|
|
39851
|
+
"suite": "",
|
|
39852
|
+
"test": "The kiln yard's board with the Elara eye (#1409): `optimise` is Func.bind of the yard's optimiser on e3 — Allocation.Types.OptimiseRequest(KilnBin) in, the day in view, the run number, the day's bins as drafted, every arrival and the board's context; Allocation.Types.OptimiseResult(KilnBin) out, the bins complete. A joint optimiser: a kiln over its maximum gives the excess to the kilns the fit ranks best, a bin not linked or partly allocated is linked, and every load not yet booked is booked into the day's free bay slots and onto the kilns the fit ranks best — whole on the best fit, split when no one kiln takes it. A click on the eye runs it once over the day; the result lands in the session as one edit — bins added, bins relinked — which one Undo reverts and Save commits with everything else.",
|
|
39853
|
+
"keywords": [
|
|
39854
|
+
"Allocation",
|
|
39855
|
+
"optimise",
|
|
39856
|
+
"Optimise",
|
|
39857
|
+
"Elara",
|
|
39858
|
+
"OptimiseRequest",
|
|
39859
|
+
"OptimiseResult",
|
|
39860
|
+
"Func.bind",
|
|
39861
|
+
"e3.function",
|
|
39862
|
+
"joint",
|
|
39863
|
+
"links",
|
|
39864
|
+
"one edit",
|
|
39865
|
+
"Undo",
|
|
39866
|
+
"Schedule.events",
|
|
39867
|
+
"Schedule.resources",
|
|
39868
|
+
"Schedule.periods",
|
|
39869
|
+
"Allocation.arrivals",
|
|
39870
|
+
"Allocation.slots",
|
|
39871
|
+
"Allocation.candidates",
|
|
39872
|
+
"Allocation.fit",
|
|
39873
|
+
"Allocation.check",
|
|
39874
|
+
"fit"
|
|
39875
|
+
],
|
|
39876
|
+
"imports": [
|
|
39877
|
+
"import { DateTimeType, DictType, East, FloatType, OptionType, SetType, StringType, StructType, example, none, some, variant } from \"@elaraai/east\";",
|
|
39878
|
+
"import e3 from \"@elaraai/e3\";",
|
|
39879
|
+
"import { Box, Reactive, Slice, UIComponentType } from \"@elaraai/east-ui\";",
|
|
39880
|
+
"import { Allocation, Data, Func, Record, Schedule } from \"@elaraai/e3-ui\";",
|
|
39881
|
+
"import { KilnBin, YardPlan, yardDay } from \"./kiln-yard.js\";",
|
|
39882
|
+
"import {"
|
|
39883
|
+
],
|
|
39884
|
+
"languages": [
|
|
39885
|
+
"tsx"
|
|
39886
|
+
],
|
|
39887
|
+
"source": "// The kiln yard's board with the Elara eye (#1409): `optimise` is Func.bind of the yard's optimiser on e3 — Allocation.Types.OptimiseRequest(KilnBin) in, the day in view, the run number, the day's bins as drafted, every arrival and the board's context; Allocation.Types.OptimiseResult(KilnBin) out, the bins complete. A joint optimiser: a kiln over its maximum gives the excess to the kilns the fit ranks best, a bin not linked or partly allocated is linked, and every load not yet booked is booked into the day's free bay slots and onto the kilns the fit ranks best — whole on the best fit, split when no one kiln takes it. A click on the eye runs it once over the day; the result lands in the session as one edit — bins added, bins relinked — which one Undo reverts and Save commits with everything else.\n// inputs: []\nconst Request = Allocation.Types.OptimiseRequest(KilnBin), Result = Allocation.Types.OptimiseResult(KilnBin);\n\n/** The day's bins by key, the yard's own rows. */\nconst Bins = DictType(StringType, KilnBin);\n\n/** A bin's split across kilns: its quantity on each. */\nconst Kilns = DictType(StringType, FloatType);\n\n/** One part a run places: a quantity on a kiln. */\nconst Part = StructType({ kiln: StringType, quantity: FloatType });\n\n/** The day as the board reads it, from the bins as they stand: each bin its roles and its own row. */\nconst dayOf = East.function([Bins], YardPlan, ($, rows) => $.const({\n status: variant(\"draft\", null),\n bins: rows.toDict(($2, _bin, key) => key, ($2, bin) => ({ lane: bin.bay, at: bin.at, arrival: bin.load, quantity: bin.m3, kind: bin.spec, hold: bin.dryFor, parts: bin.kilns, row: bin })),\n}, YardPlan));\n\n/** A bin's row with its kilns replaced, the yard's own fields kept. */\nconst withKilns = East.function([KilnBin, Kilns], KilnBin, (_$, bin, kilns) => ({\n bay: bin.bay, at: bin.at, load: bin.load, m3: bin.m3, spec: bin.spec, dryFor: bin.dryFor, kilns,\n specBooked: bin.specBooked, specMeasured: bin.specMeasured, gradeBooked: bin.gradeBooked, gradeMeasured: bin.gradeMeasured, instruction: bin.instruction,\n}));\n\n/** A bin's kilns with a quantity added to one of them — or taken off it, for a quantity below zero. */\nconst addPart = East.function([Kilns, StringType, FloatType], Kilns, ($, held, kiln, quantity) => {\n const kilns = $.let(new Map(), Kilns);\n $.for(held, ($2, part, key) => { $2(kilns.insert(key, part)); });\n $(kilns.insertOrUpdate(kiln, quantity, (_$2, part, more) => part.add(more)));\n return kilns;\n});\n\n/**\n * Places a quantity of a bin's on the kilns the fit ranks best — whole on the best fit; failing that, split, the kiln\n * with the most room first, until nothing has room — writing each part into the bin's row as it goes. The fit is asked\n * about the bin itself, its own spec and drying time, over the day as the bins stand with the bin once more under a\n * key of its own: a place's candidates leave the subject's parts out of the kilns' loads, and the copy puts them back,\n * so a kiln's room is what the day leaves it, the bin's parts there included.\n *\n * @returns The bins with the parts written, and how much of the quantity found a kiln\n */\nconst settle = East.function([DateTimeType, Allocation.Types.Context, Bins, StringType, FloatType], StructType({ bins: Bins, placed: FloatType }),\n ($, day, context, held, id, quantity) => {\n const candidatesOf = $.const(Allocation.candidates(KilnBin));\n const rule = $.const(Allocation.fit);\n const planOf = $.const(dayOf);\n const rowWith = $.const(withKilns);\n const partOn = $.const(addPart);\n const bins = $.let(new Map(), Bins);\n $.for(held, ($2, bin, key) => { $2(bins.insert(key, bin)); });\n const remaining = $.let(quantity, FloatType);\n $.while(East.greater(remaining, 0.0), ($2, loop) => {\n const rows = $2.let(new Map(), Bins);\n $2.for(bins, ($3, bin, key) => { $3(rows.insert(key, bin)); });\n $2(rows.insert(East.str`${id}·`, bins.get(id)));\n const request = $2.const({ day, plan: planOf(rows), ask: variant(\"place\", { subject: variant(\"bin\", id), quantity: remaining }), context },\n Allocation.Types.FitRequest(KilnBin));\n const best = $2.let(none, OptionType(StringType)), bestScore = $2.let(0.0, FloatType);\n const roomiest = $2.let(none, OptionType(StringType)), room = $2.let(0.0, FloatType);\n $2.for(candidatesOf(request), ($3, candidate) => {\n const kiln = $3.const(candidate.resource.key);\n $3.match(rule(candidate), {\n fits: ($4, fit) => {\n $4.if(best.hasTag(\"none\").or(() => East.greater(fit.score, bestScore)), ($5) => { $5.assign(best, some(kiln)); $5.assign(bestScore, fit.score); });\n },\n refused: ($4, refusal) => {\n // Refused for the quantity alone: a part of it, what the kiln has room for, would fit.\n const spare = $4.const(candidate.resource.max.subtract(candidate.after.subtract(candidate.quantity)));\n $4.if(East.equal(refusal.group, \"Over max\").and(() => East.greater(spare, room)), ($5) => { $5.assign(roomiest, some(kiln)); $5.assign(room, spare); });\n },\n });\n });\n const chosen = $2.let(none, OptionType(Part));\n $2.match(best, {\n some: ($3, kiln) => { $3.assign(chosen, some({ kiln, quantity: remaining })); },\n none: ($3) => { $3.match(roomiest, { some: ($4, kiln) => { $4.assign(chosen, some({ kiln, quantity: room })); } }); },\n });\n $2.match(chosen, {\n none: ($3) => { $3.break(loop); },\n some: ($3, part) => {\n const bin = $3.const(bins.get(id));\n $3(bins.insertOrUpdate(id, rowWith(bin, partOn(bin.kilns, part.kiln, part.quantity)), (_$4, _was, next) => next));\n $3.assign(remaining, remaining.subtract(part.quantity));\n },\n });\n });\n return { bins, placed: quantity.subtract(remaining) };\n });\n\n/**\n * The yard's optimiser's body, in three passes over the day as drafted, every bin it held kept in its slot. First the\n * kilns: one over its maximum gives the excess of its latest bin — the check's own fix, Move — to the kilns the fit ranks\n * best. Then the bins: one not linked, or partly allocated, gets what its parts leave of it placed the same way. Then the\n * loads: every one not yet booked, in the loads' order, is booked into the day's first free bay slot — its m³, its spec\n * and its drying time the load's, its spec booked as measured, under a key of the run's own — and placed on the kilns\n * the fit ranks best. A quantity goes whole onto the best fit (`Allocation.fit`: never a busy kiln, never one whose next\n * job its hold runs into, never over a maximum); when no kiln takes it whole it is split, the kiln with the most room\n * first; what no kiln has room for stays unallocated, for the planner. A run that finds nothing to do answers the day as\n * it was.\n */\nexport const allocationOptimiseBody = East.function([Request], Result, ($, req) => {\n const loadsOf = $.const(Allocation.loads(KilnBin));\n const checkOf = $.const(Allocation.check(KilnBin));\n const slotsOf = $.const(Allocation.slots(KilnBin));\n const planOf = $.const(dayOf);\n const rowWith = $.const(withKilns);\n const partOn = $.const(addPart);\n const place = $.const(settle);\n const day = $.const(req.day), context = $.const(req.context);\n const bins = $.let(new Map(), Bins);\n $.for(req.bins, ($2, bin, key) => { $2(bins.insert(key, bin)); });\n // The kilns: one over its maximum gives the excess of its latest bin — the check's fix, Move — to the kilns the fit\n // ranks best; its part there shrinks by what found room, and what found none stays, over, for the planner.\n $.for(checkOf(day, planOf(bins), context), ($2, issue) => {\n $2.match(issue.kind, { over: ($3) => {\n $3.match(issue.resource, { some: ($4, kiln) => {\n $4.match(issue.fix, { some: ($5, fix) => {\n $5.match(fix, { move: ($6, id) => {\n const bin = $6.const(bins.get(id));\n const over = $6.const(loadsOf(day, planOf(bins), context).get(kiln).fill.subtract(context.resources.get(kiln).max));\n const excess = $6.const(East.min(over, bin.kilns.get(kiln, () => 0.0)));\n $6.if(East.greater(excess, 0.0), ($7) => {\n const settled = $7.const(place(day, context, bins, id, excess));\n $7.assign(bins, settled.bins);\n const moved = $7.const(bins.get(id));\n const kilns = $7.let(partOn(moved.kilns, kiln, settled.placed.negate()));\n $7.if(East.lessEqual(kilns.get(kiln), 0.0), ($8) => { $8(kilns.delete(kiln)); });\n $7(bins.insertOrUpdate(id, rowWith(moved, kilns), (_$8, _was, next) => next));\n });\n } });\n } });\n } });\n } });\n });\n // The bins: one not linked, or partly allocated, gets what its parts leave of it placed; a reservation holds nothing.\n $.for(req.bins, ($2, _held, id) => {\n const bin = $2.const(bins.get(id));\n const allocated = $2.let(0.0, FloatType);\n $2.for(bin.kilns, ($3, held) => { $3.assign(allocated, allocated.add(held)); });\n const left = $2.const(bin.m3.subtract(allocated));\n $2.if(East.greater(left, 0.0), ($3) => { $3.assign(bins, place(day, context, bins, id, left).bins); });\n });\n // The loads: every one not yet booked, in the loads' order, into the day's first free bay slot — its m³, its spec and\n // its drying time the load's, its spec booked as measured, under a key of the run's own — and onto the kilns.\n const booked = $.let(new Set<string>(), SetType(StringType));\n $.for(bins, ($2, bin) => {\n // A load may be booked in two bins, a batch's: once in the set.\n $2.match(bin.load, { some: ($3, load) => { $3.if(booked.has(load).not(), ($4) => { $4(booked.insert(load)); }); } });\n });\n const free = $.const(slotsOf(day, planOf(bins), context).filter(($2, slot) => slot.status.hasTag(\"free\")));\n const waiting = $.const(req.arrivals.filter(($2, load) => booked.has(load.key).not()));\n $.for(waiting, ($2, load, i) => {\n $2.if(i.less(free.size()), ($3) => {\n const slot = $3.const(free.get(i));\n const spec = $3.const(load.kind.unwrap(\"some\", () => \"\"));\n const id = $3.const(East.str`elara-${req.run}-${load.key}`);\n $3(bins.insert(id, {\n bay: slot.lane, at: slot.at, load: some(load.key), m3: load.quantity, spec: load.kind, dryFor: load.hold, kilns: new Map(),\n specBooked: spec, specMeasured: spec, gradeBooked: \"\", gradeMeasured: \"\", instruction: none,\n }));\n $3.assign(bins, place(day, context, bins, id, load.quantity).bins);\n });\n });\n return { bins };\n});\n\n/** The yard's optimiser, on e3: the day's bins in, the day's bins out — the kilns' overloads moved, the day's links mended, every waiting load booked into a free bay slot and onto the kilns the fit ranks best. */\nexport const yardOptimise = e3.function(\"yard_optimise\", allocationOptimiseBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const bins = $.let(Record.bind(kilnBins, [kilnBinsPatch]));\n const entries = $.let(Data.bindPaged(kilnBins));\n const byTime = $.let(Data.bindPaged(kilnBins, { indexes: [kilnBinsByTime] }));\n const days = $.let(Record.bind(yardDays, [yardDaysPatch]));\n // Every bay, kiln and load is the calculation scope — and the optimiser's: the narrowing only chooses which show.\n const yard = $.let(Data.bind(kilns));\n const loads = $.let(Data.bind(arrivals));\n const bays = $.let(Data.bind(yardBays));\n const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: [yardBaysByArea] }));\n const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: [kilnsByGroup] }));\n const loadIndex = $.let(Data.bindPaged(arrivals, { indexes: [arrivalsBySpec] }));\n const slice = $.let(Slice.bindRecords(\"ex.allocationOptimise\", {\n bin: { source: byTime, range: \"at\" },\n bays: { source: bayIndex, fields: { area: { label: \"Area\", hints: [\"North\", \"South\"] } } },\n kilns: { source: kilnIndex, fields: { group: { label: \"Kiln group\", hints: [\"Hall A\", \"Hall B\", \"Compact\", \"Standard\", \"Large\", \"Bulk\"] } } },\n arrivals: { source: loadIndex, fields: { spec: { label: \"Spec\", hints: [\"MC08\", \"MC10\", \"MC12\", \"MC14\", \"MC16\", \"MC18\", \"MC20\", \"MC22\"] } } },\n }));\n const fit = $.let(Func.bind(yardFit));\n // The app's optimiser: the eye calls it once per run over the day in view, and its answer is one edit.\n const optimise = $.let(Func.bind(yardOptimise));\n return <Box width=\"100%\" height=\"900px\"><Allocation id=\"allocationOptimise\" title=\"Kiln loading\" fit={fit} optimise={optimise} slice={{ slice }} inspector\n library={[Allocation.library.arrivals(), Allocation.library.resources()]} view={{ layout: \"timeline\", date: yardDay }}\n events={{ bin: Schedule.events(bins, {\n name: \"Bin\", icon: \"box\", entries,\n at: \"at\", resource: { field: \"bay\", of: \"bays\" }, arrival: \"load\",\n quantity: { field: \"m3\", unit: \"m³\" }, parts: { field: \"kilns\", of: \"kilns\" }, hold: \"dryFor\", kind: \"spec\",\n fields: { specMeasured: Schedule.field.text({ label: \"Spec measured\" }), instruction: Schedule.field.text({ label: \"Loading instruction\" }) },\n }) }}\n resources={{\n bays: Schedule.resources(bays.read(), { name: \"Bays\", icon: \"warehouse\", label: b => b.name, offset: b => b.offset }),\n kilns: Schedule.resources(yard.read(), { name: \"Kilns\", icon: \"fire\", label: k => k.name, group: k => k.group,\n meta: k => some(East.str`${k.group} · ${East.Float.roundHalf(k.max)} m³`), note: k => k.note,\n min: k => k.min, max: k => k.max, hold: k => variant(\"days\", k.days), jobs: k => k.charges }),\n }}\n arrivals={Allocation.arrivals(loads.read(), {\n label: l => l.code, meta: l => some(East.str`${l.mill} · ${l.grade} · spec ${l.spec}`),\n quantity: l => l.m3, hold: l => variant(\"days\", l.dryingDays), kind: l => some(l.spec),\n due: l => some(l.due), window: l => some({ from: l.from, to: l.to }),\n details: l => [{ label: \"Settings\", value: l.schedule }],\n values: l => ({ specBooked: l.spec, specMeasured: l.spec, gradeBooked: l.grade.substring(0n, 1n), gradeMeasured: l.grade, instruction: l.instruction }),\n })}\n periods={Schedule.periods(days, { every: \"day\", status: { field: \"state\", draft: \"draft\", published: \"issued\" } })} />\n </Box>;\n }}</Reactive>)"
|
|
39888
|
+
},
|
|
40209
39889
|
{
|
|
40210
39890
|
"id": "e3-ui:data.examples.tsx:dataBindFloat",
|
|
40211
39891
|
"skill": "e3-ui",
|
|
@@ -40836,7 +40516,7 @@
|
|
|
40836
40516
|
"languages": [
|
|
40837
40517
|
"tsx"
|
|
40838
40518
|
],
|
|
40839
|
-
"source": "// 12,000 events across a year, narrowed through interval indexes. The opening days demonstrate carry-in and year-long events; navigate to other dates for the larger production schedule. Filters and navigation share Slice, and drafts save by original key.\n// inputs: []\n/** An event's row and original key are independent of every index entry. */\nexport const IndexedCalendarEvent = StructType({\n title: StringType, start: OptionType(DateTimeType), end: OptionType(DateTimeType),\n machine: StringType, status: StringType, customer: StringType,\n});\n\n/** A year of jobs beside the small, identifiable overlap/editing examples. */\nfunction calendarSeed(): Map<string, ValueTypeOf<typeof IndexedCalendarEvent>> {\n const rows = new Map<string, ValueTypeOf<typeof IndexedCalendarEvent>>([\n [\"carry\", { title: \"Overnight print run\", start: some(new Date(\"2026-10-01T22:00:00Z\")), end: some(new Date(\"2026-10-02T08:00:00Z\")), machine: \"press-a\", status: \"Ready\", customer: \"Northern Press\" }],\n [\"long\", { title: \"Annual maintenance reservation\", start: some(new Date(\"2026-01-01T00:00:00Z\")), end: some(new Date(\"2027-01-01T00:00:00Z\")), machine: \"press-b\", status: \"Ready\", customer: \"Operations\" }],\n [\"job\", { title: \"October brochures\", start: some(new Date(\"2026-10-02T09:00:00Z\")), end: some(new Date(\"2026-10-02T12:00:00Z\")), machine: \"press-a\", status: \"Ready\", customer: \"City Arts\" }],\n [\"done\", { title: \"Completed proof\", start: some(new Date(\"2026-10-02T08:00:00Z\")), end: some(new Date(\"2026-10-02T09:00:00Z\")), machine: \"press-a\", status: \"Done\", customer: \"City Arts\" }],\n [\"next\", { title: \"Weekend catalogue\", start: some(new Date(\"2026-10-03T09:00:00Z\")), end: some(new Date(\"2026-10-03T12:00:00Z\")), machine: \"press-a\", status: \"Ready\", customer: \"North Market\" }],\n [\"backlog\", { title: \"Unscheduled posters\", start: none, end: none, machine: \"press-b\", status: \"Ready\", customer: \"Festival\" }],\n ]);\n for (let i = 0; i < 11994; i++) {\n const ordinal = (i * 37) % 363;\n // Keep the opening two days legible; navigate to any other day to\n // inspect the larger schedule. The record and all its indexes are real.\n const day = ordinal < 274 ? ordinal : ordinal + 2;\n const start = new Date(Date.UTC(2026, 0, 1 + day, 6 + i % 10));\n rows.set(`production-${10000 + i}`, {\n title: `Production job ${10000 + i}`, start: some(start),\n end: some(new Date(start.getTime() + (1 + i % 4) * 3600000)),\n machine: i % 2 === 0 ? \"press-a\" : \"press-b\", status: i % 3 === 0 ? \"Done\" : \"Ready\",\n customer: [\"Northern Press\", \"City Arts\", \"North Market\", \"Festival\"][i % 4]!,\n });\n }\n return rows;\n}\n\nexport const indexedCalendarEvents = e3.record(\"indexed_calendar_events\", DictType(StringType, IndexedCalendarEvent), calendarSeed());\n\nexport const indexedCalendarPatch = e3.mutation.patch(indexedCalendarEvents);\n\nexport const indexedCalendarByTime = e3.recordIndex(\"calendar_by_time\", indexedCalendarEvents, {
|
|
40519
|
+
"source": "// 12,000 events across a year, narrowed through interval indexes. The opening days demonstrate carry-in and year-long events; navigate to other dates for the larger production schedule. Filters and navigation share Slice, and drafts save by original key.\n// inputs: []\n/** An event's row and original key are independent of every index entry. */\nexport const IndexedCalendarEvent = StructType({\n title: StringType, start: OptionType(DateTimeType), end: OptionType(DateTimeType),\n machine: StringType, status: StringType, customer: StringType,\n});\n\n/** A year of jobs beside the small, identifiable overlap/editing examples. */\nfunction calendarSeed(): Map<string, ValueTypeOf<typeof IndexedCalendarEvent>> {\n const rows = new Map<string, ValueTypeOf<typeof IndexedCalendarEvent>>([\n [\"carry\", { title: \"Overnight print run\", start: some(new Date(\"2026-10-01T22:00:00Z\")), end: some(new Date(\"2026-10-02T08:00:00Z\")), machine: \"press-a\", status: \"Ready\", customer: \"Northern Press\" }],\n [\"long\", { title: \"Annual maintenance reservation\", start: some(new Date(\"2026-01-01T00:00:00Z\")), end: some(new Date(\"2027-01-01T00:00:00Z\")), machine: \"press-b\", status: \"Ready\", customer: \"Operations\" }],\n [\"job\", { title: \"October brochures\", start: some(new Date(\"2026-10-02T09:00:00Z\")), end: some(new Date(\"2026-10-02T12:00:00Z\")), machine: \"press-a\", status: \"Ready\", customer: \"City Arts\" }],\n [\"done\", { title: \"Completed proof\", start: some(new Date(\"2026-10-02T08:00:00Z\")), end: some(new Date(\"2026-10-02T09:00:00Z\")), machine: \"press-a\", status: \"Done\", customer: \"City Arts\" }],\n [\"next\", { title: \"Weekend catalogue\", start: some(new Date(\"2026-10-03T09:00:00Z\")), end: some(new Date(\"2026-10-03T12:00:00Z\")), machine: \"press-a\", status: \"Ready\", customer: \"North Market\" }],\n [\"backlog\", { title: \"Unscheduled posters\", start: none, end: none, machine: \"press-b\", status: \"Ready\", customer: \"Festival\" }],\n ]);\n for (let i = 0; i < 11994; i++) {\n const ordinal = (i * 37) % 363;\n // Keep the opening two days legible; navigate to any other day to\n // inspect the larger schedule. The record and all its indexes are real.\n const day = ordinal < 274 ? ordinal : ordinal + 2;\n const start = new Date(Date.UTC(2026, 0, 1 + day, 6 + i % 10));\n rows.set(`production-${10000 + i}`, {\n title: `Production job ${10000 + i}`, start: some(start),\n end: some(new Date(start.getTime() + (1 + i % 4) * 3600000)),\n machine: i % 2 === 0 ? \"press-a\" : \"press-b\", status: i % 3 === 0 ? \"Done\" : \"Ready\",\n customer: [\"Northern Press\", \"City Arts\", \"North Market\", \"Festival\"][i % 4]!,\n });\n }\n return rows;\n}\n\nexport const indexedCalendarEvents = e3.record(\"indexed_calendar_events\", DictType(StringType, IndexedCalendarEvent), calendarSeed());\n\nexport const indexedCalendarPatch = e3.mutation.patch(indexedCalendarEvents);\n\nexport const indexedCalendarByTime = e3.recordIndex.interval(\"calendar_by_time\", indexedCalendarEvents, { from: \"start\", to: \"end\" });\n\nexport const indexedCalendarByStatus = e3.recordIndex.interval(\"calendar_by_status_time\", indexedCalendarEvents, { on: [\"status\"], from: \"start\", to: \"end\" });\n\nexport const indexedCalendarByMachine = e3.recordIndex.interval(\"calendar_by_machine_time\", indexedCalendarEvents, { on: [\"machine\"], from: \"start\", to: \"end\" });\n\nexport const indexedCalendarMachines = e3.record(\"indexed_calendar_machines\", DictType(StringType, StructType({ name: StringType })), new Map([\n [\"press-a\", { name: \"Press A\" }], [\"press-b\", { name: \"Press B\" }],\n]));\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const events = $.let(Record.bind(indexedCalendarEvents, [indexedCalendarPatch]));\n const entries = $.let(Data.bindPaged(indexedCalendarEvents));\n const indexed = $.let(Data.bindPaged(indexedCalendarEvents, { indexes: [indexedCalendarByTime, indexedCalendarByStatus, indexedCalendarByMachine] }));\n // The rail's one source, named after the kind it narrows (#1343): its interval indexes keep every overlapping event.\n const slice = $.let(Slice.bindRecords(\"ex.calendarRecordIndexes\", {\n job: { source: indexed, icon: { prefix: \"fas\", name: \"print\" },\n fields: { status: { label: \"Status\", hints: [\"Ready\", \"Done\"] }, machine: { label: \"Machine\" }, start: { label: \"Dates\" } },\n range: \"start\", search: [\"machine\"],\n initial: { status: [\"Ready\"], start: { from: new Date(\"2026-10-02T00:00:00Z\"), to: new Date(\"2026-10-03T00:00:00Z\") } } },\n }));\n const machines = $.let(Record.bind(indexedCalendarMachines, []));\n return <Box height=\"720px\" width=\"100%\"><Calendar id=\"calendarRecordIndexes\" inspector\n events={{ job: Calendar.events(events, {\n name: \"Job\", icon: \"print\", title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"machine\", of: \"machines\" }, entries,\n backlog: { duration: () => variant(\"hours\", 3) },\n }) }}\n resources={{ machines: Calendar.resources(machines.read(), { name: \"Machines\", icon: \"gears\", label: row => row.name }) }}\n library={[Calendar.library.backlog()]} slice={{ slice, affordances: [\"fields\", \"summary\"] }}\n view={{ layout: \"calendar\", period: \"day\", date: new Date(\"2026-10-02T00:00:00Z\") }}\n now={new Date(\"2026-10-02T09:00:00Z\")} />\n </Box>;\n }}</Reactive>)"
|
|
40840
40520
|
},
|
|
40841
40521
|
{
|
|
40842
40522
|
"id": "e3-ui:calendar-records.examples.tsx:calendarRecordIndexesMobile",
|
|
@@ -40862,7 +40542,139 @@
|
|
|
40862
40542
|
"languages": [
|
|
40863
40543
|
"tsx"
|
|
40864
40544
|
],
|
|
40865
|
-
"source": "// Indexed Calendar at phone width: the same Slice fields, overlap queries and original-key editing, shown as agenda cards with explicit actions\n// inputs: []\n/** An event's row and original key are independent of every index entry. */\nexport const IndexedCalendarEvent = StructType({\n title: StringType, start: OptionType(DateTimeType), end: OptionType(DateTimeType),\n machine: StringType, status: StringType, customer: StringType,\n});\n\n/** A year of jobs beside the small, identifiable overlap/editing examples. */\nfunction calendarSeed(): Map<string, ValueTypeOf<typeof IndexedCalendarEvent>> {\n const rows = new Map<string, ValueTypeOf<typeof IndexedCalendarEvent>>([\n [\"carry\", { title: \"Overnight print run\", start: some(new Date(\"2026-10-01T22:00:00Z\")), end: some(new Date(\"2026-10-02T08:00:00Z\")), machine: \"press-a\", status: \"Ready\", customer: \"Northern Press\" }],\n [\"long\", { title: \"Annual maintenance reservation\", start: some(new Date(\"2026-01-01T00:00:00Z\")), end: some(new Date(\"2027-01-01T00:00:00Z\")), machine: \"press-b\", status: \"Ready\", customer: \"Operations\" }],\n [\"job\", { title: \"October brochures\", start: some(new Date(\"2026-10-02T09:00:00Z\")), end: some(new Date(\"2026-10-02T12:00:00Z\")), machine: \"press-a\", status: \"Ready\", customer: \"City Arts\" }],\n [\"done\", { title: \"Completed proof\", start: some(new Date(\"2026-10-02T08:00:00Z\")), end: some(new Date(\"2026-10-02T09:00:00Z\")), machine: \"press-a\", status: \"Done\", customer: \"City Arts\" }],\n [\"next\", { title: \"Weekend catalogue\", start: some(new Date(\"2026-10-03T09:00:00Z\")), end: some(new Date(\"2026-10-03T12:00:00Z\")), machine: \"press-a\", status: \"Ready\", customer: \"North Market\" }],\n [\"backlog\", { title: \"Unscheduled posters\", start: none, end: none, machine: \"press-b\", status: \"Ready\", customer: \"Festival\" }],\n ]);\n for (let i = 0; i < 11994; i++) {\n const ordinal = (i * 37) % 363;\n // Keep the opening two days legible; navigate to any other day to\n // inspect the larger schedule. The record and all its indexes are real.\n const day = ordinal < 274 ? ordinal : ordinal + 2;\n const start = new Date(Date.UTC(2026, 0, 1 + day, 6 + i % 10));\n rows.set(`production-${10000 + i}`, {\n title: `Production job ${10000 + i}`, start: some(start),\n end: some(new Date(start.getTime() + (1 + i % 4) * 3600000)),\n machine: i % 2 === 0 ? \"press-a\" : \"press-b\", status: i % 3 === 0 ? \"Done\" : \"Ready\",\n customer: [\"Northern Press\", \"City Arts\", \"North Market\", \"Festival\"][i % 4]!,\n });\n }\n return rows;\n}\n\nexport const indexedCalendarEvents = e3.record(\"indexed_calendar_events\", DictType(StringType, IndexedCalendarEvent), calendarSeed());\n\nexport const indexedCalendarPatch = e3.mutation.patch(indexedCalendarEvents);\n\nexport const indexedCalendarByTime = e3.recordIndex(\"calendar_by_time\", indexedCalendarEvents, {
|
|
40545
|
+
"source": "// Indexed Calendar at phone width: the same Slice fields, overlap queries and original-key editing, shown as agenda cards with explicit actions\n// inputs: []\n/** An event's row and original key are independent of every index entry. */\nexport const IndexedCalendarEvent = StructType({\n title: StringType, start: OptionType(DateTimeType), end: OptionType(DateTimeType),\n machine: StringType, status: StringType, customer: StringType,\n});\n\n/** A year of jobs beside the small, identifiable overlap/editing examples. */\nfunction calendarSeed(): Map<string, ValueTypeOf<typeof IndexedCalendarEvent>> {\n const rows = new Map<string, ValueTypeOf<typeof IndexedCalendarEvent>>([\n [\"carry\", { title: \"Overnight print run\", start: some(new Date(\"2026-10-01T22:00:00Z\")), end: some(new Date(\"2026-10-02T08:00:00Z\")), machine: \"press-a\", status: \"Ready\", customer: \"Northern Press\" }],\n [\"long\", { title: \"Annual maintenance reservation\", start: some(new Date(\"2026-01-01T00:00:00Z\")), end: some(new Date(\"2027-01-01T00:00:00Z\")), machine: \"press-b\", status: \"Ready\", customer: \"Operations\" }],\n [\"job\", { title: \"October brochures\", start: some(new Date(\"2026-10-02T09:00:00Z\")), end: some(new Date(\"2026-10-02T12:00:00Z\")), machine: \"press-a\", status: \"Ready\", customer: \"City Arts\" }],\n [\"done\", { title: \"Completed proof\", start: some(new Date(\"2026-10-02T08:00:00Z\")), end: some(new Date(\"2026-10-02T09:00:00Z\")), machine: \"press-a\", status: \"Done\", customer: \"City Arts\" }],\n [\"next\", { title: \"Weekend catalogue\", start: some(new Date(\"2026-10-03T09:00:00Z\")), end: some(new Date(\"2026-10-03T12:00:00Z\")), machine: \"press-a\", status: \"Ready\", customer: \"North Market\" }],\n [\"backlog\", { title: \"Unscheduled posters\", start: none, end: none, machine: \"press-b\", status: \"Ready\", customer: \"Festival\" }],\n ]);\n for (let i = 0; i < 11994; i++) {\n const ordinal = (i * 37) % 363;\n // Keep the opening two days legible; navigate to any other day to\n // inspect the larger schedule. The record and all its indexes are real.\n const day = ordinal < 274 ? ordinal : ordinal + 2;\n const start = new Date(Date.UTC(2026, 0, 1 + day, 6 + i % 10));\n rows.set(`production-${10000 + i}`, {\n title: `Production job ${10000 + i}`, start: some(start),\n end: some(new Date(start.getTime() + (1 + i % 4) * 3600000)),\n machine: i % 2 === 0 ? \"press-a\" : \"press-b\", status: i % 3 === 0 ? \"Done\" : \"Ready\",\n customer: [\"Northern Press\", \"City Arts\", \"North Market\", \"Festival\"][i % 4]!,\n });\n }\n return rows;\n}\n\nexport const indexedCalendarEvents = e3.record(\"indexed_calendar_events\", DictType(StringType, IndexedCalendarEvent), calendarSeed());\n\nexport const indexedCalendarPatch = e3.mutation.patch(indexedCalendarEvents);\n\nexport const indexedCalendarByTime = e3.recordIndex.interval(\"calendar_by_time\", indexedCalendarEvents, { from: \"start\", to: \"end\" });\n\nexport const indexedCalendarByStatus = e3.recordIndex.interval(\"calendar_by_status_time\", indexedCalendarEvents, { on: [\"status\"], from: \"start\", to: \"end\" });\n\nexport const indexedCalendarByMachine = e3.recordIndex.interval(\"calendar_by_machine_time\", indexedCalendarEvents, { on: [\"machine\"], from: \"start\", to: \"end\" });\n\nexport const indexedCalendarMachines = e3.record(\"indexed_calendar_machines\", DictType(StringType, StructType({ name: StringType })), new Map([\n [\"press-a\", { name: \"Press A\" }], [\"press-b\", { name: \"Press B\" }],\n]));\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const events = $.let(Record.bind(indexedCalendarEvents, [indexedCalendarPatch]));\n const entries = $.let(Data.bindPaged(indexedCalendarEvents));\n const indexed = $.let(Data.bindPaged(indexedCalendarEvents, { indexes: [indexedCalendarByTime, indexedCalendarByStatus, indexedCalendarByMachine] }));\n // The rail's one source, named after the kind it narrows (#1343): its interval indexes keep every overlapping event.\n const slice = $.let(Slice.bindRecords(\"ex.calendarRecordIndexesMobile\", {\n job: { source: indexed, icon: { prefix: \"fas\", name: \"print\" },\n fields: { status: { label: \"Status\", hints: [\"Ready\", \"Done\"] }, machine: { label: \"Machine\" }, start: { label: \"Dates\" } },\n range: \"start\", search: [\"machine\"],\n initial: { status: [\"Ready\"], start: { from: new Date(\"2026-10-02T00:00:00Z\"), to: new Date(\"2026-10-03T00:00:00Z\") } } },\n }));\n const machines = $.let(Record.bind(indexedCalendarMachines, []));\n return <Box height=\"760px\" width=\"390px\" maxWidth=\"100%\"><Calendar id=\"calendarRecordIndexesMobile\" inspector\n events={{ job: Calendar.events(events, {\n name: \"Job\", icon: \"print\", title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"machine\", of: \"machines\" }, entries,\n backlog: { duration: () => variant(\"hours\", 3) },\n }) }}\n resources={{ machines: Calendar.resources(machines.read(), { name: \"Machines\", icon: \"gears\", label: row => row.name }) }}\n library={[Calendar.library.backlog()]} slice={{ slice, affordances: [\"fields\", \"summary\"] }}\n view={{ layout: \"calendar\", period: \"day\", date: new Date(\"2026-10-02T00:00:00Z\") }}\n now={new Date(\"2026-10-02T09:00:00Z\")} />\n </Box>;\n }}</Reactive>)"
|
|
40546
|
+
},
|
|
40547
|
+
{
|
|
40548
|
+
"id": "e3-ui:calendar-windows.examples.tsx:calendarWindows",
|
|
40549
|
+
"skill": "e3-ui",
|
|
40550
|
+
"package": "e3-ui",
|
|
40551
|
+
"file": "east-ui/packages/e3-ui/test/calendar/calendar-windows.examples.tsx",
|
|
40552
|
+
"suite": "",
|
|
40553
|
+
"test": "A Calendar read a page at a time — 2,000 presses paged 200 to a window (`Calendar.resources` over `Data.bindPaged`), the timeline's rows the pages around the view and its Choosers searching the presses by key, and the print jobs on them read a cell at a time through one Slice.bindRecords rail: its `job` source lists the interval index, a series index (`e3.recordIndex.series`, the jobs by press and week) and a summary index (`e3.recordIndex.summary`, each press's busy share by month), and the Calendar reads only the jobs of the presses and the weeks on screen, two requests a change, a press's months shaded from the summary past 10,000 — the time grids and the agenda the window counted first, its Backlog paged through the source, one job by its key through the record's own entries, never a record whole",
|
|
40554
|
+
"keywords": [
|
|
40555
|
+
"Calendar",
|
|
40556
|
+
"Schedule",
|
|
40557
|
+
"Calendar.resources",
|
|
40558
|
+
"Calendar.events",
|
|
40559
|
+
"Slice",
|
|
40560
|
+
"Slice.bindRecords",
|
|
40561
|
+
"source",
|
|
40562
|
+
"range",
|
|
40563
|
+
"window",
|
|
40564
|
+
"interval",
|
|
40565
|
+
"interval index",
|
|
40566
|
+
"series",
|
|
40567
|
+
"series index",
|
|
40568
|
+
"summary",
|
|
40569
|
+
"summary index",
|
|
40570
|
+
"e3.recordIndex",
|
|
40571
|
+
"e3.recordIndex.series",
|
|
40572
|
+
"e3.recordIndex.summary",
|
|
40573
|
+
"tile",
|
|
40574
|
+
"cell",
|
|
40575
|
+
"paged",
|
|
40576
|
+
"page",
|
|
40577
|
+
"Data.bindPaged",
|
|
40578
|
+
"backlog",
|
|
40579
|
+
"large record",
|
|
40580
|
+
"key search",
|
|
40581
|
+
"timeline",
|
|
40582
|
+
"scroll",
|
|
40583
|
+
"shading",
|
|
40584
|
+
"presses",
|
|
40585
|
+
"jobs",
|
|
40586
|
+
"Record.bind",
|
|
40587
|
+
"entries",
|
|
40588
|
+
"by key",
|
|
40589
|
+
"Save",
|
|
40590
|
+
"#1424"
|
|
40591
|
+
],
|
|
40592
|
+
"imports": [
|
|
40593
|
+
"import { East, example, some, variant } from \"@elaraai/east\";",
|
|
40594
|
+
"import { Box, Reactive, Slice, UIComponentType } from \"@elaraai/east-ui\";",
|
|
40595
|
+
"import { Calendar, Data, Record } from \"@elaraai/e3-ui\";",
|
|
40596
|
+
"import {"
|
|
40597
|
+
],
|
|
40598
|
+
"languages": [
|
|
40599
|
+
"tsx"
|
|
40600
|
+
],
|
|
40601
|
+
"source": "// A Calendar read a page at a time — 2,000 presses paged 200 to a window (`Calendar.resources` over `Data.bindPaged`), the timeline's rows the pages around the view and its Choosers searching the presses by key, and the print jobs on them read a cell at a time through one Slice.bindRecords rail: its `job` source lists the interval index, a series index (`e3.recordIndex.series`, the jobs by press and week) and a summary index (`e3.recordIndex.summary`, each press's busy share by month), and the Calendar reads only the jobs of the presses and the weeks on screen, two requests a change, a press's months shaded from the summary past 10,000 — the time grids and the agenda the window counted first, its Backlog paged through the source, one job by its key through the record's own entries, never a record whole\n// inputs: []\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n // The presses a window at a time; the jobs bound with their patch door, and read through the rail's source.\n const presses = $.let(Data.bindPaged(planWindowPresses));\n const jobs = $.let(Record.bind(planWindowJobs, [planWindowJobsPatch]));\n const entries = $.let(Data.bindPaged(planWindowJobs));\n // One rail: its `job` source lists the series and the summary beside the interval index, its range the window the\n // Calendar's navigation writes — the Calendar reads the jobs' cells through them.\n const slice = $.let(Slice.bindRecords(\"ex.calendarWindows\", {\n job: { source: Data.bindPaged(planWindowJobs, { indexes: [planWindowJobsByTime, planWindowJobsByPressWeek, planWindowJobsByPressMonth] }),\n label: \"Print jobs\", icon: { prefix: \"fas\", name: \"file-lines\" }, fields: { start: { label: \"Dates\" } }, range: \"start\" },\n }));\n return <Box height=\"720px\"><Calendar id=\"windows\" inspector\n view={{ layout: \"timeline\", period: \"week\", date: new Date(\"2026-10-05T00:00:00Z\") }} now={new Date(\"2026-10-14T09:00:00Z\")}\n slice={{ slice }}\n resources={{\n presses: Calendar.resources(presses, { name: \"Presses\", icon: \"print\", label: p => p.name, meta: (p, key) => some(East.str`${key} · ${p.hall}`) }),\n }}\n events={{\n job: Calendar.events(jobs, {\n name: \"Print job\", icon: \"file-lines\", title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"press\", of: \"presses\" },\n backlog: { duration: j => variant(\"hours\", j.sheets.divide(8000.0)), due: j => j.due },\n entries,\n }),\n }}\n library={[Calendar.library.backlog()]} />\n </Box>;\n }}</Reactive>)"
|
|
40602
|
+
},
|
|
40603
|
+
{
|
|
40604
|
+
"id": "e3-ui:calendar-windows.examples.tsx:calendarWindowsByHall",
|
|
40605
|
+
"skill": "e3-ui",
|
|
40606
|
+
"package": "e3-ui",
|
|
40607
|
+
"file": "east-ui/packages/e3-ui/test/calendar/calendar-windows.examples.tsx",
|
|
40608
|
+
"suite": "",
|
|
40609
|
+
"test": "The Calendar of calendarWindows, its 2,000 presses a record narrowed by hall through the rail — a `presses` source named after the paged resource kind's slot, `Data.bindPaged(record, { indexes })` over the presses' hall index — the timeline paging the narrowing's presses themselves in the index's order, and reading the jobs' cells for the presses of the hall on screen alone; a choice keeps the last view standing until its presses land",
|
|
40610
|
+
"keywords": [
|
|
40611
|
+
"Calendar",
|
|
40612
|
+
"Schedule",
|
|
40613
|
+
"Calendar.resources",
|
|
40614
|
+
"Slice",
|
|
40615
|
+
"Slice.bindRecords",
|
|
40616
|
+
"source",
|
|
40617
|
+
"rail",
|
|
40618
|
+
"narrow",
|
|
40619
|
+
"narrowing",
|
|
40620
|
+
"filter",
|
|
40621
|
+
"resource kind",
|
|
40622
|
+
"hall",
|
|
40623
|
+
"e3.recordIndex",
|
|
40624
|
+
"index",
|
|
40625
|
+
"paged",
|
|
40626
|
+
"Data.bindPaged",
|
|
40627
|
+
"series index",
|
|
40628
|
+
"summary index",
|
|
40629
|
+
"cell",
|
|
40630
|
+
"large record",
|
|
40631
|
+
"presses",
|
|
40632
|
+
"jobs",
|
|
40633
|
+
"timeline",
|
|
40634
|
+
"#1424"
|
|
40635
|
+
],
|
|
40636
|
+
"imports": [
|
|
40637
|
+
"import { East, example, some, variant } from \"@elaraai/east\";",
|
|
40638
|
+
"import { Box, Reactive, Slice, UIComponentType } from \"@elaraai/east-ui\";",
|
|
40639
|
+
"import { Calendar, Data, Record } from \"@elaraai/e3-ui\";",
|
|
40640
|
+
"import {"
|
|
40641
|
+
],
|
|
40642
|
+
"languages": [
|
|
40643
|
+
"tsx"
|
|
40644
|
+
],
|
|
40645
|
+
"source": "// The Calendar of calendarWindows, its 2,000 presses a record narrowed by hall through the rail — a `presses` source named after the paged resource kind's slot, `Data.bindPaged(record, { indexes })` over the presses' hall index — the timeline paging the narrowing's presses themselves in the index's order, and reading the jobs' cells for the presses of the hall on screen alone; a choice keeps the last view standing until its presses land\n// inputs: []\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const presses = $.let(Data.bindPaged(planSeriesPresses));\n const jobs = $.let(Record.bind(planWindowJobs, [planWindowJobsPatch]));\n const entries = $.let(Data.bindPaged(planWindowJobs));\n // The `presses` source narrows the paged kind by hall, through the presses' hall index: the timeline pages the\n // narrowing's presses. The `job` source reads the jobs a cell at a time, as in calendarWindows.\n const slice = $.let(Slice.bindRecords(\"ex.calendarWindowsByHall\", {\n presses: { source: Data.bindPaged(planSeriesPresses, { indexes: [planSeriesPressesByHall] }), label: \"Presses\",\n icon: { prefix: \"fas\", name: \"print\" },\n fields: { hall: { label: \"Hall\", hints: [\"Hall 1\", \"Hall 2\", \"Hall 3\", \"Hall 4\", \"Hall 5\", \"Hall 6\", \"Hall 7\", \"Hall 8\"] } } },\n job: { source: Data.bindPaged(planWindowJobs, { indexes: [planWindowJobsByTime, planWindowJobsByPressWeek, planWindowJobsByPressMonth] }),\n label: \"Print jobs\", icon: { prefix: \"fas\", name: \"file-lines\" }, fields: { start: { label: \"Dates\" } }, range: \"start\" },\n }));\n return <Box height=\"720px\"><Calendar id=\"windowsByHall\" inspector\n view={{ layout: \"timeline\", period: \"week\", date: new Date(\"2026-10-05T00:00:00Z\") }} now={new Date(\"2026-10-14T09:00:00Z\")}\n slice={{ slice }}\n resources={{\n presses: Calendar.resources(presses, { name: \"Presses\", icon: \"print\", label: p => p.name, meta: (p, key) => some(East.str`${key} · ${p.hall}`) }),\n }}\n events={{\n job: Calendar.events(jobs, {\n name: \"Print job\", icon: \"file-lines\", title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"press\", of: \"presses\" },\n backlog: { duration: j => variant(\"hours\", j.sheets.divide(8000.0)), due: j => j.due },\n entries,\n }),\n }}\n library={[Calendar.library.backlog()]} />\n </Box>;\n }}</Reactive>)"
|
|
40646
|
+
},
|
|
40647
|
+
{
|
|
40648
|
+
"id": "e3-ui:calendar-windows.examples.tsx:calendarWindowsMobile",
|
|
40649
|
+
"skill": "e3-ui",
|
|
40650
|
+
"package": "e3-ui",
|
|
40651
|
+
"file": "east-ui/packages/e3-ui/test/calendar/calendar-windows.examples.tsx",
|
|
40652
|
+
"suite": "",
|
|
40653
|
+
"test": "The Calendar of calendarWindows in a phone-width container: the agenda cards of the jobs in the day, the window counted first and read through the interval index, their explicit Move, Resize and Return to backlog actions searching the 2,000 presses by key, and the Backlog paged through the rail's source",
|
|
40654
|
+
"keywords": [
|
|
40655
|
+
"Calendar",
|
|
40656
|
+
"mobile",
|
|
40657
|
+
"agenda",
|
|
40658
|
+
"cards",
|
|
40659
|
+
"paged",
|
|
40660
|
+
"Data.bindPaged",
|
|
40661
|
+
"series index",
|
|
40662
|
+
"summary index",
|
|
40663
|
+
"presses",
|
|
40664
|
+
"jobs",
|
|
40665
|
+
"key search",
|
|
40666
|
+
"#1424"
|
|
40667
|
+
],
|
|
40668
|
+
"imports": [
|
|
40669
|
+
"import { East, example, some, variant } from \"@elaraai/east\";",
|
|
40670
|
+
"import { Box, Reactive, Slice, UIComponentType } from \"@elaraai/east-ui\";",
|
|
40671
|
+
"import { Calendar, Data, Record } from \"@elaraai/e3-ui\";",
|
|
40672
|
+
"import {"
|
|
40673
|
+
],
|
|
40674
|
+
"languages": [
|
|
40675
|
+
"tsx"
|
|
40676
|
+
],
|
|
40677
|
+
"source": "// The Calendar of calendarWindows in a phone-width container: the agenda cards of the jobs in the day, the window counted first and read through the interval index, their explicit Move, Resize and Return to backlog actions searching the 2,000 presses by key, and the Backlog paged through the rail's source\n// inputs: []\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const presses = $.let(Data.bindPaged(planWindowPresses));\n const jobs = $.let(Record.bind(planWindowJobs, [planWindowJobsPatch]));\n const entries = $.let(Data.bindPaged(planWindowJobs));\n const slice = $.let(Slice.bindRecords(\"ex.calendarWindowsMobile\", {\n job: { source: Data.bindPaged(planWindowJobs, { indexes: [planWindowJobsByTime, planWindowJobsByPressWeek, planWindowJobsByPressMonth] }),\n label: \"Print jobs\", icon: { prefix: \"fas\", name: \"file-lines\" }, fields: { start: { label: \"Dates\" } }, range: \"start\" },\n }));\n return <Box width=\"100%\" maxWidth=\"390px\" height=\"760px\"><Calendar id=\"windowsMobile\" inspector\n view={{ layout: \"calendar\", period: \"day\", date: new Date(\"2026-10-05T00:00:00Z\") }} now={new Date(\"2026-10-14T09:00:00Z\")}\n slice={{ slice }}\n resources={{\n presses: Calendar.resources(presses, { name: \"Presses\", icon: \"print\", label: p => p.name, meta: (p, key) => some(East.str`${key} · ${p.hall}`) }),\n }}\n events={{\n job: Calendar.events(jobs, {\n name: \"Print job\", icon: \"file-lines\", title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"press\", of: \"presses\" },\n backlog: { duration: j => variant(\"hours\", j.sheets.divide(8000.0)), due: j => j.due },\n entries,\n }),\n }}\n library={[Calendar.library.backlog()]} />\n </Box>;\n }}</Reactive>)"
|
|
40866
40678
|
},
|
|
40867
40679
|
{
|
|
40868
40680
|
"id": "e3-ui:calendar.examples.tsx:calendarOperations",
|
|
@@ -40898,7 +40710,7 @@
|
|
|
40898
40710
|
"languages": [
|
|
40899
40711
|
"tsx"
|
|
40900
40712
|
],
|
|
40901
|
-
"source": "// A metal fabrication calendar over three event records and bound machines, people and templates: Calendar, Resources and Timeline share one frame, one Slice.bindRecords rail — a source per event kind, resource kind and template record — and Save history. Its library schedules the event record's backlog and assigns technicians; overlapping production and maintenance warn without blocking Save.\n// inputs: []\n/** The resources of a small metal fabrication shop. */\nexport const CalendarMachine = StructType({ name: StringType, area: StringType });\n\n/** People the shifts run on and the maintenance library assigns. */\nexport const CalendarPerson = StructType({ name: StringType, team: StringType });\n\n/** A job's lifecycle. */\nexport const CalendarJobState = VariantType({ planned: NullType, active: NullType, done: NullType });\n\n/** A job may wait in the backlog; its own record holds its unscheduled rows. */\nexport const CalendarJob = StructType({ title: StringType, start: OptionType(DateTimeType), end: OptionType(DateTimeType), machine: OptionType(StringType),\n status: CalendarJobState, hours: FloatType, customer: StringType, due: OptionType(DateTimeType) });\n\n/** The initial job fields a template record supplies. */\nexport const CalendarJobValues = StructType({ title: StringType, status: CalendarJobState, hours: FloatType, customer: StringType, due: OptionType(DateTimeType) });\n\n/** Maintenance is a different record, with its own fields and patch mutation. */\nexport const CalendarService = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, machine: StringType, technician: StringType, notes: StringType });\n\n/** A person's shift is its own event kind. */\nexport const CalendarShift = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, person: StringType, notes: StringType });\n\n/** Job templates are live record data, including their duration and initial values; each has a group, which the rail narrows them by. */\nexport const CalendarJobTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: CalendarJobValues });\n\n/** Maintenance templates are also bound data, grouped as the job templates are. */\nexport const CalendarServiceTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: StructType({ title: StringType, technician: StringType, notes: StringType }) });\n\n/** Shift templates start at their hour when dropped on a day. */\nexport const CalendarShiftTemplate = StructType({ name: StringType, group: StringType, hour: IntegerType, values: StructType({ title: StringType, notes: StringType }) });\n\nexport const calendarMachines = e3.record(\"calendar_machines\", DictType(StringType, CalendarMachine), new Map([\n [\"press1\", { name: \"Press 1\", area: \"Press shop\" }], [\"press2\", { name: \"Press 2\", area: \"Press shop\" }],\n [\"lathe1\", { name: \"Lathe 1\", area: \"Machine shop\" }], [\"lathe2\", { name: \"Lathe 2\", area: \"Machine shop\" }],\n]));\n\nexport const calendarPeople = e3.record(\"calendar_people\", DictType(StringType, CalendarPerson), new Map([\n [\"ash\", { name: \"A. Ash\", team: \"Maintenance\" }], [\"birch\", { name: \"B. Birch\", team: \"Production\" }], [\"cedar\", { name: \"C. Cedar\", team: \"Maintenance\" }],\n]));\n\nexport const calendarJobs = e3.record(\"calendar_jobs\", DictType(StringType, CalendarJob), new Map([\n [\"job1\", { title: \"Mounting brackets · batch 42\", start: some(new Date(\"2026-10-01T06:00:00Z\")), end: some(new Date(\"2026-10-01T10:00:00Z\")), machine: some(\"press1\"), status: variant(\"active\", null), hours: 4.0, customer: \"Alder Works\", due: some(new Date(\"2026-10-02T00:00:00Z\")) }],\n [\"job2\", { title: \"Pump housings\", start: some(new Date(\"2026-10-01T08:00:00Z\")), end: some(new Date(\"2026-10-01T13:00:00Z\")), machine: some(\"lathe1\"), status: variant(\"planned\", null), hours: 5.0, customer: \"Birch Engineering\", due: none }],\n [\"job3\", { title: \"Long title: stainless steel mounting plates for the prototype assembly\", start: some(new Date(\"2026-10-01T11:00:00Z\")), end: some(new Date(\"2026-10-01T11:30:00Z\")), machine: some(\"press2\"), status: variant(\"planned\", null), hours: 0.5, customer: \"Cedar Fabrication\", due: none }],\n [\"job4\", { title: \"Bearing collars\", start: some(new Date(\"2026-10-02T09:00:00Z\")), end: some(new Date(\"2026-10-02T12:00:00Z\")), machine: some(\"lathe2\"), status: variant(\"planned\", null), hours: 3.0, customer: \"Alder Works\", due: none }],\n [\"job5\", { title: \"Overnight batch\", start: some(new Date(\"2026-10-01T22:00:00Z\")), end: some(new Date(\"2026-10-02T02:00:00Z\")), machine: some(\"press1\"), status: variant(\"planned\", null), hours: 4.0, customer: \"Birch Engineering\", due: none }],\n [\"job6\", { title: \"September closeout\", start: some(new Date(\"2026-09-30T08:00:00Z\")), end: some(new Date(\"2026-09-30T10:00:00Z\")), machine: some(\"press2\"), status: variant(\"done\", null), hours: 2.0, customer: \"Alder Works\", due: none }],\n [\"backlog1\", { title: \"Torque-test fixtures\", start: none, end: none, machine: some(\"press1\"), status: variant(\"planned\", null), hours: 2.0, customer: \"Alder Works\", due: some(new Date(\"2026-10-02T00:00:00Z\")) }],\n [\"backlog2\", { title: \"Valve bodies\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 3.0, customer: \"Cedar Fabrication\", due: some(new Date(\"2026-10-06T00:00:00Z\")) }],\n [\"backlog3\", { title: \"Replacement shims\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 1.0, customer: \"Birch Engineering\", due: some(new Date(\"2026-10-20T00:00:00Z\")) }],\n [\"backlog4\", { title: \"Prototype spacers\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 2.5, customer: \"Alder Works\", due: none }],\n]));\n\nexport const calendarJobsPatch = e3.mutation.patch(calendarJobs);\n\nexport const calendarServices = e3.record(\"calendar_services\", DictType(StringType, CalendarService), new Map([\n [\"service1\", { title: \"Preventive maintenance\", start: new Date(\"2026-10-01T09:00:00Z\"), end: new Date(\"2026-10-01T11:00:00Z\"), machine: \"press1\", technician: \"ash\", notes: \"Inspect the tooling and check torque.\" }],\n [\"service2\", { title: \"Calibration\", start: new Date(\"2026-10-02T13:00:00Z\"), end: new Date(\"2026-10-02T14:00:00Z\"), machine: \"lathe1\", technician: \"cedar\", notes: \"Check against the reference gauge.\" }],\n]));\n\nexport const calendarServicesPatch = e3.mutation.patch(calendarServices);\n\nexport const calendarShifts = e3.record(\"calendar_shifts\", DictType(StringType, CalendarShift), new Map([\n [\"shift1\", { title: \"Day shift\", start: new Date(\"2026-10-01T06:00:00Z\"), end: new Date(\"2026-10-01T14:00:00Z\"), person: \"ash\", notes: \"Press shop\" }],\n [\"shift2\", { title: \"Late shift\", start: new Date(\"2026-10-01T14:00:00Z\"), end: new Date(\"2026-10-01T22:00:00Z\"), person: \"birch\", notes: \"Machine shop\" }],\n]));\n\nexport const calendarShiftsPatch = e3.mutation.patch(calendarShifts);\n\nexport const calendarJobTemplates = e3.record(\"calendar_job_templates\", DictType(StringType, CalendarJobTemplate), new Map([\n [\"brackets\", { name: \"Bracket batch\", group: \"Fabrication\", hours: 4.0, values: { title: \"Bracket batch\", status: variant(\"planned\", null), hours: 4.0, customer: \"\", due: none } }],\n [\"housings\", { name: \"Housing batch\", group: \"Machining\", hours: 2.0, values: { title: \"Housing batch\", status: variant(\"planned\", null), hours: 2.0, customer: \"\", due: none } }],\n]));\n\nexport const calendarServiceTemplates = e3.record(\"calendar_service_templates\", DictType(StringType, CalendarServiceTemplate), new Map([\n [\"calibration\", { name: \"Calibration\", group: \"Calibration\", hours: 1.0, values: { title: \"Calibration\", technician: \"cedar\", notes: \"Check against the reference gauge.\" } }],\n [\"service\", { name: \"Preventive maintenance\", group: \"Preventive\", hours: 2.0, values: { title: \"Preventive maintenance\", technician: \"ash\", notes: \"\" } }],\n]));\n\nexport const calendarShiftTemplates = e3.record(\"calendar_shift_templates\", DictType(StringType, CalendarShiftTemplate), new Map([\n [\"day\", { name: \"Day shift\", group: \"Day\", hour: 6n, values: { title: \"Day shift\", notes: \"\" } }],\n [\"late\", { name: \"Late shift\", group: \"Late\", hour: 14n, values: { title: \"Late shift\", notes: \"\" } }],\n]));\n\nexport const calendarJobsByTime = e3.recordIndex(\"calendar_jobs_by_time\", calendarJobs, { interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarJobsByStatusTime = e3.recordIndex(\"calendar_jobs_by_status_time\", calendarJobs, { on: [\"status\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarJobsByMachineTime = e3.recordIndex(\"calendar_jobs_by_machine_time\", calendarJobs, { on: [\"machine\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarServicesByTime = e3.recordIndex(\"calendar_services_by_time\", calendarServices, { interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarServicesByMachineTime = e3.recordIndex(\"calendar_services_by_machine_time\", calendarServices, { on: [\"machine\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarShiftsByTime = e3.recordIndex(\"calendar_shifts_by_time\", calendarShifts, { interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarShiftsByPersonTime = e3.recordIndex(\"calendar_shifts_by_person_time\", calendarShifts, { on: [\"person\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarMachinesByArea = e3.recordIndex(\"calendar_machines_by_area\", calendarMachines, { on: [\"area\"] });\n\nexport const calendarPeopleByTeam = e3.recordIndex(\"calendar_people_by_team\", calendarPeople, { on: [\"team\"] });\n\nexport const calendarJobTemplatesByGroup = e3.recordIndex(\"calendar_job_templates_by_group\", calendarJobTemplates, { on: [\"group\"] });\n\nexport const calendarServiceTemplatesByGroup = e3.recordIndex(\"calendar_service_templates_by_group\", calendarServiceTemplates, { on: [\"group\"] });\n\nexport const calendarShiftTemplatesByGroup = e3.recordIndex(\"calendar_shift_templates_by_group\", calendarShiftTemplates, { on: [\"group\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n // What the Calendar reads whole binds with Data.bind, which keeps it loaded beside its pages; the event records\n // it writes bind with Record.bind, and it reads their events through their pages alone.\n const machines = $.let(Data.bind(calendarMachines));\n const people = $.let(Data.bind(calendarPeople));\n const jobs = $.let(Record.bind(calendarJobs, [calendarJobsPatch]));\n const services = $.let(Record.bind(calendarServices, [calendarServicesPatch]));\n const shifts = $.let(Record.bind(calendarShifts, [calendarShiftsPatch]));\n const jobTemplates = $.let(Data.bind(calendarJobTemplates));\n const serviceTemplates = $.let(Data.bind(calendarServiceTemplates));\n const shiftTemplates = $.let(Data.bind(calendarShiftTemplates));\n // Every record paged: each event kind's interval indexes and its own entries, the others' fields' indexes.\n const jobIndex = $.let(Data.bindPaged(calendarJobs, { indexes: [calendarJobsByTime, calendarJobsByStatusTime, calendarJobsByMachineTime] }));\n const serviceIndex = $.let(Data.bindPaged(calendarServices, { indexes: [calendarServicesByTime, calendarServicesByMachineTime] }));\n const shiftIndex = $.let(Data.bindPaged(calendarShifts, { indexes: [calendarShiftsByTime, calendarShiftsByPersonTime] }));\n const machineIndex = $.let(Data.bindPaged(calendarMachines, { indexes: [calendarMachinesByArea] }));\n const peopleIndex = $.let(Data.bindPaged(calendarPeople, { indexes: [calendarPeopleByTeam] }));\n const jobTemplateIndex = $.let(Data.bindPaged(calendarJobTemplates, { indexes: [calendarJobTemplatesByGroup] }));\n const serviceTemplateIndex = $.let(Data.bindPaged(calendarServiceTemplates, { indexes: [calendarServiceTemplatesByGroup] }));\n const shiftTemplateIndex = $.let(Data.bindPaged(calendarShiftTemplates, { indexes: [calendarShiftTemplatesByGroup] }));\n const jobEntries = $.let(Data.bindPaged(calendarJobs));\n const serviceEntries = $.let(Data.bindPaged(calendarServices));\n const shiftEntries = $.let(Data.bindPaged(calendarShifts));\n // One rail over every record, a source each (#1343): each event kind through its interval indexes, its range the\n // window the navigation writes in every kind at once; the machines, the people and each templates record by its fields.\n const slice = $.let(Slice.bindRecords(\"ex.calendarOperations\", {\n job: { source: jobIndex, icon: { prefix: \"fas\", name: \"industry\" },\n fields: { status: { label: \"Status\" }, machine: { label: \"Machine\", hints: [\"press1\", \"press2\", \"lathe1\", \"lathe2\", null] }, start: { label: \"Dates\" } }, range: \"start\" },\n service: { source: serviceIndex, icon: { prefix: \"fas\", name: \"screwdriver-wrench\" },\n fields: { machine: { label: \"Machine\", hints: [\"press1\", \"press2\", \"lathe1\", \"lathe2\"] }, start: { label: \"Dates\" } }, range: \"start\" },\n shift: { source: shiftIndex, icon: { prefix: \"fas\", name: \"user-clock\" },\n fields: { person: { label: \"Person\", hints: [\"ash\", \"birch\", \"cedar\"] }, start: { label: \"Dates\" } }, range: \"start\" },\n machines: { source: machineIndex, icon: { prefix: \"fas\", name: \"gears\" },\n fields: { area: { label: \"Area\", hints: [\"Press shop\", \"Machine shop\"] } } },\n people: { source: peopleIndex, icon: { prefix: \"fas\", name: \"users\" },\n fields: { team: { label: \"Team\", hints: [\"Maintenance\", \"Production\"] } } },\n jobTemplates: { source: jobTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Job template\", hints: [\"Fabrication\", \"Machining\"] } } },\n serviceTemplates: { source: serviceTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Service template\", hints: [\"Preventive\", \"Calibration\"] } } },\n shiftTemplates: { source: shiftTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Shift template\", hints: [\"Day\", \"Late\"] } } },\n }));\n return <Box height=\"760px\"><Calendar id=\"operations\" inspector view={{ date: new Date(\"2026-10-01T00:00:00Z\") }} now={new Date(\"2026-10-01T10:15:00Z\")}\n slice={{ slice }}\n resources={{\n machines: Calendar.resources(machines.read(), { name: \"Machines\", icon: \"gears\", label: row => row.name, meta: row => some(row.area) }),\n people: Calendar.resources(people.read(), { name: \"People\", icon: \"users\", label: row => row.name, meta: row => some(row.team) }),\n }}\n events={{\n job: Calendar.events(jobs, { name: \"Production\", icon: \"industry\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n entries: jobEntries,\n status: { field: \"status\", cases: { planned: { label: \"Planned\", tone: variant(\"neutral\", null), ring: true }, active: { label: \"Active\", tone: variant(\"info\", null), ring: false }, done: { label: \"Done\", tone: variant(\"success\", null), ring: false } } },\n backlog: { duration: row => variant(\"hours\", row.hours), due: row => row.due },\n ready: row => row.hours.lessEqual(0).ifElse(() => variant(\"incomplete\", [{ field: \"hours\", message: \"Duration must be positive\" }]), () => variant(\"ready\", null)),\n templates: Calendar.templates(jobTemplates.read(), { name: row => row.name, group: row => some(row.group), duration: row => variant(\"hours\", row.hours), values: row => row.values, source: \"jobTemplates\" }),\n }),\n service: Calendar.events(services, { name: \"Maintenance\", icon: \"screwdriver-wrench\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n entries: serviceEntries,\n fields: { technician: Calendar.field.reference({ of: \"people\" }) },\n templates: Calendar.templates(serviceTemplates.read(), { name: row => row.name, group: row => some(row.group), duration: row => variant(\"hours\", row.hours), values: row => row.values, source: \"serviceTemplates\" }),\n }),\n shift: Calendar.events(shifts, { name: \"Shift\", icon: \"user-clock\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"person\", of: \"people\" }, overlaps: \"allow\",\n entries: shiftEntries,\n templates: Calendar.templates(shiftTemplates.read(), { name: row => row.name, group: row => some(row.group), at: row => some({ hour: row.hour, minute: 0n }), duration: () => variant(\"hours\", 8.0), values: row => row.values, source: \"shiftTemplates\" }),\n }),\n }}\n library={[Calendar.library.templates(), Calendar.library.backlog(), Calendar.library.tab(people.read(), {\n name: \"Technicians\", icon: \"user-gear\", label: row => row.name, meta: row => some(row.team), group: row => row.team,\n drop: (_row, key) => Calendar.patch(CalendarService, { technician: key }), source: \"people\",\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
40713
|
+
"source": "// A metal fabrication calendar over three event records and bound machines, people and templates: Calendar, Resources and Timeline share one frame, one Slice.bindRecords rail — a source per event kind, resource kind and template record — and Save history. Its library schedules the event record's backlog and assigns technicians; overlapping production and maintenance warn without blocking Save.\n// inputs: []\n/** The resources of a small metal fabrication shop. */\nexport const CalendarMachine = StructType({ name: StringType, area: StringType });\n\n/** People the shifts run on and the maintenance library assigns. */\nexport const CalendarPerson = StructType({ name: StringType, team: StringType });\n\n/** A job's lifecycle. */\nexport const CalendarJobState = VariantType({ planned: NullType, active: NullType, done: NullType });\n\n/** A job may wait in the backlog; its own record holds its unscheduled rows. */\nexport const CalendarJob = StructType({ title: StringType, start: OptionType(DateTimeType), end: OptionType(DateTimeType), machine: OptionType(StringType),\n status: CalendarJobState, hours: FloatType, customer: StringType, due: OptionType(DateTimeType) });\n\n/** The initial job fields a template record supplies. */\nexport const CalendarJobValues = StructType({ title: StringType, status: CalendarJobState, hours: FloatType, customer: StringType, due: OptionType(DateTimeType) });\n\n/** Maintenance is a different record, with its own fields and patch mutation. */\nexport const CalendarService = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, machine: StringType, technician: StringType, notes: StringType });\n\n/** A person's shift is its own event kind. */\nexport const CalendarShift = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, person: StringType, notes: StringType });\n\n/** Job templates are live record data, including their duration and initial values; each has a group, which the rail narrows them by. */\nexport const CalendarJobTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: CalendarJobValues });\n\n/** Maintenance templates are also bound data, grouped as the job templates are. */\nexport const CalendarServiceTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: StructType({ title: StringType, technician: StringType, notes: StringType }) });\n\n/** Shift templates start at their hour when dropped on a day. */\nexport const CalendarShiftTemplate = StructType({ name: StringType, group: StringType, hour: IntegerType, values: StructType({ title: StringType, notes: StringType }) });\n\nexport const calendarMachines = e3.record(\"calendar_machines\", DictType(StringType, CalendarMachine), new Map([\n [\"press1\", { name: \"Press 1\", area: \"Press shop\" }], [\"press2\", { name: \"Press 2\", area: \"Press shop\" }],\n [\"lathe1\", { name: \"Lathe 1\", area: \"Machine shop\" }], [\"lathe2\", { name: \"Lathe 2\", area: \"Machine shop\" }],\n]));\n\nexport const calendarPeople = e3.record(\"calendar_people\", DictType(StringType, CalendarPerson), new Map([\n [\"ash\", { name: \"A. Ash\", team: \"Maintenance\" }], [\"birch\", { name: \"B. Birch\", team: \"Production\" }], [\"cedar\", { name: \"C. Cedar\", team: \"Maintenance\" }],\n]));\n\nexport const calendarJobs = e3.record(\"calendar_jobs\", DictType(StringType, CalendarJob), new Map([\n [\"job1\", { title: \"Mounting brackets · batch 42\", start: some(new Date(\"2026-10-01T06:00:00Z\")), end: some(new Date(\"2026-10-01T10:00:00Z\")), machine: some(\"press1\"), status: variant(\"active\", null), hours: 4.0, customer: \"Alder Works\", due: some(new Date(\"2026-10-02T00:00:00Z\")) }],\n [\"job2\", { title: \"Pump housings\", start: some(new Date(\"2026-10-01T08:00:00Z\")), end: some(new Date(\"2026-10-01T13:00:00Z\")), machine: some(\"lathe1\"), status: variant(\"planned\", null), hours: 5.0, customer: \"Birch Engineering\", due: none }],\n [\"job3\", { title: \"Long title: stainless steel mounting plates for the prototype assembly\", start: some(new Date(\"2026-10-01T11:00:00Z\")), end: some(new Date(\"2026-10-01T11:30:00Z\")), machine: some(\"press2\"), status: variant(\"planned\", null), hours: 0.5, customer: \"Cedar Fabrication\", due: none }],\n [\"job4\", { title: \"Bearing collars\", start: some(new Date(\"2026-10-02T09:00:00Z\")), end: some(new Date(\"2026-10-02T12:00:00Z\")), machine: some(\"lathe2\"), status: variant(\"planned\", null), hours: 3.0, customer: \"Alder Works\", due: none }],\n [\"job5\", { title: \"Overnight batch\", start: some(new Date(\"2026-10-01T22:00:00Z\")), end: some(new Date(\"2026-10-02T02:00:00Z\")), machine: some(\"press1\"), status: variant(\"planned\", null), hours: 4.0, customer: \"Birch Engineering\", due: none }],\n [\"job6\", { title: \"September closeout\", start: some(new Date(\"2026-09-30T08:00:00Z\")), end: some(new Date(\"2026-09-30T10:00:00Z\")), machine: some(\"press2\"), status: variant(\"done\", null), hours: 2.0, customer: \"Alder Works\", due: none }],\n [\"backlog1\", { title: \"Torque-test fixtures\", start: none, end: none, machine: some(\"press1\"), status: variant(\"planned\", null), hours: 2.0, customer: \"Alder Works\", due: some(new Date(\"2026-10-02T00:00:00Z\")) }],\n [\"backlog2\", { title: \"Valve bodies\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 3.0, customer: \"Cedar Fabrication\", due: some(new Date(\"2026-10-06T00:00:00Z\")) }],\n [\"backlog3\", { title: \"Replacement shims\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 1.0, customer: \"Birch Engineering\", due: some(new Date(\"2026-10-20T00:00:00Z\")) }],\n [\"backlog4\", { title: \"Prototype spacers\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 2.5, customer: \"Alder Works\", due: none }],\n]));\n\nexport const calendarJobsPatch = e3.mutation.patch(calendarJobs);\n\nexport const calendarServices = e3.record(\"calendar_services\", DictType(StringType, CalendarService), new Map([\n [\"service1\", { title: \"Preventive maintenance\", start: new Date(\"2026-10-01T09:00:00Z\"), end: new Date(\"2026-10-01T11:00:00Z\"), machine: \"press1\", technician: \"ash\", notes: \"Inspect the tooling and check torque.\" }],\n [\"service2\", { title: \"Calibration\", start: new Date(\"2026-10-02T13:00:00Z\"), end: new Date(\"2026-10-02T14:00:00Z\"), machine: \"lathe1\", technician: \"cedar\", notes: \"Check against the reference gauge.\" }],\n]));\n\nexport const calendarServicesPatch = e3.mutation.patch(calendarServices);\n\nexport const calendarShifts = e3.record(\"calendar_shifts\", DictType(StringType, CalendarShift), new Map([\n [\"shift1\", { title: \"Day shift\", start: new Date(\"2026-10-01T06:00:00Z\"), end: new Date(\"2026-10-01T14:00:00Z\"), person: \"ash\", notes: \"Press shop\" }],\n [\"shift2\", { title: \"Late shift\", start: new Date(\"2026-10-01T14:00:00Z\"), end: new Date(\"2026-10-01T22:00:00Z\"), person: \"birch\", notes: \"Machine shop\" }],\n]));\n\nexport const calendarShiftsPatch = e3.mutation.patch(calendarShifts);\n\nexport const calendarJobTemplates = e3.record(\"calendar_job_templates\", DictType(StringType, CalendarJobTemplate), new Map([\n [\"brackets\", { name: \"Bracket batch\", group: \"Fabrication\", hours: 4.0, values: { title: \"Bracket batch\", status: variant(\"planned\", null), hours: 4.0, customer: \"\", due: none } }],\n [\"housings\", { name: \"Housing batch\", group: \"Machining\", hours: 2.0, values: { title: \"Housing batch\", status: variant(\"planned\", null), hours: 2.0, customer: \"\", due: none } }],\n]));\n\nexport const calendarServiceTemplates = e3.record(\"calendar_service_templates\", DictType(StringType, CalendarServiceTemplate), new Map([\n [\"calibration\", { name: \"Calibration\", group: \"Calibration\", hours: 1.0, values: { title: \"Calibration\", technician: \"cedar\", notes: \"Check against the reference gauge.\" } }],\n [\"service\", { name: \"Preventive maintenance\", group: \"Preventive\", hours: 2.0, values: { title: \"Preventive maintenance\", technician: \"ash\", notes: \"\" } }],\n]));\n\nexport const calendarShiftTemplates = e3.record(\"calendar_shift_templates\", DictType(StringType, CalendarShiftTemplate), new Map([\n [\"day\", { name: \"Day shift\", group: \"Day\", hour: 6n, values: { title: \"Day shift\", notes: \"\" } }],\n [\"late\", { name: \"Late shift\", group: \"Late\", hour: 14n, values: { title: \"Late shift\", notes: \"\" } }],\n]));\n\nexport const calendarJobsByTime = e3.recordIndex.interval(\"calendar_jobs_by_time\", calendarJobs, { from: \"start\", to: \"end\" });\n\nexport const calendarJobsByStatusTime = e3.recordIndex.interval(\"calendar_jobs_by_status_time\", calendarJobs, { on: [\"status\"], from: \"start\", to: \"end\" });\n\nexport const calendarJobsByMachineTime = e3.recordIndex.interval(\"calendar_jobs_by_machine_time\", calendarJobs, { on: [\"machine\"], from: \"start\", to: \"end\" });\n\nexport const calendarServicesByTime = e3.recordIndex.interval(\"calendar_services_by_time\", calendarServices, { from: \"start\", to: \"end\" });\n\nexport const calendarServicesByMachineTime = e3.recordIndex.interval(\"calendar_services_by_machine_time\", calendarServices, { on: [\"machine\"], from: \"start\", to: \"end\" });\n\nexport const calendarShiftsByTime = e3.recordIndex.interval(\"calendar_shifts_by_time\", calendarShifts, { from: \"start\", to: \"end\" });\n\nexport const calendarShiftsByPersonTime = e3.recordIndex.interval(\"calendar_shifts_by_person_time\", calendarShifts, { on: [\"person\"], from: \"start\", to: \"end\" });\n\nexport const calendarMachinesByArea = e3.recordIndex(\"calendar_machines_by_area\", calendarMachines, { on: [\"area\"] });\n\nexport const calendarPeopleByTeam = e3.recordIndex(\"calendar_people_by_team\", calendarPeople, { on: [\"team\"] });\n\nexport const calendarJobTemplatesByGroup = e3.recordIndex(\"calendar_job_templates_by_group\", calendarJobTemplates, { on: [\"group\"] });\n\nexport const calendarServiceTemplatesByGroup = e3.recordIndex(\"calendar_service_templates_by_group\", calendarServiceTemplates, { on: [\"group\"] });\n\nexport const calendarShiftTemplatesByGroup = e3.recordIndex(\"calendar_shift_templates_by_group\", calendarShiftTemplates, { on: [\"group\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n // What the Calendar reads whole binds with Data.bind, which keeps it loaded beside its pages; the event records\n // it writes bind with Record.bind, and it reads their events through their pages alone.\n const machines = $.let(Data.bind(calendarMachines));\n const people = $.let(Data.bind(calendarPeople));\n const jobs = $.let(Record.bind(calendarJobs, [calendarJobsPatch]));\n const services = $.let(Record.bind(calendarServices, [calendarServicesPatch]));\n const shifts = $.let(Record.bind(calendarShifts, [calendarShiftsPatch]));\n const jobTemplates = $.let(Data.bind(calendarJobTemplates));\n const serviceTemplates = $.let(Data.bind(calendarServiceTemplates));\n const shiftTemplates = $.let(Data.bind(calendarShiftTemplates));\n // Every record paged: each event kind's interval indexes and its own entries, the others' fields' indexes.\n const jobIndex = $.let(Data.bindPaged(calendarJobs, { indexes: [calendarJobsByTime, calendarJobsByStatusTime, calendarJobsByMachineTime] }));\n const serviceIndex = $.let(Data.bindPaged(calendarServices, { indexes: [calendarServicesByTime, calendarServicesByMachineTime] }));\n const shiftIndex = $.let(Data.bindPaged(calendarShifts, { indexes: [calendarShiftsByTime, calendarShiftsByPersonTime] }));\n const machineIndex = $.let(Data.bindPaged(calendarMachines, { indexes: [calendarMachinesByArea] }));\n const peopleIndex = $.let(Data.bindPaged(calendarPeople, { indexes: [calendarPeopleByTeam] }));\n const jobTemplateIndex = $.let(Data.bindPaged(calendarJobTemplates, { indexes: [calendarJobTemplatesByGroup] }));\n const serviceTemplateIndex = $.let(Data.bindPaged(calendarServiceTemplates, { indexes: [calendarServiceTemplatesByGroup] }));\n const shiftTemplateIndex = $.let(Data.bindPaged(calendarShiftTemplates, { indexes: [calendarShiftTemplatesByGroup] }));\n const jobEntries = $.let(Data.bindPaged(calendarJobs));\n const serviceEntries = $.let(Data.bindPaged(calendarServices));\n const shiftEntries = $.let(Data.bindPaged(calendarShifts));\n // One rail over every record, a source each (#1343): each event kind through its interval indexes, its range the\n // window the navigation writes in every kind at once; the machines, the people and each templates record by its fields.\n const slice = $.let(Slice.bindRecords(\"ex.calendarOperations\", {\n job: { source: jobIndex, icon: { prefix: \"fas\", name: \"industry\" },\n fields: { status: { label: \"Status\" }, machine: { label: \"Machine\", hints: [\"press1\", \"press2\", \"lathe1\", \"lathe2\", null] }, start: { label: \"Dates\" } }, range: \"start\" },\n service: { source: serviceIndex, icon: { prefix: \"fas\", name: \"screwdriver-wrench\" },\n fields: { machine: { label: \"Machine\", hints: [\"press1\", \"press2\", \"lathe1\", \"lathe2\"] }, start: { label: \"Dates\" } }, range: \"start\" },\n shift: { source: shiftIndex, icon: { prefix: \"fas\", name: \"user-clock\" },\n fields: { person: { label: \"Person\", hints: [\"ash\", \"birch\", \"cedar\"] }, start: { label: \"Dates\" } }, range: \"start\" },\n machines: { source: machineIndex, icon: { prefix: \"fas\", name: \"gears\" },\n fields: { area: { label: \"Area\", hints: [\"Press shop\", \"Machine shop\"] } } },\n people: { source: peopleIndex, icon: { prefix: \"fas\", name: \"users\" },\n fields: { team: { label: \"Team\", hints: [\"Maintenance\", \"Production\"] } } },\n jobTemplates: { source: jobTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Job template\", hints: [\"Fabrication\", \"Machining\"] } } },\n serviceTemplates: { source: serviceTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Service template\", hints: [\"Preventive\", \"Calibration\"] } } },\n shiftTemplates: { source: shiftTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Shift template\", hints: [\"Day\", \"Late\"] } } },\n }));\n return <Box height=\"760px\"><Calendar id=\"operations\" inspector view={{ date: new Date(\"2026-10-01T00:00:00Z\") }} now={new Date(\"2026-10-01T10:15:00Z\")}\n slice={{ slice }}\n resources={{\n machines: Calendar.resources(machines.read(), { name: \"Machines\", icon: \"gears\", label: row => row.name, meta: row => some(row.area) }),\n people: Calendar.resources(people.read(), { name: \"People\", icon: \"users\", label: row => row.name, meta: row => some(row.team) }),\n }}\n events={{\n job: Calendar.events(jobs, { name: \"Production\", icon: \"industry\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n entries: jobEntries,\n status: { field: \"status\", cases: { planned: { label: \"Planned\", tone: variant(\"neutral\", null), ring: true }, active: { label: \"Active\", tone: variant(\"info\", null), ring: false }, done: { label: \"Done\", tone: variant(\"success\", null), ring: false } } },\n backlog: { duration: row => variant(\"hours\", row.hours), due: row => row.due },\n ready: row => row.hours.lessEqual(0).ifElse(() => variant(\"incomplete\", [{ field: \"hours\", message: \"Duration must be positive\" }]), () => variant(\"ready\", null)),\n templates: Calendar.templates(jobTemplates.read(), { name: row => row.name, group: row => some(row.group), duration: row => variant(\"hours\", row.hours), values: row => row.values, source: \"jobTemplates\" }),\n }),\n service: Calendar.events(services, { name: \"Maintenance\", icon: \"screwdriver-wrench\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n entries: serviceEntries,\n fields: { technician: Calendar.field.reference({ of: \"people\" }) },\n templates: Calendar.templates(serviceTemplates.read(), { name: row => row.name, group: row => some(row.group), duration: row => variant(\"hours\", row.hours), values: row => row.values, source: \"serviceTemplates\" }),\n }),\n shift: Calendar.events(shifts, { name: \"Shift\", icon: \"user-clock\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"person\", of: \"people\" }, overlaps: \"allow\",\n entries: shiftEntries,\n templates: Calendar.templates(shiftTemplates.read(), { name: row => row.name, group: row => some(row.group), at: row => some({ hour: row.hour, minute: 0n }), duration: () => variant(\"hours\", 8.0), values: row => row.values, source: \"shiftTemplates\" }),\n }),\n }}\n library={[Calendar.library.templates(), Calendar.library.backlog(), Calendar.library.tab(people.read(), {\n name: \"Technicians\", icon: \"user-gear\", label: row => row.name, meta: row => some(row.team), group: row => row.team,\n drop: (_row, key) => Calendar.patch(CalendarService, { technician: key }), source: \"people\",\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
40902
40714
|
},
|
|
40903
40715
|
{
|
|
40904
40716
|
"id": "e3-ui:calendar.examples.tsx:calendarMobile",
|
|
@@ -40927,7 +40739,7 @@
|
|
|
40927
40739
|
"languages": [
|
|
40928
40740
|
"tsx"
|
|
40929
40741
|
],
|
|
40930
|
-
"source": "// The same bound calendar in a phone-width container: chronological agenda cards with explicit Move, Resize, Duplicate, Delete and Return to backlog actions, Create and Schedule in the library, shared forms and Save history. Resizing preserves the view, selection and drafts; no dragging on the agenda.\n// inputs: []\n/** The resources of a small metal fabrication shop. */\nexport const CalendarMachine = StructType({ name: StringType, area: StringType });\n\n/** People the shifts run on and the maintenance library assigns. */\nexport const CalendarPerson = StructType({ name: StringType, team: StringType });\n\n/** A job's lifecycle. */\nexport const CalendarJobState = VariantType({ planned: NullType, active: NullType, done: NullType });\n\n/** A job may wait in the backlog; its own record holds its unscheduled rows. */\nexport const CalendarJob = StructType({ title: StringType, start: OptionType(DateTimeType), end: OptionType(DateTimeType), machine: OptionType(StringType),\n status: CalendarJobState, hours: FloatType, customer: StringType, due: OptionType(DateTimeType) });\n\n/** The initial job fields a template record supplies. */\nexport const CalendarJobValues = StructType({ title: StringType, status: CalendarJobState, hours: FloatType, customer: StringType, due: OptionType(DateTimeType) });\n\n/** Maintenance is a different record, with its own fields and patch mutation. */\nexport const CalendarService = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, machine: StringType, technician: StringType, notes: StringType });\n\n/** A person's shift is its own event kind. */\nexport const CalendarShift = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, person: StringType, notes: StringType });\n\n/** Job templates are live record data, including their duration and initial values; each has a group, which the rail narrows them by. */\nexport const CalendarJobTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: CalendarJobValues });\n\n/** Maintenance templates are also bound data, grouped as the job templates are. */\nexport const CalendarServiceTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: StructType({ title: StringType, technician: StringType, notes: StringType }) });\n\n/** Shift templates start at their hour when dropped on a day. */\nexport const CalendarShiftTemplate = StructType({ name: StringType, group: StringType, hour: IntegerType, values: StructType({ title: StringType, notes: StringType }) });\n\nexport const calendarMachines = e3.record(\"calendar_machines\", DictType(StringType, CalendarMachine), new Map([\n [\"press1\", { name: \"Press 1\", area: \"Press shop\" }], [\"press2\", { name: \"Press 2\", area: \"Press shop\" }],\n [\"lathe1\", { name: \"Lathe 1\", area: \"Machine shop\" }], [\"lathe2\", { name: \"Lathe 2\", area: \"Machine shop\" }],\n]));\n\nexport const calendarPeople = e3.record(\"calendar_people\", DictType(StringType, CalendarPerson), new Map([\n [\"ash\", { name: \"A. Ash\", team: \"Maintenance\" }], [\"birch\", { name: \"B. Birch\", team: \"Production\" }], [\"cedar\", { name: \"C. Cedar\", team: \"Maintenance\" }],\n]));\n\nexport const calendarJobs = e3.record(\"calendar_jobs\", DictType(StringType, CalendarJob), new Map([\n [\"job1\", { title: \"Mounting brackets · batch 42\", start: some(new Date(\"2026-10-01T06:00:00Z\")), end: some(new Date(\"2026-10-01T10:00:00Z\")), machine: some(\"press1\"), status: variant(\"active\", null), hours: 4.0, customer: \"Alder Works\", due: some(new Date(\"2026-10-02T00:00:00Z\")) }],\n [\"job2\", { title: \"Pump housings\", start: some(new Date(\"2026-10-01T08:00:00Z\")), end: some(new Date(\"2026-10-01T13:00:00Z\")), machine: some(\"lathe1\"), status: variant(\"planned\", null), hours: 5.0, customer: \"Birch Engineering\", due: none }],\n [\"job3\", { title: \"Long title: stainless steel mounting plates for the prototype assembly\", start: some(new Date(\"2026-10-01T11:00:00Z\")), end: some(new Date(\"2026-10-01T11:30:00Z\")), machine: some(\"press2\"), status: variant(\"planned\", null), hours: 0.5, customer: \"Cedar Fabrication\", due: none }],\n [\"job4\", { title: \"Bearing collars\", start: some(new Date(\"2026-10-02T09:00:00Z\")), end: some(new Date(\"2026-10-02T12:00:00Z\")), machine: some(\"lathe2\"), status: variant(\"planned\", null), hours: 3.0, customer: \"Alder Works\", due: none }],\n [\"job5\", { title: \"Overnight batch\", start: some(new Date(\"2026-10-01T22:00:00Z\")), end: some(new Date(\"2026-10-02T02:00:00Z\")), machine: some(\"press1\"), status: variant(\"planned\", null), hours: 4.0, customer: \"Birch Engineering\", due: none }],\n [\"job6\", { title: \"September closeout\", start: some(new Date(\"2026-09-30T08:00:00Z\")), end: some(new Date(\"2026-09-30T10:00:00Z\")), machine: some(\"press2\"), status: variant(\"done\", null), hours: 2.0, customer: \"Alder Works\", due: none }],\n [\"backlog1\", { title: \"Torque-test fixtures\", start: none, end: none, machine: some(\"press1\"), status: variant(\"planned\", null), hours: 2.0, customer: \"Alder Works\", due: some(new Date(\"2026-10-02T00:00:00Z\")) }],\n [\"backlog2\", { title: \"Valve bodies\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 3.0, customer: \"Cedar Fabrication\", due: some(new Date(\"2026-10-06T00:00:00Z\")) }],\n [\"backlog3\", { title: \"Replacement shims\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 1.0, customer: \"Birch Engineering\", due: some(new Date(\"2026-10-20T00:00:00Z\")) }],\n [\"backlog4\", { title: \"Prototype spacers\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 2.5, customer: \"Alder Works\", due: none }],\n]));\n\nexport const calendarJobsPatch = e3.mutation.patch(calendarJobs);\n\nexport const calendarServices = e3.record(\"calendar_services\", DictType(StringType, CalendarService), new Map([\n [\"service1\", { title: \"Preventive maintenance\", start: new Date(\"2026-10-01T09:00:00Z\"), end: new Date(\"2026-10-01T11:00:00Z\"), machine: \"press1\", technician: \"ash\", notes: \"Inspect the tooling and check torque.\" }],\n [\"service2\", { title: \"Calibration\", start: new Date(\"2026-10-02T13:00:00Z\"), end: new Date(\"2026-10-02T14:00:00Z\"), machine: \"lathe1\", technician: \"cedar\", notes: \"Check against the reference gauge.\" }],\n]));\n\nexport const calendarServicesPatch = e3.mutation.patch(calendarServices);\n\nexport const calendarShifts = e3.record(\"calendar_shifts\", DictType(StringType, CalendarShift), new Map([\n [\"shift1\", { title: \"Day shift\", start: new Date(\"2026-10-01T06:00:00Z\"), end: new Date(\"2026-10-01T14:00:00Z\"), person: \"ash\", notes: \"Press shop\" }],\n [\"shift2\", { title: \"Late shift\", start: new Date(\"2026-10-01T14:00:00Z\"), end: new Date(\"2026-10-01T22:00:00Z\"), person: \"birch\", notes: \"Machine shop\" }],\n]));\n\nexport const calendarShiftsPatch = e3.mutation.patch(calendarShifts);\n\nexport const calendarJobTemplates = e3.record(\"calendar_job_templates\", DictType(StringType, CalendarJobTemplate), new Map([\n [\"brackets\", { name: \"Bracket batch\", group: \"Fabrication\", hours: 4.0, values: { title: \"Bracket batch\", status: variant(\"planned\", null), hours: 4.0, customer: \"\", due: none } }],\n [\"housings\", { name: \"Housing batch\", group: \"Machining\", hours: 2.0, values: { title: \"Housing batch\", status: variant(\"planned\", null), hours: 2.0, customer: \"\", due: none } }],\n]));\n\nexport const calendarServiceTemplates = e3.record(\"calendar_service_templates\", DictType(StringType, CalendarServiceTemplate), new Map([\n [\"calibration\", { name: \"Calibration\", group: \"Calibration\", hours: 1.0, values: { title: \"Calibration\", technician: \"cedar\", notes: \"Check against the reference gauge.\" } }],\n [\"service\", { name: \"Preventive maintenance\", group: \"Preventive\", hours: 2.0, values: { title: \"Preventive maintenance\", technician: \"ash\", notes: \"\" } }],\n]));\n\nexport const calendarShiftTemplates = e3.record(\"calendar_shift_templates\", DictType(StringType, CalendarShiftTemplate), new Map([\n [\"day\", { name: \"Day shift\", group: \"Day\", hour: 6n, values: { title: \"Day shift\", notes: \"\" } }],\n [\"late\", { name: \"Late shift\", group: \"Late\", hour: 14n, values: { title: \"Late shift\", notes: \"\" } }],\n]));\n\nexport const calendarJobsByTime = e3.recordIndex(\"calendar_jobs_by_time\", calendarJobs, { interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarJobsByStatusTime = e3.recordIndex(\"calendar_jobs_by_status_time\", calendarJobs, { on: [\"status\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarJobsByMachineTime = e3.recordIndex(\"calendar_jobs_by_machine_time\", calendarJobs, { on: [\"machine\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarServicesByTime = e3.recordIndex(\"calendar_services_by_time\", calendarServices, { interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarServicesByMachineTime = e3.recordIndex(\"calendar_services_by_machine_time\", calendarServices, { on: [\"machine\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarShiftsByTime = e3.recordIndex(\"calendar_shifts_by_time\", calendarShifts, { interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarShiftsByPersonTime = e3.recordIndex(\"calendar_shifts_by_person_time\", calendarShifts, { on: [\"person\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarMachinesByArea = e3.recordIndex(\"calendar_machines_by_area\", calendarMachines, { on: [\"area\"] });\n\nexport const calendarPeopleByTeam = e3.recordIndex(\"calendar_people_by_team\", calendarPeople, { on: [\"team\"] });\n\nexport const calendarJobTemplatesByGroup = e3.recordIndex(\"calendar_job_templates_by_group\", calendarJobTemplates, { on: [\"group\"] });\n\nexport const calendarServiceTemplatesByGroup = e3.recordIndex(\"calendar_service_templates_by_group\", calendarServiceTemplates, { on: [\"group\"] });\n\nexport const calendarShiftTemplatesByGroup = e3.recordIndex(\"calendar_shift_templates_by_group\", calendarShiftTemplates, { on: [\"group\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n // What the Calendar reads whole binds with Data.bind, which keeps it loaded beside its pages; the event records\n // it writes bind with Record.bind, and it reads their events through their pages alone.\n const machines = $.let(Data.bind(calendarMachines));\n const people = $.let(Data.bind(calendarPeople));\n const jobs = $.let(Record.bind(calendarJobs, [calendarJobsPatch]));\n const services = $.let(Record.bind(calendarServices, [calendarServicesPatch]));\n const shifts = $.let(Record.bind(calendarShifts, [calendarShiftsPatch]));\n const jobTemplates = $.let(Data.bind(calendarJobTemplates));\n const serviceTemplates = $.let(Data.bind(calendarServiceTemplates));\n const shiftTemplates = $.let(Data.bind(calendarShiftTemplates));\n // Every record paged: each event kind's interval indexes and its own entries, the others' fields' indexes.\n const jobIndex = $.let(Data.bindPaged(calendarJobs, { indexes: [calendarJobsByTime, calendarJobsByStatusTime, calendarJobsByMachineTime] }));\n const serviceIndex = $.let(Data.bindPaged(calendarServices, { indexes: [calendarServicesByTime, calendarServicesByMachineTime] }));\n const shiftIndex = $.let(Data.bindPaged(calendarShifts, { indexes: [calendarShiftsByTime, calendarShiftsByPersonTime] }));\n const machineIndex = $.let(Data.bindPaged(calendarMachines, { indexes: [calendarMachinesByArea] }));\n const peopleIndex = $.let(Data.bindPaged(calendarPeople, { indexes: [calendarPeopleByTeam] }));\n const jobTemplateIndex = $.let(Data.bindPaged(calendarJobTemplates, { indexes: [calendarJobTemplatesByGroup] }));\n const serviceTemplateIndex = $.let(Data.bindPaged(calendarServiceTemplates, { indexes: [calendarServiceTemplatesByGroup] }));\n const shiftTemplateIndex = $.let(Data.bindPaged(calendarShiftTemplates, { indexes: [calendarShiftTemplatesByGroup] }));\n const jobEntries = $.let(Data.bindPaged(calendarJobs));\n const serviceEntries = $.let(Data.bindPaged(calendarServices));\n const shiftEntries = $.let(Data.bindPaged(calendarShifts));\n // One rail over every record, a source each (#1343): each event kind through its interval indexes, its range the\n // window the navigation writes in every kind at once; the machines, the people and each templates record by its fields.\n const slice = $.let(Slice.bindRecords(\"ex.calendarMobile\", {\n job: { source: jobIndex, icon: { prefix: \"fas\", name: \"industry\" },\n fields: { status: { label: \"Status\" }, machine: { label: \"Machine\", hints: [\"press1\", \"press2\", \"lathe1\", \"lathe2\", null] }, start: { label: \"Dates\" } }, range: \"start\" },\n service: { source: serviceIndex, icon: { prefix: \"fas\", name: \"screwdriver-wrench\" },\n fields: { machine: { label: \"Machine\", hints: [\"press1\", \"press2\", \"lathe1\", \"lathe2\"] }, start: { label: \"Dates\" } }, range: \"start\" },\n shift: { source: shiftIndex, icon: { prefix: \"fas\", name: \"user-clock\" },\n fields: { person: { label: \"Person\", hints: [\"ash\", \"birch\", \"cedar\"] }, start: { label: \"Dates\" } }, range: \"start\" },\n machines: { source: machineIndex, icon: { prefix: \"fas\", name: \"gears\" },\n fields: { area: { label: \"Area\", hints: [\"Press shop\", \"Machine shop\"] } } },\n people: { source: peopleIndex, icon: { prefix: \"fas\", name: \"users\" },\n fields: { team: { label: \"Team\", hints: [\"Maintenance\", \"Production\"] } } },\n jobTemplates: { source: jobTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Job template\", hints: [\"Fabrication\", \"Machining\"] } } },\n serviceTemplates: { source: serviceTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Service template\", hints: [\"Preventive\", \"Calibration\"] } } },\n shiftTemplates: { source: shiftTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Shift template\", hints: [\"Day\", \"Late\"] } } },\n }));\n return <Box width=\"100%\" maxWidth=\"390px\" height=\"760px\"><Calendar id=\"operations\" inspector view={{ date: new Date(\"2026-10-01T00:00:00Z\") }} now={new Date(\"2026-10-01T10:15:00Z\")}\n slice={{ slice }}\n resources={{\n machines: Calendar.resources(machines.read(), { name: \"Machines\", icon: \"gears\", label: row => row.name, meta: row => some(row.area) }),\n people: Calendar.resources(people.read(), { name: \"People\", icon: \"users\", label: row => row.name, meta: row => some(row.team) }),\n }}\n events={{\n job: Calendar.events(jobs, { name: \"Production\", icon: \"industry\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n entries: jobEntries,\n status: { field: \"status\", cases: { planned: { label: \"Planned\", tone: variant(\"neutral\", null), ring: true }, active: { label: \"Active\", tone: variant(\"info\", null), ring: false }, done: { label: \"Done\", tone: variant(\"success\", null), ring: false } } },\n backlog: { duration: row => variant(\"hours\", row.hours), due: row => row.due },\n ready: row => row.hours.lessEqual(0).ifElse(() => variant(\"incomplete\", [{ field: \"hours\", message: \"Duration must be positive\" }]), () => variant(\"ready\", null)),\n templates: Calendar.templates(jobTemplates.read(), { name: row => row.name, group: row => some(row.group), duration: row => variant(\"hours\", row.hours), values: row => row.values, source: \"jobTemplates\" }),\n }),\n service: Calendar.events(services, { name: \"Maintenance\", icon: \"screwdriver-wrench\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n entries: serviceEntries,\n fields: { technician: Calendar.field.reference({ of: \"people\" }) },\n templates: Calendar.templates(serviceTemplates.read(), { name: row => row.name, group: row => some(row.group), duration: row => variant(\"hours\", row.hours), values: row => row.values, source: \"serviceTemplates\" }),\n }),\n shift: Calendar.events(shifts, { name: \"Shift\", icon: \"user-clock\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"person\", of: \"people\" }, overlaps: \"allow\",\n entries: shiftEntries,\n templates: Calendar.templates(shiftTemplates.read(), { name: row => row.name, group: row => some(row.group), at: row => some({ hour: row.hour, minute: 0n }), duration: () => variant(\"hours\", 8.0), values: row => row.values, source: \"shiftTemplates\" }),\n }),\n }}\n library={[Calendar.library.templates(), Calendar.library.backlog(), Calendar.library.tab(people.read(), {\n name: \"Technicians\", icon: \"user-gear\", label: row => row.name, meta: row => some(row.team), group: row => row.team,\n drop: (_row, key) => Calendar.patch(CalendarService, { technician: key }), source: \"people\",\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
40742
|
+
"source": "// The same bound calendar in a phone-width container: chronological agenda cards with explicit Move, Resize, Duplicate, Delete and Return to backlog actions, Create and Schedule in the library, shared forms and Save history. Resizing preserves the view, selection and drafts; no dragging on the agenda.\n// inputs: []\n/** The resources of a small metal fabrication shop. */\nexport const CalendarMachine = StructType({ name: StringType, area: StringType });\n\n/** People the shifts run on and the maintenance library assigns. */\nexport const CalendarPerson = StructType({ name: StringType, team: StringType });\n\n/** A job's lifecycle. */\nexport const CalendarJobState = VariantType({ planned: NullType, active: NullType, done: NullType });\n\n/** A job may wait in the backlog; its own record holds its unscheduled rows. */\nexport const CalendarJob = StructType({ title: StringType, start: OptionType(DateTimeType), end: OptionType(DateTimeType), machine: OptionType(StringType),\n status: CalendarJobState, hours: FloatType, customer: StringType, due: OptionType(DateTimeType) });\n\n/** The initial job fields a template record supplies. */\nexport const CalendarJobValues = StructType({ title: StringType, status: CalendarJobState, hours: FloatType, customer: StringType, due: OptionType(DateTimeType) });\n\n/** Maintenance is a different record, with its own fields and patch mutation. */\nexport const CalendarService = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, machine: StringType, technician: StringType, notes: StringType });\n\n/** A person's shift is its own event kind. */\nexport const CalendarShift = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, person: StringType, notes: StringType });\n\n/** Job templates are live record data, including their duration and initial values; each has a group, which the rail narrows them by. */\nexport const CalendarJobTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: CalendarJobValues });\n\n/** Maintenance templates are also bound data, grouped as the job templates are. */\nexport const CalendarServiceTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: StructType({ title: StringType, technician: StringType, notes: StringType }) });\n\n/** Shift templates start at their hour when dropped on a day. */\nexport const CalendarShiftTemplate = StructType({ name: StringType, group: StringType, hour: IntegerType, values: StructType({ title: StringType, notes: StringType }) });\n\nexport const calendarMachines = e3.record(\"calendar_machines\", DictType(StringType, CalendarMachine), new Map([\n [\"press1\", { name: \"Press 1\", area: \"Press shop\" }], [\"press2\", { name: \"Press 2\", area: \"Press shop\" }],\n [\"lathe1\", { name: \"Lathe 1\", area: \"Machine shop\" }], [\"lathe2\", { name: \"Lathe 2\", area: \"Machine shop\" }],\n]));\n\nexport const calendarPeople = e3.record(\"calendar_people\", DictType(StringType, CalendarPerson), new Map([\n [\"ash\", { name: \"A. Ash\", team: \"Maintenance\" }], [\"birch\", { name: \"B. Birch\", team: \"Production\" }], [\"cedar\", { name: \"C. Cedar\", team: \"Maintenance\" }],\n]));\n\nexport const calendarJobs = e3.record(\"calendar_jobs\", DictType(StringType, CalendarJob), new Map([\n [\"job1\", { title: \"Mounting brackets · batch 42\", start: some(new Date(\"2026-10-01T06:00:00Z\")), end: some(new Date(\"2026-10-01T10:00:00Z\")), machine: some(\"press1\"), status: variant(\"active\", null), hours: 4.0, customer: \"Alder Works\", due: some(new Date(\"2026-10-02T00:00:00Z\")) }],\n [\"job2\", { title: \"Pump housings\", start: some(new Date(\"2026-10-01T08:00:00Z\")), end: some(new Date(\"2026-10-01T13:00:00Z\")), machine: some(\"lathe1\"), status: variant(\"planned\", null), hours: 5.0, customer: \"Birch Engineering\", due: none }],\n [\"job3\", { title: \"Long title: stainless steel mounting plates for the prototype assembly\", start: some(new Date(\"2026-10-01T11:00:00Z\")), end: some(new Date(\"2026-10-01T11:30:00Z\")), machine: some(\"press2\"), status: variant(\"planned\", null), hours: 0.5, customer: \"Cedar Fabrication\", due: none }],\n [\"job4\", { title: \"Bearing collars\", start: some(new Date(\"2026-10-02T09:00:00Z\")), end: some(new Date(\"2026-10-02T12:00:00Z\")), machine: some(\"lathe2\"), status: variant(\"planned\", null), hours: 3.0, customer: \"Alder Works\", due: none }],\n [\"job5\", { title: \"Overnight batch\", start: some(new Date(\"2026-10-01T22:00:00Z\")), end: some(new Date(\"2026-10-02T02:00:00Z\")), machine: some(\"press1\"), status: variant(\"planned\", null), hours: 4.0, customer: \"Birch Engineering\", due: none }],\n [\"job6\", { title: \"September closeout\", start: some(new Date(\"2026-09-30T08:00:00Z\")), end: some(new Date(\"2026-09-30T10:00:00Z\")), machine: some(\"press2\"), status: variant(\"done\", null), hours: 2.0, customer: \"Alder Works\", due: none }],\n [\"backlog1\", { title: \"Torque-test fixtures\", start: none, end: none, machine: some(\"press1\"), status: variant(\"planned\", null), hours: 2.0, customer: \"Alder Works\", due: some(new Date(\"2026-10-02T00:00:00Z\")) }],\n [\"backlog2\", { title: \"Valve bodies\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 3.0, customer: \"Cedar Fabrication\", due: some(new Date(\"2026-10-06T00:00:00Z\")) }],\n [\"backlog3\", { title: \"Replacement shims\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 1.0, customer: \"Birch Engineering\", due: some(new Date(\"2026-10-20T00:00:00Z\")) }],\n [\"backlog4\", { title: \"Prototype spacers\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 2.5, customer: \"Alder Works\", due: none }],\n]));\n\nexport const calendarJobsPatch = e3.mutation.patch(calendarJobs);\n\nexport const calendarServices = e3.record(\"calendar_services\", DictType(StringType, CalendarService), new Map([\n [\"service1\", { title: \"Preventive maintenance\", start: new Date(\"2026-10-01T09:00:00Z\"), end: new Date(\"2026-10-01T11:00:00Z\"), machine: \"press1\", technician: \"ash\", notes: \"Inspect the tooling and check torque.\" }],\n [\"service2\", { title: \"Calibration\", start: new Date(\"2026-10-02T13:00:00Z\"), end: new Date(\"2026-10-02T14:00:00Z\"), machine: \"lathe1\", technician: \"cedar\", notes: \"Check against the reference gauge.\" }],\n]));\n\nexport const calendarServicesPatch = e3.mutation.patch(calendarServices);\n\nexport const calendarShifts = e3.record(\"calendar_shifts\", DictType(StringType, CalendarShift), new Map([\n [\"shift1\", { title: \"Day shift\", start: new Date(\"2026-10-01T06:00:00Z\"), end: new Date(\"2026-10-01T14:00:00Z\"), person: \"ash\", notes: \"Press shop\" }],\n [\"shift2\", { title: \"Late shift\", start: new Date(\"2026-10-01T14:00:00Z\"), end: new Date(\"2026-10-01T22:00:00Z\"), person: \"birch\", notes: \"Machine shop\" }],\n]));\n\nexport const calendarShiftsPatch = e3.mutation.patch(calendarShifts);\n\nexport const calendarJobTemplates = e3.record(\"calendar_job_templates\", DictType(StringType, CalendarJobTemplate), new Map([\n [\"brackets\", { name: \"Bracket batch\", group: \"Fabrication\", hours: 4.0, values: { title: \"Bracket batch\", status: variant(\"planned\", null), hours: 4.0, customer: \"\", due: none } }],\n [\"housings\", { name: \"Housing batch\", group: \"Machining\", hours: 2.0, values: { title: \"Housing batch\", status: variant(\"planned\", null), hours: 2.0, customer: \"\", due: none } }],\n]));\n\nexport const calendarServiceTemplates = e3.record(\"calendar_service_templates\", DictType(StringType, CalendarServiceTemplate), new Map([\n [\"calibration\", { name: \"Calibration\", group: \"Calibration\", hours: 1.0, values: { title: \"Calibration\", technician: \"cedar\", notes: \"Check against the reference gauge.\" } }],\n [\"service\", { name: \"Preventive maintenance\", group: \"Preventive\", hours: 2.0, values: { title: \"Preventive maintenance\", technician: \"ash\", notes: \"\" } }],\n]));\n\nexport const calendarShiftTemplates = e3.record(\"calendar_shift_templates\", DictType(StringType, CalendarShiftTemplate), new Map([\n [\"day\", { name: \"Day shift\", group: \"Day\", hour: 6n, values: { title: \"Day shift\", notes: \"\" } }],\n [\"late\", { name: \"Late shift\", group: \"Late\", hour: 14n, values: { title: \"Late shift\", notes: \"\" } }],\n]));\n\nexport const calendarJobsByTime = e3.recordIndex.interval(\"calendar_jobs_by_time\", calendarJobs, { from: \"start\", to: \"end\" });\n\nexport const calendarJobsByStatusTime = e3.recordIndex.interval(\"calendar_jobs_by_status_time\", calendarJobs, { on: [\"status\"], from: \"start\", to: \"end\" });\n\nexport const calendarJobsByMachineTime = e3.recordIndex.interval(\"calendar_jobs_by_machine_time\", calendarJobs, { on: [\"machine\"], from: \"start\", to: \"end\" });\n\nexport const calendarServicesByTime = e3.recordIndex.interval(\"calendar_services_by_time\", calendarServices, { from: \"start\", to: \"end\" });\n\nexport const calendarServicesByMachineTime = e3.recordIndex.interval(\"calendar_services_by_machine_time\", calendarServices, { on: [\"machine\"], from: \"start\", to: \"end\" });\n\nexport const calendarShiftsByTime = e3.recordIndex.interval(\"calendar_shifts_by_time\", calendarShifts, { from: \"start\", to: \"end\" });\n\nexport const calendarShiftsByPersonTime = e3.recordIndex.interval(\"calendar_shifts_by_person_time\", calendarShifts, { on: [\"person\"], from: \"start\", to: \"end\" });\n\nexport const calendarMachinesByArea = e3.recordIndex(\"calendar_machines_by_area\", calendarMachines, { on: [\"area\"] });\n\nexport const calendarPeopleByTeam = e3.recordIndex(\"calendar_people_by_team\", calendarPeople, { on: [\"team\"] });\n\nexport const calendarJobTemplatesByGroup = e3.recordIndex(\"calendar_job_templates_by_group\", calendarJobTemplates, { on: [\"group\"] });\n\nexport const calendarServiceTemplatesByGroup = e3.recordIndex(\"calendar_service_templates_by_group\", calendarServiceTemplates, { on: [\"group\"] });\n\nexport const calendarShiftTemplatesByGroup = e3.recordIndex(\"calendar_shift_templates_by_group\", calendarShiftTemplates, { on: [\"group\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n // What the Calendar reads whole binds with Data.bind, which keeps it loaded beside its pages; the event records\n // it writes bind with Record.bind, and it reads their events through their pages alone.\n const machines = $.let(Data.bind(calendarMachines));\n const people = $.let(Data.bind(calendarPeople));\n const jobs = $.let(Record.bind(calendarJobs, [calendarJobsPatch]));\n const services = $.let(Record.bind(calendarServices, [calendarServicesPatch]));\n const shifts = $.let(Record.bind(calendarShifts, [calendarShiftsPatch]));\n const jobTemplates = $.let(Data.bind(calendarJobTemplates));\n const serviceTemplates = $.let(Data.bind(calendarServiceTemplates));\n const shiftTemplates = $.let(Data.bind(calendarShiftTemplates));\n // Every record paged: each event kind's interval indexes and its own entries, the others' fields' indexes.\n const jobIndex = $.let(Data.bindPaged(calendarJobs, { indexes: [calendarJobsByTime, calendarJobsByStatusTime, calendarJobsByMachineTime] }));\n const serviceIndex = $.let(Data.bindPaged(calendarServices, { indexes: [calendarServicesByTime, calendarServicesByMachineTime] }));\n const shiftIndex = $.let(Data.bindPaged(calendarShifts, { indexes: [calendarShiftsByTime, calendarShiftsByPersonTime] }));\n const machineIndex = $.let(Data.bindPaged(calendarMachines, { indexes: [calendarMachinesByArea] }));\n const peopleIndex = $.let(Data.bindPaged(calendarPeople, { indexes: [calendarPeopleByTeam] }));\n const jobTemplateIndex = $.let(Data.bindPaged(calendarJobTemplates, { indexes: [calendarJobTemplatesByGroup] }));\n const serviceTemplateIndex = $.let(Data.bindPaged(calendarServiceTemplates, { indexes: [calendarServiceTemplatesByGroup] }));\n const shiftTemplateIndex = $.let(Data.bindPaged(calendarShiftTemplates, { indexes: [calendarShiftTemplatesByGroup] }));\n const jobEntries = $.let(Data.bindPaged(calendarJobs));\n const serviceEntries = $.let(Data.bindPaged(calendarServices));\n const shiftEntries = $.let(Data.bindPaged(calendarShifts));\n // One rail over every record, a source each (#1343): each event kind through its interval indexes, its range the\n // window the navigation writes in every kind at once; the machines, the people and each templates record by its fields.\n const slice = $.let(Slice.bindRecords(\"ex.calendarMobile\", {\n job: { source: jobIndex, icon: { prefix: \"fas\", name: \"industry\" },\n fields: { status: { label: \"Status\" }, machine: { label: \"Machine\", hints: [\"press1\", \"press2\", \"lathe1\", \"lathe2\", null] }, start: { label: \"Dates\" } }, range: \"start\" },\n service: { source: serviceIndex, icon: { prefix: \"fas\", name: \"screwdriver-wrench\" },\n fields: { machine: { label: \"Machine\", hints: [\"press1\", \"press2\", \"lathe1\", \"lathe2\"] }, start: { label: \"Dates\" } }, range: \"start\" },\n shift: { source: shiftIndex, icon: { prefix: \"fas\", name: \"user-clock\" },\n fields: { person: { label: \"Person\", hints: [\"ash\", \"birch\", \"cedar\"] }, start: { label: \"Dates\" } }, range: \"start\" },\n machines: { source: machineIndex, icon: { prefix: \"fas\", name: \"gears\" },\n fields: { area: { label: \"Area\", hints: [\"Press shop\", \"Machine shop\"] } } },\n people: { source: peopleIndex, icon: { prefix: \"fas\", name: \"users\" },\n fields: { team: { label: \"Team\", hints: [\"Maintenance\", \"Production\"] } } },\n jobTemplates: { source: jobTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Job template\", hints: [\"Fabrication\", \"Machining\"] } } },\n serviceTemplates: { source: serviceTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Service template\", hints: [\"Preventive\", \"Calibration\"] } } },\n shiftTemplates: { source: shiftTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Shift template\", hints: [\"Day\", \"Late\"] } } },\n }));\n return <Box width=\"100%\" maxWidth=\"390px\" height=\"760px\"><Calendar id=\"operations\" inspector view={{ date: new Date(\"2026-10-01T00:00:00Z\") }} now={new Date(\"2026-10-01T10:15:00Z\")}\n slice={{ slice }}\n resources={{\n machines: Calendar.resources(machines.read(), { name: \"Machines\", icon: \"gears\", label: row => row.name, meta: row => some(row.area) }),\n people: Calendar.resources(people.read(), { name: \"People\", icon: \"users\", label: row => row.name, meta: row => some(row.team) }),\n }}\n events={{\n job: Calendar.events(jobs, { name: \"Production\", icon: \"industry\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n entries: jobEntries,\n status: { field: \"status\", cases: { planned: { label: \"Planned\", tone: variant(\"neutral\", null), ring: true }, active: { label: \"Active\", tone: variant(\"info\", null), ring: false }, done: { label: \"Done\", tone: variant(\"success\", null), ring: false } } },\n backlog: { duration: row => variant(\"hours\", row.hours), due: row => row.due },\n ready: row => row.hours.lessEqual(0).ifElse(() => variant(\"incomplete\", [{ field: \"hours\", message: \"Duration must be positive\" }]), () => variant(\"ready\", null)),\n templates: Calendar.templates(jobTemplates.read(), { name: row => row.name, group: row => some(row.group), duration: row => variant(\"hours\", row.hours), values: row => row.values, source: \"jobTemplates\" }),\n }),\n service: Calendar.events(services, { name: \"Maintenance\", icon: \"screwdriver-wrench\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n entries: serviceEntries,\n fields: { technician: Calendar.field.reference({ of: \"people\" }) },\n templates: Calendar.templates(serviceTemplates.read(), { name: row => row.name, group: row => some(row.group), duration: row => variant(\"hours\", row.hours), values: row => row.values, source: \"serviceTemplates\" }),\n }),\n shift: Calendar.events(shifts, { name: \"Shift\", icon: \"user-clock\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"person\", of: \"people\" }, overlaps: \"allow\",\n entries: shiftEntries,\n templates: Calendar.templates(shiftTemplates.read(), { name: row => row.name, group: row => some(row.group), at: row => some({ hour: row.hour, minute: 0n }), duration: () => variant(\"hours\", 8.0), values: row => row.values, source: \"shiftTemplates\" }),\n }),\n }}\n library={[Calendar.library.templates(), Calendar.library.backlog(), Calendar.library.tab(people.read(), {\n name: \"Technicians\", icon: \"user-gear\", label: row => row.name, meta: row => some(row.team), group: row => row.team,\n drop: (_row, key) => Calendar.patch(CalendarService, { technician: key }), source: \"people\",\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
40931
40743
|
},
|
|
40932
40744
|
{
|
|
40933
40745
|
"id": "e3-ui:calendar.examples.tsx:calendarReadOnly",
|
|
@@ -40952,7 +40764,7 @@
|
|
|
40952
40764
|
"languages": [
|
|
40953
40765
|
"tsx"
|
|
40954
40766
|
],
|
|
40955
|
-
"source": "// The same record calendar given readOnly: navigation, filtering, selection and overlaps, with a read-only inspector and no mutation session or editing actions.\n// inputs: []\n/** The resources of a small metal fabrication shop. */\nexport const CalendarMachine = StructType({ name: StringType, area: StringType });\n\n/** People the shifts run on and the maintenance library assigns. */\nexport const CalendarPerson = StructType({ name: StringType, team: StringType });\n\n/** A job's lifecycle. */\nexport const CalendarJobState = VariantType({ planned: NullType, active: NullType, done: NullType });\n\n/** A job may wait in the backlog; its own record holds its unscheduled rows. */\nexport const CalendarJob = StructType({ title: StringType, start: OptionType(DateTimeType), end: OptionType(DateTimeType), machine: OptionType(StringType),\n status: CalendarJobState, hours: FloatType, customer: StringType, due: OptionType(DateTimeType) });\n\n/** The initial job fields a template record supplies. */\nexport const CalendarJobValues = StructType({ title: StringType, status: CalendarJobState, hours: FloatType, customer: StringType, due: OptionType(DateTimeType) });\n\n/** Maintenance is a different record, with its own fields and patch mutation. */\nexport const CalendarService = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, machine: StringType, technician: StringType, notes: StringType });\n\n/** A person's shift is its own event kind. */\nexport const CalendarShift = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, person: StringType, notes: StringType });\n\n/** Job templates are live record data, including their duration and initial values; each has a group, which the rail narrows them by. */\nexport const CalendarJobTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: CalendarJobValues });\n\n/** Maintenance templates are also bound data, grouped as the job templates are. */\nexport const CalendarServiceTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: StructType({ title: StringType, technician: StringType, notes: StringType }) });\n\n/** Shift templates start at their hour when dropped on a day. */\nexport const CalendarShiftTemplate = StructType({ name: StringType, group: StringType, hour: IntegerType, values: StructType({ title: StringType, notes: StringType }) });\n\nexport const calendarMachines = e3.record(\"calendar_machines\", DictType(StringType, CalendarMachine), new Map([\n [\"press1\", { name: \"Press 1\", area: \"Press shop\" }], [\"press2\", { name: \"Press 2\", area: \"Press shop\" }],\n [\"lathe1\", { name: \"Lathe 1\", area: \"Machine shop\" }], [\"lathe2\", { name: \"Lathe 2\", area: \"Machine shop\" }],\n]));\n\nexport const calendarPeople = e3.record(\"calendar_people\", DictType(StringType, CalendarPerson), new Map([\n [\"ash\", { name: \"A. Ash\", team: \"Maintenance\" }], [\"birch\", { name: \"B. Birch\", team: \"Production\" }], [\"cedar\", { name: \"C. Cedar\", team: \"Maintenance\" }],\n]));\n\nexport const calendarJobs = e3.record(\"calendar_jobs\", DictType(StringType, CalendarJob), new Map([\n [\"job1\", { title: \"Mounting brackets · batch 42\", start: some(new Date(\"2026-10-01T06:00:00Z\")), end: some(new Date(\"2026-10-01T10:00:00Z\")), machine: some(\"press1\"), status: variant(\"active\", null), hours: 4.0, customer: \"Alder Works\", due: some(new Date(\"2026-10-02T00:00:00Z\")) }],\n [\"job2\", { title: \"Pump housings\", start: some(new Date(\"2026-10-01T08:00:00Z\")), end: some(new Date(\"2026-10-01T13:00:00Z\")), machine: some(\"lathe1\"), status: variant(\"planned\", null), hours: 5.0, customer: \"Birch Engineering\", due: none }],\n [\"job3\", { title: \"Long title: stainless steel mounting plates for the prototype assembly\", start: some(new Date(\"2026-10-01T11:00:00Z\")), end: some(new Date(\"2026-10-01T11:30:00Z\")), machine: some(\"press2\"), status: variant(\"planned\", null), hours: 0.5, customer: \"Cedar Fabrication\", due: none }],\n [\"job4\", { title: \"Bearing collars\", start: some(new Date(\"2026-10-02T09:00:00Z\")), end: some(new Date(\"2026-10-02T12:00:00Z\")), machine: some(\"lathe2\"), status: variant(\"planned\", null), hours: 3.0, customer: \"Alder Works\", due: none }],\n [\"job5\", { title: \"Overnight batch\", start: some(new Date(\"2026-10-01T22:00:00Z\")), end: some(new Date(\"2026-10-02T02:00:00Z\")), machine: some(\"press1\"), status: variant(\"planned\", null), hours: 4.0, customer: \"Birch Engineering\", due: none }],\n [\"job6\", { title: \"September closeout\", start: some(new Date(\"2026-09-30T08:00:00Z\")), end: some(new Date(\"2026-09-30T10:00:00Z\")), machine: some(\"press2\"), status: variant(\"done\", null), hours: 2.0, customer: \"Alder Works\", due: none }],\n [\"backlog1\", { title: \"Torque-test fixtures\", start: none, end: none, machine: some(\"press1\"), status: variant(\"planned\", null), hours: 2.0, customer: \"Alder Works\", due: some(new Date(\"2026-10-02T00:00:00Z\")) }],\n [\"backlog2\", { title: \"Valve bodies\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 3.0, customer: \"Cedar Fabrication\", due: some(new Date(\"2026-10-06T00:00:00Z\")) }],\n [\"backlog3\", { title: \"Replacement shims\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 1.0, customer: \"Birch Engineering\", due: some(new Date(\"2026-10-20T00:00:00Z\")) }],\n [\"backlog4\", { title: \"Prototype spacers\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 2.5, customer: \"Alder Works\", due: none }],\n]));\n\nexport const calendarJobsPatch = e3.mutation.patch(calendarJobs);\n\nexport const calendarServices = e3.record(\"calendar_services\", DictType(StringType, CalendarService), new Map([\n [\"service1\", { title: \"Preventive maintenance\", start: new Date(\"2026-10-01T09:00:00Z\"), end: new Date(\"2026-10-01T11:00:00Z\"), machine: \"press1\", technician: \"ash\", notes: \"Inspect the tooling and check torque.\" }],\n [\"service2\", { title: \"Calibration\", start: new Date(\"2026-10-02T13:00:00Z\"), end: new Date(\"2026-10-02T14:00:00Z\"), machine: \"lathe1\", technician: \"cedar\", notes: \"Check against the reference gauge.\" }],\n]));\n\nexport const calendarServicesPatch = e3.mutation.patch(calendarServices);\n\nexport const calendarShifts = e3.record(\"calendar_shifts\", DictType(StringType, CalendarShift), new Map([\n [\"shift1\", { title: \"Day shift\", start: new Date(\"2026-10-01T06:00:00Z\"), end: new Date(\"2026-10-01T14:00:00Z\"), person: \"ash\", notes: \"Press shop\" }],\n [\"shift2\", { title: \"Late shift\", start: new Date(\"2026-10-01T14:00:00Z\"), end: new Date(\"2026-10-01T22:00:00Z\"), person: \"birch\", notes: \"Machine shop\" }],\n]));\n\nexport const calendarShiftsPatch = e3.mutation.patch(calendarShifts);\n\nexport const calendarJobTemplates = e3.record(\"calendar_job_templates\", DictType(StringType, CalendarJobTemplate), new Map([\n [\"brackets\", { name: \"Bracket batch\", group: \"Fabrication\", hours: 4.0, values: { title: \"Bracket batch\", status: variant(\"planned\", null), hours: 4.0, customer: \"\", due: none } }],\n [\"housings\", { name: \"Housing batch\", group: \"Machining\", hours: 2.0, values: { title: \"Housing batch\", status: variant(\"planned\", null), hours: 2.0, customer: \"\", due: none } }],\n]));\n\nexport const calendarServiceTemplates = e3.record(\"calendar_service_templates\", DictType(StringType, CalendarServiceTemplate), new Map([\n [\"calibration\", { name: \"Calibration\", group: \"Calibration\", hours: 1.0, values: { title: \"Calibration\", technician: \"cedar\", notes: \"Check against the reference gauge.\" } }],\n [\"service\", { name: \"Preventive maintenance\", group: \"Preventive\", hours: 2.0, values: { title: \"Preventive maintenance\", technician: \"ash\", notes: \"\" } }],\n]));\n\nexport const calendarShiftTemplates = e3.record(\"calendar_shift_templates\", DictType(StringType, CalendarShiftTemplate), new Map([\n [\"day\", { name: \"Day shift\", group: \"Day\", hour: 6n, values: { title: \"Day shift\", notes: \"\" } }],\n [\"late\", { name: \"Late shift\", group: \"Late\", hour: 14n, values: { title: \"Late shift\", notes: \"\" } }],\n]));\n\nexport const calendarJobsByTime = e3.recordIndex(\"calendar_jobs_by_time\", calendarJobs, { interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarJobsByStatusTime = e3.recordIndex(\"calendar_jobs_by_status_time\", calendarJobs, { on: [\"status\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarJobsByMachineTime = e3.recordIndex(\"calendar_jobs_by_machine_time\", calendarJobs, { on: [\"machine\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarServicesByTime = e3.recordIndex(\"calendar_services_by_time\", calendarServices, { interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarServicesByMachineTime = e3.recordIndex(\"calendar_services_by_machine_time\", calendarServices, { on: [\"machine\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarShiftsByTime = e3.recordIndex(\"calendar_shifts_by_time\", calendarShifts, { interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarShiftsByPersonTime = e3.recordIndex(\"calendar_shifts_by_person_time\", calendarShifts, { on: [\"person\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const calendarMachinesByArea = e3.recordIndex(\"calendar_machines_by_area\", calendarMachines, { on: [\"area\"] });\n\nexport const calendarPeopleByTeam = e3.recordIndex(\"calendar_people_by_team\", calendarPeople, { on: [\"team\"] });\n\nexport const calendarJobTemplatesByGroup = e3.recordIndex(\"calendar_job_templates_by_group\", calendarJobTemplates, { on: [\"group\"] });\n\nexport const calendarServiceTemplatesByGroup = e3.recordIndex(\"calendar_service_templates_by_group\", calendarServiceTemplates, { on: [\"group\"] });\n\nexport const calendarShiftTemplatesByGroup = e3.recordIndex(\"calendar_shift_templates_by_group\", calendarShiftTemplates, { on: [\"group\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n // What the Calendar reads whole binds with Data.bind, which keeps it loaded beside its pages; the event records\n // it writes bind with Record.bind, and it reads their events through their pages alone.\n const machines = $.let(Data.bind(calendarMachines));\n const people = $.let(Data.bind(calendarPeople));\n const jobs = $.let(Record.bind(calendarJobs, [calendarJobsPatch]));\n const services = $.let(Record.bind(calendarServices, [calendarServicesPatch]));\n const shifts = $.let(Record.bind(calendarShifts, [calendarShiftsPatch]));\n const jobTemplates = $.let(Data.bind(calendarJobTemplates));\n const serviceTemplates = $.let(Data.bind(calendarServiceTemplates));\n const shiftTemplates = $.let(Data.bind(calendarShiftTemplates));\n // Every record paged: each event kind's interval indexes and its own entries, the others' fields' indexes.\n const jobIndex = $.let(Data.bindPaged(calendarJobs, { indexes: [calendarJobsByTime, calendarJobsByStatusTime, calendarJobsByMachineTime] }));\n const serviceIndex = $.let(Data.bindPaged(calendarServices, { indexes: [calendarServicesByTime, calendarServicesByMachineTime] }));\n const shiftIndex = $.let(Data.bindPaged(calendarShifts, { indexes: [calendarShiftsByTime, calendarShiftsByPersonTime] }));\n const machineIndex = $.let(Data.bindPaged(calendarMachines, { indexes: [calendarMachinesByArea] }));\n const peopleIndex = $.let(Data.bindPaged(calendarPeople, { indexes: [calendarPeopleByTeam] }));\n const jobTemplateIndex = $.let(Data.bindPaged(calendarJobTemplates, { indexes: [calendarJobTemplatesByGroup] }));\n const serviceTemplateIndex = $.let(Data.bindPaged(calendarServiceTemplates, { indexes: [calendarServiceTemplatesByGroup] }));\n const shiftTemplateIndex = $.let(Data.bindPaged(calendarShiftTemplates, { indexes: [calendarShiftTemplatesByGroup] }));\n const jobEntries = $.let(Data.bindPaged(calendarJobs));\n const serviceEntries = $.let(Data.bindPaged(calendarServices));\n const shiftEntries = $.let(Data.bindPaged(calendarShifts));\n // One rail over every record, a source each (#1343): each event kind through its interval indexes, its range the\n // window the navigation writes in every kind at once; the machines, the people and each templates record by its fields.\n const slice = $.let(Slice.bindRecords(\"ex.calendarReadOnly\", {\n job: { source: jobIndex, icon: { prefix: \"fas\", name: \"industry\" },\n fields: { status: { label: \"Status\" }, machine: { label: \"Machine\", hints: [\"press1\", \"press2\", \"lathe1\", \"lathe2\", null] }, start: { label: \"Dates\" } }, range: \"start\" },\n service: { source: serviceIndex, icon: { prefix: \"fas\", name: \"screwdriver-wrench\" },\n fields: { machine: { label: \"Machine\", hints: [\"press1\", \"press2\", \"lathe1\", \"lathe2\"] }, start: { label: \"Dates\" } }, range: \"start\" },\n shift: { source: shiftIndex, icon: { prefix: \"fas\", name: \"user-clock\" },\n fields: { person: { label: \"Person\", hints: [\"ash\", \"birch\", \"cedar\"] }, start: { label: \"Dates\" } }, range: \"start\" },\n machines: { source: machineIndex, icon: { prefix: \"fas\", name: \"gears\" },\n fields: { area: { label: \"Area\", hints: [\"Press shop\", \"Machine shop\"] } } },\n people: { source: peopleIndex, icon: { prefix: \"fas\", name: \"users\" },\n fields: { team: { label: \"Team\", hints: [\"Maintenance\", \"Production\"] } } },\n jobTemplates: { source: jobTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Job template\", hints: [\"Fabrication\", \"Machining\"] } } },\n serviceTemplates: { source: serviceTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Service template\", hints: [\"Preventive\", \"Calibration\"] } } },\n shiftTemplates: { source: shiftTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Shift template\", hints: [\"Day\", \"Late\"] } } },\n }));\n return <Box height=\"760px\"><Calendar id=\"operations\" inspector readOnly view={{ date: new Date(\"2026-10-01T00:00:00Z\") }} now={new Date(\"2026-10-01T10:15:00Z\")}\n slice={{ slice }}\n resources={{\n machines: Calendar.resources(machines.read(), { name: \"Machines\", icon: \"gears\", label: row => row.name, meta: row => some(row.area) }),\n people: Calendar.resources(people.read(), { name: \"People\", icon: \"users\", label: row => row.name, meta: row => some(row.team) }),\n }}\n events={{\n job: Calendar.events(jobs, { name: \"Production\", icon: \"industry\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n entries: jobEntries,\n status: { field: \"status\", cases: { planned: { label: \"Planned\", tone: variant(\"neutral\", null), ring: true }, active: { label: \"Active\", tone: variant(\"info\", null), ring: false }, done: { label: \"Done\", tone: variant(\"success\", null), ring: false } } },\n backlog: { duration: row => variant(\"hours\", row.hours), due: row => row.due },\n ready: row => row.hours.lessEqual(0).ifElse(() => variant(\"incomplete\", [{ field: \"hours\", message: \"Duration must be positive\" }]), () => variant(\"ready\", null)),\n templates: Calendar.templates(jobTemplates.read(), { name: row => row.name, group: row => some(row.group), duration: row => variant(\"hours\", row.hours), values: row => row.values, source: \"jobTemplates\" }),\n }),\n service: Calendar.events(services, { name: \"Maintenance\", icon: \"screwdriver-wrench\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n entries: serviceEntries,\n fields: { technician: Calendar.field.reference({ of: \"people\" }) },\n templates: Calendar.templates(serviceTemplates.read(), { name: row => row.name, group: row => some(row.group), duration: row => variant(\"hours\", row.hours), values: row => row.values, source: \"serviceTemplates\" }),\n }),\n shift: Calendar.events(shifts, { name: \"Shift\", icon: \"user-clock\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"person\", of: \"people\" }, overlaps: \"allow\",\n entries: shiftEntries,\n templates: Calendar.templates(shiftTemplates.read(), { name: row => row.name, group: row => some(row.group), at: row => some({ hour: row.hour, minute: 0n }), duration: () => variant(\"hours\", 8.0), values: row => row.values, source: \"shiftTemplates\" }),\n }),\n }}\n library={[Calendar.library.templates(), Calendar.library.backlog(), Calendar.library.tab(people.read(), {\n name: \"Technicians\", icon: \"user-gear\", label: row => row.name, meta: row => some(row.team), group: row => row.team,\n drop: (_row, key) => Calendar.patch(CalendarService, { technician: key }), source: \"people\",\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
40767
|
+
"source": "// The same record calendar given readOnly: navigation, filtering, selection and overlaps, with a read-only inspector and no mutation session or editing actions.\n// inputs: []\n/** The resources of a small metal fabrication shop. */\nexport const CalendarMachine = StructType({ name: StringType, area: StringType });\n\n/** People the shifts run on and the maintenance library assigns. */\nexport const CalendarPerson = StructType({ name: StringType, team: StringType });\n\n/** A job's lifecycle. */\nexport const CalendarJobState = VariantType({ planned: NullType, active: NullType, done: NullType });\n\n/** A job may wait in the backlog; its own record holds its unscheduled rows. */\nexport const CalendarJob = StructType({ title: StringType, start: OptionType(DateTimeType), end: OptionType(DateTimeType), machine: OptionType(StringType),\n status: CalendarJobState, hours: FloatType, customer: StringType, due: OptionType(DateTimeType) });\n\n/** The initial job fields a template record supplies. */\nexport const CalendarJobValues = StructType({ title: StringType, status: CalendarJobState, hours: FloatType, customer: StringType, due: OptionType(DateTimeType) });\n\n/** Maintenance is a different record, with its own fields and patch mutation. */\nexport const CalendarService = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, machine: StringType, technician: StringType, notes: StringType });\n\n/** A person's shift is its own event kind. */\nexport const CalendarShift = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, person: StringType, notes: StringType });\n\n/** Job templates are live record data, including their duration and initial values; each has a group, which the rail narrows them by. */\nexport const CalendarJobTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: CalendarJobValues });\n\n/** Maintenance templates are also bound data, grouped as the job templates are. */\nexport const CalendarServiceTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: StructType({ title: StringType, technician: StringType, notes: StringType }) });\n\n/** Shift templates start at their hour when dropped on a day. */\nexport const CalendarShiftTemplate = StructType({ name: StringType, group: StringType, hour: IntegerType, values: StructType({ title: StringType, notes: StringType }) });\n\nexport const calendarMachines = e3.record(\"calendar_machines\", DictType(StringType, CalendarMachine), new Map([\n [\"press1\", { name: \"Press 1\", area: \"Press shop\" }], [\"press2\", { name: \"Press 2\", area: \"Press shop\" }],\n [\"lathe1\", { name: \"Lathe 1\", area: \"Machine shop\" }], [\"lathe2\", { name: \"Lathe 2\", area: \"Machine shop\" }],\n]));\n\nexport const calendarPeople = e3.record(\"calendar_people\", DictType(StringType, CalendarPerson), new Map([\n [\"ash\", { name: \"A. Ash\", team: \"Maintenance\" }], [\"birch\", { name: \"B. Birch\", team: \"Production\" }], [\"cedar\", { name: \"C. Cedar\", team: \"Maintenance\" }],\n]));\n\nexport const calendarJobs = e3.record(\"calendar_jobs\", DictType(StringType, CalendarJob), new Map([\n [\"job1\", { title: \"Mounting brackets · batch 42\", start: some(new Date(\"2026-10-01T06:00:00Z\")), end: some(new Date(\"2026-10-01T10:00:00Z\")), machine: some(\"press1\"), status: variant(\"active\", null), hours: 4.0, customer: \"Alder Works\", due: some(new Date(\"2026-10-02T00:00:00Z\")) }],\n [\"job2\", { title: \"Pump housings\", start: some(new Date(\"2026-10-01T08:00:00Z\")), end: some(new Date(\"2026-10-01T13:00:00Z\")), machine: some(\"lathe1\"), status: variant(\"planned\", null), hours: 5.0, customer: \"Birch Engineering\", due: none }],\n [\"job3\", { title: \"Long title: stainless steel mounting plates for the prototype assembly\", start: some(new Date(\"2026-10-01T11:00:00Z\")), end: some(new Date(\"2026-10-01T11:30:00Z\")), machine: some(\"press2\"), status: variant(\"planned\", null), hours: 0.5, customer: \"Cedar Fabrication\", due: none }],\n [\"job4\", { title: \"Bearing collars\", start: some(new Date(\"2026-10-02T09:00:00Z\")), end: some(new Date(\"2026-10-02T12:00:00Z\")), machine: some(\"lathe2\"), status: variant(\"planned\", null), hours: 3.0, customer: \"Alder Works\", due: none }],\n [\"job5\", { title: \"Overnight batch\", start: some(new Date(\"2026-10-01T22:00:00Z\")), end: some(new Date(\"2026-10-02T02:00:00Z\")), machine: some(\"press1\"), status: variant(\"planned\", null), hours: 4.0, customer: \"Birch Engineering\", due: none }],\n [\"job6\", { title: \"September closeout\", start: some(new Date(\"2026-09-30T08:00:00Z\")), end: some(new Date(\"2026-09-30T10:00:00Z\")), machine: some(\"press2\"), status: variant(\"done\", null), hours: 2.0, customer: \"Alder Works\", due: none }],\n [\"backlog1\", { title: \"Torque-test fixtures\", start: none, end: none, machine: some(\"press1\"), status: variant(\"planned\", null), hours: 2.0, customer: \"Alder Works\", due: some(new Date(\"2026-10-02T00:00:00Z\")) }],\n [\"backlog2\", { title: \"Valve bodies\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 3.0, customer: \"Cedar Fabrication\", due: some(new Date(\"2026-10-06T00:00:00Z\")) }],\n [\"backlog3\", { title: \"Replacement shims\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 1.0, customer: \"Birch Engineering\", due: some(new Date(\"2026-10-20T00:00:00Z\")) }],\n [\"backlog4\", { title: \"Prototype spacers\", start: none, end: none, machine: none, status: variant(\"planned\", null), hours: 2.5, customer: \"Alder Works\", due: none }],\n]));\n\nexport const calendarJobsPatch = e3.mutation.patch(calendarJobs);\n\nexport const calendarServices = e3.record(\"calendar_services\", DictType(StringType, CalendarService), new Map([\n [\"service1\", { title: \"Preventive maintenance\", start: new Date(\"2026-10-01T09:00:00Z\"), end: new Date(\"2026-10-01T11:00:00Z\"), machine: \"press1\", technician: \"ash\", notes: \"Inspect the tooling and check torque.\" }],\n [\"service2\", { title: \"Calibration\", start: new Date(\"2026-10-02T13:00:00Z\"), end: new Date(\"2026-10-02T14:00:00Z\"), machine: \"lathe1\", technician: \"cedar\", notes: \"Check against the reference gauge.\" }],\n]));\n\nexport const calendarServicesPatch = e3.mutation.patch(calendarServices);\n\nexport const calendarShifts = e3.record(\"calendar_shifts\", DictType(StringType, CalendarShift), new Map([\n [\"shift1\", { title: \"Day shift\", start: new Date(\"2026-10-01T06:00:00Z\"), end: new Date(\"2026-10-01T14:00:00Z\"), person: \"ash\", notes: \"Press shop\" }],\n [\"shift2\", { title: \"Late shift\", start: new Date(\"2026-10-01T14:00:00Z\"), end: new Date(\"2026-10-01T22:00:00Z\"), person: \"birch\", notes: \"Machine shop\" }],\n]));\n\nexport const calendarShiftsPatch = e3.mutation.patch(calendarShifts);\n\nexport const calendarJobTemplates = e3.record(\"calendar_job_templates\", DictType(StringType, CalendarJobTemplate), new Map([\n [\"brackets\", { name: \"Bracket batch\", group: \"Fabrication\", hours: 4.0, values: { title: \"Bracket batch\", status: variant(\"planned\", null), hours: 4.0, customer: \"\", due: none } }],\n [\"housings\", { name: \"Housing batch\", group: \"Machining\", hours: 2.0, values: { title: \"Housing batch\", status: variant(\"planned\", null), hours: 2.0, customer: \"\", due: none } }],\n]));\n\nexport const calendarServiceTemplates = e3.record(\"calendar_service_templates\", DictType(StringType, CalendarServiceTemplate), new Map([\n [\"calibration\", { name: \"Calibration\", group: \"Calibration\", hours: 1.0, values: { title: \"Calibration\", technician: \"cedar\", notes: \"Check against the reference gauge.\" } }],\n [\"service\", { name: \"Preventive maintenance\", group: \"Preventive\", hours: 2.0, values: { title: \"Preventive maintenance\", technician: \"ash\", notes: \"\" } }],\n]));\n\nexport const calendarShiftTemplates = e3.record(\"calendar_shift_templates\", DictType(StringType, CalendarShiftTemplate), new Map([\n [\"day\", { name: \"Day shift\", group: \"Day\", hour: 6n, values: { title: \"Day shift\", notes: \"\" } }],\n [\"late\", { name: \"Late shift\", group: \"Late\", hour: 14n, values: { title: \"Late shift\", notes: \"\" } }],\n]));\n\nexport const calendarJobsByTime = e3.recordIndex.interval(\"calendar_jobs_by_time\", calendarJobs, { from: \"start\", to: \"end\" });\n\nexport const calendarJobsByStatusTime = e3.recordIndex.interval(\"calendar_jobs_by_status_time\", calendarJobs, { on: [\"status\"], from: \"start\", to: \"end\" });\n\nexport const calendarJobsByMachineTime = e3.recordIndex.interval(\"calendar_jobs_by_machine_time\", calendarJobs, { on: [\"machine\"], from: \"start\", to: \"end\" });\n\nexport const calendarServicesByTime = e3.recordIndex.interval(\"calendar_services_by_time\", calendarServices, { from: \"start\", to: \"end\" });\n\nexport const calendarServicesByMachineTime = e3.recordIndex.interval(\"calendar_services_by_machine_time\", calendarServices, { on: [\"machine\"], from: \"start\", to: \"end\" });\n\nexport const calendarShiftsByTime = e3.recordIndex.interval(\"calendar_shifts_by_time\", calendarShifts, { from: \"start\", to: \"end\" });\n\nexport const calendarShiftsByPersonTime = e3.recordIndex.interval(\"calendar_shifts_by_person_time\", calendarShifts, { on: [\"person\"], from: \"start\", to: \"end\" });\n\nexport const calendarMachinesByArea = e3.recordIndex(\"calendar_machines_by_area\", calendarMachines, { on: [\"area\"] });\n\nexport const calendarPeopleByTeam = e3.recordIndex(\"calendar_people_by_team\", calendarPeople, { on: [\"team\"] });\n\nexport const calendarJobTemplatesByGroup = e3.recordIndex(\"calendar_job_templates_by_group\", calendarJobTemplates, { on: [\"group\"] });\n\nexport const calendarServiceTemplatesByGroup = e3.recordIndex(\"calendar_service_templates_by_group\", calendarServiceTemplates, { on: [\"group\"] });\n\nexport const calendarShiftTemplatesByGroup = e3.recordIndex(\"calendar_shift_templates_by_group\", calendarShiftTemplates, { on: [\"group\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n // What the Calendar reads whole binds with Data.bind, which keeps it loaded beside its pages; the event records\n // it writes bind with Record.bind, and it reads their events through their pages alone.\n const machines = $.let(Data.bind(calendarMachines));\n const people = $.let(Data.bind(calendarPeople));\n const jobs = $.let(Record.bind(calendarJobs, [calendarJobsPatch]));\n const services = $.let(Record.bind(calendarServices, [calendarServicesPatch]));\n const shifts = $.let(Record.bind(calendarShifts, [calendarShiftsPatch]));\n const jobTemplates = $.let(Data.bind(calendarJobTemplates));\n const serviceTemplates = $.let(Data.bind(calendarServiceTemplates));\n const shiftTemplates = $.let(Data.bind(calendarShiftTemplates));\n // Every record paged: each event kind's interval indexes and its own entries, the others' fields' indexes.\n const jobIndex = $.let(Data.bindPaged(calendarJobs, { indexes: [calendarJobsByTime, calendarJobsByStatusTime, calendarJobsByMachineTime] }));\n const serviceIndex = $.let(Data.bindPaged(calendarServices, { indexes: [calendarServicesByTime, calendarServicesByMachineTime] }));\n const shiftIndex = $.let(Data.bindPaged(calendarShifts, { indexes: [calendarShiftsByTime, calendarShiftsByPersonTime] }));\n const machineIndex = $.let(Data.bindPaged(calendarMachines, { indexes: [calendarMachinesByArea] }));\n const peopleIndex = $.let(Data.bindPaged(calendarPeople, { indexes: [calendarPeopleByTeam] }));\n const jobTemplateIndex = $.let(Data.bindPaged(calendarJobTemplates, { indexes: [calendarJobTemplatesByGroup] }));\n const serviceTemplateIndex = $.let(Data.bindPaged(calendarServiceTemplates, { indexes: [calendarServiceTemplatesByGroup] }));\n const shiftTemplateIndex = $.let(Data.bindPaged(calendarShiftTemplates, { indexes: [calendarShiftTemplatesByGroup] }));\n const jobEntries = $.let(Data.bindPaged(calendarJobs));\n const serviceEntries = $.let(Data.bindPaged(calendarServices));\n const shiftEntries = $.let(Data.bindPaged(calendarShifts));\n // One rail over every record, a source each (#1343): each event kind through its interval indexes, its range the\n // window the navigation writes in every kind at once; the machines, the people and each templates record by its fields.\n const slice = $.let(Slice.bindRecords(\"ex.calendarReadOnly\", {\n job: { source: jobIndex, icon: { prefix: \"fas\", name: \"industry\" },\n fields: { status: { label: \"Status\" }, machine: { label: \"Machine\", hints: [\"press1\", \"press2\", \"lathe1\", \"lathe2\", null] }, start: { label: \"Dates\" } }, range: \"start\" },\n service: { source: serviceIndex, icon: { prefix: \"fas\", name: \"screwdriver-wrench\" },\n fields: { machine: { label: \"Machine\", hints: [\"press1\", \"press2\", \"lathe1\", \"lathe2\"] }, start: { label: \"Dates\" } }, range: \"start\" },\n shift: { source: shiftIndex, icon: { prefix: \"fas\", name: \"user-clock\" },\n fields: { person: { label: \"Person\", hints: [\"ash\", \"birch\", \"cedar\"] }, start: { label: \"Dates\" } }, range: \"start\" },\n machines: { source: machineIndex, icon: { prefix: \"fas\", name: \"gears\" },\n fields: { area: { label: \"Area\", hints: [\"Press shop\", \"Machine shop\"] } } },\n people: { source: peopleIndex, icon: { prefix: \"fas\", name: \"users\" },\n fields: { team: { label: \"Team\", hints: [\"Maintenance\", \"Production\"] } } },\n jobTemplates: { source: jobTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Job template\", hints: [\"Fabrication\", \"Machining\"] } } },\n serviceTemplates: { source: serviceTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Service template\", hints: [\"Preventive\", \"Calibration\"] } } },\n shiftTemplates: { source: shiftTemplateIndex, icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Shift template\", hints: [\"Day\", \"Late\"] } } },\n }));\n return <Box height=\"760px\"><Calendar id=\"operations\" inspector readOnly view={{ date: new Date(\"2026-10-01T00:00:00Z\") }} now={new Date(\"2026-10-01T10:15:00Z\")}\n slice={{ slice }}\n resources={{\n machines: Calendar.resources(machines.read(), { name: \"Machines\", icon: \"gears\", label: row => row.name, meta: row => some(row.area) }),\n people: Calendar.resources(people.read(), { name: \"People\", icon: \"users\", label: row => row.name, meta: row => some(row.team) }),\n }}\n events={{\n job: Calendar.events(jobs, { name: \"Production\", icon: \"industry\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n entries: jobEntries,\n status: { field: \"status\", cases: { planned: { label: \"Planned\", tone: variant(\"neutral\", null), ring: true }, active: { label: \"Active\", tone: variant(\"info\", null), ring: false }, done: { label: \"Done\", tone: variant(\"success\", null), ring: false } } },\n backlog: { duration: row => variant(\"hours\", row.hours), due: row => row.due },\n ready: row => row.hours.lessEqual(0).ifElse(() => variant(\"incomplete\", [{ field: \"hours\", message: \"Duration must be positive\" }]), () => variant(\"ready\", null)),\n templates: Calendar.templates(jobTemplates.read(), { name: row => row.name, group: row => some(row.group), duration: row => variant(\"hours\", row.hours), values: row => row.values, source: \"jobTemplates\" }),\n }),\n service: Calendar.events(services, { name: \"Maintenance\", icon: \"screwdriver-wrench\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n entries: serviceEntries,\n fields: { technician: Calendar.field.reference({ of: \"people\" }) },\n templates: Calendar.templates(serviceTemplates.read(), { name: row => row.name, group: row => some(row.group), duration: row => variant(\"hours\", row.hours), values: row => row.values, source: \"serviceTemplates\" }),\n }),\n shift: Calendar.events(shifts, { name: \"Shift\", icon: \"user-clock\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"person\", of: \"people\" }, overlaps: \"allow\",\n entries: shiftEntries,\n templates: Calendar.templates(shiftTemplates.read(), { name: row => row.name, group: row => some(row.group), at: row => some({ hour: row.hour, minute: 0n }), duration: () => variant(\"hours\", 8.0), values: row => row.values, source: \"shiftTemplates\" }),\n }),\n }}\n library={[Calendar.library.templates(), Calendar.library.backlog(), Calendar.library.tab(people.read(), {\n name: \"Technicians\", icon: \"user-gear\", label: row => row.name, meta: row => some(row.team), group: row => row.team,\n drop: (_row, key) => Calendar.patch(CalendarService, { technician: key }), source: \"people\",\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
40956
40768
|
},
|
|
40957
40769
|
{
|
|
40958
40770
|
"id": "e3-ui:calendar.examples.tsx:calendarMinimal",
|
|
@@ -41060,6 +40872,41 @@
|
|
|
41060
40872
|
],
|
|
41061
40873
|
"source": "// A typed custom appointment inspector updates the whole row through Calendar's shared Undo, Redo and Save session.\n// inputs: []\n/** The smallest event record needs only its title and two instants. */\nexport const CalendarAppointment = StructType({ title: StringType, start: DateTimeType, end: DateTimeType });\n\nexport const calendarAppointments = e3.record(\"calendar_appointments\", DictType(StringType, CalendarAppointment), new Map([\n [\"check\", { title: \"Safety check\", start: new Date(\"2026-10-01T09:00:00Z\"), end: new Date(\"2026-10-01T10:00:00Z\") }],\n [\"review\", { title: \"Production review\", start: new Date(\"2026-10-01T11:00:00Z\"), end: new Date(\"2026-10-01T12:00:00Z\") }],\n]));\n\nexport const calendarAppointmentsPatch = e3.mutation.patch(calendarAppointments);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const appointments = $.let(Record.bind(calendarAppointments, [calendarAppointmentsPatch]));\n const inspect = $.const(East.function([CalendarAppointment, FunctionType([CalendarAppointment], NullType)], UIComponentType, ($2, row, update) => {\n const change = $2.const(East.function([StringType], NullType, ($3, title) => { $3(update({ title, start: row.start, end: row.end })); }));\n return <Input.String value={row.title} placeholder=\"Appointment title\" onChange={change} />;\n }));\n return <Box height=\"640px\"><Calendar inspector view={{ period: \"day\", date: new Date(\"2026-10-01T00:00:00Z\") }}\n events={{ appointment: Calendar.events(appointments, { name: \"Appointment\", icon: \"calendar\", title: \"title\", start: \"start\", end: \"end\", inspector: inspect }) }} />\n </Box>;\n }}</Reactive>)"
|
|
41062
40874
|
},
|
|
40875
|
+
{
|
|
40876
|
+
"id": "e3-ui:optimise.examples.tsx:calendarOptimise",
|
|
40877
|
+
"skill": "e3-ui",
|
|
40878
|
+
"package": "e3-ui",
|
|
40879
|
+
"file": "east-ui/packages/e3-ui/test/calendar/optimise.examples.tsx",
|
|
40880
|
+
"suite": "",
|
|
40881
|
+
"test": "The jobs calendar with the Elara eye (#1409): `optimise` is Func.bind of the shop's optimiser on e3 — Calendar.Types.OptimiseRequest({ job: CalendarJob }, { machines: CalendarMachine }) in, the window in view, the run number, the window's jobs and the backlog by key, the machines beside them; Calendar.Types.OptimiseResult({ job: CalendarJob }) out, the jobs complete, each backlog job scheduled into its machine's free time. A click on the eye runs it once over the window; the result lands in the session as one edit, which one Undo reverts and Save commits with everything else.",
|
|
40882
|
+
"keywords": [
|
|
40883
|
+
"Calendar",
|
|
40884
|
+
"optimise",
|
|
40885
|
+
"Optimise",
|
|
40886
|
+
"Elara",
|
|
40887
|
+
"OptimiseRequest",
|
|
40888
|
+
"OptimiseResult",
|
|
40889
|
+
"Func.bind",
|
|
40890
|
+
"e3.function",
|
|
40891
|
+
"backlog",
|
|
40892
|
+
"one edit",
|
|
40893
|
+
"Undo",
|
|
40894
|
+
"Record.bind",
|
|
40895
|
+
"Schedule.events",
|
|
40896
|
+
"Schedule.resources"
|
|
40897
|
+
],
|
|
40898
|
+
"imports": [
|
|
40899
|
+
"import { DateTimeType, DictType, East, StringType, example, some, variant } from \"@elaraai/east\";",
|
|
40900
|
+
"import e3 from \"@elaraai/e3\";",
|
|
40901
|
+
"import { Box, Reactive, UIComponentType } from \"@elaraai/east-ui\";",
|
|
40902
|
+
"import { Calendar, Func, Record } from \"@elaraai/e3-ui\";",
|
|
40903
|
+
"import { CalendarJob, CalendarMachine, calendarJobs, calendarJobsPatch, calendarMachines } from \"./calendar.examples.js\";"
|
|
40904
|
+
],
|
|
40905
|
+
"languages": [
|
|
40906
|
+
"tsx"
|
|
40907
|
+
],
|
|
40908
|
+
"source": "// The jobs calendar with the Elara eye (#1409): `optimise` is Func.bind of the shop's optimiser on e3 — Calendar.Types.OptimiseRequest({ job: CalendarJob }, { machines: CalendarMachine }) in, the window in view, the run number, the window's jobs and the backlog by key, the machines beside them; Calendar.Types.OptimiseResult({ job: CalendarJob }) out, the jobs complete, each backlog job scheduled into its machine's free time. A click on the eye runs it once over the window; the result lands in the session as one edit, which one Undo reverts and Save commits with everything else.\n// inputs: []\n/** The jobs' optimiser's request: the window's jobs and the backlog by key, the machines beside them. */\nexport const CalendarOptimiseRequest = Calendar.Types.OptimiseRequest({ job: CalendarJob }, { machines: CalendarMachine });\n\n/** The jobs' optimiser's answer: the jobs, complete. */\nexport const CalendarOptimiseResult = Calendar.Types.OptimiseResult({ job: CalendarJob });\n\n/**\n * The shop's optimiser's body: each job of the backlog — one with no start — is scheduled into free machine time, in key\n * order: on its machine where it names one, else on the machine free soonest, from when that machine is free — the\n * window's start, or the end of its last scheduled job — for as long as its hours take; every scheduled job stays as it was.\n */\nexport const calendarOptimiseBody = East.function([CalendarOptimiseRequest], CalendarOptimiseResult, ($, req) => {\n const planned = $.let(new Map(), DictType(StringType, CalendarJob));\n // When each machine is next free: the window's start, or past its last scheduled job.\n const free = $.let(new Map(), DictType(StringType, DateTimeType));\n $.for(req.resources.machines, ($2, _machine, key) => { $2(free.insert(key, req.from)); });\n $.for(req.events.job, ($2, job, key) => {\n $2(planned.insert(key, job));\n $2.match(job.machine, { some: ($3, machine) => {\n $3.match(job.end, { some: ($4, end) => {\n $4.if(free.has(machine).and(() => East.less(free.get(machine), end)), ($5) => { $5(free.insertOrUpdate(machine, end, (_$6, _held, later) => later)); });\n } });\n } });\n });\n // The backlog in key order: each job on its machine, or the one free soonest, from when it is free.\n const backlog = $.const(req.events.job.toArray(($2, job, key) => ({ key, job })).filter(($2, entry) => entry.job.start.hasTag(\"none\")));\n $.for(backlog, ($2, entry) => {\n const soonest = $2.const(free.toArray(($3, at, key) => ({ key, at })).sort(($3, slot) => slot.at).get(0n).key);\n const machine = $2.const(entry.job.machine.match({\n some: ($3, named) => free.has(named).ifElse(() => named, () => soonest),\n none: ($3) => soonest,\n }));\n const start = $2.const(free.get(machine));\n // A job's hours may be fractional: its minutes, whole.\n const end = $2.const(start.addMinutes(East.Float.roundHalf(entry.job.hours.multiply(60.0))));\n $2(planned.insertOrUpdate(entry.key, {\n title: entry.job.title, start: some(start), end: some(end), machine: some(machine), status: entry.job.status,\n hours: entry.job.hours, customer: entry.job.customer, due: entry.job.due,\n }, (_$3, _waiting, scheduled) => scheduled));\n $2(free.insertOrUpdate(machine, end, (_$3, _held, later) => later));\n });\n return { events: { job: planned } };\n});\n\n/** The shop's optimiser, on e3: the backlog scheduled into free machine time. */\nexport const scheduleBacklog = e3.function(\"calendar_schedule_backlog\", calendarOptimiseBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const machines = $.let(Record.bind(calendarMachines, []));\n const jobs = $.let(Record.bind(calendarJobs, [calendarJobsPatch]));\n // The app's optimiser: the eye calls it once per run over the window in view, and its answer is one edit.\n const optimise = $.let(Func.bind(scheduleBacklog));\n return <Box height=\"680px\"><Calendar id=\"calendarOptimise\" inspector view={{ period: \"week\", date: new Date(\"2026-10-01T00:00:00Z\") }} now={new Date(\"2026-10-01T10:15:00Z\")}\n optimise={optimise}\n resources={{ machines: Calendar.resources(machines.read(), { name: \"Machines\", icon: \"gears\", label: row => row.name }) }}\n events={{ job: Calendar.events(jobs, { name: \"Production\", icon: \"industry\", title: \"title\", start: \"start\", end: \"end\", resource: { field: \"machine\", of: \"machines\" },\n backlog: { duration: row => variant(\"hours\", row.hours), due: row => row.due } }) }}\n library={[Calendar.library.backlog()]} />\n </Box>;\n }}</Reactive>)"
|
|
40909
|
+
},
|
|
41063
40910
|
{
|
|
41064
40911
|
"id": "e3-ui:journal.examples.tsx:decisionJournalResolved",
|
|
41065
40912
|
"skill": "e3-ui",
|
|
@@ -42128,6 +41975,42 @@
|
|
|
42128
41975
|
],
|
|
42129
41976
|
"source": "// Ontology editor + Diff panel — pending node/link patch surfaces in Diff card\n// inputs: []\nexport const supplyChainOntologyInput = e3.input('supply_chain_ontology', OntologyType, variant('value', {\n nodes: [\n { id: 'obj-1', name: 'Reduce stockout rate', description: some('Strategic target for FY26.'), type: variant('objective', null) },\n { id: 'kpi-1', name: 'Fill rate (weekly)', description: some('Orders fulfilled / total.'), type: variant('kpi', null) },\n { id: 'kpi-2', name: 'Inventory turnover', description: none, type: variant('kpi', null) },\n { id: 'proc-1', name: 'Receive raw goods', description: some('Dock-to-stock receipt.'), type: variant('process', null) },\n { id: 'proc-2', name: 'Quality inspection', description: some('Sampling per inbound lot.'), type: variant('process', null) },\n { id: 'proc-3', name: 'Pick & pack', description: none, type: variant('process', null) },\n { id: 'proc-4', name: 'Dispatch', description: none, type: variant('process', null) },\n { id: 'res-1', name: 'Pallet inventory', description: some('On-hand pallets at DC-West.'), type: variant('resource', null) },\n { id: 'res-2', name: 'Picker pool', description: none, type: variant('resource', null) },\n { id: 'res-3', name: 'Packed orders', description: none, type: variant('resource', null) },\n { id: 'res-4', name: 'Shipped consignments', description: none, type: variant('resource', null) },\n { id: 'dec-1', name: 'Replenishment quantity', description: some('Order-up-to level per SKU.'), type: variant('decision', null) },\n { id: 'dec-2', name: 'Pick-wave size', description: none, type: variant('decision', null) },\n { id: 'ag-1', name: 'DC operations team', description: none, type: variant('agent', null) },\n { id: 'ag-2', name: 'Inventory planner', description: none, type: variant('agent', null) },\n { id: 'data-1', name: 'WMS order stream', description: none, type: variant('data', null) },\n { id: 'data-2', name: 'Carrier API', description: some('Shipment status webhook.'), type: variant('data', null) },\n { id: 'pol-1', name: 'FEFO picking policy', description: some('First-expiring-first-out.'), type: variant('policy', null) },\n ],\n links: [\n // Measurement up to the objective.\n { id: 'l-1', source: 'kpi-1', target: 'obj-1', type: variant('drives', null) },\n { id: 'l-2', source: 'kpi-2', target: 'obj-1', type: variant('drives', null) },\n { id: 'l-3', source: 'kpi-1', target: 'proc-4', type: variant('measures', null) },\n { id: 'l-4', source: 'kpi-2', target: 'proc-1', type: variant('measures', null) },\n // Resource flow: receive -> pick & pack -> dispatch.\n { id: 'l-5', source: 'proc-1', target: 'res-1', type: variant('produces', null) },\n { id: 'l-6', source: 'proc-3', target: 'res-1', type: variant('uses', null) },\n { id: 'l-7', source: 'proc-3', target: 'res-2', type: variant('uses', null) },\n { id: 'l-8', source: 'proc-3', target: 'res-3', type: variant('produces', null) },\n { id: 'l-9', source: 'proc-4', target: 'res-3', type: variant('uses', null) },\n { id: 'l-10', source: 'proc-4', target: 'res-4', type: variant('produces', null) },\n // Quality inspection consumes pallets but produces nothing —\n // a deliberate REA-duality gap the table view flags.\n { id: 'l-11', source: 'res-1', target: 'proc-2', type: variant('used_by', null) },\n // Decisions and owners.\n { id: 'l-12', source: 'dec-1', target: 'proc-1', type: variant('drives', null) },\n { id: 'l-13', source: 'dec-2', target: 'proc-3', type: variant('drives', null) },\n { id: 'l-14', source: 'ag-1', target: 'proc-3', type: variant('executes', null) },\n { id: 'l-15', source: 'ag-1', target: 'proc-4', type: variant('executes', null) },\n { id: 'l-16', source: 'ag-2', target: 'dec-1', type: variant('executes', null) },\n // Systems and constraints.\n { id: 'l-17', source: 'data-1', target: 'proc-3', type: variant('informs', null) },\n { id: 'l-18', source: 'proc-4', target: 'data-2', type: variant('inserts_data_into', null) },\n { id: 'l-19', source: 'data-2', target: 'kpi-1', type: variant('gets_data_from', null) },\n { id: 'l-20', source: 'pol-1', target: 'proc-3', type: variant('constrains', null) },\n ],\n metadata: some({\n version: '1.1.0',\n created: new Date('2026-01-15T00:00:00Z'),\n updated: new Date('2026-06-12T00:00:00Z'),\n description: some('Supply-chain operations ontology — fulfilment leg.'),\n }),\n}));\n\nEast.function([], UIComponentType, (_$) => {\n return (\n <Reactive>{$ => {\n const view = $.let(Data.bind(supplyChainOntologyInput));\n return (\n <Card>\n <VStack gap=\"4\" align=\"stretch\">\n <Text textStyle=\"heading-md\">Fulfilment ontology</Text>\n <Text>Edit nodes and links above; review the pending patch below before applying.</Text>\n <Ontology value={view} />\n <Diff bindings={[view.binding]} hideUnchanged={some(true)} />\n </VStack>\n </Card>\n );\n }}</Reactive>\n );\n })"
|
|
42130
41977
|
},
|
|
41978
|
+
{
|
|
41979
|
+
"id": "e3-ui:optimise.examples.tsx:planOptimise",
|
|
41980
|
+
"skill": "e3-ui",
|
|
41981
|
+
"package": "e3-ui",
|
|
41982
|
+
"file": "east-ui/packages/e3-ui/test/plan/optimise.examples.tsx",
|
|
41983
|
+
"suite": "",
|
|
41984
|
+
"test": "The smallest builder with the Elara eye (#1409): `optimise` is Func.bind of the print works' optimiser on e3 — Plan.Types.OptimiseRequest({ job: PrintJob }, { presses: PrintPress }) in, the plan window in view, the run number, the window's jobs and the backlog by key, the presses beside them; Plan.Types.OptimiseResult({ job: PrintJob }) out, the jobs complete, each backlog job placed on a press's free time. A click on the eye runs it once over the window; the result lands in the session as one edit, which one Undo reverts and Save commits with everything else.",
|
|
41985
|
+
"keywords": [
|
|
41986
|
+
"Plan",
|
|
41987
|
+
"optimise",
|
|
41988
|
+
"Optimise",
|
|
41989
|
+
"Elara",
|
|
41990
|
+
"OptimiseRequest",
|
|
41991
|
+
"OptimiseResult",
|
|
41992
|
+
"Func.bind",
|
|
41993
|
+
"e3.function",
|
|
41994
|
+
"backlog",
|
|
41995
|
+
"one edit",
|
|
41996
|
+
"Undo",
|
|
41997
|
+
"Record.bind",
|
|
41998
|
+
"Schedule.events",
|
|
41999
|
+
"Schedule.resources",
|
|
42000
|
+
"Plan.library.backlog"
|
|
42001
|
+
],
|
|
42002
|
+
"imports": [
|
|
42003
|
+
"import { DateTimeType, DictType, East, StringType, example, some, variant } from \"@elaraai/east\";",
|
|
42004
|
+
"import e3 from \"@elaraai/e3\";",
|
|
42005
|
+
"import { Reactive, UIComponentType } from \"@elaraai/east-ui\";",
|
|
42006
|
+
"import { Func, Plan, Record, Schedule } from \"@elaraai/e3-ui\";",
|
|
42007
|
+
"import { PrintJob, PrintPress, planEventJobs, planEventJobsPatch, planPrintCustomers, planPrintPresses } from \"./plan-events.examples.js\";"
|
|
42008
|
+
],
|
|
42009
|
+
"languages": [
|
|
42010
|
+
"tsx"
|
|
42011
|
+
],
|
|
42012
|
+
"source": "// The smallest builder with the Elara eye (#1409): `optimise` is Func.bind of the print works' optimiser on e3 — Plan.Types.OptimiseRequest({ job: PrintJob }, { presses: PrintPress }) in, the plan window in view, the run number, the window's jobs and the backlog by key, the presses beside them; Plan.Types.OptimiseResult({ job: PrintJob }) out, the jobs complete, each backlog job placed on a press's free time. A click on the eye runs it once over the window; the result lands in the session as one edit, which one Undo reverts and Save commits with everything else.\n// inputs: []\n/** The jobs' optimiser's request: the window's jobs and the backlog by key, the presses beside them. */\nexport const PlanOptimiseRequest = Plan.Types.OptimiseRequest({ job: PrintJob }, { presses: PrintPress });\n\n/** The jobs' optimiser's answer: the jobs, complete. */\nexport const PlanOptimiseResult = Plan.Types.OptimiseResult({ job: PrintJob });\n\n/**\n * The print works' optimiser's body: each job of the backlog — one with no start — is placed on a press, in key order: on\n * its press where it names one, else on the press free soonest, from when that press is free — the window's start, or the\n * end of its last placed job — for as long as its sheets take at 8,000 an hour; every placed job stays as it was.\n */\nexport const planOptimiseBody = East.function([PlanOptimiseRequest], PlanOptimiseResult, ($, req) => {\n const planned = $.let(new Map(), DictType(StringType, PrintJob));\n // When each press is next free: the window's start, or past its last placed job.\n const free = $.let(new Map(), DictType(StringType, DateTimeType));\n $.for(req.resources.presses, ($2, _press, key) => { $2(free.insert(key, req.from)); });\n $.for(req.events.job, ($2, job, key) => {\n $2(planned.insert(key, job));\n $2.match(job.press, { some: ($3, press) => {\n $3.match(job.end, { some: ($4, end) => {\n $4.if(free.has(press).and(() => East.less(free.get(press), end)), ($5) => { $5(free.insertOrUpdate(press, end, (_$6, _held, later) => later)); });\n } });\n } });\n });\n // The backlog in key order: each job on its press, or the one free soonest, from when it is free.\n const backlog = $.const(req.events.job.toArray(($2, job, key) => ({ key, job })).filter(($2, entry) => entry.job.start.hasTag(\"none\")));\n $.for(backlog, ($2, entry) => {\n const soonest = $2.const(free.toArray(($3, at, key) => ({ key, at })).sort(($3, slot) => slot.at).get(0n).key);\n const press = $2.const(entry.job.press.match({\n some: ($3, named) => free.has(named).ifElse(() => named, () => soonest),\n none: ($3) => soonest,\n }));\n const start = $2.const(free.get(press));\n // A job's hours on a press are its sheets at 8,000 an hour: its minutes, whole.\n const end = $2.const(start.addMinutes(East.Float.roundHalf(entry.job.sheets.divide(8000.0).multiply(60.0))));\n $2(planned.insertOrUpdate(entry.key, {\n title: entry.job.title, start: some(start), end: some(end), press: some(press), state: entry.job.state,\n sheets: entry.job.sheets, customer: entry.job.customer, stock: entry.job.stock, due: entry.job.due,\n }, (_$3, _waiting, placed) => placed));\n $2(free.insertOrUpdate(press, end, (_$3, _held, later) => later));\n });\n return { events: { job: planned } };\n});\n\n/** The print works' optimiser, on e3: the backlog placed on the presses' free time. */\nexport const placeJobs = e3.function(\"plan_place_jobs\", planOptimiseBody);\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const presses = $.let(Record.bind(planPrintPresses, []));\n const jobs = $.let(Record.bind(planEventJobs, [planEventJobsPatch]));\n const customers = $.let(Record.bind(planPrintCustomers, []));\n const axis = $.let(Plan.axis({\n window: { min: new Date(\"2026-10-05T00:00:00Z\"), max: new Date(\"2026-11-02T00:00:00Z\") },\n resolution: \"day\", now: new Date(\"2026-10-14T09:00:00Z\"),\n }));\n // The app's optimiser: the eye calls it once per run over the plan window in view, and its answer is one edit.\n const optimise = $.let(Func.bind(placeJobs));\n return (\n <Plan\n id=\"planOptimise\"\n axis={axis}\n optimise={optimise}\n resources={{ presses: Schedule.resources(presses.read(), { name: \"Presses\", icon: \"print\", label: p => p.name }) }}\n events={{\n job: Schedule.events(jobs, {\n name: \"Print job\", icon: \"file-lines\",\n title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"press\", of: \"presses\" },\n state: \"state\",\n backlog: { duration: j => variant(\"hours\", j.sheets.divide(8000.0)), due: j => j.due },\n fields: {\n customer: Schedule.field.text({ label: \"Customer\" }),\n stock: Schedule.field.select({ labels: { coated: \"Coated\", uncoated: \"Uncoated\", board: \"Board\" } }),\n },\n }),\n }}\n library={[\n Plan.library.backlog(),\n Plan.library.tab(customers.read(), {\n name: \"Customers\", icon: \"building\",\n label: c => c.name, meta: c => some(c.trade), group: c => c.district,\n drop: c => Schedule.patch(PrintJob, { customer: c.name }),\n }),\n ]}\n inspector\n />\n );\n }}</Reactive>\n ))"
|
|
42013
|
+
},
|
|
42131
42014
|
{
|
|
42132
42015
|
"id": "e3-ui:plan-events.examples.tsx:planEvents",
|
|
42133
42016
|
"skill": "e3-ui",
|
|
@@ -42288,7 +42171,7 @@
|
|
|
42288
42171
|
"languages": [
|
|
42289
42172
|
"tsx"
|
|
42290
42173
|
],
|
|
42291
|
-
"source": "// The print works from plates to dispatch — jobs, stops and shifts on grouped presses and crews, nested bindery resources, deliveries in AM/PM lanes on a bay or a van, status and readiness, links between event kinds, utilisation and paper stock; one Slice.bindRecords rail — the jobs and the shifts through their interval indexes, their range the one window, the presses, the customers and each kind's templates record — and the shared library, inspector and Save session\n// inputs: []\n/** A press: its name, its hall and the sheets it prints an hour. */\nexport const PrintPress = StructType({ name: StringType, hall: StringType, sheets_per_hour: FloatType });\n\n/** A crew: its name and its hall. */\nexport const PrintCrew = StructType({ name: StringType, hall: StringType });\n\n/**\n * A print job: when it runs and on which press — none of the three while it\n * waits in the backlog — the lifecycle it wears, its sheets, and the\n * customer's details.\n */\nexport const PrintJob = StructType({\n title: StringType,\n start: OptionType(DateTimeType),\n end: OptionType(DateTimeType),\n press: OptionType(StringType),\n state: EventStateType,\n sheets: FloatType,\n customer: StringType,\n stock: VariantType({ coated: NullType, uncoated: NullType, board: NullType }),\n due: OptionType(DateTimeType),\n});\n\n/** A stop on a press, at one instant: a plate change or a service. */\nexport const PrintStop = StructType({\n title: StringType,\n at: DateTimeType,\n press: StringType,\n kind: VariantType({ plate_change: NullType, service: NullType }),\n});\n\n/** A crew's shift. */\nexport const PrintShift = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, crew: StringType, state: EventStateType });\n\n/** A customer: its name, the district it is in and its trade. */\nexport const PrintCustomer = StructType({ name: StringType, district: StringType, trade: StringType });\n\n/** The presses: three in each hall. A Plan never writes its resources. */\nexport const planPrintPresses = e3.record(\"plan_print_presses\", DictType(StringType, PrintPress), new Map([\n [\"a1\", { name: \"Press A1\", hall: \"Hall A\", sheets_per_hour: 12000.0 }],\n [\"a2\", { name: \"Press A2\", hall: \"Hall A\", sheets_per_hour: 10000.0 }],\n [\"a3\", { name: \"Press A3\", hall: \"Hall A\", sheets_per_hour: 8000.0 }],\n [\"b1\", { name: \"Press B1\", hall: \"Hall B\", sheets_per_hour: 15000.0 }],\n [\"b2\", { name: \"Press B2\", hall: \"Hall B\", sheets_per_hour: 12000.0 }],\n [\"b3\", { name: \"Press B3\", hall: \"Hall B\", sheets_per_hour: 6000.0 }],\n]));\n\n/** The crews: two in each hall. */\nexport const planPrintCrews = e3.record(\"plan_print_crews\", DictType(StringType, PrintCrew), new Map([\n [\"c1\", { name: \"Crew 1\", hall: \"Hall A\" }],\n [\"c2\", { name: \"Crew 2\", hall: \"Hall A\" }],\n [\"c3\", { name: \"Crew 3\", hall: \"Hall B\" }],\n [\"c4\", { name: \"Crew 4\", hall: \"Hall B\" }],\n]));\n\n/**\n * The print jobs: actual before 14 October, in progress on it, confirmed,\n * proposed and estimated after it; two overlapping on Press B2 on the 20th;\n * and eight with no start waiting in the backlog, two due this week, two next\n * week, two later and two with no due date.\n */\nexport const planPrintJobs = e3.record(\"plan_print_jobs\", DictType(StringType, PrintJob), new Map([\n [\"J-1001\", { title: \"Spring catalogue\", start: some(new Date(\"2026-10-05T06:00:00Z\")), end: some(new Date(\"2026-10-05T14:00:00Z\")), press: some(\"a1\"), state: variant(\"actual\", null), sheets: 96000.0, customer: \"Alder & Finch\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-07T00:00:00Z\")) }],\n [\"J-1002\", { title: \"Museum guide\", start: some(new Date(\"2026-10-07T06:00:00Z\")), end: some(new Date(\"2026-10-07T12:00:00Z\")), press: some(\"a1\"), state: variant(\"actual\", null), sheets: 72000.0, customer: \"Driftwood Museum\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-09T00:00:00Z\")) }],\n [\"J-1003\", { title: \"Event posters\", start: some(new Date(\"2026-10-14T07:00:00Z\")), end: some(new Date(\"2026-10-14T12:00:00Z\")), press: some(\"a1\"), state: variant(\"in-progress\", null), sheets: 60000.0, customer: \"Granite Hall\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-15T00:00:00Z\")) }],\n [\"J-1004\", { title: \"Course handbook\", start: some(new Date(\"2026-10-19T06:00:00Z\")), end: some(new Date(\"2026-10-19T18:00:00Z\")), press: some(\"a1\"), state: variant(\"confirmed\", null), sheets: 144000.0, customer: \"Elmway College\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-23T00:00:00Z\")) }],\n [\"J-1005\", { title: \"Menu cards\", start: some(new Date(\"2026-10-27T06:00:00Z\")), end: some(new Date(\"2026-10-27T08:00:00Z\")), press: some(\"a1\"), state: variant(\"proposed\", variant(\"recommended\", null)), sheets: 24000.0, customer: \"Copperleaf Cafe\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-30T00:00:00Z\")) }],\n [\"J-1006\", { title: \"Tour brochure\", start: some(new Date(\"2026-10-06T06:00:00Z\")), end: some(new Date(\"2026-10-06T14:00:00Z\")), press: some(\"a2\"), state: variant(\"actual\", null), sheets: 80000.0, customer: \"Bluewater Tours\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-08T00:00:00Z\")) }],\n [\"J-1007\", { title: \"Annual report\", start: some(new Date(\"2026-10-14T06:00:00Z\")), end: some(new Date(\"2026-10-14T12:00:00Z\")), press: some(\"a2\"), state: variant(\"in-progress\", null), sheets: 60000.0, customer: \"Harbour Arts Society\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"J-1008\", { title: \"Seed catalogue\", start: some(new Date(\"2026-10-21T06:00:00Z\")), end: some(new Date(\"2026-10-21T16:00:00Z\")), press: some(\"a2\"), state: variant(\"proposed\", variant(\"added\", null)), sheets: 100000.0, customer: \"Foxglove Gardens\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-24T00:00:00Z\")) }],\n [\"J-1009\", { title: \"Season flyers\", start: some(new Date(\"2026-10-28T06:00:00Z\")), end: some(new Date(\"2026-10-28T09:00:00Z\")), press: some(\"a2\"), state: variant(\"estimated\", null), sheets: 30000.0, customer: \"Heathfield Theatre\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-31T00:00:00Z\")) }],\n [\"J-1010\", { title: \"Club newsletter\", start: some(new Date(\"2026-10-08T06:00:00Z\")), end: some(new Date(\"2026-10-08T08:00:00Z\")), press: some(\"a3\"), state: variant(\"actual\", null), sheets: 16000.0, customer: \"Kestrel Cycling Club\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-09T00:00:00Z\")) }],\n [\"J-1011\", { title: \"Stationery set\", start: some(new Date(\"2026-10-15T06:00:00Z\")), end: some(new Date(\"2026-10-15T09:00:00Z\")), press: some(\"a3\"), state: variant(\"confirmed\", null), sheets: 24000.0, customer: \"Ivy Lane Studio\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"J-1012\", { title: \"Gift boxes\", start: some(new Date(\"2026-10-22T06:00:00Z\")), end: some(new Date(\"2026-10-22T12:00:00Z\")), press: some(\"a3\"), state: variant(\"proposed\", variant(\"recommended\", null)), sheets: 48000.0, customer: \"Juniper Toys\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-26T00:00:00Z\")) }],\n [\"J-1013\", { title: \"Holiday catalogue\", start: some(new Date(\"2026-10-05T06:00:00Z\")), end: some(new Date(\"2026-10-05T22:00:00Z\")), press: some(\"b1\"), state: variant(\"actual\", null), sheets: 240000.0, customer: \"Larkspur Home\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-09T00:00:00Z\")) }],\n [\"J-1014\", { title: \"Magazine run\", start: some(new Date(\"2026-10-12T06:00:00Z\")), end: some(new Date(\"2026-10-12T18:00:00Z\")), press: some(\"b1\"), state: variant(\"actual\", null), sheets: 180000.0, customer: \"Meridian Monthly\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-13T00:00:00Z\")) }],\n [\"J-1015\", { title: \"Store flyers\", start: some(new Date(\"2026-10-14T06:00:00Z\")), end: some(new Date(\"2026-10-14T16:00:00Z\")), press: some(\"b1\"), state: variant(\"in-progress\", null), sheets: 150000.0, customer: \"Northwind Outfitters\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"J-1016\", { title: \"Exhibition book\", start: some(new Date(\"2026-10-26T06:00:00Z\")), end: some(new Date(\"2026-10-26T12:00:00Z\")), press: some(\"b1\"), state: variant(\"estimated\", null), sheets: 90000.0, customer: \"Driftwood Museum\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-30T00:00:00Z\")) }],\n [\"J-1017\", { title: \"Timetables\", start: some(new Date(\"2026-10-09T06:00:00Z\")), end: some(new Date(\"2026-10-09T10:00:00Z\")), press: some(\"b2\"), state: variant(\"actual\", null), sheets: 48000.0, customer: \"Bluewater Tours\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-12T00:00:00Z\")) }],\n [\"J-1018\", { title: \"Market posters\", start: some(new Date(\"2026-10-20T06:00:00Z\")), end: some(new Date(\"2026-10-20T12:00:00Z\")), press: some(\"b2\"), state: variant(\"confirmed\", null), sheets: 72000.0, customer: \"Orchard Street Market\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-22T00:00:00Z\")) }],\n [\"J-1019\", { title: \"Loyalty cards\", start: some(new Date(\"2026-10-20T10:00:00Z\")), end: some(new Date(\"2026-10-20T13:00:00Z\")), press: some(\"b2\"), state: variant(\"proposed\", variant(\"added\", null)), sheets: 36000.0, customer: \"Copperleaf Cafe\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-23T00:00:00Z\")) }],\n [\"J-1020\", { title: \"Ticket books\", start: some(new Date(\"2026-10-29T06:00:00Z\")), end: some(new Date(\"2026-10-29T08:00:00Z\")), press: some(\"b2\"), state: variant(\"estimated\", null), sheets: 24000.0, customer: \"Heathfield Theatre\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-11-02T00:00:00Z\")) }],\n [\"J-1021\", { title: \"Handbook covers\", start: some(new Date(\"2026-10-16T06:00:00Z\")), end: some(new Date(\"2026-10-16T08:00:00Z\")), press: some(\"b3\"), state: variant(\"confirmed\", null), sheets: 12000.0, customer: \"Elmway College\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-19T00:00:00Z\")) }],\n [\"J-1022\", { title: \"Box sleeves\", start: some(new Date(\"2026-10-20T06:00:00Z\")), end: some(new Date(\"2026-10-20T09:00:00Z\")), press: some(\"b3\"), state: variant(\"proposed\", variant(\"recommended\", null)), sheets: 18000.0, customer: \"Juniper Toys\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-22T00:00:00Z\")) }],\n [\"J-1023\", { title: \"Guide reprint\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 24000.0, customer: \"Driftwood Museum\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"J-1024\", { title: \"Order forms\", start: none, end: none, press: none, state: variant(\"proposed\", variant(\"added\", null)), sheets: 40000.0, customer: \"Larkspur Home\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-17T00:00:00Z\")) }],\n [\"J-1025\", { title: \"Winter brochure\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 64000.0, customer: \"Bluewater Tours\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-21T00:00:00Z\")) }],\n [\"J-1026\", { title: \"Wall calendars\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 50000.0, customer: \"Foxglove Gardens\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-23T00:00:00Z\")) }],\n [\"J-1027\", { title: \"Prospectus\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 100000.0, customer: \"Elmway College\", stock: variant(\"coated\", null), due: some(new Date(\"2026-11-06T00:00:00Z\")) }],\n [\"J-1028\", { title: \"Price lists\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 16000.0, customer: \"Northwind Outfitters\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-11-13T00:00:00Z\")) }],\n [\"J-1029\", { title: \"Spare covers\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 8000.0, customer: \"Meridian Monthly\", stock: variant(\"board\", null), due: none }],\n [\"J-1030\", { title: \"Proof sheets\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 2000.0, customer: \"Alder & Finch\", stock: variant(\"uncoated\", null), due: none }],\n]));\n\n/** The jobs' patch door: every Save commits through it. */\nexport const planPrintJobsPatch = e3.mutation.patch(planPrintJobs);\n\n/** The stops: plate changes and services across the presses. */\nexport const planPrintStops = e3.record(\"plan_print_stops\", DictType(StringType, PrintStop), new Map([\n [\"S-01\", { title: \"Plate change\", at: new Date(\"2026-10-07T12:00:00Z\"), press: \"a1\", kind: variant(\"plate_change\", null) }],\n [\"S-02\", { title: \"Service\", at: new Date(\"2026-10-12T04:00:00Z\"), press: \"b1\", kind: variant(\"service\", null) }],\n [\"S-03\", { title: \"Plate change\", at: new Date(\"2026-10-14T12:00:00Z\"), press: \"a2\", kind: variant(\"plate_change\", null) }],\n [\"S-04\", { title: \"Plate change\", at: new Date(\"2026-10-19T18:00:00Z\"), press: \"a1\", kind: variant(\"plate_change\", null) }],\n [\"S-05\", { title: \"Service\", at: new Date(\"2026-10-26T14:00:00Z\"), press: \"a3\", kind: variant(\"service\", null) }],\n [\"S-06\", { title: \"Plate change\", at: new Date(\"2026-10-28T10:00:00Z\"), press: \"b2\", kind: variant(\"plate_change\", null) }],\n]));\n\n/** The stops' patch door. */\nexport const planPrintStopsPatch = e3.mutation.patch(planPrintStops);\n\n/** The shifts: each crew's early and late shifts, confirmed for the first two weeks and proposed for the last two. */\nexport const planPrintShifts = e3.record(\"plan_print_shifts\", DictType(StringType, PrintShift), new Map([\n [\"SH-01\", { title: \"Early\", start: new Date(\"2026-10-05T06:00:00Z\"), end: new Date(\"2026-10-05T14:00:00Z\"), crew: \"c1\", state: variant(\"confirmed\", null) }],\n [\"SH-02\", { title: \"Early\", start: new Date(\"2026-10-07T06:00:00Z\"), end: new Date(\"2026-10-07T14:00:00Z\"), crew: \"c1\", state: variant(\"confirmed\", null) }],\n [\"SH-03\", { title: \"Late\", start: new Date(\"2026-10-13T14:00:00Z\"), end: new Date(\"2026-10-13T22:00:00Z\"), crew: \"c1\", state: variant(\"confirmed\", null) }],\n [\"SH-04\", { title: \"Late\", start: new Date(\"2026-10-15T14:00:00Z\"), end: new Date(\"2026-10-15T22:00:00Z\"), crew: \"c1\", state: variant(\"confirmed\", null) }],\n [\"SH-05\", { title: \"Early\", start: new Date(\"2026-10-19T06:00:00Z\"), end: new Date(\"2026-10-19T14:00:00Z\"), crew: \"c1\", state: variant(\"proposed\", variant(\"recommended\", null)) }],\n [\"SH-06\", { title: \"Early\", start: new Date(\"2026-10-26T06:00:00Z\"), end: new Date(\"2026-10-26T14:00:00Z\"), crew: \"c1\", state: variant(\"proposed\", variant(\"recommended\", null)) }],\n [\"SH-07\", { title: \"Late\", start: new Date(\"2026-10-05T14:00:00Z\"), end: new Date(\"2026-10-05T22:00:00Z\"), crew: \"c2\", state: variant(\"confirmed\", null) }],\n [\"SH-08\", { title: \"Late\", start: new Date(\"2026-10-07T14:00:00Z\"), end: new Date(\"2026-10-07T22:00:00Z\"), crew: \"c2\", state: variant(\"confirmed\", null) }],\n [\"SH-09\", { title: \"Early\", start: new Date(\"2026-10-13T06:00:00Z\"), end: new Date(\"2026-10-13T14:00:00Z\"), crew: \"c2\", state: variant(\"confirmed\", null) }],\n [\"SH-10\", { title: \"Early\", start: new Date(\"2026-10-15T06:00:00Z\"), end: new Date(\"2026-10-15T14:00:00Z\"), crew: \"c2\", state: variant(\"confirmed\", null) }],\n [\"SH-11\", { title: \"Late\", start: new Date(\"2026-10-19T14:00:00Z\"), end: new Date(\"2026-10-19T22:00:00Z\"), crew: \"c2\", state: variant(\"proposed\", variant(\"recommended\", null)) }],\n [\"SH-12\", { title: \"Late\", start: new Date(\"2026-10-26T14:00:00Z\"), end: new Date(\"2026-10-26T22:00:00Z\"), crew: \"c2\", state: variant(\"proposed\", variant(\"recommended\", null)) }],\n [\"SH-13\", { title: \"Early\", start: new Date(\"2026-10-06T06:00:00Z\"), end: new Date(\"2026-10-06T14:00:00Z\"), crew: \"c3\", state: variant(\"confirmed\", null) }],\n [\"SH-14\", { title: \"Early\", start: new Date(\"2026-10-08T06:00:00Z\"), end: new Date(\"2026-10-08T14:00:00Z\"), crew: \"c3\", state: variant(\"confirmed\", null) }],\n [\"SH-15\", { title: \"Late\", start: new Date(\"2026-10-12T14:00:00Z\"), end: new Date(\"2026-10-12T22:00:00Z\"), crew: \"c3\", state: variant(\"confirmed\", null) }],\n [\"SH-16\", { title: \"Late\", start: new Date(\"2026-10-14T14:00:00Z\"), end: new Date(\"2026-10-14T22:00:00Z\"), crew: \"c3\", state: variant(\"confirmed\", null) }],\n [\"SH-17\", { title: \"Early\", start: new Date(\"2026-10-20T06:00:00Z\"), end: new Date(\"2026-10-20T14:00:00Z\"), crew: \"c3\", state: variant(\"proposed\", variant(\"recommended\", null)) }],\n [\"SH-18\", { title: \"Early\", start: new Date(\"2026-10-27T06:00:00Z\"), end: new Date(\"2026-10-27T14:00:00Z\"), crew: \"c3\", state: variant(\"proposed\", variant(\"added\", null)) }],\n [\"SH-19\", { title: \"Late\", start: new Date(\"2026-10-06T14:00:00Z\"), end: new Date(\"2026-10-06T22:00:00Z\"), crew: \"c4\", state: variant(\"confirmed\", null) }],\n [\"SH-20\", { title: \"Late\", start: new Date(\"2026-10-08T14:00:00Z\"), end: new Date(\"2026-10-08T22:00:00Z\"), crew: \"c4\", state: variant(\"confirmed\", null) }],\n [\"SH-21\", { title: \"Early\", start: new Date(\"2026-10-12T06:00:00Z\"), end: new Date(\"2026-10-12T14:00:00Z\"), crew: \"c4\", state: variant(\"confirmed\", null) }],\n [\"SH-22\", { title: \"Early\", start: new Date(\"2026-10-14T06:00:00Z\"), end: new Date(\"2026-10-14T14:00:00Z\"), crew: \"c4\", state: variant(\"confirmed\", null) }],\n [\"SH-23\", { title: \"Late\", start: new Date(\"2026-10-20T14:00:00Z\"), end: new Date(\"2026-10-20T22:00:00Z\"), crew: \"c4\", state: variant(\"proposed\", variant(\"recommended\", null)) }],\n [\"SH-24\", { title: \"Late\", start: new Date(\"2026-10-27T14:00:00Z\"), end: new Date(\"2026-10-27T22:00:00Z\"), crew: \"c4\", state: variant(\"proposed\", variant(\"added\", null)) }],\n]));\n\n/** The shifts' patch door. */\nexport const planPrintShiftsPatch = e3.mutation.patch(planPrintShifts);\n\n/** The customers the jobs are printed for, by account, in three districts: what the print works' library lists. A Plan never writes them. */\nexport const planPrintCustomers = e3.record(\"plan_print_customers\", DictType(StringType, PrintCustomer), new Map([\n [\"alder-finch\", { name: \"Alder & Finch\", district: \"Old Town\", trade: \"Retail\" }],\n [\"bluewater-tours\", { name: \"Bluewater Tours\", district: \"North Quay\", trade: \"Travel\" }],\n [\"copperleaf-cafe\", { name: \"Copperleaf Cafe\", district: \"Old Town\", trade: \"Hospitality\" }],\n [\"driftwood-museum\", { name: \"Driftwood Museum\", district: \"North Quay\", trade: \"Arts\" }],\n [\"elmway-college\", { name: \"Elmway College\", district: \"Riverside\", trade: \"Education\" }],\n [\"foxglove-gardens\", { name: \"Foxglove Gardens\", district: \"Riverside\", trade: \"Retail\" }],\n [\"granite-hall\", { name: \"Granite Hall\", district: \"Old Town\", trade: \"Events\" }],\n [\"harbour-arts\", { name: \"Harbour Arts Society\", district: \"North Quay\", trade: \"Arts\" }],\n [\"heathfield\", { name: \"Heathfield Theatre\", district: \"Old Town\", trade: \"Arts\" }],\n [\"ivy-lane\", { name: \"Ivy Lane Studio\", district: \"Riverside\", trade: \"Design\" }],\n [\"juniper-toys\", { name: \"Juniper Toys\", district: \"Riverside\", trade: \"Retail\" }],\n [\"kestrel-cycling\", { name: \"Kestrel Cycling Club\", district: \"Riverside\", trade: \"Sport\" }],\n [\"larkspur-home\", { name: \"Larkspur Home\", district: \"Old Town\", trade: \"Retail\" }],\n [\"meridian-monthly\", { name: \"Meridian Monthly\", district: \"North Quay\", trade: \"Publishing\" }],\n [\"northwind\", { name: \"Northwind Outfitters\", district: \"North Quay\", trade: \"Retail\" }],\n [\"orchard-market\", { name: \"Orchard Street Market\", district: \"Old Town\", trade: \"Markets\" }],\n]));\n\n/**\n * Each press's utilisation (%): a reading a day from 5 October, as the\n * dataflow measures it, idle at the weekends. Press B2's 97 on the 20th is\n * the day its two jobs overlap.\n */\nexport const planPrintUtilisation = e3.input(\"plan_print_utilisation\", DictType(StringType, ArrayType(FloatType)), variant(\"value\", new Map([\n [\"a1\", [50.0, 12.0, 38.0, 20.0, 15.0, 0.0, 0.0, 22.0, 18.0, 31.0, 25.0, 20.0, 0.0, 0.0, 75.0, 40.0, 30.0, 28.0, 20.0, 0.0, 0.0, 18.0, 13.0, 10.0, 8.0, 6.0, 0.0, 0.0]],\n [\"a2\", [20.0, 50.0, 25.0, 18.0, 15.0, 0.0, 0.0, 24.0, 20.0, 38.0, 30.0, 22.0, 0.0, 0.0, 26.0, 30.0, 63.0, 35.0, 22.0, 0.0, 0.0, 20.0, 18.0, 19.0, 12.0, 10.0, 0.0, 0.0]],\n [\"a3\", [10.0, 12.0, 14.0, 13.0, 18.0, 0.0, 0.0, 15.0, 16.0, 20.0, 19.0, 22.0, 0.0, 0.0, 24.0, 28.0, 30.0, 38.0, 25.0, 0.0, 0.0, 14.0, 20.0, 12.0, 10.0, 8.0, 0.0, 0.0]],\n [\"b1\", [100.0, 45.0, 30.0, 28.0, 26.0, 0.0, 0.0, 75.0, 40.0, 63.0, 48.0, 36.0, 0.0, 0.0, 30.0, 34.0, 28.0, 26.0, 24.0, 0.0, 0.0, 38.0, 22.0, 18.0, 14.0, 12.0, 0.0, 0.0]],\n [\"b2\", [18.0, 20.0, 22.0, 24.0, 25.0, 0.0, 0.0, 26.0, 28.0, 30.0, 32.0, 30.0, 0.0, 0.0, 34.0, 97.0, 40.0, 36.0, 30.0, 0.0, 0.0, 26.0, 24.0, 22.0, 13.0, 10.0, 0.0, 0.0]],\n [\"b3\", [8.0, 10.0, 12.0, 10.0, 9.0, 0.0, 0.0, 12.0, 14.0, 16.0, 18.0, 13.0, 0.0, 0.0, 20.0, 19.0, 16.0, 14.0, 12.0, 0.0, 0.0, 10.0, 9.0, 8.0, 7.0, 6.0, 0.0, 0.0]],\n])));\n\n/** The sheets printed each day from 5 October, across every press. */\nexport const planPrintOutput = e3.input(\"plan_print_output\", ArrayType(FloatType), variant(\"value\", [\n 336000.0, 80000.0, 72000.0, 16000.0, 48000.0, 0.0, 0.0,\n 180000.0, 0.0, 270000.0, 24000.0, 12000.0, 0.0, 0.0,\n 144000.0, 126000.0, 100000.0, 48000.0, 0.0, 0.0, 0.0,\n 90000.0, 24000.0, 30000.0, 24000.0, 0.0, 0.0, 0.0,\n]));\n\n/** A paper stock: its name, and its level in thousands of sheets, a reading a week from 5 October. */\nexport const PrintStock = StructType({ name: StringType, weekly: ArrayType(FloatType) });\n\n/** The paper in stock. */\nexport const planLinkStock = e3.input(\"plan_link_stock\", DictType(StringType, PrintStock), variant(\"value\", new Map([\n [\"board\", { name: \"Board\", weekly: [40.0, 22.0, 31.0, 18.0] }],\n [\"coated\", { name: \"Coated\", weekly: [520.0, 410.0, 460.0, 380.0] }],\n [\"uncoated\", { name: \"Uncoated\", weekly: [300.0, 260.0, 280.0, 240.0] }],\n])));\n\n/** A work centre a job passes through on its way out: the platesetter, the bindery's folder and binder, the dispatch bay. */\nexport const PrintCentre = StructType({ name: StringType });\n\n/** A set of plates the platesetter makes for a job, at the moment it makes them. */\nexport const PrintPlates = StructType({ title: StringType, at: DateTimeType, setter: StringType });\n\n/** A bindery job — folding a press job's sections, or binding them — on the folder or the binder. */\nexport const PrintBinding = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, line: StringType, state: EventStateType });\n\n/** The platesetter. A Plan never writes its resources. */\nexport const planLinkSetters = e3.record(\"plan_link_setters\", DictType(StringType, PrintCentre), new Map([\n [\"ps\", { name: \"Platesetter\" }],\n]));\n\n/** The bindery's two lines. */\nexport const planLinkLines = e3.record(\"plan_link_lines\", DictType(StringType, PrintCentre), new Map([\n [\"fold\", { name: \"Folder\" }],\n [\"bind\", { name: \"Binder\" }],\n]));\n\n/** The dispatch bay. */\nexport const planLinkBays = e3.record(\"plan_link_bays\", DictType(StringType, PrintCentre), new Map([\n [\"bay\", { name: \"Dispatch bay\" }],\n]));\n\n/** The parent of the shared folder and binder, held in a record like every other resource. */\nexport const planPrintBindery = e3.record(\"plan_print_bindery\", DictType(StringType, PrintCentre), new Map([\n [\"bindery\", { name: \"Bindery\" }],\n]));\n\n/** The vans a delivery can move onto from the dispatch bay. */\nexport const planPrintVans = e3.record(\"plan_print_vans\", DictType(StringType, PrintCentre), new Map([\n [\"van1\", { name: \"Van 1\" }], [\"van2\", { name: \"Van 2\" }],\n]));\n\n/** A delivery's own status, separate from the schedule's lifecycle. */\nexport const PrintDispatchStatus = VariantType({ queued: NullType, ready: NullType, dispatched: NullType });\n\n/** A delivery on a bay or a van: the variant preserves the resource kind when it moves. */\nexport const PrintDispatch = StructType({\n title: StringType, at: DateTimeType,\n location: VariantType({ bay: StringType, van: StringType }),\n lane: StringType, status: PrintDispatchStatus, pallets: FloatType,\n});\n\n/** The print works' own plate orders, timed before its existing jobs. */\nexport const planPrintPlates = e3.record(\"plan_print_plates\", DictType(StringType, PrintPlates), new Map([\n [\"P-101\", { title: \"Handbook plates\", at: new Date(\"2026-10-16T14:00:00Z\"), setter: \"ps\" }],\n [\"P-102\", { title: \"Poster plates\", at: new Date(\"2026-10-13T14:00:00Z\"), setter: \"ps\" }],\n]));\n\n/** The print works' plates commit through their own patch door. */\nexport const planPrintPlatesPatch = e3.mutation.patch(planPrintPlates);\n\n/** Two foldings run in parallel, followed by binding, after the handbook leaves Press A1. */\nexport const planPrintBindings = e3.record(\"plan_print_bindings\", DictType(StringType, PrintBinding), new Map([\n [\"B-101\", { title: \"Fold handbook sections\", start: new Date(\"2026-10-20T06:00:00Z\"), end: new Date(\"2026-10-20T14:00:00Z\"), line: \"fold\", state: variant(\"confirmed\", null) }],\n [\"B-102\", { title: \"Fold handbook inserts\", start: new Date(\"2026-10-20T10:00:00Z\"), end: new Date(\"2026-10-20T16:00:00Z\"), line: \"fold\", state: variant(\"confirmed\", null) }],\n [\"B-103\", { title: \"Bind handbooks\", start: new Date(\"2026-10-21T06:00:00Z\"), end: new Date(\"2026-10-21T14:00:00Z\"), line: \"bind\", state: variant(\"confirmed\", null) }],\n]));\n\n/** The print works' bindery jobs commit through their own patch door. */\nexport const planPrintBindingsPatch = e3.mutation.patch(planPrintBindings);\n\n/** Deliveries occupy AM/PM lanes and carry each of the dispatch statuses. */\nexport const planPrintDeliveries = e3.record(\"plan_print_deliveries\", DictType(StringType, PrintDispatch), new Map([\n [\"D-101\", { title: \"Handbooks\", at: new Date(\"2026-10-22T10:00:00Z\"), location: variant(\"bay\", \"bay\"), lane: \"AM\", status: variant(\"queued\", null), pallets: 3.0 }],\n [\"D-102\", { title: \"Event posters\", at: new Date(\"2026-10-15T14:00:00Z\"), location: variant(\"van\", \"van1\"), lane: \"PM\", status: variant(\"ready\", null), pallets: 2.0 }],\n [\"D-103\", { title: \"Museum guides\", at: new Date(\"2026-10-09T10:00:00Z\"), location: variant(\"van\", \"van2\"), lane: \"AM\", status: variant(\"dispatched\", null), pallets: 1.0 }],\n]));\n\n/** The print works' deliveries commit through their own patch door. */\nexport const planPrintDeliveriesPatch = e3.mutation.patch(planPrintDeliveries);\n\n/** A job template: its name and group, how long the job it creates runs, and that job's fields. */\nexport const PrintJobTemplate = StructType({\n name: StringType, group: StringType, hours: FloatType,\n values: StructType({ title: StringType, state: EventStateType, sheets: FloatType, customer: StringType, stock: PrintJob.fields.stock, due: OptionType(DateTimeType) }),\n});\n\n/** A stop template: a stop is one instant, so it runs no time. */\nexport const PrintStopTemplate = StructType({ name: StringType, group: StringType, values: StructType({ title: StringType, kind: PrintStop.fields.kind }) });\n\n/** A shift template: the hour a shift dropped on a day starts at, eight hours long. */\nexport const PrintShiftTemplate = StructType({ name: StringType, group: StringType, hour: IntegerType, values: StructType({ title: StringType, state: EventStateType }) });\n\n/** A plates template: plates are made at one instant. */\nexport const PrintPlatesTemplate = StructType({ name: StringType, group: StringType, values: StructType({ title: StringType }) });\n\n/** A bindery template: how long the bindery job it creates runs. */\nexport const PrintBindingTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: StructType({ title: StringType, state: EventStateType }) });\n\n/** A delivery template: the hour a delivery dropped on a day leaves at. */\nexport const PrintDeliveryTemplate = StructType({\n name: StringType, group: StringType, hour: IntegerType,\n values: StructType({ title: StringType, lane: StringType, status: PrintDispatchStatus, pallets: FloatType }),\n});\n\n/** The job templates. A Plan never writes them: the library lists them, and a drop creates a job from one. */\nexport const planPrintJobTemplates = e3.record(\"plan_print_job_templates\", DictType(StringType, PrintJobTemplate), new Map([\n [\"board\", { name: \"Board run\", group: \"Jobs\", hours: 8.0, values: { title: \"Board run\", state: variant(\"proposed\", variant(\"added\", null)), sheets: 24000.0, customer: \"\", stock: variant(\"board\", null), due: none } }],\n [\"brochure\", { name: \"Brochure run\", group: \"Jobs\", hours: 6.0, values: { title: \"Brochure run\", state: variant(\"proposed\", variant(\"added\", null)), sheets: 40000.0, customer: \"\", stock: variant(\"coated\", null), due: none } }],\n [\"catalogue\", { name: \"Catalogue run\", group: \"Jobs\", hours: 12.0, values: { title: \"Catalogue run\", state: variant(\"proposed\", variant(\"added\", null)), sheets: 96000.0, customer: \"\", stock: variant(\"coated\", null), due: none } }],\n [\"poster\", { name: \"Poster run\", group: \"Jobs\", hours: 4.0, values: { title: \"Poster run\", state: variant(\"proposed\", variant(\"added\", null)), sheets: 20000.0, customer: \"\", stock: variant(\"uncoated\", null), due: none } }],\n]));\n\n/** The stop templates. */\nexport const planPrintStopTemplates = e3.record(\"plan_print_stop_templates\", DictType(StringType, PrintStopTemplate), new Map([\n [\"plates\", { name: \"Plate change\", group: \"Stops\", values: { title: \"Plate change\", kind: variant(\"plate_change\", null) } }],\n [\"service\", { name: \"Service\", group: \"Stops\", values: { title: \"Service\", kind: variant(\"service\", null) } }],\n]));\n\n/** The shift templates: early, late and night. */\nexport const planPrintShiftTemplates = e3.record(\"plan_print_shift_templates\", DictType(StringType, PrintShiftTemplate), new Map([\n [\"early\", { name: \"Early shift\", group: \"Shifts\", hour: 6n, values: { title: \"Early\", state: variant(\"confirmed\", null) } }],\n [\"late\", { name: \"Late shift\", group: \"Shifts\", hour: 14n, values: { title: \"Late\", state: variant(\"confirmed\", null) } }],\n [\"night\", { name: \"Night shift\", group: \"Shifts\", hour: 22n, values: { title: \"Night\", state: variant(\"proposed\", variant(\"added\", null)) } }],\n]));\n\n/** The plates templates. */\nexport const planPrintPlatesTemplates = e3.record(\"plan_print_plates_templates\", DictType(StringType, PrintPlatesTemplate), new Map([\n [\"plates\", { name: \"Job plates\", group: \"Plate room\", values: { title: \"Job plates\" } }],\n]));\n\n/** The bindery templates. */\nexport const planPrintBindingTemplates = e3.record(\"plan_print_binding_templates\", DictType(StringType, PrintBindingTemplate), new Map([\n [\"fold\", { name: \"Fold sections\", group: \"Bindery\", hours: 8.0, values: { title: \"Fold sections\", state: variant(\"confirmed\", null) } }],\n]));\n\n/** The delivery templates. */\nexport const planPrintDeliveryTemplates = e3.record(\"plan_print_delivery_templates\", DictType(StringType, PrintDeliveryTemplate), new Map([\n [\"delivery\", { name: \"Morning delivery\", group: \"Dispatch\", hour: 10n, values: { title: \"Delivery\", lane: \"AM\", status: variant(\"queued\", null), pallets: 1.0 } }],\n]));\n\nexport const planPrintJobsByTime = e3.recordIndex(\"plan_print_jobs_by_time\", planPrintJobs, { interval: { from: \"start\", to: \"end\" } });\n\nexport const planPrintJobsByStockTime = e3.recordIndex(\"plan_print_jobs_by_stock_time\", planPrintJobs, { on: [\"stock\"], interval: { from: \"start\", to: \"end\" } });\n\nexport const planPrintShiftsByTime = e3.recordIndex(\"plan_print_shifts_by_time\", planPrintShifts, { interval: { from: \"start\", to: \"end\" } });\n\nexport const planPrintPressesByHall = e3.recordIndex(\"plan_print_presses_by_hall\", planPrintPresses, { on: [\"hall\"] });\n\nexport const planPrintCustomersByDistrict = e3.recordIndex(\"plan_print_customers_by_district\", planPrintCustomers, { on: [\"district\"] });\n\nexport const planPrintJobTemplatesByGroup = e3.recordIndex(\"plan_print_job_templates_by_group\", planPrintJobTemplates, { on: [\"group\"] });\n\nexport const planPrintStopTemplatesByGroup = e3.recordIndex(\"plan_print_stop_templates_by_group\", planPrintStopTemplates, { on: [\"group\"] });\n\nexport const planPrintShiftTemplatesByGroup = e3.recordIndex(\"plan_print_shift_templates_by_group\", planPrintShiftTemplates, { on: [\"group\"] });\n\nexport const planPrintPlatesTemplatesByGroup = e3.recordIndex(\"plan_print_plates_templates_by_group\", planPrintPlatesTemplates, { on: [\"group\"] });\n\nexport const planPrintBindingTemplatesByGroup = e3.recordIndex(\"plan_print_binding_templates_by_group\", planPrintBindingTemplates, { on: [\"group\"] });\n\nexport const planPrintDeliveryTemplatesByGroup = e3.recordIndex(\"plan_print_delivery_templates_by_group\", planPrintDeliveryTemplates, { on: [\"group\"] });\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n // What the Plan reads whole binds with Data.bind, which keeps it loaded beside its pages; the event\n // records it writes bind with Record.bind, and it reads the jobs' and the shifts' events through their\n // pages alone.\n const presses = $.let(Data.bind(planPrintPresses));\n const crews = $.let(Record.bind(planPrintCrews, []));\n const jobs = $.let(Record.bind(planPrintJobs, [planPrintJobsPatch]));\n const stops = $.let(Record.bind(planPrintStops, [planPrintStopsPatch]));\n const shifts = $.let(Record.bind(planPrintShifts, [planPrintShiftsPatch]));\n const customers = $.let(Data.bind(planPrintCustomers));\n const jobTemplates = $.let(Data.bind(planPrintJobTemplates));\n const stopTemplates = $.let(Data.bind(planPrintStopTemplates));\n const shiftTemplates = $.let(Data.bind(planPrintShiftTemplates));\n const platesTemplates = $.let(Data.bind(planPrintPlatesTemplates));\n const bindingTemplates = $.let(Data.bind(planPrintBindingTemplates));\n const deliveryTemplates = $.let(Data.bind(planPrintDeliveryTemplates));\n // One rail over the records, a source each (#1344): the jobs and the shifts through their interval\n // indexes, their range the one window the toolbar's dates, the resolution and the brush write; the\n // presses, the customers and each templates record by its fields.\n const slice = $.let(Slice.bindRecords(\"ex.planPrintWorks\", {\n job: { source: Data.bindPaged(planPrintJobs, { indexes: [planPrintJobsByTime, planPrintJobsByStockTime] }), icon: { prefix: \"fas\", name: \"file-lines\" },\n fields: { stock: { label: \"Stock\" }, start: { label: \"Dates\" } }, range: \"start\" },\n shift: { source: Data.bindPaged(planPrintShifts, { indexes: [planPrintShiftsByTime] }), icon: { prefix: \"fas\", name: \"user-clock\" },\n fields: { start: { label: \"Dates\" } }, range: \"start\" },\n presses: { source: Data.bindPaged(planPrintPresses, { indexes: [planPrintPressesByHall] }), icon: { prefix: \"fas\", name: \"print\" },\n fields: { hall: { label: \"Hall\", hints: [\"Hall A\", \"Hall B\"] } } },\n customers: { source: Data.bindPaged(planPrintCustomers, { indexes: [planPrintCustomersByDistrict] }), icon: { prefix: \"fas\", name: \"building\" },\n fields: { district: { label: \"District\", hints: [\"North Quay\", \"Old Town\", \"Riverside\"] } } },\n jobTemplates: { source: Data.bindPaged(planPrintJobTemplates, { indexes: [planPrintJobTemplatesByGroup] }), icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Job templates\", hints: [\"Jobs\"] } } },\n stopTemplates: { source: Data.bindPaged(planPrintStopTemplates, { indexes: [planPrintStopTemplatesByGroup] }), icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Stop templates\", hints: [\"Stops\"] } } },\n shiftTemplates: { source: Data.bindPaged(planPrintShiftTemplates, { indexes: [planPrintShiftTemplatesByGroup] }), icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Shift templates\", hints: [\"Shifts\"] } } },\n platesTemplates: { source: Data.bindPaged(planPrintPlatesTemplates, { indexes: [planPrintPlatesTemplatesByGroup] }), icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Plates templates\", hints: [\"Plate room\"] } } },\n bindingTemplates: { source: Data.bindPaged(planPrintBindingTemplates, { indexes: [planPrintBindingTemplatesByGroup] }), icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Bindery templates\", hints: [\"Bindery\"] } } },\n deliveryTemplates: { source: Data.bindPaged(planPrintDeliveryTemplates, { indexes: [planPrintDeliveryTemplatesByGroup] }), icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Delivery templates\", hints: [\"Dispatch\"] } } },\n }));\n // A kind narrowed through its source reads one event by its key through the record's own entries.\n const jobEntries = $.let(Data.bindPaged(planPrintJobs));\n const shiftEntries = $.let(Data.bindPaged(planPrintShifts));\n const setters = $.let(Record.bind(planLinkSetters, []));\n const lines = $.let(Record.bind(planLinkLines, []));\n const bindery = $.let(Record.bind(planPrintBindery, []));\n const bays = $.let(Record.bind(planLinkBays, []));\n const vans = $.let(Record.bind(planPrintVans, []));\n const plates = $.let(Record.bind(planPrintPlates, [planPrintPlatesPatch]));\n const bindings = $.let(Record.bind(planPrintBindings, [planPrintBindingsPatch]));\n const deliveries = $.let(Record.bind(planPrintDeliveries, [planPrintDeliveriesPatch]));\n const stock = $.let(Data.bind(planLinkStock));\n const lineRows = $.const(lines.read().union(bindery.read(), (_$2, line) => line));\n const util = $.let(Data.bind(planPrintUtilisation));\n const output = $.let(Data.bind(planPrintOutput));\n // The window's first day: each reading runs from it, a day apiece.\n const first = $.const(new Date(\"2026-10-05T00:00:00Z\"), DateTimeType);\n const parentLine = $.const(some(\"bindery\"), OptionType(StringType));\n const topLine = $.const(none, OptionType(StringType));\n // A press's readings as heat cells, one a day.\n const daily = $.const(East.function([ArrayType(FloatType)], ArrayType(Plan.Types.HeatCell), (_$2, readings) =>\n East.Array.generate(readings.size(), Plan.Types.HeatCell, (_$3, i) => ({\n at: Plan.at.time(first.addDays(i)), value: some(readings.get(i)), label: none,\n }))));\n // The sheets printed each day, as a chart's points.\n const PrintDay = StructType({ day: DateTimeType, sheets: FloatType });\n const printed = $.let(output.read());\n const days = $.let(East.Array.generate(printed.size(), PrintDay, (_$2, i) => ({ day: first.addDays(i), sheets: printed.get(i) })));\n const axis = $.let(Plan.axis({\n window: { min: new Date(\"2026-10-05T00:00:00Z\"), max: new Date(\"2026-11-02T00:00:00Z\") },\n resolution: \"day\", resolutions: [\"week\", \"day\"], now: new Date(\"2026-10-14T09:00:00Z\"),\n }));\n const StockWeek = StructType({ at: DateTimeType, value: OptionType(FloatType) });\n const weekly = $.const(East.function([ArrayType(FloatType)], ArrayType(StockWeek), (_$2, readings) =>\n East.Array.generate(readings.size(), StockWeek, (_$3, i) => ({ at: first.addWeeks(i), value: some(readings.get(i)) }))));\n const canDrop = $.const(East.function([Schedule.Types.Candidate], OptionType(StringType), ($2, candidate) => {\n const reason = $2.let(none, OptionType(StringType));\n $2.match(candidate.resource, { some: ($3, resource) => {\n $3.if(resource.kind.equal(\"presses\").and(() => resource.key.equal(\"b3\")), $4 => {\n $4.assign(reason, some(\"Press B3 is in service; choose another press\"));\n });\n } });\n return reason;\n }));\n // A stop's own inspector: its title, and whether it is a plate change or a\n // service, chosen on a segment — the edited stop goes back through `update`.\n const StopKindType = PrintStop.fields.kind;\n const inspectStop = $.const(East.function([PrintStop, FunctionType([PrintStop], NullType)], UIComponentType, ($2, stop, update) => {\n const setKind = $2.const(East.function([StringType], NullType, ($3, picked) => {\n const kind = $3.const(picked.equal(\"service\").ifElse(\n () => East.value(variant(\"service\", null), StopKindType),\n () => East.value(variant(\"plate_change\", null), StopKindType)));\n // East has no struct spread: the stop, rebuilt with its new kind.\n $3(update({ title: stop.title, at: stop.at, press: stop.press, kind }));\n }));\n return (\n <VStack gap=\"3\" align=\"stretch\">\n <Text>{stop.title}</Text>\n <SegmentGroup value={stop.kind.getTag()} onChange={setKind} size=\"sm\"\n items={[SegmentGroup.Item(\"plate_change\", \"Plate change\"), SegmentGroup.Item(\"service\", \"Service\")]} />\n </VStack>\n );\n }));\n return (\n <Plan\n id=\"printWorks\"\n axis={axis}\n date={first}\n canDrop={canDrop}\n slice={{ slice }}\n resources={{\n presses: Schedule.resources(presses.read(), {\n name: \"Presses\", icon: \"print\",\n label: p => p.name, group: p => p.hall,\n sub: p => some(East.str`${East.Float.printCommaSeperated(p.sheets_per_hour, 0n)} sheets/h`),\n // A row under each press, from the dataflow's utilisation: an ordinary\n // heat series over the press rows, read only.\n measures: [\n Plan.series.heat(PrintPress, {\n key: \"util\", title: \"Utilisation\", label: () => \"Utilisation\",\n cells: (_p, key) => Plan.heatCells(daily(util.read().get(key)), { min: 0, max: 100, warnAt: 95 }),\n }),\n ],\n }),\n crews: Schedule.resources(crews.read(), { name: \"Crews\", icon: \"user-group\", label: c => c.name, group: c => c.hall }),\n setters: Schedule.resources(setters.read(), { name: \"Plate room\", icon: \"layer-group\", label: p => p.name, meta: () => some(\"Plates before printing\") }),\n lines: Schedule.resources(lineRows, {\n name: \"Bindery\", icon: \"book\", label: l => l.name,\n parent: (_l, key) => key.equal(\"bindery\").ifElse(() => topLine, () => parentLine),\n rollup: \"union\", collapsed: (_l, key) => key.equal(\"bindery\"),\n meta: () => some(\"Finishing\"), value: (_l, key) => key.equal(\"bindery\").ifElse(() => some(\"2 lines\"), () => topLine),\n }),\n bays: Schedule.resources(bays.read(), { name: \"Dispatch\", icon: \"warehouse\", label: b => b.name, meta: () => some(\"Loading\") }),\n vans: Schedule.resources(vans.read(), { name: \"Vans\", icon: \"truck\", label: v => v.name, meta: () => some(\"Delivery fleet\") }),\n }}\n events={{\n job: Schedule.events(jobs, {\n name: \"Print job\", icon: \"file-lines\", draw: \"span\",\n title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"press\", of: \"presses\" },\n state: \"state\",\n quantity: { field: \"sheets\", unit: \"sheets\" },\n backlog: { duration: j => variant(\"hours\", j.sheets.divide(8000.0)), due: j => j.due },\n fields: {\n customer: Schedule.field.text({ label: \"Customer\" }),\n stock: Schedule.field.select({ labels: { coated: \"Coated\", uncoated: \"Uncoated\", board: \"Board\" } }),\n sheets: Schedule.field.number({ label: \"Sheets\", step: 1000.0, min: 0.0 }),\n },\n // Narrowed through its source of the slice (`job`): one job is read by its key here.\n entries: jobEntries,\n templates: Schedule.templates(jobTemplates.read(), {\n name: t => t.name, group: t => some(t.group), duration: t => variant(\"hours\", t.hours), values: t => t.values, source: \"jobTemplates\",\n }),\n }),\n stop: Schedule.events(stops, {\n name: \"Stop\", icon: \"screwdriver-wrench\", draw: \"marks\",\n title: \"title\", at: \"at\",\n resource: { field: \"press\", of: \"presses\" },\n inspector: inspectStop,\n // A stop is one instant: its templates give no duration.\n templates: Schedule.templates(stopTemplates.read(), { name: t => t.name, group: t => some(t.group), values: t => t.values, source: \"stopTemplates\" }),\n }),\n shift: Schedule.events(shifts, {\n name: \"Crew shift\", icon: \"user-clock\", draw: \"cards\",\n title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"crew\", of: \"crews\" }, state: \"state\",\n entries: shiftEntries,\n templates: Schedule.templates(shiftTemplates.read(), {\n name: t => t.name, group: t => some(t.group), at: t => some({ hour: t.hour, minute: 0n }), duration: () => variant(\"hours\", 8.0),\n values: t => t.values, source: \"shiftTemplates\",\n }),\n }),\n plate: Schedule.events(plates, {\n name: \"Plates\", icon: \"clone\", title: \"title\", at: \"at\", resource: { field: \"setter\", of: \"setters\" },\n templates: Schedule.templates(platesTemplates.read(), { name: t => t.name, group: t => some(t.group), values: t => t.values, source: \"platesTemplates\" }),\n }),\n binding: Schedule.events(bindings, {\n name: \"Bindery job\", icon: \"book-open\", draw: \"cards\", title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"line\", of: \"lines\" }, state: \"state\", overlaps: \"allow\",\n templates: Schedule.templates(bindingTemplates.read(), {\n name: t => t.name, group: t => some(t.group), duration: t => variant(\"hours\", t.hours), values: t => t.values, source: \"bindingTemplates\",\n }),\n }),\n delivery: Schedule.events(deliveries, {\n name: \"Delivery\", icon: \"truck\", draw: \"buckets\", title: \"title\", at: \"at\",\n resource: { field: \"location\", of: { bay: \"bays\", van: \"vans\" } }, lane: \"lane\",\n status: { field: \"status\", cases: {\n queued: { label: \"Queued\", tone: variant(\"warning\", null), ring: true },\n ready: { label: \"Ready\", tone: variant(\"info\", null), ring: false },\n dispatched: { label: \"Dispatched\", tone: variant(\"success\", null), ring: false },\n } },\n ready: d => d.pallets.lessEqual(0.0).ifElse(\n () => variant(\"invalid\", [{ field: \"pallets\", message: \"Pallets must be positive\" }]),\n () => variant(\"ready\", null)),\n fields: { pallets: Schedule.field.number({ label: \"Pallets\", min: 0.0, step: 1.0 }) },\n templates: Schedule.templates(deliveryTemplates.read(), {\n name: t => t.name, group: t => some(t.group), at: t => some({ hour: t.hour, minute: 0n }), values: t => t.values, source: \"deliveryTemplates\",\n }),\n }),\n }}\n rows={[\n Plan.chart({\n key: \"output\", label: \"SHEETS / DAY\", id: true, pinned: true, height: \"spark\", expandable: true,\n layers: [Chart.Column(days, { x: r => r.day, y: r => r.sheets })],\n }),\n Plan.over(stock, [Plan.series.table(PrintStock, {\n key: \"stock\", title: \"Paper stock\", label: s => s.name,\n cells: s => Plan.tableCells(weekly(s.weekly)), format: Format.Number({ maximumFractionDigits: 0n }),\n })]),\n ]}\n links={[\n Plan.link({ key: \"plates\", from: Plan.eventRef(\"plate\", \"P-101\"), to: Plan.eventRef(\"job\", \"J-1004\") }),\n Plan.link({ key: \"printed\", from: Plan.eventRef(\"job\", \"J-1004\"), to: Plan.eventRef(\"binding\", \"B-101\"), quantity: Plan.quantity(144, { unit: \"k sheets\" }) }),\n Plan.link({ key: \"folded\", from: Plan.eventRef(\"binding\", \"B-101\"), to: Plan.eventRef(\"binding\", \"B-103\") }),\n Plan.link({ key: \"dispatched\", from: Plan.eventRef(\"binding\", \"B-103\"), to: Plan.eventRef(\"delivery\", \"D-101\") }),\n ]}\n grain=\"resource\"\n library={[\n Plan.library.events(),\n Plan.library.backlog(),\n Plan.library.series(),\n // The customers, by district: a card dropped on a job sets its customer. The tab follows the\n // rail's `customers` source, by its rows' keys.\n Plan.library.tab(customers.read(), {\n name: \"Customers\", icon: \"building\",\n label: c => c.name, meta: c => some(c.trade), group: c => c.district,\n drop: c => Schedule.patch(PrintJob, { customer: c.name }), source: \"customers\",\n }),\n ]}\n inspector\n />\n );\n }}</Reactive>\n ))"
|
|
42174
|
+
"source": "// The print works from plates to dispatch — jobs, stops and shifts on grouped presses and crews, nested bindery resources, deliveries in AM/PM lanes on a bay or a van, status and readiness, links between event kinds, utilisation and paper stock; one Slice.bindRecords rail — the jobs and the shifts through their interval indexes, their range the one window, the presses, the customers and each kind's templates record — and the shared library, inspector and Save session\n// inputs: []\n/** A press: its name, its hall and the sheets it prints an hour. */\nexport const PrintPress = StructType({ name: StringType, hall: StringType, sheets_per_hour: FloatType });\n\n/** A crew: its name and its hall. */\nexport const PrintCrew = StructType({ name: StringType, hall: StringType });\n\n/**\n * A print job: when it runs and on which press — none of the three while it\n * waits in the backlog — the lifecycle it wears, its sheets, and the\n * customer's details.\n */\nexport const PrintJob = StructType({\n title: StringType,\n start: OptionType(DateTimeType),\n end: OptionType(DateTimeType),\n press: OptionType(StringType),\n state: EventStateType,\n sheets: FloatType,\n customer: StringType,\n stock: VariantType({ coated: NullType, uncoated: NullType, board: NullType }),\n due: OptionType(DateTimeType),\n});\n\n/** A stop on a press, at one instant: a plate change or a service. */\nexport const PrintStop = StructType({\n title: StringType,\n at: DateTimeType,\n press: StringType,\n kind: VariantType({ plate_change: NullType, service: NullType }),\n});\n\n/** A crew's shift. */\nexport const PrintShift = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, crew: StringType, state: EventStateType });\n\n/** A customer: its name, the district it is in and its trade. */\nexport const PrintCustomer = StructType({ name: StringType, district: StringType, trade: StringType });\n\n/** The presses: three in each hall. A Plan never writes its resources. */\nexport const planPrintPresses = e3.record(\"plan_print_presses\", DictType(StringType, PrintPress), new Map([\n [\"a1\", { name: \"Press A1\", hall: \"Hall A\", sheets_per_hour: 12000.0 }],\n [\"a2\", { name: \"Press A2\", hall: \"Hall A\", sheets_per_hour: 10000.0 }],\n [\"a3\", { name: \"Press A3\", hall: \"Hall A\", sheets_per_hour: 8000.0 }],\n [\"b1\", { name: \"Press B1\", hall: \"Hall B\", sheets_per_hour: 15000.0 }],\n [\"b2\", { name: \"Press B2\", hall: \"Hall B\", sheets_per_hour: 12000.0 }],\n [\"b3\", { name: \"Press B3\", hall: \"Hall B\", sheets_per_hour: 6000.0 }],\n]));\n\n/** The crews: two in each hall. */\nexport const planPrintCrews = e3.record(\"plan_print_crews\", DictType(StringType, PrintCrew), new Map([\n [\"c1\", { name: \"Crew 1\", hall: \"Hall A\" }],\n [\"c2\", { name: \"Crew 2\", hall: \"Hall A\" }],\n [\"c3\", { name: \"Crew 3\", hall: \"Hall B\" }],\n [\"c4\", { name: \"Crew 4\", hall: \"Hall B\" }],\n]));\n\n/**\n * The print jobs: actual before 14 October, in progress on it, confirmed,\n * proposed and estimated after it; two overlapping on Press B2 on the 20th;\n * and eight with no start waiting in the backlog, two due this week, two next\n * week, two later and two with no due date.\n */\nexport const planPrintJobs = e3.record(\"plan_print_jobs\", DictType(StringType, PrintJob), new Map([\n [\"J-1001\", { title: \"Spring catalogue\", start: some(new Date(\"2026-10-05T06:00:00Z\")), end: some(new Date(\"2026-10-05T14:00:00Z\")), press: some(\"a1\"), state: variant(\"actual\", null), sheets: 96000.0, customer: \"Alder & Finch\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-07T00:00:00Z\")) }],\n [\"J-1002\", { title: \"Museum guide\", start: some(new Date(\"2026-10-07T06:00:00Z\")), end: some(new Date(\"2026-10-07T12:00:00Z\")), press: some(\"a1\"), state: variant(\"actual\", null), sheets: 72000.0, customer: \"Driftwood Museum\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-09T00:00:00Z\")) }],\n [\"J-1003\", { title: \"Event posters\", start: some(new Date(\"2026-10-14T07:00:00Z\")), end: some(new Date(\"2026-10-14T12:00:00Z\")), press: some(\"a1\"), state: variant(\"in-progress\", null), sheets: 60000.0, customer: \"Granite Hall\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-15T00:00:00Z\")) }],\n [\"J-1004\", { title: \"Course handbook\", start: some(new Date(\"2026-10-19T06:00:00Z\")), end: some(new Date(\"2026-10-19T18:00:00Z\")), press: some(\"a1\"), state: variant(\"confirmed\", null), sheets: 144000.0, customer: \"Elmway College\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-23T00:00:00Z\")) }],\n [\"J-1005\", { title: \"Menu cards\", start: some(new Date(\"2026-10-27T06:00:00Z\")), end: some(new Date(\"2026-10-27T08:00:00Z\")), press: some(\"a1\"), state: variant(\"proposed\", variant(\"recommended\", null)), sheets: 24000.0, customer: \"Copperleaf Cafe\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-30T00:00:00Z\")) }],\n [\"J-1006\", { title: \"Tour brochure\", start: some(new Date(\"2026-10-06T06:00:00Z\")), end: some(new Date(\"2026-10-06T14:00:00Z\")), press: some(\"a2\"), state: variant(\"actual\", null), sheets: 80000.0, customer: \"Bluewater Tours\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-08T00:00:00Z\")) }],\n [\"J-1007\", { title: \"Annual report\", start: some(new Date(\"2026-10-14T06:00:00Z\")), end: some(new Date(\"2026-10-14T12:00:00Z\")), press: some(\"a2\"), state: variant(\"in-progress\", null), sheets: 60000.0, customer: \"Harbour Arts Society\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"J-1008\", { title: \"Seed catalogue\", start: some(new Date(\"2026-10-21T06:00:00Z\")), end: some(new Date(\"2026-10-21T16:00:00Z\")), press: some(\"a2\"), state: variant(\"proposed\", variant(\"added\", null)), sheets: 100000.0, customer: \"Foxglove Gardens\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-24T00:00:00Z\")) }],\n [\"J-1009\", { title: \"Season flyers\", start: some(new Date(\"2026-10-28T06:00:00Z\")), end: some(new Date(\"2026-10-28T09:00:00Z\")), press: some(\"a2\"), state: variant(\"estimated\", null), sheets: 30000.0, customer: \"Heathfield Theatre\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-31T00:00:00Z\")) }],\n [\"J-1010\", { title: \"Club newsletter\", start: some(new Date(\"2026-10-08T06:00:00Z\")), end: some(new Date(\"2026-10-08T08:00:00Z\")), press: some(\"a3\"), state: variant(\"actual\", null), sheets: 16000.0, customer: \"Kestrel Cycling Club\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-09T00:00:00Z\")) }],\n [\"J-1011\", { title: \"Stationery set\", start: some(new Date(\"2026-10-15T06:00:00Z\")), end: some(new Date(\"2026-10-15T09:00:00Z\")), press: some(\"a3\"), state: variant(\"confirmed\", null), sheets: 24000.0, customer: \"Ivy Lane Studio\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"J-1012\", { title: \"Gift boxes\", start: some(new Date(\"2026-10-22T06:00:00Z\")), end: some(new Date(\"2026-10-22T12:00:00Z\")), press: some(\"a3\"), state: variant(\"proposed\", variant(\"recommended\", null)), sheets: 48000.0, customer: \"Juniper Toys\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-26T00:00:00Z\")) }],\n [\"J-1013\", { title: \"Holiday catalogue\", start: some(new Date(\"2026-10-05T06:00:00Z\")), end: some(new Date(\"2026-10-05T22:00:00Z\")), press: some(\"b1\"), state: variant(\"actual\", null), sheets: 240000.0, customer: \"Larkspur Home\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-09T00:00:00Z\")) }],\n [\"J-1014\", { title: \"Magazine run\", start: some(new Date(\"2026-10-12T06:00:00Z\")), end: some(new Date(\"2026-10-12T18:00:00Z\")), press: some(\"b1\"), state: variant(\"actual\", null), sheets: 180000.0, customer: \"Meridian Monthly\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-13T00:00:00Z\")) }],\n [\"J-1015\", { title: \"Store flyers\", start: some(new Date(\"2026-10-14T06:00:00Z\")), end: some(new Date(\"2026-10-14T16:00:00Z\")), press: some(\"b1\"), state: variant(\"in-progress\", null), sheets: 150000.0, customer: \"Northwind Outfitters\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"J-1016\", { title: \"Exhibition book\", start: some(new Date(\"2026-10-26T06:00:00Z\")), end: some(new Date(\"2026-10-26T12:00:00Z\")), press: some(\"b1\"), state: variant(\"estimated\", null), sheets: 90000.0, customer: \"Driftwood Museum\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-30T00:00:00Z\")) }],\n [\"J-1017\", { title: \"Timetables\", start: some(new Date(\"2026-10-09T06:00:00Z\")), end: some(new Date(\"2026-10-09T10:00:00Z\")), press: some(\"b2\"), state: variant(\"actual\", null), sheets: 48000.0, customer: \"Bluewater Tours\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-12T00:00:00Z\")) }],\n [\"J-1018\", { title: \"Market posters\", start: some(new Date(\"2026-10-20T06:00:00Z\")), end: some(new Date(\"2026-10-20T12:00:00Z\")), press: some(\"b2\"), state: variant(\"confirmed\", null), sheets: 72000.0, customer: \"Orchard Street Market\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-22T00:00:00Z\")) }],\n [\"J-1019\", { title: \"Loyalty cards\", start: some(new Date(\"2026-10-20T10:00:00Z\")), end: some(new Date(\"2026-10-20T13:00:00Z\")), press: some(\"b2\"), state: variant(\"proposed\", variant(\"added\", null)), sheets: 36000.0, customer: \"Copperleaf Cafe\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-23T00:00:00Z\")) }],\n [\"J-1020\", { title: \"Ticket books\", start: some(new Date(\"2026-10-29T06:00:00Z\")), end: some(new Date(\"2026-10-29T08:00:00Z\")), press: some(\"b2\"), state: variant(\"estimated\", null), sheets: 24000.0, customer: \"Heathfield Theatre\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-11-02T00:00:00Z\")) }],\n [\"J-1021\", { title: \"Handbook covers\", start: some(new Date(\"2026-10-16T06:00:00Z\")), end: some(new Date(\"2026-10-16T08:00:00Z\")), press: some(\"b3\"), state: variant(\"confirmed\", null), sheets: 12000.0, customer: \"Elmway College\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-19T00:00:00Z\")) }],\n [\"J-1022\", { title: \"Box sleeves\", start: some(new Date(\"2026-10-20T06:00:00Z\")), end: some(new Date(\"2026-10-20T09:00:00Z\")), press: some(\"b3\"), state: variant(\"proposed\", variant(\"recommended\", null)), sheets: 18000.0, customer: \"Juniper Toys\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-22T00:00:00Z\")) }],\n [\"J-1023\", { title: \"Guide reprint\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 24000.0, customer: \"Driftwood Museum\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"J-1024\", { title: \"Order forms\", start: none, end: none, press: none, state: variant(\"proposed\", variant(\"added\", null)), sheets: 40000.0, customer: \"Larkspur Home\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-17T00:00:00Z\")) }],\n [\"J-1025\", { title: \"Winter brochure\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 64000.0, customer: \"Bluewater Tours\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-21T00:00:00Z\")) }],\n [\"J-1026\", { title: \"Wall calendars\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 50000.0, customer: \"Foxglove Gardens\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-23T00:00:00Z\")) }],\n [\"J-1027\", { title: \"Prospectus\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 100000.0, customer: \"Elmway College\", stock: variant(\"coated\", null), due: some(new Date(\"2026-11-06T00:00:00Z\")) }],\n [\"J-1028\", { title: \"Price lists\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 16000.0, customer: \"Northwind Outfitters\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-11-13T00:00:00Z\")) }],\n [\"J-1029\", { title: \"Spare covers\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 8000.0, customer: \"Meridian Monthly\", stock: variant(\"board\", null), due: none }],\n [\"J-1030\", { title: \"Proof sheets\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 2000.0, customer: \"Alder & Finch\", stock: variant(\"uncoated\", null), due: none }],\n]));\n\n/** The jobs' patch door: every Save commits through it. */\nexport const planPrintJobsPatch = e3.mutation.patch(planPrintJobs);\n\n/** The stops: plate changes and services across the presses. */\nexport const planPrintStops = e3.record(\"plan_print_stops\", DictType(StringType, PrintStop), new Map([\n [\"S-01\", { title: \"Plate change\", at: new Date(\"2026-10-07T12:00:00Z\"), press: \"a1\", kind: variant(\"plate_change\", null) }],\n [\"S-02\", { title: \"Service\", at: new Date(\"2026-10-12T04:00:00Z\"), press: \"b1\", kind: variant(\"service\", null) }],\n [\"S-03\", { title: \"Plate change\", at: new Date(\"2026-10-14T12:00:00Z\"), press: \"a2\", kind: variant(\"plate_change\", null) }],\n [\"S-04\", { title: \"Plate change\", at: new Date(\"2026-10-19T18:00:00Z\"), press: \"a1\", kind: variant(\"plate_change\", null) }],\n [\"S-05\", { title: \"Service\", at: new Date(\"2026-10-26T14:00:00Z\"), press: \"a3\", kind: variant(\"service\", null) }],\n [\"S-06\", { title: \"Plate change\", at: new Date(\"2026-10-28T10:00:00Z\"), press: \"b2\", kind: variant(\"plate_change\", null) }],\n]));\n\n/** The stops' patch door. */\nexport const planPrintStopsPatch = e3.mutation.patch(planPrintStops);\n\n/** The shifts: each crew's early and late shifts, confirmed for the first two weeks and proposed for the last two. */\nexport const planPrintShifts = e3.record(\"plan_print_shifts\", DictType(StringType, PrintShift), new Map([\n [\"SH-01\", { title: \"Early\", start: new Date(\"2026-10-05T06:00:00Z\"), end: new Date(\"2026-10-05T14:00:00Z\"), crew: \"c1\", state: variant(\"confirmed\", null) }],\n [\"SH-02\", { title: \"Early\", start: new Date(\"2026-10-07T06:00:00Z\"), end: new Date(\"2026-10-07T14:00:00Z\"), crew: \"c1\", state: variant(\"confirmed\", null) }],\n [\"SH-03\", { title: \"Late\", start: new Date(\"2026-10-13T14:00:00Z\"), end: new Date(\"2026-10-13T22:00:00Z\"), crew: \"c1\", state: variant(\"confirmed\", null) }],\n [\"SH-04\", { title: \"Late\", start: new Date(\"2026-10-15T14:00:00Z\"), end: new Date(\"2026-10-15T22:00:00Z\"), crew: \"c1\", state: variant(\"confirmed\", null) }],\n [\"SH-05\", { title: \"Early\", start: new Date(\"2026-10-19T06:00:00Z\"), end: new Date(\"2026-10-19T14:00:00Z\"), crew: \"c1\", state: variant(\"proposed\", variant(\"recommended\", null)) }],\n [\"SH-06\", { title: \"Early\", start: new Date(\"2026-10-26T06:00:00Z\"), end: new Date(\"2026-10-26T14:00:00Z\"), crew: \"c1\", state: variant(\"proposed\", variant(\"recommended\", null)) }],\n [\"SH-07\", { title: \"Late\", start: new Date(\"2026-10-05T14:00:00Z\"), end: new Date(\"2026-10-05T22:00:00Z\"), crew: \"c2\", state: variant(\"confirmed\", null) }],\n [\"SH-08\", { title: \"Late\", start: new Date(\"2026-10-07T14:00:00Z\"), end: new Date(\"2026-10-07T22:00:00Z\"), crew: \"c2\", state: variant(\"confirmed\", null) }],\n [\"SH-09\", { title: \"Early\", start: new Date(\"2026-10-13T06:00:00Z\"), end: new Date(\"2026-10-13T14:00:00Z\"), crew: \"c2\", state: variant(\"confirmed\", null) }],\n [\"SH-10\", { title: \"Early\", start: new Date(\"2026-10-15T06:00:00Z\"), end: new Date(\"2026-10-15T14:00:00Z\"), crew: \"c2\", state: variant(\"confirmed\", null) }],\n [\"SH-11\", { title: \"Late\", start: new Date(\"2026-10-19T14:00:00Z\"), end: new Date(\"2026-10-19T22:00:00Z\"), crew: \"c2\", state: variant(\"proposed\", variant(\"recommended\", null)) }],\n [\"SH-12\", { title: \"Late\", start: new Date(\"2026-10-26T14:00:00Z\"), end: new Date(\"2026-10-26T22:00:00Z\"), crew: \"c2\", state: variant(\"proposed\", variant(\"recommended\", null)) }],\n [\"SH-13\", { title: \"Early\", start: new Date(\"2026-10-06T06:00:00Z\"), end: new Date(\"2026-10-06T14:00:00Z\"), crew: \"c3\", state: variant(\"confirmed\", null) }],\n [\"SH-14\", { title: \"Early\", start: new Date(\"2026-10-08T06:00:00Z\"), end: new Date(\"2026-10-08T14:00:00Z\"), crew: \"c3\", state: variant(\"confirmed\", null) }],\n [\"SH-15\", { title: \"Late\", start: new Date(\"2026-10-12T14:00:00Z\"), end: new Date(\"2026-10-12T22:00:00Z\"), crew: \"c3\", state: variant(\"confirmed\", null) }],\n [\"SH-16\", { title: \"Late\", start: new Date(\"2026-10-14T14:00:00Z\"), end: new Date(\"2026-10-14T22:00:00Z\"), crew: \"c3\", state: variant(\"confirmed\", null) }],\n [\"SH-17\", { title: \"Early\", start: new Date(\"2026-10-20T06:00:00Z\"), end: new Date(\"2026-10-20T14:00:00Z\"), crew: \"c3\", state: variant(\"proposed\", variant(\"recommended\", null)) }],\n [\"SH-18\", { title: \"Early\", start: new Date(\"2026-10-27T06:00:00Z\"), end: new Date(\"2026-10-27T14:00:00Z\"), crew: \"c3\", state: variant(\"proposed\", variant(\"added\", null)) }],\n [\"SH-19\", { title: \"Late\", start: new Date(\"2026-10-06T14:00:00Z\"), end: new Date(\"2026-10-06T22:00:00Z\"), crew: \"c4\", state: variant(\"confirmed\", null) }],\n [\"SH-20\", { title: \"Late\", start: new Date(\"2026-10-08T14:00:00Z\"), end: new Date(\"2026-10-08T22:00:00Z\"), crew: \"c4\", state: variant(\"confirmed\", null) }],\n [\"SH-21\", { title: \"Early\", start: new Date(\"2026-10-12T06:00:00Z\"), end: new Date(\"2026-10-12T14:00:00Z\"), crew: \"c4\", state: variant(\"confirmed\", null) }],\n [\"SH-22\", { title: \"Early\", start: new Date(\"2026-10-14T06:00:00Z\"), end: new Date(\"2026-10-14T14:00:00Z\"), crew: \"c4\", state: variant(\"confirmed\", null) }],\n [\"SH-23\", { title: \"Late\", start: new Date(\"2026-10-20T14:00:00Z\"), end: new Date(\"2026-10-20T22:00:00Z\"), crew: \"c4\", state: variant(\"proposed\", variant(\"recommended\", null)) }],\n [\"SH-24\", { title: \"Late\", start: new Date(\"2026-10-27T14:00:00Z\"), end: new Date(\"2026-10-27T22:00:00Z\"), crew: \"c4\", state: variant(\"proposed\", variant(\"added\", null)) }],\n]));\n\n/** The shifts' patch door. */\nexport const planPrintShiftsPatch = e3.mutation.patch(planPrintShifts);\n\n/** The customers the jobs are printed for, by account, in three districts: what the print works' library lists. A Plan never writes them. */\nexport const planPrintCustomers = e3.record(\"plan_print_customers\", DictType(StringType, PrintCustomer), new Map([\n [\"alder-finch\", { name: \"Alder & Finch\", district: \"Old Town\", trade: \"Retail\" }],\n [\"bluewater-tours\", { name: \"Bluewater Tours\", district: \"North Quay\", trade: \"Travel\" }],\n [\"copperleaf-cafe\", { name: \"Copperleaf Cafe\", district: \"Old Town\", trade: \"Hospitality\" }],\n [\"driftwood-museum\", { name: \"Driftwood Museum\", district: \"North Quay\", trade: \"Arts\" }],\n [\"elmway-college\", { name: \"Elmway College\", district: \"Riverside\", trade: \"Education\" }],\n [\"foxglove-gardens\", { name: \"Foxglove Gardens\", district: \"Riverside\", trade: \"Retail\" }],\n [\"granite-hall\", { name: \"Granite Hall\", district: \"Old Town\", trade: \"Events\" }],\n [\"harbour-arts\", { name: \"Harbour Arts Society\", district: \"North Quay\", trade: \"Arts\" }],\n [\"heathfield\", { name: \"Heathfield Theatre\", district: \"Old Town\", trade: \"Arts\" }],\n [\"ivy-lane\", { name: \"Ivy Lane Studio\", district: \"Riverside\", trade: \"Design\" }],\n [\"juniper-toys\", { name: \"Juniper Toys\", district: \"Riverside\", trade: \"Retail\" }],\n [\"kestrel-cycling\", { name: \"Kestrel Cycling Club\", district: \"Riverside\", trade: \"Sport\" }],\n [\"larkspur-home\", { name: \"Larkspur Home\", district: \"Old Town\", trade: \"Retail\" }],\n [\"meridian-monthly\", { name: \"Meridian Monthly\", district: \"North Quay\", trade: \"Publishing\" }],\n [\"northwind\", { name: \"Northwind Outfitters\", district: \"North Quay\", trade: \"Retail\" }],\n [\"orchard-market\", { name: \"Orchard Street Market\", district: \"Old Town\", trade: \"Markets\" }],\n]));\n\n/**\n * Each press's utilisation (%): a reading a day from 5 October, as the\n * dataflow measures it, idle at the weekends. Press B2's 97 on the 20th is\n * the day its two jobs overlap.\n */\nexport const planPrintUtilisation = e3.input(\"plan_print_utilisation\", DictType(StringType, ArrayType(FloatType)), variant(\"value\", new Map([\n [\"a1\", [50.0, 12.0, 38.0, 20.0, 15.0, 0.0, 0.0, 22.0, 18.0, 31.0, 25.0, 20.0, 0.0, 0.0, 75.0, 40.0, 30.0, 28.0, 20.0, 0.0, 0.0, 18.0, 13.0, 10.0, 8.0, 6.0, 0.0, 0.0]],\n [\"a2\", [20.0, 50.0, 25.0, 18.0, 15.0, 0.0, 0.0, 24.0, 20.0, 38.0, 30.0, 22.0, 0.0, 0.0, 26.0, 30.0, 63.0, 35.0, 22.0, 0.0, 0.0, 20.0, 18.0, 19.0, 12.0, 10.0, 0.0, 0.0]],\n [\"a3\", [10.0, 12.0, 14.0, 13.0, 18.0, 0.0, 0.0, 15.0, 16.0, 20.0, 19.0, 22.0, 0.0, 0.0, 24.0, 28.0, 30.0, 38.0, 25.0, 0.0, 0.0, 14.0, 20.0, 12.0, 10.0, 8.0, 0.0, 0.0]],\n [\"b1\", [100.0, 45.0, 30.0, 28.0, 26.0, 0.0, 0.0, 75.0, 40.0, 63.0, 48.0, 36.0, 0.0, 0.0, 30.0, 34.0, 28.0, 26.0, 24.0, 0.0, 0.0, 38.0, 22.0, 18.0, 14.0, 12.0, 0.0, 0.0]],\n [\"b2\", [18.0, 20.0, 22.0, 24.0, 25.0, 0.0, 0.0, 26.0, 28.0, 30.0, 32.0, 30.0, 0.0, 0.0, 34.0, 97.0, 40.0, 36.0, 30.0, 0.0, 0.0, 26.0, 24.0, 22.0, 13.0, 10.0, 0.0, 0.0]],\n [\"b3\", [8.0, 10.0, 12.0, 10.0, 9.0, 0.0, 0.0, 12.0, 14.0, 16.0, 18.0, 13.0, 0.0, 0.0, 20.0, 19.0, 16.0, 14.0, 12.0, 0.0, 0.0, 10.0, 9.0, 8.0, 7.0, 6.0, 0.0, 0.0]],\n])));\n\n/** The sheets printed each day from 5 October, across every press. */\nexport const planPrintOutput = e3.input(\"plan_print_output\", ArrayType(FloatType), variant(\"value\", [\n 336000.0, 80000.0, 72000.0, 16000.0, 48000.0, 0.0, 0.0,\n 180000.0, 0.0, 270000.0, 24000.0, 12000.0, 0.0, 0.0,\n 144000.0, 126000.0, 100000.0, 48000.0, 0.0, 0.0, 0.0,\n 90000.0, 24000.0, 30000.0, 24000.0, 0.0, 0.0, 0.0,\n]));\n\n/** A paper stock: its name, and its level in thousands of sheets, a reading a week from 5 October. */\nexport const PrintStock = StructType({ name: StringType, weekly: ArrayType(FloatType) });\n\n/** The paper in stock. */\nexport const planLinkStock = e3.input(\"plan_link_stock\", DictType(StringType, PrintStock), variant(\"value\", new Map([\n [\"board\", { name: \"Board\", weekly: [40.0, 22.0, 31.0, 18.0] }],\n [\"coated\", { name: \"Coated\", weekly: [520.0, 410.0, 460.0, 380.0] }],\n [\"uncoated\", { name: \"Uncoated\", weekly: [300.0, 260.0, 280.0, 240.0] }],\n])));\n\n/** A work centre a job passes through on its way out: the platesetter, the bindery's folder and binder, the dispatch bay. */\nexport const PrintCentre = StructType({ name: StringType });\n\n/** A set of plates the platesetter makes for a job, at the moment it makes them. */\nexport const PrintPlates = StructType({ title: StringType, at: DateTimeType, setter: StringType });\n\n/** A bindery job — folding a press job's sections, or binding them — on the folder or the binder. */\nexport const PrintBinding = StructType({ title: StringType, start: DateTimeType, end: DateTimeType, line: StringType, state: EventStateType });\n\n/** The platesetter. A Plan never writes its resources. */\nexport const planLinkSetters = e3.record(\"plan_link_setters\", DictType(StringType, PrintCentre), new Map([\n [\"ps\", { name: \"Platesetter\" }],\n]));\n\n/** The bindery's two lines. */\nexport const planLinkLines = e3.record(\"plan_link_lines\", DictType(StringType, PrintCentre), new Map([\n [\"fold\", { name: \"Folder\" }],\n [\"bind\", { name: \"Binder\" }],\n]));\n\n/** The dispatch bay. */\nexport const planLinkBays = e3.record(\"plan_link_bays\", DictType(StringType, PrintCentre), new Map([\n [\"bay\", { name: \"Dispatch bay\" }],\n]));\n\n/** The parent of the shared folder and binder, held in a record like every other resource. */\nexport const planPrintBindery = e3.record(\"plan_print_bindery\", DictType(StringType, PrintCentre), new Map([\n [\"bindery\", { name: \"Bindery\" }],\n]));\n\n/** The vans a delivery can move onto from the dispatch bay. */\nexport const planPrintVans = e3.record(\"plan_print_vans\", DictType(StringType, PrintCentre), new Map([\n [\"van1\", { name: \"Van 1\" }], [\"van2\", { name: \"Van 2\" }],\n]));\n\n/** A delivery's own status, separate from the schedule's lifecycle. */\nexport const PrintDispatchStatus = VariantType({ queued: NullType, ready: NullType, dispatched: NullType });\n\n/** A delivery on a bay or a van: the variant preserves the resource kind when it moves. */\nexport const PrintDispatch = StructType({\n title: StringType, at: DateTimeType,\n location: VariantType({ bay: StringType, van: StringType }),\n lane: StringType, status: PrintDispatchStatus, pallets: FloatType,\n});\n\n/** The print works' own plate orders, timed before its existing jobs. */\nexport const planPrintPlates = e3.record(\"plan_print_plates\", DictType(StringType, PrintPlates), new Map([\n [\"P-101\", { title: \"Handbook plates\", at: new Date(\"2026-10-16T14:00:00Z\"), setter: \"ps\" }],\n [\"P-102\", { title: \"Poster plates\", at: new Date(\"2026-10-13T14:00:00Z\"), setter: \"ps\" }],\n]));\n\n/** The print works' plates commit through their own patch door. */\nexport const planPrintPlatesPatch = e3.mutation.patch(planPrintPlates);\n\n/** Two foldings run in parallel, followed by binding, after the handbook leaves Press A1. */\nexport const planPrintBindings = e3.record(\"plan_print_bindings\", DictType(StringType, PrintBinding), new Map([\n [\"B-101\", { title: \"Fold handbook sections\", start: new Date(\"2026-10-20T06:00:00Z\"), end: new Date(\"2026-10-20T14:00:00Z\"), line: \"fold\", state: variant(\"confirmed\", null) }],\n [\"B-102\", { title: \"Fold handbook inserts\", start: new Date(\"2026-10-20T10:00:00Z\"), end: new Date(\"2026-10-20T16:00:00Z\"), line: \"fold\", state: variant(\"confirmed\", null) }],\n [\"B-103\", { title: \"Bind handbooks\", start: new Date(\"2026-10-21T06:00:00Z\"), end: new Date(\"2026-10-21T14:00:00Z\"), line: \"bind\", state: variant(\"confirmed\", null) }],\n]));\n\n/** The print works' bindery jobs commit through their own patch door. */\nexport const planPrintBindingsPatch = e3.mutation.patch(planPrintBindings);\n\n/** Deliveries occupy AM/PM lanes and carry each of the dispatch statuses. */\nexport const planPrintDeliveries = e3.record(\"plan_print_deliveries\", DictType(StringType, PrintDispatch), new Map([\n [\"D-101\", { title: \"Handbooks\", at: new Date(\"2026-10-22T10:00:00Z\"), location: variant(\"bay\", \"bay\"), lane: \"AM\", status: variant(\"queued\", null), pallets: 3.0 }],\n [\"D-102\", { title: \"Event posters\", at: new Date(\"2026-10-15T14:00:00Z\"), location: variant(\"van\", \"van1\"), lane: \"PM\", status: variant(\"ready\", null), pallets: 2.0 }],\n [\"D-103\", { title: \"Museum guides\", at: new Date(\"2026-10-09T10:00:00Z\"), location: variant(\"van\", \"van2\"), lane: \"AM\", status: variant(\"dispatched\", null), pallets: 1.0 }],\n]));\n\n/** The print works' deliveries commit through their own patch door. */\nexport const planPrintDeliveriesPatch = e3.mutation.patch(planPrintDeliveries);\n\n/** A job template: its name and group, how long the job it creates runs, and that job's fields. */\nexport const PrintJobTemplate = StructType({\n name: StringType, group: StringType, hours: FloatType,\n values: StructType({ title: StringType, state: EventStateType, sheets: FloatType, customer: StringType, stock: PrintJob.fields.stock, due: OptionType(DateTimeType) }),\n});\n\n/** A stop template: a stop is one instant, so it runs no time. */\nexport const PrintStopTemplate = StructType({ name: StringType, group: StringType, values: StructType({ title: StringType, kind: PrintStop.fields.kind }) });\n\n/** A shift template: the hour a shift dropped on a day starts at, eight hours long. */\nexport const PrintShiftTemplate = StructType({ name: StringType, group: StringType, hour: IntegerType, values: StructType({ title: StringType, state: EventStateType }) });\n\n/** A plates template: plates are made at one instant. */\nexport const PrintPlatesTemplate = StructType({ name: StringType, group: StringType, values: StructType({ title: StringType }) });\n\n/** A bindery template: how long the bindery job it creates runs. */\nexport const PrintBindingTemplate = StructType({ name: StringType, group: StringType, hours: FloatType, values: StructType({ title: StringType, state: EventStateType }) });\n\n/** A delivery template: the hour a delivery dropped on a day leaves at. */\nexport const PrintDeliveryTemplate = StructType({\n name: StringType, group: StringType, hour: IntegerType,\n values: StructType({ title: StringType, lane: StringType, status: PrintDispatchStatus, pallets: FloatType }),\n});\n\n/** The job templates. A Plan never writes them: the library lists them, and a drop creates a job from one. */\nexport const planPrintJobTemplates = e3.record(\"plan_print_job_templates\", DictType(StringType, PrintJobTemplate), new Map([\n [\"board\", { name: \"Board run\", group: \"Jobs\", hours: 8.0, values: { title: \"Board run\", state: variant(\"proposed\", variant(\"added\", null)), sheets: 24000.0, customer: \"\", stock: variant(\"board\", null), due: none } }],\n [\"brochure\", { name: \"Brochure run\", group: \"Jobs\", hours: 6.0, values: { title: \"Brochure run\", state: variant(\"proposed\", variant(\"added\", null)), sheets: 40000.0, customer: \"\", stock: variant(\"coated\", null), due: none } }],\n [\"catalogue\", { name: \"Catalogue run\", group: \"Jobs\", hours: 12.0, values: { title: \"Catalogue run\", state: variant(\"proposed\", variant(\"added\", null)), sheets: 96000.0, customer: \"\", stock: variant(\"coated\", null), due: none } }],\n [\"poster\", { name: \"Poster run\", group: \"Jobs\", hours: 4.0, values: { title: \"Poster run\", state: variant(\"proposed\", variant(\"added\", null)), sheets: 20000.0, customer: \"\", stock: variant(\"uncoated\", null), due: none } }],\n]));\n\n/** The stop templates. */\nexport const planPrintStopTemplates = e3.record(\"plan_print_stop_templates\", DictType(StringType, PrintStopTemplate), new Map([\n [\"plates\", { name: \"Plate change\", group: \"Stops\", values: { title: \"Plate change\", kind: variant(\"plate_change\", null) } }],\n [\"service\", { name: \"Service\", group: \"Stops\", values: { title: \"Service\", kind: variant(\"service\", null) } }],\n]));\n\n/** The shift templates: early, late and night. */\nexport const planPrintShiftTemplates = e3.record(\"plan_print_shift_templates\", DictType(StringType, PrintShiftTemplate), new Map([\n [\"early\", { name: \"Early shift\", group: \"Shifts\", hour: 6n, values: { title: \"Early\", state: variant(\"confirmed\", null) } }],\n [\"late\", { name: \"Late shift\", group: \"Shifts\", hour: 14n, values: { title: \"Late\", state: variant(\"confirmed\", null) } }],\n [\"night\", { name: \"Night shift\", group: \"Shifts\", hour: 22n, values: { title: \"Night\", state: variant(\"proposed\", variant(\"added\", null)) } }],\n]));\n\n/** The plates templates. */\nexport const planPrintPlatesTemplates = e3.record(\"plan_print_plates_templates\", DictType(StringType, PrintPlatesTemplate), new Map([\n [\"plates\", { name: \"Job plates\", group: \"Plate room\", values: { title: \"Job plates\" } }],\n]));\n\n/** The bindery templates. */\nexport const planPrintBindingTemplates = e3.record(\"plan_print_binding_templates\", DictType(StringType, PrintBindingTemplate), new Map([\n [\"fold\", { name: \"Fold sections\", group: \"Bindery\", hours: 8.0, values: { title: \"Fold sections\", state: variant(\"confirmed\", null) } }],\n]));\n\n/** The delivery templates. */\nexport const planPrintDeliveryTemplates = e3.record(\"plan_print_delivery_templates\", DictType(StringType, PrintDeliveryTemplate), new Map([\n [\"delivery\", { name: \"Morning delivery\", group: \"Dispatch\", hour: 10n, values: { title: \"Delivery\", lane: \"AM\", status: variant(\"queued\", null), pallets: 1.0 } }],\n]));\n\nexport const planPrintJobsByTime = e3.recordIndex.interval(\"plan_print_jobs_by_time\", planPrintJobs, { from: \"start\", to: \"end\" });\n\nexport const planPrintJobsByStockTime = e3.recordIndex.interval(\"plan_print_jobs_by_stock_time\", planPrintJobs, { on: [\"stock\"], from: \"start\", to: \"end\" });\n\nexport const planPrintShiftsByTime = e3.recordIndex.interval(\"plan_print_shifts_by_time\", planPrintShifts, { from: \"start\", to: \"end\" });\n\nexport const planPrintPressesByHall = e3.recordIndex(\"plan_print_presses_by_hall\", planPrintPresses, { on: [\"hall\"] });\n\nexport const planPrintCustomersByDistrict = e3.recordIndex(\"plan_print_customers_by_district\", planPrintCustomers, { on: [\"district\"] });\n\nexport const planPrintJobTemplatesByGroup = e3.recordIndex(\"plan_print_job_templates_by_group\", planPrintJobTemplates, { on: [\"group\"] });\n\nexport const planPrintStopTemplatesByGroup = e3.recordIndex(\"plan_print_stop_templates_by_group\", planPrintStopTemplates, { on: [\"group\"] });\n\nexport const planPrintShiftTemplatesByGroup = e3.recordIndex(\"plan_print_shift_templates_by_group\", planPrintShiftTemplates, { on: [\"group\"] });\n\nexport const planPrintPlatesTemplatesByGroup = e3.recordIndex(\"plan_print_plates_templates_by_group\", planPrintPlatesTemplates, { on: [\"group\"] });\n\nexport const planPrintBindingTemplatesByGroup = e3.recordIndex(\"plan_print_binding_templates_by_group\", planPrintBindingTemplates, { on: [\"group\"] });\n\nexport const planPrintDeliveryTemplatesByGroup = e3.recordIndex(\"plan_print_delivery_templates_by_group\", planPrintDeliveryTemplates, { on: [\"group\"] });\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n // What the Plan reads whole binds with Data.bind, which keeps it loaded beside its pages; the event\n // records it writes bind with Record.bind, and it reads the jobs' and the shifts' events through their\n // pages alone.\n const presses = $.let(Data.bind(planPrintPresses));\n const crews = $.let(Record.bind(planPrintCrews, []));\n const jobs = $.let(Record.bind(planPrintJobs, [planPrintJobsPatch]));\n const stops = $.let(Record.bind(planPrintStops, [planPrintStopsPatch]));\n const shifts = $.let(Record.bind(planPrintShifts, [planPrintShiftsPatch]));\n const customers = $.let(Data.bind(planPrintCustomers));\n const jobTemplates = $.let(Data.bind(planPrintJobTemplates));\n const stopTemplates = $.let(Data.bind(planPrintStopTemplates));\n const shiftTemplates = $.let(Data.bind(planPrintShiftTemplates));\n const platesTemplates = $.let(Data.bind(planPrintPlatesTemplates));\n const bindingTemplates = $.let(Data.bind(planPrintBindingTemplates));\n const deliveryTemplates = $.let(Data.bind(planPrintDeliveryTemplates));\n // One rail over the records, a source each (#1344): the jobs and the shifts through their interval\n // indexes, their range the one window the toolbar's dates, the resolution and the brush write; the\n // presses, the customers and each templates record by its fields.\n const slice = $.let(Slice.bindRecords(\"ex.planPrintWorks\", {\n job: { source: Data.bindPaged(planPrintJobs, { indexes: [planPrintJobsByTime, planPrintJobsByStockTime] }), icon: { prefix: \"fas\", name: \"file-lines\" },\n fields: { stock: { label: \"Stock\" }, start: { label: \"Dates\" } }, range: \"start\" },\n shift: { source: Data.bindPaged(planPrintShifts, { indexes: [planPrintShiftsByTime] }), icon: { prefix: \"fas\", name: \"user-clock\" },\n fields: { start: { label: \"Dates\" } }, range: \"start\" },\n presses: { source: Data.bindPaged(planPrintPresses, { indexes: [planPrintPressesByHall] }), icon: { prefix: \"fas\", name: \"print\" },\n fields: { hall: { label: \"Hall\", hints: [\"Hall A\", \"Hall B\"] } } },\n customers: { source: Data.bindPaged(planPrintCustomers, { indexes: [planPrintCustomersByDistrict] }), icon: { prefix: \"fas\", name: \"building\" },\n fields: { district: { label: \"District\", hints: [\"North Quay\", \"Old Town\", \"Riverside\"] } } },\n jobTemplates: { source: Data.bindPaged(planPrintJobTemplates, { indexes: [planPrintJobTemplatesByGroup] }), icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Job templates\", hints: [\"Jobs\"] } } },\n stopTemplates: { source: Data.bindPaged(planPrintStopTemplates, { indexes: [planPrintStopTemplatesByGroup] }), icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Stop templates\", hints: [\"Stops\"] } } },\n shiftTemplates: { source: Data.bindPaged(planPrintShiftTemplates, { indexes: [planPrintShiftTemplatesByGroup] }), icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Shift templates\", hints: [\"Shifts\"] } } },\n platesTemplates: { source: Data.bindPaged(planPrintPlatesTemplates, { indexes: [planPrintPlatesTemplatesByGroup] }), icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Plates templates\", hints: [\"Plate room\"] } } },\n bindingTemplates: { source: Data.bindPaged(planPrintBindingTemplates, { indexes: [planPrintBindingTemplatesByGroup] }), icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Bindery templates\", hints: [\"Bindery\"] } } },\n deliveryTemplates: { source: Data.bindPaged(planPrintDeliveryTemplates, { indexes: [planPrintDeliveryTemplatesByGroup] }), icon: { prefix: \"fas\", name: \"layer-group\" },\n fields: { group: { label: \"Delivery templates\", hints: [\"Dispatch\"] } } },\n }));\n // A kind narrowed through its source reads one event by its key through the record's own entries.\n const jobEntries = $.let(Data.bindPaged(planPrintJobs));\n const shiftEntries = $.let(Data.bindPaged(planPrintShifts));\n const setters = $.let(Record.bind(planLinkSetters, []));\n const lines = $.let(Record.bind(planLinkLines, []));\n const bindery = $.let(Record.bind(planPrintBindery, []));\n const bays = $.let(Record.bind(planLinkBays, []));\n const vans = $.let(Record.bind(planPrintVans, []));\n const plates = $.let(Record.bind(planPrintPlates, [planPrintPlatesPatch]));\n const bindings = $.let(Record.bind(planPrintBindings, [planPrintBindingsPatch]));\n const deliveries = $.let(Record.bind(planPrintDeliveries, [planPrintDeliveriesPatch]));\n const stock = $.let(Data.bind(planLinkStock));\n const lineRows = $.const(lines.read().union(bindery.read(), (_$2, line) => line));\n const util = $.let(Data.bind(planPrintUtilisation));\n const output = $.let(Data.bind(planPrintOutput));\n // The window's first day: each reading runs from it, a day apiece.\n const first = $.const(new Date(\"2026-10-05T00:00:00Z\"), DateTimeType);\n const parentLine = $.const(some(\"bindery\"), OptionType(StringType));\n const topLine = $.const(none, OptionType(StringType));\n // A press's readings as heat cells, one a day.\n const daily = $.const(East.function([ArrayType(FloatType)], ArrayType(Plan.Types.HeatCell), (_$2, readings) =>\n East.Array.generate(readings.size(), Plan.Types.HeatCell, (_$3, i) => ({\n at: Plan.at.time(first.addDays(i)), value: some(readings.get(i)), label: none,\n }))));\n // The sheets printed each day, as a chart's points.\n const PrintDay = StructType({ day: DateTimeType, sheets: FloatType });\n const printed = $.let(output.read());\n const days = $.let(East.Array.generate(printed.size(), PrintDay, (_$2, i) => ({ day: first.addDays(i), sheets: printed.get(i) })));\n const axis = $.let(Plan.axis({\n window: { min: new Date(\"2026-10-05T00:00:00Z\"), max: new Date(\"2026-11-02T00:00:00Z\") },\n resolution: \"day\", resolutions: [\"week\", \"day\"], now: new Date(\"2026-10-14T09:00:00Z\"),\n }));\n const StockWeek = StructType({ at: DateTimeType, value: OptionType(FloatType) });\n const weekly = $.const(East.function([ArrayType(FloatType)], ArrayType(StockWeek), (_$2, readings) =>\n East.Array.generate(readings.size(), StockWeek, (_$3, i) => ({ at: first.addWeeks(i), value: some(readings.get(i)) }))));\n const canDrop = $.const(East.function([Schedule.Types.Candidate], OptionType(StringType), ($2, candidate) => {\n const reason = $2.let(none, OptionType(StringType));\n $2.match(candidate.resource, { some: ($3, resource) => {\n $3.if(resource.kind.equal(\"presses\").and(() => resource.key.equal(\"b3\")), $4 => {\n $4.assign(reason, some(\"Press B3 is in service; choose another press\"));\n });\n } });\n return reason;\n }));\n // A stop's own inspector: its title, and whether it is a plate change or a\n // service, chosen on a segment — the edited stop goes back through `update`.\n const StopKindType = PrintStop.fields.kind;\n const inspectStop = $.const(East.function([PrintStop, FunctionType([PrintStop], NullType)], UIComponentType, ($2, stop, update) => {\n const setKind = $2.const(East.function([StringType], NullType, ($3, picked) => {\n const kind = $3.const(picked.equal(\"service\").ifElse(\n () => East.value(variant(\"service\", null), StopKindType),\n () => East.value(variant(\"plate_change\", null), StopKindType)));\n // East has no struct spread: the stop, rebuilt with its new kind.\n $3(update({ title: stop.title, at: stop.at, press: stop.press, kind }));\n }));\n return (\n <VStack gap=\"3\" align=\"stretch\">\n <Text>{stop.title}</Text>\n <SegmentGroup value={stop.kind.getTag()} onChange={setKind} size=\"sm\"\n items={[SegmentGroup.Item(\"plate_change\", \"Plate change\"), SegmentGroup.Item(\"service\", \"Service\")]} />\n </VStack>\n );\n }));\n return (\n <Plan\n id=\"printWorks\"\n axis={axis}\n date={first}\n canDrop={canDrop}\n slice={{ slice }}\n resources={{\n presses: Schedule.resources(presses.read(), {\n name: \"Presses\", icon: \"print\",\n label: p => p.name, group: p => p.hall,\n sub: p => some(East.str`${East.Float.printCommaSeperated(p.sheets_per_hour, 0n)} sheets/h`),\n // A row under each press, from the dataflow's utilisation: an ordinary\n // heat series over the press rows, read only.\n measures: [\n Plan.series.heat(PrintPress, {\n key: \"util\", title: \"Utilisation\", label: () => \"Utilisation\",\n cells: (_p, key) => Plan.heatCells(daily(util.read().get(key)), { min: 0, max: 100, warnAt: 95 }),\n }),\n ],\n }),\n crews: Schedule.resources(crews.read(), { name: \"Crews\", icon: \"user-group\", label: c => c.name, group: c => c.hall }),\n setters: Schedule.resources(setters.read(), { name: \"Plate room\", icon: \"layer-group\", label: p => p.name, meta: () => some(\"Plates before printing\") }),\n lines: Schedule.resources(lineRows, {\n name: \"Bindery\", icon: \"book\", label: l => l.name,\n parent: (_l, key) => key.equal(\"bindery\").ifElse(() => topLine, () => parentLine),\n rollup: \"union\", collapsed: (_l, key) => key.equal(\"bindery\"),\n meta: () => some(\"Finishing\"), value: (_l, key) => key.equal(\"bindery\").ifElse(() => some(\"2 lines\"), () => topLine),\n }),\n bays: Schedule.resources(bays.read(), { name: \"Dispatch\", icon: \"warehouse\", label: b => b.name, meta: () => some(\"Loading\") }),\n vans: Schedule.resources(vans.read(), { name: \"Vans\", icon: \"truck\", label: v => v.name, meta: () => some(\"Delivery fleet\") }),\n }}\n events={{\n job: Schedule.events(jobs, {\n name: \"Print job\", icon: \"file-lines\", draw: \"span\",\n title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"press\", of: \"presses\" },\n state: \"state\",\n quantity: { field: \"sheets\", unit: \"sheets\" },\n backlog: { duration: j => variant(\"hours\", j.sheets.divide(8000.0)), due: j => j.due },\n fields: {\n customer: Schedule.field.text({ label: \"Customer\" }),\n stock: Schedule.field.select({ labels: { coated: \"Coated\", uncoated: \"Uncoated\", board: \"Board\" } }),\n sheets: Schedule.field.number({ label: \"Sheets\", step: 1000.0, min: 0.0 }),\n },\n // Narrowed through its source of the slice (`job`): one job is read by its key here.\n entries: jobEntries,\n templates: Schedule.templates(jobTemplates.read(), {\n name: t => t.name, group: t => some(t.group), duration: t => variant(\"hours\", t.hours), values: t => t.values, source: \"jobTemplates\",\n }),\n }),\n stop: Schedule.events(stops, {\n name: \"Stop\", icon: \"screwdriver-wrench\", draw: \"marks\",\n title: \"title\", at: \"at\",\n resource: { field: \"press\", of: \"presses\" },\n inspector: inspectStop,\n // A stop is one instant: its templates give no duration.\n templates: Schedule.templates(stopTemplates.read(), { name: t => t.name, group: t => some(t.group), values: t => t.values, source: \"stopTemplates\" }),\n }),\n shift: Schedule.events(shifts, {\n name: \"Crew shift\", icon: \"user-clock\", draw: \"cards\",\n title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"crew\", of: \"crews\" }, state: \"state\",\n entries: shiftEntries,\n templates: Schedule.templates(shiftTemplates.read(), {\n name: t => t.name, group: t => some(t.group), at: t => some({ hour: t.hour, minute: 0n }), duration: () => variant(\"hours\", 8.0),\n values: t => t.values, source: \"shiftTemplates\",\n }),\n }),\n plate: Schedule.events(plates, {\n name: \"Plates\", icon: \"clone\", title: \"title\", at: \"at\", resource: { field: \"setter\", of: \"setters\" },\n templates: Schedule.templates(platesTemplates.read(), { name: t => t.name, group: t => some(t.group), values: t => t.values, source: \"platesTemplates\" }),\n }),\n binding: Schedule.events(bindings, {\n name: \"Bindery job\", icon: \"book-open\", draw: \"cards\", title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"line\", of: \"lines\" }, state: \"state\", overlaps: \"allow\",\n templates: Schedule.templates(bindingTemplates.read(), {\n name: t => t.name, group: t => some(t.group), duration: t => variant(\"hours\", t.hours), values: t => t.values, source: \"bindingTemplates\",\n }),\n }),\n delivery: Schedule.events(deliveries, {\n name: \"Delivery\", icon: \"truck\", draw: \"buckets\", title: \"title\", at: \"at\",\n resource: { field: \"location\", of: { bay: \"bays\", van: \"vans\" } }, lane: \"lane\",\n status: { field: \"status\", cases: {\n queued: { label: \"Queued\", tone: variant(\"warning\", null), ring: true },\n ready: { label: \"Ready\", tone: variant(\"info\", null), ring: false },\n dispatched: { label: \"Dispatched\", tone: variant(\"success\", null), ring: false },\n } },\n ready: d => d.pallets.lessEqual(0.0).ifElse(\n () => variant(\"invalid\", [{ field: \"pallets\", message: \"Pallets must be positive\" }]),\n () => variant(\"ready\", null)),\n fields: { pallets: Schedule.field.number({ label: \"Pallets\", min: 0.0, step: 1.0 }) },\n templates: Schedule.templates(deliveryTemplates.read(), {\n name: t => t.name, group: t => some(t.group), at: t => some({ hour: t.hour, minute: 0n }), values: t => t.values, source: \"deliveryTemplates\",\n }),\n }),\n }}\n rows={[\n Plan.chart({\n key: \"output\", label: \"SHEETS / DAY\", id: true, pinned: true, height: \"spark\", expandable: true,\n layers: [Chart.Column(days, { x: r => r.day, y: r => r.sheets })],\n }),\n Plan.over(stock, [Plan.series.table(PrintStock, {\n key: \"stock\", title: \"Paper stock\", label: s => s.name,\n cells: s => Plan.tableCells(weekly(s.weekly)), format: Format.Number({ maximumFractionDigits: 0n }),\n })]),\n ]}\n links={[\n Plan.link({ key: \"plates\", from: Plan.eventRef(\"plate\", \"P-101\"), to: Plan.eventRef(\"job\", \"J-1004\") }),\n Plan.link({ key: \"printed\", from: Plan.eventRef(\"job\", \"J-1004\"), to: Plan.eventRef(\"binding\", \"B-101\"), quantity: Plan.quantity(144, { unit: \"k sheets\" }) }),\n Plan.link({ key: \"folded\", from: Plan.eventRef(\"binding\", \"B-101\"), to: Plan.eventRef(\"binding\", \"B-103\") }),\n Plan.link({ key: \"dispatched\", from: Plan.eventRef(\"binding\", \"B-103\"), to: Plan.eventRef(\"delivery\", \"D-101\") }),\n ]}\n grain=\"resource\"\n library={[\n Plan.library.events(),\n Plan.library.backlog(),\n Plan.library.series(),\n // The customers, by district: a card dropped on a job sets its customer. The tab follows the\n // rail's `customers` source, by its rows' keys.\n Plan.library.tab(customers.read(), {\n name: \"Customers\", icon: \"building\",\n label: c => c.name, meta: c => some(c.trade), group: c => c.district,\n drop: c => Schedule.patch(PrintJob, { customer: c.name }), source: \"customers\",\n }),\n ]}\n inspector\n />\n );\n }}</Reactive>\n ))"
|
|
42292
42175
|
},
|
|
42293
42176
|
{
|
|
42294
42177
|
"id": "e3-ui:plan-events.examples.tsx:planEventLinks",
|
|
@@ -42361,31 +42244,52 @@
|
|
|
42361
42244
|
"package": "e3-ui",
|
|
42362
42245
|
"file": "east-ui/packages/e3-ui/test/plan/plan-records.examples.tsx",
|
|
42363
42246
|
"suite": "",
|
|
42364
|
-
"test": "The Sheet's 50,000
|
|
42247
|
+
"test": "The Sheet's 50,000 work orders on a Plan — a paged resource kind narrowed through the rail's `orders` source over their indexes: Status with a Due range, Job, Quantity, Urgent and Tag, the presets and the search, Due a field of the rail the viewer sets — and their operations a record of their own placed on them, read a cell at a time: the `operation` source lists their interval, series and summary indexes, its range the canvas's window, so the orders' rows stay as the dates move and only the cells around the rows in view are read; a moved operation is a draft of its kind's session, saved through the operations' patch door by its key",
|
|
42365
42248
|
"keywords": [
|
|
42366
42249
|
"Plan",
|
|
42250
|
+
"Schedule",
|
|
42251
|
+
"Schedule.resources",
|
|
42252
|
+
"Schedule.events",
|
|
42367
42253
|
"Slice",
|
|
42368
|
-
"bindRecords",
|
|
42369
|
-
"
|
|
42370
|
-
"
|
|
42371
|
-
"
|
|
42372
|
-
"
|
|
42373
|
-
"
|
|
42254
|
+
"Slice.bindRecords",
|
|
42255
|
+
"source",
|
|
42256
|
+
"rail",
|
|
42257
|
+
"narrow",
|
|
42258
|
+
"resource kind",
|
|
42259
|
+
"paged",
|
|
42260
|
+
"Data.bindPaged",
|
|
42261
|
+
"e3.recordIndex",
|
|
42262
|
+
"interval index",
|
|
42263
|
+
"series index",
|
|
42264
|
+
"summary index",
|
|
42265
|
+
"e3.recordIndex.interval",
|
|
42266
|
+
"e3.recordIndex.series",
|
|
42267
|
+
"e3.recordIndex.summary",
|
|
42268
|
+
"cell",
|
|
42269
|
+
"window",
|
|
42374
42270
|
"range",
|
|
42375
|
-
"
|
|
42376
|
-
"
|
|
42271
|
+
"large record",
|
|
42272
|
+
"million rows",
|
|
42273
|
+
"work orders",
|
|
42274
|
+
"operations",
|
|
42275
|
+
"Record.bind",
|
|
42276
|
+
"entries",
|
|
42277
|
+
"editing",
|
|
42278
|
+
"Save",
|
|
42279
|
+
"#1344",
|
|
42280
|
+
"#1039"
|
|
42377
42281
|
],
|
|
42378
42282
|
"imports": [
|
|
42379
|
-
"import { ArrayType, DateTimeType, DictType, East, FloatType, IntegerType, OptionType, StringType, StructType, example, none, some } from \"@elaraai/east\";",
|
|
42283
|
+
"import { ArrayType, DateTimeType, DictType, East, FloatType, IntegerType, OptionType, StringType, StructType, example, none, some, variant } from \"@elaraai/east\";",
|
|
42380
42284
|
"import { Box, Reactive, Slice, UIComponentType } from \"@elaraai/east-ui\";",
|
|
42381
|
-
"import { Data, Plan, Record } from \"@elaraai/e3-ui\";",
|
|
42285
|
+
"import { Data, Plan, Record, Schedule } from \"@elaraai/e3-ui\";",
|
|
42382
42286
|
"import e3 from \"@elaraai/e3\";",
|
|
42383
|
-
"import {
|
|
42287
|
+
"import {"
|
|
42384
42288
|
],
|
|
42385
42289
|
"languages": [
|
|
42386
42290
|
"tsx"
|
|
42387
42291
|
],
|
|
42388
|
-
"source": "// The Sheet's 50,000
|
|
42292
|
+
"source": "// The Sheet's 50,000 work orders on a Plan — a paged resource kind narrowed through the rail's `orders` source over their indexes: Status with a Due range, Job, Quantity, Urgent and Tag, the presets and the search, Due a field of the rail the viewer sets — and their operations a record of their own placed on them, read a cell at a time: the `operation` source lists their interval, series and summary indexes, its range the canvas's window, so the orders' rows stay as the dates move and only the cells around the rows in view are read; a moved operation is a draft of its kind's session, saved through the operations' patch door by its key\n// inputs: []\n/** An operation on a work order: the order it is on, by its key, the work it does, and when it runs. */\nexport const PlanOperation = StructType({ work: StringType, label: StringType, start: DateTimeType, end: DateTimeType });\n\nconst Operations = DictType(StringType, PlanOperation);\n\n/**\n * The work orders' operations, from their count: each of the first `count` orders' run of its job on the day it is\n * due, 06:00 to 14:00, keyed by its order's reference and `-run`. The same pure generator seeds the record and runs as\n * an e3 task.\n */\nconst generateOperations = East.function([IntegerType], Operations, ($, count) => {\n const rows = $.let(new Map(), Operations);\n const referenceOf = $.const(indexedWorkReference);\n const jobOf = $.const(indexedWorkJob);\n const dueOf = $.const(indexedWorkDue);\n const i = $.let(0n);\n $.while(i.less(count), ($2) => {\n const work = $2.const(referenceOf(i));\n const due = $2.const(dueOf(i));\n $2(rows.insert(East.str`${work}-run`, { work, label: jobOf(i), start: due.addMilliseconds(21600000n), end: due.addMilliseconds(50400000n) }));\n $2.assign(i, i.add(1n));\n });\n return rows;\n});\n\n/** The 50,000 work orders' operations, one an order, keyed by the operation's key: read through the indexes below, never whole. */\nexport const planOperations = e3.record(\"plan_operations\", Operations, East.compile(generateOperations, [])(50000n));\n\n/** The operations' patch door: a canvas's Save commits through it, by the operation's key. */\nexport const planOperationsPatch = e3.mutation.patch(planOperations);\n\n/** The operations over the interval each runs: the canvas's window, and the rail's exact count of the operations in it. */\nexport const planOperationsByTime = e3.recordIndex.interval(\"plan_operations_by_time\", planOperations, { from: \"start\", to: \"end\" });\n\n/** The operations by their work order and the weeks they touch: a cell is the operations of fifty orders in one week. */\nexport const planOperationsByWorkWeek = e3.recordIndex.series(\"plan_operations_by_work_week\", planOperations, {\n key: \"work\", from: \"start\", to: \"end\", tile: \"week\",\n});\n\n/** How busy each work order is, a month at a time: what the canvas shades where the operations in view are too many to draw. */\nexport const planOperationsByWorkMonth = e3.recordIndex.summary(\"plan_operations_by_work_month\", planOperations, {\n key: \"work\", from: \"start\", to: \"end\", tile: \"month\",\n});\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const orders = $.let(Data.bindPaged(indexedWorkOrders));\n const operations = $.let(Record.bind(planOperations, [planOperationsPatch]));\n const operationEntries = $.let(Data.bindPaged(planOperations));\n // One rail. Its `orders` source narrows the paged work orders through their indexes, and the canvas pages the\n // narrowing's orders, their Due a field of the rail the viewer sets. Its `operation` source reads the\n // operations a cell at a time through their series and summary indexes, its range the canvas's window.\n const slice = $.let(Slice.bindRecords(\"ex.planRecordIndexes\", {\n orders: {\n source: Data.bindPaged(indexedWorkOrders, { indexes: [indexedWorkByStatusDue, indexedWorkByDue, indexedWorkByJob, indexedWorkByQuantity, indexedWorkByUrgent, indexedWorkByTag] }),\n label: \"Work orders\", icon: { prefix: \"fas\", name: \"clipboard-list\" },\n fields: { status: { label: \"Status\", hints: [\"Ready\", \"Scheduled\", \"Done\"] }, due: { label: \"Due\" }, job: { label: \"Job\" }, quantity: { label: \"Quantity\" }, urgent: { label: \"Urgent\" }, tags: { label: \"Tag\", hints: [\"Production\", \"Dispatch\"] } },\n search: [\"job\"], initial: { status: [\"Ready\"], due: { from: new Date(\"2026-10-01T00:00:00Z\"), to: new Date(\"2026-10-15T00:00:00Z\") } },\n },\n operation: {\n source: Data.bindPaged(planOperations, { indexes: [planOperationsByTime, planOperationsByWorkWeek, planOperationsByWorkMonth] }),\n label: \"Operations\", fields: { start: { label: \"Dates\" } }, range: \"start\",\n },\n }, { presets: [{ id: \"ready\", name: \"Ready\", where: { orders: { status: [\"Ready\"] } } }, { id: \"urgent\", name: \"Urgent\", where: { orders: { urgent: [true] } } }] }));\n return <Box width=\"100%\" height=\"760px\"><Plan id=\"planRecordIndexes\" slice={{ slice }}\n axis={Plan.axis({ window: { min: new Date(\"2026-10-01T00:00:00Z\"), max: new Date(\"2026-10-15T00:00:00Z\") }, resolution: \"day\", resolutions: [\"month\", \"week\", \"day\"] })}\n resources={{\n orders: Schedule.resources(orders, { name: \"Work orders\", icon: \"clipboard-list\", label: (w) => w.reference, sub: (w) => some(w.status) }),\n }}\n events={{\n operation: Schedule.events(operations, {\n name: \"Operation\", icon: \"gears\", title: \"label\", start: \"start\", end: \"end\",\n resource: { field: \"work\", of: \"orders\" }, entries: operationEntries,\n }),\n }}\n style={{ height: \"fill\" }} />\n </Box>;\n }}</Reactive>\n ))"
|
|
42389
42293
|
},
|
|
42390
42294
|
{
|
|
42391
42295
|
"id": "e3-ui:plan-records.examples.tsx:planRecordIndexesMobile",
|
|
@@ -42393,27 +42297,39 @@
|
|
|
42393
42297
|
"package": "e3-ui",
|
|
42394
42298
|
"file": "east-ui/packages/e3-ui/test/plan/plan-records.examples.tsx",
|
|
42395
42299
|
"suite": "",
|
|
42396
|
-
"test": "
|
|
42300
|
+
"test": "The work orders Plan of planRecordIndexes in a phone-width frame — the paged work orders narrowed through the same rail on the toolbar's responsive ladder, Due a field of it, and their operations read a cell at a time, their source holding the canvas's window, so the dates never narrow the orders",
|
|
42397
42301
|
"keywords": [
|
|
42398
42302
|
"Plan",
|
|
42303
|
+
"Schedule",
|
|
42304
|
+
"Schedule.resources",
|
|
42305
|
+
"Schedule.events",
|
|
42399
42306
|
"Slice",
|
|
42400
|
-
"bindRecords",
|
|
42401
|
-
"
|
|
42307
|
+
"Slice.bindRecords",
|
|
42308
|
+
"rail",
|
|
42402
42309
|
"mobile",
|
|
42403
|
-
"
|
|
42404
|
-
"
|
|
42310
|
+
"narrow layout",
|
|
42311
|
+
"resource kind",
|
|
42312
|
+
"paged",
|
|
42313
|
+
"Data.bindPaged",
|
|
42314
|
+
"series index",
|
|
42315
|
+
"summary index",
|
|
42316
|
+
"cell",
|
|
42317
|
+
"work orders",
|
|
42318
|
+
"operations",
|
|
42319
|
+
"#1344",
|
|
42320
|
+
"#1039"
|
|
42405
42321
|
],
|
|
42406
42322
|
"imports": [
|
|
42407
|
-
"import { ArrayType, DateTimeType, DictType, East, FloatType, IntegerType, OptionType, StringType, StructType, example, none, some } from \"@elaraai/east\";",
|
|
42323
|
+
"import { ArrayType, DateTimeType, DictType, East, FloatType, IntegerType, OptionType, StringType, StructType, example, none, some, variant } from \"@elaraai/east\";",
|
|
42408
42324
|
"import { Box, Reactive, Slice, UIComponentType } from \"@elaraai/east-ui\";",
|
|
42409
|
-
"import { Data, Plan, Record } from \"@elaraai/e3-ui\";",
|
|
42325
|
+
"import { Data, Plan, Record, Schedule } from \"@elaraai/e3-ui\";",
|
|
42410
42326
|
"import e3 from \"@elaraai/e3\";",
|
|
42411
|
-
"import {
|
|
42327
|
+
"import {"
|
|
42412
42328
|
],
|
|
42413
42329
|
"languages": [
|
|
42414
42330
|
"tsx"
|
|
42415
42331
|
],
|
|
42416
|
-
"source": "//
|
|
42332
|
+
"source": "// The work orders Plan of planRecordIndexes in a phone-width frame — the paged work orders narrowed through the same rail on the toolbar's responsive ladder, Due a field of it, and their operations read a cell at a time, their source holding the canvas's window, so the dates never narrow the orders\n// inputs: []\n/** An operation on a work order: the order it is on, by its key, the work it does, and when it runs. */\nexport const PlanOperation = StructType({ work: StringType, label: StringType, start: DateTimeType, end: DateTimeType });\n\nconst Operations = DictType(StringType, PlanOperation);\n\n/**\n * The work orders' operations, from their count: each of the first `count` orders' run of its job on the day it is\n * due, 06:00 to 14:00, keyed by its order's reference and `-run`. The same pure generator seeds the record and runs as\n * an e3 task.\n */\nconst generateOperations = East.function([IntegerType], Operations, ($, count) => {\n const rows = $.let(new Map(), Operations);\n const referenceOf = $.const(indexedWorkReference);\n const jobOf = $.const(indexedWorkJob);\n const dueOf = $.const(indexedWorkDue);\n const i = $.let(0n);\n $.while(i.less(count), ($2) => {\n const work = $2.const(referenceOf(i));\n const due = $2.const(dueOf(i));\n $2(rows.insert(East.str`${work}-run`, { work, label: jobOf(i), start: due.addMilliseconds(21600000n), end: due.addMilliseconds(50400000n) }));\n $2.assign(i, i.add(1n));\n });\n return rows;\n});\n\n/** The 50,000 work orders' operations, one an order, keyed by the operation's key: read through the indexes below, never whole. */\nexport const planOperations = e3.record(\"plan_operations\", Operations, East.compile(generateOperations, [])(50000n));\n\n/** The operations' patch door: a canvas's Save commits through it, by the operation's key. */\nexport const planOperationsPatch = e3.mutation.patch(planOperations);\n\n/** The operations over the interval each runs: the canvas's window, and the rail's exact count of the operations in it. */\nexport const planOperationsByTime = e3.recordIndex.interval(\"plan_operations_by_time\", planOperations, { from: \"start\", to: \"end\" });\n\n/** The operations by their work order and the weeks they touch: a cell is the operations of fifty orders in one week. */\nexport const planOperationsByWorkWeek = e3.recordIndex.series(\"plan_operations_by_work_week\", planOperations, {\n key: \"work\", from: \"start\", to: \"end\", tile: \"week\",\n});\n\n/** How busy each work order is, a month at a time: what the canvas shades where the operations in view are too many to draw. */\nexport const planOperationsByWorkMonth = e3.recordIndex.summary(\"plan_operations_by_work_month\", planOperations, {\n key: \"work\", from: \"start\", to: \"end\", tile: \"month\",\n});\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const orders = $.let(Data.bindPaged(indexedWorkOrders));\n const operations = $.let(Record.bind(planOperations, [planOperationsPatch]));\n const operationEntries = $.let(Data.bindPaged(planOperations));\n // The rail of planRecordIndexes: the paged work orders by their indexes, and the operations by cells, holding the window.\n const slice = $.let(Slice.bindRecords(\"ex.planRecordIndexesMobile\", {\n orders: {\n source: Data.bindPaged(indexedWorkOrders, { indexes: [indexedWorkByStatusDue, indexedWorkByDue, indexedWorkByJob, indexedWorkByQuantity, indexedWorkByUrgent, indexedWorkByTag] }),\n label: \"Work orders\", icon: { prefix: \"fas\", name: \"clipboard-list\" },\n fields: { status: { label: \"Status\", hints: [\"Ready\", \"Scheduled\", \"Done\"] }, due: { label: \"Due\" }, job: { label: \"Job\" }, quantity: { label: \"Quantity\" }, urgent: { label: \"Urgent\" }, tags: { label: \"Tag\", hints: [\"Production\", \"Dispatch\"] } },\n search: [\"job\"], initial: { status: [\"Ready\"], due: { from: new Date(\"2026-10-01T00:00:00Z\"), to: new Date(\"2026-10-15T00:00:00Z\") } },\n },\n operation: {\n source: Data.bindPaged(planOperations, { indexes: [planOperationsByTime, planOperationsByWorkWeek, planOperationsByWorkMonth] }),\n label: \"Operations\", fields: { start: { label: \"Dates\" } }, range: \"start\",\n },\n }, { presets: [{ id: \"ready\", name: \"Ready\", where: { orders: { status: [\"Ready\"] } } }, { id: \"urgent\", name: \"Urgent\", where: { orders: { urgent: [true] } } }] }));\n return <Box width=\"100%\" maxWidth=\"390px\" height=\"760px\"><Plan id=\"planRecordIndexesMobile\" slice={{ slice }}\n axis={Plan.axis({ window: { min: new Date(\"2026-10-01T00:00:00Z\"), max: new Date(\"2026-10-15T00:00:00Z\") }, resolution: \"day\", resolutions: [\"month\", \"week\", \"day\"] })}\n resources={{\n orders: Schedule.resources(orders, { name: \"Work orders\", icon: \"clipboard-list\", label: (w) => w.reference, sub: (w) => some(w.status) }),\n }}\n events={{\n operation: Schedule.events(operations, {\n name: \"Operation\", icon: \"gears\", title: \"label\", start: \"start\", end: \"end\",\n resource: { field: \"work\", of: \"orders\" }, entries: operationEntries,\n }),\n }}\n style={{ height: \"fill\" }} />\n </Box>;\n }}</Reactive>\n ))"
|
|
42417
42333
|
},
|
|
42418
42334
|
{
|
|
42419
42335
|
"id": "e3-ui:plan-windows.examples.tsx:planWindows",
|
|
@@ -42421,7 +42337,7 @@
|
|
|
42421
42337
|
"package": "e3-ui",
|
|
42422
42338
|
"file": "east-ui/packages/e3-ui/test/plan/plan-windows.examples.tsx",
|
|
42423
42339
|
"suite": "",
|
|
42424
|
-
"test": "A Plan read a window at a time — 2,000 presses paged 200 to a window as the canvas scrolls (`Schedule.resources` over `Data.bindPaged`, the key search seeking a press), and the jobs
|
|
42340
|
+
"test": "A Plan read a window at a time — 2,000 presses paged 200 to a window as the canvas scrolls (`Schedule.resources` over `Data.bindPaged`, the key search seeking a press), and the jobs on them read a cell at a time through one Slice.bindRecords rail: its `job` source lists the interval index, a series index (`e3.recordIndex.series`, the jobs by press and week) and a summary index (`e3.recordIndex.summary`, each press's busy share by month), and the Plan reads only the cells around the rows in view, two requests a change, shading from the summary when zoomed out past what it draws — the window, its count and the backlog the interval index's, one job by its key through the record's own entries, and its editing a session over the record's revision, never the record whole",
|
|
42425
42341
|
"keywords": [
|
|
42426
42342
|
"Plan",
|
|
42427
42343
|
"Schedule",
|
|
@@ -42434,7 +42350,15 @@
|
|
|
42434
42350
|
"window",
|
|
42435
42351
|
"interval",
|
|
42436
42352
|
"interval index",
|
|
42353
|
+
"series",
|
|
42354
|
+
"series index",
|
|
42355
|
+
"summary",
|
|
42356
|
+
"summary index",
|
|
42437
42357
|
"e3.recordIndex",
|
|
42358
|
+
"e3.recordIndex.series",
|
|
42359
|
+
"e3.recordIndex.summary",
|
|
42360
|
+
"tile",
|
|
42361
|
+
"cell",
|
|
42438
42362
|
"paged",
|
|
42439
42363
|
"page",
|
|
42440
42364
|
"Data.bindPaged",
|
|
@@ -42444,6 +42368,9 @@
|
|
|
42444
42368
|
"seek",
|
|
42445
42369
|
"scroll",
|
|
42446
42370
|
"pan",
|
|
42371
|
+
"zoom",
|
|
42372
|
+
"resolutions",
|
|
42373
|
+
"shading",
|
|
42447
42374
|
"brush",
|
|
42448
42375
|
"presses",
|
|
42449
42376
|
"jobs",
|
|
@@ -42456,7 +42383,55 @@
|
|
|
42456
42383
|
"editing",
|
|
42457
42384
|
"Save",
|
|
42458
42385
|
"#1199",
|
|
42459
|
-
"#1344"
|
|
42386
|
+
"#1344",
|
|
42387
|
+
"#1039"
|
|
42388
|
+
],
|
|
42389
|
+
"imports": [
|
|
42390
|
+
"import {",
|
|
42391
|
+
"import { Reactive, Slice, UIComponentType } from \"@elaraai/east-ui\";",
|
|
42392
|
+
"import { Data, Plan, Record, Schedule } from \"@elaraai/e3-ui\";",
|
|
42393
|
+
"import e3 from \"@elaraai/e3\";",
|
|
42394
|
+
"import { PrintJob, PrintPress } from \"./plan-events.examples.js\";"
|
|
42395
|
+
],
|
|
42396
|
+
"languages": [
|
|
42397
|
+
"tsx"
|
|
42398
|
+
],
|
|
42399
|
+
"source": "// A Plan read a window at a time — 2,000 presses paged 200 to a window as the canvas scrolls (`Schedule.resources` over `Data.bindPaged`, the key search seeking a press), and the jobs on them read a cell at a time through one Slice.bindRecords rail: its `job` source lists the interval index, a series index (`e3.recordIndex.series`, the jobs by press and week) and a summary index (`e3.recordIndex.summary`, each press's busy share by month), and the Plan reads only the cells around the rows in view, two requests a change, shading from the summary when zoomed out past what it draws — the window, its count and the backlog the interval index's, one job by its key through the record's own entries, and its editing a session over the record's revision, never the record whole\n// inputs: []\n/** How many presses the works runs: a small authored constant, the presses themselves generated when the dataflow runs. */\nexport const planWindowPressCount = e3.input(\"plan_window_press_count\", IntegerType, variant(\"value\", 2_000n));\n\n/** A press's key: `P-` and its number plus 1,000 — fixed width, so key order is number order. */\nconst pressKey = East.function([IntegerType], StringType, (_$, n) => East.str`P-${n.add(1_001n)}`);\n\n/**\n * The presses, generated from their count, by key: each named by its number,\n * in one of eight halls, printing 6,000 to 15,000 sheets an hour.\n */\nexport const generateWindowPresses = East.function([IntegerType], DictType(StringType, PrintPress), (_$, count) =>\n East.Array.range(0n, count).toDict(\n (_$2, n) => pressKey(n),\n (_$2, n) => ({\n name: East.str`Press ${n.add(1_001n)}`,\n hall: East.str`Hall ${n.remainder(8n).add(1n)}`,\n sheets_per_hour: n.remainder(10n).multiply(1_000n).add(6_000n).toFloat(),\n }),\n ));\n\n/** The task that generates the presses. */\nexport const planWindowPresses = e3.task(\"plan_window_presses\", [planWindowPressCount], generateWindowPresses);\n\n/**\n * The print jobs on the presses, from Monday 5 October 2026: a night run into\n * the next day, a run over three days, jobs on presses in each window of 200,\n * jobs in the weeks after the first two, and four waiting in the backlog.\n */\nexport const planWindowJobs = e3.record(\"plan_window_jobs\", DictType(StringType, PrintJob), new Map([\n [\"W-0001\", { title: \"Brochure run\", start: some(new Date(\"2026-10-05T06:00:00Z\")), end: some(new Date(\"2026-10-05T14:00:00Z\")), press: some(\"P-1001\"), state: variant(\"actual\", null), sheets: 40000.0, customer: \"Alder & Finch\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-07T00:00:00Z\")) }],\n [\"W-0002\", { title: \"Night catalogue\", start: some(new Date(\"2026-10-05T22:00:00Z\")), end: some(new Date(\"2026-10-06T06:00:00Z\")), press: some(\"P-1001\"), state: variant(\"actual\", null), sheets: 96000.0, customer: \"Larkspur Home\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-08T00:00:00Z\")) }],\n [\"W-0003\", { title: \"Museum guide\", start: some(new Date(\"2026-10-06T06:00:00Z\")), end: some(new Date(\"2026-10-06T12:00:00Z\")), press: some(\"P-1002\"), state: variant(\"actual\", null), sheets: 72000.0, customer: \"Driftwood Museum\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-09T00:00:00Z\")) }],\n [\"W-0004\", { title: \"Course handbooks\", start: some(new Date(\"2026-10-07T06:00:00Z\")), end: some(new Date(\"2026-10-09T18:00:00Z\")), press: some(\"P-1003\"), state: variant(\"confirmed\", null), sheets: 144000.0, customer: \"Elmway College\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-12T00:00:00Z\")) }],\n [\"W-0005\", { title: \"Event posters\", start: some(new Date(\"2026-10-12T07:00:00Z\")), end: some(new Date(\"2026-10-12T12:00:00Z\")), press: some(\"P-1004\"), state: variant(\"confirmed\", null), sheets: 60000.0, customer: \"Granite Hall\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-13T00:00:00Z\")) }],\n [\"W-0006\", { title: \"Menu cards\", start: some(new Date(\"2026-10-14T06:00:00Z\")), end: some(new Date(\"2026-10-14T08:00:00Z\")), press: some(\"P-1010\"), state: variant(\"in-progress\", null), sheets: 24000.0, customer: \"Copperleaf Cafe\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-15T00:00:00Z\")) }],\n [\"W-0007\", { title: \"Seed catalogue\", start: some(new Date(\"2026-10-15T06:00:00Z\")), end: some(new Date(\"2026-10-15T16:00:00Z\")), press: some(\"P-1150\"), state: variant(\"proposed\", variant(\"added\", null)), sheets: 100000.0, customer: \"Foxglove Gardens\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-17T00:00:00Z\")) }],\n [\"W-0008\", { title: \"Tour brochure\", start: some(new Date(\"2026-10-16T06:00:00Z\")), end: some(new Date(\"2026-10-16T14:00:00Z\")), press: some(\"P-1200\"), state: variant(\"proposed\", variant(\"recommended\", null)), sheets: 80000.0, customer: \"Bluewater Tours\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-19T00:00:00Z\")) }],\n [\"W-0009\", { title: \"Timetables\", start: some(new Date(\"2026-10-08T06:00:00Z\")), end: some(new Date(\"2026-10-08T10:00:00Z\")), press: some(\"P-1201\"), state: variant(\"actual\", null), sheets: 48000.0, customer: \"Bluewater Tours\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-09T00:00:00Z\")) }],\n [\"W-0010\", { title: \"Annual report\", start: some(new Date(\"2026-10-13T06:00:00Z\")), end: some(new Date(\"2026-10-13T12:00:00Z\")), press: some(\"P-1250\"), state: variant(\"confirmed\", null), sheets: 60000.0, customer: \"Harbour Arts Society\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"W-0011\", { title: \"Gift boxes\", start: some(new Date(\"2026-10-09T06:00:00Z\")), end: some(new Date(\"2026-10-09T12:00:00Z\")), press: some(\"P-1333\"), state: variant(\"confirmed\", null), sheets: 48000.0, customer: \"Juniper Toys\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-12T00:00:00Z\")) }],\n [\"W-0012\", { title: \"Store flyers\", start: some(new Date(\"2026-10-14T06:00:00Z\")), end: some(new Date(\"2026-10-14T16:00:00Z\")), press: some(\"P-1401\"), state: variant(\"in-progress\", null), sheets: 150000.0, customer: \"Northwind Outfitters\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"W-0013\", { title: \"Magazine run\", start: some(new Date(\"2026-10-15T06:00:00Z\")), end: some(new Date(\"2026-10-16T18:00:00Z\")), press: some(\"P-1460\"), state: variant(\"proposed\", variant(\"added\", null)), sheets: 180000.0, customer: \"Meridian Monthly\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-19T00:00:00Z\")) }],\n [\"W-0014\", { title: \"Ticket books\", start: some(new Date(\"2026-10-22T06:00:00Z\")), end: some(new Date(\"2026-10-22T08:00:00Z\")), press: some(\"P-1001\"), state: variant(\"estimated\", null), sheets: 24000.0, customer: \"Heathfield Theatre\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-26T00:00:00Z\")) }],\n [\"W-0015\", { title: \"Wall calendars\", start: some(new Date(\"2026-10-26T06:00:00Z\")), end: some(new Date(\"2026-10-26T18:00:00Z\")), press: some(\"P-1002\"), state: variant(\"estimated\", null), sheets: 50000.0, customer: \"Foxglove Gardens\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-30T00:00:00Z\")) }],\n [\"W-0016\", { title: \"Exhibition book\", start: some(new Date(\"2026-10-28T06:00:00Z\")), end: some(new Date(\"2026-10-28T12:00:00Z\")), press: some(\"P-1470\"), state: variant(\"estimated\", null), sheets: 54000.0, customer: \"Driftwood Museum\", stock: variant(\"coated\", null), due: some(new Date(\"2026-11-02T00:00:00Z\")) }],\n [\"W-0017\", { title: \"Order forms\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 40000.0, customer: \"Larkspur Home\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"W-0018\", { title: \"Winter brochure\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 64000.0, customer: \"Bluewater Tours\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-21T00:00:00Z\")) }],\n [\"W-0019\", { title: \"Prospectus\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 100000.0, customer: \"Elmway College\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-30T00:00:00Z\")) }],\n [\"W-0020\", { title: \"Proof sheets\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 2000.0, customer: \"Alder & Finch\", stock: variant(\"uncoated\", null), due: none }],\n]));\n\n/** The jobs' patch door: every Save commits through it. */\nexport const planWindowJobsPatch = e3.mutation.patch(planWindowJobs);\n\n/**\n * The jobs over their interval index (#1344): a job is filed by the interval\n * it runs over, so a window reads each job overlapping it once, however many\n * days it runs, and a job in the backlog under its Unset start — what the\n * rail's `job` source narrows them through.\n */\nexport const planWindowJobsByTime = e3.recordIndex.interval(\"plan_window_jobs_by_time\", planWindowJobs, { from: \"start\", to: \"end\" });\n\n/**\n * The jobs by the press each is on and the weeks each touches (#1039): a job\n * is filed under every week it runs into, carrying its row, so the jobs of\n * fifty presses over a week are one contiguous range of the index — a cell —\n * and `planWindows` reads only the cells around the rows in view.\n */\nexport const planWindowJobsByPressWeek = e3.recordIndex.series(\"plan_window_jobs_by_press_week\", planWindowJobs, {\n key: \"press\", from: \"start\", to: \"end\", tile: \"week\",\n});\n\n/**\n * How busy each press is, a month at a time (#1039): under each press and\n * month, how many jobs touch it and the milliseconds of it they cover — what\n * `planWindows` shades where the jobs in view are too many to draw.\n */\nexport const planWindowJobsByPressMonth = e3.recordIndex.summary(\"plan_window_jobs_by_press_month\", planWindowJobs, {\n key: \"press\", from: \"start\", to: \"end\", tile: \"month\",\n});\n\nEast.function([], UIComponentType, (_$) => {\n return (<Reactive>{$ => {\n const presses = $.let(Data.bindPaged(planWindowPresses));\n const jobs = $.let(Record.bind(planWindowJobs, [planWindowJobsPatch]));\n const jobEntries = $.let(Data.bindPaged(planWindowJobs));\n // One rail: its `job` source lists the series and the summary beside the interval index, its range the window\n // in view — the Plan reads the jobs' cells through them.\n const slice = $.let(Slice.bindRecords(\"ex.plan.windows\", {\n job: { source: Data.bindPaged(planWindowJobs, { indexes: [planWindowJobsByTime, planWindowJobsByPressWeek, planWindowJobsByPressMonth] }),\n label: \"Print jobs\", fields: { start: { label: \"Dates\" } }, range: \"start\" },\n }));\n const axis = $.let(Plan.axis({\n window: { min: new Date(\"2026-10-05T00:00:00Z\"), max: new Date(\"2026-10-19T00:00:00Z\") },\n resolution: \"day\", resolutions: [\"day\", \"week\", \"month\", \"quarter\"], now: new Date(\"2026-10-14T09:00:00Z\"),\n }));\n return (\n <Plan\n // Named: a page holding it beside another Plan keeps each one's panes, hidden series and library its own.\n id=\"windows\"\n axis={axis}\n slice={{ slice }}\n resources={{\n presses: Schedule.resources(presses, {\n name: \"Presses\", icon: \"print\", label: p => p.name,\n sub: (p, key) => some(East.str`${key} · ${p.hall}`),\n }),\n }}\n events={{\n job: Schedule.events(jobs, {\n name: \"Print job\", icon: \"file-lines\",\n title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"press\", of: \"presses\" },\n state: \"state\", quantity: { field: \"sheets\", unit: \"sheets\" },\n backlog: { duration: j => variant(\"hours\", j.sheets.divide(8000.0)), due: j => j.due },\n entries: jobEntries,\n }),\n }}\n library={[Plan.library.backlog()]}\n style={{ height: \"480px\" }}\n />\n );\n }}</Reactive>);\n })"
|
|
42400
|
+
},
|
|
42401
|
+
{
|
|
42402
|
+
"id": "e3-ui:plan-windows.examples.tsx:planSeriesByHall",
|
|
42403
|
+
"skill": "e3-ui",
|
|
42404
|
+
"package": "e3-ui",
|
|
42405
|
+
"file": "east-ui/packages/e3-ui/test/plan/plan-windows.examples.tsx",
|
|
42406
|
+
"suite": "",
|
|
42407
|
+
"test": "The Plan of planWindows, its 2,000 presses a record narrowed by hall through the rail — a `presses` source named after the paged resource kind's slot, `Data.bindPaged(record, { indexes })` over the presses' hall index — the canvas paging the narrowing's presses themselves in the index's order, counting the hall's, and reading the jobs' cells for the presses of the hall in view alone; a choice keeps the last view standing until its presses land, and drafts Save whatever the halls chosen",
|
|
42408
|
+
"keywords": [
|
|
42409
|
+
"Plan",
|
|
42410
|
+
"Schedule",
|
|
42411
|
+
"Schedule.resources",
|
|
42412
|
+
"Schedule.events",
|
|
42413
|
+
"Slice",
|
|
42414
|
+
"Slice.bindRecords",
|
|
42415
|
+
"source",
|
|
42416
|
+
"rail",
|
|
42417
|
+
"narrow",
|
|
42418
|
+
"narrowing",
|
|
42419
|
+
"filter",
|
|
42420
|
+
"resource kind",
|
|
42421
|
+
"hall",
|
|
42422
|
+
"e3.recordIndex",
|
|
42423
|
+
"index",
|
|
42424
|
+
"paged",
|
|
42425
|
+
"Data.bindPaged",
|
|
42426
|
+
"series index",
|
|
42427
|
+
"summary index",
|
|
42428
|
+
"cell",
|
|
42429
|
+
"large record",
|
|
42430
|
+
"million rows",
|
|
42431
|
+
"presses",
|
|
42432
|
+
"jobs",
|
|
42433
|
+
"#1344",
|
|
42434
|
+
"#1039"
|
|
42460
42435
|
],
|
|
42461
42436
|
"imports": [
|
|
42462
42437
|
"import {",
|
|
@@ -42468,7 +42443,7 @@
|
|
|
42468
42443
|
"languages": [
|
|
42469
42444
|
"tsx"
|
|
42470
42445
|
],
|
|
42471
|
-
"source": "// A Plan read a window at a time — 2,000 presses paged 200 to a window as the canvas scrolls (`Schedule.resources` over `Data.bindPaged`, the key search seeking a press), and the jobs narrowed through one Slice.bindRecords rail, its `job` source reading them through their interval index — only the jobs overlapping the window, each once, the backlog through its Unset start — its range the window the toolbar's dates, a pan and the brush write, and one job by its key through the record's own entries; its editing a session over the record's revision, never the record whole\n// inputs: []\n/** How many presses the works runs: a small authored constant, the presses themselves generated when the dataflow runs. */\nexport const planWindowPressCount = e3.input(\"plan_window_press_count\", IntegerType, variant(\"value\", 2_000n));\n\n/** A press's key: `P-` and its number plus 1,000 — fixed width, so key order is number order. */\nconst pressKey = East.function([IntegerType], StringType, (_$, n) => East.str`P-${n.add(1_001n)}`);\n\n/**\n * The presses, generated from their count, by key: each named by its number,\n * in one of eight halls, printing 6,000 to 15,000 sheets an hour.\n */\nexport const generateWindowPresses = East.function([IntegerType], DictType(StringType, PrintPress), (_$, count) =>\n East.Array.range(0n, count).toDict(\n (_$2, n) => pressKey(n),\n (_$2, n) => ({\n name: East.str`Press ${n.add(1_001n)}`,\n hall: East.str`Hall ${n.remainder(8n).add(1n)}`,\n sheets_per_hour: n.remainder(10n).multiply(1_000n).add(6_000n).toFloat(),\n }),\n ));\n\n/** The task that generates the presses. */\nexport const planWindowPresses = e3.task(\"plan_window_presses\", [planWindowPressCount], generateWindowPresses);\n\n/**\n * The print jobs on the presses, from Monday 5 October 2026: a night run into\n * the next day, a run over three days, jobs on presses in each window of 200,\n * jobs in the weeks after the first two, and four waiting in the backlog.\n */\nexport const planWindowJobs = e3.record(\"plan_window_jobs\", DictType(StringType, PrintJob), new Map([\n [\"W-0001\", { title: \"Brochure run\", start: some(new Date(\"2026-10-05T06:00:00Z\")), end: some(new Date(\"2026-10-05T14:00:00Z\")), press: some(\"P-1001\"), state: variant(\"actual\", null), sheets: 40000.0, customer: \"Alder & Finch\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-07T00:00:00Z\")) }],\n [\"W-0002\", { title: \"Night catalogue\", start: some(new Date(\"2026-10-05T22:00:00Z\")), end: some(new Date(\"2026-10-06T06:00:00Z\")), press: some(\"P-1001\"), state: variant(\"actual\", null), sheets: 96000.0, customer: \"Larkspur Home\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-08T00:00:00Z\")) }],\n [\"W-0003\", { title: \"Museum guide\", start: some(new Date(\"2026-10-06T06:00:00Z\")), end: some(new Date(\"2026-10-06T12:00:00Z\")), press: some(\"P-1002\"), state: variant(\"actual\", null), sheets: 72000.0, customer: \"Driftwood Museum\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-09T00:00:00Z\")) }],\n [\"W-0004\", { title: \"Course handbooks\", start: some(new Date(\"2026-10-07T06:00:00Z\")), end: some(new Date(\"2026-10-09T18:00:00Z\")), press: some(\"P-1003\"), state: variant(\"confirmed\", null), sheets: 144000.0, customer: \"Elmway College\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-12T00:00:00Z\")) }],\n [\"W-0005\", { title: \"Event posters\", start: some(new Date(\"2026-10-12T07:00:00Z\")), end: some(new Date(\"2026-10-12T12:00:00Z\")), press: some(\"P-1004\"), state: variant(\"confirmed\", null), sheets: 60000.0, customer: \"Granite Hall\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-13T00:00:00Z\")) }],\n [\"W-0006\", { title: \"Menu cards\", start: some(new Date(\"2026-10-14T06:00:00Z\")), end: some(new Date(\"2026-10-14T08:00:00Z\")), press: some(\"P-1010\"), state: variant(\"in-progress\", null), sheets: 24000.0, customer: \"Copperleaf Cafe\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-15T00:00:00Z\")) }],\n [\"W-0007\", { title: \"Seed catalogue\", start: some(new Date(\"2026-10-15T06:00:00Z\")), end: some(new Date(\"2026-10-15T16:00:00Z\")), press: some(\"P-1150\"), state: variant(\"proposed\", variant(\"added\", null)), sheets: 100000.0, customer: \"Foxglove Gardens\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-17T00:00:00Z\")) }],\n [\"W-0008\", { title: \"Tour brochure\", start: some(new Date(\"2026-10-16T06:00:00Z\")), end: some(new Date(\"2026-10-16T14:00:00Z\")), press: some(\"P-1200\"), state: variant(\"proposed\", variant(\"recommended\", null)), sheets: 80000.0, customer: \"Bluewater Tours\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-19T00:00:00Z\")) }],\n [\"W-0009\", { title: \"Timetables\", start: some(new Date(\"2026-10-08T06:00:00Z\")), end: some(new Date(\"2026-10-08T10:00:00Z\")), press: some(\"P-1201\"), state: variant(\"actual\", null), sheets: 48000.0, customer: \"Bluewater Tours\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-09T00:00:00Z\")) }],\n [\"W-0010\", { title: \"Annual report\", start: some(new Date(\"2026-10-13T06:00:00Z\")), end: some(new Date(\"2026-10-13T12:00:00Z\")), press: some(\"P-1250\"), state: variant(\"confirmed\", null), sheets: 60000.0, customer: \"Harbour Arts Society\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"W-0011\", { title: \"Gift boxes\", start: some(new Date(\"2026-10-09T06:00:00Z\")), end: some(new Date(\"2026-10-09T12:00:00Z\")), press: some(\"P-1333\"), state: variant(\"confirmed\", null), sheets: 48000.0, customer: \"Juniper Toys\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-12T00:00:00Z\")) }],\n [\"W-0012\", { title: \"Store flyers\", start: some(new Date(\"2026-10-14T06:00:00Z\")), end: some(new Date(\"2026-10-14T16:00:00Z\")), press: some(\"P-1401\"), state: variant(\"in-progress\", null), sheets: 150000.0, customer: \"Northwind Outfitters\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"W-0013\", { title: \"Magazine run\", start: some(new Date(\"2026-10-15T06:00:00Z\")), end: some(new Date(\"2026-10-16T18:00:00Z\")), press: some(\"P-1460\"), state: variant(\"proposed\", variant(\"added\", null)), sheets: 180000.0, customer: \"Meridian Monthly\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-19T00:00:00Z\")) }],\n [\"W-0014\", { title: \"Ticket books\", start: some(new Date(\"2026-10-22T06:00:00Z\")), end: some(new Date(\"2026-10-22T08:00:00Z\")), press: some(\"P-1001\"), state: variant(\"estimated\", null), sheets: 24000.0, customer: \"Heathfield Theatre\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-26T00:00:00Z\")) }],\n [\"W-0015\", { title: \"Wall calendars\", start: some(new Date(\"2026-10-26T06:00:00Z\")), end: some(new Date(\"2026-10-26T18:00:00Z\")), press: some(\"P-1002\"), state: variant(\"estimated\", null), sheets: 50000.0, customer: \"Foxglove Gardens\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-30T00:00:00Z\")) }],\n [\"W-0016\", { title: \"Exhibition book\", start: some(new Date(\"2026-10-28T06:00:00Z\")), end: some(new Date(\"2026-10-28T12:00:00Z\")), press: some(\"P-1470\"), state: variant(\"estimated\", null), sheets: 54000.0, customer: \"Driftwood Museum\", stock: variant(\"coated\", null), due: some(new Date(\"2026-11-02T00:00:00Z\")) }],\n [\"W-0017\", { title: \"Order forms\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 40000.0, customer: \"Larkspur Home\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"W-0018\", { title: \"Winter brochure\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 64000.0, customer: \"Bluewater Tours\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-21T00:00:00Z\")) }],\n [\"W-0019\", { title: \"Prospectus\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 100000.0, customer: \"Elmway College\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-30T00:00:00Z\")) }],\n [\"W-0020\", { title: \"Proof sheets\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 2000.0, customer: \"Alder & Finch\", stock: variant(\"uncoated\", null), due: none }],\n]));\n\n/** The jobs' patch door: every Save commits through it. */\nexport const planWindowJobsPatch = e3.mutation.patch(planWindowJobs);\n\n/**\n * The jobs over their interval index (#1344): a job is filed by the interval\n * it runs over, so a window reads each job overlapping it once, however many\n * days it runs, and a job in the backlog under its Unset start — what the\n * rail's `job` source narrows them through.\n */\nexport const planWindowJobsByTime = e3.recordIndex(\"plan_window_jobs_by_time\", planWindowJobs, { interval: { from: \"start\", to: \"end\" } });\n\nEast.function([], UIComponentType, (_$) => {\n return (<Reactive>{$ => {\n const presses = $.let(Data.bindPaged(planWindowPresses));\n const jobs = $.let(Record.bind(planWindowJobs, [planWindowJobsPatch]));\n const jobEntries = $.let(Data.bindPaged(planWindowJobs));\n // One rail: its `job` source reads the jobs through their interval index, its range the window in view.\n const slice = $.let(Slice.bindRecords(\"ex.plan.windows\", {\n job: { source: Data.bindPaged(planWindowJobs, { indexes: [planWindowJobsByTime] }), label: \"Print jobs\",\n fields: { start: { label: \"Dates\" } }, range: \"start\" },\n }));\n const axis = $.let(Plan.axis({\n window: { min: new Date(\"2026-10-05T00:00:00Z\"), max: new Date(\"2026-10-19T00:00:00Z\") },\n resolution: \"day\", now: new Date(\"2026-10-14T09:00:00Z\"),\n }));\n return (\n <Plan\n // Named: a page holding it beside another Plan keeps each one's panes, hidden series and library its own.\n id=\"windows\"\n axis={axis}\n slice={{ slice }}\n resources={{\n presses: Schedule.resources(presses, {\n name: \"Presses\", icon: \"print\", label: p => p.name,\n sub: (p, key) => some(East.str`${key} · ${p.hall}`),\n }),\n }}\n events={{\n job: Schedule.events(jobs, {\n name: \"Print job\", icon: \"file-lines\",\n title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"press\", of: \"presses\" },\n state: \"state\", quantity: { field: \"sheets\", unit: \"sheets\" },\n backlog: { duration: j => variant(\"hours\", j.sheets.divide(8000.0)), due: j => j.due },\n entries: jobEntries,\n }),\n }}\n library={[Plan.library.backlog()]}\n style={{ height: \"480px\" }}\n />\n );\n }}</Reactive>);\n })"
|
|
42446
|
+
"source": "// The Plan of planWindows, its 2,000 presses a record narrowed by hall through the rail — a `presses` source named after the paged resource kind's slot, `Data.bindPaged(record, { indexes })` over the presses' hall index — the canvas paging the narrowing's presses themselves in the index's order, counting the hall's, and reading the jobs' cells for the presses of the hall in view alone; a choice keeps the last view standing until its presses land, and drafts Save whatever the halls chosen\n// inputs: []\n/** A press's key: `P-` and its number plus 1,000 — fixed width, so key order is number order. */\nconst pressKey = East.function([IntegerType], StringType, (_$, n) => East.str`P-${n.add(1_001n)}`);\n\n/**\n * The presses, generated from their count, by key: each named by its number,\n * in one of eight halls, printing 6,000 to 15,000 sheets an hour.\n */\nexport const generateWindowPresses = East.function([IntegerType], DictType(StringType, PrintPress), (_$, count) =>\n East.Array.range(0n, count).toDict(\n (_$2, n) => pressKey(n),\n (_$2, n) => ({\n name: East.str`Press ${n.add(1_001n)}`,\n hall: East.str`Hall ${n.remainder(8n).add(1n)}`,\n sheets_per_hour: n.remainder(10n).multiply(1_000n).add(6_000n).toFloat(),\n }),\n ));\n\n/**\n * The works' 2,000 presses as a record of their own (#1344): the presses\n * `generateWindowPresses` makes, stored, so the rail narrows them — a resource\n * kind's source of the slice is an index of its record.\n */\nexport const planSeriesPresses = e3.record(\"plan_series_presses\", DictType(StringType, PrintPress), East.compile(generateWindowPresses, [])(2_000n));\n\n/**\n * The presses by hall (#1344): each hall's presses, in their key order — what\n * the rail's `presses` source narrows them through, and the order a Plan pages\n * the presses of the halls chosen in.\n */\nexport const planSeriesPressesByHall = e3.recordIndex(\"plan_series_presses_by_hall\", planSeriesPresses, { on: [\"hall\"] });\n\n/**\n * The print jobs on the presses, from Monday 5 October 2026: a night run into\n * the next day, a run over three days, jobs on presses in each window of 200,\n * jobs in the weeks after the first two, and four waiting in the backlog.\n */\nexport const planWindowJobs = e3.record(\"plan_window_jobs\", DictType(StringType, PrintJob), new Map([\n [\"W-0001\", { title: \"Brochure run\", start: some(new Date(\"2026-10-05T06:00:00Z\")), end: some(new Date(\"2026-10-05T14:00:00Z\")), press: some(\"P-1001\"), state: variant(\"actual\", null), sheets: 40000.0, customer: \"Alder & Finch\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-07T00:00:00Z\")) }],\n [\"W-0002\", { title: \"Night catalogue\", start: some(new Date(\"2026-10-05T22:00:00Z\")), end: some(new Date(\"2026-10-06T06:00:00Z\")), press: some(\"P-1001\"), state: variant(\"actual\", null), sheets: 96000.0, customer: \"Larkspur Home\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-08T00:00:00Z\")) }],\n [\"W-0003\", { title: \"Museum guide\", start: some(new Date(\"2026-10-06T06:00:00Z\")), end: some(new Date(\"2026-10-06T12:00:00Z\")), press: some(\"P-1002\"), state: variant(\"actual\", null), sheets: 72000.0, customer: \"Driftwood Museum\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-09T00:00:00Z\")) }],\n [\"W-0004\", { title: \"Course handbooks\", start: some(new Date(\"2026-10-07T06:00:00Z\")), end: some(new Date(\"2026-10-09T18:00:00Z\")), press: some(\"P-1003\"), state: variant(\"confirmed\", null), sheets: 144000.0, customer: \"Elmway College\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-12T00:00:00Z\")) }],\n [\"W-0005\", { title: \"Event posters\", start: some(new Date(\"2026-10-12T07:00:00Z\")), end: some(new Date(\"2026-10-12T12:00:00Z\")), press: some(\"P-1004\"), state: variant(\"confirmed\", null), sheets: 60000.0, customer: \"Granite Hall\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-13T00:00:00Z\")) }],\n [\"W-0006\", { title: \"Menu cards\", start: some(new Date(\"2026-10-14T06:00:00Z\")), end: some(new Date(\"2026-10-14T08:00:00Z\")), press: some(\"P-1010\"), state: variant(\"in-progress\", null), sheets: 24000.0, customer: \"Copperleaf Cafe\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-15T00:00:00Z\")) }],\n [\"W-0007\", { title: \"Seed catalogue\", start: some(new Date(\"2026-10-15T06:00:00Z\")), end: some(new Date(\"2026-10-15T16:00:00Z\")), press: some(\"P-1150\"), state: variant(\"proposed\", variant(\"added\", null)), sheets: 100000.0, customer: \"Foxglove Gardens\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-17T00:00:00Z\")) }],\n [\"W-0008\", { title: \"Tour brochure\", start: some(new Date(\"2026-10-16T06:00:00Z\")), end: some(new Date(\"2026-10-16T14:00:00Z\")), press: some(\"P-1200\"), state: variant(\"proposed\", variant(\"recommended\", null)), sheets: 80000.0, customer: \"Bluewater Tours\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-19T00:00:00Z\")) }],\n [\"W-0009\", { title: \"Timetables\", start: some(new Date(\"2026-10-08T06:00:00Z\")), end: some(new Date(\"2026-10-08T10:00:00Z\")), press: some(\"P-1201\"), state: variant(\"actual\", null), sheets: 48000.0, customer: \"Bluewater Tours\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-09T00:00:00Z\")) }],\n [\"W-0010\", { title: \"Annual report\", start: some(new Date(\"2026-10-13T06:00:00Z\")), end: some(new Date(\"2026-10-13T12:00:00Z\")), press: some(\"P-1250\"), state: variant(\"confirmed\", null), sheets: 60000.0, customer: \"Harbour Arts Society\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"W-0011\", { title: \"Gift boxes\", start: some(new Date(\"2026-10-09T06:00:00Z\")), end: some(new Date(\"2026-10-09T12:00:00Z\")), press: some(\"P-1333\"), state: variant(\"confirmed\", null), sheets: 48000.0, customer: \"Juniper Toys\", stock: variant(\"board\", null), due: some(new Date(\"2026-10-12T00:00:00Z\")) }],\n [\"W-0012\", { title: \"Store flyers\", start: some(new Date(\"2026-10-14T06:00:00Z\")), end: some(new Date(\"2026-10-14T16:00:00Z\")), press: some(\"P-1401\"), state: variant(\"in-progress\", null), sheets: 150000.0, customer: \"Northwind Outfitters\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"W-0013\", { title: \"Magazine run\", start: some(new Date(\"2026-10-15T06:00:00Z\")), end: some(new Date(\"2026-10-16T18:00:00Z\")), press: some(\"P-1460\"), state: variant(\"proposed\", variant(\"added\", null)), sheets: 180000.0, customer: \"Meridian Monthly\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-19T00:00:00Z\")) }],\n [\"W-0014\", { title: \"Ticket books\", start: some(new Date(\"2026-10-22T06:00:00Z\")), end: some(new Date(\"2026-10-22T08:00:00Z\")), press: some(\"P-1001\"), state: variant(\"estimated\", null), sheets: 24000.0, customer: \"Heathfield Theatre\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-26T00:00:00Z\")) }],\n [\"W-0015\", { title: \"Wall calendars\", start: some(new Date(\"2026-10-26T06:00:00Z\")), end: some(new Date(\"2026-10-26T18:00:00Z\")), press: some(\"P-1002\"), state: variant(\"estimated\", null), sheets: 50000.0, customer: \"Foxglove Gardens\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-30T00:00:00Z\")) }],\n [\"W-0016\", { title: \"Exhibition book\", start: some(new Date(\"2026-10-28T06:00:00Z\")), end: some(new Date(\"2026-10-28T12:00:00Z\")), press: some(\"P-1470\"), state: variant(\"estimated\", null), sheets: 54000.0, customer: \"Driftwood Museum\", stock: variant(\"coated\", null), due: some(new Date(\"2026-11-02T00:00:00Z\")) }],\n [\"W-0017\", { title: \"Order forms\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 40000.0, customer: \"Larkspur Home\", stock: variant(\"uncoated\", null), due: some(new Date(\"2026-10-16T00:00:00Z\")) }],\n [\"W-0018\", { title: \"Winter brochure\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 64000.0, customer: \"Bluewater Tours\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-21T00:00:00Z\")) }],\n [\"W-0019\", { title: \"Prospectus\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 100000.0, customer: \"Elmway College\", stock: variant(\"coated\", null), due: some(new Date(\"2026-10-30T00:00:00Z\")) }],\n [\"W-0020\", { title: \"Proof sheets\", start: none, end: none, press: none, state: variant(\"estimated\", null), sheets: 2000.0, customer: \"Alder & Finch\", stock: variant(\"uncoated\", null), due: none }],\n]));\n\n/** The jobs' patch door: every Save commits through it. */\nexport const planWindowJobsPatch = e3.mutation.patch(planWindowJobs);\n\n/**\n * The jobs over their interval index (#1344): a job is filed by the interval\n * it runs over, so a window reads each job overlapping it once, however many\n * days it runs, and a job in the backlog under its Unset start — what the\n * rail's `job` source narrows them through.\n */\nexport const planWindowJobsByTime = e3.recordIndex.interval(\"plan_window_jobs_by_time\", planWindowJobs, { from: \"start\", to: \"end\" });\n\n/**\n * The jobs by the press each is on and the weeks each touches (#1039): a job\n * is filed under every week it runs into, carrying its row, so the jobs of\n * fifty presses over a week are one contiguous range of the index — a cell —\n * and `planWindows` reads only the cells around the rows in view.\n */\nexport const planWindowJobsByPressWeek = e3.recordIndex.series(\"plan_window_jobs_by_press_week\", planWindowJobs, {\n key: \"press\", from: \"start\", to: \"end\", tile: \"week\",\n});\n\n/**\n * How busy each press is, a month at a time (#1039): under each press and\n * month, how many jobs touch it and the milliseconds of it they cover — what\n * `planWindows` shades where the jobs in view are too many to draw.\n */\nexport const planWindowJobsByPressMonth = e3.recordIndex.summary(\"plan_window_jobs_by_press_month\", planWindowJobs, {\n key: \"press\", from: \"start\", to: \"end\", tile: \"month\",\n});\n\nEast.function([], UIComponentType, (_$) => {\n return (<Reactive>{$ => {\n const presses = $.let(Data.bindPaged(planSeriesPresses));\n const jobs = $.let(Record.bind(planWindowJobs, [planWindowJobsPatch]));\n const jobEntries = $.let(Data.bindPaged(planWindowJobs));\n // The `presses` source narrows the paged kind by hall, through the presses' hall index: the canvas pages the\n // narrowing's presses. The `job` source reads the jobs a cell at a time, as in planWindows.\n const slice = $.let(Slice.bindRecords(\"ex.plan.series.halls\", {\n presses: { source: Data.bindPaged(planSeriesPresses, { indexes: [planSeriesPressesByHall] }), label: \"Presses\",\n icon: { prefix: \"fas\", name: \"print\" },\n fields: { hall: { label: \"Hall\", hints: [\"Hall 1\", \"Hall 2\", \"Hall 3\", \"Hall 4\", \"Hall 5\", \"Hall 6\", \"Hall 7\", \"Hall 8\"] } } },\n job: { source: Data.bindPaged(planWindowJobs, { indexes: [planWindowJobsByTime, planWindowJobsByPressWeek, planWindowJobsByPressMonth] }),\n label: \"Print jobs\", fields: { start: { label: \"Dates\" } }, range: \"start\" },\n }));\n const axis = $.let(Plan.axis({\n window: { min: new Date(\"2026-10-05T00:00:00Z\"), max: new Date(\"2026-10-19T00:00:00Z\") },\n resolution: \"day\", resolutions: [\"day\", \"week\", \"month\", \"quarter\"], now: new Date(\"2026-10-14T09:00:00Z\"),\n }));\n return (\n <Plan\n id=\"series-halls\"\n axis={axis}\n slice={{ slice }}\n resources={{\n presses: Schedule.resources(presses, {\n name: \"Presses\", icon: \"print\", label: p => p.name,\n sub: (p, key) => some(East.str`${key} · ${p.hall}`),\n }),\n }}\n events={{\n job: Schedule.events(jobs, {\n name: \"Print job\", icon: \"file-lines\",\n title: \"title\", start: \"start\", end: \"end\",\n resource: { field: \"press\", of: \"presses\" },\n state: \"state\", quantity: { field: \"sheets\", unit: \"sheets\" },\n backlog: { duration: j => variant(\"hours\", j.sheets.divide(8000.0)), due: j => j.due },\n entries: jobEntries,\n }),\n }}\n library={[Plan.library.backlog()]}\n style={{ height: \"480px\" }}\n />\n );\n }}</Reactive>);\n })"
|
|
42472
42447
|
},
|
|
42473
42448
|
{
|
|
42474
42449
|
"id": "e3-ui:plan.examples.tsx:planTargetState",
|
|
@@ -43135,7 +43110,7 @@
|
|
|
43135
43110
|
"package": "e3-ui",
|
|
43136
43111
|
"file": "east-ui/packages/e3-ui/test/plan/plan.examples.tsx",
|
|
43137
43112
|
"suite": "",
|
|
43138
|
-
"test": "Ops plan — a Plan narrowed through one Slice.bindRecords rail over tasks bound from e3: its `rows` source gives the canvas its rows, paged through the tasks' interval index and narrowed by owner
|
|
43113
|
+
"test": "Ops plan — a Plan narrowed through one Slice.bindRecords rail over tasks bound from e3: its `rows` source gives the canvas its rows, paged through the tasks' interval index and narrowed by owner and by their dates — fields of the rail the viewer sets — while the window is the canvas's own: the toolbar's dates and the horizon brush move it, and never decide which tasks are listed",
|
|
43139
43114
|
"keywords": [
|
|
43140
43115
|
"Slice",
|
|
43141
43116
|
"Slice.bindRecords",
|
|
@@ -43163,7 +43138,7 @@
|
|
|
43163
43138
|
"languages": [
|
|
43164
43139
|
"tsx"
|
|
43165
43140
|
],
|
|
43166
|
-
"source": "// Ops plan — a Plan narrowed through one Slice.bindRecords rail over tasks bound from e3: its `rows` source gives the canvas its rows, paged through the tasks' interval index and narrowed by owner
|
|
43141
|
+
"source": "// Ops plan — a Plan narrowed through one Slice.bindRecords rail over tasks bound from e3: its `rows` source gives the canvas its rows, paged through the tasks' interval index and narrowed by owner and by their dates — fields of the rail the viewer sets — while the window is the canvas's own: the toolbar's dates and the horizon brush move it, and never decide which tasks are listed\n// inputs: []\n/** A task on the plan — its owner and its window. */\nexport const SliceJob = StructType({ task: StringType, owner: StringType, start: DateTimeType, end: DateTimeType });\n\n/** The plan's tasks, by name — a record of their own, the rows the slice's `rows` source narrows (#1344). */\nexport const planSliceJobs = e3.record(\"plan_slice_jobs\", DictType(StringType, SliceJob), new Map([\n [\"Planning\", { task: \"Planning\", owner: \"Team A\", start: new Date(\"2024-01-01T00:00:00Z\"), end: new Date(\"2024-01-15T00:00:00Z\") }],\n [\"Design\", { task: \"Design\", owner: \"Team B\", start: new Date(\"2024-01-10T00:00:00Z\"), end: new Date(\"2024-02-01T00:00:00Z\") }],\n [\"Development\", { task: \"Development\", owner: \"Team C\", start: new Date(\"2024-01-20T00:00:00Z\"), end: new Date(\"2024-03-15T00:00:00Z\") }],\n [\"Testing\", { task: \"Testing\", owner: \"Team A\", start: new Date(\"2024-03-01T00:00:00Z\"), end: new Date(\"2024-03-30T00:00:00Z\") }],\n]));\n\nexport const planSliceJobsByTime = e3.recordIndex.interval(\"plan_slice_jobs_by_time\", planSliceJobs, { from: \"start\", to: \"end\" });\n\nexport const planSliceJobsByOwnerTime = e3.recordIndex.interval(\"plan_slice_jobs_by_owner_time\", planSliceJobs, { on: [\"owner\"], from: \"start\", to: \"end\" });\n\nEast.function([], UIComponentType, (_$) => {\n return (\n <Reactive>{$ => {\n // One rail: its `rows` source pages the tasks onto the canvas, each task once. Their dates are a field of\n // the rail, the viewer's to set: the dates on screen never narrow the tasks.\n const slice = $.let(Slice.bindRecords(\"ex.slice.plan.chrome\", {\n rows: {\n source: Data.bindPaged(planSliceJobs, { indexes: [planSliceJobsByTime, planSliceJobsByOwnerTime] }),\n label: \"Tasks\",\n fields: { owner: { label: \"Owner\", hints: [\"Team A\", \"Team B\", \"Team C\"] }, start: { label: \"Start\" } },\n search: [],\n initial: { owner: [\"Team A\"] },\n },\n }));\n const series = $.const([\n Plan.series.span(SliceJob, {\n key: \"jobs\", title: \"Jobs\",\n label: r => r.task, id: true,\n sub: r => some(r.owner),\n runs: (r, k) => [Plan.run({\n key: k, start: r.start, end: r.end,\n label: r.task, state: variant(\"confirmed\", null),\n })],\n }),\n ], ArrayType(Plan.Types.Series(SliceJob)));\n const axis = $.const(Plan.axis({\n window: { min: new Date(\"2024-01-01\"), max: new Date(\"2024-04-01\") },\n resolution: \"week\", now: new Date(\"2024-01-20\"),\n }));\n return (\n <Plan\n axis={axis}\n series={series}\n slice={{ slice, affordances: [\"fields\", \"brush\"] }}\n />\n );\n }}</Reactive>\n );\n })"
|
|
43167
43142
|
},
|
|
43168
43143
|
{
|
|
43169
43144
|
"id": "e3-ui:query.examples.tsx:queryBuilder",
|
|
@@ -43378,7 +43353,7 @@
|
|
|
43378
43353
|
"languages": [
|
|
43379
43354
|
"typescript"
|
|
43380
43355
|
],
|
|
43381
|
-
"source": "// Coverage over the app's own duties: named staff, requested positions, nominal skill capacity, each duty's own hours, and the slot's cost, the app's pay summed\n// inputs: [week, context, pay] returns: { positions: 4n, filled: 3n, open: 1n, people: 2n,\n hours: { permanent: 10, agency: 8, requested: 8, needed: 30, rostered: 26 },\n lines: [{ skill: \"reach\", needed: 24, holders: some(1n), capacity: some(8), gap: 16, assigned: 10 },\n { skill: \"general\", needed: 6, holders: none, capacity: none, gap: 0, assigned: 8 }],\n cost: 1278, lead: true, trainer: true }\n/** A duty of the app's own: who — one of its staff, or an agency asked to send someone — its own times, and a note the Roster carries but never reads. */\nexport const DomainDuty = StructType({\n who: VariantType({ staff: StringType, agency: StringType }), start: DateTimeType, end: DateTimeType, note: StringType,\n});\n\n/** A week of the app's duties, as the Roster reads it. */\nexport const DomainWeek = Roster.Types.Week(DomainDuty);\n\n/** Small staffing fixture with independently calculable hours and cost. */\nexport const context: ValueTypeOf<typeof Roster.Types.Context> = {\n people: new Map([\n [\"lead\", { key: \"lead\", name: \"Lead\", group: \"goods\", position: \"lead\", skills: new Set([\"reach\"]), contract: 40, agency: false, trainer: true, trainee: false, usual: some(\"early\") }],\n [\"agency\", { key: \"agency\", name: \"Agency\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 0, agency: true, trainer: false, trainee: false, usual: none }],\n [\"off\", { key: \"off\", name: \"Off\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 40, agency: false, trainer: false, trainee: false, usual: none }],\n ]),\n groups: [{ key: \"goods\", label: \"Goods\" }],\n // Goods' one skill, and the duty every group shares.\n activities: [{ key: \"reach\", label: \"Reach truck\", code: \"RT\", group: some(\"goods\") }, { key: \"general\", label: \"General duties\", code: \"GD\", group: none }],\n shifts: [\n { key: \"early\", label: \"Early\", code: \"E\", start: { hour: 6n, minute: 0n }, hours: 8 },\n { key: \"late\", label: \"Late\", code: \"L\", start: { hour: 14n, minute: 0n }, hours: 8 },\n { key: \"night\", label: \"Night\", code: \"N\", start: { hour: 22n, minute: 0n }, hours: 8 },\n ],\n positions: [{ key: \"crew\", label: \"Crew\", code: \"\", lead: false }, { key: \"lead\", label: \"Lead\", code: \"SV\", lead: true }],\n agencies: [{ key: \"agency\", name: \"Staffing agency\" }],\n rules: { rest: 10, lead: true, skill: true, trainer: true },\n};\n\nexport const slot = { day: 0n, group: \"goods\", shift: \"early\" };\n\n/** Sunday's early shift: the lead in an hour late and on to 17:00, an agency's worker, and a request to the agency. */\nexport const week: ValueTypeOf<typeof DomainWeek> = {\n status: variant(\"draft\", null), dismissed: new Set<string>(),\n assignments: new Map([\n [\"a\", { slot, who: variant(\"person\", \"lead\"), position: \"lead\", activity: some(\"reach\"),\n start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), hours: 10,\n row: { who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), note: \"in late, stays on\" } }],\n [\"b\", { slot, who: variant(\"person\", \"agency\"), position: \"crew\", activity: some(\"general\"),\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"staff\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n [\"c\", { slot, who: variant(\"request\", \"agency\"), position: \"crew\", activity: none,\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"agency\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n ]),\n requirements: new SortedMap([[slot, { positions: 4n, hours: new Map([[\"reach\", 24], [\"general\", 6]]) }]], compareFor(Roster.Types.Slot)),\n};\n\n/** The app's pay for the week, by its own codes: the lead's eight ordinary hours and two of overtime, and the agency's sixteen. */\nexport const pay: ValueTypeOf<typeof Roster.Types.Pay> = new Map([\n [\"a\", new Map([[\"ORD\", 336], [\"OT1\", 126]])], [\"b\", new Map([[\"AGENCY\", 408]])], [\"c\", new Map([[\"AGENCY\", 408]])],\n]);\n\nEast.function([DomainWeek, Roster.Types.Context, Roster.Types.Pay], Roster.Types.Coverage, ($, week, context, pay) => {\n const coverage = $.const(Roster.coverage(DomainDuty));\n return coverage(week, context, pay).get(slot);\n })"
|
|
43356
|
+
"source": "// Coverage over the app's own duties: named staff, requested positions, nominal skill capacity, each duty's own hours, and the slot's cost, the app's pay summed\n// inputs: [week, context, pay] returns: { positions: 4n, filled: 3n, open: 1n, people: 2n,\n hours: { permanent: 10, agency: 8, requested: 8, needed: 30, rostered: 26 },\n lines: [{ skill: \"reach\", needed: 24, holders: some(1n), capacity: some(8), gap: 16, assigned: 10 },\n { skill: \"general\", needed: 6, holders: none, capacity: none, gap: 0, assigned: 8 }],\n cost: 1278, lead: true, trainer: true }\n/** A duty of the app's own: who — one of its staff, or an agency asked to send someone — its own times, and a note the Roster carries but never reads. */\nexport const DomainDuty = StructType({\n who: VariantType({ staff: StringType, agency: StringType }), start: DateTimeType, end: DateTimeType, note: StringType,\n});\n\n/** A week of the app's duties, as the Roster reads it. */\nexport const DomainWeek = Roster.Types.Week(DomainDuty);\n\n/** Small staffing fixture with independently calculable hours and cost. */\nexport const context: ValueTypeOf<typeof Roster.Types.Context> = {\n people: new Map([\n [\"lead\", { key: \"lead\", name: \"Lead\", group: \"goods\", position: \"lead\", skills: new Set([\"reach\"]), contract: 40, agency: false, trainer: true, trainee: false, usual: some(\"early\") }],\n [\"agency\", { key: \"agency\", name: \"Agency\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 0, agency: true, trainer: false, trainee: false, usual: none }],\n [\"off\", { key: \"off\", name: \"Off\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 40, agency: false, trainer: false, trainee: false, usual: none }],\n ]),\n groups: [{ key: \"goods\", label: \"Goods\" }],\n // Goods' one skill, and the duty every group shares.\n activities: [{ key: \"reach\", label: \"Reach truck\", code: \"RT\", group: some(\"goods\") }, { key: \"general\", label: \"General duties\", code: \"GD\", group: none }],\n shifts: [\n { key: \"early\", label: \"Early\", code: \"E\", start: { hour: 6n, minute: 0n }, hours: 8, color: none },\n { key: \"late\", label: \"Late\", code: \"L\", start: { hour: 14n, minute: 0n }, hours: 8, color: none },\n { key: \"night\", label: \"Night\", code: \"N\", start: { hour: 22n, minute: 0n }, hours: 8, color: none },\n ],\n positions: [{ key: \"crew\", label: \"Crew\", code: \"\", lead: false }, { key: \"lead\", label: \"Lead\", code: \"SV\", lead: true }],\n agencies: [{ key: \"agency\", name: \"Staffing agency\" }],\n rules: { rest: 10, lead: true, skill: true, trainer: true },\n};\n\nexport const slot = { day: 0n, group: \"goods\", shift: \"early\" };\n\n/** Sunday's early shift: the lead in an hour late and on to 17:00, an agency's worker, and a request to the agency. */\nexport const week: ValueTypeOf<typeof DomainWeek> = {\n status: variant(\"draft\", null), dismissed: new Set<string>(),\n assignments: new Map([\n [\"a\", { slot, who: variant(\"person\", \"lead\"), position: \"lead\", activity: some(\"reach\"),\n start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), hours: 10,\n row: { who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), note: \"in late, stays on\" } }],\n [\"b\", { slot, who: variant(\"person\", \"agency\"), position: \"crew\", activity: some(\"general\"),\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"staff\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n [\"c\", { slot, who: variant(\"request\", \"agency\"), position: \"crew\", activity: none,\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"agency\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n ]),\n requirements: new SortedMap([[slot, { positions: 4n, hours: new Map([[\"reach\", 24], [\"general\", 6]]) }]], compareFor(Roster.Types.Slot)),\n};\n\n/** The app's pay for the week, by its own codes: the lead's eight ordinary hours and two of overtime, and the agency's sixteen. */\nexport const pay: ValueTypeOf<typeof Roster.Types.Pay> = new Map([\n [\"a\", new Map([[\"ORD\", 336], [\"OT1\", 126]])], [\"b\", new Map([[\"AGENCY\", 408]])], [\"c\", new Map([[\"AGENCY\", 408]])],\n]);\n\nEast.function([DomainWeek, Roster.Types.Context, Roster.Types.Pay], Roster.Types.Coverage, ($, week, context, pay) => {\n const coverage = $.const(Roster.coverage(DomainDuty));\n return coverage(week, context, pay).get(slot);\n })"
|
|
43382
43357
|
},
|
|
43383
43358
|
{
|
|
43384
43359
|
"id": "e3-ui:domain.examples.ts:rosterChecks",
|
|
@@ -43400,7 +43375,7 @@
|
|
|
43400
43375
|
"languages": [
|
|
43401
43376
|
"typescript"
|
|
43402
43377
|
],
|
|
43403
|
-
"source": "// Built-in checks report an open position and a skill-capacity gap; a lead holds the slot, and the duties' own times ask for no agreement\n// inputs: [week, context] returns: 2n\n/** A duty of the app's own: who — one of its staff, or an agency asked to send someone — its own times, and a note the Roster carries but never reads. */\nexport const DomainDuty = StructType({\n who: VariantType({ staff: StringType, agency: StringType }), start: DateTimeType, end: DateTimeType, note: StringType,\n});\n\n/** A week of the app's duties, as the Roster reads it. */\nexport const DomainWeek = Roster.Types.Week(DomainDuty);\n\n/** Small staffing fixture with independently calculable hours and cost. */\nexport const context: ValueTypeOf<typeof Roster.Types.Context> = {\n people: new Map([\n [\"lead\", { key: \"lead\", name: \"Lead\", group: \"goods\", position: \"lead\", skills: new Set([\"reach\"]), contract: 40, agency: false, trainer: true, trainee: false, usual: some(\"early\") }],\n [\"agency\", { key: \"agency\", name: \"Agency\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 0, agency: true, trainer: false, trainee: false, usual: none }],\n [\"off\", { key: \"off\", name: \"Off\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 40, agency: false, trainer: false, trainee: false, usual: none }],\n ]),\n groups: [{ key: \"goods\", label: \"Goods\" }],\n // Goods' one skill, and the duty every group shares.\n activities: [{ key: \"reach\", label: \"Reach truck\", code: \"RT\", group: some(\"goods\") }, { key: \"general\", label: \"General duties\", code: \"GD\", group: none }],\n shifts: [\n { key: \"early\", label: \"Early\", code: \"E\", start: { hour: 6n, minute: 0n }, hours: 8 },\n { key: \"late\", label: \"Late\", code: \"L\", start: { hour: 14n, minute: 0n }, hours: 8 },\n { key: \"night\", label: \"Night\", code: \"N\", start: { hour: 22n, minute: 0n }, hours: 8 },\n ],\n positions: [{ key: \"crew\", label: \"Crew\", code: \"\", lead: false }, { key: \"lead\", label: \"Lead\", code: \"SV\", lead: true }],\n agencies: [{ key: \"agency\", name: \"Staffing agency\" }],\n rules: { rest: 10, lead: true, skill: true, trainer: true },\n};\n\nexport const slot = { day: 0n, group: \"goods\", shift: \"early\" };\n\n/** Sunday's early shift: the lead in an hour late and on to 17:00, an agency's worker, and a request to the agency. */\nexport const week: ValueTypeOf<typeof DomainWeek> = {\n status: variant(\"draft\", null), dismissed: new Set<string>(),\n assignments: new Map([\n [\"a\", { slot, who: variant(\"person\", \"lead\"), position: \"lead\", activity: some(\"reach\"),\n start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), hours: 10,\n row: { who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), note: \"in late, stays on\" } }],\n [\"b\", { slot, who: variant(\"person\", \"agency\"), position: \"crew\", activity: some(\"general\"),\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"staff\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n [\"c\", { slot, who: variant(\"request\", \"agency\"), position: \"crew\", activity: none,\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"agency\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n ]),\n requirements: new SortedMap([[slot, { positions: 4n, hours: new Map([[\"reach\", 24], [\"general\", 6]]) }]], compareFor(Roster.Types.Slot)),\n};\n\nEast.function([DomainWeek, Roster.Types.Context], IntegerType, ($, week, context) => {\n const check = $.const(Roster.check(DomainDuty));\n return check(week, context).size();\n })"
|
|
43378
|
+
"source": "// Built-in checks report an open position and a skill-capacity gap; a lead holds the slot, and the duties' own times ask for no agreement\n// inputs: [week, context] returns: 2n\n/** A duty of the app's own: who — one of its staff, or an agency asked to send someone — its own times, and a note the Roster carries but never reads. */\nexport const DomainDuty = StructType({\n who: VariantType({ staff: StringType, agency: StringType }), start: DateTimeType, end: DateTimeType, note: StringType,\n});\n\n/** A week of the app's duties, as the Roster reads it. */\nexport const DomainWeek = Roster.Types.Week(DomainDuty);\n\n/** Small staffing fixture with independently calculable hours and cost. */\nexport const context: ValueTypeOf<typeof Roster.Types.Context> = {\n people: new Map([\n [\"lead\", { key: \"lead\", name: \"Lead\", group: \"goods\", position: \"lead\", skills: new Set([\"reach\"]), contract: 40, agency: false, trainer: true, trainee: false, usual: some(\"early\") }],\n [\"agency\", { key: \"agency\", name: \"Agency\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 0, agency: true, trainer: false, trainee: false, usual: none }],\n [\"off\", { key: \"off\", name: \"Off\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 40, agency: false, trainer: false, trainee: false, usual: none }],\n ]),\n groups: [{ key: \"goods\", label: \"Goods\" }],\n // Goods' one skill, and the duty every group shares.\n activities: [{ key: \"reach\", label: \"Reach truck\", code: \"RT\", group: some(\"goods\") }, { key: \"general\", label: \"General duties\", code: \"GD\", group: none }],\n shifts: [\n { key: \"early\", label: \"Early\", code: \"E\", start: { hour: 6n, minute: 0n }, hours: 8, color: none },\n { key: \"late\", label: \"Late\", code: \"L\", start: { hour: 14n, minute: 0n }, hours: 8, color: none },\n { key: \"night\", label: \"Night\", code: \"N\", start: { hour: 22n, minute: 0n }, hours: 8, color: none },\n ],\n positions: [{ key: \"crew\", label: \"Crew\", code: \"\", lead: false }, { key: \"lead\", label: \"Lead\", code: \"SV\", lead: true }],\n agencies: [{ key: \"agency\", name: \"Staffing agency\" }],\n rules: { rest: 10, lead: true, skill: true, trainer: true },\n};\n\nexport const slot = { day: 0n, group: \"goods\", shift: \"early\" };\n\n/** Sunday's early shift: the lead in an hour late and on to 17:00, an agency's worker, and a request to the agency. */\nexport const week: ValueTypeOf<typeof DomainWeek> = {\n status: variant(\"draft\", null), dismissed: new Set<string>(),\n assignments: new Map([\n [\"a\", { slot, who: variant(\"person\", \"lead\"), position: \"lead\", activity: some(\"reach\"),\n start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), hours: 10,\n row: { who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), note: \"in late, stays on\" } }],\n [\"b\", { slot, who: variant(\"person\", \"agency\"), position: \"crew\", activity: some(\"general\"),\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"staff\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n [\"c\", { slot, who: variant(\"request\", \"agency\"), position: \"crew\", activity: none,\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"agency\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n ]),\n requirements: new SortedMap([[slot, { positions: 4n, hours: new Map([[\"reach\", 24], [\"general\", 6]]) }]], compareFor(Roster.Types.Slot)),\n};\n\nEast.function([DomainWeek, Roster.Types.Context], IntegerType, ($, week, context) => {\n const check = $.const(Roster.check(DomainDuty));\n return check(week, context).size();\n })"
|
|
43404
43379
|
},
|
|
43405
43380
|
{
|
|
43406
43381
|
"id": "e3-ui:domain.examples.ts:rosterWeeklyHours",
|
|
@@ -43421,7 +43396,7 @@
|
|
|
43421
43396
|
"languages": [
|
|
43422
43397
|
"typescript"
|
|
43423
43398
|
],
|
|
43424
|
-
"source": "// A person's week counts each duty's own hours: the lead's 07:00–17:00 counts ten\n// inputs: [week, context] returns: 10\n/** A duty of the app's own: who — one of its staff, or an agency asked to send someone — its own times, and a note the Roster carries but never reads. */\nexport const DomainDuty = StructType({\n who: VariantType({ staff: StringType, agency: StringType }), start: DateTimeType, end: DateTimeType, note: StringType,\n});\n\n/** A week of the app's duties, as the Roster reads it. */\nexport const DomainWeek = Roster.Types.Week(DomainDuty);\n\n/** Small staffing fixture with independently calculable hours and cost. */\nexport const context: ValueTypeOf<typeof Roster.Types.Context> = {\n people: new Map([\n [\"lead\", { key: \"lead\", name: \"Lead\", group: \"goods\", position: \"lead\", skills: new Set([\"reach\"]), contract: 40, agency: false, trainer: true, trainee: false, usual: some(\"early\") }],\n [\"agency\", { key: \"agency\", name: \"Agency\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 0, agency: true, trainer: false, trainee: false, usual: none }],\n [\"off\", { key: \"off\", name: \"Off\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 40, agency: false, trainer: false, trainee: false, usual: none }],\n ]),\n groups: [{ key: \"goods\", label: \"Goods\" }],\n // Goods' one skill, and the duty every group shares.\n activities: [{ key: \"reach\", label: \"Reach truck\", code: \"RT\", group: some(\"goods\") }, { key: \"general\", label: \"General duties\", code: \"GD\", group: none }],\n shifts: [\n { key: \"early\", label: \"Early\", code: \"E\", start: { hour: 6n, minute: 0n }, hours: 8 },\n { key: \"late\", label: \"Late\", code: \"L\", start: { hour: 14n, minute: 0n }, hours: 8 },\n { key: \"night\", label: \"Night\", code: \"N\", start: { hour: 22n, minute: 0n }, hours: 8 },\n ],\n positions: [{ key: \"crew\", label: \"Crew\", code: \"\", lead: false }, { key: \"lead\", label: \"Lead\", code: \"SV\", lead: true }],\n agencies: [{ key: \"agency\", name: \"Staffing agency\" }],\n rules: { rest: 10, lead: true, skill: true, trainer: true },\n};\n\nexport const slot = { day: 0n, group: \"goods\", shift: \"early\" };\n\n/** Sunday's early shift: the lead in an hour late and on to 17:00, an agency's worker, and a request to the agency. */\nexport const week: ValueTypeOf<typeof DomainWeek> = {\n status: variant(\"draft\", null), dismissed: new Set<string>(),\n assignments: new Map([\n [\"a\", { slot, who: variant(\"person\", \"lead\"), position: \"lead\", activity: some(\"reach\"),\n start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), hours: 10,\n row: { who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), note: \"in late, stays on\" } }],\n [\"b\", { slot, who: variant(\"person\", \"agency\"), position: \"crew\", activity: some(\"general\"),\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"staff\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n [\"c\", { slot, who: variant(\"request\", \"agency\"), position: \"crew\", activity: none,\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"agency\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n ]),\n requirements: new SortedMap([[slot, { positions: 4n, hours: new Map([[\"reach\", 24], [\"general\", 6]]) }]], compareFor(Roster.Types.Slot)),\n};\n\nEast.function([DomainWeek, Roster.Types.Context], FloatType, ($, week, context) => {\n const hours = $.const(Roster.weekHours(DomainDuty));\n return hours(week, context).get(\"lead\");\n })"
|
|
43399
|
+
"source": "// A person's week counts each duty's own hours: the lead's 07:00–17:00 counts ten\n// inputs: [week, context] returns: 10\n/** A duty of the app's own: who — one of its staff, or an agency asked to send someone — its own times, and a note the Roster carries but never reads. */\nexport const DomainDuty = StructType({\n who: VariantType({ staff: StringType, agency: StringType }), start: DateTimeType, end: DateTimeType, note: StringType,\n});\n\n/** A week of the app's duties, as the Roster reads it. */\nexport const DomainWeek = Roster.Types.Week(DomainDuty);\n\n/** Small staffing fixture with independently calculable hours and cost. */\nexport const context: ValueTypeOf<typeof Roster.Types.Context> = {\n people: new Map([\n [\"lead\", { key: \"lead\", name: \"Lead\", group: \"goods\", position: \"lead\", skills: new Set([\"reach\"]), contract: 40, agency: false, trainer: true, trainee: false, usual: some(\"early\") }],\n [\"agency\", { key: \"agency\", name: \"Agency\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 0, agency: true, trainer: false, trainee: false, usual: none }],\n [\"off\", { key: \"off\", name: \"Off\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 40, agency: false, trainer: false, trainee: false, usual: none }],\n ]),\n groups: [{ key: \"goods\", label: \"Goods\" }],\n // Goods' one skill, and the duty every group shares.\n activities: [{ key: \"reach\", label: \"Reach truck\", code: \"RT\", group: some(\"goods\") }, { key: \"general\", label: \"General duties\", code: \"GD\", group: none }],\n shifts: [\n { key: \"early\", label: \"Early\", code: \"E\", start: { hour: 6n, minute: 0n }, hours: 8, color: none },\n { key: \"late\", label: \"Late\", code: \"L\", start: { hour: 14n, minute: 0n }, hours: 8, color: none },\n { key: \"night\", label: \"Night\", code: \"N\", start: { hour: 22n, minute: 0n }, hours: 8, color: none },\n ],\n positions: [{ key: \"crew\", label: \"Crew\", code: \"\", lead: false }, { key: \"lead\", label: \"Lead\", code: \"SV\", lead: true }],\n agencies: [{ key: \"agency\", name: \"Staffing agency\" }],\n rules: { rest: 10, lead: true, skill: true, trainer: true },\n};\n\nexport const slot = { day: 0n, group: \"goods\", shift: \"early\" };\n\n/** Sunday's early shift: the lead in an hour late and on to 17:00, an agency's worker, and a request to the agency. */\nexport const week: ValueTypeOf<typeof DomainWeek> = {\n status: variant(\"draft\", null), dismissed: new Set<string>(),\n assignments: new Map([\n [\"a\", { slot, who: variant(\"person\", \"lead\"), position: \"lead\", activity: some(\"reach\"),\n start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), hours: 10,\n row: { who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), note: \"in late, stays on\" } }],\n [\"b\", { slot, who: variant(\"person\", \"agency\"), position: \"crew\", activity: some(\"general\"),\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"staff\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n [\"c\", { slot, who: variant(\"request\", \"agency\"), position: \"crew\", activity: none,\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"agency\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n ]),\n requirements: new SortedMap([[slot, { positions: 4n, hours: new Map([[\"reach\", 24], [\"general\", 6]]) }]], compareFor(Roster.Types.Slot)),\n};\n\nEast.function([DomainWeek, Roster.Types.Context], FloatType, ($, week, context) => {\n const hours = $.const(Roster.weekHours(DomainDuty));\n return hours(week, context).get(\"lead\");\n })"
|
|
43425
43400
|
},
|
|
43426
43401
|
{
|
|
43427
43402
|
"id": "e3-ui:domain.examples.ts:rosterRest",
|
|
@@ -43443,7 +43418,7 @@
|
|
|
43443
43418
|
"languages": [
|
|
43444
43419
|
"typescript"
|
|
43445
43420
|
],
|
|
43446
|
-
"source": "// A night duty that runs on to 08:00 leaves only six hours before the next day's late shift\n// inputs: [context] returns: 6\n/** A duty of the app's own: who — one of its staff, or an agency asked to send someone — its own times, and a note the Roster carries but never reads. */\nexport const DomainDuty = StructType({\n who: VariantType({ staff: StringType, agency: StringType }), start: DateTimeType, end: DateTimeType, note: StringType,\n});\n\n/** A week of the app's duties, as the Roster reads it. */\nexport const DomainWeek = Roster.Types.Week(DomainDuty);\n\n/** Small staffing fixture with independently calculable hours and cost. */\nexport const context: ValueTypeOf<typeof Roster.Types.Context> = {\n people: new Map([\n [\"lead\", { key: \"lead\", name: \"Lead\", group: \"goods\", position: \"lead\", skills: new Set([\"reach\"]), contract: 40, agency: false, trainer: true, trainee: false, usual: some(\"early\") }],\n [\"agency\", { key: \"agency\", name: \"Agency\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 0, agency: true, trainer: false, trainee: false, usual: none }],\n [\"off\", { key: \"off\", name: \"Off\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 40, agency: false, trainer: false, trainee: false, usual: none }],\n ]),\n groups: [{ key: \"goods\", label: \"Goods\" }],\n // Goods' one skill, and the duty every group shares.\n activities: [{ key: \"reach\", label: \"Reach truck\", code: \"RT\", group: some(\"goods\") }, { key: \"general\", label: \"General duties\", code: \"GD\", group: none }],\n shifts: [\n { key: \"early\", label: \"Early\", code: \"E\", start: { hour: 6n, minute: 0n }, hours: 8 },\n { key: \"late\", label: \"Late\", code: \"L\", start: { hour: 14n, minute: 0n }, hours: 8 },\n { key: \"night\", label: \"Night\", code: \"N\", start: { hour: 22n, minute: 0n }, hours: 8 },\n ],\n positions: [{ key: \"crew\", label: \"Crew\", code: \"\", lead: false }, { key: \"lead\", label: \"Lead\", code: \"SV\", lead: true }],\n agencies: [{ key: \"agency\", name: \"Staffing agency\" }],\n rules: { rest: 10, lead: true, skill: true, trainer: true },\n};\n\nEast.function([Roster.Types.Context], FloatType, ($, context) => {\n const rest = $.const(Roster.rest(DomainDuty));\n const night = $.const({ who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-05T22:00:00Z\"), end: new Date(\"2028-03-06T08:00:00Z\"), note: \"\" }, DomainDuty);\n const prior = $.const({ status: variant(\"draft\", null), dismissed: new Set<string>(), requirements: new Map(), assignments: new Map([\n [\"night\", { slot: { day: 0n, group: \"goods\", shift: \"night\" }, who: variant(\"person\", \"lead\"), position: \"lead\", activity: none,\n start: new Date(\"2028-03-05T22:00:00Z\"), end: new Date(\"2028-03-06T08:00:00Z\"), hours: 10, row: night }],\n ]) }, DomainWeek);\n const late = $.const({ who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-06T14:00:00Z\"), end: new Date(\"2028-03-06T22:00:00Z\"), note: \"\" }, DomainDuty);\n const next = $.const({ slot: { day: 1n, group: \"goods\", shift: \"late\" }, who: variant(\"person\", \"lead\"), position: \"lead\", activity: none,\n start: new Date(\"2028-03-06T14:00:00Z\"), end: new Date(\"2028-03-06T22:00:00Z\"), hours: 8, row: late }, Roster.Types.Assignment(DomainDuty));\n return rest(next, \"next\", prior, context).before;\n })"
|
|
43421
|
+
"source": "// A night duty that runs on to 08:00 leaves only six hours before the next day's late shift\n// inputs: [context] returns: 6\n/** A duty of the app's own: who — one of its staff, or an agency asked to send someone — its own times, and a note the Roster carries but never reads. */\nexport const DomainDuty = StructType({\n who: VariantType({ staff: StringType, agency: StringType }), start: DateTimeType, end: DateTimeType, note: StringType,\n});\n\n/** A week of the app's duties, as the Roster reads it. */\nexport const DomainWeek = Roster.Types.Week(DomainDuty);\n\n/** Small staffing fixture with independently calculable hours and cost. */\nexport const context: ValueTypeOf<typeof Roster.Types.Context> = {\n people: new Map([\n [\"lead\", { key: \"lead\", name: \"Lead\", group: \"goods\", position: \"lead\", skills: new Set([\"reach\"]), contract: 40, agency: false, trainer: true, trainee: false, usual: some(\"early\") }],\n [\"agency\", { key: \"agency\", name: \"Agency\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 0, agency: true, trainer: false, trainee: false, usual: none }],\n [\"off\", { key: \"off\", name: \"Off\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 40, agency: false, trainer: false, trainee: false, usual: none }],\n ]),\n groups: [{ key: \"goods\", label: \"Goods\" }],\n // Goods' one skill, and the duty every group shares.\n activities: [{ key: \"reach\", label: \"Reach truck\", code: \"RT\", group: some(\"goods\") }, { key: \"general\", label: \"General duties\", code: \"GD\", group: none }],\n shifts: [\n { key: \"early\", label: \"Early\", code: \"E\", start: { hour: 6n, minute: 0n }, hours: 8, color: none },\n { key: \"late\", label: \"Late\", code: \"L\", start: { hour: 14n, minute: 0n }, hours: 8, color: none },\n { key: \"night\", label: \"Night\", code: \"N\", start: { hour: 22n, minute: 0n }, hours: 8, color: none },\n ],\n positions: [{ key: \"crew\", label: \"Crew\", code: \"\", lead: false }, { key: \"lead\", label: \"Lead\", code: \"SV\", lead: true }],\n agencies: [{ key: \"agency\", name: \"Staffing agency\" }],\n rules: { rest: 10, lead: true, skill: true, trainer: true },\n};\n\nEast.function([Roster.Types.Context], FloatType, ($, context) => {\n const rest = $.const(Roster.rest(DomainDuty));\n const night = $.const({ who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-05T22:00:00Z\"), end: new Date(\"2028-03-06T08:00:00Z\"), note: \"\" }, DomainDuty);\n const prior = $.const({ status: variant(\"draft\", null), dismissed: new Set<string>(), requirements: new Map(), assignments: new Map([\n [\"night\", { slot: { day: 0n, group: \"goods\", shift: \"night\" }, who: variant(\"person\", \"lead\"), position: \"lead\", activity: none,\n start: new Date(\"2028-03-05T22:00:00Z\"), end: new Date(\"2028-03-06T08:00:00Z\"), hours: 10, row: night }],\n ]) }, DomainWeek);\n const late = $.const({ who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-06T14:00:00Z\"), end: new Date(\"2028-03-06T22:00:00Z\"), note: \"\" }, DomainDuty);\n const next = $.const({ slot: { day: 1n, group: \"goods\", shift: \"late\" }, who: variant(\"person\", \"lead\"), position: \"lead\", activity: none,\n start: new Date(\"2028-03-06T14:00:00Z\"), end: new Date(\"2028-03-06T22:00:00Z\"), hours: 8, row: late }, Roster.Types.Assignment(DomainDuty));\n return rest(next, \"next\", prior, context).before;\n })"
|
|
43447
43422
|
},
|
|
43448
43423
|
{
|
|
43449
43424
|
"id": "e3-ui:domain.examples.ts:rosterFitTrainer",
|
|
@@ -43468,7 +43443,7 @@
|
|
|
43468
43443
|
"languages": [
|
|
43469
43444
|
"typescript"
|
|
43470
43445
|
],
|
|
43471
|
-
"source": "// The ready-made fit: a trainer off that day whose rest is kept fits Goods' early shift, scored by spare contract hours and a week's more in the slot's group\n// inputs: [week, context] returns: variant(\"fits\", { score: 198, why: \"Goods · 30 h spare\", warn: none })\n/** A duty of the app's own: who — one of its staff, or an agency asked to send someone — its own times, and a note the Roster carries but never reads. */\nexport const DomainDuty = StructType({\n who: VariantType({ staff: StringType, agency: StringType }), start: DateTimeType, end: DateTimeType, note: StringType,\n});\n\n/** A week of the app's duties, as the Roster reads it. */\nexport const DomainWeek = Roster.Types.Week(DomainDuty);\n\n/** Small staffing fixture with independently calculable hours and cost. */\nexport const context: ValueTypeOf<typeof Roster.Types.Context> = {\n people: new Map([\n [\"lead\", { key: \"lead\", name: \"Lead\", group: \"goods\", position: \"lead\", skills: new Set([\"reach\"]), contract: 40, agency: false, trainer: true, trainee: false, usual: some(\"early\") }],\n [\"agency\", { key: \"agency\", name: \"Agency\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 0, agency: true, trainer: false, trainee: false, usual: none }],\n [\"off\", { key: \"off\", name: \"Off\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 40, agency: false, trainer: false, trainee: false, usual: none }],\n ]),\n groups: [{ key: \"goods\", label: \"Goods\" }],\n // Goods' one skill, and the duty every group shares.\n activities: [{ key: \"reach\", label: \"Reach truck\", code: \"RT\", group: some(\"goods\") }, { key: \"general\", label: \"General duties\", code: \"GD\", group: none }],\n shifts: [\n { key: \"early\", label: \"Early\", code: \"E\", start: { hour: 6n, minute: 0n }, hours: 8 },\n { key: \"late\", label: \"Late\", code: \"L\", start: { hour: 14n, minute: 0n }, hours: 8 },\n { key: \"night\", label: \"Night\", code: \"N\", start: { hour: 22n, minute: 0n }, hours: 8 },\n ],\n positions: [{ key: \"crew\", label: \"Crew\", code: \"\", lead: false }, { key: \"lead\", label: \"Lead\", code: \"SV\", lead: true }],\n agencies: [{ key: \"agency\", name: \"Staffing agency\" }],\n rules: { rest: 10, lead: true, skill: true, trainer: true },\n};\n\nexport const slot = { day: 0n, group: \"goods\", shift: \"early\" };\n\n/** Sunday's early shift: the lead in an hour late and on to 17:00, an agency's worker, and a request to the agency. */\nexport const week: ValueTypeOf<typeof DomainWeek> = {\n status: variant(\"draft\", null), dismissed: new Set<string>(),\n assignments: new Map([\n [\"a\", { slot, who: variant(\"person\", \"lead\"), position: \"lead\", activity: some(\"reach\"),\n start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), hours: 10,\n row: { who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), note: \"in late, stays on\" } }],\n [\"b\", { slot, who: variant(\"person\", \"agency\"), position: \"crew\", activity: some(\"general\"),\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"staff\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n [\"c\", { slot, who: variant(\"request\", \"agency\"), position: \"crew\", activity: none,\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"agency\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n ]),\n requirements: new SortedMap([[slot, { positions: 4n, hours: new Map([[\"reach\", 24], [\"general\", 6]]) }]], compareFor(Roster.Types.Slot)),\n};\n\nEast.function([DomainWeek, Roster.Types.Context], Roster.Types.Fit, ($, week, context) => {\n const coverage = $.const(Roster.coverage(DomainDuty));\n const fit = $.const(Roster.fit(DomainDuty));\n const noPay = $.const(new Map(), Roster.Types.Pay);\n // Monday's early shift: the lead's Sunday duty ends at 17:00, so 13 hours' rest is kept; 10 of their 40 contract hours are rostered.\n const request = $.const({ need: variant(\"trainers\", null), slot: { day: 1n, group: \"goods\", shift: \"early\" }, start: new Date(\"2028-03-05T00:00:00Z\"),\n week, context, coverage: coverage(week, context, noPay) }, Roster.Types.FitRequest(DomainDuty));\n return fit(request, context.people.get(\"lead\"));\n })"
|
|
43446
|
+
"source": "// The ready-made fit: a trainer off that day whose rest is kept fits Goods' early shift, scored by spare contract hours and a week's more in the slot's group\n// inputs: [week, context] returns: variant(\"fits\", { score: 198, why: \"Goods · 30 h spare\", warn: none })\n/** A duty of the app's own: who — one of its staff, or an agency asked to send someone — its own times, and a note the Roster carries but never reads. */\nexport const DomainDuty = StructType({\n who: VariantType({ staff: StringType, agency: StringType }), start: DateTimeType, end: DateTimeType, note: StringType,\n});\n\n/** A week of the app's duties, as the Roster reads it. */\nexport const DomainWeek = Roster.Types.Week(DomainDuty);\n\n/** Small staffing fixture with independently calculable hours and cost. */\nexport const context: ValueTypeOf<typeof Roster.Types.Context> = {\n people: new Map([\n [\"lead\", { key: \"lead\", name: \"Lead\", group: \"goods\", position: \"lead\", skills: new Set([\"reach\"]), contract: 40, agency: false, trainer: true, trainee: false, usual: some(\"early\") }],\n [\"agency\", { key: \"agency\", name: \"Agency\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 0, agency: true, trainer: false, trainee: false, usual: none }],\n [\"off\", { key: \"off\", name: \"Off\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 40, agency: false, trainer: false, trainee: false, usual: none }],\n ]),\n groups: [{ key: \"goods\", label: \"Goods\" }],\n // Goods' one skill, and the duty every group shares.\n activities: [{ key: \"reach\", label: \"Reach truck\", code: \"RT\", group: some(\"goods\") }, { key: \"general\", label: \"General duties\", code: \"GD\", group: none }],\n shifts: [\n { key: \"early\", label: \"Early\", code: \"E\", start: { hour: 6n, minute: 0n }, hours: 8, color: none },\n { key: \"late\", label: \"Late\", code: \"L\", start: { hour: 14n, minute: 0n }, hours: 8, color: none },\n { key: \"night\", label: \"Night\", code: \"N\", start: { hour: 22n, minute: 0n }, hours: 8, color: none },\n ],\n positions: [{ key: \"crew\", label: \"Crew\", code: \"\", lead: false }, { key: \"lead\", label: \"Lead\", code: \"SV\", lead: true }],\n agencies: [{ key: \"agency\", name: \"Staffing agency\" }],\n rules: { rest: 10, lead: true, skill: true, trainer: true },\n};\n\nexport const slot = { day: 0n, group: \"goods\", shift: \"early\" };\n\n/** Sunday's early shift: the lead in an hour late and on to 17:00, an agency's worker, and a request to the agency. */\nexport const week: ValueTypeOf<typeof DomainWeek> = {\n status: variant(\"draft\", null), dismissed: new Set<string>(),\n assignments: new Map([\n [\"a\", { slot, who: variant(\"person\", \"lead\"), position: \"lead\", activity: some(\"reach\"),\n start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), hours: 10,\n row: { who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), note: \"in late, stays on\" } }],\n [\"b\", { slot, who: variant(\"person\", \"agency\"), position: \"crew\", activity: some(\"general\"),\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"staff\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n [\"c\", { slot, who: variant(\"request\", \"agency\"), position: \"crew\", activity: none,\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"agency\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n ]),\n requirements: new SortedMap([[slot, { positions: 4n, hours: new Map([[\"reach\", 24], [\"general\", 6]]) }]], compareFor(Roster.Types.Slot)),\n};\n\nEast.function([DomainWeek, Roster.Types.Context], Roster.Types.Fit, ($, week, context) => {\n const coverage = $.const(Roster.coverage(DomainDuty));\n const fit = $.const(Roster.fit(DomainDuty));\n const noPay = $.const(new Map(), Roster.Types.Pay);\n // Monday's early shift: the lead's Sunday duty ends at 17:00, so 13 hours' rest is kept; 10 of their 40 contract hours are rostered.\n const request = $.const({ need: variant(\"trainers\", null), slot: { day: 1n, group: \"goods\", shift: \"early\" }, start: new Date(\"2028-03-05T00:00:00Z\"),\n week, context, coverage: coverage(week, context, noPay) }, Roster.Types.FitRequest(DomainDuty));\n return fit(request, context.people.get(\"lead\"));\n })"
|
|
43472
43447
|
},
|
|
43473
43448
|
{
|
|
43474
43449
|
"id": "e3-ui:domain.examples.ts:rosterAnswerLeads",
|
|
@@ -43493,7 +43468,47 @@
|
|
|
43493
43468
|
"languages": [
|
|
43494
43469
|
"typescript"
|
|
43495
43470
|
],
|
|
43496
|
-
"source": "// Roster.answer maps a rule over every person, echoing the need and the slot: the lead fits, and the two associates are refused as not leads\n// inputs: [week, context] returns: {\n need: variant(\"leads\", null), slot: { day: 1n, group: \"goods\", shift: \"early\" },\n people: new Map<string, ValueTypeOf<typeof Roster.Types.Fit>>([\n [\"agency\", variant(\"refused\", { group: \"Not a lead\", reason: \"Crew · not a lead\", measure: variant(\"none\", null) })],\n [\"lead\", variant(\"fits\", { score: 198, why: \"Goods · 30 h spare\", warn: none })],\n [\"off\", variant(\"refused\", { group: \"Not a lead\", reason: \"Crew · not a lead\", measure: variant(\"none\", null) })],\n ]),\n }\n/** A duty of the app's own: who — one of its staff, or an agency asked to send someone — its own times, and a note the Roster carries but never reads. */\nexport const DomainDuty = StructType({\n who: VariantType({ staff: StringType, agency: StringType }), start: DateTimeType, end: DateTimeType, note: StringType,\n});\n\n/** A week of the app's duties, as the Roster reads it. */\nexport const DomainWeek = Roster.Types.Week(DomainDuty);\n\n/** Small staffing fixture with independently calculable hours and cost. */\nexport const context: ValueTypeOf<typeof Roster.Types.Context> = {\n people: new Map([\n [\"lead\", { key: \"lead\", name: \"Lead\", group: \"goods\", position: \"lead\", skills: new Set([\"reach\"]), contract: 40, agency: false, trainer: true, trainee: false, usual: some(\"early\") }],\n [\"agency\", { key: \"agency\", name: \"Agency\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 0, agency: true, trainer: false, trainee: false, usual: none }],\n [\"off\", { key: \"off\", name: \"Off\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 40, agency: false, trainer: false, trainee: false, usual: none }],\n ]),\n groups: [{ key: \"goods\", label: \"Goods\" }],\n // Goods' one skill, and the duty every group shares.\n activities: [{ key: \"reach\", label: \"Reach truck\", code: \"RT\", group: some(\"goods\") }, { key: \"general\", label: \"General duties\", code: \"GD\", group: none }],\n shifts: [\n { key: \"early\", label: \"Early\", code: \"E\", start: { hour: 6n, minute: 0n }, hours: 8 },\n { key: \"late\", label: \"Late\", code: \"L\", start: { hour: 14n, minute: 0n }, hours: 8 },\n { key: \"night\", label: \"Night\", code: \"N\", start: { hour: 22n, minute: 0n }, hours: 8 },\n ],\n positions: [{ key: \"crew\", label: \"Crew\", code: \"\", lead: false }, { key: \"lead\", label: \"Lead\", code: \"SV\", lead: true }],\n agencies: [{ key: \"agency\", name: \"Staffing agency\" }],\n rules: { rest: 10, lead: true, skill: true, trainer: true },\n};\n\nexport const slot = { day: 0n, group: \"goods\", shift: \"early\" };\n\n/** Sunday's early shift: the lead in an hour late and on to 17:00, an agency's worker, and a request to the agency. */\nexport const week: ValueTypeOf<typeof DomainWeek> = {\n status: variant(\"draft\", null), dismissed: new Set<string>(),\n assignments: new Map([\n [\"a\", { slot, who: variant(\"person\", \"lead\"), position: \"lead\", activity: some(\"reach\"),\n start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), hours: 10,\n row: { who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), note: \"in late, stays on\" } }],\n [\"b\", { slot, who: variant(\"person\", \"agency\"), position: \"crew\", activity: some(\"general\"),\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"staff\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n [\"c\", { slot, who: variant(\"request\", \"agency\"), position: \"crew\", activity: none,\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"agency\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n ]),\n requirements: new SortedMap([[slot, { positions: 4n, hours: new Map([[\"reach\", 24], [\"general\", 6]]) }]], compareFor(Roster.Types.Slot)),\n};\n\nEast.function([DomainWeek, Roster.Types.Context], Roster.Types.FitResult, ($, week, context) => {\n const coverage = $.const(Roster.coverage(DomainDuty));\n const answer = $.const(Roster.answer(DomainDuty));\n const fit = $.const(Roster.fit(DomainDuty));\n const noPay = $.const(new Map(), Roster.Types.Pay);\n const request = $.const({ need: variant(\"leads\", null), slot: { day: 1n, group: \"goods\", shift: \"early\" }, start: new Date(\"2028-03-05T00:00:00Z\"),\n week, context, coverage: coverage(week, context, noPay) }, Roster.Types.FitRequest(DomainDuty));\n return answer(request, fit);\n })"
|
|
43471
|
+
"source": "// Roster.answer maps a rule over every person, echoing the need and the slot: the lead fits, and the two associates are refused as not leads\n// inputs: [week, context] returns: {\n need: variant(\"leads\", null), slot: { day: 1n, group: \"goods\", shift: \"early\" },\n people: new Map<string, ValueTypeOf<typeof Roster.Types.Fit>>([\n [\"agency\", variant(\"refused\", { group: \"Not a lead\", reason: \"Crew · not a lead\", measure: variant(\"none\", null) })],\n [\"lead\", variant(\"fits\", { score: 198, why: \"Goods · 30 h spare\", warn: none })],\n [\"off\", variant(\"refused\", { group: \"Not a lead\", reason: \"Crew · not a lead\", measure: variant(\"none\", null) })],\n ]),\n }\n/** A duty of the app's own: who — one of its staff, or an agency asked to send someone — its own times, and a note the Roster carries but never reads. */\nexport const DomainDuty = StructType({\n who: VariantType({ staff: StringType, agency: StringType }), start: DateTimeType, end: DateTimeType, note: StringType,\n});\n\n/** A week of the app's duties, as the Roster reads it. */\nexport const DomainWeek = Roster.Types.Week(DomainDuty);\n\n/** Small staffing fixture with independently calculable hours and cost. */\nexport const context: ValueTypeOf<typeof Roster.Types.Context> = {\n people: new Map([\n [\"lead\", { key: \"lead\", name: \"Lead\", group: \"goods\", position: \"lead\", skills: new Set([\"reach\"]), contract: 40, agency: false, trainer: true, trainee: false, usual: some(\"early\") }],\n [\"agency\", { key: \"agency\", name: \"Agency\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 0, agency: true, trainer: false, trainee: false, usual: none }],\n [\"off\", { key: \"off\", name: \"Off\", group: \"goods\", position: \"crew\", skills: new Set<string>(), contract: 40, agency: false, trainer: false, trainee: false, usual: none }],\n ]),\n groups: [{ key: \"goods\", label: \"Goods\" }],\n // Goods' one skill, and the duty every group shares.\n activities: [{ key: \"reach\", label: \"Reach truck\", code: \"RT\", group: some(\"goods\") }, { key: \"general\", label: \"General duties\", code: \"GD\", group: none }],\n shifts: [\n { key: \"early\", label: \"Early\", code: \"E\", start: { hour: 6n, minute: 0n }, hours: 8, color: none },\n { key: \"late\", label: \"Late\", code: \"L\", start: { hour: 14n, minute: 0n }, hours: 8, color: none },\n { key: \"night\", label: \"Night\", code: \"N\", start: { hour: 22n, minute: 0n }, hours: 8, color: none },\n ],\n positions: [{ key: \"crew\", label: \"Crew\", code: \"\", lead: false }, { key: \"lead\", label: \"Lead\", code: \"SV\", lead: true }],\n agencies: [{ key: \"agency\", name: \"Staffing agency\" }],\n rules: { rest: 10, lead: true, skill: true, trainer: true },\n};\n\nexport const slot = { day: 0n, group: \"goods\", shift: \"early\" };\n\n/** Sunday's early shift: the lead in an hour late and on to 17:00, an agency's worker, and a request to the agency. */\nexport const week: ValueTypeOf<typeof DomainWeek> = {\n status: variant(\"draft\", null), dismissed: new Set<string>(),\n assignments: new Map([\n [\"a\", { slot, who: variant(\"person\", \"lead\"), position: \"lead\", activity: some(\"reach\"),\n start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), hours: 10,\n row: { who: variant(\"staff\", \"lead\"), start: new Date(\"2028-03-05T07:00:00Z\"), end: new Date(\"2028-03-05T17:00:00Z\"), note: \"in late, stays on\" } }],\n [\"b\", { slot, who: variant(\"person\", \"agency\"), position: \"crew\", activity: some(\"general\"),\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"staff\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n [\"c\", { slot, who: variant(\"request\", \"agency\"), position: \"crew\", activity: none,\n start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), hours: 8,\n row: { who: variant(\"agency\", \"agency\"), start: new Date(\"2028-03-05T06:00:00Z\"), end: new Date(\"2028-03-05T14:00:00Z\"), note: \"\" } }],\n ]),\n requirements: new SortedMap([[slot, { positions: 4n, hours: new Map([[\"reach\", 24], [\"general\", 6]]) }]], compareFor(Roster.Types.Slot)),\n};\n\nEast.function([DomainWeek, Roster.Types.Context], Roster.Types.FitResult, ($, week, context) => {\n const coverage = $.const(Roster.coverage(DomainDuty));\n const answer = $.const(Roster.answer(DomainDuty));\n const fit = $.const(Roster.fit(DomainDuty));\n const noPay = $.const(new Map(), Roster.Types.Pay);\n const request = $.const({ need: variant(\"leads\", null), slot: { day: 1n, group: \"goods\", shift: \"early\" }, start: new Date(\"2028-03-05T00:00:00Z\"),\n week, context, coverage: coverage(week, context, noPay) }, Roster.Types.FitRequest(DomainDuty));\n return answer(request, fit);\n })"
|
|
43472
|
+
},
|
|
43473
|
+
{
|
|
43474
|
+
"id": "e3-ui:optimise.examples.tsx:rosterOptimise",
|
|
43475
|
+
"skill": "e3-ui",
|
|
43476
|
+
"package": "e3-ui",
|
|
43477
|
+
"file": "east-ui/packages/e3-ui/test/roster/optimise.examples.tsx",
|
|
43478
|
+
"suite": "",
|
|
43479
|
+
"test": "The warehouse roster with the Elara eye (#1409): `optimise` is Func.bind of the warehouse's optimiser on e3 — Roster.Types.OptimiseRequest(WarehouseDuty) in, the week in view, the run number, the week's duties as drafted, its targets by slot and the Roster's context; Roster.Types.OptimiseResult(WarehouseDuty) out, the duties complete: every slot short of its heads filled from the people of its group, then everyone to their contract from their usual shift — on any week, an empty one too — within their contract and ten hours clear of their other duties. A click on the eye runs it once over the week; the result lands in the session as one edit, which one Undo reverts and Save commits with everything else.",
|
|
43480
|
+
"keywords": [
|
|
43481
|
+
"Roster",
|
|
43482
|
+
"optimise",
|
|
43483
|
+
"Optimise",
|
|
43484
|
+
"Elara",
|
|
43485
|
+
"OptimiseRequest",
|
|
43486
|
+
"OptimiseResult",
|
|
43487
|
+
"Func.bind",
|
|
43488
|
+
"e3.function",
|
|
43489
|
+
"one edit",
|
|
43490
|
+
"Undo",
|
|
43491
|
+
"Schedule.events",
|
|
43492
|
+
"Schedule.resources",
|
|
43493
|
+
"Schedule.periods",
|
|
43494
|
+
"Roster.targets",
|
|
43495
|
+
"Roster.patterns",
|
|
43496
|
+
"people",
|
|
43497
|
+
"week",
|
|
43498
|
+
"fit"
|
|
43499
|
+
],
|
|
43500
|
+
"imports": [
|
|
43501
|
+
"import { ArrayType, DateTimeType, DictType, East, FloatType, IntegerType, SetType, StringType, StructType, example, none, variant, type BlockBuilder, type ExprType } from \"@elaraai/east\";",
|
|
43502
|
+
"import e3 from \"@elaraai/e3\";",
|
|
43503
|
+
"import { Box, Reactive, Slice, UIComponentType } from \"@elaraai/east-ui\";",
|
|
43504
|
+
"import { Data, Func, Record, Roster, Schedule } from \"@elaraai/e3-ui\";",
|
|
43505
|
+
"import { WarehouseDuty } from \"./warehouse.js\";",
|
|
43506
|
+
"import {"
|
|
43507
|
+
],
|
|
43508
|
+
"languages": [
|
|
43509
|
+
"tsx"
|
|
43510
|
+
],
|
|
43511
|
+
"source": "// The warehouse roster with the Elara eye (#1409): `optimise` is Func.bind of the warehouse's optimiser on e3 — Roster.Types.OptimiseRequest(WarehouseDuty) in, the week in view, the run number, the week's duties as drafted, its targets by slot and the Roster's context; Roster.Types.OptimiseResult(WarehouseDuty) out, the duties complete: every slot short of its heads filled from the people of its group, then everyone to their contract from their usual shift — on any week, an empty one too — within their contract and ten hours clear of their other duties. A click on the eye runs it once over the week; the result lands in the session as one edit, which one Undo reverts and Save commits with everything else.\n// inputs: []\n/** One person on one day of the week: who is booked when, so a run never books the same person twice on a day. */\nconst Booked = StructType({ day: IntegerType, person: StringType });\n\n/** A person's duty's times: what a new duty of theirs keeps ten hours clear of — the warehouse's rest rule. */\nconst Span = StructType({ person: StringType, start: DateTimeType, end: DateTimeType });\n\nconst Request = Roster.Types.OptimiseRequest(WarehouseDuty), Result = Roster.Types.OptimiseResult(WarehouseDuty);\n\ntype Block = BlockBuilder<typeof Result>;\n\ntype PersonExpr = ExprType<typeof Roster.Types.Person>;\n\ntype ShiftExpr = ExprType<typeof Roster.Types.Shift>;\n\n/**\n * The warehouse's optimiser's body, in two passes over the week as drafted, every duty it held kept as it was. First the\n * targets: every slot short of its heads is filled from the people of its group who can take it, those with the fewest\n * hours this week first. Then everyone to their contract, whatever the week's targets — so an empty week fills, this one\n * or any other: on each of their days off, their usual shift in their own group. A person takes a shift only off that\n * day, within their contract's hours and ten hours clear of every other duty of theirs (the warehouse's rest rule); the\n * agency's workers are left out. A new duty runs the shift's hours on its day, booked to the team's cost centre, under a\n * key of the run's own.\n */\nexport const rosterOptimiseBody = East.function([Request], Result, ($, req) => {\n const planned = $.let(new Map(), DictType(StringType, WarehouseDuty));\n // Who is booked on each day, each person's hours this week and each team's cost centre, from the week as drafted.\n const booked = $.let(new Set<{ day: bigint; person: string }>(), SetType(Booked));\n const spans = $.let(new Set<{ person: string; start: Date; end: Date }>(), SetType(Span));\n const hours = $.let(new Map(), DictType(StringType, FloatType));\n const centres = $.let(new Map(), DictType(StringType, StringType));\n $.for(req.duties, ($2, duty, key) => {\n $2(planned.insert(key, duty));\n $2(centres.insertOrUpdate(duty.team, duty.costCentre, (_$3, held, _given) => held));\n $2.match(duty.who, { staff: ($3, person) => {\n const day = $3.const(req.from.durationMilliseconds(duty.start).divide(86_400_000n));\n // A person drafted twice on a day is booked once.\n $3.if(booked.has({ day, person }).not(), ($4) => { $4(booked.insert({ day, person })); });\n $3(spans.insert({ person, start: duty.start, end: duty.end }));\n $3(hours.insertOrUpdate(person, duty.start.durationHours(duty.end), (_$4, held, more) => held.add(more)));\n } });\n });\n const shifts = $.const(req.context.shifts.toDict(($2, shift) => shift.key, ($2, shift) => shift));\n const people = $.const(req.context.people.toArray(($2, person) => person));\n const days = $.let([0n, 1n, 2n, 3n, 4n, 5n, 6n], ArrayType(IntegerType));\n const added = $.let(0n, IntegerType);\n /** A shift's start on a day of the week, and its end for its hours — fractional hours as whole minutes. */\n const timesOf = (day: ExprType<IntegerType>, shift: ShiftExpr) => {\n const start = req.from.addDays(day).addHours(shift.start.hour).addMinutes(shift.start.minute);\n return { start, end: start.addMinutes(East.Float.roundHalf(shift.hours.multiply(60.0))) };\n };\n /** Whether a person can take a shift on a day: off that day, within their contract, and ten hours clear of every other duty of theirs. */\n const takes = (person: PersonExpr, day: ExprType<IntegerType>, shift: ShiftExpr, start: ExprType<DateTimeType>, end: ExprType<DateTimeType>) =>\n booked.has({ day, person: person.key }).not()\n .and(() => hours.get(person.key, () => 0.0).add(shift.hours).lessEqual(person.contract))\n .and(() => spans.filter(($2, span) => span.person.equal(person.key).and(() => span.end.durationHours(start).less(10.0)).and(() => end.durationHours(span.start).less(10.0))).size().equal(0n));\n /** A new duty: the person on the shift that day in the group, at the shift's times, booked to the group's cost centre, under a key of the run's own. */\n const place = ($2: Block, person: PersonExpr, day: ExprType<IntegerType>, shift: ShiftExpr, group: ExprType<StringType>, start: ExprType<DateTimeType>, end: ExprType<DateTimeType>) => {\n $2.assign(added, added.add(1n));\n $2(planned.insert(East.str`elara-${req.run}-${added}`, {\n who: variant(\"staff\", person.key), start, end, shift: shift.key, team: group, role: person.position, task: none,\n unpaidBreak: 0.0, agreed: none, costCentre: centres.get(group, () => \"\"), note: \"\",\n }));\n $2(booked.insert({ day, person: person.key }));\n $2(spans.insert({ person: person.key, start, end }));\n $2(hours.insertOrUpdate(person.key, shift.hours, (_$3, held, more) => held.add(more)));\n };\n // First the targets: every slot short of its heads, from the group's own people who can take it, the fewest hours first.\n $.for(req.requirements, ($2, need, slot) => {\n // The slot's heads, less the duties already on it.\n const filled = $2.const(planned.filter(($3, duty) => duty.team.equal(slot.group).and(() => duty.shift.equal(slot.shift))\n .and(() => req.from.durationMilliseconds(duty.start).divide(86_400_000n).equal(slot.day))).size());\n const open = $2.const(need.positions.subtract(filled));\n $2.if(open.greater(0n).and(() => shifts.has(slot.shift)), ($3) => {\n const shift = $3.const(shifts.get(slot.shift));\n const times = timesOf(slot.day, shift);\n const start = $3.const(times.start), end = $3.const(times.end);\n const free = $3.const(people\n .filter(($4, person) => person.group.equal(slot.group).and(() => person.agency.not()).and(() => takes(person, slot.day, shift, start, end)))\n .sort(($4, person) => hours.get(person.key, () => 0.0)));\n $3.for(free, ($4, person, i) => {\n $4.if(i.less(open), ($5) => { place($5, person, slot.day, shift, slot.group, start, end); });\n });\n });\n });\n // Then everyone to their contract, whatever the week's targets: on each of their days off, their usual shift in their own\n // group, while another fits within their contract and keeps ten hours clear of their other duties.\n $.for(people, ($2, person) => {\n $2.match(person.usual, { some: ($3, usual) => {\n $3.if(person.agency.not().and(() => shifts.has(usual)), ($4) => {\n const shift = $4.const(shifts.get(usual));\n $4.for(days, ($5, day) => {\n const times = timesOf(day, shift);\n const start = $5.const(times.start), end = $5.const(times.end);\n $5.if(takes(person, day, shift, start, end), ($6) => { place($6, person, day, shift, person.group, start, end); });\n });\n });\n } });\n });\n return { duties: planned };\n});\n\n/** The warehouse's optimiser, on e3: the week's duties in, the week's duties out — its targets met, then everyone to their contract, whatever the week. */\nexport const rotaOptimise = e3.function(\"rota_optimise\", rosterOptimiseBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const rota = $.let(Record.bind(rosterDuties, [rosterDutiesPatch]));\n const entries = $.let(Data.bindPaged(rosterDuties));\n const byTime = $.let(Data.bindPaged(rosterDuties, { indexes: [rosterDutiesByTime] }));\n const weeks = $.let(Record.bind(rosterWeeks, [rosterWeeksPatch]));\n const cover = $.let(Record.bind(rosterCover, [rosterCoverPatch]));\n // Every person and activity is the calculation scope — and the optimiser's: the narrowing only chooses which show (#1342).\n const people = $.let(Data.bind(rosterStaff));\n const acts = $.let(Data.bind(rosterActivities));\n const staffIndex = $.let(Data.bindPaged(rosterStaff, { indexes: [rosterStaffByTeamName, rosterStaffByPosition, rosterStaffByTrainer, rosterStaffByAgency, rosterStaffBySkill, rosterStaffByName] }));\n const actIndex = $.let(Data.bindPaged(rosterActivities, { indexes: [rosterActivitiesByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.rosterOptimise\", {\n duty: { source: byTime, range: \"start\" },\n people: { source: staffIndex, icon: { prefix: \"fas\", name: \"user\" }, search: [\"name\", \"skills\"],\n fields: { team: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\"] }, position: { label: \"Position\", hints: [\"lead\", \"deputy\", \"acting\", \"crew\"] },\n trainer: { label: \"Trainer\" }, agency: { label: \"Agency\" }, skills: { label: \"Skill\" }, name: { label: \"Name\" } } },\n activities: { source: actIndex, icon: { prefix: \"fas\", name: \"list-check\" }, fields: { group: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\", null] } } },\n }, { presets: [{ id: \"leads\", name: \"Leads\", where: { people: { position: [\"lead\", \"deputy\", \"acting\"] } } }] }));\n const firms = $.let(Data.bind(rosterAgencies));\n const times = $.let(Data.bind(rosterShiftTimes));\n const teams = $.let(Data.bind(rosterTeams));\n const roles = $.let(Data.bind(rosterPositions));\n const ideas = $.let(Data.bind(rosterSuggestions));\n const weekPatterns = $.let(Data.bind(rosterPatterns));\n const fit = $.let(Func.bind(rosterStaffFit));\n // The app's optimiser: the eye calls it once per run over the week in view, and its answer is one edit.\n const optimise = $.let(Func.bind(rotaOptimise));\n return <Box width=\"100%\" height=\"760px\"><Roster id=\"rosterOptimise\" week={{ start: \"sunday\" }} slice={{ slice }} inspector fit={fit} optimise={optimise}\n view={{ layout: \"people\", period: \"week\", date: new Date(\"2028-03-09T00:00:00Z\") }}\n tileTimes=\"different\"\n events={{ duty: Schedule.events(rota, {\n name: \"Duty\", icon: \"user-clock\", entries,\n start: \"start\", end: \"end\", resource: { field: \"who\", of: { staff: \"people\", agency: \"agencies\" } },\n shift: \"shift\", group: \"team\", position: \"role\", activity: \"task\",\n hours: d => d.start.durationMinutes(d.end).subtract(d.unpaidBreak).divide(60.0),\n }) }}\n resources={{\n people: Schedule.resources(people.read(), { name: \"People\", icon: \"user\", label: p => p.name, group: p => p.team,\n position: p => p.position, skills: p => p.skills, contract: p => p.contract, agency: p => p.agency,\n trainer: p => p.trainer, trainee: p => p.trainee, usual: p => p.usual }),\n agencies: Schedule.resources(firms.read(), { name: \"Agencies\", icon: \"building\", label: a => a.name }),\n }}\n periods={Schedule.periods(weeks, { every: \"week\", status: { field: \"state\", draft: \"open\", published: \"issued\" }, dismissed: \"dismissed\" })}\n targets={Roster.targets(cover, { day: \"day\", group: \"team\", shift: \"shift\", positions: \"heads\", hours: \"hours\" })}\n shifts={Roster.shifts(times.read(), { label: s => s.label, code: s => s.code, start: s => s.start, hours: s => s.hours, order: s => s.order, color: s => s.colour })}\n groups={Roster.groups(teams.read(), { label: t => t.label, order: t => t.order })}\n positions={Roster.positions(roles.read(), { label: p => p.label, code: p => p.code, lead: p => p.lead, order: p => p.order })}\n activities={Roster.activities(acts.read(), { label: a => a.label, code: a => a.code, group: a => a.group, order: a => a.order })}\n proposals={Roster.proposals(ideas.read(), { person: s => s.person, day: s => s.day, group: s => s.team, shift: s => s.shift, reason: s => s.why })}\n patterns={Roster.patterns(weekPatterns.read(), { name: \"Patterns\", icon: \"calendar-week\", label: w => w.label, days: w => w.days, order: w => w.order })}\n rules={{ rest: 10.0 }}\n library={[Roster.library.people(), Roster.library.patterns(), Roster.library.activities()]} />\n </Box>;\n }}</Reactive>)"
|
|
43497
43512
|
},
|
|
43498
43513
|
{
|
|
43499
43514
|
"id": "e3-ui:roster-records.examples.tsx:rosterRecordIndexes",
|
|
@@ -43529,7 +43544,7 @@
|
|
|
43529
43544
|
"languages": [
|
|
43530
43545
|
"tsx"
|
|
43531
43546
|
],
|
|
43532
|
-
"source": "// 500 staff and a year of duties. The week's duties are read through the slice's duty source — an interval index over their times — and the rota's weeks by key, so no record is read whole; indexed staff narrowing keeps full-staff coverage and rules, and a Find fix asks the warehouse's greedy fit over every person, whatever the rail shows. Hidden duty edits Save by their own keys.\n// inputs: []\nconst stringOrder = compareFor(StringType);\n\nconst WEEK = 7 * 86_400_000;\n\n/** A larger staff pool for these indexed examples, preserving the mock's named people. */\nfunction staffSeed() {\n const people = new Map(warehouseStaff.default);\n const originals = [...people.values()];\n for (let i = 0; people.size < 500; i++) {\n const person = originals[i % originals.length]!;\n people.set(`pool-${1000 + i}`, { ...person, name: `${person.name} ${i + 1}` });\n }\n return people;\n}\n\n/** How many weeks of history before the warehouse's two: with its two, a year. */\nconst HISTORY = 50;\n\n/** A year of duties: the warehouse's two weeks, and each of the fifty weeks before them the week before's duties again, in its own week. */\nfunction dutySeed() {\n const duties = new SortedMap<string, ValueTypeOf<typeof WarehouseDuty>>(warehouseDuties, stringOrder);\n const before = [...warehouseDuties].filter(([, duty]) => duty.start.getTime() < warehousePreviousStart.getTime() + WEEK);\n for (let week = 1; week <= HISTORY; week++) {\n for (const [key, duty] of before) {\n duties.set(`h${week}-${key}`, { ...duty, start: new Date(duty.start.getTime() - week * WEEK), end: new Date(duty.end.getTime() - week * WEEK) });\n }\n }\n return duties;\n}\n\n/** The rota's weeks over the year: the warehouse's two, and each week of history issued at its end. */\nfunction weekSeed() {\n const weeks = new SortedMap<Date, ValueTypeOf<typeof WarehouseWeek>>(warehouseWeeks, compareFor(DateTimeType));\n const issued = warehouseWeeks.get(warehousePreviousStart)!;\n for (let week = 1; week <= HISTORY; week++) {\n const start = new Date(warehousePreviousStart.getTime() - week * WEEK);\n weeks.set(start, { ...issued, state: variant(\"issued\", { at: new Date(start.getTime() + WEEK - 12 * 3_600_000) }) });\n }\n return weeks;\n}\n\nexport const indexedRosterDuties = e3.record(\"indexed_roster_duties\", DictType(StringType, WarehouseDuty), dutySeed());\n\nexport const indexedRosterDutiesPatch = e3.mutation.patch(indexedRosterDuties);\n\n/** The year's duties by their times: what the Roster's slice reads a week of them through. */\nexport const indexedRosterDutiesByTime = e3.recordIndex(\"indexed_roster_duties_by_time\", indexedRosterDuties, {
|
|
43547
|
+
"source": "// 500 staff and a year of duties. The week's duties are read through the slice's duty source — an interval index over their times — and the rota's weeks by key, so no record is read whole; indexed staff narrowing keeps full-staff coverage and rules, and a Find fix asks the warehouse's greedy fit over every person, whatever the rail shows. Hidden duty edits Save by their own keys.\n// inputs: []\nconst stringOrder = compareFor(StringType);\n\nconst WEEK = 7 * 86_400_000;\n\n/** A larger staff pool for these indexed examples, preserving the mock's named people. */\nfunction staffSeed() {\n const people = new Map(warehouseStaff.default);\n const originals = [...people.values()];\n for (let i = 0; people.size < 500; i++) {\n const person = originals[i % originals.length]!;\n people.set(`pool-${1000 + i}`, { ...person, name: `${person.name} ${i + 1}` });\n }\n return people;\n}\n\n/** How many weeks of history before the warehouse's two: with its two, a year. */\nconst HISTORY = 50;\n\n/** A year of duties: the warehouse's two weeks, and each of the fifty weeks before them the week before's duties again, in its own week. */\nfunction dutySeed() {\n const duties = new SortedMap<string, ValueTypeOf<typeof WarehouseDuty>>(warehouseDuties, stringOrder);\n const before = [...warehouseDuties].filter(([, duty]) => duty.start.getTime() < warehousePreviousStart.getTime() + WEEK);\n for (let week = 1; week <= HISTORY; week++) {\n for (const [key, duty] of before) {\n duties.set(`h${week}-${key}`, { ...duty, start: new Date(duty.start.getTime() - week * WEEK), end: new Date(duty.end.getTime() - week * WEEK) });\n }\n }\n return duties;\n}\n\n/** The rota's weeks over the year: the warehouse's two, and each week of history issued at its end. */\nfunction weekSeed() {\n const weeks = new SortedMap<Date, ValueTypeOf<typeof WarehouseWeek>>(warehouseWeeks, compareFor(DateTimeType));\n const issued = warehouseWeeks.get(warehousePreviousStart)!;\n for (let week = 1; week <= HISTORY; week++) {\n const start = new Date(warehousePreviousStart.getTime() - week * WEEK);\n weeks.set(start, { ...issued, state: variant(\"issued\", { at: new Date(start.getTime() + WEEK - 12 * 3_600_000) }) });\n }\n return weeks;\n}\n\nexport const indexedRosterDuties = e3.record(\"indexed_roster_duties\", DictType(StringType, WarehouseDuty), dutySeed());\n\nexport const indexedRosterDutiesPatch = e3.mutation.patch(indexedRosterDuties);\n\n/** The year's duties by their times: what the Roster's slice reads a week of them through. */\nexport const indexedRosterDutiesByTime = e3.recordIndex.interval(\"indexed_roster_duties_by_time\", indexedRosterDuties, { from: \"start\", to: \"end\" });\n\nexport const indexedRosterWeeks = e3.record(\"indexed_roster_weeks\", DictType(DateTimeType, WarehouseWeek), weekSeed());\n\nexport const indexedRosterWeeksPatch = e3.mutation.patch(indexedRosterWeeks);\n\nexport const indexedRosterStaff = e3.record(\"indexed_roster_staff\", warehouseStaff.type, staffSeed());\n\nexport const rosterStaffByName = e3.recordIndex(\"staff_by_name\", indexedRosterStaff, { on: [\"name\"] });\n\nexport const rosterStaffByTeam = e3.recordIndex(\"staff_by_team_name\", indexedRosterStaff, { on: [\"team\", \"name\"] });\n\nexport const rosterStaffByTrainer = e3.recordIndex(\"staff_by_trainer\", indexedRosterStaff, { on: [\"trainer\"] });\n\nexport const rosterStaffBySkill = e3.recordIndex(\"staff_by_skill\", indexedRosterStaff, { on: [\"skills\"] });\n\nexport const rosterStaffByPosition = e3.recordIndex(\"staff_by_position\", indexedRosterStaff, { on: [\"position\"] });\n\nexport const rosterStaffByAgency = e3.recordIndex(\"staff_by_agency\", indexedRosterStaff, { on: [\"agency\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const rota = $.let(Record.bind(indexedRosterDuties, [indexedRosterDutiesPatch]));\n const entries = $.let(Data.bindPaged(indexedRosterDuties));\n const byTime = $.let(Data.bindPaged(indexedRosterDuties, { indexes: [indexedRosterDutiesByTime] }));\n const weeks = $.let(Record.bind(indexedRosterWeeks, [indexedRosterWeeksPatch]));\n const weekEntries = $.let(Data.bindPaged(indexedRosterWeeks));\n const cover = $.let(Record.bind(rosterCover, [rosterCoverPatch]));\n // Complete staff is the calculation context; visibility is a separate indexed query.\n const staff = $.let(Data.bind(indexedRosterStaff));\n const acts = $.let(Data.bind(rosterActivities));\n // One rail over the week's duties, the staff, opening on Inbound, and the activities (#1342).\n const people = $.let(Data.bindPaged(indexedRosterStaff, { indexes: [rosterStaffByName, rosterStaffByTeam, rosterStaffByPosition, rosterStaffByTrainer, rosterStaffByAgency, rosterStaffBySkill] }));\n const activities = $.let(Data.bindPaged(rosterActivities, { indexes: [rosterActivitiesByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.rosterRecordIndexes\", {\n duty: { source: byTime, range: \"start\" },\n people: { source: people, icon: { prefix: \"fas\", name: \"user\" }, search: [\"name\", \"skills\"], initial: { team: [\"inbound\"] },\n fields: { team: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\"] }, name: { label: \"Name\" }, position: { label: \"Position\", hints: [\"lead\", \"deputy\", \"acting\", \"crew\"] },\n trainer: { label: \"Trainer\" }, agency: { label: \"Agency\" }, skills: { label: \"Skills\" } } },\n activities: { source: activities, icon: { prefix: \"fas\", name: \"list-check\" }, fields: { group: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\", null] } } },\n }, { presets: [{ id: \"trainers\", name: \"Trainers\", where: { people: { trainer: [true] } } }] }));\n const firms = $.let(Data.bind(rosterAgencies));\n const times = $.let(Data.bind(rosterShiftTimes));\n const teams = $.let(Data.bind(rosterTeams));\n const roles = $.let(Data.bind(rosterPositions));\n const ideas = $.let(Data.bind(rosterSuggestions));\n // The app's fit: a Find fix asks it who fits, over all 500 people, and the People tab shows its answer among what the rail shows.\n const fit = $.let(Func.bind(rosterStaffFit));\n return <Box width=\"100%\" height=\"760px\"><Roster id=\"rosterRecordIndexes\" week={{ start: \"sunday\" }} slice={{ slice }} fit={fit} inspector\n view={{ layout: \"shifts\", period: \"day\", date: new Date(\"2028-03-09T00:00:00Z\") }}\n events={{ duty: Schedule.events(rota, {\n name: \"Duty\", icon: \"user-clock\", entries,\n start: \"start\", end: \"end\", resource: { field: \"who\", of: { staff: \"people\", agency: \"agencies\" } },\n shift: \"shift\", group: \"team\", position: \"role\", activity: \"task\",\n hours: d => d.start.durationMinutes(d.end).subtract(d.unpaidBreak).divide(60.0),\n }) }}\n resources={{\n people: Schedule.resources(staff.read(), { name: \"People\", icon: \"user\", label: p => p.name, group: p => p.team,\n position: p => p.position, skills: p => p.skills, contract: p => p.contract, agency: p => p.agency,\n trainer: p => p.trainer, trainee: p => p.trainee, usual: p => p.usual }),\n agencies: Schedule.resources(firms.read(), { name: \"Agencies\", icon: \"building\", label: a => a.name }),\n }}\n periods={Schedule.periods(weeks, { every: \"week\", status: { field: \"state\", draft: \"open\", published: \"issued\" }, dismissed: \"dismissed\", entries: weekEntries })}\n targets={Roster.targets(cover, { day: \"day\", group: \"team\", shift: \"shift\", positions: \"heads\", hours: \"hours\" })}\n shifts={Roster.shifts(times.read(), { label: s => s.label, code: s => s.code, start: s => s.start, hours: s => s.hours, order: s => s.order })}\n groups={Roster.groups(teams.read(), { label: t => t.label, order: t => t.order })}\n positions={Roster.positions(roles.read(), { label: p => p.label, code: p => p.code, lead: p => p.lead, order: p => p.order })}\n activities={Roster.activities(acts.read(), { label: a => a.label, code: a => a.code, group: a => a.group, order: a => a.order })}\n proposals={Roster.proposals(ideas.read(), { person: s => s.person, day: s => s.day, group: s => s.team, shift: s => s.shift, reason: s => s.why })}\n rules={{ rest: 10.0 }}\n library={[Roster.library.people(), Roster.library.activities()]} />\n </Box>;\n }}</Reactive>)"
|
|
43533
43548
|
},
|
|
43534
43549
|
{
|
|
43535
43550
|
"id": "e3-ui:roster-records.examples.tsx:rosterRecordIndexesMobile",
|
|
@@ -43562,7 +43577,7 @@
|
|
|
43562
43577
|
"languages": [
|
|
43563
43578
|
"tsx"
|
|
43564
43579
|
],
|
|
43565
|
-
"source": "// Phone roster over the same year of duties, with the shared indexed staff controls and explicit actions; weekly drafts survive filtering.\n// inputs: []\nconst stringOrder = compareFor(StringType);\n\nconst WEEK = 7 * 86_400_000;\n\n/** A larger staff pool for these indexed examples, preserving the mock's named people. */\nfunction staffSeed() {\n const people = new Map(warehouseStaff.default);\n const originals = [...people.values()];\n for (let i = 0; people.size < 500; i++) {\n const person = originals[i % originals.length]!;\n people.set(`pool-${1000 + i}`, { ...person, name: `${person.name} ${i + 1}` });\n }\n return people;\n}\n\n/** How many weeks of history before the warehouse's two: with its two, a year. */\nconst HISTORY = 50;\n\n/** A year of duties: the warehouse's two weeks, and each of the fifty weeks before them the week before's duties again, in its own week. */\nfunction dutySeed() {\n const duties = new SortedMap<string, ValueTypeOf<typeof WarehouseDuty>>(warehouseDuties, stringOrder);\n const before = [...warehouseDuties].filter(([, duty]) => duty.start.getTime() < warehousePreviousStart.getTime() + WEEK);\n for (let week = 1; week <= HISTORY; week++) {\n for (const [key, duty] of before) {\n duties.set(`h${week}-${key}`, { ...duty, start: new Date(duty.start.getTime() - week * WEEK), end: new Date(duty.end.getTime() - week * WEEK) });\n }\n }\n return duties;\n}\n\n/** The rota's weeks over the year: the warehouse's two, and each week of history issued at its end. */\nfunction weekSeed() {\n const weeks = new SortedMap<Date, ValueTypeOf<typeof WarehouseWeek>>(warehouseWeeks, compareFor(DateTimeType));\n const issued = warehouseWeeks.get(warehousePreviousStart)!;\n for (let week = 1; week <= HISTORY; week++) {\n const start = new Date(warehousePreviousStart.getTime() - week * WEEK);\n weeks.set(start, { ...issued, state: variant(\"issued\", { at: new Date(start.getTime() + WEEK - 12 * 3_600_000) }) });\n }\n return weeks;\n}\n\nexport const indexedRosterDuties = e3.record(\"indexed_roster_duties\", DictType(StringType, WarehouseDuty), dutySeed());\n\nexport const indexedRosterDutiesPatch = e3.mutation.patch(indexedRosterDuties);\n\n/** The year's duties by their times: what the Roster's slice reads a week of them through. */\nexport const indexedRosterDutiesByTime = e3.recordIndex(\"indexed_roster_duties_by_time\", indexedRosterDuties, {
|
|
43580
|
+
"source": "// Phone roster over the same year of duties, with the shared indexed staff controls and explicit actions; weekly drafts survive filtering.\n// inputs: []\nconst stringOrder = compareFor(StringType);\n\nconst WEEK = 7 * 86_400_000;\n\n/** A larger staff pool for these indexed examples, preserving the mock's named people. */\nfunction staffSeed() {\n const people = new Map(warehouseStaff.default);\n const originals = [...people.values()];\n for (let i = 0; people.size < 500; i++) {\n const person = originals[i % originals.length]!;\n people.set(`pool-${1000 + i}`, { ...person, name: `${person.name} ${i + 1}` });\n }\n return people;\n}\n\n/** How many weeks of history before the warehouse's two: with its two, a year. */\nconst HISTORY = 50;\n\n/** A year of duties: the warehouse's two weeks, and each of the fifty weeks before them the week before's duties again, in its own week. */\nfunction dutySeed() {\n const duties = new SortedMap<string, ValueTypeOf<typeof WarehouseDuty>>(warehouseDuties, stringOrder);\n const before = [...warehouseDuties].filter(([, duty]) => duty.start.getTime() < warehousePreviousStart.getTime() + WEEK);\n for (let week = 1; week <= HISTORY; week++) {\n for (const [key, duty] of before) {\n duties.set(`h${week}-${key}`, { ...duty, start: new Date(duty.start.getTime() - week * WEEK), end: new Date(duty.end.getTime() - week * WEEK) });\n }\n }\n return duties;\n}\n\n/** The rota's weeks over the year: the warehouse's two, and each week of history issued at its end. */\nfunction weekSeed() {\n const weeks = new SortedMap<Date, ValueTypeOf<typeof WarehouseWeek>>(warehouseWeeks, compareFor(DateTimeType));\n const issued = warehouseWeeks.get(warehousePreviousStart)!;\n for (let week = 1; week <= HISTORY; week++) {\n const start = new Date(warehousePreviousStart.getTime() - week * WEEK);\n weeks.set(start, { ...issued, state: variant(\"issued\", { at: new Date(start.getTime() + WEEK - 12 * 3_600_000) }) });\n }\n return weeks;\n}\n\nexport const indexedRosterDuties = e3.record(\"indexed_roster_duties\", DictType(StringType, WarehouseDuty), dutySeed());\n\nexport const indexedRosterDutiesPatch = e3.mutation.patch(indexedRosterDuties);\n\n/** The year's duties by their times: what the Roster's slice reads a week of them through. */\nexport const indexedRosterDutiesByTime = e3.recordIndex.interval(\"indexed_roster_duties_by_time\", indexedRosterDuties, { from: \"start\", to: \"end\" });\n\nexport const indexedRosterWeeks = e3.record(\"indexed_roster_weeks\", DictType(DateTimeType, WarehouseWeek), weekSeed());\n\nexport const indexedRosterWeeksPatch = e3.mutation.patch(indexedRosterWeeks);\n\nexport const indexedRosterStaff = e3.record(\"indexed_roster_staff\", warehouseStaff.type, staffSeed());\n\nexport const rosterStaffByName = e3.recordIndex(\"staff_by_name\", indexedRosterStaff, { on: [\"name\"] });\n\nexport const rosterStaffByTeam = e3.recordIndex(\"staff_by_team_name\", indexedRosterStaff, { on: [\"team\", \"name\"] });\n\nexport const rosterStaffByTrainer = e3.recordIndex(\"staff_by_trainer\", indexedRosterStaff, { on: [\"trainer\"] });\n\nexport const rosterStaffBySkill = e3.recordIndex(\"staff_by_skill\", indexedRosterStaff, { on: [\"skills\"] });\n\nexport const rosterStaffByPosition = e3.recordIndex(\"staff_by_position\", indexedRosterStaff, { on: [\"position\"] });\n\nexport const rosterStaffByAgency = e3.recordIndex(\"staff_by_agency\", indexedRosterStaff, { on: [\"agency\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const rota = $.let(Record.bind(indexedRosterDuties, [indexedRosterDutiesPatch]));\n const entries = $.let(Data.bindPaged(indexedRosterDuties));\n const byTime = $.let(Data.bindPaged(indexedRosterDuties, { indexes: [indexedRosterDutiesByTime] }));\n const weeks = $.let(Record.bind(indexedRosterWeeks, [indexedRosterWeeksPatch]));\n const weekEntries = $.let(Data.bindPaged(indexedRosterWeeks));\n const cover = $.let(Record.bind(rosterCover, [rosterCoverPatch]));\n // Complete staff is the calculation context; visibility is a separate indexed query.\n const staff = $.let(Data.bind(indexedRosterStaff));\n const acts = $.let(Data.bind(rosterActivities));\n // One rail over the week's duties, the staff, opening on Inbound, and the activities (#1342).\n const people = $.let(Data.bindPaged(indexedRosterStaff, { indexes: [rosterStaffByName, rosterStaffByTeam, rosterStaffByPosition, rosterStaffByTrainer, rosterStaffByAgency, rosterStaffBySkill] }));\n const activities = $.let(Data.bindPaged(rosterActivities, { indexes: [rosterActivitiesByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.rosterRecordIndexesMobile\", {\n duty: { source: byTime, range: \"start\" },\n people: { source: people, icon: { prefix: \"fas\", name: \"user\" }, search: [\"name\", \"skills\"], initial: { team: [\"inbound\"] },\n fields: { team: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\"] }, name: { label: \"Name\" }, position: { label: \"Position\", hints: [\"lead\", \"deputy\", \"acting\", \"crew\"] },\n trainer: { label: \"Trainer\" }, agency: { label: \"Agency\" }, skills: { label: \"Skills\" } } },\n activities: { source: activities, icon: { prefix: \"fas\", name: \"list-check\" }, fields: { group: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\", null] } } },\n }, { presets: [{ id: \"trainers\", name: \"Trainers\", where: { people: { trainer: [true] } } }] }));\n const firms = $.let(Data.bind(rosterAgencies));\n const times = $.let(Data.bind(rosterShiftTimes));\n const teams = $.let(Data.bind(rosterTeams));\n const roles = $.let(Data.bind(rosterPositions));\n const ideas = $.let(Data.bind(rosterSuggestions));\n return <Box width=\"100%\" maxWidth=\"390px\" height=\"760px\"><Roster id=\"rosterRecordIndexesMobile\" week={{ start: \"sunday\" }} slice={{ slice }} inspector\n view={{ layout: \"shifts\", period: \"day\", date: new Date(\"2028-03-09T00:00:00Z\") }}\n events={{ duty: Schedule.events(rota, {\n name: \"Duty\", icon: \"user-clock\", entries,\n start: \"start\", end: \"end\", resource: { field: \"who\", of: { staff: \"people\", agency: \"agencies\" } },\n shift: \"shift\", group: \"team\", position: \"role\", activity: \"task\",\n hours: d => d.start.durationMinutes(d.end).subtract(d.unpaidBreak).divide(60.0),\n }) }}\n resources={{\n people: Schedule.resources(staff.read(), { name: \"People\", icon: \"user\", label: p => p.name, group: p => p.team,\n position: p => p.position, skills: p => p.skills, contract: p => p.contract, agency: p => p.agency,\n trainer: p => p.trainer, trainee: p => p.trainee, usual: p => p.usual }),\n agencies: Schedule.resources(firms.read(), { name: \"Agencies\", icon: \"building\", label: a => a.name }),\n }}\n periods={Schedule.periods(weeks, { every: \"week\", status: { field: \"state\", draft: \"open\", published: \"issued\" }, dismissed: \"dismissed\", entries: weekEntries })}\n targets={Roster.targets(cover, { day: \"day\", group: \"team\", shift: \"shift\", positions: \"heads\", hours: \"hours\" })}\n shifts={Roster.shifts(times.read(), { label: s => s.label, code: s => s.code, start: s => s.start, hours: s => s.hours, order: s => s.order })}\n groups={Roster.groups(teams.read(), { label: t => t.label, order: t => t.order })}\n positions={Roster.positions(roles.read(), { label: p => p.label, code: p => p.code, lead: p => p.lead, order: p => p.order })}\n activities={Roster.activities(acts.read(), { label: a => a.label, code: a => a.code, group: a => a.group, order: a => a.order })}\n proposals={Roster.proposals(ideas.read(), { person: s => s.person, day: s => s.day, group: s => s.team, shift: s => s.shift, reason: s => s.why })}\n rules={{ rest: 10.0 }}\n library={[Roster.library.people(), Roster.library.activities()]} />\n </Box>;\n }}</Reactive>)"
|
|
43566
43581
|
},
|
|
43567
43582
|
{
|
|
43568
43583
|
"id": "e3-ui:roster.examples.tsx:rosterWarehouse",
|
|
@@ -43606,7 +43621,7 @@
|
|
|
43606
43621
|
"languages": [
|
|
43607
43622
|
"tsx"
|
|
43608
43623
|
],
|
|
43609
|
-
"source": "// The mock warehouse roster over its own records: its duties, the weeks of its rota, its cover targets and suggestions, 69 staff and an agency, its shift times, teams, positions and activities. One Slice.bindRecords rail over the week's duties, the staff and the activities; the warehouse's pay in its own codes and its check over the pay; Publish; a Positions tab; the inspector; and the warehouse's greedy fit on e3, which a Find fix asks who fits.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex(\"roster_duties_by_time\", rosterDuties, { interval: { from: \"start\", to: \"end\" } });\n\n/** The weeks of the rota: the one before issued, this one open. */\nexport const rosterWeeks = e3.record(\"roster_rota_weeks\", DictType(DateTimeType, WarehouseWeek), warehouseWeeks);\n\nexport const rosterWeeksPatch = e3.mutation.patch(rosterWeeks);\n\n/** The cover targets, a row per slot: the week before's, this week's and the forecast's for the week after. */\nexport const rosterCover = e3.record(\"roster_cover\", DictType(StringType, WarehouseCover), warehouseCover);\n\nexport const rosterCoverPatch = e3.mutation.patch(rosterCover);\n\n/** The suggestions: someone for a slot, and why. */\nexport const rosterSuggestions = e3.record(\"roster_suggestions\", DictType(StringType, WarehouseSuggestion), warehouseSuggestions);\n\n/** The pay rates, an hour, by the warehouse's own pay codes. */\nexport const rosterPayRates = e3.record(\"roster_pay_rates\", DictType(StringType, FloatType), warehousePayRates);\n\nexport const rosterStaff = e3.record(\"roster_staff\", DictType(StringType, WarehousePerson), warehousePeople);\n\nexport const rosterAgencies = e3.record(\"roster_agencies\", DictType(StringType, WarehouseAgency), warehouseAgencies);\n\nexport const rosterShiftTimes = e3.record(\"roster_shift_times\", DictType(StringType, WarehouseShift), warehouseShifts);\n\nexport const rosterTeams = e3.record(\"roster_teams\", DictType(StringType, WarehouseTeam), warehouseTeams);\n\nexport const rosterPositions = e3.record(\"roster_positions\", DictType(StringType, WarehousePosition), warehousePositions);\n\n/** Every activity by its key: each group's skills, and the duty of none (#1342). */\nexport const rosterActivities = e3.record(\"roster_activities\", DictType(StringType, WarehouseActivity), warehouseActivities);\n\nexport const rosterStaffByTeamName = e3.recordIndex(\"roster_staff_by_team_name\", rosterStaff, { on: [\"team\", \"name\"] });\n\nexport const rosterStaffByPosition = e3.recordIndex(\"roster_staff_by_position\", rosterStaff, { on: [\"position\"] });\n\nexport const rosterStaffByTrainer = e3.recordIndex(\"roster_staff_by_trainer\", rosterStaff, { on: [\"trainer\"] });\n\nexport const rosterStaffByAgency = e3.recordIndex(\"roster_staff_by_agency\", rosterStaff, { on: [\"agency\"] });\n\nexport const rosterStaffBySkill = e3.recordIndex(\"roster_staff_by_skill\", rosterStaff, { on: [\"skills\"] });\n\nexport const rosterStaffByName = e3.recordIndex(\"roster_staff_by_name\", rosterStaff, { on: [\"name\"] });\n\nexport const rosterActivitiesByGroup = e3.recordIndex(\"roster_activities_by_group\", rosterActivities, { on: [\"group\"] });\n\n/** The warehouse's fit's body: the ready-made rule, then a greedy order — the people off that day with the most spare contract hours first, whatever their group. */\nexport const rosterStaffFitBody = East.function([Roster.Types.FitRequest(WarehouseDuty)], Roster.Types.FitResult, ($, req) => {\n const answer = $.const(Roster.answer(WarehouseDuty));\n const rule = $.const(Roster.fit(WarehouseDuty));\n const weekHours = $.const(Roster.weekHours(WarehouseDuty));\n // Every person's hours this week, once: what is left of each one's contract ranks them.\n const hours = $.const(weekHours(req.week, req.context));\n return answer(req, East.function([Roster.Types.FitRequest(WarehouseDuty), Roster.Types.Person], Roster.Types.Fit, ($2, request, person) => {\n const verdict = $2.const(rule(request, person));\n $2.match(verdict, { refused: ($3) => { $3.return(verdict); } });\n const fits = $2.const(verdict.unwrap(\"fits\"));\n return variant(\"fits\", { score: person.contract.subtract(hours.get(person.key, () => 0.0)), why: fits.why, warn: fits.warn });\n }));\n});\n\nexport const rosterStaffFit = e3.function(\"roster_staff_fit\", rosterStaffFitBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n // What the Roster writes: the duties, the weeks of the rota and the cover targets, each through its patch door.\n const rota = $.let(Record.bind(rosterDuties, [rosterDutiesPatch]));\n const entries = $.let(Data.bindPaged(rosterDuties));\n const byTime = $.let(Data.bindPaged(rosterDuties, { indexes: [rosterDutiesByTime] }));\n const weeks = $.let(Record.bind(rosterWeeks, [rosterWeeksPatch]));\n const cover = $.let(Record.bind(rosterCover, [rosterCoverPatch]));\n // Every person and activity is the calculation scope; the narrowing only chooses which show (#1342).\n const people = $.let(Data.bind(rosterStaff));\n const acts = $.let(Data.bind(rosterActivities));\n const staffIndex = $.let(Data.bindPaged(rosterStaff, { indexes: [rosterStaffByTeamName, rosterStaffByPosition, rosterStaffByTrainer, rosterStaffByAgency, rosterStaffBySkill, rosterStaffByName] }));\n const actIndex = $.let(Data.bindPaged(rosterActivities, { indexes: [rosterActivitiesByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.rosterWarehouse\", {\n // The week's duties: the Roster's navigation writes the range; no chip picks the week.\n duty: { source: byTime, range: \"start\" },\n people: { source: staffIndex, icon: { prefix: \"fas\", name: \"user\" }, search: [\"name\", \"skills\"],\n fields: { team: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\"] }, position: { label: \"Position\", hints: [\"lead\", \"deputy\", \"acting\", \"crew\"] },\n trainer: { label: \"Trainer\" }, agency: { label: \"Agency\" }, skills: { label: \"Skill\" }, name: { label: \"Name\" } } },\n activities: { source: actIndex, icon: { prefix: \"fas\", name: \"list-check\" }, fields: { group: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\", null] } } },\n }, { presets: [{ id: \"leads\", name: \"Leads\", where: { people: { position: [\"lead\", \"deputy\", \"acting\"] } } }] }));\n const firms = $.let(Data.bind(rosterAgencies));\n const times = $.let(Data.bind(rosterShiftTimes));\n const teams = $.let(Data.bind(rosterTeams));\n const roles = $.let(Data.bind(rosterPositions));\n const ideas = $.let(Data.bind(rosterSuggestions));\n const rateRows = $.let(Data.bind(rosterPayRates));\n const rates = $.const(rateRows.read());\n // The app's fit: a Find fix asks it who fits, over every person, and the People tab shows its answer.\n const fit = $.let(Func.bind(rosterStaffFit));\n // The warehouse's pay (§5.6), in its own codes. One person's duties for the week in, or one agency's requests;\n // each duty's cost by code out: ordinary time up to the person's contracted hours in the week, OT1 past them,\n // and an agency's hours — its named worker's, or its request's — at its own rate.\n const pay = $.const(East.function([Roster.Types.PayRequest(WarehouseDuty)], Roster.Types.Pay, ($2, req) => {\n const out = $2.let(new Map(), Roster.Types.Pay);\n const zero = $2.const(0.0, FloatType);\n const agency = $2.let(true, BooleanType);\n const contract = $2.let(0.0, FloatType);\n $2.match(req.who, { person: ($3, key) => {\n $3.if(req.context.people.has(key), ($4) => {\n const person = $4.const(req.context.people.get(key));\n $4.assign(agency, person.agency);\n $4.assign(contract, person.contract);\n });\n } });\n const worked = $2.let(0.0, FloatType);\n // The week's duties in time order, so the contract's hours run out where they do.\n const ordered = $2.const(req.duties.toArray(($3, duty, key) => ({ key, duty })).sort(($3, d) => d.duty.start));\n $2.for(ordered, ($3, d) => {\n const costs = $3.let(new Map(), DictType(StringType, FloatType));\n $3.if(agency, ($4) => {\n $4(costs.insert(\"AGENCY\", d.duty.hours.multiply(rates.get(\"AGENCY\", () => 0.0))));\n }).else(($4) => {\n const ordinary = $4.const(East.max(East.min(d.duty.hours, contract.subtract(worked)), zero));\n const over = $4.const(d.duty.hours.subtract(ordinary));\n $4(costs.insert(\"ORD\", ordinary.multiply(rates.get(\"ORD\", () => 0.0))));\n $4.if(East.greater(over, 0.0), ($5) => { $5(costs.insert(\"OT1\", over.multiply(rates.get(\"OT1\", () => 0.0)))); });\n });\n $3.assign(worked, worked.add(d.duty.hours));\n $3(out.insert(d.key, costs));\n });\n return out;\n }));\n // The warehouse's check sees the pay too (§5.8): overtime on a duty nobody has agreed.\n const check = $.const(East.function([Roster.Types.CheckContext(WarehouseDuty)], ArrayType(Roster.Types.Issue), ($2, cx) => {\n const issues = $2.let([], ArrayType(Roster.Types.Issue));\n $2.for(cx.week.assignments, ($3, a, id) => {\n const costs = $3.const(cx.pay.get(id, () => East.value(new Map(), DictType(StringType, FloatType))));\n $3.if(costs.has(\"OT1\").and(() => East.notEqual(a.row.agreed, some(true))), ($4) => {\n $4(issues.pushLast({ kind: variant(\"breach\", null), tone: variant(\"info\", null),\n title: \"Overtime not agreed\", detail: East.str`${a.row.team} · ${a.row.shift}`,\n slot: some(a.slot), assignment: some(id), flag: some(\"OT\"), fix: none }));\n });\n });\n return issues;\n }));\n return <Box width=\"100%\" height=\"760px\"><Roster id=\"rosterWarehouse\" week={{ start: \"sunday\" }} slice={{ slice }} fit={fit}\n pay={pay} currency=\"AUD\" check={check} inspector view={{ layout: \"shifts\", period: \"day\", date: new Date(\"2028-03-09T00:00:00Z\") }}\n events={{ duty: Schedule.events(rota, {\n name: \"Duty\", icon: \"user-clock\", entries,\n start: \"start\", end: \"end\", resource: { field: \"who\", of: { staff: \"people\", agency: \"agencies\" } },\n shift: \"shift\", group: \"team\", position: \"role\", activity: \"task\",\n // The hours a duty counts: its length less its unpaid break.\n hours: d => d.start.durationMinutes(d.end).subtract(d.unpaidBreak).divide(60.0),\n fields: { unpaidBreak: Schedule.field.number({ label: \"Unpaid break\", unit: \"min\", step: 15.0, min: 0.0 }),\n costCentre: Schedule.field.text({ label: \"Cost centre\" }), note: Schedule.field.text({ label: \"Note\" }) },\n // The app's check on a draft: Save waits until every duty has a cost centre.\n ready: d => East.equal(d.costCentre, \"\").ifElse(\n () => variant(\"invalid\", [{ field: \"costCentre\", message: \"Give the duty a cost centre\" }]),\n () => variant(\"ready\", null)),\n }) }}\n resources={{\n people: Schedule.resources(people.read(), { name: \"People\", icon: \"user\", label: p => p.name, group: p => p.team,\n position: p => p.position, skills: p => p.skills, contract: p => p.contract, agency: p => p.agency,\n trainer: p => p.trainer, trainee: p => p.trainee, usual: p => p.usual }),\n agencies: Schedule.resources(firms.read(), { name: \"Agencies\", icon: \"building\", label: a => a.name }),\n }}\n periods={Schedule.periods(weeks, { every: \"week\", status: { field: \"state\", draft: \"open\", published: \"issued\" }, dismissed: \"dismissed\" })}\n targets={Roster.targets(cover, { day: \"day\", group: \"team\", shift: \"shift\", positions: \"heads\", hours: \"hours\" })}\n shifts={Roster.shifts(times.read(), { label: s => s.label, code: s => s.code, start: s => s.start, hours: s => s.hours, order: s => s.order })}\n groups={Roster.groups(teams.read(), { label: t => t.label, order: t => t.order })}\n positions={Roster.positions(roles.read(), { label: p => p.label, code: p => p.code, lead: p => p.lead, order: p => p.order })}\n activities={Roster.activities(acts.read(), { label: a => a.label, code: a => a.code, group: a => a.group, order: a => a.order })}\n proposals={Roster.proposals(ideas.read(), { person: s => s.person, day: s => s.day, group: s => s.team, shift: s => s.shift, reason: s => s.why })}\n rules={{ rest: 10.0 }}\n library={[Roster.library.people(), Roster.library.activities(), Roster.library.tab(roles.read(), {\n name: \"Positions\", icon: \"id-badge\", label: p => p.label, meta: p => some(p.code),\n drop: (_p, key) => Schedule.patch(WarehouseDuty, { role: key }),\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
43624
|
+
"source": "// The mock warehouse roster over its own records: its duties, the weeks of its rota, its cover targets and suggestions, 69 staff and an agency, its shift times, teams, positions and activities. One Slice.bindRecords rail over the week's duties, the staff and the activities; the warehouse's pay in its own codes and its check over the pay; Publish; a Positions tab; the inspector; and the warehouse's greedy fit on e3, which a Find fix asks who fits.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex.interval(\"roster_duties_by_time\", rosterDuties, { from: \"start\", to: \"end\" });\n\n/** The weeks of the rota: the one before issued, this one open. */\nexport const rosterWeeks = e3.record(\"roster_rota_weeks\", DictType(DateTimeType, WarehouseWeek), warehouseWeeks);\n\nexport const rosterWeeksPatch = e3.mutation.patch(rosterWeeks);\n\n/** The cover targets, a row per slot: the week before's, this week's and the forecast's for the week after. */\nexport const rosterCover = e3.record(\"roster_cover\", DictType(StringType, WarehouseCover), warehouseCover);\n\nexport const rosterCoverPatch = e3.mutation.patch(rosterCover);\n\n/** The suggestions: someone for a slot, and why. */\nexport const rosterSuggestions = e3.record(\"roster_suggestions\", DictType(StringType, WarehouseSuggestion), warehouseSuggestions);\n\n/** The pay rates, an hour, by the warehouse's own pay codes. */\nexport const rosterPayRates = e3.record(\"roster_pay_rates\", DictType(StringType, FloatType), warehousePayRates);\n\nexport const rosterStaff = e3.record(\"roster_staff\", DictType(StringType, WarehousePerson), warehousePeople);\n\nexport const rosterAgencies = e3.record(\"roster_agencies\", DictType(StringType, WarehouseAgency), warehouseAgencies);\n\nexport const rosterShiftTimes = e3.record(\"roster_shift_times\", DictType(StringType, WarehouseShift), warehouseShifts);\n\nexport const rosterTeams = e3.record(\"roster_teams\", DictType(StringType, WarehouseTeam), warehouseTeams);\n\nexport const rosterPositions = e3.record(\"roster_positions\", DictType(StringType, WarehousePosition), warehousePositions);\n\n/** Every activity by its key: each group's skills, and the duty of none (#1342). */\nexport const rosterActivities = e3.record(\"roster_activities\", DictType(StringType, WarehouseActivity), warehouseActivities);\n\nexport const rosterStaffByTeamName = e3.recordIndex(\"roster_staff_by_team_name\", rosterStaff, { on: [\"team\", \"name\"] });\n\nexport const rosterStaffByPosition = e3.recordIndex(\"roster_staff_by_position\", rosterStaff, { on: [\"position\"] });\n\nexport const rosterStaffByTrainer = e3.recordIndex(\"roster_staff_by_trainer\", rosterStaff, { on: [\"trainer\"] });\n\nexport const rosterStaffByAgency = e3.recordIndex(\"roster_staff_by_agency\", rosterStaff, { on: [\"agency\"] });\n\nexport const rosterStaffBySkill = e3.recordIndex(\"roster_staff_by_skill\", rosterStaff, { on: [\"skills\"] });\n\nexport const rosterStaffByName = e3.recordIndex(\"roster_staff_by_name\", rosterStaff, { on: [\"name\"] });\n\nexport const rosterActivitiesByGroup = e3.recordIndex(\"roster_activities_by_group\", rosterActivities, { on: [\"group\"] });\n\n/** The warehouse's fit's body: the ready-made rule, then a greedy order — the people off that day with the most spare contract hours first, whatever their group. */\nexport const rosterStaffFitBody = East.function([Roster.Types.FitRequest(WarehouseDuty)], Roster.Types.FitResult, ($, req) => {\n const answer = $.const(Roster.answer(WarehouseDuty));\n const rule = $.const(Roster.fit(WarehouseDuty));\n const weekHours = $.const(Roster.weekHours(WarehouseDuty));\n // Every person's hours this week, once: what is left of each one's contract ranks them.\n const hours = $.const(weekHours(req.week, req.context));\n return answer(req, East.function([Roster.Types.FitRequest(WarehouseDuty), Roster.Types.Person], Roster.Types.Fit, ($2, request, person) => {\n const verdict = $2.const(rule(request, person));\n $2.match(verdict, { refused: ($3) => { $3.return(verdict); } });\n const fits = $2.const(verdict.unwrap(\"fits\"));\n return variant(\"fits\", { score: person.contract.subtract(hours.get(person.key, () => 0.0)), why: fits.why, warn: fits.warn });\n }));\n});\n\nexport const rosterStaffFit = e3.function(\"roster_staff_fit\", rosterStaffFitBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n // What the Roster writes: the duties, the weeks of the rota and the cover targets, each through its patch door.\n const rota = $.let(Record.bind(rosterDuties, [rosterDutiesPatch]));\n const entries = $.let(Data.bindPaged(rosterDuties));\n const byTime = $.let(Data.bindPaged(rosterDuties, { indexes: [rosterDutiesByTime] }));\n const weeks = $.let(Record.bind(rosterWeeks, [rosterWeeksPatch]));\n const cover = $.let(Record.bind(rosterCover, [rosterCoverPatch]));\n // Every person and activity is the calculation scope; the narrowing only chooses which show (#1342).\n const people = $.let(Data.bind(rosterStaff));\n const acts = $.let(Data.bind(rosterActivities));\n const staffIndex = $.let(Data.bindPaged(rosterStaff, { indexes: [rosterStaffByTeamName, rosterStaffByPosition, rosterStaffByTrainer, rosterStaffByAgency, rosterStaffBySkill, rosterStaffByName] }));\n const actIndex = $.let(Data.bindPaged(rosterActivities, { indexes: [rosterActivitiesByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.rosterWarehouse\", {\n // The week's duties: the Roster's navigation writes the range; no chip picks the week.\n duty: { source: byTime, range: \"start\" },\n people: { source: staffIndex, icon: { prefix: \"fas\", name: \"user\" }, search: [\"name\", \"skills\"],\n fields: { team: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\"] }, position: { label: \"Position\", hints: [\"lead\", \"deputy\", \"acting\", \"crew\"] },\n trainer: { label: \"Trainer\" }, agency: { label: \"Agency\" }, skills: { label: \"Skill\" }, name: { label: \"Name\" } } },\n activities: { source: actIndex, icon: { prefix: \"fas\", name: \"list-check\" }, fields: { group: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\", null] } } },\n }, { presets: [{ id: \"leads\", name: \"Leads\", where: { people: { position: [\"lead\", \"deputy\", \"acting\"] } } }] }));\n const firms = $.let(Data.bind(rosterAgencies));\n const times = $.let(Data.bind(rosterShiftTimes));\n const teams = $.let(Data.bind(rosterTeams));\n const roles = $.let(Data.bind(rosterPositions));\n const ideas = $.let(Data.bind(rosterSuggestions));\n const rateRows = $.let(Data.bind(rosterPayRates));\n const rates = $.const(rateRows.read());\n // The app's fit: a Find fix asks it who fits, over every person, and the People tab shows its answer.\n const fit = $.let(Func.bind(rosterStaffFit));\n // The warehouse's pay (§5.6), in its own codes. One person's duties for the week in, or one agency's requests;\n // each duty's cost by code out: ordinary time up to the person's contracted hours in the week, OT1 past them,\n // and an agency's hours — its named worker's, or its request's — at its own rate.\n const pay = $.const(East.function([Roster.Types.PayRequest(WarehouseDuty)], Roster.Types.Pay, ($2, req) => {\n const out = $2.let(new Map(), Roster.Types.Pay);\n const zero = $2.const(0.0, FloatType);\n const agency = $2.let(true, BooleanType);\n const contract = $2.let(0.0, FloatType);\n $2.match(req.who, { person: ($3, key) => {\n $3.if(req.context.people.has(key), ($4) => {\n const person = $4.const(req.context.people.get(key));\n $4.assign(agency, person.agency);\n $4.assign(contract, person.contract);\n });\n } });\n const worked = $2.let(0.0, FloatType);\n // The week's duties in time order, so the contract's hours run out where they do.\n const ordered = $2.const(req.duties.toArray(($3, duty, key) => ({ key, duty })).sort(($3, d) => d.duty.start));\n $2.for(ordered, ($3, d) => {\n const costs = $3.let(new Map(), DictType(StringType, FloatType));\n $3.if(agency, ($4) => {\n $4(costs.insert(\"AGENCY\", d.duty.hours.multiply(rates.get(\"AGENCY\", () => 0.0))));\n }).else(($4) => {\n const ordinary = $4.const(East.max(East.min(d.duty.hours, contract.subtract(worked)), zero));\n const over = $4.const(d.duty.hours.subtract(ordinary));\n $4(costs.insert(\"ORD\", ordinary.multiply(rates.get(\"ORD\", () => 0.0))));\n $4.if(East.greater(over, 0.0), ($5) => { $5(costs.insert(\"OT1\", over.multiply(rates.get(\"OT1\", () => 0.0)))); });\n });\n $3.assign(worked, worked.add(d.duty.hours));\n $3(out.insert(d.key, costs));\n });\n return out;\n }));\n // The warehouse's check sees the pay too (§5.8): overtime on a duty nobody has agreed.\n const check = $.const(East.function([Roster.Types.CheckContext(WarehouseDuty)], ArrayType(Roster.Types.Issue), ($2, cx) => {\n const issues = $2.let([], ArrayType(Roster.Types.Issue));\n $2.for(cx.week.assignments, ($3, a, id) => {\n const costs = $3.const(cx.pay.get(id, () => East.value(new Map(), DictType(StringType, FloatType))));\n $3.if(costs.has(\"OT1\").and(() => East.notEqual(a.row.agreed, some(true))), ($4) => {\n $4(issues.pushLast({ kind: variant(\"breach\", null), tone: variant(\"info\", null),\n title: \"Overtime not agreed\", detail: East.str`${a.row.team} · ${a.row.shift}`,\n slot: some(a.slot), assignment: some(id), flag: some(\"OT\"), fix: none }));\n });\n });\n return issues;\n }));\n return <Box width=\"100%\" height=\"760px\"><Roster id=\"rosterWarehouse\" week={{ start: \"sunday\" }} slice={{ slice }} fit={fit}\n pay={pay} currency=\"AUD\" check={check} inspector view={{ layout: \"shifts\", period: \"day\", date: new Date(\"2028-03-09T00:00:00Z\") }}\n events={{ duty: Schedule.events(rota, {\n name: \"Duty\", icon: \"user-clock\", entries,\n start: \"start\", end: \"end\", resource: { field: \"who\", of: { staff: \"people\", agency: \"agencies\" } },\n shift: \"shift\", group: \"team\", position: \"role\", activity: \"task\",\n // The hours a duty counts: its length less its unpaid break.\n hours: d => d.start.durationMinutes(d.end).subtract(d.unpaidBreak).divide(60.0),\n fields: { unpaidBreak: Schedule.field.number({ label: \"Unpaid break\", unit: \"min\", step: 15.0, min: 0.0 }),\n costCentre: Schedule.field.text({ label: \"Cost centre\" }), note: Schedule.field.text({ label: \"Note\" }) },\n // The app's check on a draft: Save waits until every duty has a cost centre.\n ready: d => East.equal(d.costCentre, \"\").ifElse(\n () => variant(\"invalid\", [{ field: \"costCentre\", message: \"Give the duty a cost centre\" }]),\n () => variant(\"ready\", null)),\n }) }}\n resources={{\n people: Schedule.resources(people.read(), { name: \"People\", icon: \"user\", label: p => p.name, group: p => p.team,\n position: p => p.position, skills: p => p.skills, contract: p => p.contract, agency: p => p.agency,\n trainer: p => p.trainer, trainee: p => p.trainee, usual: p => p.usual }),\n agencies: Schedule.resources(firms.read(), { name: \"Agencies\", icon: \"building\", label: a => a.name }),\n }}\n periods={Schedule.periods(weeks, { every: \"week\", status: { field: \"state\", draft: \"open\", published: \"issued\" }, dismissed: \"dismissed\" })}\n targets={Roster.targets(cover, { day: \"day\", group: \"team\", shift: \"shift\", positions: \"heads\", hours: \"hours\" })}\n shifts={Roster.shifts(times.read(), { label: s => s.label, code: s => s.code, start: s => s.start, hours: s => s.hours, order: s => s.order })}\n groups={Roster.groups(teams.read(), { label: t => t.label, order: t => t.order })}\n positions={Roster.positions(roles.read(), { label: p => p.label, code: p => p.code, lead: p => p.lead, order: p => p.order })}\n activities={Roster.activities(acts.read(), { label: a => a.label, code: a => a.code, group: a => a.group, order: a => a.order })}\n proposals={Roster.proposals(ideas.read(), { person: s => s.person, day: s => s.day, group: s => s.team, shift: s => s.shift, reason: s => s.why })}\n rules={{ rest: 10.0 }}\n library={[Roster.library.people(), Roster.library.activities(), Roster.library.tab(roles.read(), {\n name: \"Positions\", icon: \"id-badge\", label: p => p.label, meta: p => some(p.code),\n drop: (_p, key) => Schedule.patch(WarehouseDuty, { role: key }),\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
43610
43625
|
},
|
|
43611
43626
|
{
|
|
43612
43627
|
"id": "e3-ui:roster.examples.tsx:rosterMobile",
|
|
@@ -43640,7 +43655,7 @@
|
|
|
43640
43655
|
"languages": [
|
|
43641
43656
|
"tsx"
|
|
43642
43657
|
],
|
|
43643
|
-
"source": "// The warehouse roster in a phone-width container: explicit Assign, Move, Edit, Remove, Restore, Request, Accept, Reject and target actions over the warehouse's own records. The same session survives resizing; mobile cards register no drag sources or targets.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex(\"roster_duties_by_time\", rosterDuties, {
|
|
43658
|
+
"source": "// The warehouse roster in a phone-width container: explicit Assign, Move, Edit, Remove, Restore, Request, Accept, Reject and target actions over the warehouse's own records. The same session survives resizing; mobile cards register no drag sources or targets.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex.interval(\"roster_duties_by_time\", rosterDuties, { from: \"start\", to: \"end\" });\n\n/** The weeks of the rota: the one before issued, this one open. */\nexport const rosterWeeks = e3.record(\"roster_rota_weeks\", DictType(DateTimeType, WarehouseWeek), warehouseWeeks);\n\nexport const rosterWeeksPatch = e3.mutation.patch(rosterWeeks);\n\n/** The cover targets, a row per slot: the week before's, this week's and the forecast's for the week after. */\nexport const rosterCover = e3.record(\"roster_cover\", DictType(StringType, WarehouseCover), warehouseCover);\n\nexport const rosterCoverPatch = e3.mutation.patch(rosterCover);\n\n/** The suggestions: someone for a slot, and why. */\nexport const rosterSuggestions = e3.record(\"roster_suggestions\", DictType(StringType, WarehouseSuggestion), warehouseSuggestions);\n\nexport const rosterStaff = e3.record(\"roster_staff\", DictType(StringType, WarehousePerson), warehousePeople);\n\nexport const rosterAgencies = e3.record(\"roster_agencies\", DictType(StringType, WarehouseAgency), warehouseAgencies);\n\nexport const rosterShiftTimes = e3.record(\"roster_shift_times\", DictType(StringType, WarehouseShift), warehouseShifts);\n\nexport const rosterTeams = e3.record(\"roster_teams\", DictType(StringType, WarehouseTeam), warehouseTeams);\n\nexport const rosterPositions = e3.record(\"roster_positions\", DictType(StringType, WarehousePosition), warehousePositions);\n\n/** Every activity by its key: each group's skills, and the duty of none (#1342). */\nexport const rosterActivities = e3.record(\"roster_activities\", DictType(StringType, WarehouseActivity), warehouseActivities);\n\nexport const rosterStaffByTeamName = e3.recordIndex(\"roster_staff_by_team_name\", rosterStaff, { on: [\"team\", \"name\"] });\n\nexport const rosterStaffByPosition = e3.recordIndex(\"roster_staff_by_position\", rosterStaff, { on: [\"position\"] });\n\nexport const rosterStaffByTrainer = e3.recordIndex(\"roster_staff_by_trainer\", rosterStaff, { on: [\"trainer\"] });\n\nexport const rosterStaffByAgency = e3.recordIndex(\"roster_staff_by_agency\", rosterStaff, { on: [\"agency\"] });\n\nexport const rosterStaffBySkill = e3.recordIndex(\"roster_staff_by_skill\", rosterStaff, { on: [\"skills\"] });\n\nexport const rosterStaffByName = e3.recordIndex(\"roster_staff_by_name\", rosterStaff, { on: [\"name\"] });\n\nexport const rosterActivitiesByGroup = e3.recordIndex(\"roster_activities_by_group\", rosterActivities, { on: [\"group\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const rota = $.let(Record.bind(rosterDuties, [rosterDutiesPatch]));\n const entries = $.let(Data.bindPaged(rosterDuties));\n const byTime = $.let(Data.bindPaged(rosterDuties, { indexes: [rosterDutiesByTime] }));\n const weeks = $.let(Record.bind(rosterWeeks, [rosterWeeksPatch]));\n const cover = $.let(Record.bind(rosterCover, [rosterCoverPatch]));\n // Every person and activity is the calculation scope; the narrowing only chooses which show (#1342).\n const people = $.let(Data.bind(rosterStaff));\n const acts = $.let(Data.bind(rosterActivities));\n const staffIndex = $.let(Data.bindPaged(rosterStaff, { indexes: [rosterStaffByTeamName, rosterStaffByPosition, rosterStaffByTrainer, rosterStaffByAgency, rosterStaffBySkill, rosterStaffByName] }));\n const actIndex = $.let(Data.bindPaged(rosterActivities, { indexes: [rosterActivitiesByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.rosterMobile\", {\n duty: { source: byTime, range: \"start\" },\n people: { source: staffIndex, icon: { prefix: \"fas\", name: \"user\" }, search: [\"name\", \"skills\"],\n fields: { team: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\"] }, position: { label: \"Position\", hints: [\"lead\", \"deputy\", \"acting\", \"crew\"] },\n trainer: { label: \"Trainer\" }, agency: { label: \"Agency\" }, skills: { label: \"Skill\" }, name: { label: \"Name\" } } },\n activities: { source: actIndex, icon: { prefix: \"fas\", name: \"list-check\" }, fields: { group: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\", null] } } },\n }, { presets: [{ id: \"leads\", name: \"Leads\", where: { people: { position: [\"lead\", \"deputy\", \"acting\"] } } }] }));\n const firms = $.let(Data.bind(rosterAgencies));\n const times = $.let(Data.bind(rosterShiftTimes));\n const teams = $.let(Data.bind(rosterTeams));\n const roles = $.let(Data.bind(rosterPositions));\n const ideas = $.let(Data.bind(rosterSuggestions));\n return <Box width=\"100%\" maxWidth=\"390px\" height=\"760px\"><Roster id=\"rosterMobile\" week={{ start: \"sunday\" }} slice={{ slice }} inspector\n view={{ layout: \"shifts\", period: \"day\", date: new Date(\"2028-03-09T00:00:00Z\") }}\n events={{ duty: Schedule.events(rota, {\n name: \"Duty\", icon: \"user-clock\", entries,\n start: \"start\", end: \"end\", resource: { field: \"who\", of: { staff: \"people\", agency: \"agencies\" } },\n shift: \"shift\", group: \"team\", position: \"role\", activity: \"task\",\n hours: d => d.start.durationMinutes(d.end).subtract(d.unpaidBreak).divide(60.0),\n }) }}\n resources={{\n people: Schedule.resources(people.read(), { name: \"People\", icon: \"user\", label: p => p.name, group: p => p.team,\n position: p => p.position, skills: p => p.skills, contract: p => p.contract, agency: p => p.agency,\n trainer: p => p.trainer, trainee: p => p.trainee, usual: p => p.usual }),\n agencies: Schedule.resources(firms.read(), { name: \"Agencies\", icon: \"building\", label: a => a.name }),\n }}\n periods={Schedule.periods(weeks, { every: \"week\", status: { field: \"state\", draft: \"open\", published: \"issued\" }, dismissed: \"dismissed\" })}\n targets={Roster.targets(cover, { day: \"day\", group: \"team\", shift: \"shift\", positions: \"heads\", hours: \"hours\" })}\n shifts={Roster.shifts(times.read(), { label: s => s.label, code: s => s.code, start: s => s.start, hours: s => s.hours, order: s => s.order })}\n groups={Roster.groups(teams.read(), { label: t => t.label, order: t => t.order })}\n positions={Roster.positions(roles.read(), { label: p => p.label, code: p => p.code, lead: p => p.lead, order: p => p.order })}\n activities={Roster.activities(acts.read(), { label: a => a.label, code: a => a.code, group: a => a.group, order: a => a.order })}\n proposals={Roster.proposals(ideas.read(), { person: s => s.person, day: s => s.day, group: s => s.team, shift: s => s.shift, reason: s => s.why })}\n rules={{ rest: 10.0 }}\n library={[Roster.library.people(), Roster.library.activities(), Roster.library.tab(roles.read(), {\n name: \"Positions\", icon: \"id-badge\", label: p => p.label, meta: p => some(p.code),\n drop: (_p, key) => Schedule.patch(WarehouseDuty, { role: key }),\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
43644
43659
|
},
|
|
43645
43660
|
{
|
|
43646
43661
|
"id": "e3-ui:roster.examples.tsx:rosterPeople",
|
|
@@ -43648,18 +43663,25 @@
|
|
|
43648
43663
|
"package": "e3-ui",
|
|
43649
43664
|
"file": "east-ui/packages/e3-ui/test/roster/roster.examples.tsx",
|
|
43650
43665
|
"suite": "",
|
|
43651
|
-
"test": "
|
|
43666
|
+
"test": "The People layout by week over the same records (#1406): a row per person under their group's band, a tile per duty in each day cell in the shift's colour token — its hours only where a duty's own times differ (tileTimes=\"different\") — the week's hours and a meter in the name cell, `+ Assign` on an off day opening a menu of the home group's shifts, and the warehouse's week patterns in a Patterns tab, each dropped on a person's week as one undo step; the agency's requests their own row.",
|
|
43652
43667
|
"keywords": [
|
|
43653
43668
|
"Roster",
|
|
43654
43669
|
"Schedule.events",
|
|
43655
43670
|
"Schedule.resources",
|
|
43656
43671
|
"Schedule.periods",
|
|
43657
43672
|
"Roster.targets",
|
|
43673
|
+
"Roster.shifts",
|
|
43674
|
+
"color",
|
|
43675
|
+
"ColorToken",
|
|
43676
|
+
"Roster.patterns",
|
|
43677
|
+
"Roster.library.patterns",
|
|
43678
|
+
"tileTimes",
|
|
43658
43679
|
"Slice",
|
|
43659
43680
|
"Slice.bindRecords",
|
|
43660
43681
|
"Data.bindPaged",
|
|
43661
43682
|
"BuilderFrame",
|
|
43662
43683
|
"people",
|
|
43684
|
+
"week",
|
|
43663
43685
|
"Save",
|
|
43664
43686
|
"Publish",
|
|
43665
43687
|
"FieldForm"
|
|
@@ -43674,7 +43696,46 @@
|
|
|
43674
43696
|
"languages": [
|
|
43675
43697
|
"tsx"
|
|
43676
43698
|
],
|
|
43677
|
-
"source": "//
|
|
43699
|
+
"source": "// The People layout by week over the same records (#1406): a row per person under their group's band, a tile per duty in each day cell in the shift's colour token — its hours only where a duty's own times differ (tileTimes=\"different\") — the week's hours and a meter in the name cell, `+ Assign` on an off day opening a menu of the home group's shifts, and the warehouse's week patterns in a Patterns tab, each dropped on a person's week as one undo step; the agency's requests their own row.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex.interval(\"roster_duties_by_time\", rosterDuties, { from: \"start\", to: \"end\" });\n\n/** The weeks of the rota: the one before issued, this one open. */\nexport const rosterWeeks = e3.record(\"roster_rota_weeks\", DictType(DateTimeType, WarehouseWeek), warehouseWeeks);\n\nexport const rosterWeeksPatch = e3.mutation.patch(rosterWeeks);\n\n/** The cover targets, a row per slot: the week before's, this week's and the forecast's for the week after. */\nexport const rosterCover = e3.record(\"roster_cover\", DictType(StringType, WarehouseCover), warehouseCover);\n\nexport const rosterCoverPatch = e3.mutation.patch(rosterCover);\n\n/** The suggestions: someone for a slot, and why. */\nexport const rosterSuggestions = e3.record(\"roster_suggestions\", DictType(StringType, WarehouseSuggestion), warehouseSuggestions);\n\nexport const rosterStaff = e3.record(\"roster_staff\", DictType(StringType, WarehousePerson), warehousePeople);\n\nexport const rosterAgencies = e3.record(\"roster_agencies\", DictType(StringType, WarehouseAgency), warehouseAgencies);\n\nexport const rosterShiftTimes = e3.record(\"roster_shift_times\", DictType(StringType, WarehouseShift), warehouseShifts);\n\nexport const rosterTeams = e3.record(\"roster_teams\", DictType(StringType, WarehouseTeam), warehouseTeams);\n\nexport const rosterPositions = e3.record(\"roster_positions\", DictType(StringType, WarehousePosition), warehousePositions);\n\n/** Every activity by its key: each group's skills, and the duty of none (#1342). */\nexport const rosterActivities = e3.record(\"roster_activities\", DictType(StringType, WarehouseActivity), warehouseActivities);\n\n/** The week patterns (#1406): the thirteen a planner drops on a person's week, the warehouse's own record. */\nexport const rosterPatterns = e3.record(\"roster_week_patterns\", DictType(StringType, WarehousePattern), warehousePatterns);\n\nexport const rosterStaffByTeamName = e3.recordIndex(\"roster_staff_by_team_name\", rosterStaff, { on: [\"team\", \"name\"] });\n\nexport const rosterStaffByPosition = e3.recordIndex(\"roster_staff_by_position\", rosterStaff, { on: [\"position\"] });\n\nexport const rosterStaffByTrainer = e3.recordIndex(\"roster_staff_by_trainer\", rosterStaff, { on: [\"trainer\"] });\n\nexport const rosterStaffByAgency = e3.recordIndex(\"roster_staff_by_agency\", rosterStaff, { on: [\"agency\"] });\n\nexport const rosterStaffBySkill = e3.recordIndex(\"roster_staff_by_skill\", rosterStaff, { on: [\"skills\"] });\n\nexport const rosterStaffByName = e3.recordIndex(\"roster_staff_by_name\", rosterStaff, { on: [\"name\"] });\n\nexport const rosterActivitiesByGroup = e3.recordIndex(\"roster_activities_by_group\", rosterActivities, { on: [\"group\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const rota = $.let(Record.bind(rosterDuties, [rosterDutiesPatch]));\n const entries = $.let(Data.bindPaged(rosterDuties));\n const byTime = $.let(Data.bindPaged(rosterDuties, { indexes: [rosterDutiesByTime] }));\n const weeks = $.let(Record.bind(rosterWeeks, [rosterWeeksPatch]));\n const cover = $.let(Record.bind(rosterCover, [rosterCoverPatch]));\n // Every person and activity is the calculation scope; the narrowing only chooses which show (#1342).\n const people = $.let(Data.bind(rosterStaff));\n const acts = $.let(Data.bind(rosterActivities));\n const staffIndex = $.let(Data.bindPaged(rosterStaff, { indexes: [rosterStaffByTeamName, rosterStaffByPosition, rosterStaffByTrainer, rosterStaffByAgency, rosterStaffBySkill, rosterStaffByName] }));\n const actIndex = $.let(Data.bindPaged(rosterActivities, { indexes: [rosterActivitiesByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.rosterPeople\", {\n duty: { source: byTime, range: \"start\" },\n people: { source: staffIndex, icon: { prefix: \"fas\", name: \"user\" }, search: [\"name\", \"skills\"],\n fields: { team: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\"] }, position: { label: \"Position\", hints: [\"lead\", \"deputy\", \"acting\", \"crew\"] },\n trainer: { label: \"Trainer\" }, agency: { label: \"Agency\" }, skills: { label: \"Skill\" }, name: { label: \"Name\" } } },\n activities: { source: actIndex, icon: { prefix: \"fas\", name: \"list-check\" }, fields: { group: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\", null] } } },\n }, { presets: [{ id: \"leads\", name: \"Leads\", where: { people: { position: [\"lead\", \"deputy\", \"acting\"] } } }] }));\n const firms = $.let(Data.bind(rosterAgencies));\n const times = $.let(Data.bind(rosterShiftTimes));\n const teams = $.let(Data.bind(rosterTeams));\n const roles = $.let(Data.bind(rosterPositions));\n const ideas = $.let(Data.bind(rosterSuggestions));\n // The warehouse's week patterns: the cards of the Patterns tab, each dropped on a person's week.\n const weekPatterns = $.let(Data.bind(rosterPatterns));\n return <Box width=\"100%\" height=\"760px\"><Roster id=\"rosterPeople\" week={{ start: \"sunday\" }} slice={{ slice }} inspector\n view={{ layout: \"people\", period: \"week\", date: new Date(\"2028-03-09T00:00:00Z\") }}\n tileTimes=\"different\"\n events={{ duty: Schedule.events(rota, {\n name: \"Duty\", icon: \"user-clock\", entries,\n start: \"start\", end: \"end\", resource: { field: \"who\", of: { staff: \"people\", agency: \"agencies\" } },\n shift: \"shift\", group: \"team\", position: \"role\", activity: \"task\",\n hours: d => d.start.durationMinutes(d.end).subtract(d.unpaidBreak).divide(60.0),\n }) }}\n resources={{\n people: Schedule.resources(people.read(), { name: \"People\", icon: \"user\", label: p => p.name, group: p => p.team,\n position: p => p.position, skills: p => p.skills, contract: p => p.contract, agency: p => p.agency,\n trainer: p => p.trainer, trainee: p => p.trainee, usual: p => p.usual }),\n agencies: Schedule.resources(firms.read(), { name: \"Agencies\", icon: \"building\", label: a => a.name }),\n }}\n periods={Schedule.periods(weeks, { every: \"week\", status: { field: \"state\", draft: \"open\", published: \"issued\" }, dismissed: \"dismissed\" })}\n targets={Roster.targets(cover, { day: \"day\", group: \"team\", shift: \"shift\", positions: \"heads\", hours: \"hours\" })}\n // Each shift's colour token, the warehouse's own: the tiles' and the marks' fill.\n shifts={Roster.shifts(times.read(), { label: s => s.label, code: s => s.code, start: s => s.start, hours: s => s.hours, order: s => s.order, color: s => s.colour })}\n groups={Roster.groups(teams.read(), { label: t => t.label, order: t => t.order })}\n positions={Roster.positions(roles.read(), { label: p => p.label, code: p => p.code, lead: p => p.lead, order: p => p.order })}\n activities={Roster.activities(acts.read(), { label: a => a.label, code: a => a.code, group: a => a.group, order: a => a.order })}\n proposals={Roster.proposals(ideas.read(), { person: s => s.person, day: s => s.day, group: s => s.team, shift: s => s.shift, reason: s => s.why })}\n patterns={Roster.patterns(weekPatterns.read(), { name: \"Patterns\", icon: \"calendar-week\", label: w => w.label, days: w => w.days, order: w => w.order })}\n rules={{ rest: 10.0 }}\n library={[Roster.library.people(), Roster.library.patterns(), Roster.library.activities(), Roster.library.tab(roles.read(), {\n name: \"Positions\", icon: \"id-badge\", label: p => p.label, meta: p => some(p.code),\n drop: (_p, key) => Schedule.patch(WarehouseDuty, { role: key }),\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
43700
|
+
},
|
|
43701
|
+
{
|
|
43702
|
+
"id": "e3-ui:roster.examples.tsx:rosterPeopleMonth",
|
|
43703
|
+
"skill": "e3-ui",
|
|
43704
|
+
"package": "e3-ui",
|
|
43705
|
+
"file": "east-ui/packages/e3-ui/test/roster/roster.examples.tsx",
|
|
43706
|
+
"suite": "",
|
|
43707
|
+
"test": "The People layout by month (#1406): a column per week touching the month, four to six, each cell seven marks in the shifts' colours lettered where a day is wide enough; a column's header opens its week, a cell's click selects the person-week the inspector's Details then read, and a pattern dropped on a cell replaces that week as one undo step. Week · Month in the Period segment; ‹ › step a month.",
|
|
43708
|
+
"keywords": [
|
|
43709
|
+
"Roster",
|
|
43710
|
+
"Schedule.events",
|
|
43711
|
+
"Schedule.resources",
|
|
43712
|
+
"Schedule.periods",
|
|
43713
|
+
"Roster.shifts",
|
|
43714
|
+
"color",
|
|
43715
|
+
"ColorToken",
|
|
43716
|
+
"Roster.patterns",
|
|
43717
|
+
"Roster.library.patterns",
|
|
43718
|
+
"letters",
|
|
43719
|
+
"month",
|
|
43720
|
+
"Slice",
|
|
43721
|
+
"Slice.bindRecords",
|
|
43722
|
+
"Data.bindPaged",
|
|
43723
|
+
"BuilderFrame",
|
|
43724
|
+
"people",
|
|
43725
|
+
"Save",
|
|
43726
|
+
"Publish"
|
|
43727
|
+
],
|
|
43728
|
+
"imports": [
|
|
43729
|
+
"import {",
|
|
43730
|
+
"import e3 from \"@elaraai/e3\";",
|
|
43731
|
+
"import { Box, Field, Reactive, Slice, UIComponentType, VStack } from \"@elaraai/east-ui\";",
|
|
43732
|
+
"import { Data, Func, Record, Roster, Schedule } from \"@elaraai/e3-ui\";",
|
|
43733
|
+
"import {"
|
|
43734
|
+
],
|
|
43735
|
+
"languages": [
|
|
43736
|
+
"tsx"
|
|
43737
|
+
],
|
|
43738
|
+
"source": "// The People layout by month (#1406): a column per week touching the month, four to six, each cell seven marks in the shifts' colours lettered where a day is wide enough; a column's header opens its week, a cell's click selects the person-week the inspector's Details then read, and a pattern dropped on a cell replaces that week as one undo step. Week · Month in the Period segment; ‹ › step a month.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex.interval(\"roster_duties_by_time\", rosterDuties, { from: \"start\", to: \"end\" });\n\n/** The weeks of the rota: the one before issued, this one open. */\nexport const rosterWeeks = e3.record(\"roster_rota_weeks\", DictType(DateTimeType, WarehouseWeek), warehouseWeeks);\n\nexport const rosterWeeksPatch = e3.mutation.patch(rosterWeeks);\n\n/** The cover targets, a row per slot: the week before's, this week's and the forecast's for the week after. */\nexport const rosterCover = e3.record(\"roster_cover\", DictType(StringType, WarehouseCover), warehouseCover);\n\nexport const rosterCoverPatch = e3.mutation.patch(rosterCover);\n\n/** The suggestions: someone for a slot, and why. */\nexport const rosterSuggestions = e3.record(\"roster_suggestions\", DictType(StringType, WarehouseSuggestion), warehouseSuggestions);\n\nexport const rosterStaff = e3.record(\"roster_staff\", DictType(StringType, WarehousePerson), warehousePeople);\n\nexport const rosterAgencies = e3.record(\"roster_agencies\", DictType(StringType, WarehouseAgency), warehouseAgencies);\n\nexport const rosterShiftTimes = e3.record(\"roster_shift_times\", DictType(StringType, WarehouseShift), warehouseShifts);\n\nexport const rosterTeams = e3.record(\"roster_teams\", DictType(StringType, WarehouseTeam), warehouseTeams);\n\nexport const rosterPositions = e3.record(\"roster_positions\", DictType(StringType, WarehousePosition), warehousePositions);\n\n/** Every activity by its key: each group's skills, and the duty of none (#1342). */\nexport const rosterActivities = e3.record(\"roster_activities\", DictType(StringType, WarehouseActivity), warehouseActivities);\n\n/** The week patterns (#1406): the thirteen a planner drops on a person's week, the warehouse's own record. */\nexport const rosterPatterns = e3.record(\"roster_week_patterns\", DictType(StringType, WarehousePattern), warehousePatterns);\n\nexport const rosterStaffByTeamName = e3.recordIndex(\"roster_staff_by_team_name\", rosterStaff, { on: [\"team\", \"name\"] });\n\nexport const rosterStaffByPosition = e3.recordIndex(\"roster_staff_by_position\", rosterStaff, { on: [\"position\"] });\n\nexport const rosterStaffByTrainer = e3.recordIndex(\"roster_staff_by_trainer\", rosterStaff, { on: [\"trainer\"] });\n\nexport const rosterStaffByAgency = e3.recordIndex(\"roster_staff_by_agency\", rosterStaff, { on: [\"agency\"] });\n\nexport const rosterStaffBySkill = e3.recordIndex(\"roster_staff_by_skill\", rosterStaff, { on: [\"skills\"] });\n\nexport const rosterStaffByName = e3.recordIndex(\"roster_staff_by_name\", rosterStaff, { on: [\"name\"] });\n\nexport const rosterActivitiesByGroup = e3.recordIndex(\"roster_activities_by_group\", rosterActivities, { on: [\"group\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const rota = $.let(Record.bind(rosterDuties, [rosterDutiesPatch]));\n const entries = $.let(Data.bindPaged(rosterDuties));\n const byTime = $.let(Data.bindPaged(rosterDuties, { indexes: [rosterDutiesByTime] }));\n const weeks = $.let(Record.bind(rosterWeeks, [rosterWeeksPatch]));\n const cover = $.let(Record.bind(rosterCover, [rosterCoverPatch]));\n const people = $.let(Data.bind(rosterStaff));\n const acts = $.let(Data.bind(rosterActivities));\n const staffIndex = $.let(Data.bindPaged(rosterStaff, { indexes: [rosterStaffByTeamName, rosterStaffByPosition, rosterStaffByTrainer, rosterStaffByAgency, rosterStaffBySkill, rosterStaffByName] }));\n const actIndex = $.let(Data.bindPaged(rosterActivities, { indexes: [rosterActivitiesByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.rosterPeopleMonth\", {\n duty: { source: byTime, range: \"start\" },\n people: { source: staffIndex, icon: { prefix: \"fas\", name: \"user\" }, search: [\"name\", \"skills\"],\n fields: { team: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\"] }, position: { label: \"Position\", hints: [\"lead\", \"deputy\", \"acting\", \"crew\"] },\n trainer: { label: \"Trainer\" }, agency: { label: \"Agency\" }, skills: { label: \"Skill\" }, name: { label: \"Name\" } } },\n activities: { source: actIndex, icon: { prefix: \"fas\", name: \"list-check\" }, fields: { group: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\", null] } } },\n }, { presets: [{ id: \"leads\", name: \"Leads\", where: { people: { position: [\"lead\", \"deputy\", \"acting\"] } } }] }));\n const firms = $.let(Data.bind(rosterAgencies));\n const times = $.let(Data.bind(rosterShiftTimes));\n const teams = $.let(Data.bind(rosterTeams));\n const roles = $.let(Data.bind(rosterPositions));\n const ideas = $.let(Data.bind(rosterSuggestions));\n const weekPatterns = $.let(Data.bind(rosterPatterns));\n return <Box width=\"100%\" height=\"760px\"><Roster id=\"rosterPeopleMonth\" week={{ start: \"sunday\" }} slice={{ slice }} inspector\n view={{ layout: \"people\", period: \"month\", date: new Date(\"2028-03-09T00:00:00Z\") }}\n letters\n events={{ duty: Schedule.events(rota, {\n name: \"Duty\", icon: \"user-clock\", entries,\n start: \"start\", end: \"end\", resource: { field: \"who\", of: { staff: \"people\", agency: \"agencies\" } },\n shift: \"shift\", group: \"team\", position: \"role\", activity: \"task\",\n hours: d => d.start.durationMinutes(d.end).subtract(d.unpaidBreak).divide(60.0),\n }) }}\n resources={{\n people: Schedule.resources(people.read(), { name: \"People\", icon: \"user\", label: p => p.name, group: p => p.team,\n position: p => p.position, skills: p => p.skills, contract: p => p.contract, agency: p => p.agency,\n trainer: p => p.trainer, trainee: p => p.trainee, usual: p => p.usual }),\n agencies: Schedule.resources(firms.read(), { name: \"Agencies\", icon: \"building\", label: a => a.name }),\n }}\n periods={Schedule.periods(weeks, { every: \"week\", status: { field: \"state\", draft: \"open\", published: \"issued\" }, dismissed: \"dismissed\" })}\n targets={Roster.targets(cover, { day: \"day\", group: \"team\", shift: \"shift\", positions: \"heads\", hours: \"hours\" })}\n shifts={Roster.shifts(times.read(), { label: s => s.label, code: s => s.code, start: s => s.start, hours: s => s.hours, order: s => s.order, color: s => s.colour })}\n groups={Roster.groups(teams.read(), { label: t => t.label, order: t => t.order })}\n positions={Roster.positions(roles.read(), { label: p => p.label, code: p => p.code, lead: p => p.lead, order: p => p.order })}\n activities={Roster.activities(acts.read(), { label: a => a.label, code: a => a.code, group: a => a.group, order: a => a.order })}\n proposals={Roster.proposals(ideas.read(), { person: s => s.person, day: s => s.day, group: s => s.team, shift: s => s.shift, reason: s => s.why })}\n patterns={Roster.patterns(weekPatterns.read(), { name: \"Patterns\", icon: \"calendar-week\", label: w => w.label, days: w => w.days, order: w => w.order })}\n rules={{ rest: 10.0 }}\n library={[Roster.library.people(), Roster.library.patterns(), Roster.library.activities()]} />\n </Box>;\n }}</Reactive>)"
|
|
43678
43739
|
},
|
|
43679
43740
|
{
|
|
43680
43741
|
"id": "e3-ui:roster.examples.tsx:rosterWeek",
|
|
@@ -43708,7 +43769,7 @@
|
|
|
43708
43769
|
"languages": [
|
|
43709
43770
|
"tsx"
|
|
43710
43771
|
],
|
|
43711
|
-
"source": "// Seven days of group-by-shift coverage over the warehouse's own records, opening any day for detailed staffing.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex(\"roster_duties_by_time\", rosterDuties, {
|
|
43772
|
+
"source": "// Seven days of group-by-shift coverage over the warehouse's own records, opening any day for detailed staffing.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex.interval(\"roster_duties_by_time\", rosterDuties, { from: \"start\", to: \"end\" });\n\n/** The weeks of the rota: the one before issued, this one open. */\nexport const rosterWeeks = e3.record(\"roster_rota_weeks\", DictType(DateTimeType, WarehouseWeek), warehouseWeeks);\n\nexport const rosterWeeksPatch = e3.mutation.patch(rosterWeeks);\n\n/** The cover targets, a row per slot: the week before's, this week's and the forecast's for the week after. */\nexport const rosterCover = e3.record(\"roster_cover\", DictType(StringType, WarehouseCover), warehouseCover);\n\nexport const rosterCoverPatch = e3.mutation.patch(rosterCover);\n\n/** The suggestions: someone for a slot, and why. */\nexport const rosterSuggestions = e3.record(\"roster_suggestions\", DictType(StringType, WarehouseSuggestion), warehouseSuggestions);\n\nexport const rosterStaff = e3.record(\"roster_staff\", DictType(StringType, WarehousePerson), warehousePeople);\n\nexport const rosterAgencies = e3.record(\"roster_agencies\", DictType(StringType, WarehouseAgency), warehouseAgencies);\n\nexport const rosterShiftTimes = e3.record(\"roster_shift_times\", DictType(StringType, WarehouseShift), warehouseShifts);\n\nexport const rosterTeams = e3.record(\"roster_teams\", DictType(StringType, WarehouseTeam), warehouseTeams);\n\nexport const rosterPositions = e3.record(\"roster_positions\", DictType(StringType, WarehousePosition), warehousePositions);\n\n/** Every activity by its key: each group's skills, and the duty of none (#1342). */\nexport const rosterActivities = e3.record(\"roster_activities\", DictType(StringType, WarehouseActivity), warehouseActivities);\n\nexport const rosterStaffByTeamName = e3.recordIndex(\"roster_staff_by_team_name\", rosterStaff, { on: [\"team\", \"name\"] });\n\nexport const rosterStaffByPosition = e3.recordIndex(\"roster_staff_by_position\", rosterStaff, { on: [\"position\"] });\n\nexport const rosterStaffByTrainer = e3.recordIndex(\"roster_staff_by_trainer\", rosterStaff, { on: [\"trainer\"] });\n\nexport const rosterStaffByAgency = e3.recordIndex(\"roster_staff_by_agency\", rosterStaff, { on: [\"agency\"] });\n\nexport const rosterStaffBySkill = e3.recordIndex(\"roster_staff_by_skill\", rosterStaff, { on: [\"skills\"] });\n\nexport const rosterStaffByName = e3.recordIndex(\"roster_staff_by_name\", rosterStaff, { on: [\"name\"] });\n\nexport const rosterActivitiesByGroup = e3.recordIndex(\"roster_activities_by_group\", rosterActivities, { on: [\"group\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const rota = $.let(Record.bind(rosterDuties, [rosterDutiesPatch]));\n const entries = $.let(Data.bindPaged(rosterDuties));\n const byTime = $.let(Data.bindPaged(rosterDuties, { indexes: [rosterDutiesByTime] }));\n const weeks = $.let(Record.bind(rosterWeeks, [rosterWeeksPatch]));\n const cover = $.let(Record.bind(rosterCover, [rosterCoverPatch]));\n // Every person and activity is the calculation scope; the narrowing only chooses which show (#1342).\n const people = $.let(Data.bind(rosterStaff));\n const acts = $.let(Data.bind(rosterActivities));\n const staffIndex = $.let(Data.bindPaged(rosterStaff, { indexes: [rosterStaffByTeamName, rosterStaffByPosition, rosterStaffByTrainer, rosterStaffByAgency, rosterStaffBySkill, rosterStaffByName] }));\n const actIndex = $.let(Data.bindPaged(rosterActivities, { indexes: [rosterActivitiesByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.rosterWeek\", {\n duty: { source: byTime, range: \"start\" },\n people: { source: staffIndex, icon: { prefix: \"fas\", name: \"user\" }, search: [\"name\", \"skills\"],\n fields: { team: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\"] }, position: { label: \"Position\", hints: [\"lead\", \"deputy\", \"acting\", \"crew\"] },\n trainer: { label: \"Trainer\" }, agency: { label: \"Agency\" }, skills: { label: \"Skill\" }, name: { label: \"Name\" } } },\n activities: { source: actIndex, icon: { prefix: \"fas\", name: \"list-check\" }, fields: { group: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\", null] } } },\n }, { presets: [{ id: \"leads\", name: \"Leads\", where: { people: { position: [\"lead\", \"deputy\", \"acting\"] } } }] }));\n const firms = $.let(Data.bind(rosterAgencies));\n const times = $.let(Data.bind(rosterShiftTimes));\n const teams = $.let(Data.bind(rosterTeams));\n const roles = $.let(Data.bind(rosterPositions));\n const ideas = $.let(Data.bind(rosterSuggestions));\n return <Box width=\"100%\" height=\"760px\"><Roster id=\"rosterWeek\" week={{ start: \"sunday\" }} slice={{ slice }} inspector\n view={{ layout: \"shifts\", period: \"week\", date: new Date(\"2028-03-09T00:00:00Z\") }}\n events={{ duty: Schedule.events(rota, {\n name: \"Duty\", icon: \"user-clock\", entries,\n start: \"start\", end: \"end\", resource: { field: \"who\", of: { staff: \"people\", agency: \"agencies\" } },\n shift: \"shift\", group: \"team\", position: \"role\", activity: \"task\",\n hours: d => d.start.durationMinutes(d.end).subtract(d.unpaidBreak).divide(60.0),\n }) }}\n resources={{\n people: Schedule.resources(people.read(), { name: \"People\", icon: \"user\", label: p => p.name, group: p => p.team,\n position: p => p.position, skills: p => p.skills, contract: p => p.contract, agency: p => p.agency,\n trainer: p => p.trainer, trainee: p => p.trainee, usual: p => p.usual }),\n agencies: Schedule.resources(firms.read(), { name: \"Agencies\", icon: \"building\", label: a => a.name }),\n }}\n periods={Schedule.periods(weeks, { every: \"week\", status: { field: \"state\", draft: \"open\", published: \"issued\" }, dismissed: \"dismissed\" })}\n targets={Roster.targets(cover, { day: \"day\", group: \"team\", shift: \"shift\", positions: \"heads\", hours: \"hours\" })}\n shifts={Roster.shifts(times.read(), { label: s => s.label, code: s => s.code, start: s => s.start, hours: s => s.hours, order: s => s.order })}\n groups={Roster.groups(teams.read(), { label: t => t.label, order: t => t.order })}\n positions={Roster.positions(roles.read(), { label: p => p.label, code: p => p.code, lead: p => p.lead, order: p => p.order })}\n activities={Roster.activities(acts.read(), { label: a => a.label, code: a => a.code, group: a => a.group, order: a => a.order })}\n proposals={Roster.proposals(ideas.read(), { person: s => s.person, day: s => s.day, group: s => s.team, shift: s => s.shift, reason: s => s.why })}\n rules={{ rest: 10.0 }}\n library={[Roster.library.people(), Roster.library.activities(), Roster.library.tab(roles.read(), {\n name: \"Positions\", icon: \"id-badge\", label: p => p.label, meta: p => some(p.code),\n drop: (_p, key) => Schedule.patch(WarehouseDuty, { role: key }),\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
43712
43773
|
},
|
|
43713
43774
|
{
|
|
43714
43775
|
"id": "e3-ui:roster.examples.tsx:rosterWindowed",
|
|
@@ -43740,7 +43801,7 @@
|
|
|
43740
43801
|
"languages": [
|
|
43741
43802
|
"tsx"
|
|
43742
43803
|
],
|
|
43743
|
-
"source": "// The duties read a week at a time through the slice's duty source — an interval index over their times, its range the Roster's own navigation — and the rota's weeks by key: only the week, the week before it (Copy's source) and the open picker's month are requested, never a record whole.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex(\"roster_duties_by_time\", rosterDuties, {
|
|
43804
|
+
"source": "// The duties read a week at a time through the slice's duty source — an interval index over their times, its range the Roster's own navigation — and the rota's weeks by key: only the week, the week before it (Copy's source) and the open picker's month are requested, never a record whole.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex.interval(\"roster_duties_by_time\", rosterDuties, { from: \"start\", to: \"end\" });\n\n/** The weeks of the rota: the one before issued, this one open. */\nexport const rosterWeeks = e3.record(\"roster_rota_weeks\", DictType(DateTimeType, WarehouseWeek), warehouseWeeks);\n\nexport const rosterWeeksPatch = e3.mutation.patch(rosterWeeks);\n\n/** The cover targets, a row per slot: the week before's, this week's and the forecast's for the week after. */\nexport const rosterCover = e3.record(\"roster_cover\", DictType(StringType, WarehouseCover), warehouseCover);\n\nexport const rosterCoverPatch = e3.mutation.patch(rosterCover);\n\n/** The suggestions: someone for a slot, and why. */\nexport const rosterSuggestions = e3.record(\"roster_suggestions\", DictType(StringType, WarehouseSuggestion), warehouseSuggestions);\n\nexport const rosterStaff = e3.record(\"roster_staff\", DictType(StringType, WarehousePerson), warehousePeople);\n\nexport const rosterAgencies = e3.record(\"roster_agencies\", DictType(StringType, WarehouseAgency), warehouseAgencies);\n\nexport const rosterShiftTimes = e3.record(\"roster_shift_times\", DictType(StringType, WarehouseShift), warehouseShifts);\n\nexport const rosterTeams = e3.record(\"roster_teams\", DictType(StringType, WarehouseTeam), warehouseTeams);\n\nexport const rosterPositions = e3.record(\"roster_positions\", DictType(StringType, WarehousePosition), warehousePositions);\n\n/** Every activity by its key: each group's skills, and the duty of none (#1342). */\nexport const rosterActivities = e3.record(\"roster_activities\", DictType(StringType, WarehouseActivity), warehouseActivities);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const rota = $.let(Record.bind(rosterDuties, [rosterDutiesPatch]));\n const entries = $.let(Data.bindPaged(rosterDuties));\n const byTime = $.let(Data.bindPaged(rosterDuties, { indexes: [rosterDutiesByTime] }));\n // The weeks of the rota read by key: the week, the week before it and the open picker's month.\n const weeks = $.let(Record.bind(rosterWeeks, [rosterWeeksPatch]));\n const weekEntries = $.let(Data.bindPaged(rosterWeeks));\n const cover = $.let(Record.bind(rosterCover, [rosterCoverPatch]));\n const people = $.let(Data.bind(rosterStaff));\n const acts = $.let(Data.bind(rosterActivities));\n // One source: the week's duties, its range the Roster's navigation.\n const slice = $.let(Slice.bindRecords(\"ex.rosterWindowed\", { duty: { source: byTime, range: \"start\" } }));\n const firms = $.let(Data.bind(rosterAgencies));\n const times = $.let(Data.bind(rosterShiftTimes));\n const teams = $.let(Data.bind(rosterTeams));\n const roles = $.let(Data.bind(rosterPositions));\n const ideas = $.let(Data.bind(rosterSuggestions));\n return <Box width=\"100%\" height=\"760px\"><Roster id=\"rosterWindowed\" week={{ start: \"sunday\" }} slice={{ slice }} inspector\n view={{ layout: \"shifts\", period: \"day\", date: new Date(\"2028-03-12T00:00:00Z\") }}\n events={{ duty: Schedule.events(rota, {\n name: \"Duty\", icon: \"user-clock\", entries,\n start: \"start\", end: \"end\", resource: { field: \"who\", of: { staff: \"people\", agency: \"agencies\" } },\n shift: \"shift\", group: \"team\", position: \"role\", activity: \"task\",\n hours: d => d.start.durationMinutes(d.end).subtract(d.unpaidBreak).divide(60.0),\n }) }}\n resources={{\n people: Schedule.resources(people.read(), { name: \"People\", icon: \"user\", label: p => p.name, group: p => p.team,\n position: p => p.position, skills: p => p.skills, contract: p => p.contract, agency: p => p.agency,\n trainer: p => p.trainer, trainee: p => p.trainee, usual: p => p.usual }),\n agencies: Schedule.resources(firms.read(), { name: \"Agencies\", icon: \"building\", label: a => a.name }),\n }}\n periods={Schedule.periods(weeks, { every: \"week\", status: { field: \"state\", draft: \"open\", published: \"issued\" }, dismissed: \"dismissed\", entries: weekEntries })}\n targets={Roster.targets(cover, { day: \"day\", group: \"team\", shift: \"shift\", positions: \"heads\", hours: \"hours\" })}\n shifts={Roster.shifts(times.read(), { label: s => s.label, code: s => s.code, start: s => s.start, hours: s => s.hours, order: s => s.order })}\n groups={Roster.groups(teams.read(), { label: t => t.label, order: t => t.order })}\n positions={Roster.positions(roles.read(), { label: p => p.label, code: p => p.code, lead: p => p.lead, order: p => p.order })}\n activities={Roster.activities(acts.read(), { label: a => a.label, code: a => a.code, group: a => a.group, order: a => a.order })}\n proposals={Roster.proposals(ideas.read(), { person: s => s.person, day: s => s.day, group: s => s.team, shift: s => s.shift, reason: s => s.why })}\n rules={{ rest: 10.0 }}\n library={[Roster.library.people(), Roster.library.activities(), Roster.library.tab(roles.read(), {\n name: \"Positions\", icon: \"id-badge\", label: p => p.label, meta: p => some(p.code),\n drop: (_p, key) => Schedule.patch(WarehouseDuty, { role: key }),\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
43744
43805
|
},
|
|
43745
43806
|
{
|
|
43746
43807
|
"id": "e3-ui:roster.examples.tsx:rosterMinimal",
|
|
@@ -43801,7 +43862,7 @@
|
|
|
43801
43862
|
"languages": [
|
|
43802
43863
|
"tsx"
|
|
43803
43864
|
],
|
|
43804
|
-
"source": "// The week before, published, is read-only: navigation, staff filtering, coverage and inspection remain available.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex(\"roster_duties_by_time\", rosterDuties, {
|
|
43865
|
+
"source": "// The week before, published, is read-only: navigation, staff filtering, coverage and inspection remain available.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex.interval(\"roster_duties_by_time\", rosterDuties, { from: \"start\", to: \"end\" });\n\n/** The weeks of the rota: the one before issued, this one open. */\nexport const rosterWeeks = e3.record(\"roster_rota_weeks\", DictType(DateTimeType, WarehouseWeek), warehouseWeeks);\n\nexport const rosterWeeksPatch = e3.mutation.patch(rosterWeeks);\n\n/** The cover targets, a row per slot: the week before's, this week's and the forecast's for the week after. */\nexport const rosterCover = e3.record(\"roster_cover\", DictType(StringType, WarehouseCover), warehouseCover);\n\nexport const rosterCoverPatch = e3.mutation.patch(rosterCover);\n\n/** The suggestions: someone for a slot, and why. */\nexport const rosterSuggestions = e3.record(\"roster_suggestions\", DictType(StringType, WarehouseSuggestion), warehouseSuggestions);\n\nexport const rosterStaff = e3.record(\"roster_staff\", DictType(StringType, WarehousePerson), warehousePeople);\n\nexport const rosterAgencies = e3.record(\"roster_agencies\", DictType(StringType, WarehouseAgency), warehouseAgencies);\n\nexport const rosterShiftTimes = e3.record(\"roster_shift_times\", DictType(StringType, WarehouseShift), warehouseShifts);\n\nexport const rosterTeams = e3.record(\"roster_teams\", DictType(StringType, WarehouseTeam), warehouseTeams);\n\nexport const rosterPositions = e3.record(\"roster_positions\", DictType(StringType, WarehousePosition), warehousePositions);\n\n/** Every activity by its key: each group's skills, and the duty of none (#1342). */\nexport const rosterActivities = e3.record(\"roster_activities\", DictType(StringType, WarehouseActivity), warehouseActivities);\n\nexport const rosterStaffByTeamName = e3.recordIndex(\"roster_staff_by_team_name\", rosterStaff, { on: [\"team\", \"name\"] });\n\nexport const rosterStaffByPosition = e3.recordIndex(\"roster_staff_by_position\", rosterStaff, { on: [\"position\"] });\n\nexport const rosterStaffByTrainer = e3.recordIndex(\"roster_staff_by_trainer\", rosterStaff, { on: [\"trainer\"] });\n\nexport const rosterStaffByAgency = e3.recordIndex(\"roster_staff_by_agency\", rosterStaff, { on: [\"agency\"] });\n\nexport const rosterStaffBySkill = e3.recordIndex(\"roster_staff_by_skill\", rosterStaff, { on: [\"skills\"] });\n\nexport const rosterStaffByName = e3.recordIndex(\"roster_staff_by_name\", rosterStaff, { on: [\"name\"] });\n\nexport const rosterActivitiesByGroup = e3.recordIndex(\"roster_activities_by_group\", rosterActivities, { on: [\"group\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const rota = $.let(Record.bind(rosterDuties, [rosterDutiesPatch]));\n const entries = $.let(Data.bindPaged(rosterDuties));\n const byTime = $.let(Data.bindPaged(rosterDuties, { indexes: [rosterDutiesByTime] }));\n const weeks = $.let(Record.bind(rosterWeeks, [rosterWeeksPatch]));\n const cover = $.let(Record.bind(rosterCover, [rosterCoverPatch]));\n // Every person and activity is the calculation scope; the narrowing only chooses which show (#1342).\n const people = $.let(Data.bind(rosterStaff));\n const acts = $.let(Data.bind(rosterActivities));\n const staffIndex = $.let(Data.bindPaged(rosterStaff, { indexes: [rosterStaffByTeamName, rosterStaffByPosition, rosterStaffByTrainer, rosterStaffByAgency, rosterStaffBySkill, rosterStaffByName] }));\n const actIndex = $.let(Data.bindPaged(rosterActivities, { indexes: [rosterActivitiesByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.rosterPublished\", {\n duty: { source: byTime, range: \"start\" },\n people: { source: staffIndex, icon: { prefix: \"fas\", name: \"user\" }, search: [\"name\", \"skills\"],\n fields: { team: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\"] }, position: { label: \"Position\", hints: [\"lead\", \"deputy\", \"acting\", \"crew\"] },\n trainer: { label: \"Trainer\" }, agency: { label: \"Agency\" }, skills: { label: \"Skill\" }, name: { label: \"Name\" } } },\n activities: { source: actIndex, icon: { prefix: \"fas\", name: \"list-check\" }, fields: { group: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\", null] } } },\n }, { presets: [{ id: \"leads\", name: \"Leads\", where: { people: { position: [\"lead\", \"deputy\", \"acting\"] } } }] }));\n const firms = $.let(Data.bind(rosterAgencies));\n const times = $.let(Data.bind(rosterShiftTimes));\n const teams = $.let(Data.bind(rosterTeams));\n const roles = $.let(Data.bind(rosterPositions));\n const ideas = $.let(Data.bind(rosterSuggestions));\n return <Box width=\"100%\" height=\"760px\"><Roster id=\"rosterPublished\" week={{ start: \"sunday\" }} slice={{ slice }} inspector\n view={{ layout: \"shifts\", period: \"day\", date: new Date(\"2028-03-02T00:00:00Z\") }}\n events={{ duty: Schedule.events(rota, {\n name: \"Duty\", icon: \"user-clock\", entries,\n start: \"start\", end: \"end\", resource: { field: \"who\", of: { staff: \"people\", agency: \"agencies\" } },\n shift: \"shift\", group: \"team\", position: \"role\", activity: \"task\",\n hours: d => d.start.durationMinutes(d.end).subtract(d.unpaidBreak).divide(60.0),\n }) }}\n resources={{\n people: Schedule.resources(people.read(), { name: \"People\", icon: \"user\", label: p => p.name, group: p => p.team,\n position: p => p.position, skills: p => p.skills, contract: p => p.contract, agency: p => p.agency,\n trainer: p => p.trainer, trainee: p => p.trainee, usual: p => p.usual }),\n agencies: Schedule.resources(firms.read(), { name: \"Agencies\", icon: \"building\", label: a => a.name }),\n }}\n periods={Schedule.periods(weeks, { every: \"week\", status: { field: \"state\", draft: \"open\", published: \"issued\" }, dismissed: \"dismissed\" })}\n targets={Roster.targets(cover, { day: \"day\", group: \"team\", shift: \"shift\", positions: \"heads\", hours: \"hours\" })}\n shifts={Roster.shifts(times.read(), { label: s => s.label, code: s => s.code, start: s => s.start, hours: s => s.hours, order: s => s.order })}\n groups={Roster.groups(teams.read(), { label: t => t.label, order: t => t.order })}\n positions={Roster.positions(roles.read(), { label: p => p.label, code: p => p.code, lead: p => p.lead, order: p => p.order })}\n activities={Roster.activities(acts.read(), { label: a => a.label, code: a => a.code, group: a => a.group, order: a => a.order })}\n proposals={Roster.proposals(ideas.read(), { person: s => s.person, day: s => s.day, group: s => s.team, shift: s => s.shift, reason: s => s.why })}\n rules={{ rest: 10.0 }}\n library={[Roster.library.people(), Roster.library.activities(), Roster.library.tab(roles.read(), {\n name: \"Positions\", icon: \"id-badge\", label: p => p.label, meta: p => some(p.code),\n drop: (_p, key) => Schedule.patch(WarehouseDuty, { role: key }),\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
43805
43866
|
},
|
|
43806
43867
|
{
|
|
43807
43868
|
"id": "e3-ui:roster.examples.tsx:rosterCustomInspector",
|
|
@@ -43835,7 +43896,273 @@
|
|
|
43835
43896
|
"languages": [
|
|
43836
43897
|
"tsx"
|
|
43837
43898
|
],
|
|
43838
|
-
"source": "// The duty kind's own inspector and the targets' own form replace the built-in fields while preserving the shared headers, coverage, actions and undo history.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex(\"roster_duties_by_time\", rosterDuties, {
|
|
43899
|
+
"source": "// The duty kind's own inspector and the targets' own form replace the built-in fields while preserving the shared headers, coverage, actions and undo history.\n// inputs: []\n/** The duties, both weeks', each the warehouse's own row. */\nexport const rosterDuties = e3.record(\"roster_duties\", DictType(StringType, WarehouseDuty), warehouseDuties);\n\nexport const rosterDutiesPatch = e3.mutation.patch(rosterDuties);\n\n/** The duties by their times: what the Roster's slice reads a week of them through, its navigation writing the range. */\nexport const rosterDutiesByTime = e3.recordIndex.interval(\"roster_duties_by_time\", rosterDuties, { from: \"start\", to: \"end\" });\n\n/** The weeks of the rota: the one before issued, this one open. */\nexport const rosterWeeks = e3.record(\"roster_rota_weeks\", DictType(DateTimeType, WarehouseWeek), warehouseWeeks);\n\nexport const rosterWeeksPatch = e3.mutation.patch(rosterWeeks);\n\n/** The cover targets, a row per slot: the week before's, this week's and the forecast's for the week after. */\nexport const rosterCover = e3.record(\"roster_cover\", DictType(StringType, WarehouseCover), warehouseCover);\n\nexport const rosterCoverPatch = e3.mutation.patch(rosterCover);\n\n/** The suggestions: someone for a slot, and why. */\nexport const rosterSuggestions = e3.record(\"roster_suggestions\", DictType(StringType, WarehouseSuggestion), warehouseSuggestions);\n\nexport const rosterStaff = e3.record(\"roster_staff\", DictType(StringType, WarehousePerson), warehousePeople);\n\nexport const rosterAgencies = e3.record(\"roster_agencies\", DictType(StringType, WarehouseAgency), warehouseAgencies);\n\nexport const rosterShiftTimes = e3.record(\"roster_shift_times\", DictType(StringType, WarehouseShift), warehouseShifts);\n\nexport const rosterTeams = e3.record(\"roster_teams\", DictType(StringType, WarehouseTeam), warehouseTeams);\n\nexport const rosterPositions = e3.record(\"roster_positions\", DictType(StringType, WarehousePosition), warehousePositions);\n\n/** Every activity by its key: each group's skills, and the duty of none (#1342). */\nexport const rosterActivities = e3.record(\"roster_activities\", DictType(StringType, WarehouseActivity), warehouseActivities);\n\nexport const rosterStaffByTeamName = e3.recordIndex(\"roster_staff_by_team_name\", rosterStaff, { on: [\"team\", \"name\"] });\n\nexport const rosterStaffByPosition = e3.recordIndex(\"roster_staff_by_position\", rosterStaff, { on: [\"position\"] });\n\nexport const rosterStaffByTrainer = e3.recordIndex(\"roster_staff_by_trainer\", rosterStaff, { on: [\"trainer\"] });\n\nexport const rosterStaffByAgency = e3.recordIndex(\"roster_staff_by_agency\", rosterStaff, { on: [\"agency\"] });\n\nexport const rosterStaffBySkill = e3.recordIndex(\"roster_staff_by_skill\", rosterStaff, { on: [\"skills\"] });\n\nexport const rosterStaffByName = e3.recordIndex(\"roster_staff_by_name\", rosterStaff, { on: [\"name\"] });\n\nexport const rosterActivitiesByGroup = e3.recordIndex(\"roster_activities_by_group\", rosterActivities, { on: [\"group\"] });\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const rota = $.let(Record.bind(rosterDuties, [rosterDutiesPatch]));\n const entries = $.let(Data.bindPaged(rosterDuties));\n const byTime = $.let(Data.bindPaged(rosterDuties, { indexes: [rosterDutiesByTime] }));\n const weeks = $.let(Record.bind(rosterWeeks, [rosterWeeksPatch]));\n const cover = $.let(Record.bind(rosterCover, [rosterCoverPatch]));\n // Every person and activity is the calculation scope; the narrowing only chooses which show (#1342).\n const people = $.let(Data.bind(rosterStaff));\n const acts = $.let(Data.bind(rosterActivities));\n const staffIndex = $.let(Data.bindPaged(rosterStaff, { indexes: [rosterStaffByTeamName, rosterStaffByPosition, rosterStaffByTrainer, rosterStaffByAgency, rosterStaffBySkill, rosterStaffByName] }));\n const actIndex = $.let(Data.bindPaged(rosterActivities, { indexes: [rosterActivitiesByGroup] }));\n const slice = $.let(Slice.bindRecords(\"ex.rosterCustomInspector\", {\n duty: { source: byTime, range: \"start\" },\n people: { source: staffIndex, icon: { prefix: \"fas\", name: \"user\" }, search: [\"name\", \"skills\"],\n fields: { team: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\"] }, position: { label: \"Position\", hints: [\"lead\", \"deputy\", \"acting\", \"crew\"] },\n trainer: { label: \"Trainer\" }, agency: { label: \"Agency\" }, skills: { label: \"Skill\" }, name: { label: \"Name\" } } },\n activities: { source: actIndex, icon: { prefix: \"fas\", name: \"list-check\" }, fields: { group: { label: \"Group\", hints: [\"inbound\", \"picking\", \"dispatch\", null] } } },\n }, { presets: [{ id: \"leads\", name: \"Leads\", where: { people: { position: [\"lead\", \"deputy\", \"acting\"] } } }] }));\n const firms = $.let(Data.bind(rosterAgencies));\n const times = $.let(Data.bind(rosterShiftTimes));\n const teams = $.let(Data.bind(rosterTeams));\n const roles = $.let(Data.bind(rosterPositions));\n const ideas = $.let(Data.bind(rosterSuggestions));\n // The duty's own form: its unpaid break and its note, each change the duty written back whole.\n const dutyForm = $.const(East.function([WarehouseDuty, FunctionType([WarehouseDuty], NullType)], UIComponentType, (_$2, row, update) =>\n <VStack>\n <Field.FloatInput label=\"Custom unpaid break\" value={row.unpaidBreak} min={0} max={120} step={15} onChange={(_$3, value) => update({\n who: row.who, start: row.start, end: row.end, shift: row.shift, team: row.team, role: row.role, task: row.task,\n unpaidBreak: value, agreed: row.agreed, costCentre: row.costCentre, note: row.note })} />\n <Field.StringInput label=\"Custom note\" value={row.note} onChange={(_$3, value) => update({\n who: row.who, start: row.start, end: row.end, shift: row.shift, team: row.team, role: row.role, task: row.task,\n unpaidBreak: row.unpaidBreak, agreed: row.agreed, costCentre: row.costCentre, note: value })} />\n </VStack>));\n // The target's own form: the heads it needs.\n const coverForm = $.const(East.function([WarehouseCover, FunctionType([WarehouseCover], NullType)], UIComponentType, (_$2, row, update) =>\n <Field.IntegerInput label=\"Custom heads\" value={row.heads} min={0n} step={1n} onChange={(_$3, value) => update({\n day: row.day, team: row.team, shift: row.shift, heads: value, hours: row.hours, basis: \"planner\" })} />));\n return <Box width=\"100%\" height=\"760px\"><Roster id=\"rosterCustomInspector\" week={{ start: \"sunday\" }} slice={{ slice }} inspector\n view={{ layout: \"shifts\", period: \"day\", date: new Date(\"2028-03-09T00:00:00Z\") }}\n events={{ duty: Schedule.events(rota, {\n name: \"Duty\", icon: \"user-clock\", entries,\n start: \"start\", end: \"end\", resource: { field: \"who\", of: { staff: \"people\", agency: \"agencies\" } },\n shift: \"shift\", group: \"team\", position: \"role\", activity: \"task\",\n hours: d => d.start.durationMinutes(d.end).subtract(d.unpaidBreak).divide(60.0),\n inspector: dutyForm,\n }) }}\n resources={{\n people: Schedule.resources(people.read(), { name: \"People\", icon: \"user\", label: p => p.name, group: p => p.team,\n position: p => p.position, skills: p => p.skills, contract: p => p.contract, agency: p => p.agency,\n trainer: p => p.trainer, trainee: p => p.trainee, usual: p => p.usual }),\n agencies: Schedule.resources(firms.read(), { name: \"Agencies\", icon: \"building\", label: a => a.name }),\n }}\n periods={Schedule.periods(weeks, { every: \"week\", status: { field: \"state\", draft: \"open\", published: \"issued\" }, dismissed: \"dismissed\" })}\n targets={Roster.targets(cover, { day: \"day\", group: \"team\", shift: \"shift\", positions: \"heads\", hours: \"hours\", inspector: coverForm })}\n shifts={Roster.shifts(times.read(), { label: s => s.label, code: s => s.code, start: s => s.start, hours: s => s.hours, order: s => s.order })}\n groups={Roster.groups(teams.read(), { label: t => t.label, order: t => t.order })}\n positions={Roster.positions(roles.read(), { label: p => p.label, code: p => p.code, lead: p => p.lead, order: p => p.order })}\n activities={Roster.activities(acts.read(), { label: a => a.label, code: a => a.code, group: a => a.group, order: a => a.order })}\n proposals={Roster.proposals(ideas.read(), { person: s => s.person, day: s => s.day, group: s => s.team, shift: s => s.shift, reason: s => s.why })}\n rules={{ rest: 10.0 }}\n library={[Roster.library.people(), Roster.library.activities(), Roster.library.tab(roles.read(), {\n name: \"Positions\", icon: \"id-badge\", label: p => p.label, meta: p => some(p.code),\n drop: (_p, key) => Schedule.patch(WarehouseDuty, { role: key }),\n })]} />\n </Box>;\n }}</Reactive>)"
|
|
43900
|
+
},
|
|
43901
|
+
{
|
|
43902
|
+
"id": "e3-ui:schematic-records.examples.tsx:schematicRecordIndexes",
|
|
43903
|
+
"skill": "e3-ui",
|
|
43904
|
+
"package": "e3-ui",
|
|
43905
|
+
"file": "east-ui/packages/e3-ui/test/schematic/schematic-records.examples.tsx",
|
|
43906
|
+
"suite": "",
|
|
43907
|
+
"test": "A floor of 20,000 racks in ten halls, read a window at a time: the racks bound with their patch door and read through their source of the slice, whose indexes include an area index at a 1 cm cell, so the canvas reads only the racks its view shows, from the index alone, and draws their density from the index's counts while the view holds more than 5,000; a choice of halls reads through the area index the hall leads, and a choice of models through the one led by nothing, asking which of the window's racks the choice holds; each rack's load read by the runs its window's cells are of the area index — e3 reads their keys from the index, so none is sent — every cell not yet read in one request, so a dot draws its rack's alarm as a card does; a rack moved is a draft that waits in its session wherever the window goes, and Save writes it through the door",
|
|
43908
|
+
"keywords": [
|
|
43909
|
+
"Schematic",
|
|
43910
|
+
"Schematic.items",
|
|
43911
|
+
"Schematic.zones",
|
|
43912
|
+
"Schematic.details",
|
|
43913
|
+
"Record.bind",
|
|
43914
|
+
"Data.bindPaged",
|
|
43915
|
+
"Slice.bindRecords",
|
|
43916
|
+
"e3.recordIndex.area",
|
|
43917
|
+
"area index",
|
|
43918
|
+
"window",
|
|
43919
|
+
"paging",
|
|
43920
|
+
"density",
|
|
43921
|
+
"records",
|
|
43922
|
+
"details",
|
|
43923
|
+
"editing",
|
|
43924
|
+
"drafts",
|
|
43925
|
+
"Save",
|
|
43926
|
+
"20,000"
|
|
43927
|
+
],
|
|
43928
|
+
"imports": [
|
|
43929
|
+
"import { ArrayType, DictType, East, FloatType, IntegerType, NullType, StringType, StructType, VariantType, example, some, variant } from \"@elaraai/east\";",
|
|
43930
|
+
"import { Box, Reactive, Slice, UIComponentType } from \"@elaraai/east-ui\";",
|
|
43931
|
+
"import { Data, Record, Schematic } from \"@elaraai/e3-ui\";",
|
|
43932
|
+
"import e3 from \"@elaraai/e3\";"
|
|
43933
|
+
],
|
|
43934
|
+
"languages": [
|
|
43935
|
+
"tsx"
|
|
43936
|
+
],
|
|
43937
|
+
"source": "// A floor of 20,000 racks in ten halls, read a window at a time: the racks bound with their patch door and read through their source of the slice, whose indexes include an area index at a 1 cm cell, so the canvas reads only the racks its view shows, from the index alone, and draws their density from the index's counts while the view holds more than 5,000; a choice of halls reads through the area index the hall leads, and a choice of models through the one led by nothing, asking which of the window's racks the choice holds; each rack's load read by the runs its window's cells are of the area index — e3 reads their keys from the index, so none is sent — every cell not yet read in one request, so a dot draws its rack's alarm as a card does; a rack moved is a draft that waits in its session wherever the window goes, and Save writes it through the door\n// inputs: []\n/** A rack of the floor: its hall and model, placed by its centre, its box 0.6 m by 1.2 m. */\nexport const StressRack = StructType({ name: StringType, hall: StringType, model: StringType, x: FloatType, y: FloatType, w: FloatType, h: FloatType });\n\n/** How hot a rack runs. */\nexport const StressAlarm = VariantType({ ok: NullType, warm: NullType, hot: NullType });\n\n/** A rack's load: what its card's bar, figure and dot draw. */\nexport const StressLoad = StructType({ kw: FloatType, capacity: FloatType, alarm: StressAlarm });\n\n/** A hall of the floor: its box and its name. */\nexport const StressHall = StructType({ name: StringType, x: FloatType, y: FloatType, w: FloatType, h: FloatType });\n\nconst StressRacks = DictType(StringType, StressRack);\n\nconst StressLoads = DictType(StringType, StressLoad);\n\n/**\n * The floor's racks, a row of 100 in each of the ten halls in turn — five\n * halls to a row of the floor, each holding 50 rows, 0.7 m apart along a row\n * and 2.4 m from row to row — so any count fills every hall's rows from the\n * front: 20,000 racks fill each hall's first 20, and 50,000 the floor. The\n * budget run makes the floor's 50,000 with it, and patches in what the seed's\n * 20,000 lack.\n */\nexport const generateRacks = East.function([IntegerType], StressRacks, ($, count) => {\n const racks = $.let(new Map(), StressRacks);\n const models = $.const([\"42U · 8 kW\", \"48U · 12 kW\", \"24U · 4 kW\"], ArrayType(StringType));\n const i = $.let(0n);\n $.while(i.less(count), ($2) => {\n const hall = $2.const(i.divide(100n).remainder(10n));\n const row = $2.const(i.divide(1000n));\n const key = $2.const(East.str`R${i.add(100000n)}`);\n $2(racks.insert(key, {\n name: key,\n hall: East.str`Hall ${hall.add(1n)}`,\n model: models.get(i.remainder(3n)),\n x: hall.remainder(5n).toFloat().multiply(76.0).add(i.remainder(100n).toFloat().multiply(0.7)).add(3.3),\n y: hall.divide(5n).toFloat().multiply(126.0).add(row.toFloat().multiply(2.4)).add(3.6),\n w: 0.6,\n h: 1.2,\n }));\n $2.assign(i, i.add(1n));\n });\n return racks;\n});\n\n/** The racks' loads, keyed as the racks are: up to 12 kW of a 12 kW rack, warm past 9.6 and hot past 10.8. The budget run makes the floor's with it. */\nexport const generateLoads = East.function([IntegerType], StressLoads, ($, count) => {\n const loads = $.let(new Map(), StressLoads);\n const i = $.let(0n);\n $.while(i.less(count), ($2) => {\n const kw = $2.const(i.multiply(37n).remainder(120n).toFloat().divide(10.0));\n const alarm = $2.let(variant(\"ok\", null), StressAlarm);\n $2.if(kw.greater(9.6), ($3) => { $3.assign(alarm, variant(\"warm\", null)); });\n $2.if(kw.greater(10.8), ($3) => { $3.assign(alarm, variant(\"hot\", null)); });\n $2(loads.insert(East.str`R${i.add(100000n)}`, { kw, capacity: 12.0, alarm }));\n $2.assign(i, i.add(1n));\n });\n return loads;\n});\n\nexport const schematicStressRacks = e3.record(\"schematic_stress_racks\", StressRacks, East.compile(generateRacks, [])(20000n));\n\n/** The racks' patch door: each Save of the floor writes through it, and the budget run fills the floor through it. */\nexport const schematicStressRacksPatch = e3.mutation.patch(schematicStressRacks);\n\n/** The window, whatever the rail chooses: every rack filed once by its box, at a 1 cm cell. */\nexport const schematicStressRacksByArea = e3.recordIndex.area(\"schematic_stress_racks_by_area\", schematicStressRacks, {\n x: \"x\", y: \"y\", width: \"w\", height: \"h\", at: \"centre\", cell: 0.01,\n});\n\n/** Beside it, a choice of halls and the window in one read. */\nexport const schematicStressRacksByHallArea = e3.recordIndex.area(\"schematic_stress_racks_by_hall_area\", schematicStressRacks, {\n on: [\"hall\"], x: \"x\", y: \"y\", width: \"w\", height: \"h\", at: \"centre\", cell: 0.01,\n});\n\n/** A choice no area index leads: a window under it asks which of its racks the choice holds. */\nexport const schematicStressRacksByModel = e3.recordIndex(\"schematic_stress_racks_by_model\", schematicStressRacks, { on: [\"model\"] });\n\nexport const schematicStressRackLoads = e3.record(\"schematic_stress_rack_loads\", StressLoads, East.compile(generateLoads, [])(20000n));\n\nexport const schematicStressHalls = e3.record(\"schematic_stress_halls\", DictType(StringType, StressHall), new Map([\n [\"hall-1\", { name: \"Hall 1\", x: 2.0, y: 2.0, w: 72.0, h: 122.0 }],\n [\"hall-2\", { name: \"Hall 2\", x: 78.0, y: 2.0, w: 72.0, h: 122.0 }],\n [\"hall-3\", { name: \"Hall 3\", x: 154.0, y: 2.0, w: 72.0, h: 122.0 }],\n [\"hall-4\", { name: \"Hall 4\", x: 230.0, y: 2.0, w: 72.0, h: 122.0 }],\n [\"hall-5\", { name: \"Hall 5\", x: 306.0, y: 2.0, w: 72.0, h: 122.0 }],\n [\"hall-6\", { name: \"Hall 6\", x: 2.0, y: 128.0, w: 72.0, h: 122.0 }],\n [\"hall-7\", { name: \"Hall 7\", x: 78.0, y: 128.0, w: 72.0, h: 122.0 }],\n [\"hall-8\", { name: \"Hall 8\", x: 154.0, y: 128.0, w: 72.0, h: 122.0 }],\n [\"hall-9\", { name: \"Hall 9\", x: 230.0, y: 128.0, w: 72.0, h: 122.0 }],\n [\"hall-10\", { name: \"Hall 10\", x: 306.0, y: 128.0, w: 72.0, h: 122.0 }],\n]));\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n // The racks bound with their patch door, read by key through their own entries beside their source of the slice.\n const racks = $.let(Record.bind(schematicStressRacks, [schematicStressRacksPatch]));\n const rackEntries = $.let(Data.bindPaged(schematicStressRacks));\n const loads = $.let(Data.bindPaged(schematicStressRackLoads));\n const halls = $.let(Data.bind(schematicStressHalls));\n // One rail: the racks by hall, through the area index the hall leads, and by model, through the one led by nothing.\n const slice = $.let(Slice.bindRecords(\"ex.schematicRecordIndexes\", {\n racks: {\n source: Data.bindPaged(schematicStressRacks, { indexes: [schematicStressRacksByArea, schematicStressRacksByHallArea, schematicStressRacksByModel] }),\n icon: { prefix: \"fas\", name: \"server\" },\n fields: {\n hall: { label: \"Hall\", hints: [\"Hall 1\", \"Hall 2\", \"Hall 3\", \"Hall 4\", \"Hall 5\", \"Hall 6\", \"Hall 7\", \"Hall 8\", \"Hall 9\", \"Hall 10\"] },\n model: { label: \"Model\", hints: [\"42U · 8 kW\", \"48U · 12 kW\", \"24U · 4 kW\"] },\n },\n },\n }));\n return (\n <Box height=\"640px\">\n <Schematic\n id=\"recordIndexes\"\n extent={{ width: 382, height: 254 }}\n scaleUnit=\"m\"\n slice={{ slice }}\n items={{\n racks: Schematic.items(racks, {\n name: \"Rack\", icon: \"server\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\", entries: rackEntries,\n sublabel: (r) => r.model,\n details: Schematic.details(loads, {\n meter: (l) => ({ value: l.kw, max: l.capacity }),\n metric: (l) => some(East.str`${East.Float.printFixed(l.kw, 1n)} kW`),\n status: { field: \"alarm\", cases: {\n ok: { label: \"OK\", tone: variant(\"success\", null), ring: false },\n warm: { label: \"Warm\", tone: variant(\"warning\", null), ring: false },\n hot: { label: \"Hot\", tone: variant(\"danger\", null), ring: false },\n } },\n }),\n }),\n }}\n zones={{\n halls: Schematic.zones(halls, { name: \"Hall\", icon: \"warehouse\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\" }),\n }}\n />\n </Box>\n );\n }}</Reactive>\n ))"
|
|
43938
|
+
},
|
|
43939
|
+
{
|
|
43940
|
+
"id": "e3-ui:schematic-records.examples.tsx:schematicRecordIndexesMobile",
|
|
43941
|
+
"skill": "e3-ui",
|
|
43942
|
+
"package": "e3-ui",
|
|
43943
|
+
"file": "east-ui/packages/e3-ui/test/schematic/schematic-records.examples.tsx",
|
|
43944
|
+
"suite": "",
|
|
43945
|
+
"test": "The same floor of 20,000 racks in a phone-width frame, with the inspector: one finger pans it and two pinch it, and the toolbar's one row holds the rail's icon, the View — the tools, the zoom and Fit over Find's box — and the history; nothing drags, so a rack tapped shows in the inspector, opened from its rail, with its load, its x and y to move it and its Delete, each a step Save writes through the racks' patch door; the canvas reads only the racks its view shows, from the area index alone, and their density while the view holds more than 5,000",
|
|
43946
|
+
"keywords": [
|
|
43947
|
+
"Schematic",
|
|
43948
|
+
"Schematic.items",
|
|
43949
|
+
"Schematic.zones",
|
|
43950
|
+
"Schematic.details",
|
|
43951
|
+
"Record.bind",
|
|
43952
|
+
"Data.bindPaged",
|
|
43953
|
+
"Slice.bindRecords",
|
|
43954
|
+
"e3.recordIndex.area",
|
|
43955
|
+
"area index",
|
|
43956
|
+
"window",
|
|
43957
|
+
"density",
|
|
43958
|
+
"inspector",
|
|
43959
|
+
"mobile",
|
|
43960
|
+
"phone",
|
|
43961
|
+
"touch",
|
|
43962
|
+
"pinch",
|
|
43963
|
+
"20,000"
|
|
43964
|
+
],
|
|
43965
|
+
"imports": [
|
|
43966
|
+
"import { ArrayType, DictType, East, FloatType, IntegerType, NullType, StringType, StructType, VariantType, example, some, variant } from \"@elaraai/east\";",
|
|
43967
|
+
"import { Box, Reactive, Slice, UIComponentType } from \"@elaraai/east-ui\";",
|
|
43968
|
+
"import { Data, Record, Schematic } from \"@elaraai/e3-ui\";",
|
|
43969
|
+
"import e3 from \"@elaraai/e3\";"
|
|
43970
|
+
],
|
|
43971
|
+
"languages": [
|
|
43972
|
+
"tsx"
|
|
43973
|
+
],
|
|
43974
|
+
"source": "// The same floor of 20,000 racks in a phone-width frame, with the inspector: one finger pans it and two pinch it, and the toolbar's one row holds the rail's icon, the View — the tools, the zoom and Fit over Find's box — and the history; nothing drags, so a rack tapped shows in the inspector, opened from its rail, with its load, its x and y to move it and its Delete, each a step Save writes through the racks' patch door; the canvas reads only the racks its view shows, from the area index alone, and their density while the view holds more than 5,000\n// inputs: []\n/** A rack of the floor: its hall and model, placed by its centre, its box 0.6 m by 1.2 m. */\nexport const StressRack = StructType({ name: StringType, hall: StringType, model: StringType, x: FloatType, y: FloatType, w: FloatType, h: FloatType });\n\n/** How hot a rack runs. */\nexport const StressAlarm = VariantType({ ok: NullType, warm: NullType, hot: NullType });\n\n/** A rack's load: what its card's bar, figure and dot draw. */\nexport const StressLoad = StructType({ kw: FloatType, capacity: FloatType, alarm: StressAlarm });\n\n/** A hall of the floor: its box and its name. */\nexport const StressHall = StructType({ name: StringType, x: FloatType, y: FloatType, w: FloatType, h: FloatType });\n\nconst StressRacks = DictType(StringType, StressRack);\n\nconst StressLoads = DictType(StringType, StressLoad);\n\n/**\n * The floor's racks, a row of 100 in each of the ten halls in turn — five\n * halls to a row of the floor, each holding 50 rows, 0.7 m apart along a row\n * and 2.4 m from row to row — so any count fills every hall's rows from the\n * front: 20,000 racks fill each hall's first 20, and 50,000 the floor. The\n * budget run makes the floor's 50,000 with it, and patches in what the seed's\n * 20,000 lack.\n */\nexport const generateRacks = East.function([IntegerType], StressRacks, ($, count) => {\n const racks = $.let(new Map(), StressRacks);\n const models = $.const([\"42U · 8 kW\", \"48U · 12 kW\", \"24U · 4 kW\"], ArrayType(StringType));\n const i = $.let(0n);\n $.while(i.less(count), ($2) => {\n const hall = $2.const(i.divide(100n).remainder(10n));\n const row = $2.const(i.divide(1000n));\n const key = $2.const(East.str`R${i.add(100000n)}`);\n $2(racks.insert(key, {\n name: key,\n hall: East.str`Hall ${hall.add(1n)}`,\n model: models.get(i.remainder(3n)),\n x: hall.remainder(5n).toFloat().multiply(76.0).add(i.remainder(100n).toFloat().multiply(0.7)).add(3.3),\n y: hall.divide(5n).toFloat().multiply(126.0).add(row.toFloat().multiply(2.4)).add(3.6),\n w: 0.6,\n h: 1.2,\n }));\n $2.assign(i, i.add(1n));\n });\n return racks;\n});\n\n/** The racks' loads, keyed as the racks are: up to 12 kW of a 12 kW rack, warm past 9.6 and hot past 10.8. The budget run makes the floor's with it. */\nexport const generateLoads = East.function([IntegerType], StressLoads, ($, count) => {\n const loads = $.let(new Map(), StressLoads);\n const i = $.let(0n);\n $.while(i.less(count), ($2) => {\n const kw = $2.const(i.multiply(37n).remainder(120n).toFloat().divide(10.0));\n const alarm = $2.let(variant(\"ok\", null), StressAlarm);\n $2.if(kw.greater(9.6), ($3) => { $3.assign(alarm, variant(\"warm\", null)); });\n $2.if(kw.greater(10.8), ($3) => { $3.assign(alarm, variant(\"hot\", null)); });\n $2(loads.insert(East.str`R${i.add(100000n)}`, { kw, capacity: 12.0, alarm }));\n $2.assign(i, i.add(1n));\n });\n return loads;\n});\n\nexport const schematicStressRacks = e3.record(\"schematic_stress_racks\", StressRacks, East.compile(generateRacks, [])(20000n));\n\n/** The racks' patch door: each Save of the floor writes through it, and the budget run fills the floor through it. */\nexport const schematicStressRacksPatch = e3.mutation.patch(schematicStressRacks);\n\n/** The window, whatever the rail chooses: every rack filed once by its box, at a 1 cm cell. */\nexport const schematicStressRacksByArea = e3.recordIndex.area(\"schematic_stress_racks_by_area\", schematicStressRacks, {\n x: \"x\", y: \"y\", width: \"w\", height: \"h\", at: \"centre\", cell: 0.01,\n});\n\n/** Beside it, a choice of halls and the window in one read. */\nexport const schematicStressRacksByHallArea = e3.recordIndex.area(\"schematic_stress_racks_by_hall_area\", schematicStressRacks, {\n on: [\"hall\"], x: \"x\", y: \"y\", width: \"w\", height: \"h\", at: \"centre\", cell: 0.01,\n});\n\n/** A choice no area index leads: a window under it asks which of its racks the choice holds. */\nexport const schematicStressRacksByModel = e3.recordIndex(\"schematic_stress_racks_by_model\", schematicStressRacks, { on: [\"model\"] });\n\nexport const schematicStressRackLoads = e3.record(\"schematic_stress_rack_loads\", StressLoads, East.compile(generateLoads, [])(20000n));\n\nexport const schematicStressHalls = e3.record(\"schematic_stress_halls\", DictType(StringType, StressHall), new Map([\n [\"hall-1\", { name: \"Hall 1\", x: 2.0, y: 2.0, w: 72.0, h: 122.0 }],\n [\"hall-2\", { name: \"Hall 2\", x: 78.0, y: 2.0, w: 72.0, h: 122.0 }],\n [\"hall-3\", { name: \"Hall 3\", x: 154.0, y: 2.0, w: 72.0, h: 122.0 }],\n [\"hall-4\", { name: \"Hall 4\", x: 230.0, y: 2.0, w: 72.0, h: 122.0 }],\n [\"hall-5\", { name: \"Hall 5\", x: 306.0, y: 2.0, w: 72.0, h: 122.0 }],\n [\"hall-6\", { name: \"Hall 6\", x: 2.0, y: 128.0, w: 72.0, h: 122.0 }],\n [\"hall-7\", { name: \"Hall 7\", x: 78.0, y: 128.0, w: 72.0, h: 122.0 }],\n [\"hall-8\", { name: \"Hall 8\", x: 154.0, y: 128.0, w: 72.0, h: 122.0 }],\n [\"hall-9\", { name: \"Hall 9\", x: 230.0, y: 128.0, w: 72.0, h: 122.0 }],\n [\"hall-10\", { name: \"Hall 10\", x: 306.0, y: 128.0, w: 72.0, h: 122.0 }],\n]));\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n // The racks bound with their patch door, read by key through their own entries beside their source of the slice.\n const racks = $.let(Record.bind(schematicStressRacks, [schematicStressRacksPatch]));\n const rackEntries = $.let(Data.bindPaged(schematicStressRacks));\n const loads = $.let(Data.bindPaged(schematicStressRackLoads));\n const halls = $.let(Data.bind(schematicStressHalls));\n // One rail: the racks by hall, through the area index the hall leads, and by model, through the one led by nothing.\n const slice = $.let(Slice.bindRecords(\"ex.schematicRecordIndexesMobile\", {\n racks: {\n source: Data.bindPaged(schematicStressRacks, { indexes: [schematicStressRacksByArea, schematicStressRacksByHallArea, schematicStressRacksByModel] }),\n icon: { prefix: \"fas\", name: \"server\" },\n fields: {\n hall: { label: \"Hall\", hints: [\"Hall 1\", \"Hall 2\", \"Hall 3\", \"Hall 4\", \"Hall 5\", \"Hall 6\", \"Hall 7\", \"Hall 8\", \"Hall 9\", \"Hall 10\"] },\n model: { label: \"Model\", hints: [\"42U · 8 kW\", \"48U · 12 kW\", \"24U · 4 kW\"] },\n },\n },\n }));\n return (\n <Box width=\"390px\" maxWidth=\"100%\" height=\"640px\">\n <Schematic\n id=\"recordIndexesMobile\"\n inspector\n extent={{ width: 382, height: 254 }}\n scaleUnit=\"m\"\n slice={{ slice }}\n items={{\n racks: Schematic.items(racks, {\n name: \"Rack\", icon: \"server\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\", entries: rackEntries,\n sublabel: (r) => r.model,\n details: Schematic.details(loads, {\n meter: (l) => ({ value: l.kw, max: l.capacity }),\n metric: (l) => some(East.str`${East.Float.printFixed(l.kw, 1n)} kW`),\n status: { field: \"alarm\", cases: {\n ok: { label: \"OK\", tone: variant(\"success\", null), ring: false },\n warm: { label: \"Warm\", tone: variant(\"warning\", null), ring: false },\n hot: { label: \"Hot\", tone: variant(\"danger\", null), ring: false },\n } },\n }),\n }),\n }}\n zones={{\n halls: Schematic.zones(halls, { name: \"Hall\", icon: \"warehouse\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\" }),\n }}\n />\n </Box>\n );\n }}</Reactive>\n ))"
|
|
43975
|
+
},
|
|
43976
|
+
{
|
|
43977
|
+
"id": "e3-ui:schematic.examples.tsx:schematicFloor",
|
|
43978
|
+
"skill": "e3-ui",
|
|
43979
|
+
"package": "e3-ui",
|
|
43980
|
+
"file": "east-ui/packages/e3-ui/test/schematic/schematic.examples.tsx",
|
|
43981
|
+
"suite": "The smallest: one hall's racks, read only",
|
|
43982
|
+
"test": "One hall's racks, read only, with no panes: a record of racks bound with Data.bind, each rack's fields mapped to the Schematic's roles — its centre, its box, its name, its model and its power as a meter",
|
|
43983
|
+
"keywords": [
|
|
43984
|
+
"Schematic",
|
|
43985
|
+
"Schematic.items",
|
|
43986
|
+
"Data.bind",
|
|
43987
|
+
"records",
|
|
43988
|
+
"racks",
|
|
43989
|
+
"meter",
|
|
43990
|
+
"read only",
|
|
43991
|
+
"canvas",
|
|
43992
|
+
"id"
|
|
43993
|
+
],
|
|
43994
|
+
"imports": [
|
|
43995
|
+
"import {",
|
|
43996
|
+
"import { Box, Button, Reactive, Slice, State, Text, UIComponentType, VStack } from \"@elaraai/east-ui\";",
|
|
43997
|
+
"import { Data, Record, Schematic } from \"@elaraai/e3-ui\";",
|
|
43998
|
+
"import e3 from \"@elaraai/e3\";"
|
|
43999
|
+
],
|
|
44000
|
+
"languages": [
|
|
44001
|
+
"tsx"
|
|
44002
|
+
],
|
|
44003
|
+
"source": "// One hall's racks, read only, with no panes: a record of racks bound with Data.bind, each rack's fields mapped to the Schematic's roles — its centre, its box, its name, its model and its power as a meter\n// inputs: []\nexport const FloorRack = StructType({ name: StringType, x: FloatType, y: FloatType, w: FloatType, h: FloatType, model: StringType, kw: FloatType, capacity: FloatType });\n\nexport const schematicFloorRacks = e3.record(\"schematic_floor_racks\", DictType(StringType, FloorRack), new Map([\n [\"A01\", { name: \"A01\", x: 2.4, y: 2.9, w: 3.2, h: 1.2, model: \"42U · 8 kW\", kw: 5.2, capacity: 8.0 }],\n [\"A02\", { name: \"A02\", x: 6.0, y: 2.9, w: 3.2, h: 1.2, model: \"42U · 8 kW\", kw: 7.4, capacity: 8.0 }],\n [\"A03\", { name: \"A03\", x: 9.6, y: 2.9, w: 3.2, h: 1.2, model: \"42U · 8 kW\", kw: 3.9, capacity: 8.0 }],\n [\"A04\", { name: \"A04\", x: 13.2, y: 2.9, w: 3.2, h: 1.2, model: \"48U · 12 kW\", kw: 9.6, capacity: 12.0 }],\n [\"B01\", { name: \"B01\", x: 2.4, y: 8.6, w: 3.2, h: 1.2, model: \"42U · 8 kW\", kw: 6.1, capacity: 8.0 }],\n [\"B02\", { name: \"B02\", x: 6.0, y: 8.6, w: 3.2, h: 1.2, model: \"42U · 8 kW\", kw: 2.8, capacity: 8.0 }],\n]));\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const racks = $.let(Data.bind(schematicFloorRacks));\n return (\n <Box height=\"440px\">\n <Schematic\n id=\"floor\"\n extent={{ width: 18, height: 12 }}\n scaleUnit=\"m\"\n items={{\n racks: Schematic.items(racks, {\n name: \"Rack\", icon: \"server\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\",\n sublabel: (r) => r.model,\n meter: (r) => ({ value: r.kw, max: r.capacity }),\n }),\n }}\n />\n </Box>\n );\n }}</Reactive>\n ))"
|
|
44004
|
+
},
|
|
44005
|
+
{
|
|
44006
|
+
"id": "e3-ui:schematic.examples.tsx:schematicHandover",
|
|
44007
|
+
"skill": "e3-ui",
|
|
44008
|
+
"package": "e3-ui",
|
|
44009
|
+
"file": "east-ui/packages/e3-ui/test/schematic/schematic.examples.tsx",
|
|
44010
|
+
"suite": "The handover: the same hall edited, with no pane",
|
|
44011
|
+
"test": "The same hall handed over to its install crew, who move its racks into place: the racks bound with their patch door, so Move moves them and Del deletes them, each a step of the history Save writes through it; with no pane — no library and no inspector — its frame is its toolbar, its canvas and its footer",
|
|
44012
|
+
"keywords": [
|
|
44013
|
+
"Schematic",
|
|
44014
|
+
"Schematic.items",
|
|
44015
|
+
"Record.bind",
|
|
44016
|
+
"records",
|
|
44017
|
+
"editing",
|
|
44018
|
+
"Move",
|
|
44019
|
+
"Delete",
|
|
44020
|
+
"Save",
|
|
44021
|
+
"undo",
|
|
44022
|
+
"history",
|
|
44023
|
+
"no pane",
|
|
44024
|
+
"toolbar",
|
|
44025
|
+
"footer",
|
|
44026
|
+
"id"
|
|
44027
|
+
],
|
|
44028
|
+
"imports": [
|
|
44029
|
+
"import {",
|
|
44030
|
+
"import { Box, Button, Reactive, Slice, State, Text, UIComponentType, VStack } from \"@elaraai/east-ui\";",
|
|
44031
|
+
"import { Data, Record, Schematic } from \"@elaraai/e3-ui\";",
|
|
44032
|
+
"import e3 from \"@elaraai/e3\";"
|
|
44033
|
+
],
|
|
44034
|
+
"languages": [
|
|
44035
|
+
"tsx"
|
|
44036
|
+
],
|
|
44037
|
+
"source": "// The same hall handed over to its install crew, who move its racks into place: the racks bound with their patch door, so Move moves them and Del deletes them, each a step of the history Save writes through it; with no pane — no library and no inspector — its frame is its toolbar, its canvas and its footer\n// inputs: []\nexport const FloorRack = StructType({ name: StringType, x: FloatType, y: FloatType, w: FloatType, h: FloatType, model: StringType, kw: FloatType, capacity: FloatType });\n\nexport const schematicFloorRacks = e3.record(\"schematic_floor_racks\", DictType(StringType, FloorRack), new Map([\n [\"A01\", { name: \"A01\", x: 2.4, y: 2.9, w: 3.2, h: 1.2, model: \"42U · 8 kW\", kw: 5.2, capacity: 8.0 }],\n [\"A02\", { name: \"A02\", x: 6.0, y: 2.9, w: 3.2, h: 1.2, model: \"42U · 8 kW\", kw: 7.4, capacity: 8.0 }],\n [\"A03\", { name: \"A03\", x: 9.6, y: 2.9, w: 3.2, h: 1.2, model: \"42U · 8 kW\", kw: 3.9, capacity: 8.0 }],\n [\"A04\", { name: \"A04\", x: 13.2, y: 2.9, w: 3.2, h: 1.2, model: \"48U · 12 kW\", kw: 9.6, capacity: 12.0 }],\n [\"B01\", { name: \"B01\", x: 2.4, y: 8.6, w: 3.2, h: 1.2, model: \"42U · 8 kW\", kw: 6.1, capacity: 8.0 }],\n [\"B02\", { name: \"B02\", x: 6.0, y: 8.6, w: 3.2, h: 1.2, model: \"42U · 8 kW\", kw: 2.8, capacity: 8.0 }],\n]));\n\n/** The floor's racks' patch door: the handover's Save, and another session's moves of a rack, which the floor redraws. */\nexport const schematicFloorRacksPatch = e3.mutation.patch(schematicFloorRacks);\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const racks = $.let(Record.bind(schematicFloorRacks, [schematicFloorRacksPatch]));\n return (\n <Box height=\"440px\">\n <Schematic\n id=\"handover\"\n extent={{ width: 18, height: 12 }}\n scaleUnit=\"m\"\n items={{\n racks: Schematic.items(racks, {\n name: \"Rack\", icon: \"server\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\",\n sublabel: (r) => r.model,\n meter: (r) => ({ value: r.kw, max: r.capacity }),\n }),\n }}\n />\n </Box>\n );\n }}</Reactive>\n ))"
|
|
44038
|
+
},
|
|
44039
|
+
{
|
|
44040
|
+
"id": "e3-ui:schematic.examples.tsx:schematicShapes",
|
|
44041
|
+
"skill": "e3-ui",
|
|
44042
|
+
"package": "e3-ui",
|
|
44043
|
+
"file": "east-ui/packages/e3-ui/test/schematic/schematic.examples.tsx",
|
|
44044
|
+
"suite": "Shapes: footprints and zone geometry about their anchors, tones, layers",
|
|
44045
|
+
"test": "Footprints and zone shapes built by accessors from the rows' own fields and drawn about their anchors — a cooling unit's outline and a tower's radius about each unit's centre, a hall's corners and a duct bank's arcing band about each area's box centre — with theme colour tokens, tones and layers from a systems record, read only",
|
|
44046
|
+
"keywords": [
|
|
44047
|
+
"Schematic",
|
|
44048
|
+
"Schematic.items",
|
|
44049
|
+
"Schematic.zones",
|
|
44050
|
+
"Schematic.layers",
|
|
44051
|
+
"footprint",
|
|
44052
|
+
"geometry",
|
|
44053
|
+
"accessor",
|
|
44054
|
+
"Schematic.circle",
|
|
44055
|
+
"Schematic.polygon",
|
|
44056
|
+
"Schematic.polyline",
|
|
44057
|
+
"Schematic.hatch",
|
|
44058
|
+
"arc",
|
|
44059
|
+
"bulge",
|
|
44060
|
+
"anchor",
|
|
44061
|
+
"tone",
|
|
44062
|
+
"color",
|
|
44063
|
+
"theme token",
|
|
44064
|
+
"layers",
|
|
44065
|
+
"read only"
|
|
44066
|
+
],
|
|
44067
|
+
"imports": [
|
|
44068
|
+
"import {",
|
|
44069
|
+
"import { Box, Button, Reactive, Slice, State, Text, UIComponentType, VStack } from \"@elaraai/east-ui\";",
|
|
44070
|
+
"import { Data, Record, Schematic } from \"@elaraai/e3-ui\";",
|
|
44071
|
+
"import e3 from \"@elaraai/e3\";"
|
|
44072
|
+
],
|
|
44073
|
+
"languages": [
|
|
44074
|
+
"tsx"
|
|
44075
|
+
],
|
|
44076
|
+
"source": "// Footprints and zone shapes built by accessors from the rows' own fields and drawn about their anchors — a cooling unit's outline and a tower's radius about each unit's centre, a hall's corners and a duct bank's arcing band about each area's box centre — with theme colour tokens, tones and layers from a systems record, read only\n// inputs: []\n/** A system the floor's equipment and areas belong to: a layer of the drawing, its colour a theme token. */\nexport const DcSystem = StructType({ label: StringType, colour: StringType, order: IntegerType, fixed: BooleanType, dim: FloatType });\n\nexport const schematicSystems = e3.record(\"schematic_systems\", DictType(StringType, DcSystem), new Map([\n [\"structure\", { label: \"Structure\", colour: \"fg.muted\", order: 1n, fixed: true, dim: 0.6 }],\n [\"power\", { label: \"Power\", colour: \"status.warn\", order: 2n, fixed: false, dim: 1.0 }],\n [\"cooling\", { label: \"Cooling\", colour: \"brand.solid\", order: 3n, fixed: false, dim: 1.0 }],\n [\"network\", { label: \"Network\", colour: \"status.pos\", order: 4n, fixed: false, dim: 1.0 }],\n]));\n\n/** A corner of a unit's outline, about its centre, in metres. */\nexport const ShapePoint = StructType({ x: FloatType, y: FloatType });\n\n/** A unit, measured as the site measures it: a cooling tower by its radius, a cooling unit by its outline's corners, or by neither. */\nexport const ShapeUnit = StructType({\n name: StringType, x: FloatType, y: FloatType, w: FloatType, h: FloatType, kind: StringType,\n radius: OptionType(FloatType), outline: ArrayType(ShapePoint), system: StringType, tint: OptionType(StringType),\n});\n\n/** A corner of an area's edge, about its box's centre; its bulge curves the edge to the next corner (0 is straight). */\nexport const ShapeCorner = StructType({ x: FloatType, y: FloatType, bulge: FloatType });\n\n/** What an area is: a hall, outlined, or a duct bank, a hatched band of its width. */\nexport const ShapeAreaKind = VariantType({ hall: NullType, duct: StructType({ width: FloatType }) });\n\n/** An area: its box, what it is, and its edge's corners. */\nexport const ShapeArea = StructType({\n name: StringType, x: FloatType, y: FloatType, w: FloatType, h: FloatType, kind: ShapeAreaKind, corners: ArrayType(ShapeCorner), system: StringType,\n});\n\nexport const schematicShapeUnits = e3.record(\"schematic_shape_units\", DictType(StringType, ShapeUnit), new Map([\n [\"CRAH-1\", { name: \"CRAH-1\", x: 6.2, y: 4.6, w: 5.0, h: 3.4, kind: \"cooling unit\", system: \"cooling\", tint: some(\"status.info\"), radius: none,\n outline: [{ x: -2.2, y: -1.6 }, { x: 2.4, y: -1.1 }, { x: 2.1, y: 1.6 }, { x: -2.5, y: 1.1 }] }],\n [\"CT-2\", { name: \"CT-2\", x: 16.4, y: 4.8, w: 4.6, h: 4.6, kind: \"cooling tower\", system: \"cooling\", tint: some(\"heat.3\"), radius: some(2.3), outline: [] }],\n [\"PDU-3\", { name: \"PDU-3\", x: 22.0, y: 4.6, w: 1.2, h: 0.8, kind: \"power distribution unit\", system: \"power\", tint: none, radius: none, outline: [] }],\n]));\n\nexport const schematicShapeAreas = e3.record(\"schematic_shape_areas\", DictType(StringType, ShapeArea), new Map([\n [\"hall-3\", { name: \"Hall 3\", x: 1.6, y: 1.4, w: 9.3, h: 6.6, system: \"structure\", kind: variant(\"hall\", null),\n corners: [{ x: -4.65, y: -2.5, bulge: 0.0 }, { x: 4.15, y: -3.3, bulge: 0.0 }, { x: 4.65, y: 2.7, bulge: 0.0 }, { x: -4.25, y: 3.3, bulge: 0.0 }] }],\n [\"duct\", { name: \"Duct bank\", x: 1.0, y: 9.1, w: 23.8, h: 3.4, system: \"power\", kind: variant(\"duct\", { width: 1.4 }),\n corners: [\n { x: -11.7, y: 1.0, bulge: 0.0 }, { x: -6.3, y: -0.8, bulge: 0.45 }, { x: -0.1, y: 0.8, bulge: -0.45 }, { x: 5.7, y: -1.0, bulge: 0.35 }, { x: 11.5, y: 0.2, bulge: 0.0 },\n ] }],\n]));\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const units = $.let(Data.bind(schematicShapeUnits));\n const areas = $.let(Data.bind(schematicShapeAreas));\n const systems = $.let(Data.bind(schematicSystems));\n return (\n <Box height=\"440px\">\n <Schematic\n id=\"shapes\"\n extent={{ width: 26.5, height: 14 }}\n scaleUnit=\"m\"\n items={{\n units: Schematic.items(units, {\n name: \"Equipment\", icon: \"gear\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\", layer: \"system\",\n // A tower by its radius, a unit by its outline's corners, about its centre.\n footprint: (u) => u.radius.match({\n some: (_$2, r) => some(Schematic.circle(r)),\n none: (_$2) => u.outline.size().greater(0n).ifElse((_$3) => some(Schematic.polygon(u.outline)), (_$3) => none),\n }),\n sublabel: (u) => u.kind,\n color: (u) => u.tint,\n fillOpacity: (_u) => some(0.18),\n }),\n }}\n zones={{\n areas: Schematic.zones(areas, {\n name: \"Area\", icon: \"vector-square\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\", layer: \"system\",\n // A hall outlined by its corners; a duct bank hatched, a band of its width along them.\n pattern: (a) => a.kind.match({\n hall: (_$2) => Schematic.outline(),\n duct: (_$2) => Schematic.hatch({ tone: \"warning\" }),\n }),\n geometry: (a) => some(a.kind.match({\n hall: (_$2) => Schematic.polygon(a.corners),\n duct: (_$2, duct) => Schematic.polyline(a.corners, { width: duct.width }),\n })),\n }),\n }}\n layers={Schematic.layers(systems.read(), {\n label: (s) => s.label, color: (s) => some(s.colour), order: (s) => s.order, locked: (s) => s.fixed, opacity: (s) => s.dim,\n })}\n />\n </Box>\n );\n }}</Reactive>\n ))"
|
|
44077
|
+
},
|
|
44078
|
+
{
|
|
44079
|
+
"id": "e3-ui:schematic.examples.tsx:schematicDataCentre",
|
|
44080
|
+
"skill": "e3-ui",
|
|
44081
|
+
"package": "e3-ui",
|
|
44082
|
+
"file": "east-ui/packages/e3-ui/test/schematic/schematic.examples.tsx",
|
|
44083
|
+
"suite": "Shapes: footprints and zone geometry about their anchors, tones, layers",
|
|
44084
|
+
"test": "A data centre floor over the app's records, each holding its own fields: racks read through their source of the slice, editable with their patch door, their loads as details on each card, editable in the inspector; network frames, round cooling units with their own Details and power distribution units; halls, cages and aisles, outlined or hatched by what they are; fibre runs between racks and frames, solid or dashed by their cable, a run between two frames refused in the veto's words; power feeds, and fibre trunks a Shift-connect gathers runs into, none onto the core; every gesture a step of one history, Save writing each record through its patch door; the inspector; the whole selection kept by the host; and in the library every kind's templates, dropped on the canvas or on a fibre run, the racks with no position, a drop placing one, the zones' index, the layers, a systems record, and the floor's owners, an owner's card dropped on a rack making it the rack's owner",
|
|
44085
|
+
"keywords": [
|
|
44086
|
+
"Schematic",
|
|
44087
|
+
"Schematic.items",
|
|
44088
|
+
"Schematic.zones",
|
|
44089
|
+
"Schematic.links",
|
|
44090
|
+
"Schematic.nets",
|
|
44091
|
+
"Schematic.details",
|
|
44092
|
+
"Schematic.templates",
|
|
44093
|
+
"Schematic.layers",
|
|
44094
|
+
"Schematic.library",
|
|
44095
|
+
"Schematic.library.tab",
|
|
44096
|
+
"Schematic.patch",
|
|
44097
|
+
"Record.bind",
|
|
44098
|
+
"Data.bindPaged",
|
|
44099
|
+
"Slice.bindRecords",
|
|
44100
|
+
"records",
|
|
44101
|
+
"details",
|
|
44102
|
+
"templates",
|
|
44103
|
+
"backlog",
|
|
44104
|
+
"layers",
|
|
44105
|
+
"narrowed",
|
|
44106
|
+
"inspector",
|
|
44107
|
+
"accessor",
|
|
44108
|
+
"id",
|
|
44109
|
+
"onSelect",
|
|
44110
|
+
"canConnect",
|
|
44111
|
+
"Schematic.Types.Ref",
|
|
44112
|
+
"Schematic.Types.Candidate",
|
|
44113
|
+
"veto",
|
|
44114
|
+
"editing",
|
|
44115
|
+
"Save",
|
|
44116
|
+
"undo",
|
|
44117
|
+
"Move",
|
|
44118
|
+
"Connect",
|
|
44119
|
+
"nets",
|
|
44120
|
+
"drag and drop",
|
|
44121
|
+
"drop"
|
|
44122
|
+
],
|
|
44123
|
+
"imports": [
|
|
44124
|
+
"import {",
|
|
44125
|
+
"import { Box, Button, Reactive, Slice, State, Text, UIComponentType, VStack } from \"@elaraai/east-ui\";",
|
|
44126
|
+
"import { Data, Record, Schematic } from \"@elaraai/e3-ui\";",
|
|
44127
|
+
"import e3 from \"@elaraai/e3\";"
|
|
44128
|
+
],
|
|
44129
|
+
"languages": [
|
|
44130
|
+
"tsx"
|
|
44131
|
+
],
|
|
44132
|
+
"source": "// A data centre floor over the app's records, each holding its own fields: racks read through their source of the slice, editable with their patch door, their loads as details on each card, editable in the inspector; network frames, round cooling units with their own Details and power distribution units; halls, cages and aisles, outlined or hatched by what they are; fibre runs between racks and frames, solid or dashed by their cable, a run between two frames refused in the veto's words; power feeds, and fibre trunks a Shift-connect gathers runs into, none onto the core; every gesture a step of one history, Save writing each record through its patch door; the inspector; the whole selection kept by the host; and in the library every kind's templates, dropped on the canvas or on a fibre run, the racks with no position, a drop placing one, the zones' index, the layers, a systems record, and the floor's owners, an owner's card dropped on a rack making it the rack's owner\n// inputs: []\n/** A system the floor's equipment and areas belong to: a layer of the drawing, its colour a theme token. */\nexport const DcSystem = StructType({ label: StringType, colour: StringType, order: IntegerType, fixed: BooleanType, dim: FloatType });\n\nexport const schematicSystems = e3.record(\"schematic_systems\", DictType(StringType, DcSystem), new Map([\n [\"structure\", { label: \"Structure\", colour: \"fg.muted\", order: 1n, fixed: true, dim: 0.6 }],\n [\"power\", { label: \"Power\", colour: \"status.warn\", order: 2n, fixed: false, dim: 1.0 }],\n [\"cooling\", { label: \"Cooling\", colour: \"brand.solid\", order: 3n, fixed: false, dim: 1.0 }],\n [\"network\", { label: \"Network\", colour: \"status.pos\", order: 4n, fixed: false, dim: 1.0 }],\n]));\n\n/** A rack: placed by its centre, or unplaced until a drop places it. */\nexport const DcRack = StructType({\n name: StringType, x: OptionType(FloatType), y: OptionType(FloatType), w: FloatType, h: FloatType,\n model: StringType, hall: StringType, system: StringType, owner: StringType,\n});\n\n/** A rack's load: what its card's bar, figure and dot draw. */\nexport const DcLoad = StructType({ kw: FloatType, capacity: FloatType, alarm: VariantType({ ok: NullType, warm: NullType, hot: NullType }) });\n\n/** A network frame or a power distribution unit. */\nexport const DcUnit = StructType({ name: StringType, x: FloatType, y: FloatType, w: FloatType, h: FloatType, kind: StringType, system: StringType });\n\n/** A cooling unit: round, drawn by its radius. */\nexport const DcCooling = StructType({\n name: StringType, x: FloatType, y: FloatType, w: FloatType, h: FloatType, radius: FloatType, setpoint: FloatType, system: StringType,\n});\n\n/** What an area is: a room or a cage, outlined, or a cold or a hot aisle, hatched. */\nexport const DcAreaKind = VariantType({ room: NullType, cold: NullType, hot: NullType });\n\n/** A hall, a cage or an aisle. */\nexport const DcArea = StructType({ name: StringType, x: FloatType, y: FloatType, w: FloatType, h: FloatType, kind: DcAreaKind, system: StringType });\n\n/** A fibre run's end: a rack, or a network frame. */\nexport const DcEnd = VariantType({ rack: StringType, frame: StringType });\n\n/** A bend of a fibre run's path, in metres. */\nexport const DcPoint = StructType({ x: FloatType, y: FloatType });\n\n/** What a run carries: fibre, drawn solid, or a logical link — management, a cross-connect — drawn dashed. */\nexport const DcCable = VariantType({ fibre: NullType, logical: NullType });\n\n/** A fibre run between two ends. */\nexport const DcFibre = StructType({\n from: DcEnd, to: DcEnd, path: ArrayType(DcPoint), label: OptionType(StringType), speed: OptionType(StringType), cable: DcCable, system: StringType,\n});\n\n/** A power feed: power distribution units feeding racks. */\nexport const DcFeed = StructType({ supply: ArrayType(StringType), racks: ArrayType(StringType), label: StringType, load: StringType, system: StringType });\n\n/** A fibre trunk: one cable gathering several racks' runs into a frame. */\nexport const DcTrunk = StructType({ racks: ArrayType(StringType), frames: ArrayType(StringType), label: OptionType(StringType), system: StringType });\n\n/** What a rack template gives a new rack: every field of a rack but its centre, which the drop places. */\nexport const DcRackValues = StructType({ name: StringType, w: FloatType, h: FloatType, model: StringType, hall: StringType, system: StringType, owner: StringType });\n\n/** A rack template: its card's name and group, and the rack it creates. */\nexport const DcRackTemplate = StructType({ name: StringType, group: StringType, values: DcRackValues });\n\n/** What a fibre template gives a run: every field of a run but its ends. */\nexport const DcFibreValues = StructType({\n path: ArrayType(DcPoint), label: OptionType(StringType), speed: OptionType(StringType), cable: DcCable, system: StringType,\n});\n\n/** A fibre template: its card's name and system, and what it gives a run. */\nexport const DcFibreTemplate = StructType({ name: StringType, system: StringType, values: DcFibreValues });\n\n/** A tenant of the floor: the owner a rack is let to, and who looks after its racks. */\nexport const DcOwner = StructType({ name: StringType, contact: StringType });\n\nexport const schematicRacks = e3.record(\"schematic_racks\", DictType(StringType, DcRack), new Map([\n [\"A01\", { name: \"A01\", x: some(2.4), y: some(2.9), w: 3.2, h: 1.2, model: \"42U · 8 kW\", hall: \"Cage A\", system: \"power\", owner: \"Harbour Labs\" }],\n [\"A02\", { name: \"A02\", x: some(6.0), y: some(2.9), w: 3.2, h: 1.2, model: \"42U · 8 kW\", hall: \"Cage A\", system: \"power\", owner: \"Harbour Labs\" }],\n [\"A03\", { name: \"A03\", x: some(9.6), y: some(2.9), w: 3.2, h: 1.2, model: \"42U · 8 kW\", hall: \"Cage A\", system: \"power\", owner: \"Northwind Media\" }],\n [\"A04\", { name: \"A04\", x: some(13.2), y: some(2.9), w: 3.2, h: 1.2, model: \"48U · 12 kW\", hall: \"Cage A\", system: \"power\", owner: \"Northwind Media\" }],\n [\"A05\", { name: \"A05\", x: some(16.8), y: some(2.9), w: 3.2, h: 1.2, model: \"48U · 12 kW\", hall: \"Cage A\", system: \"power\", owner: \"Elmway College\" }],\n [\"A06\", { name: \"A06\", x: none, y: none, w: 3.2, h: 1.2, model: \"48U · 12 kW\", hall: \"Cage A\", system: \"power\", owner: \"Elmway College\" }],\n [\"A07\", { name: \"A07\", x: none, y: none, w: 3.2, h: 1.2, model: \"42U · 8 kW\", hall: \"Cage A\", system: \"power\", owner: \"\" }],\n]));\n\nexport const schematicRacksPatch = e3.mutation.patch(schematicRacks);\n\nexport const schematicRacksByName = e3.recordIndex(\"schematic_racks_by_name\", schematicRacks, { on: [\"name\"] });\n\nexport const schematicRacksByHall = e3.recordIndex(\"schematic_racks_by_hall\", schematicRacks, { on: [\"hall\"] });\n\nexport const schematicRacksByOwner = e3.recordIndex(\"schematic_racks_by_owner\", schematicRacks, { on: [\"owner\"] });\n\nexport const schematicRackLoads = e3.record(\"schematic_rack_loads\", DictType(StringType, DcLoad), new Map([\n [\"A01\", { kw: 5.2, capacity: 8.0, alarm: variant(\"ok\", null) }],\n [\"A02\", { kw: 7.4, capacity: 8.0, alarm: variant(\"warm\", null) }],\n [\"A03\", { kw: 3.9, capacity: 8.0, alarm: variant(\"ok\", null) }],\n [\"A04\", { kw: 11.6, capacity: 12.0, alarm: variant(\"hot\", null) }],\n [\"A05\", { kw: 0.0, capacity: 12.0, alarm: variant(\"ok\", null) }],\n]));\n\n/** The racks' loads' patch door: a rack's capacity edited in the inspector's details. */\nexport const schematicRackLoadsPatch = e3.mutation.patch(schematicRackLoads);\n\nexport const schematicFrames = e3.record(\"schematic_frames\", DictType(StringType, DcUnit), new Map([\n [\"MMR-1\", { name: \"MMR-1\", x: 24.2, y: 2.9, w: 4.0, h: 1.2, kind: \"demarc · 48 ports\", system: \"network\" }],\n [\"ODF-1\", { name: \"ODF-1\", x: 8.4, y: 9.6, w: 12.8, h: 1.0, kind: \"fibre frame · 288 F\", system: \"network\" }],\n [\"CORE-1\", { name: \"CORE-1\", x: 23.2, y: 9.7, w: 4.0, h: 1.2, kind: \"core · 64 ports\", system: \"network\" }],\n]));\n\nexport const schematicPdus = e3.record(\"schematic_pdus\", DictType(StringType, DcUnit), new Map([\n [\"PDU-A\", { name: \"PDU-A\", x: 1.6, y: 4.2, w: 1.2, h: 0.6, kind: \"PDU · 32 A\", system: \"power\" }],\n [\"PDU-B\", { name: \"PDU-B\", x: 18.6, y: 4.2, w: 1.2, h: 0.6, kind: \"PDU · 32 A\", system: \"power\" }],\n]));\n\nexport const schematicCooling = e3.record(\"schematic_cooling\", DictType(StringType, DcCooling), new Map([\n [\"CRAH-1\", { name: \"CRAH-1\", x: 3.0, y: 11.9, w: 1.4, h: 1.4, radius: 0.7, setpoint: 22.0, system: \"cooling\" }],\n [\"CRAH-2\", { name: \"CRAH-2\", x: 9.0, y: 11.9, w: 1.4, h: 1.4, radius: 0.7, setpoint: 22.0, system: \"cooling\" }],\n [\"CRAH-3\", { name: \"CRAH-3\", x: 15.0, y: 11.9, w: 1.4, h: 1.4, radius: 0.7, setpoint: 23.0, system: \"cooling\" }],\n]));\n\nexport const schematicCoolingPatch = e3.mutation.patch(schematicCooling);\n\nexport const schematicAreas = e3.record(\"schematic_areas\", DictType(StringType, DcArea), new Map([\n [\"cage-a\", { name: \"Cage A\", x: 0.6, y: 0.6, w: 19.6, h: 4.6, kind: variant(\"room\", null), system: \"structure\" }],\n [\"mmr\", { name: \"Meet-me room\", x: 21.0, y: 0.6, w: 6.4, h: 4.6, kind: variant(\"room\", null), system: \"structure\" }],\n [\"cold-a\", { name: \"Cold aisle A · 1.2 m\", x: 0.6, y: 6.0, w: 26.8, h: 1.2, kind: variant(\"cold\", null), system: \"cooling\" }],\n [\"net-room\", { name: \"Network room\", x: 19.0, y: 7.8, w: 8.4, h: 3.8, kind: variant(\"room\", null), system: \"structure\" }],\n [\"hot-a\", { name: \"Hot aisle A · 1.0 m\", x: 0.6, y: 12.9, w: 18.0, h: 0.9, kind: variant(\"hot\", null), system: \"cooling\" }],\n]));\n\n/** The floor's fibre runs, as its record and the frozen floor's hold them. */\nconst dcFibreRuns = new Map([\n [\"u1\", { from: variant(\"rack\", \"A01\"), to: variant(\"frame\", \"ODF-1\"), path: [], label: some(\"uplink\"), speed: some(\"100G\"), cable: variant(\"fibre\", null), system: \"network\" }],\n [\"u2\", { from: variant(\"rack\", \"A02\"), to: variant(\"frame\", \"ODF-1\"), path: [], label: some(\"uplink\"), speed: some(\"100G\"), cable: variant(\"fibre\", null), system: \"network\" }],\n [\"u2m\", { from: variant(\"rack\", \"A02\"), to: variant(\"frame\", \"ODF-1\"), path: [], label: some(\"OOB mgmt\"), speed: none, cable: variant(\"logical\", null), system: \"network\" }],\n [\"u3\", { from: variant(\"rack\", \"A03\"), to: variant(\"frame\", \"ODF-1\"), path: [], label: none, speed: none, cable: variant(\"fibre\", null), system: \"network\" }],\n [\"u4\", { from: variant(\"rack\", \"A04\"), to: variant(\"frame\", \"ODF-1\"), path: [], label: none, speed: some(\"2 × 100G\"), cable: variant(\"fibre\", null), system: \"network\" }],\n [\"x1\", { from: variant(\"frame\", \"MMR-1\"), to: variant(\"frame\", \"CORE-1\"), path: [{ x: 26.0, y: 6.6 }], label: none, speed: some(\"2 × 10G\"), cable: variant(\"logical\", null), system: \"network\" }],\n]);\n\nexport const schematicFibre = e3.record(\"schematic_fibre\", DictType(StringType, DcFibre), dcFibreRuns);\n\nexport const schematicFibrePatch = e3.mutation.patch(schematicFibre);\n\nexport const schematicFeeds = e3.record(\"schematic_feeds\", DictType(StringType, DcFeed), new Map([\n [\"feed-a\", { supply: [\"PDU-A\"], racks: [\"A01\", \"A02\", \"A03\"], label: \"PDU-A · A side\", load: \"16.5 kW\", system: \"power\" }],\n [\"feed-b\", { supply: [\"PDU-B\"], racks: [\"A04\", \"A05\"], label: \"PDU-B · A side\", load: \"11.6 kW\", system: \"power\" }],\n]));\n\nexport const schematicFeedsPatch = e3.mutation.patch(schematicFeeds);\n\n/** The floor's fibre trunks: none yet — a Shift-connect from racks to a frame gathers their runs into one. */\nexport const schematicTrunks = e3.record(\"schematic_trunks\", DictType(StringType, DcTrunk), new Map());\n\nexport const schematicTrunksPatch = e3.mutation.patch(schematicTrunks);\n\nexport const schematicRackTemplates = e3.record(\"schematic_rack_templates\", DictType(StringType, DcRackTemplate), new Map([\n [\"r42\", { name: \"42U rack · 8 kW\", group: \"Racks\", values: { name: \"New 42U rack\", w: 3.2, h: 1.2, model: \"42U · 8 kW\", hall: \"Cage A\", system: \"power\", owner: \"\" } }],\n [\"r48\", { name: \"48U rack · 12 kW\", group: \"Racks\", values: { name: \"New 48U rack\", w: 3.2, h: 1.2, model: \"48U · 12 kW\", hall: \"Cage A\", system: \"power\", owner: \"\" } }],\n [\"half\", { name: \"Half rack · 4 kW\", group: \"Half racks\", values: { name: \"New half rack\", w: 1.6, h: 1.2, model: \"24U · 4 kW\", hall: \"Cage A\", system: \"power\", owner: \"\" } }],\n]));\n\nexport const schematicRackTemplatesByGroup = e3.recordIndex(\"schematic_rack_templates_by_group\", schematicRackTemplates, { on: [\"group\"] });\n\nexport const schematicFibreTemplates = e3.record(\"schematic_fibre_templates\", DictType(StringType, DcFibreTemplate), new Map([\n [\"uplink\", { name: \"Uplink · 100G\", system: \"network\",\n values: { path: [], label: some(\"uplink\"), speed: some(\"100G\"), cable: variant(\"fibre\", null), system: \"network\" } }],\n [\"oob\", { name: \"Out-of-band management\", system: \"network\",\n values: { path: [], label: some(\"OOB mgmt\"), speed: none, cable: variant(\"logical\", null), system: \"network\" } }],\n]));\n\nexport const schematicFibreTemplatesBySystem = e3.recordIndex(\"schematic_fibre_templates_by_system\", schematicFibreTemplates, { on: [\"system\"] });\n\nexport const schematicOwners = e3.record(\"schematic_owners\", DictType(StringType, DcOwner), new Map([\n [\"elmway\", { name: \"Elmway College\", contact: \"IT services\" }],\n [\"harbour\", { name: \"Harbour Labs\", contact: \"Network desk\" }],\n [\"northwind\", { name: \"Northwind Media\", contact: \"Facilities\" }],\n]));\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const racks = $.let(Record.bind(schematicRacks, [schematicRacksPatch]));\n const rackEntries = $.let(Data.bindPaged(schematicRacks));\n // The racks' loads, edited in the inspector: their patch door, read by key beside the racks' source.\n const loads = $.let(Record.bind(schematicRackLoads, [schematicRackLoadsPatch]));\n const loadEntries = $.let(Data.bindPaged(schematicRackLoads));\n const frames = $.let(Data.bind(schematicFrames));\n const pdus = $.let(Data.bind(schematicPdus));\n const cooling = $.let(Record.bind(schematicCooling, [schematicCoolingPatch]));\n const areas = $.let(Data.bind(schematicAreas));\n const fibre = $.let(Record.bind(schematicFibre, [schematicFibrePatch]));\n const feeds = $.let(Record.bind(schematicFeeds, [schematicFeedsPatch]));\n const trunks = $.let(Record.bind(schematicTrunks, [schematicTrunksPatch]));\n const systems = $.let(Data.bind(schematicSystems));\n const rackTemplates = $.let(Data.bind(schematicRackTemplates));\n const fibreTemplates = $.let(Data.bind(schematicFibreTemplates));\n const owners = $.let(Data.bind(schematicOwners));\n // A cooling unit's own Details: its setpoint, and a button that lowers it — the edited unit goes back through `update`.\n const coolingDetails = $.const(East.function([DcCooling, FunctionType([DcCooling], NullType)], UIComponentType, ($2, unit, update) => {\n const lower = $2.const(East.function([], NullType, ($3) => {\n // East has no struct spread: the unit, rebuilt with its new setpoint.\n const edited = $3.const({\n name: unit.name, x: unit.x, y: unit.y, w: unit.w, h: unit.h, radius: unit.radius, setpoint: unit.setpoint.subtract(1.0), system: unit.system,\n }, DcCooling);\n $3(update(edited));\n }));\n return (\n <VStack gap=\"2\" align=\"stretch\">\n <Text>{East.str`${unit.name} holds ${East.Float.printFixed(unit.setpoint, 1n)} °C`}</Text>\n <Button variant=\"outline\" onClick={lower}>Lower the setpoint by 1 °C</Button>\n </VStack>\n );\n }));\n // The whole selection, after each change — items, zones, links and nets alike, each by its kind's slot and\n // key — kept where the rest of the app reads it.\n const selected = $.let(State.bind([ArrayType(Schematic.Types.Ref)], \"ex.schematicDataCentre.selected\", []));\n const onSelect = $.const(East.function([ArrayType(Schematic.Types.Ref)], NullType, ($2, refs) => {\n $2(selected.write(refs));\n }));\n // Two frames are joined by a cross-connect, never a fibre run, and the racks' trunks land on a fibre frame,\n // never on the core: the veto's words refuse the pair — a drag's draft shows them, and the footer says them of\n // the inspector's ends, sides and Connect to….\n const canConnect = $.const(East.function([Schematic.Types.Candidate], OptionType(StringType), ($2, candidate) => {\n const reason = $2.let(none, OptionType(StringType));\n $2.if(candidate.from.kind.equal(\"network\").and(() => candidate.to.kind.equal(\"network\")), ($3) => {\n $3.assign(reason, some(\"Two frames are joined by a cross-connect\"));\n });\n $2.if(candidate.kind.equal(\"trunks\").and(() => candidate.to.key.equal(\"CORE-1\")), ($3) => {\n $3.assign(reason, some(\"A trunk lands on a fibre frame, never the core\"));\n });\n return reason;\n }));\n // One rail: the racks by name, hall and owner, and each templates list by its group or system.\n const slice = $.let(Slice.bindRecords(\"ex.schematicDataCentre\", {\n racks: { source: Data.bindPaged(schematicRacks, { indexes: [schematicRacksByName, schematicRacksByHall, schematicRacksByOwner] }), icon: { prefix: \"fas\", name: \"server\" },\n search: [\"name\", \"owner\"], fields: {\n name: { label: \"Rack\" }, hall: { label: \"Hall\", hints: [\"Cage A\"] }, owner: { label: \"Owner\", hints: [\"Harbour Labs\", \"Northwind Media\", \"Elmway College\"] },\n } },\n rackTemplates: { source: Data.bindPaged(schematicRackTemplates, { indexes: [schematicRackTemplatesByGroup] }), fields: { group: { label: \"Group\", hints: [\"Racks\", \"Half racks\"] } } },\n fibreTemplates: { source: Data.bindPaged(schematicFibreTemplates, { indexes: [schematicFibreTemplatesBySystem] }), fields: { system: { label: \"System\", hints: [\"network\"] } } },\n }));\n return (\n <Box height=\"640px\">\n <Schematic\n id=\"dataCentre\"\n inspector\n extent={{ width: 28, height: 14 }}\n scaleUnit=\"m\"\n slice={{ slice }}\n narrowed=\"dim\"\n onSelect={onSelect}\n canConnect={canConnect}\n items={{\n racks: Schematic.items(racks, {\n name: \"Rack\", icon: \"server\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\", layer: \"system\", entries: rackEntries,\n sublabel: (r) => r.model,\n details: Schematic.details(loads, {\n entries: loadEntries,\n meter: (l) => ({ value: l.kw, max: l.capacity }),\n metric: (l) => some(East.str`${East.Float.printFixed(l.kw, 1n)} kW`),\n status: { field: \"alarm\", cases: {\n ok: { label: \"OK\", tone: variant(\"success\", null), ring: false },\n warm: { label: \"Warm\", tone: variant(\"warning\", null), ring: false },\n hot: { label: \"Hot\", tone: variant(\"danger\", null), ring: false },\n } },\n }),\n templates: Schematic.templates(rackTemplates.read(), {\n name: (t) => t.name, group: (t) => some(t.group), values: (t) => t.values, source: \"rackTemplates\",\n }),\n }),\n network: Schematic.items(frames, {\n name: \"Network frame\", icon: \"network-wired\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\", layer: \"system\",\n sublabel: (f) => f.kind,\n }),\n cooling: Schematic.items(cooling, {\n name: \"Cooling unit\", icon: \"fan\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\", layer: \"system\",\n footprint: (c) => some(Schematic.circle(c.radius)),\n sublabel: (c) => East.str`set ${East.Float.printFixed(c.setpoint, 1n)} °C`,\n inspector: coolingDetails,\n }),\n pdus: Schematic.items(pdus, {\n name: \"Power distribution unit\", icon: \"plug\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\", layer: \"system\",\n sublabel: (p) => p.kind,\n }),\n }}\n zones={{\n areas: Schematic.zones(areas, {\n name: \"Area\", icon: \"vector-square\", x: \"x\", y: \"y\", width: \"w\", height: \"h\", label: \"name\", layer: \"system\",\n pattern: (a) => a.kind.match({\n room: (_$2) => Schematic.outline(),\n cold: (_$2) => Schematic.hatch({ tone: \"brand\" }),\n hot: (_$2) => Schematic.hatch({ tone: \"warning\" }),\n }),\n }),\n }}\n links={{\n fibre: Schematic.links(fibre, {\n name: \"Fibre run\", icon: \"bolt\", layer: \"system\", via: \"path\",\n from: { field: \"from\", of: { rack: \"racks\", frame: \"network\" } },\n to: { field: \"to\", of: { rack: \"racks\", frame: \"network\" } },\n label: (f) => f.label, metric: (f) => f.speed,\n style: (f) => f.cable.match({ fibre: (_$2) => Schematic.solid(), logical: (_$2) => Schematic.dashed() }),\n templates: Schematic.templates(fibreTemplates.read(), {\n name: (t) => t.name, group: (t) => some(t.system), values: (t) => t.values, source: \"fibreTemplates\",\n }),\n }),\n }}\n nets={{\n feeds: Schematic.nets(feeds, {\n name: \"Power feed\", icon: \"plug-circle-bolt\", layer: \"system\",\n sources: { field: \"supply\", of: \"pdus\" }, destinations: { field: \"racks\", of: \"racks\" },\n label: (f) => some(f.label), metric: (f) => some(f.load),\n }),\n // A Shift-connect from racks to a frame grows its first run into a trunk of them.\n trunks: Schematic.nets(trunks, {\n name: \"Fibre trunk\", icon: \"diagram-project\", layer: \"system\",\n sources: { field: \"racks\", of: \"racks\" }, destinations: { field: \"frames\", of: \"network\" },\n label: (t) => t.label,\n }),\n }}\n layers={Schematic.layers(systems.read(), {\n label: (s) => s.label, color: (s) => some(s.colour), order: (s) => s.order, locked: (s) => s.fixed, opacity: (s) => s.dim,\n })}\n library={[\n Schematic.library.templates(),\n Schematic.library.backlog(),\n Schematic.library.index(),\n Schematic.library.layers(),\n // The floor's owners: a card dropped on a rack — its patch is over a rack's row — makes it the rack's owner.\n Schematic.library.tab(owners.read(), {\n name: \"Owners\", icon: \"building\", label: (o) => o.name, meta: (o) => some(o.contact),\n drop: (o) => Schematic.patch(DcRack, { owner: o.name }),\n }),\n ]}\n />\n </Box>\n );\n }}</Reactive>\n ))"
|
|
44133
|
+
},
|
|
44134
|
+
{
|
|
44135
|
+
"id": "e3-ui:optimise.examples.tsx:sheetOptimise",
|
|
44136
|
+
"skill": "e3-ui",
|
|
44137
|
+
"package": "e3-ui",
|
|
44138
|
+
"file": "east-ui/packages/e3-ui/test/sheet/optimise.examples.tsx",
|
|
44139
|
+
"suite": "",
|
|
44140
|
+
"test": "A sheet of work orders with the Elara eye (#1409): `optimise` is Func.bind of the shop's optimiser on e3 — Sheet.Types.OptimiseRequest(OptimiseWorkOrder) in, the run number and the rows in view by key as the session holds them; Sheet.Types.OptimiseResult(OptimiseWorkOrder) out, the rows complete, each order with no due date given one and set Scheduled. A click on the eye runs it once; the result lands in the session as one edit, cell by cell, which one Undo reverts and Save commits with everything else.",
|
|
44141
|
+
"keywords": [
|
|
44142
|
+
"Sheet",
|
|
44143
|
+
"optimise",
|
|
44144
|
+
"Optimise",
|
|
44145
|
+
"Elara",
|
|
44146
|
+
"OptimiseRequest",
|
|
44147
|
+
"OptimiseResult",
|
|
44148
|
+
"Func.bind",
|
|
44149
|
+
"e3.function",
|
|
44150
|
+
"one edit",
|
|
44151
|
+
"Undo",
|
|
44152
|
+
"Record.bind",
|
|
44153
|
+
"Sheet.column.date",
|
|
44154
|
+
"Sheet.column.integer"
|
|
44155
|
+
],
|
|
44156
|
+
"imports": [
|
|
44157
|
+
"import { DateTimeType, DictType, East, IntegerType, OptionType, SortedMap, StringType, StructType, compareFor, example, none, some, type ValueTypeOf } from \"@elaraai/east\";",
|
|
44158
|
+
"import e3 from \"@elaraai/e3\";",
|
|
44159
|
+
"import { Box, Reactive, UIComponentType } from \"@elaraai/east-ui\";",
|
|
44160
|
+
"import { Func, Record, Sheet } from \"@elaraai/e3-ui\";"
|
|
44161
|
+
],
|
|
44162
|
+
"languages": [
|
|
44163
|
+
"tsx"
|
|
44164
|
+
],
|
|
44165
|
+
"source": "// A sheet of work orders with the Elara eye (#1409): `optimise` is Func.bind of the shop's optimiser on e3 — Sheet.Types.OptimiseRequest(OptimiseWorkOrder) in, the run number and the rows in view by key as the session holds them; Sheet.Types.OptimiseResult(OptimiseWorkOrder) out, the rows complete, each order with no due date given one and set Scheduled. A click on the eye runs it once; the result lands in the session as one edit, cell by cell, which one Undo reverts and Save commits with everything else.\n// inputs: []\n/** A work order of a small shop: its reference, its job, its status, when it is due — none until it is scheduled — and its quantity. */\nexport const OptimiseWorkOrder = StructType({ reference: StringType, job: StringType, status: StringType, due: OptionType(DateTimeType), quantity: IntegerType });\n\n/** Eight work orders: five with a due date, three ready and with none. */\nexport const optimiseWorkOrderRows: SortedMap<string, ValueTypeOf<typeof OptimiseWorkOrder>> = new SortedMap<string, ValueTypeOf<typeof OptimiseWorkOrder>>([\n [\"WO-1001\", { reference: \"WO-1001\", job: \"Cut panels\", status: \"Scheduled\", due: some(new Date(\"2026-10-06T00:00:00Z\")), quantity: 400n }],\n [\"WO-1002\", { reference: \"WO-1002\", job: \"Edge band\", status: \"Scheduled\", due: some(new Date(\"2026-10-07T00:00:00Z\")), quantity: 1200n }],\n [\"WO-1003\", { reference: \"WO-1003\", job: \"Inspect\", status: \"Done\", due: some(new Date(\"2026-10-02T00:00:00Z\")), quantity: 300n }],\n [\"WO-1004\", { reference: \"WO-1004\", job: \"Pack\", status: \"Scheduled\", due: some(new Date(\"2026-10-08T00:00:00Z\")), quantity: 150n }],\n [\"WO-1005\", { reference: \"WO-1005\", job: \"Cut panels\", status: \"Ready\", due: none, quantity: 800n }],\n [\"WO-1006\", { reference: \"WO-1006\", job: \"Edge band\", status: \"Scheduled\", due: some(new Date(\"2026-10-09T00:00:00Z\")), quantity: 600n }],\n [\"WO-1007\", { reference: \"WO-1007\", job: \"Inspect\", status: \"Ready\", due: none, quantity: 200n }],\n [\"WO-1008\", { reference: \"WO-1008\", job: \"Pack\", status: \"Ready\", due: none, quantity: 500n }],\n], compareFor(StringType));\n\n/** The work orders, the shop's own record, which the sheet writes through its patch door. */\nexport const optimiseWorkOrders = e3.record(\"optimise_work_orders\", DictType(StringType, OptimiseWorkOrder), optimiseWorkOrderRows);\n\nexport const optimiseWorkOrdersPatch = e3.mutation.patch(optimiseWorkOrders);\n\n/**\n * The shop's optimiser's body: every order with no due date is given one — the day after the latest due the orders hold,\n * then a day later for each in key order — and set Scheduled; every other order stays as it is.\n */\nexport const sheetOptimiseBody = East.function([Sheet.Types.OptimiseRequest(OptimiseWorkOrder)], Sheet.Types.OptimiseResult(OptimiseWorkOrder), ($, req) => {\n const rows = $.let(new Map(), DictType(StringType, OptimiseWorkOrder));\n // The latest due date the orders hold: the first order scheduled is due the day after it.\n const latest = $.let(new Date(\"2026-10-01T00:00:00Z\"), DateTimeType);\n $.for(req.rows, ($2, order) => {\n $2.match(order.due, { some: ($3, due) => { $3.if(East.less(latest, due), ($4) => { $4.assign(latest, due); }); } });\n });\n $.for(req.rows, ($2, order, key) => {\n $2.match(order.due, {\n some: ($3) => { $3(rows.insert(key, order)); },\n none: ($3) => {\n $3.assign(latest, latest.addDays(1n));\n $3(rows.insert(key, { reference: order.reference, job: order.job, status: \"Scheduled\", due: some(latest), quantity: order.quantity }));\n },\n });\n });\n return { rows };\n});\n\n/** The shop's optimiser, on e3: every order with no due date given one, and set Scheduled. */\nexport const setDueDates = e3.function(\"sheet_set_due_dates\", sheetOptimiseBody);\n\nEast.function([], UIComponentType, _$ => <Reactive>{$ => {\n const orders = $.let(Record.bind(optimiseWorkOrders, [optimiseWorkOrdersPatch]));\n // The app's optimiser: the eye calls it once per run over the rows in view, and its answer is one edit.\n const optimise = $.let(Func.bind(setDueDates));\n return <Box height=\"560px\">\n <Sheet record={orders} name=\"sheetOptimise\" optimise={optimise}\n columns={{\n reference: Sheet.column.text(OptimiseWorkOrder, { header: \"Reference\", width: \"136px\" }),\n job: Sheet.column.text(OptimiseWorkOrder, { header: \"Job\", width: \"180px\" }),\n status: Sheet.column.text(OptimiseWorkOrder, { header: \"Status\", width: \"120px\" }),\n due: Sheet.column.date(OptimiseWorkOrder, { header: \"Due\", width: \"144px\" }),\n quantity: Sheet.column.integer(OptimiseWorkOrder, { header: \"Quantity\", width: \"112px\" }),\n }} />\n </Box>;\n }}</Reactive>)"
|
|
43839
44166
|
},
|
|
43840
44167
|
{
|
|
43841
44168
|
"id": "e3-ui:sheet-link.examples.ts:sheetLinkPrint",
|
|
@@ -43903,7 +44230,7 @@
|
|
|
43903
44230
|
"package": "e3-ui",
|
|
43904
44231
|
"file": "east-ui/packages/e3-ui/test/sheet/sheet-records.examples.tsx",
|
|
43905
44232
|
"suite": "",
|
|
43906
|
-
"test": "50,000 work orders
|
|
44233
|
+
"test": "50,000 work orders in the record's order, the rail's rows source narrowing them through maintained record indexes and the sheet drawing the narrowing over them as the lens, a filtered spreadsheet: only the rows it keeps, one after another, each at its row number. Choose Ready and Scheduled together, then a Due range: e3 places their rows from the indexes alone, and the sheet reads only the segments near the view. Save retains original keys and hidden drafts; saved tabs retain every source's narrowing.",
|
|
43907
44234
|
"keywords": [
|
|
43908
44235
|
"Sheet",
|
|
43909
44236
|
"Slice.bindRecords",
|
|
@@ -43912,6 +44239,7 @@
|
|
|
43912
44239
|
"Record",
|
|
43913
44240
|
"Record.onApply",
|
|
43914
44241
|
"recordIndex",
|
|
44242
|
+
"lens",
|
|
43915
44243
|
"saved views",
|
|
43916
44244
|
"RecordView",
|
|
43917
44245
|
"paging",
|
|
@@ -43927,7 +44255,7 @@
|
|
|
43927
44255
|
"languages": [
|
|
43928
44256
|
"tsx"
|
|
43929
44257
|
],
|
|
43930
|
-
"source": "// 50,000 work orders
|
|
44258
|
+
"source": "// 50,000 work orders in the record's order, the rail's rows source narrowing them through maintained record indexes and the sheet drawing the narrowing over them as the lens, a filtered spreadsheet: only the rows it keeps, one after another, each at its row number. Choose Ready and Scheduled together, then a Due range: e3 places their rows from the indexes alone, and the sheet reads only the segments near the view. Save retains original keys and hidden drafts; saved tabs retain every source's narrowing.\n// inputs: []\n/**\n * A work order: edited as a Sheet row, and a row of the Plan whose operations\n * are a record of their own (`plan/plan-records.examples.tsx`). Its String\n * record key is kept through every index.\n */\nexport const IndexedWork = StructType({ reference: StringType, job: StringType, status: StringType, due: DateTimeType, quantity: IntegerType, urgent: BooleanType, tags: SetType(StringType) });\n\nconst Work = DictType(StringType, IndexedWork);\n\n/** The `i`-th work order's reference, its record key: `JOB-` and its number plus 10,000, so key order is number order. */\nexport const indexedWorkReference = East.function([IntegerType], StringType, (_$, i) => East.str`JOB-${i.add(10000n)}`);\n\n/** The job the `i`-th work order runs: cut panels, edge band, inspect and pack, in turn. */\nexport const indexedWorkJob = East.function([IntegerType], StringType, ($, i) => {\n const jobs = $.const([\"Cut panels\", \"Edge band\", \"Inspect\", \"Pack\"], ArrayType(StringType));\n return jobs.get(i.remainder(4n));\n});\n\n/** When the `i`-th work order is due: a day of the ninety from 1 October 2026, each order thirty-seven days on from the one before. */\nexport const indexedWorkDue = East.function([IntegerType], DateTimeType, ($, i) => {\n const start = $.const(new Date(\"2026-10-01T00:00:00Z\"));\n return start.addMilliseconds(i.multiply(37n).remainder(90n).multiply(86400000n));\n});\n\n/** The same pure generator seeds the example record and runs as an e3 task. */\nconst generateWork = East.function([IntegerType], Work, ($, count) => {\n const rows = $.let(new Map(), Work);\n const statuses = $.const([\"Ready\", \"Scheduled\", \"Done\"], ArrayType(StringType));\n const referenceOf = $.const(indexedWorkReference);\n const jobOf = $.const(indexedWorkJob);\n const dueOf = $.const(indexedWorkDue);\n const i = $.let(0n);\n $.while(i.less(count), ($) => {\n const reference = $.const(referenceOf(i));\n $(rows.insert(reference, {\n reference, job: jobOf(i), status: statuses.get(i.remainder(3n)),\n due: dueOf(i),\n quantity: i.remainder(24n).add(1n).multiply(100n), urgent: i.remainder(5n).equal(0n),\n tags: i.remainder(2n).equal(0n).ifElse(() => new Set([\"Production\"]), () => new Set([\"Dispatch\"])),\n }));\n $.assign(i, i.add(1n));\n });\n return rows;\n});\n\nexport const indexedWorkOrders = e3.record(\"indexed_work_orders\", Work, East.compile(generateWork, [])(50000n));\n\nexport const indexedWorkPatch = e3.mutation.patch(indexedWorkOrders);\n\nexport const indexedWorkByStatusDue = e3.recordIndex(\"by_status_due\", indexedWorkOrders, { on: [\"status\", \"due\"] });\n\nexport const indexedWorkByDue = e3.recordIndex(\"by_due\", indexedWorkOrders, { on: [\"due\"] });\n\nexport const indexedWorkByJob = e3.recordIndex(\"by_job\", indexedWorkOrders, { on: [\"job\"] });\n\nexport const indexedWorkByQuantity = e3.recordIndex(\"by_quantity\", indexedWorkOrders, { on: [\"quantity\"] });\n\nexport const indexedWorkByUrgent = e3.recordIndex(\"by_urgent\", indexedWorkOrders, { on: [\"urgent\"] });\n\nexport const indexedWorkByTag = e3.recordIndex(\"by_tag\", indexedWorkOrders, { on: [\"tags\"] });\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{($) => {\n const record = $.let(Record.bind(indexedWorkOrders, [indexedWorkPatch]));\n // One rail over the work orders: its `rows` source narrows them, and the sheet draws the narrowing over the record's rows.\n const slice = $.let(Slice.bindRecords(\"ex.sheetRecordIndexes\", {\n rows: {\n source: Data.bindPaged(indexedWorkOrders, { indexes: [indexedWorkByStatusDue, indexedWorkByDue, indexedWorkByJob, indexedWorkByQuantity, indexedWorkByUrgent, indexedWorkByTag] }),\n fields: { status: { label: \"Status\", hints: [\"Ready\", \"Scheduled\", \"Done\"] }, due: { label: \"Due\" }, job: { label: \"Job\" }, quantity: { label: \"Quantity\" }, urgent: { label: \"Urgent\" }, tags: { label: \"Tag\", hints: [\"Production\", \"Dispatch\"] } },\n range: \"due\", search: [\"job\"], initial: { status: [\"Ready\"] },\n },\n }, { presets: [{ id: \"ready\", name: \"Ready\", where: { rows: { status: [\"Ready\"] } } }, { id: \"urgent\", name: \"Urgent\", where: { rows: { urgent: [true] } } }] }));\n const views = $.let(State.bind([ArrayType(Sheet.Types.RecordView)], \"ex.sheetRecordIndexes.views\", []));\n return <Box width=\"100%\" height=\"760px\">\n <Sheet name=\"sheetRecordIndexes\" slice={{ slice }} views={views}\n columns={{ reference: Sheet.column.text(IndexedWork, { header: \"Reference\", width: \"136px\" }), job: Sheet.column.text(IndexedWork, { header: \"Job\", width: \"180px\" }), status: Sheet.column.text(IndexedWork, { header: \"Status\", width: \"120px\" }), due: Sheet.column.date(IndexedWork, { header: \"Due\", width: \"144px\" }), quantity: Sheet.column.integer(IndexedWork, { header: \"Quantity\", width: \"112px\" }) }}\n onApply={Record.onApply(record, { keyed: true })} edits={{ insertRows: false }} />\n </Box>;\n }}</Reactive>\n ))"
|
|
43931
44259
|
},
|
|
43932
44260
|
{
|
|
43933
44261
|
"id": "e3-ui:sheet-records.examples.tsx:sheetRecordIndexesMobile",
|
|
@@ -43935,7 +44263,7 @@
|
|
|
43935
44263
|
"package": "e3-ui",
|
|
43936
44264
|
"file": "east-ui/packages/e3-ui/test/sheet/sheet-records.examples.tsx",
|
|
43937
44265
|
"suite": "",
|
|
43938
|
-
"test": "The same indexed work orders in a phone-width frame, with the rail's shared Slice controls on the toolbar's responsive ladder.",
|
|
44266
|
+
"test": "The same indexed work orders and their lens in a phone-width frame, with the rail's shared Slice controls on the toolbar's responsive ladder.",
|
|
43939
44267
|
"keywords": [
|
|
43940
44268
|
"Sheet",
|
|
43941
44269
|
"Slice.bindRecords",
|
|
@@ -43943,6 +44271,7 @@
|
|
|
43943
44271
|
"mobile",
|
|
43944
44272
|
"record",
|
|
43945
44273
|
"filters",
|
|
44274
|
+
"lens",
|
|
43946
44275
|
"saved views",
|
|
43947
44276
|
"RecordView"
|
|
43948
44277
|
],
|
|
@@ -43955,7 +44284,7 @@
|
|
|
43955
44284
|
"languages": [
|
|
43956
44285
|
"tsx"
|
|
43957
44286
|
],
|
|
43958
|
-
"source": "// The same indexed work orders in a phone-width frame, with the rail's shared Slice controls on the toolbar's responsive ladder.\n// inputs: []\n
|
|
44287
|
+
"source": "// The same indexed work orders and their lens in a phone-width frame, with the rail's shared Slice controls on the toolbar's responsive ladder.\n// inputs: []\n/**\n * A work order: edited as a Sheet row, and a row of the Plan whose operations\n * are a record of their own (`plan/plan-records.examples.tsx`). Its String\n * record key is kept through every index.\n */\nexport const IndexedWork = StructType({ reference: StringType, job: StringType, status: StringType, due: DateTimeType, quantity: IntegerType, urgent: BooleanType, tags: SetType(StringType) });\n\nconst Work = DictType(StringType, IndexedWork);\n\n/** The `i`-th work order's reference, its record key: `JOB-` and its number plus 10,000, so key order is number order. */\nexport const indexedWorkReference = East.function([IntegerType], StringType, (_$, i) => East.str`JOB-${i.add(10000n)}`);\n\n/** The job the `i`-th work order runs: cut panels, edge band, inspect and pack, in turn. */\nexport const indexedWorkJob = East.function([IntegerType], StringType, ($, i) => {\n const jobs = $.const([\"Cut panels\", \"Edge band\", \"Inspect\", \"Pack\"], ArrayType(StringType));\n return jobs.get(i.remainder(4n));\n});\n\n/** When the `i`-th work order is due: a day of the ninety from 1 October 2026, each order thirty-seven days on from the one before. */\nexport const indexedWorkDue = East.function([IntegerType], DateTimeType, ($, i) => {\n const start = $.const(new Date(\"2026-10-01T00:00:00Z\"));\n return start.addMilliseconds(i.multiply(37n).remainder(90n).multiply(86400000n));\n});\n\n/** The same pure generator seeds the example record and runs as an e3 task. */\nconst generateWork = East.function([IntegerType], Work, ($, count) => {\n const rows = $.let(new Map(), Work);\n const statuses = $.const([\"Ready\", \"Scheduled\", \"Done\"], ArrayType(StringType));\n const referenceOf = $.const(indexedWorkReference);\n const jobOf = $.const(indexedWorkJob);\n const dueOf = $.const(indexedWorkDue);\n const i = $.let(0n);\n $.while(i.less(count), ($) => {\n const reference = $.const(referenceOf(i));\n $(rows.insert(reference, {\n reference, job: jobOf(i), status: statuses.get(i.remainder(3n)),\n due: dueOf(i),\n quantity: i.remainder(24n).add(1n).multiply(100n), urgent: i.remainder(5n).equal(0n),\n tags: i.remainder(2n).equal(0n).ifElse(() => new Set([\"Production\"]), () => new Set([\"Dispatch\"])),\n }));\n $.assign(i, i.add(1n));\n });\n return rows;\n});\n\nexport const indexedWorkOrders = e3.record(\"indexed_work_orders\", Work, East.compile(generateWork, [])(50000n));\n\nexport const indexedWorkPatch = e3.mutation.patch(indexedWorkOrders);\n\nexport const indexedWorkByStatusDue = e3.recordIndex(\"by_status_due\", indexedWorkOrders, { on: [\"status\", \"due\"] });\n\nexport const indexedWorkByDue = e3.recordIndex(\"by_due\", indexedWorkOrders, { on: [\"due\"] });\n\nexport const indexedWorkByJob = e3.recordIndex(\"by_job\", indexedWorkOrders, { on: [\"job\"] });\n\nexport const indexedWorkByQuantity = e3.recordIndex(\"by_quantity\", indexedWorkOrders, { on: [\"quantity\"] });\n\nexport const indexedWorkByUrgent = e3.recordIndex(\"by_urgent\", indexedWorkOrders, { on: [\"urgent\"] });\n\nexport const indexedWorkByTag = e3.recordIndex(\"by_tag\", indexedWorkOrders, { on: [\"tags\"] });\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{($) => {\n const record = $.let(Record.bind(indexedWorkOrders, [indexedWorkPatch]));\n // One rail over the work orders: its `rows` source narrows them, and the sheet draws the narrowing over the record's rows.\n const slice = $.let(Slice.bindRecords(\"ex.sheetRecordIndexesMobile\", {\n rows: {\n source: Data.bindPaged(indexedWorkOrders, { indexes: [indexedWorkByStatusDue, indexedWorkByDue, indexedWorkByJob, indexedWorkByQuantity, indexedWorkByUrgent, indexedWorkByTag] }),\n fields: { status: { label: \"Status\", hints: [\"Ready\", \"Scheduled\", \"Done\"] }, due: { label: \"Due\" }, job: { label: \"Job\" }, quantity: { label: \"Quantity\" }, urgent: { label: \"Urgent\" }, tags: { label: \"Tag\", hints: [\"Production\", \"Dispatch\"] } },\n range: \"due\", search: [\"job\"], initial: { status: [\"Ready\"] },\n },\n }, { presets: [{ id: \"ready\", name: \"Ready\", where: { rows: { status: [\"Ready\"] } } }, { id: \"urgent\", name: \"Urgent\", where: { rows: { urgent: [true] } } }] }));\n const views = $.let(State.bind([ArrayType(Sheet.Types.RecordView)], \"ex.sheetRecordIndexesMobile.views\", []));\n return <Box width=\"390px\" maxWidth=\"100%\" height=\"760px\">\n <Sheet name=\"sheetRecordIndexesMobile\" slice={{ slice }} views={views}\n columns={{ reference: Sheet.column.text(IndexedWork, { header: \"Reference\", width: \"136px\" }), job: Sheet.column.text(IndexedWork, { header: \"Job\", width: \"180px\" }), status: Sheet.column.text(IndexedWork, { header: \"Status\", width: \"120px\" }), due: Sheet.column.date(IndexedWork, { header: \"Due\", width: \"144px\" }), quantity: Sheet.column.integer(IndexedWork, { header: \"Quantity\", width: \"112px\" }) }}\n onApply={Record.onApply(record, { keyed: true })} edits={{ insertRows: false }} />\n </Box>;\n }}</Reactive>\n ))"
|
|
43959
44288
|
},
|
|
43960
44289
|
{
|
|
43961
44290
|
"id": "e3-ui:sheet-transactions.examples.ts:sheetApplyBatch",
|
|
@@ -44094,7 +44423,7 @@
|
|
|
44094
44423
|
"package": "e3-ui",
|
|
44095
44424
|
"file": "east-ui/packages/e3-ui/test/sheet/sheet.examples.tsx",
|
|
44096
44425
|
"suite": "",
|
|
44097
|
-
"test": "Two thousand jobs of an e3 record, read only and virtualised in the sheet's frame,
|
|
44426
|
+
"test": "Two thousand jobs of an e3 record, read only and virtualised in the sheet's frame, in the record's order, the rail's narrowing — by activity, by status and by a prefix of their notes, each through the record's index — drawn over them as the lens, a filtered spreadsheet showing only the jobs it keeps, with saved views kept per viewer, each every source's narrowing",
|
|
44098
44427
|
"keywords": [
|
|
44099
44428
|
"Sheet",
|
|
44100
44429
|
"stress",
|
|
@@ -44111,6 +44440,7 @@
|
|
|
44111
44440
|
"record",
|
|
44112
44441
|
"search",
|
|
44113
44442
|
"filter",
|
|
44443
|
+
"lens",
|
|
44114
44444
|
"views",
|
|
44115
44445
|
"RecordView",
|
|
44116
44446
|
"State.bind",
|
|
@@ -44132,7 +44462,7 @@
|
|
|
44132
44462
|
"languages": [
|
|
44133
44463
|
"tsx"
|
|
44134
44464
|
],
|
|
44135
|
-
"source": "// Two thousand jobs of an e3 record, read only and virtualised in the sheet's frame,
|
|
44465
|
+
"source": "// Two thousand jobs of an e3 record, read only and virtualised in the sheet's frame, in the record's order, the rail's narrowing — by activity, by status and by a prefix of their notes, each through the record's index — drawn over them as the lens, a filtered spreadsheet showing only the jobs it keeps, with saved views kept per viewer, each every source's narrowing\n// inputs: []\n/** A job on the stress sheet — its start, activity, notes, work centres (a\n * machine by code, or a count of a family), status and quantity. */\nexport const StressJob = StructType({\n id: StringType, start: OptionType(DateTimeType), activity: StringType, notes: StringType,\n stations: Sheet.Types.Link, status: StringType, qty: OptionType(FloatType),\n});\n\n/**\n * The jobs, generated from their count — four a day from 5 January, the\n * activities in turn: every fifth a routing run naming a count of routers,\n * the rest a machine by code; every seventh urgent, every eleventh quantity\n * blank.\n */\nexport const generateStressJobs = East.function([IntegerType], ArrayType(StressJob), ($, count) => {\n const activities = $.const([\"Routing\", \"Spraying\", \"Wrapping\", \"Set-up\", \"Maintenance\"], ArrayType(StringType));\n const words = $.const([\"PLANNED\", \"RELEASED\", \"COMPLETE\"], ArrayType(StringType));\n const codes = $.const([\"R2140\", \"R2141\", \"R2145\", \"P3210\", \"A7301\"], ArrayType(StringType));\n const first = $.const(new Date(\"2026-01-05T00:00:00Z\"), DateTimeType);\n const noMembers = $.const([], ArrayType(Sheet.Types.Member));\n const blank = $.const(none, OptionType(FloatType));\n return East.Array.generate(count, StressJob, (_$, i) => ({\n id: East.str`S${i}`,\n start: some(first.addDays(i.divide(4n))),\n activity: activities.get(i.remainder(5n)),\n notes: i.remainder(7n).equal(0n).ifElse((_$2) => \"urgent — inspect before delivery\", (_$2) => East.str`batch ${i.add(100n)}`),\n // Routing runs name a count of routers; the rest a machine by code.\n stations: i.remainder(5n).equal(0n).ifElse(\n (_$2) => East.value({ from: noMembers, to: [variant(\"counted\", { n: i.remainder(3n).add(2n), key: \"CNC router\" })] }, Sheet.Types.Link),\n (_$2) => East.value({ from: noMembers, to: [variant(\"identified\", { key: codes.get(i.remainder(5n)) })] }, Sheet.Types.Link)),\n status: words.get(i.remainder(3n)),\n qty: i.remainder(11n).equal(0n).ifElse((_$2) => blank, (_$2) => East.value(some(i.multiply(7n).toFloat().add(50.0)), OptionType(FloatType))),\n }));\n});\n\n/** The jobs, keyed by their id — two thousand made by {@link generateStressJobs}: what the stress sheet pages and its rail narrows (#1345). */\nexport const sheetStressJobs = e3.record(\"sheet_stress_jobs\", DictType(StringType, StressJob),\n East.compile(East.function([IntegerType], DictType(StringType, StressJob), ($, count) => {\n const generate = $.const(generateStressJobs);\n return generate(count).toDict((_$, job) => job.id, (_$, job) => job);\n }), [])(2000n));\n\n/** The jobs by activity, by status and by their notes: what the rail narrows them by, each choice one index's runs. */\nexport const sheetStressByActivity = e3.recordIndex(\"sheet_stress_by_activity\", sheetStressJobs, { on: [\"activity\"] });\n\nexport const sheetStressByStatus = e3.recordIndex(\"sheet_stress_by_status\", sheetStressJobs, { on: [\"status\"] });\n\nexport const sheetStressByNotes = e3.recordIndex(\"sheet_stress_by_notes\", sheetStressJobs, { on: [\"notes\"] });\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const MachineType = StructType({ code: StringType, family: StringType });\n const machines = $.const([\n { code: \"R2140\", family: \"CNC router\" }, { code: \"R2141\", family: \"CNC router\" }, { code: \"R2145\", family: \"CNC router\" },\n { code: \"P3210\", family: \"4-side planer\" }, { code: \"A7301\", family: \"assembly bench\" },\n ], ArrayType(MachineType));\n // One rail over the jobs: the sheet's rows are the record's, its `rows` source's narrowing drawn over them as the lens.\n const slice = $.let(Slice.bindRecords(\"sheet_stress\", {\n rows: {\n source: Data.bindPaged(sheetStressJobs, { indexes: [sheetStressByActivity, sheetStressByStatus, sheetStressByNotes] }),\n fields: {\n activity: { label: \"Activity\", hints: [\"Routing\", \"Spraying\", \"Wrapping\", \"Set-up\", \"Maintenance\"] },\n status: { label: \"Status\", hints: [\"PLANNED\", \"RELEASED\", \"COMPLETE\"] },\n notes: { label: \"Notes\" },\n },\n search: [\"notes\"],\n },\n }));\n // Each view keeps every source's narrowing, by name: an activity's value is its `.east` literal, a search its prefix.\n const views = $.let(State.bind([ArrayType(Sheet.Types.RecordView)], \"sheet_stress_views\", [\n { id: \"spraying\", name: \"SPRAYING\", folds: new Map(),\n narrowing: new Map([[\"rows\", { where: new Map([[\"activity\", variant(\"values\", [\"\\\"Spraying\\\"\"])]]), preset: none, resolution: none }]]) },\n { id: \"routers\", name: \"ROUTERS\", folds: new Map(),\n narrowing: new Map([[\"rows\", { where: new Map([[\"activity\", variant(\"values\", [\"\\\"Routing\\\"\"])]]), preset: none, resolution: none }]]) },\n { id: \"urgent\", name: \"URGENT\", folds: new Map(),\n narrowing: new Map([[\"rows\", { where: new Map([[\"notes\", variant(\"prefix\", \"urgent\")]]), preset: none, resolution: none }]]) },\n ]));\n return (\n <Box height=\"480px\">\n <Sheet\n slice={{ slice }}\n name=\"stress\"\n registers={{\n stations: Sheet.register.concat([\n Sheet.register.members(machines, { kind: \"machine\", key: m => m.code, label: m => m.code, meta: m => some(m.family) }),\n Sheet.register.members(machines, { kind: \"family\", key: m => m.family, label: m => m.family, meta: _m => some(\"family\") }),\n ]),\n }}\n columns={{\n start: Sheet.column.date(StressJob, { header: \"Start\", width: \"96px\" }),\n activity: Sheet.column.text(StressJob, { header: \"Activity\", width: \"160px\" }),\n notes: Sheet.column.text(StressJob, { header: \"Notes\", sub: \"free text\", width: \"240px\" }),\n stations: Sheet.column.set(StressJob, \"stations\", { header: \"Work centres\", sub: \"3 x router · machine\", width: \"220px\",\n members: [{ kind: \"machine\", identified: true }, { kind: \"family\", countable: true, resolvesTo: \"machine\" }] }),\n status: Sheet.column.text(StressJob, { header: \"Status\", width: \"120px\" }),\n qty: Sheet.column.quantity(StressJob, { header: \"Qty\", width: \"112px\", format: Format.Number({ maximumFractionDigits: 0n }) }),\n }}\n views={views} activeView={some(\"spraying\")}\n readOnly\n footer={[{ text: \"virtualised\" }]}\n />\n </Box>\n );\n }}</Reactive>\n ))"
|
|
44136
44466
|
},
|
|
44137
44467
|
{
|
|
44138
44468
|
"id": "e3-ui:sheet.examples.tsx:sheetLibrary",
|
|
@@ -44248,7 +44578,7 @@
|
|
|
44248
44578
|
"languages": [
|
|
44249
44579
|
"tsx"
|
|
44250
44580
|
],
|
|
44251
|
-
"source": "// The flagship sheet — the workshop's orders as groups and their operations as lines; an activity driver giving units, durations and live halves; one work-centre register joined from a machines record (Dict), the bays (aliases) and the families (folded), its link grammar counting, locking, implying and checking machines; the copilot's fills (derive, history, sequence, default, phrase, capacity) and proposers (a pattern, a learned follower, an async model); an order check; one rail over the orders and the operation and order templates records, each its own source; a library of the templates, the statuses (dropped on an order to set its status) and the columns; an inspector pane showing every field; views kept per viewer; and a footer\n// inputs: []\n/** An activity — the driver's row: its unit, how many days it takes, how many\n * units an hour, the family of machine it runs on (`\"\"` for none) and the\n * halves of the work centres it makes live. */\nexport const WorkshopActivity = StructType({\n name: StringType, uom: StringType, days: IntegerType, rate: FloatType, family: StringType, sides: Sheet.Types.Sides,\n});\n\n/** A machine — its family and the bay it stands in; its code is its key. */\nexport const WorkshopMachine = StructType({ family: StringType, bay: StringType });\n\n/** A status — a word of the statuses register, and its tone. */\nexport const WorkshopStatus = StructType({ word: StringType, tone: StatusValueType });\n\n/** One operation of an order. */\nexport const WorkshopOperation = StructType({\n activity: StringType, // the driver: an activity's name\n start: OptionType(DateTimeType),\n end: OptionType(DateTimeType),\n qty: OptionType(FloatType), // in the activity's unit\n machines: Sheet.Types.Link, // work centres, from → to\n notes: StringType,\n created_by: StringType, // no column: the inspector shows it\n});\n\n/** An order — its name, customer, due date and status, and its operations in order. */\nexport const WorkshopOrder = StructType({\n name: StringType,\n customer: StringType,\n due: OptionType(DateTimeType),\n status: StringType, // a word of the statuses register\n ops: ArrayType(WorkshopOperation),\n});\n\n/** The activities — the driver's members, each with its unit and days. */\nexport const sheetWorkshopActivities = e3.input(\"sheet_workshop_activities\", ArrayType(WorkshopActivity), variant(\"value\", [\n { name: \"Panel cutting\", uom: \"panels\", days: 1n, rate: 12.0, family: \"beam saw\", sides: variant(\"both\", null) },\n { name: \"Edge banding\", uom: \"metres\", days: 1n, rate: 60.0, family: \"edge bander\", sides: variant(\"both\", null) },\n { name: \"CNC routing\", uom: \"panels\", days: 2n, rate: 6.0, family: \"CNC router\", sides: variant(\"both\", null) },\n { name: \"Drilling\", uom: \"panels\", days: 1n, rate: 10.0, family: \"CNC router\", sides: variant(\"both\", null) },\n { name: \"Sanding\", uom: \"panels\", days: 1n, rate: 20.0, family: \"\", sides: variant(\"in\", null) },\n { name: \"Assembly\", uom: \"units\", days: 2n, rate: 2.0, family: \"assembly bench\", sides: variant(\"both\", null) },\n { name: \"Spray finish\", uom: \"doors\", days: 3n, rate: 8.0, family: \"spray booth\", sides: variant(\"both\", null) },\n { name: \"Wrapping\", uom: \"units\", days: 1n, rate: 10.0, family: \"\", sides: variant(\"in\", null) },\n { name: \"Delivery\", uom: \"loads\", days: 1n, rate: 1.0, family: \"\", sides: variant(\"from\", null) },\n]));\n\n/** The machines, keyed by code — the work centres' register. */\nexport const sheetWorkshopMachines = e3.record(\"sheet_workshop_machines\", DictType(StringType, WorkshopMachine), new Map([\n [\"S101\", { family: \"beam saw\", bay: \"Bay 1\" }],\n [\"S102\", { family: \"beam saw\", bay: \"Bay 1\" }],\n [\"S103\", { family: \"beam saw\", bay: \"Bay 1\" }],\n [\"E201\", { family: \"edge bander\", bay: \"Bay 2\" }],\n [\"E202\", { family: \"edge bander\", bay: \"Bay 2\" }],\n [\"R301\", { family: \"CNC router\", bay: \"Bay 3\" }],\n [\"R302\", { family: \"CNC router\", bay: \"Bay 3\" }],\n [\"R303\", { family: \"CNC router\", bay: \"Bay 3\" }],\n [\"F401\", { family: \"spray booth\", bay: \"Bay 4\" }],\n [\"F402\", { family: \"spray booth\", bay: \"Bay 4\" }],\n [\"A701\", { family: \"assembly bench\", bay: \"Bay 7\" }],\n [\"A702\", { family: \"assembly bench\", bay: \"Bay 7\" }],\n]));\n\n/** The orders, keyed by order number: each a group, its operations its lines. */\nexport const sheetWorkshopOrders = e3.record(\"sheet_workshop_orders\", DictType(StringType, WorkshopOrder), new Map([\n [\"WO-2201\", { name: \"WO-2201 · Kitchen, oak\", customer: \"Quillfeather Interiors\", due: some(new Date(\"2026-10-23T00:00:00Z\")), status: \"RELEASED\", ops: [\n { activity: \"Panel cutting\", start: some(new Date(\"2026-10-12T00:00:00Z\")), end: some(new Date(\"2026-10-13T00:00:00Z\")), qty: some(48.0),\n machines: { from: [variant(\"identified\", { key: \"S101\" })], to: [variant(\"identified\", { key: \"E201\" })] }, notes: \"Oak veneered board\", created_by: \"planner\" },\n { activity: \"Edge banding\", start: some(new Date(\"2026-10-14T00:00:00Z\")), end: some(new Date(\"2026-10-15T00:00:00Z\")), qty: some(120.0),\n machines: { from: [variant(\"identified\", { key: \"E201\" })], to: [variant(\"identified\", { key: \"R301\" })] }, notes: \"\", created_by: \"planner\" },\n { activity: \"CNC routing\", start: some(new Date(\"2026-10-15T00:00:00Z\")), end: some(new Date(\"2026-10-17T00:00:00Z\")), qty: some(48.0),\n machines: { from: [variant(\"identified\", { key: \"R301\" })], to: [variant(\"identified\", { key: \"A701\" })] }, notes: \"Hinge cups and handle slots\", created_by: \"planner\" },\n { activity: \"Assembly\", start: none, end: none, qty: some(12.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"A701\" })] }, notes: \"Twelve carcasses\", created_by: \"planner\" },\n { activity: \"Spray finish\", start: none, end: none, qty: some(24.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"F401\" })] }, notes: \"Matt lacquer\", created_by: \"planner\" },\n ] }],\n [\"WO-2202\", { name: \"WO-2202 · Wardrobes, ash\", customer: \"Marrowby Lettings\", due: some(new Date(\"2026-10-30T00:00:00Z\")), status: \"PLANNED\", ops: [\n { activity: \"Panel cutting\", start: some(new Date(\"2026-10-13T00:00:00Z\")), end: some(new Date(\"2026-10-14T00:00:00Z\")), qty: some(36.0),\n machines: { from: [variant(\"identified\", { key: \"S102\" })], to: [variant(\"identified\", { key: \"E202\" })] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Edge banding\", start: none, end: none, qty: some(90.0),\n machines: { from: [variant(\"identified\", { key: \"E202\" })], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Drilling\", start: none, end: none, qty: some(36.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"R302\" })] }, notes: \"Hinge and shelf pins\", created_by: \"planner\" },\n { activity: \"Assembly\", start: none, end: none, qty: some(6.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"A702\" })] }, notes: \"\", created_by: \"planner\" },\n ] }],\n [\"WO-2203\", { name: \"WO-2203 · Vanity unit, walnut\", customer: \"Tallowmere Homes\", due: some(new Date(\"2026-11-02T00:00:00Z\")), status: \"PLANNED\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: some(12.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"S101\" })] }, notes: \"\", created_by: \"planner\" },\n { activity: \"CNC routing\", start: none, end: none, qty: some(12.0),\n machines: { from: [], to: [variant(\"counted\", { n: 1n, key: \"CNC router\" })] }, notes: \"Basin cut-out\", created_by: \"planner\" },\n { activity: \"Spray finish\", start: none, end: none, qty: some(4.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"F402\" })] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Assembly\", start: none, end: none, qty: some(2.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n ] }],\n [\"WO-2204\", { name: \"WO-2204 · Shelving, birch\", customer: \"Pebblecombe School\", due: some(new Date(\"2026-10-20T00:00:00Z\")), status: \"ON HOLD\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: some(60.0),\n machines: { from: [], to: [variant(\"counted\", { n: 2n, key: \"beam saw\" })] }, notes: \"Awaiting board delivery\", created_by: \"planner\" },\n { activity: \"Edge banding\", start: none, end: none, qty: some(150.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Sanding\", start: none, end: none, qty: some(60.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n ] }],\n [\"WO-2205\", { name: \"WO-2205 · Office fit-out, maple\", customer: \"\", due: none, status: \"PLANNED\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: some(80.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"CNC routing\", start: none, end: none, qty: some(40.0),\n machines: { from: [], to: [] }, notes: \"Cable ports\", created_by: \"planner\" },\n { activity: \"Assembly\", start: none, end: none, qty: some(10.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Wrapping\", start: none, end: none, qty: some(10.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Delivery\", start: none, end: none, qty: some(2.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n ] }],\n [\"WO-2206\", { name: \"WO-2206 · Kitchen, painted\", customer: \"Orrisdale Cottages\", due: some(new Date(\"2026-11-06T00:00:00Z\")), status: \"PLANNED\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: some(30.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"S103\" })] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Edge banding\", start: none, end: none, qty: some(70.0),\n machines: { from: [], to: [variant(\"counted\", { n: 1n, key: \"edge bander\" })] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Spray finish\", start: none, end: none, qty: some(18.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"F401\" })] }, notes: \"Primer, then two coats\", created_by: \"planner\" },\n { activity: \"Assembly\", start: none, end: none, qty: some(8.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Wrapping\", start: none, end: none, qty: some(8.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Delivery\", start: none, end: none, qty: some(1.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n ] }],\n]));\n\n/** The orders' patch door — every Save commits through it. */\nexport const sheetWorkshopOrdersPatch = e3.mutation.patch(sheetWorkshopOrders);\n\n/** The orders by status and by customer: what the rail narrows them by, each choice one index's runs. */\nexport const sheetWorkshopOrdersByStatus = e3.recordIndex(\"sheet_workshop_orders_by_status\", sheetWorkshopOrders, { on: [\"status\"] });\n\nexport const sheetWorkshopOrdersByCustomer = e3.recordIndex(\"sheet_workshop_orders_by_customer\", sheetWorkshopOrders, { on: [\"customer\"] });\n\n/** An operation template — the Rows tab's card, the group it sits under, and the activity and work centres it drops. */\nexport const WorkshopOperationTemplate = StructType({ name: StringType, group: StringType, activity: StringType, machines: Sheet.Types.Link });\n\n/** The operation templates, keyed by their key: a record of their own, the rail's `rowTemplates` source (#1345). */\nexport const sheetWorkshopOperationTemplates = e3.record(\"sheet_workshop_operation_templates\", DictType(StringType, WorkshopOperationTemplate), new Map([\n [\"assemble\", { name: \"Assembly\", group: \"Operations\", activity: \"Assembly\", machines: { from: [], to: [] } }],\n [\"cut\", { name: \"Panel cutting\", group: \"Operations\", activity: \"Panel cutting\", machines: { from: [], to: [variant(\"counted\", { n: 1n, key: \"beam saw\" })] } }],\n [\"deliver\", { name: \"Delivery\", group: \"Dispatch\", activity: \"Delivery\", machines: { from: [], to: [] } }],\n [\"edge\", { name: \"Edge banding\", group: \"Operations\", activity: \"Edge banding\", machines: { from: [], to: [variant(\"counted\", { n: 1n, key: \"edge bander\" })] } }],\n [\"route\", { name: \"CNC routing\", group: \"Operations\", activity: \"CNC routing\", machines: { from: [], to: [variant(\"counted\", { n: 1n, key: \"CNC router\" })] } }],\n [\"sand\", { name: \"Sanding\", group: \"Operations\", activity: \"Sanding\", machines: { from: [], to: [] } }],\n [\"spray\", { name: \"Spray finish\", group: \"Operations\", activity: \"Spray finish\", machines: { from: [], to: [variant(\"counted\", { n: 1n, key: \"spray booth\" })] } }],\n [\"wrap\", { name: \"Wrapping\", group: \"Dispatch\", activity: \"Wrapping\", machines: { from: [], to: [] } }],\n]));\n\n/** The operation templates by their group: what the rail narrows them by. */\nexport const sheetWorkshopOperationTemplatesByGroup = e3.recordIndex(\"sheet_workshop_operation_templates_by_group\", sheetWorkshopOperationTemplates, { on: [\"group\"] });\n\n/** An order template — the Rows tab's card, the group it sits under, and the operations each kind of order starts with. */\nexport const WorkshopOrderTemplate = StructType({ name: StringType, group: StringType, ops: ArrayType(WorkshopOperation) });\n\n/** The order templates, keyed by their key: a record of their own, the rail's `groupTemplates` source (#1345). */\nexport const sheetWorkshopOrderTemplates = e3.record(\"sheet_workshop_order_templates\", DictType(StringType, WorkshopOrderTemplate), new Map([\n [\"kitchen\", { name: \"Kitchen order\", group: \"Orders\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Edge banding\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Assembly\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Spray finish\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n ] }],\n [\"vanity\", { name: \"Vanity unit\", group: \"Orders\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"CNC routing\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Spray finish\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Assembly\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n ] }],\n [\"wardrobe\", { name: \"Wardrobe order\", group: \"Orders\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Edge banding\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Drilling\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Assembly\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n ] }],\n]));\n\n/** The order templates by their group: what the rail narrows them by. */\nexport const sheetWorkshopOrderTemplatesByGroup = e3.recordIndex(\"sheet_workshop_order_templates_by_group\", sheetWorkshopOrderTemplates, { on: [\"group\"] });\n\n/** The model behind the workshop's async proposer — an e3 function, a service, a notebook; the sheet needs only its types. */\nconst workshopRecommend = East.asyncPlatform(\n \"sheet_workshop_recommend\",\n [Sheet.Types.DraftContext(WorkshopOrder, \"ops\", WorkshopActivity)],\n ArrayType(Sheet.Types.Proposal(WorkshopOperation)),\n { optional: true },\n);\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const orders = $.let(Record.bind(sheetWorkshopOrders, [sheetWorkshopOrdersPatch]));\n const machines = $.let(Record.bind(sheetWorkshopMachines, []));\n const activities = $.let(Data.bind(sheetWorkshopActivities));\n const operationTemplates = $.let(Data.bind(sheetWorkshopOperationTemplates));\n const orderTemplates = $.let(Data.bind(sheetWorkshopOrderTemplates));\n // The orders read whole, for the footer's count: the sheet pages its rows from the rail's `rows` source,\n // so `orders` is the patch door its Save commits through, never read whole.\n const allOrders = $.let(Data.bind(sheetWorkshopOrders));\n const views = $.let(State.bind([ArrayType(Sheet.Types.RecordView)], \"sheet.workshop.views\", []));\n const statuses = $.let([\n { word: \"PLANNED\", tone: variant(\"neutral\", null) },\n { word: \"RELEASED\", tone: variant(\"info\", null) },\n { word: \"ON HOLD\", tone: variant(\"warning\", null) },\n ], ArrayType(WorkshopStatus));\n const BayType = StructType({ name: StringType, aliases: ArrayType(StringType) });\n const bays = $.let([\n { name: \"Bay 1\", aliases: [\"b1\", \"saw bay\"] },\n { name: \"Bay 2\", aliases: [\"b2\", \"banding bay\"] },\n { name: \"Bay 3\", aliases: [\"b3\", \"router bay\"] },\n { name: \"Bay 4\", aliases: [\"b4\", \"spray bay\"] },\n { name: \"Bay 7\", aliases: [\"b7\", \"assembly bay\"] },\n ], ArrayType(BayType));\n // Every operation of every order: what the footer counts, whatever the rail shows.\n const operations = $.let(allOrders.read().toArray((_$, o) => o.ops).flatMap((_$, ops) => ops));\n // One rail over three records: the orders — the sheet's rows — and each templates record, its own source.\n const slice = $.let(Slice.bindRecords(\"sheet.workshop\", {\n rows: {\n source: Data.bindPaged(sheetWorkshopOrders, { indexes: [sheetWorkshopOrdersByStatus, sheetWorkshopOrdersByCustomer] }),\n label: \"Orders\",\n fields: { status: { label: \"Status\", hints: [\"PLANNED\", \"RELEASED\", \"ON HOLD\"] }, customer: { label: \"Customer\" } },\n search: [\"customer\"],\n },\n rowTemplates: {\n source: Data.bindPaged(sheetWorkshopOperationTemplates, { indexes: [sheetWorkshopOperationTemplatesByGroup] }),\n label: \"Operation templates\",\n fields: { group: { label: \"Group\", hints: [\"Operations\", \"Dispatch\"] } },\n },\n groupTemplates: {\n source: Data.bindPaged(sheetWorkshopOrderTemplates, { indexes: [sheetWorkshopOrderTemplatesByGroup] }),\n label: \"Order templates\",\n fields: { group: { label: \"Group\", hints: [\"Orders\"] } },\n },\n }));\n const Ctx = Sheet.Types.DraftContext(WorkshopOrder, \"ops\", WorkshopActivity);\n const DateFill = OptionType(Sheet.Types.Fill(DateTimeType));\n const FloatFill = OptionType(Sheet.Types.Fill(FloatType));\n const TextFill = OptionType(Sheet.Types.Fill(StringType));\n const LinkFill = OptionType(Sheet.Types.Fill(Sheet.Types.Link));\n const Counted = OptionType(Sheet.Types.Counted);\n const Proposals = ArrayType(Sheet.Types.Proposal(WorkshopOperation));\n // derive — End = start + the activity's days.\n const endFromStart = $.const(East.function([Ctx], DateFill, ($, ctx) => {\n const noFill = $.const(none, DateFill);\n return ctx.row.start.match({\n value: (_$, supplied) => supplied.match({\n none: () => noFill,\n some: (_$, start) => ctx.driver.match({\n none: () => noFill,\n some: (_$, a) => some({ value: start.addDays(a.days), meta: East.str`+${a.days}d · ${a.name}` }),\n }),\n }),\n }, () => noFill);\n }));\n // sequence — the day after the last dated line above; else next Monday.\n const nextSlot = $.const(East.function([Ctx], DateFill, ($, ctx) => {\n const dated = $.let(ctx.rows.slice(0n, ctx.rowIndex).filter((_$, r) => r.start.hasTag(\"value\").and(() => r.start.unwrap(\"value\").hasTag(\"some\"))));\n return dated.length().greater(0n).ifElse(\n ($2) => {\n const last = $2.let(dated.get(dated.length().subtract(1n)));\n return East.value(some({ value: last.start.unwrap(\"value\").unwrap(\"some\").addDays(1n), meta: \"the day after the line above\" }), DateFill);\n },\n ($2) => {\n const daysToMonday = $2.let(East.value(8n, IntegerType).subtract(ctx.today.getDayOfWeek()).remainder(7n));\n const monday = $2.let(ctx.today.addDays(daysToMonday.equal(0n).ifElse((_$) => 7n, (_$) => daysToMonday)));\n return East.value(some({ value: monday, meta: \"next Monday\" }), DateFill);\n });\n }));\n // history — the quantity of the last like operation in the order.\n const lastQuantity = $.const(East.function([Ctx], FloatFill, ($, ctx) => {\n const noFill = $.const(none, FloatFill);\n return ctx.row.activity.match({\n value: ($, activity) => {\n const similar = $.let(ctx.rows.slice(0n, ctx.rowIndex).filter((_$, r) =>\n r.activity.hasTag(\"value\").and(() => r.activity.unwrap(\"value\").equal(activity))\n .and(() => r.qty.hasTag(\"value\")).and(() => r.qty.unwrap(\"value\").hasTag(\"some\"))));\n return similar.size().equal(0n).ifElse(() => noFill, ($) => {\n const row = $.let(similar.get(similar.size().subtract(1n)));\n return some({ value: row.qty.unwrap(\"value\").unwrap(\"some\"), meta: \"the last like operation's quantity\" });\n });\n },\n }, () => noFill);\n }));\n // default — a shift's worth at the activity's rate.\n const shiftQuantity = $.const(East.function([Ctx], FloatFill, ($, ctx) => {\n const noFill = $.const(none, FloatFill);\n return ctx.driver.match({\n none: (_$) => noFill,\n some: (_$, a) => East.value(some({ value: a.rate.multiply(8.0), meta: East.str`${a.rate}/h × 8 h` }), FloatFill),\n });\n }));\n // phrase — the notes, from the supplied quantity and the activity's unit.\n const phrase = $.const(East.function([Ctx], TextFill, ($, ctx) => {\n const noFill = $.const(none, TextFill);\n return ctx.row.qty.hasTag(\"value\").and(() => ctx.row.qty.unwrap(\"value\").hasTag(\"some\")).ifElse(($) => {\n const qty = $.const(ctx.row.qty.unwrap(\"value\").unwrap(\"some\"));\n return ctx.driver.match({\n none: (_$) => noFill,\n some: (_$, a) => East.value(some({ value: East.str`${qty} ${a.uom} · ${a.name}`, meta: \"phrasing from the supplied activity and quantity\" }), TextFill),\n });\n }, () => noFill);\n }));\n // The arity rule — one machine of the activity's family for every sixty units.\n const impliedMachines = $.const(East.function([Ctx], Counted, ($, ctx) => {\n const noCount = $.const(none, Counted);\n $.if(ctx.row.qty.hasTag(\"value\").not(), ($) => { $.return(noCount); });\n const supplied = $.const(ctx.row.qty.unwrap(\"value\"));\n $.if(supplied.hasTag(\"some\").not(), ($) => { $.return(noCount); });\n const qty = $.const(supplied.unwrap(\"some\"));\n // ⌈qty ÷ 60⌉ by hand — `toInteger` refuses a fraction.\n const share = $.let(qty.divide(60.0));\n const frac = $.let(share.remainder(1.0));\n const needed = $.let(frac.equal(0.0).ifElse((_$) => share, (_$) => share.subtract(frac).add(1.0)).toInteger());\n return ctx.driver.match({\n none: (_$) => noCount,\n some: (_$, a) => a.family.equal(\"\").or(() => qty.greater(0.0).not()).ifElse(\n (_$2) => noCount,\n (_$2) => East.value(some({ n: needed, key: a.family }), Counted)),\n });\n }));\n // capacity — the machines the quantity implies, counted.\n const capacity = $.const(East.function([Ctx], LinkFill, ($, ctx) => {\n const noFill = $.const(none, LinkFill);\n const noMembers = $.const([], ArrayType(Sheet.Types.Member));\n const implied = $.const(impliedMachines);\n return implied(ctx).match({\n none: (_$) => noFill,\n some: (_$, c) => East.value(some({ value: { from: [variant(\"counted\", { n: c.n, key: c.key })], to: noMembers }, meta: \"capacity · from the quantity\" }), LinkFill),\n });\n }));\n // history — the work centres of the last like operation in the order.\n const lastMachines = $.const(East.function([Ctx], LinkFill, ($, ctx) => {\n const noFill = $.const(none, LinkFill);\n return ctx.row.activity.match({\n value: ($, activity) => {\n const similar = $.let(ctx.rows.slice(0n, ctx.rowIndex).filter((_$, r) =>\n r.activity.hasTag(\"value\").and(() => r.activity.unwrap(\"value\").equal(activity)).and(() => r.machines.hasTag(\"value\"))));\n return similar.size().equal(0n).ifElse(() => noFill, ($) => {\n const link = $.let(similar.get(similar.size().subtract(1n)).machines.unwrap(\"value\"));\n return link.from.size().add(link.to.size()).equal(0n).ifElse(\n () => noFill, () => some({ value: link, meta: \"the work centres of the last like operation\" }));\n });\n },\n }, () => noFill);\n }));\n // A member check — a machine on the From half runs the operation, so it is of the activity's family.\n const familyOf = $.let(activities.read().toDict((_$, a) => a.name, (_$, a) => a.family));\n const machineDict = $.let(machines.read());\n const familyFits = $.const(East.function([Sheet.Types.CheckContext(WorkshopOrder, \"ops\")], OptionType(StringType), ($, c) => {\n const noFlag = $.const(none, OptionType(StringType));\n return c.half.match({\n to: (_$) => noFlag,\n from: (_$) => c.member.match({\n identified: (_$2, m) => c.row.activity.match({\n value: (_$3, activity) => familyOf.has(activity)\n .and(() => familyOf.get(activity).equal(\"\").not())\n .and(() => machineDict.has(m.key))\n .and(() => machineDict.get(m.key).family.equal(familyOf.get(activity)).not())\n .ifElse(\n (_$4) => East.value(some(East.str`${m.key} is a ${machineDict.get(m.key).family}, not a ${familyOf.get(activity)}`), OptionType(StringType)),\n (_$4) => noFlag),\n }, (_$3) => noFlag),\n }, (_$2) => noFlag),\n });\n }));\n // A pattern — panels cut are banded, then routed.\n const followUps = $.const(East.function([Ctx], Proposals, ($, ctx) => {\n const empty = $.const([], Proposals);\n return ctx.row.activity.hasTag(\"value\")\n .and(() => ctx.row.activity.unwrap(\"value\").equal(\"Panel cutting\"))\n .and(() => ctx.row.end.hasTag(\"value\"))\n .and(() => ctx.row.end.unwrap(\"value\").hasTag(\"some\")).ifElse(($) => {\n const end = $.const(ctx.row.end.unwrap(\"value\").unwrap(\"some\"));\n return $.const([\n { patch: Sheet.patch(WorkshopOperation, { activity: \"Edge banding\", start: some(end), end: some(end.addDays(1n)), notes: \"Band the cut panels\" }), meta: \"edge banding after the cut\" },\n { patch: Sheet.patch(WorkshopOperation, { activity: \"CNC routing\", start: some(end.addDays(1n)), end: some(end.addDays(3n)), notes: \"Route the banded panels\" }), meta: \"routing · end +1…+3 d\" },\n ], Proposals);\n }, () => empty);\n }));\n // Learned from the order — what followed this activity in it, after how long.\n const lastFollower = $.const(East.function([Ctx], Proposals, ($, ctx) => {\n const empty = $.const([], Proposals);\n return ctx.row.activity.hasTag(\"value\")\n .and(() => ctx.row.start.hasTag(\"value\"))\n .and(() => ctx.row.start.unwrap(\"value\").hasTag(\"some\")).ifElse(($) => {\n const activity = $.const(ctx.row.activity.unwrap(\"value\"));\n const start = $.const(ctx.row.start.unwrap(\"value\").unwrap(\"some\"));\n const dated = $.const(ctx.rows.filter((_$, r) => r.start.hasTag(\"value\")\n .and(() => r.start.unwrap(\"value\").hasTag(\"some\"))\n .and(() => r.activity.hasTag(\"value\"))));\n const upper = $.const(dated.size().greater(1n).ifElse(() => dated.size().subtract(1n), () => 0n));\n const pairs = $.const(East.Array.range(0n, upper).filter((_$, i) =>\n dated.get(i).activity.unwrap(\"value\").equal(activity)\n .and(() => dated.get(i.add(1n)).activity.unwrap(\"value\").equal(activity).not())));\n return pairs.size().equal(0n).ifElse(() => empty, ($) => {\n const index = $.const(pairs.get(pairs.size().subtract(1n)));\n const from = $.const(dated.get(index));\n const next = $.const(dated.get(index.add(1n)));\n const gap = $.const(next.start.unwrap(\"value\").unwrap(\"some\").toEpochMilliseconds()\n .subtract(from.start.unwrap(\"value\").unwrap(\"some\").toEpochMilliseconds()));\n return $.const([{ patch: Sheet.patch(WorkshopOperation, { activity: next.activity.unwrap(\"value\"), start: some(start.addMilliseconds(gap)) }),\n meta: \"follower learned from the order\" }], Proposals);\n });\n }, () => empty);\n }));\n // A model — an ASYNC proposer; the strip shows a pending chip, and a newer context cancels the wait.\n const modelProposals = $.const(East.asyncFunction([Ctx], Proposals, ($, ctx) => {\n const result = $.let([], Proposals);\n $.try(($) => { $.assign(result, workshopRecommend(ctx)); }).catch(($, message) => {\n // The showcase has no model behind it; any other failure still reaches the sheet's provider diagnostic.\n $.if(message.notEqual(\"Platform function 'sheet_workshop_recommend' is not available\"), ($) => { $.error(message); });\n });\n return result;\n }));\n // A new operation's and a new order's defaults, and the check every order passes before Save.\n const newRow = $.const(East.function([Sheet.Types.NewRow], Sheet.Types.Patch(WorkshopOperation), () =>\n Sheet.patch(WorkshopOperation, { start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" })));\n const newGroup = $.const(East.function([Sheet.Types.NewGroup], Sheet.Types.Patch(WorkshopOrder), () =>\n Sheet.patch(WorkshopOrder, { status: \"PLANNED\", due: none, ops: [] })));\n const readyOrder = $.const(East.function([Sheet.Types.DraftGroup(WorkshopOrder, \"ops\")], Sheet.Types.Readiness, ($, order) => {\n $.if(order.customer.hasTag(\"value\").and(() => order.customer.unwrap(\"value\").length().equal(0n)), $ => {\n $.return(East.value(variant(\"incomplete\", [{ field: \"customer\", message: \"Name the customer\" }]), Sheet.Types.Readiness));\n });\n return East.value(variant(\"ready\", null), Sheet.Types.Readiness);\n }));\n return (\n <Box height=\"760px\">\n <Sheet\n slice={{ slice }}\n group={Sheet.group(WorkshopOrder, \"ops\", {\n title: \"name\", sub: o => o.customer, noun: { singular: \"order\", plural: \"orders\" },\n cells: { activity: Sheet.group.cell.enum(WorkshopOrder, \"statuses\", \"status\"),\n end: Sheet.group.cell.date(WorkshopOrder, \"due\") },\n })}\n driver={Sheet.driver(\"activity\", activities.read(), { key: a => a.name, label: a => a.name, meta: a => some(a.uom) })}\n registers={{\n machines: Sheet.register.concat([\n // A Dict's key rides as the accessors' second argument.\n Sheet.register.members(machines.read(), { kind: \"machine\", key: (_m, code) => code, label: (_m, code) => code,\n meta: m => some(m.family), parent: m => some(m.bay) }),\n Sheet.register.members(bays, { kind: \"bay\", key: b => b.name, label: b => b.name, aliases: b => b.aliases,\n meta: _b => some(\"bay\") }),\n // Every machine names a family — duplicate keys fold, the first wins.\n Sheet.register.members(machines.read(), { kind: \"family\", key: m => m.family, label: m => m.family,\n meta: _m => some(\"family\") }),\n ]),\n statuses: Sheet.register.members(statuses, { kind: \"status\", key: s => s.word, label: s => s.word, tone: s => some(s.tone) }),\n }}\n columns={{\n activity: Sheet.column.lookup(WorkshopOperation, { header: \"Activity\", width: \"160px\" }),\n start: Sheet.column.date(WorkshopOperation, { header: \"Start\", width: \"96px\", fill: [nextSlot] }),\n end: Sheet.column.date(WorkshopOperation, { header: \"End\", sub: \"start + days\", width: \"96px\",\n base: \"start\", fill: [endFromStart] }),\n qty: Sheet.column.quantity(WorkshopOperation, WorkshopActivity, { header: \"Qty\", sub: \"unit per activity\",\n width: \"104px\", uom: a => a.uom, format: Format.Number({ maximumFractionDigits: 0n }), fill: [lastQuantity, shiftQuantity] }),\n machines: Sheet.column.link(WorkshopOperation, WorkshopActivity, \"machines\", {\n header: \"Work centres\", sub: \"from → to · 2 x edge bander\", width: \"300px\",\n members: [{ kind: \"machine\", identified: true },\n { kind: \"bay\", countable: true, resolvesTo: \"machine\" },\n { kind: \"family\", countable: true, resolvesTo: \"machine\" }],\n multiple: { forms: [\"N x kind\", \"kind x N\"], ops: [\"x\", \"X\", \"*\", \"×\"], appliesTo: \"countable\" },\n sides: { value: a => a.sides, locks: { from: { to: \"external\", in: \"in place\" }, to: { from: \"external\" } } },\n arity: Sheet.link.arity(\"from\", impliedMachines),\n check: [Sheet.link.check.exists(), familyFits],\n fill: [lastMachines, capacity] }),\n notes: Sheet.column.text(WorkshopOperation, { header: \"Notes\", width: \"240px\", fill: [phrase] }),\n }}\n suggest={{ ahead: 2n, triggers: [\"activity\", \"start\", \"end\", \"qty\", \"notes\", \"machines\"],\n propose: [followUps, modelProposals, lastFollower] }}\n // The inspector pane: every field of an operation; created_by, which no column shows, read only.\n inspector\n fields={{ created_by: Sheet.field.readonly() }}\n // The templates: two records, each list following its source of the rail.\n templates={[\n Sheet.templates(operationTemplates.read(), { name: t => t.name, group: t => some(t.group),\n values: t => Sheet.patch(WorkshopOperation, { activity: t.activity, machines: t.machines }), source: \"rowTemplates\" }),\n Sheet.templates(orderTemplates.read(), { name: t => t.name, group: t => some(t.group),\n values: t => Sheet.patch(WorkshopOrder, { status: \"PLANNED\", due: none, ops: t.ops }), source: \"groupTemplates\" }),\n ]}\n library={[\n Sheet.library.rows(),\n // The statuses an order takes: a card dropped on an order's band sets its status.\n Sheet.library.tab(statuses, { name: \"Statuses\", icon: \"flag\",\n key: s => s.word, label: s => s.word, drop: s => Sheet.patch(WorkshopOrder, { status: s.word }) }),\n Sheet.library.columns(),\n ]}\n newRow={newRow}\n newGroup={newGroup}\n ready={{ group: readyOrder }}\n views={views}\n onApply={Record.onApply(orders, { keyed: true })}\n footer={[{ text: East.str`${operations.size()} operations` }]}\n name=\"workshop\"\n />\n </Box>\n );\n }}</Reactive>\n ))"
|
|
44581
|
+
"source": "// The flagship sheet — the workshop's orders as groups and their operations as lines; an activity driver giving units, durations and live halves; one work-centre register joined from a machines record (Dict), the bays (aliases) and the families (folded), its link grammar counting, locking, implying and checking machines; the copilot's fills (derive, history, sequence, default, phrase, capacity) and proposers (a pattern, a learned follower, an async model); an order check; one rail over the orders and the operation and order templates records, each its own source; a library of the templates, the statuses (dropped on an order to set its status) and the columns; an inspector pane showing every field; views kept per viewer; and a footer\n// inputs: []\n/** An activity — the driver's row: its unit, how many days it takes, how many\n * units an hour, the family of machine it runs on (`\"\"` for none) and the\n * halves of the work centres it makes live. */\nexport const WorkshopActivity = StructType({\n name: StringType, uom: StringType, days: IntegerType, rate: FloatType, family: StringType, sides: Sheet.Types.Sides,\n});\n\n/** A machine — its family and the bay it stands in; its code is its key. */\nexport const WorkshopMachine = StructType({ family: StringType, bay: StringType });\n\n/** A status — a word of the statuses register, and its tone. */\nexport const WorkshopStatus = StructType({ word: StringType, tone: StatusValueType });\n\n/** One operation of an order. */\nexport const WorkshopOperation = StructType({\n activity: StringType, // the driver: an activity's name\n start: OptionType(DateTimeType),\n end: OptionType(DateTimeType),\n qty: OptionType(FloatType), // in the activity's unit\n machines: Sheet.Types.Link, // work centres, from → to\n notes: StringType,\n created_by: StringType, // no column: the inspector shows it\n});\n\n/** An order — its name, customer, due date and status, and its operations in order. */\nexport const WorkshopOrder = StructType({\n name: StringType,\n customer: StringType,\n due: OptionType(DateTimeType),\n status: StringType, // a word of the statuses register\n ops: ArrayType(WorkshopOperation),\n});\n\n/** The activities — the driver's members, each with its unit and days. */\nexport const sheetWorkshopActivities = e3.input(\"sheet_workshop_activities\", ArrayType(WorkshopActivity), variant(\"value\", [\n { name: \"Panel cutting\", uom: \"panels\", days: 1n, rate: 12.0, family: \"beam saw\", sides: variant(\"both\", null) },\n { name: \"Edge banding\", uom: \"metres\", days: 1n, rate: 60.0, family: \"edge bander\", sides: variant(\"both\", null) },\n { name: \"CNC routing\", uom: \"panels\", days: 2n, rate: 6.0, family: \"CNC router\", sides: variant(\"both\", null) },\n { name: \"Drilling\", uom: \"panels\", days: 1n, rate: 10.0, family: \"CNC router\", sides: variant(\"both\", null) },\n { name: \"Sanding\", uom: \"panels\", days: 1n, rate: 20.0, family: \"\", sides: variant(\"in\", null) },\n { name: \"Assembly\", uom: \"units\", days: 2n, rate: 2.0, family: \"assembly bench\", sides: variant(\"both\", null) },\n { name: \"Spray finish\", uom: \"doors\", days: 3n, rate: 8.0, family: \"spray booth\", sides: variant(\"both\", null) },\n { name: \"Wrapping\", uom: \"units\", days: 1n, rate: 10.0, family: \"\", sides: variant(\"in\", null) },\n { name: \"Delivery\", uom: \"loads\", days: 1n, rate: 1.0, family: \"\", sides: variant(\"from\", null) },\n]));\n\n/** The machines, keyed by code — the work centres' register. */\nexport const sheetWorkshopMachines = e3.record(\"sheet_workshop_machines\", DictType(StringType, WorkshopMachine), new Map([\n [\"S101\", { family: \"beam saw\", bay: \"Bay 1\" }],\n [\"S102\", { family: \"beam saw\", bay: \"Bay 1\" }],\n [\"S103\", { family: \"beam saw\", bay: \"Bay 1\" }],\n [\"E201\", { family: \"edge bander\", bay: \"Bay 2\" }],\n [\"E202\", { family: \"edge bander\", bay: \"Bay 2\" }],\n [\"R301\", { family: \"CNC router\", bay: \"Bay 3\" }],\n [\"R302\", { family: \"CNC router\", bay: \"Bay 3\" }],\n [\"R303\", { family: \"CNC router\", bay: \"Bay 3\" }],\n [\"F401\", { family: \"spray booth\", bay: \"Bay 4\" }],\n [\"F402\", { family: \"spray booth\", bay: \"Bay 4\" }],\n [\"A701\", { family: \"assembly bench\", bay: \"Bay 7\" }],\n [\"A702\", { family: \"assembly bench\", bay: \"Bay 7\" }],\n]));\n\n/** The orders, keyed by order number: each a group, its operations its lines. */\nexport const sheetWorkshopOrders = e3.record(\"sheet_workshop_orders\", DictType(StringType, WorkshopOrder), new Map([\n [\"WO-2201\", { name: \"WO-2201 · Kitchen, oak\", customer: \"Quillfeather Interiors\", due: some(new Date(\"2026-10-23T00:00:00Z\")), status: \"RELEASED\", ops: [\n { activity: \"Panel cutting\", start: some(new Date(\"2026-10-12T00:00:00Z\")), end: some(new Date(\"2026-10-13T00:00:00Z\")), qty: some(48.0),\n machines: { from: [variant(\"identified\", { key: \"S101\" })], to: [variant(\"identified\", { key: \"E201\" })] }, notes: \"Oak veneered board\", created_by: \"planner\" },\n { activity: \"Edge banding\", start: some(new Date(\"2026-10-14T00:00:00Z\")), end: some(new Date(\"2026-10-15T00:00:00Z\")), qty: some(120.0),\n machines: { from: [variant(\"identified\", { key: \"E201\" })], to: [variant(\"identified\", { key: \"R301\" })] }, notes: \"\", created_by: \"planner\" },\n { activity: \"CNC routing\", start: some(new Date(\"2026-10-15T00:00:00Z\")), end: some(new Date(\"2026-10-17T00:00:00Z\")), qty: some(48.0),\n machines: { from: [variant(\"identified\", { key: \"R301\" })], to: [variant(\"identified\", { key: \"A701\" })] }, notes: \"Hinge cups and handle slots\", created_by: \"planner\" },\n { activity: \"Assembly\", start: none, end: none, qty: some(12.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"A701\" })] }, notes: \"Twelve carcasses\", created_by: \"planner\" },\n { activity: \"Spray finish\", start: none, end: none, qty: some(24.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"F401\" })] }, notes: \"Matt lacquer\", created_by: \"planner\" },\n ] }],\n [\"WO-2202\", { name: \"WO-2202 · Wardrobes, ash\", customer: \"Marrowby Lettings\", due: some(new Date(\"2026-10-30T00:00:00Z\")), status: \"PLANNED\", ops: [\n { activity: \"Panel cutting\", start: some(new Date(\"2026-10-13T00:00:00Z\")), end: some(new Date(\"2026-10-14T00:00:00Z\")), qty: some(36.0),\n machines: { from: [variant(\"identified\", { key: \"S102\" })], to: [variant(\"identified\", { key: \"E202\" })] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Edge banding\", start: none, end: none, qty: some(90.0),\n machines: { from: [variant(\"identified\", { key: \"E202\" })], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Drilling\", start: none, end: none, qty: some(36.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"R302\" })] }, notes: \"Hinge and shelf pins\", created_by: \"planner\" },\n { activity: \"Assembly\", start: none, end: none, qty: some(6.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"A702\" })] }, notes: \"\", created_by: \"planner\" },\n ] }],\n [\"WO-2203\", { name: \"WO-2203 · Vanity unit, walnut\", customer: \"Tallowmere Homes\", due: some(new Date(\"2026-11-02T00:00:00Z\")), status: \"PLANNED\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: some(12.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"S101\" })] }, notes: \"\", created_by: \"planner\" },\n { activity: \"CNC routing\", start: none, end: none, qty: some(12.0),\n machines: { from: [], to: [variant(\"counted\", { n: 1n, key: \"CNC router\" })] }, notes: \"Basin cut-out\", created_by: \"planner\" },\n { activity: \"Spray finish\", start: none, end: none, qty: some(4.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"F402\" })] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Assembly\", start: none, end: none, qty: some(2.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n ] }],\n [\"WO-2204\", { name: \"WO-2204 · Shelving, birch\", customer: \"Pebblecombe School\", due: some(new Date(\"2026-10-20T00:00:00Z\")), status: \"ON HOLD\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: some(60.0),\n machines: { from: [], to: [variant(\"counted\", { n: 2n, key: \"beam saw\" })] }, notes: \"Awaiting board delivery\", created_by: \"planner\" },\n { activity: \"Edge banding\", start: none, end: none, qty: some(150.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Sanding\", start: none, end: none, qty: some(60.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n ] }],\n [\"WO-2205\", { name: \"WO-2205 · Office fit-out, maple\", customer: \"\", due: none, status: \"PLANNED\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: some(80.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"CNC routing\", start: none, end: none, qty: some(40.0),\n machines: { from: [], to: [] }, notes: \"Cable ports\", created_by: \"planner\" },\n { activity: \"Assembly\", start: none, end: none, qty: some(10.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Wrapping\", start: none, end: none, qty: some(10.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Delivery\", start: none, end: none, qty: some(2.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n ] }],\n [\"WO-2206\", { name: \"WO-2206 · Kitchen, painted\", customer: \"Orrisdale Cottages\", due: some(new Date(\"2026-11-06T00:00:00Z\")), status: \"PLANNED\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: some(30.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"S103\" })] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Edge banding\", start: none, end: none, qty: some(70.0),\n machines: { from: [], to: [variant(\"counted\", { n: 1n, key: \"edge bander\" })] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Spray finish\", start: none, end: none, qty: some(18.0),\n machines: { from: [], to: [variant(\"identified\", { key: \"F401\" })] }, notes: \"Primer, then two coats\", created_by: \"planner\" },\n { activity: \"Assembly\", start: none, end: none, qty: some(8.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Wrapping\", start: none, end: none, qty: some(8.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n { activity: \"Delivery\", start: none, end: none, qty: some(1.0),\n machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" },\n ] }],\n]));\n\n/** The orders' patch door — every Save commits through it. */\nexport const sheetWorkshopOrdersPatch = e3.mutation.patch(sheetWorkshopOrders);\n\n/** The orders by status and by customer: what the rail narrows them by, each choice one index's runs. */\nexport const sheetWorkshopOrdersByStatus = e3.recordIndex(\"sheet_workshop_orders_by_status\", sheetWorkshopOrders, { on: [\"status\"] });\n\nexport const sheetWorkshopOrdersByCustomer = e3.recordIndex(\"sheet_workshop_orders_by_customer\", sheetWorkshopOrders, { on: [\"customer\"] });\n\n/** An operation template — the Rows tab's card, the group it sits under, and the activity and work centres it drops. */\nexport const WorkshopOperationTemplate = StructType({ name: StringType, group: StringType, activity: StringType, machines: Sheet.Types.Link });\n\n/** The operation templates, keyed by their key: a record of their own, the rail's `rowTemplates` source (#1345). */\nexport const sheetWorkshopOperationTemplates = e3.record(\"sheet_workshop_operation_templates\", DictType(StringType, WorkshopOperationTemplate), new Map([\n [\"assemble\", { name: \"Assembly\", group: \"Operations\", activity: \"Assembly\", machines: { from: [], to: [] } }],\n [\"cut\", { name: \"Panel cutting\", group: \"Operations\", activity: \"Panel cutting\", machines: { from: [], to: [variant(\"counted\", { n: 1n, key: \"beam saw\" })] } }],\n [\"deliver\", { name: \"Delivery\", group: \"Dispatch\", activity: \"Delivery\", machines: { from: [], to: [] } }],\n [\"edge\", { name: \"Edge banding\", group: \"Operations\", activity: \"Edge banding\", machines: { from: [], to: [variant(\"counted\", { n: 1n, key: \"edge bander\" })] } }],\n [\"route\", { name: \"CNC routing\", group: \"Operations\", activity: \"CNC routing\", machines: { from: [], to: [variant(\"counted\", { n: 1n, key: \"CNC router\" })] } }],\n [\"sand\", { name: \"Sanding\", group: \"Operations\", activity: \"Sanding\", machines: { from: [], to: [] } }],\n [\"spray\", { name: \"Spray finish\", group: \"Operations\", activity: \"Spray finish\", machines: { from: [], to: [variant(\"counted\", { n: 1n, key: \"spray booth\" })] } }],\n [\"wrap\", { name: \"Wrapping\", group: \"Dispatch\", activity: \"Wrapping\", machines: { from: [], to: [] } }],\n]));\n\n/** The operation templates by their group: what the rail narrows them by. */\nexport const sheetWorkshopOperationTemplatesByGroup = e3.recordIndex(\"sheet_workshop_operation_templates_by_group\", sheetWorkshopOperationTemplates, { on: [\"group\"] });\n\n/** An order template — the Rows tab's card, the group it sits under, and the operations each kind of order starts with. */\nexport const WorkshopOrderTemplate = StructType({ name: StringType, group: StringType, ops: ArrayType(WorkshopOperation) });\n\n/** The order templates, keyed by their key: a record of their own, the rail's `groupTemplates` source (#1345). */\nexport const sheetWorkshopOrderTemplates = e3.record(\"sheet_workshop_order_templates\", DictType(StringType, WorkshopOrderTemplate), new Map([\n [\"kitchen\", { name: \"Kitchen order\", group: \"Orders\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Edge banding\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Assembly\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Spray finish\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n ] }],\n [\"vanity\", { name: \"Vanity unit\", group: \"Orders\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"CNC routing\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Spray finish\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Assembly\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n ] }],\n [\"wardrobe\", { name: \"Wardrobe order\", group: \"Orders\", ops: [\n { activity: \"Panel cutting\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Edge banding\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Drilling\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n { activity: \"Assembly\", start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"\" },\n ] }],\n]));\n\n/** The order templates by their group: what the rail narrows them by. */\nexport const sheetWorkshopOrderTemplatesByGroup = e3.recordIndex(\"sheet_workshop_order_templates_by_group\", sheetWorkshopOrderTemplates, { on: [\"group\"] });\n\n/** The model behind the workshop's async proposer — an e3 function, a service, a notebook; the sheet needs only its types. */\nconst workshopRecommend = East.asyncPlatform(\n \"sheet_workshop_recommend\",\n [Sheet.Types.DraftContext(WorkshopOrder, \"ops\", WorkshopActivity)],\n ArrayType(Sheet.Types.Proposal(WorkshopOperation)),\n { optional: true },\n);\n\nEast.function([], UIComponentType, (_$) => (\n <Reactive>{$ => {\n const orders = $.let(Record.bind(sheetWorkshopOrders, [sheetWorkshopOrdersPatch]));\n const machines = $.let(Record.bind(sheetWorkshopMachines, []));\n const activities = $.let(Data.bind(sheetWorkshopActivities));\n const operationTemplates = $.let(Data.bind(sheetWorkshopOperationTemplates));\n const orderTemplates = $.let(Data.bind(sheetWorkshopOrderTemplates));\n // The orders read whole, for the footer's count: the sheet reads its rows through the rail's `rows` source,\n // so `orders` is the patch door its Save commits through, never read whole.\n const allOrders = $.let(Data.bind(sheetWorkshopOrders));\n const views = $.let(State.bind([ArrayType(Sheet.Types.RecordView)], \"sheet.workshop.views\", []));\n const statuses = $.let([\n { word: \"PLANNED\", tone: variant(\"neutral\", null) },\n { word: \"RELEASED\", tone: variant(\"info\", null) },\n { word: \"ON HOLD\", tone: variant(\"warning\", null) },\n ], ArrayType(WorkshopStatus));\n const BayType = StructType({ name: StringType, aliases: ArrayType(StringType) });\n const bays = $.let([\n { name: \"Bay 1\", aliases: [\"b1\", \"saw bay\"] },\n { name: \"Bay 2\", aliases: [\"b2\", \"banding bay\"] },\n { name: \"Bay 3\", aliases: [\"b3\", \"router bay\"] },\n { name: \"Bay 4\", aliases: [\"b4\", \"spray bay\"] },\n { name: \"Bay 7\", aliases: [\"b7\", \"assembly bay\"] },\n ], ArrayType(BayType));\n // Every operation of every order: what the footer counts, whatever the rail shows.\n const operations = $.let(allOrders.read().toArray((_$, o) => o.ops).flatMap((_$, ops) => ops));\n // One rail over three records: the orders — the sheet's rows — and each templates record, its own source.\n const slice = $.let(Slice.bindRecords(\"sheet.workshop\", {\n rows: {\n source: Data.bindPaged(sheetWorkshopOrders, { indexes: [sheetWorkshopOrdersByStatus, sheetWorkshopOrdersByCustomer] }),\n label: \"Orders\",\n fields: { status: { label: \"Status\", hints: [\"PLANNED\", \"RELEASED\", \"ON HOLD\"] }, customer: { label: \"Customer\" } },\n search: [\"customer\"],\n },\n rowTemplates: {\n source: Data.bindPaged(sheetWorkshopOperationTemplates, { indexes: [sheetWorkshopOperationTemplatesByGroup] }),\n label: \"Operation templates\",\n fields: { group: { label: \"Group\", hints: [\"Operations\", \"Dispatch\"] } },\n },\n groupTemplates: {\n source: Data.bindPaged(sheetWorkshopOrderTemplates, { indexes: [sheetWorkshopOrderTemplatesByGroup] }),\n label: \"Order templates\",\n fields: { group: { label: \"Group\", hints: [\"Orders\"] } },\n },\n }));\n const Ctx = Sheet.Types.DraftContext(WorkshopOrder, \"ops\", WorkshopActivity);\n const DateFill = OptionType(Sheet.Types.Fill(DateTimeType));\n const FloatFill = OptionType(Sheet.Types.Fill(FloatType));\n const TextFill = OptionType(Sheet.Types.Fill(StringType));\n const LinkFill = OptionType(Sheet.Types.Fill(Sheet.Types.Link));\n const Counted = OptionType(Sheet.Types.Counted);\n const Proposals = ArrayType(Sheet.Types.Proposal(WorkshopOperation));\n // derive — End = start + the activity's days.\n const endFromStart = $.const(East.function([Ctx], DateFill, ($, ctx) => {\n const noFill = $.const(none, DateFill);\n return ctx.row.start.match({\n value: (_$, supplied) => supplied.match({\n none: () => noFill,\n some: (_$, start) => ctx.driver.match({\n none: () => noFill,\n some: (_$, a) => some({ value: start.addDays(a.days), meta: East.str`+${a.days}d · ${a.name}` }),\n }),\n }),\n }, () => noFill);\n }));\n // sequence — the day after the last dated line above; else next Monday.\n const nextSlot = $.const(East.function([Ctx], DateFill, ($, ctx) => {\n const dated = $.let(ctx.rows.slice(0n, ctx.rowIndex).filter((_$, r) => r.start.hasTag(\"value\").and(() => r.start.unwrap(\"value\").hasTag(\"some\"))));\n return dated.length().greater(0n).ifElse(\n ($2) => {\n const last = $2.let(dated.get(dated.length().subtract(1n)));\n return East.value(some({ value: last.start.unwrap(\"value\").unwrap(\"some\").addDays(1n), meta: \"the day after the line above\" }), DateFill);\n },\n ($2) => {\n const daysToMonday = $2.let(East.value(8n, IntegerType).subtract(ctx.today.getDayOfWeek()).remainder(7n));\n const monday = $2.let(ctx.today.addDays(daysToMonday.equal(0n).ifElse((_$) => 7n, (_$) => daysToMonday)));\n return East.value(some({ value: monday, meta: \"next Monday\" }), DateFill);\n });\n }));\n // history — the quantity of the last like operation in the order.\n const lastQuantity = $.const(East.function([Ctx], FloatFill, ($, ctx) => {\n const noFill = $.const(none, FloatFill);\n return ctx.row.activity.match({\n value: ($, activity) => {\n const similar = $.let(ctx.rows.slice(0n, ctx.rowIndex).filter((_$, r) =>\n r.activity.hasTag(\"value\").and(() => r.activity.unwrap(\"value\").equal(activity))\n .and(() => r.qty.hasTag(\"value\")).and(() => r.qty.unwrap(\"value\").hasTag(\"some\"))));\n return similar.size().equal(0n).ifElse(() => noFill, ($) => {\n const row = $.let(similar.get(similar.size().subtract(1n)));\n return some({ value: row.qty.unwrap(\"value\").unwrap(\"some\"), meta: \"the last like operation's quantity\" });\n });\n },\n }, () => noFill);\n }));\n // default — a shift's worth at the activity's rate.\n const shiftQuantity = $.const(East.function([Ctx], FloatFill, ($, ctx) => {\n const noFill = $.const(none, FloatFill);\n return ctx.driver.match({\n none: (_$) => noFill,\n some: (_$, a) => East.value(some({ value: a.rate.multiply(8.0), meta: East.str`${a.rate}/h × 8 h` }), FloatFill),\n });\n }));\n // phrase — the notes, from the supplied quantity and the activity's unit.\n const phrase = $.const(East.function([Ctx], TextFill, ($, ctx) => {\n const noFill = $.const(none, TextFill);\n return ctx.row.qty.hasTag(\"value\").and(() => ctx.row.qty.unwrap(\"value\").hasTag(\"some\")).ifElse(($) => {\n const qty = $.const(ctx.row.qty.unwrap(\"value\").unwrap(\"some\"));\n return ctx.driver.match({\n none: (_$) => noFill,\n some: (_$, a) => East.value(some({ value: East.str`${qty} ${a.uom} · ${a.name}`, meta: \"phrasing from the supplied activity and quantity\" }), TextFill),\n });\n }, () => noFill);\n }));\n // The arity rule — one machine of the activity's family for every sixty units.\n const impliedMachines = $.const(East.function([Ctx], Counted, ($, ctx) => {\n const noCount = $.const(none, Counted);\n $.if(ctx.row.qty.hasTag(\"value\").not(), ($) => { $.return(noCount); });\n const supplied = $.const(ctx.row.qty.unwrap(\"value\"));\n $.if(supplied.hasTag(\"some\").not(), ($) => { $.return(noCount); });\n const qty = $.const(supplied.unwrap(\"some\"));\n // ⌈qty ÷ 60⌉ by hand — `toInteger` refuses a fraction.\n const share = $.let(qty.divide(60.0));\n const frac = $.let(share.remainder(1.0));\n const needed = $.let(frac.equal(0.0).ifElse((_$) => share, (_$) => share.subtract(frac).add(1.0)).toInteger());\n return ctx.driver.match({\n none: (_$) => noCount,\n some: (_$, a) => a.family.equal(\"\").or(() => qty.greater(0.0).not()).ifElse(\n (_$2) => noCount,\n (_$2) => East.value(some({ n: needed, key: a.family }), Counted)),\n });\n }));\n // capacity — the machines the quantity implies, counted.\n const capacity = $.const(East.function([Ctx], LinkFill, ($, ctx) => {\n const noFill = $.const(none, LinkFill);\n const noMembers = $.const([], ArrayType(Sheet.Types.Member));\n const implied = $.const(impliedMachines);\n return implied(ctx).match({\n none: (_$) => noFill,\n some: (_$, c) => East.value(some({ value: { from: [variant(\"counted\", { n: c.n, key: c.key })], to: noMembers }, meta: \"capacity · from the quantity\" }), LinkFill),\n });\n }));\n // history — the work centres of the last like operation in the order.\n const lastMachines = $.const(East.function([Ctx], LinkFill, ($, ctx) => {\n const noFill = $.const(none, LinkFill);\n return ctx.row.activity.match({\n value: ($, activity) => {\n const similar = $.let(ctx.rows.slice(0n, ctx.rowIndex).filter((_$, r) =>\n r.activity.hasTag(\"value\").and(() => r.activity.unwrap(\"value\").equal(activity)).and(() => r.machines.hasTag(\"value\"))));\n return similar.size().equal(0n).ifElse(() => noFill, ($) => {\n const link = $.let(similar.get(similar.size().subtract(1n)).machines.unwrap(\"value\"));\n return link.from.size().add(link.to.size()).equal(0n).ifElse(\n () => noFill, () => some({ value: link, meta: \"the work centres of the last like operation\" }));\n });\n },\n }, () => noFill);\n }));\n // A member check — a machine on the From half runs the operation, so it is of the activity's family.\n const familyOf = $.let(activities.read().toDict((_$, a) => a.name, (_$, a) => a.family));\n const machineDict = $.let(machines.read());\n const familyFits = $.const(East.function([Sheet.Types.CheckContext(WorkshopOrder, \"ops\")], OptionType(StringType), ($, c) => {\n const noFlag = $.const(none, OptionType(StringType));\n return c.half.match({\n to: (_$) => noFlag,\n from: (_$) => c.member.match({\n identified: (_$2, m) => c.row.activity.match({\n value: (_$3, activity) => familyOf.has(activity)\n .and(() => familyOf.get(activity).equal(\"\").not())\n .and(() => machineDict.has(m.key))\n .and(() => machineDict.get(m.key).family.equal(familyOf.get(activity)).not())\n .ifElse(\n (_$4) => East.value(some(East.str`${m.key} is a ${machineDict.get(m.key).family}, not a ${familyOf.get(activity)}`), OptionType(StringType)),\n (_$4) => noFlag),\n }, (_$3) => noFlag),\n }, (_$2) => noFlag),\n });\n }));\n // A pattern — panels cut are banded, then routed.\n const followUps = $.const(East.function([Ctx], Proposals, ($, ctx) => {\n const empty = $.const([], Proposals);\n return ctx.row.activity.hasTag(\"value\")\n .and(() => ctx.row.activity.unwrap(\"value\").equal(\"Panel cutting\"))\n .and(() => ctx.row.end.hasTag(\"value\"))\n .and(() => ctx.row.end.unwrap(\"value\").hasTag(\"some\")).ifElse(($) => {\n const end = $.const(ctx.row.end.unwrap(\"value\").unwrap(\"some\"));\n return $.const([\n { patch: Sheet.patch(WorkshopOperation, { activity: \"Edge banding\", start: some(end), end: some(end.addDays(1n)), notes: \"Band the cut panels\" }), meta: \"edge banding after the cut\" },\n { patch: Sheet.patch(WorkshopOperation, { activity: \"CNC routing\", start: some(end.addDays(1n)), end: some(end.addDays(3n)), notes: \"Route the banded panels\" }), meta: \"routing · end +1…+3 d\" },\n ], Proposals);\n }, () => empty);\n }));\n // Learned from the order — what followed this activity in it, after how long.\n const lastFollower = $.const(East.function([Ctx], Proposals, ($, ctx) => {\n const empty = $.const([], Proposals);\n return ctx.row.activity.hasTag(\"value\")\n .and(() => ctx.row.start.hasTag(\"value\"))\n .and(() => ctx.row.start.unwrap(\"value\").hasTag(\"some\")).ifElse(($) => {\n const activity = $.const(ctx.row.activity.unwrap(\"value\"));\n const start = $.const(ctx.row.start.unwrap(\"value\").unwrap(\"some\"));\n const dated = $.const(ctx.rows.filter((_$, r) => r.start.hasTag(\"value\")\n .and(() => r.start.unwrap(\"value\").hasTag(\"some\"))\n .and(() => r.activity.hasTag(\"value\"))));\n const upper = $.const(dated.size().greater(1n).ifElse(() => dated.size().subtract(1n), () => 0n));\n const pairs = $.const(East.Array.range(0n, upper).filter((_$, i) =>\n dated.get(i).activity.unwrap(\"value\").equal(activity)\n .and(() => dated.get(i.add(1n)).activity.unwrap(\"value\").equal(activity).not())));\n return pairs.size().equal(0n).ifElse(() => empty, ($) => {\n const index = $.const(pairs.get(pairs.size().subtract(1n)));\n const from = $.const(dated.get(index));\n const next = $.const(dated.get(index.add(1n)));\n const gap = $.const(next.start.unwrap(\"value\").unwrap(\"some\").toEpochMilliseconds()\n .subtract(from.start.unwrap(\"value\").unwrap(\"some\").toEpochMilliseconds()));\n return $.const([{ patch: Sheet.patch(WorkshopOperation, { activity: next.activity.unwrap(\"value\"), start: some(start.addMilliseconds(gap)) }),\n meta: \"follower learned from the order\" }], Proposals);\n });\n }, () => empty);\n }));\n // A model — an ASYNC proposer; the strip shows a pending chip, and a newer context cancels the wait.\n const modelProposals = $.const(East.asyncFunction([Ctx], Proposals, ($, ctx) => {\n const result = $.let([], Proposals);\n $.try(($) => { $.assign(result, workshopRecommend(ctx)); }).catch(($, message) => {\n // The showcase has no model behind it; any other failure still reaches the sheet's provider diagnostic.\n $.if(message.notEqual(\"Platform function 'sheet_workshop_recommend' is not available\"), ($) => { $.error(message); });\n });\n return result;\n }));\n // A new operation's and a new order's defaults, and the check every order passes before Save.\n const newRow = $.const(East.function([Sheet.Types.NewRow], Sheet.Types.Patch(WorkshopOperation), () =>\n Sheet.patch(WorkshopOperation, { start: none, end: none, qty: none, machines: { from: [], to: [] }, notes: \"\", created_by: \"planner\" })));\n const newGroup = $.const(East.function([Sheet.Types.NewGroup], Sheet.Types.Patch(WorkshopOrder), () =>\n Sheet.patch(WorkshopOrder, { status: \"PLANNED\", due: none, ops: [] })));\n const readyOrder = $.const(East.function([Sheet.Types.DraftGroup(WorkshopOrder, \"ops\")], Sheet.Types.Readiness, ($, order) => {\n $.if(order.customer.hasTag(\"value\").and(() => order.customer.unwrap(\"value\").length().equal(0n)), $ => {\n $.return(East.value(variant(\"incomplete\", [{ field: \"customer\", message: \"Name the customer\" }]), Sheet.Types.Readiness));\n });\n return East.value(variant(\"ready\", null), Sheet.Types.Readiness);\n }));\n return (\n <Box height=\"760px\">\n <Sheet\n slice={{ slice }}\n group={Sheet.group(WorkshopOrder, \"ops\", {\n title: \"name\", sub: o => o.customer, noun: { singular: \"order\", plural: \"orders\" },\n cells: { activity: Sheet.group.cell.enum(WorkshopOrder, \"statuses\", \"status\"),\n end: Sheet.group.cell.date(WorkshopOrder, \"due\") },\n })}\n driver={Sheet.driver(\"activity\", activities.read(), { key: a => a.name, label: a => a.name, meta: a => some(a.uom) })}\n registers={{\n machines: Sheet.register.concat([\n // A Dict's key rides as the accessors' second argument.\n Sheet.register.members(machines.read(), { kind: \"machine\", key: (_m, code) => code, label: (_m, code) => code,\n meta: m => some(m.family), parent: m => some(m.bay) }),\n Sheet.register.members(bays, { kind: \"bay\", key: b => b.name, label: b => b.name, aliases: b => b.aliases,\n meta: _b => some(\"bay\") }),\n // Every machine names a family — duplicate keys fold, the first wins.\n Sheet.register.members(machines.read(), { kind: \"family\", key: m => m.family, label: m => m.family,\n meta: _m => some(\"family\") }),\n ]),\n statuses: Sheet.register.members(statuses, { kind: \"status\", key: s => s.word, label: s => s.word, tone: s => some(s.tone) }),\n }}\n columns={{\n activity: Sheet.column.lookup(WorkshopOperation, { header: \"Activity\", width: \"160px\" }),\n start: Sheet.column.date(WorkshopOperation, { header: \"Start\", width: \"96px\", fill: [nextSlot] }),\n end: Sheet.column.date(WorkshopOperation, { header: \"End\", sub: \"start + days\", width: \"96px\",\n base: \"start\", fill: [endFromStart] }),\n qty: Sheet.column.quantity(WorkshopOperation, WorkshopActivity, { header: \"Qty\", sub: \"unit per activity\",\n width: \"104px\", uom: a => a.uom, format: Format.Number({ maximumFractionDigits: 0n }), fill: [lastQuantity, shiftQuantity] }),\n machines: Sheet.column.link(WorkshopOperation, WorkshopActivity, \"machines\", {\n header: \"Work centres\", sub: \"from → to · 2 x edge bander\", width: \"300px\",\n members: [{ kind: \"machine\", identified: true },\n { kind: \"bay\", countable: true, resolvesTo: \"machine\" },\n { kind: \"family\", countable: true, resolvesTo: \"machine\" }],\n multiple: { forms: [\"N x kind\", \"kind x N\"], ops: [\"x\", \"X\", \"*\", \"×\"], appliesTo: \"countable\" },\n sides: { value: a => a.sides, locks: { from: { to: \"external\", in: \"in place\" }, to: { from: \"external\" } } },\n arity: Sheet.link.arity(\"from\", impliedMachines),\n check: [Sheet.link.check.exists(), familyFits],\n fill: [lastMachines, capacity] }),\n notes: Sheet.column.text(WorkshopOperation, { header: \"Notes\", width: \"240px\", fill: [phrase] }),\n }}\n suggest={{ ahead: 2n, triggers: [\"activity\", \"start\", \"end\", \"qty\", \"notes\", \"machines\"],\n propose: [followUps, modelProposals, lastFollower] }}\n // The inspector pane: every field of an operation; created_by, which no column shows, read only.\n inspector\n fields={{ created_by: Sheet.field.readonly() }}\n // The templates: two records, each list following its source of the rail.\n templates={[\n Sheet.templates(operationTemplates.read(), { name: t => t.name, group: t => some(t.group),\n values: t => Sheet.patch(WorkshopOperation, { activity: t.activity, machines: t.machines }), source: \"rowTemplates\" }),\n Sheet.templates(orderTemplates.read(), { name: t => t.name, group: t => some(t.group),\n values: t => Sheet.patch(WorkshopOrder, { status: \"PLANNED\", due: none, ops: t.ops }), source: \"groupTemplates\" }),\n ]}\n library={[\n Sheet.library.rows(),\n // The statuses an order takes: a card dropped on an order's band sets its status.\n Sheet.library.tab(statuses, { name: \"Statuses\", icon: \"flag\",\n key: s => s.word, label: s => s.word, drop: s => Sheet.patch(WorkshopOrder, { status: s.word }) }),\n Sheet.library.columns(),\n ]}\n newRow={newRow}\n newGroup={newGroup}\n ready={{ group: readyOrder }}\n views={views}\n onApply={Record.onApply(orders, { keyed: true })}\n footer={[{ text: East.str`${operations.size()} operations` }]}\n name=\"workshop\"\n />\n </Box>\n );\n }}</Reactive>\n ))"
|
|
44252
44582
|
},
|
|
44253
44583
|
{
|
|
44254
44584
|
"id": "e3-ui:sheet.examples.tsx:sheetWeeks",
|