@asteby/metacore-runtime-react 23.7.1 → 23.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -36,6 +36,7 @@ export {
36
36
  mergeLaneStages,
37
37
  resolveSmartLanes,
38
38
  smartLaneParams,
39
+ cardMatchesStageFilters,
39
40
  customStageFilterFields,
40
41
  isCustomStageDraftValid,
41
42
  slugifyStageKey,
@@ -44,6 +45,9 @@ export {
44
45
  CustomStageLaneMenu,
45
46
  CustomStageDialog,
46
47
  CustomStageDeleteDialog,
48
+ StageConditionBuilder,
49
+ StageConfigDialog,
50
+ stageFilterOpSymbol,
47
51
  SmartLane,
48
52
  CUSTOM_STAGE_COLORS,
49
53
  CUSTOM_STAGE_FILTER_OPS,
@@ -53,7 +57,21 @@ export {
53
57
  type CustomStageFilter,
54
58
  type CustomStageFilterOp,
55
59
  type UseCustomStagesResult,
60
+ type StageConfigTarget,
61
+ type StageConfigKind,
62
+ type StageConditionBuilderProps,
63
+ type StageConfigDialogProps,
56
64
  } from './custom-stages'
65
+ export {
66
+ useStageLayout,
67
+ type StageLayout,
68
+ type UseStageLayoutResult,
69
+ } from './stage-layout'
70
+ export {
71
+ useStageOverrides,
72
+ type StageOverridePatch,
73
+ type UseStageOverridesResult,
74
+ } from './stage-overrides'
57
75
  export {
58
76
  DynamicView,
59
77
  resolveViewRenderer,
@@ -0,0 +1,93 @@
1
+ // Stage layout — per-org persistence of the DynamicKanban lane order. Lets a
2
+ // user drag the board's columns (declared stages, custom stages and smart
3
+ // lanes alike) into any order; the chosen order is saved server-side and
4
+ // re-applied on load (the ops backend stamps it onto `metadata.stages[].order`
5
+ // / `smart_lanes[].order`, so the board already paints ordered — this hook only
6
+ // owns the drag + the optimistic PUT).
7
+ //
8
+ // Non-intrusive by design: mirrors `useStageAutomations`. If the host wires no
9
+ // `/stage-layout` endpoint, the GET 404s, `available` stays false, and the lane
10
+ // drag simply never turns on — the kanban keeps working untouched.
11
+ //
12
+ // Contract (matches the ops backend; envelope is {success, data} → read .data):
13
+ // GET /stage-layout?model=<m> → { model, stage_order: string[] } | null
14
+ // PUT /stage-layout { model, stage_order: string[] } (full order)
15
+ // DELETE /stage-layout?model=<m> → reset to the declared order
16
+ import { useCallback, useEffect, useState } from 'react'
17
+ import { useApi } from './api-context'
18
+
19
+ export interface StageLayout {
20
+ model: string
21
+ /** The full lane order — every lane key (stages + smart lanes) in order. */
22
+ stage_order: string[]
23
+ }
24
+
25
+ export interface UseStageLayoutResult {
26
+ /** True only after a successful GET — gates the lane drag + reset affordance. */
27
+ available: boolean
28
+ /** True when a custom order is stored server-side (drives the reset affordance). */
29
+ hasCustomLayout: boolean
30
+ /** Persist the full lane order (the new order of every lane key). Throws on failure. */
31
+ save: (order: string[]) => Promise<void>
32
+ /** Drop the stored order, reverting to the declared stage order. */
33
+ reset: () => Promise<void>
34
+ }
35
+
36
+ function unwrap(res: { data: any }): any {
37
+ const body = res?.data
38
+ if (body && typeof body === 'object' && 'data' in body) return body.data
39
+ return body
40
+ }
41
+
42
+ /**
43
+ * Loads a model's saved lane order (only to learn availability + whether a
44
+ * custom order exists) and exposes save/reset. A missing endpoint degrades to
45
+ * `available: false` so the board's lane drag stays off; a real save failure
46
+ * re-throws so the caller can revert its optimistic reorder.
47
+ */
48
+ export function useStageLayout(model: string): UseStageLayoutResult {
49
+ const api = useApi()
50
+ const [available, setAvailable] = useState(false)
51
+ const [hasCustomLayout, setHasCustomLayout] = useState(false)
52
+
53
+ useEffect(() => {
54
+ let cancelled = false
55
+ api
56
+ .get(`/stage-layout?model=${encodeURIComponent(model)}`)
57
+ .then((res) => {
58
+ if (cancelled) return
59
+ setAvailable(true)
60
+ const data = unwrap(res)
61
+ const order = data?.stage_order
62
+ setHasCustomLayout(Array.isArray(order) && order.length > 0)
63
+ })
64
+ .catch(() => {
65
+ // Endpoint absent or errored — leave lane drag off.
66
+ if (!cancelled) setAvailable(false)
67
+ })
68
+ return () => {
69
+ cancelled = true
70
+ }
71
+ }, [api, model])
72
+
73
+ const save = useCallback(
74
+ async (order: string[]) => {
75
+ const res = (await api.put('/stage-layout', {
76
+ model,
77
+ stage_order: order,
78
+ })) as { data?: { success?: boolean; message?: string } }
79
+ if (res?.data && res.data.success === false) {
80
+ throw new Error(res.data.message || 'stage_layout_save_failed')
81
+ }
82
+ setHasCustomLayout(true)
83
+ },
84
+ [api, model],
85
+ )
86
+
87
+ const reset = useCallback(async () => {
88
+ await api.delete(`/stage-layout?model=${encodeURIComponent(model)}`)
89
+ setHasCustomLayout(false)
90
+ }, [api, model])
91
+
92
+ return { available, hasCustomLayout, save, reset }
93
+ }
@@ -0,0 +1,95 @@
1
+ // Stage overrides — per-org customization of a model's DECLARED kanban lanes
2
+ // (Backlog, Done, …). A user can rename a lane, recolor it, and attach extra
3
+ // "conditions" (a field/operator/value builder) that narrow which cards the lane
4
+ // shows and counts — all without touching the addon manifest. The chosen values
5
+ // are stored server-side and the kernel serves each declared stage already
6
+ // carrying the overridden label/color (+ an `overridden` flag and the extra
7
+ // `filters`), so the board paints them straight from `metadata.stages`.
8
+ //
9
+ // Custom stages (`custom: true`) are NOT edited here — they keep their own CRUD
10
+ // (`/custom-stages`); this hook only owns the DECLARED-lane overrides.
11
+ //
12
+ // Non-intrusive by design: mirrors `useStageLayout`. If the host wires no
13
+ // `/stage-overrides` endpoint, the GET 404s, `available` stays false, and the
14
+ // per-lane gear (⚙) simply never renders on declared stages — the kanban keeps
15
+ // working untouched.
16
+ //
17
+ // Contract (matches the ops backend; envelope is {success, data} → read .data):
18
+ // GET /stage-overrides?model=<m> → [{ stage_key, label?, color?, filters? }] | null
19
+ // PUT /stage-overrides { model, stage_key, label?, color?, filters? } (upsert)
20
+ // DELETE /stage-overrides?model=<m>&stage_key=<k> → reset the lane to its declared default
21
+ import { useCallback, useEffect, useState } from 'react'
22
+ import { useApi } from './api-context'
23
+ import type { CustomStageFilter } from './custom-stages'
24
+
25
+ /** Fields a stage override can carry. All optional — send only what changed. */
26
+ export interface StageOverridePatch {
27
+ label?: string
28
+ color?: string
29
+ /** Extra lane conditions (same shape/ops as smart-lane filters). */
30
+ filters?: CustomStageFilter[]
31
+ }
32
+
33
+ export interface UseStageOverridesResult {
34
+ /** True only after a successful GET — gates the per-lane gear on declared stages. */
35
+ available: boolean
36
+ /** Upsert a declared lane's override (label/color/conditions). Throws on failure. */
37
+ save: (stageKey: string, patch: StageOverridePatch) => Promise<void>
38
+ /** Reset a declared lane to its manifest default (drops the stored override). */
39
+ reset: (stageKey: string) => Promise<void>
40
+ }
41
+
42
+ /**
43
+ * Learns whether the host wired `/stage-overrides` (so the gear can appear on
44
+ * declared lanes) and exposes save/reset. A missing endpoint degrades to
45
+ * `available: false`; a real save/reset failure re-throws so the caller can
46
+ * surface an error + revert. The overridden VALUES themselves are read off
47
+ * `metadata.stages` (the kernel applies them), so this hook doesn't cache a list.
48
+ */
49
+ export function useStageOverrides(model: string): UseStageOverridesResult {
50
+ const api = useApi()
51
+ const [available, setAvailable] = useState(false)
52
+
53
+ useEffect(() => {
54
+ let cancelled = false
55
+ api
56
+ .get(`/stage-overrides?model=${encodeURIComponent(model)}`)
57
+ .then(() => {
58
+ if (!cancelled) setAvailable(true)
59
+ })
60
+ .catch(() => {
61
+ // Endpoint absent or errored — leave the gear off on declared lanes.
62
+ if (!cancelled) setAvailable(false)
63
+ })
64
+ return () => {
65
+ cancelled = true
66
+ }
67
+ }, [api, model])
68
+
69
+ const save = useCallback(
70
+ async (stageKey: string, patch: StageOverridePatch) => {
71
+ const res = (await api.put('/stage-overrides', {
72
+ model,
73
+ stage_key: stageKey,
74
+ ...patch,
75
+ })) as { data?: { success?: boolean; message?: string } }
76
+ if (res?.data && res.data.success === false) {
77
+ throw new Error(res.data.message || 'stage_override_save_failed')
78
+ }
79
+ },
80
+ [api, model],
81
+ )
82
+
83
+ const reset = useCallback(
84
+ async (stageKey: string) => {
85
+ await api.delete(
86
+ `/stage-overrides?model=${encodeURIComponent(
87
+ model,
88
+ )}&stage_key=${encodeURIComponent(stageKey)}`,
89
+ )
90
+ },
91
+ [api, model],
92
+ )
93
+
94
+ return { available, save, reset }
95
+ }
package/src/types.ts CHANGED
@@ -77,6 +77,34 @@ export interface StageMeta {
77
77
  * Editar/Eliminar menu on it.
78
78
  */
79
79
  custom?: boolean
80
+ /**
81
+ * True when a per-org stage override (label/color/conditions) has been
82
+ * applied to this DECLARED lane (ops stage-overrides). The kernel serves the
83
+ * lane already carrying the overridden label/color; this flag only drives the
84
+ * "Restablecer etapa" affordance in the config dialog. Absent on hosts without
85
+ * stage overrides — purely additive.
86
+ */
87
+ overridden?: boolean
88
+ /**
89
+ * Extra per-lane conditions layered on top of the stage's own `group_by`
90
+ * scope (ops stage-overrides). When present the lane queries its data — and
91
+ * counts its header — with the stage filter PLUS these conditions (serialized
92
+ * the same way as smart-lane filters). The lane stays a normal drop target;
93
+ * dropping a card only sets the stage value. Absent → the lane behaves as a
94
+ * plain declared stage. Snake_case ops as the kernel serves them.
95
+ */
96
+ filters?: { field: string; op: string; value: string }[]
97
+ /**
98
+ * The manifest ORIGINAL (pre-override) label/color/conditions, served
99
+ * alongside an overridden declared lane so the "Restablecer al original"
100
+ * confirm can spell out exactly what reverts. Optional — hosts that don't
101
+ * snapshot the original simply omit it and the SDK shows a generic confirm.
102
+ */
103
+ original?: {
104
+ label?: string
105
+ color?: string
106
+ filters?: { field: string; op: string; value: string }[]
107
+ }
80
108
  }
81
109
 
82
110
  /**