@asteby/metacore-runtime-react 23.8.0 → 23.9.1
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/CHANGELOG.md +46 -0
- package/dist/custom-stages.d.ts +96 -1
- package/dist/custom-stages.d.ts.map +1 -1
- package/dist/custom-stages.js +259 -24
- package/dist/dynamic-form-schema.d.ts +18 -1
- package/dist/dynamic-form-schema.d.ts.map +1 -1
- package/dist/dynamic-form-schema.js +43 -0
- package/dist/dynamic-form.d.ts.map +1 -1
- package/dist/dynamic-form.js +30 -9
- package/dist/dynamic-kanban.d.ts +8 -0
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +188 -16
- package/dist/dynamic-line-items.d.ts.map +1 -1
- package/dist/dynamic-line-items.js +24 -3
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +5 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/stage-overrides.d.ts +25 -0
- package/dist/stage-overrides.d.ts.map +1 -0
- package/dist/stage-overrides.js +64 -0
- package/dist/types.d.ts +69 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/dependent-enum-form.test.tsx +84 -0
- package/src/__tests__/dependent-enum-options.test.ts +67 -0
- package/src/__tests__/dynamic-kanban.test.tsx +75 -0
- package/src/__tests__/stage-overrides.test.tsx +293 -0
- package/src/custom-stages.tsx +716 -109
- package/src/dynamic-form-schema.ts +44 -1
- package/src/dynamic-form.tsx +76 -28
- package/src/dynamic-kanban.tsx +236 -25
- package/src/dynamic-line-items.tsx +26 -2
- package/src/dynamic-table.tsx +4 -0
- package/src/index.ts +13 -0
- package/src/stage-overrides.ts +95 -0
- package/src/types.ts +63 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 23.9.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3db80ab: fix(data-table/kanban): indicadores de filtro sin duplicados y tarjetas kanban con relaciones resueltas
|
|
8
|
+
- El botón "Limpiar filtros" del toolbar ya no muestra badge de conteo y solo aparece para el estado propio de la tabla (column filters / búsqueda); los filtros dinámicos ya tienen su fila de chips con "Limpiar todo".
|
|
9
|
+
- El icono de filtro del header de columna ya no superpone un badge de conteo: el icono en color primary es el único indicador activo.
|
|
10
|
+
- `f_<col>=eq:<valor>` en la URL se desenvuelve al valor plano, por lo que el chip muestra la etiqueta de la opción ("Estado: Recepción") en vez de `eq:reception`.
|
|
11
|
+
- Las tarjetas del kanban resuelven columnas FK (`<rel>_id`) al objeto hermano resuelto por el backend (nombre/label) en vez del UUID crudo, y un FK UUID-cero se muestra como vacío (—).
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [3db80ab]
|
|
14
|
+
- @asteby/metacore-ui@2.9.2
|
|
15
|
+
|
|
16
|
+
## 23.9.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- 0c41860: DynamicKanban: per-lane configuration gear (⚙). Every board lane — declared
|
|
21
|
+
stages (Backlog, Done…) included, not just custom ones — now carries a subtle
|
|
22
|
+
gear next to the search/filter/⚡ header icons that opens a "Configurar etapa"
|
|
23
|
+
dialog to rename the lane, recolor it, and attach extra CONDITIONS (a
|
|
24
|
+
field/operator/value builder reusing the same eq/neq/contains/in component as
|
|
25
|
+
custom stages).
|
|
26
|
+
- Declared lanes persist through a new `/stage-overrides` endpoint
|
|
27
|
+
(`useStageOverrides`), with a "Restablecer etapa" action that drops the
|
|
28
|
+
override; custom real stages persist through their existing `/custom-stages`
|
|
29
|
+
CRUD (which now also accepts `filters`). The gear unifies both behind one UI.
|
|
30
|
+
- A lane that carries conditions queries its data — and counts its header —
|
|
31
|
+
with the stage scope PLUS those conditions (serialized like smart-lane
|
|
32
|
+
filters), and shows a filter indicator + tooltip listing them. Cards can still
|
|
33
|
+
be dragged into the lane (the drop only sets the stage value).
|
|
34
|
+
- The dialog pre-populates the whole form from the lane's live metadata (label,
|
|
35
|
+
color and every condition, editable) and opens with a "Condiciones actuales"
|
|
36
|
+
chip row summarizing the effective query: a locked base "Etapa = <label>" chip,
|
|
37
|
+
an "Etapa final" chip (with tooltip) for terminal stages, and one
|
|
38
|
+
editable/removable chip per extra condition. An overridden lane shows a
|
|
39
|
+
"Personalizada" badge and a two-step "Restablecer al original" that spells out
|
|
40
|
+
exactly what reverts (from `metadata.stages[].original` when the host serves it).
|
|
41
|
+
- Non-intrusive: when `/stage-overrides` is absent the gear simply doesn't
|
|
42
|
+
render on declared lanes. All copy goes through `t('dynamic.stage_config.*')`
|
|
43
|
+
with Spanish defaults.
|
|
44
|
+
|
|
45
|
+
New exports: `useStageOverrides`, `StageConfigDialog`, `StageConditionBuilder`,
|
|
46
|
+
`cardMatchesStageFilters`, and their types. `StageMeta` gains optional
|
|
47
|
+
`overridden` and `filters`.
|
|
48
|
+
|
|
3
49
|
## 23.8.0
|
|
4
50
|
|
|
5
51
|
### Minor Changes
|
package/dist/custom-stages.d.ts
CHANGED
|
@@ -57,7 +57,11 @@ export declare function mergeLaneStages(declared: StageMeta[], customLaneStages:
|
|
|
57
57
|
* - in → `f_<field>=IN:v1,v2,...` (comma-separated after `IN:`)
|
|
58
58
|
* Empty fields/values are skipped.
|
|
59
59
|
*/
|
|
60
|
-
export declare function smartLaneParams(filters:
|
|
60
|
+
export declare function smartLaneParams(filters: {
|
|
61
|
+
field: string;
|
|
62
|
+
op: string;
|
|
63
|
+
value: string;
|
|
64
|
+
}[] | undefined): Record<string, string>;
|
|
61
65
|
/**
|
|
62
66
|
* Resolves the smart lanes to paint. The kernel's `metadata.smart_lanes` is the
|
|
63
67
|
* source of truth for rendering (ops #704); the CRUD list only backs the
|
|
@@ -75,6 +79,17 @@ export declare function isCustomStageDraftValid(draft: {
|
|
|
75
79
|
type: CustomStageType;
|
|
76
80
|
filters: CustomStageFilter[];
|
|
77
81
|
}): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Whether a card passes a set of extra lane `filters` (the same conditions a
|
|
84
|
+
* smart lane uses, layered on top of a real stage). Ops:
|
|
85
|
+
* - eq → equal (string compare)
|
|
86
|
+
* - neq → not equal
|
|
87
|
+
* - contains → the card's value (array or string) includes the value
|
|
88
|
+
* - in → the card's value is one of the comma-separated candidates
|
|
89
|
+
* Empty/absent filters pass. Pure — a client-side belt-and-suspenders over the
|
|
90
|
+
* initial (unscoped) board page; the server scopes the per-lane top-up queries.
|
|
91
|
+
*/
|
|
92
|
+
export declare function cardMatchesStageFilters(card: any, filters: CustomStageFilter[] | undefined): boolean;
|
|
78
93
|
/** Slugify a label into a stable-ish lane key (used only when creating). */
|
|
79
94
|
export declare function slugifyStageKey(label: string): string;
|
|
80
95
|
export interface UseCustomStagesResult {
|
|
@@ -111,6 +126,23 @@ export interface CustomStageLaneMenuProps {
|
|
|
111
126
|
}
|
|
112
127
|
/** The ⋮ menu shown in a custom lane's header. */
|
|
113
128
|
export declare function CustomStageLaneMenu({ stage, onEdit, onDelete, }: CustomStageLaneMenuProps): React.JSX.Element;
|
|
129
|
+
export interface StageConditionBuilderProps {
|
|
130
|
+
/** The current filter rows. */
|
|
131
|
+
filters: CustomStageFilter[];
|
|
132
|
+
/** Called with the next filter rows on any add/remove/patch. */
|
|
133
|
+
onChange: (filters: CustomStageFilter[]) => void;
|
|
134
|
+
/** Columns offered in the field dropdown (visible, non-id). */
|
|
135
|
+
fieldChoices: ColumnDefinition[];
|
|
136
|
+
/** Optional heading shown above the rows. */
|
|
137
|
+
label?: string;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The reusable field/operator/value condition builder. Emits `CustomStageFilter[]`
|
|
141
|
+
* through `onChange`. Same ops (eq/neq/contains/in) and testids the smart-lane
|
|
142
|
+
* editor has always used, so it drops in for both the custom-stage smart lane
|
|
143
|
+
* and the declared-stage config dialog.
|
|
144
|
+
*/
|
|
145
|
+
export declare function StageConditionBuilder({ filters, onChange, fieldChoices, label, }: StageConditionBuilderProps): React.JSX.Element;
|
|
114
146
|
export interface CustomStageDialogProps {
|
|
115
147
|
open: boolean;
|
|
116
148
|
onOpenChange: (open: boolean) => void;
|
|
@@ -144,6 +176,69 @@ export interface CustomStageDeleteDialogProps {
|
|
|
144
176
|
* cards to; confirming again retries the delete with `reassign_to`.
|
|
145
177
|
*/
|
|
146
178
|
export declare function CustomStageDeleteDialog({ open, onOpenChange, stage, reassignTargets, onConfirm, }: CustomStageDeleteDialogProps): React.JSX.Element | null;
|
|
179
|
+
export type StageConfigKind = 'declared' | 'custom';
|
|
180
|
+
/** What the gear opens the config dialog against — a declared or custom lane. */
|
|
181
|
+
export interface StageConfigTarget {
|
|
182
|
+
kind: StageConfigKind;
|
|
183
|
+
/** Stable lane key. */
|
|
184
|
+
stageKey: string;
|
|
185
|
+
/** Custom-stage id — required (and only used) when `kind === 'custom'`. */
|
|
186
|
+
id?: CustomStage['id'];
|
|
187
|
+
label: string;
|
|
188
|
+
color: string;
|
|
189
|
+
filters: CustomStageFilter[];
|
|
190
|
+
/**
|
|
191
|
+
* Declared kind: an override is currently applied → shows a "Personalizada"
|
|
192
|
+
* badge + "Restablecer al original". Ignored for custom stages (they reset
|
|
193
|
+
* via their own delete flow).
|
|
194
|
+
*/
|
|
195
|
+
overridden?: boolean;
|
|
196
|
+
/** Terminal stage (e.g. "Done") — surfaces an "Etapa final" chip + tooltip. */
|
|
197
|
+
isFinal?: boolean;
|
|
198
|
+
/**
|
|
199
|
+
* The manifest ORIGINAL (pre-override) values, when the host serves them
|
|
200
|
+
* (`metadata.stages[].original`). Drives the "Restablecer al original" confirm
|
|
201
|
+
* so the user sees exactly what reverts. Absent → a generic confirm.
|
|
202
|
+
*/
|
|
203
|
+
original?: {
|
|
204
|
+
label?: string;
|
|
205
|
+
color?: string;
|
|
206
|
+
filters?: CustomStageFilter[];
|
|
207
|
+
};
|
|
208
|
+
/** The backing CustomStage (custom kind) so "Eliminar" can trigger delete. */
|
|
209
|
+
customStage?: CustomStage;
|
|
210
|
+
}
|
|
211
|
+
/** A legible operator glyph for a condition chip: = / ≠ / contiene / en. */
|
|
212
|
+
export declare function stageFilterOpSymbol(op: string): string;
|
|
213
|
+
export interface StageConfigDialogProps {
|
|
214
|
+
open: boolean;
|
|
215
|
+
onOpenChange: (open: boolean) => void;
|
|
216
|
+
/** Columns for the condition builder. */
|
|
217
|
+
columns: ColumnDefinition[];
|
|
218
|
+
/** The lane being configured, or null. */
|
|
219
|
+
target: StageConfigTarget | null;
|
|
220
|
+
/** Declared: upsert the lane override (PUT /stage-overrides). */
|
|
221
|
+
onSaveOverride: (stageKey: string, patch: {
|
|
222
|
+
label: string;
|
|
223
|
+
color: string;
|
|
224
|
+
filters: CustomStageFilter[];
|
|
225
|
+
}) => Promise<void>;
|
|
226
|
+
/** Declared: reset the lane to its manifest default (DELETE /stage-overrides). */
|
|
227
|
+
onResetOverride: (stageKey: string) => Promise<void>;
|
|
228
|
+
/** Custom: update the stage via its own CRUD (PUT /custom-stages/:id). */
|
|
229
|
+
onUpdateCustom: (id: CustomStage['id'], patch: Partial<CustomStage>) => Promise<void>;
|
|
230
|
+
/** Custom: hand off to the existing delete (reassign) flow. */
|
|
231
|
+
onDeleteCustom: (stage: CustomStage) => void;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The gear (⚙) dialog. Renames, recolors and attaches extra CONDITIONS to a
|
|
235
|
+
* lane. One UI, two backends: a DECLARED lane persists through `/stage-overrides`
|
|
236
|
+
* (with a "Restablecer etapa" that drops the override); a CUSTOM real stage
|
|
237
|
+
* persists through its own `/custom-stages` CRUD (and deletes via that flow). The
|
|
238
|
+
* conditions narrow which cards the lane shows/counts but never stop it being a
|
|
239
|
+
* drop target — dropping a card only sets the stage value.
|
|
240
|
+
*/
|
|
241
|
+
export declare function StageConfigDialog({ open, onOpenChange, columns, target, onSaveOverride, onResetOverride, onUpdateCustom, onDeleteCustom, }: StageConfigDialogProps): React.JSX.Element | null;
|
|
147
242
|
export interface SmartLaneProps {
|
|
148
243
|
stage: CustomStage;
|
|
149
244
|
model: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-stages.d.ts","sourceRoot":"","sources":["../src/custom-stages.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAoD,MAAM,OAAO,CAAA;AA4BxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EACR,gBAAgB,EAChB,SAAS,EACT,aAAa,EAEhB,MAAM,SAAS,CAAA;AAMhB,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,OAAO,CAAA;AAC/C,MAAM,MAAM,mBAAmB,GAAG,IAAI,GAAG,KAAK,GAAG,UAAU,GAAG,IAAI,CAAA;AAElE,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,mBAAmB,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAA;IACb,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,eAAe,CAAA;IACrB,uEAAuE;IACvE,OAAO,EAAE,iBAAiB,EAAE,CAAA;IAC5B,OAAO,EAAE,OAAO,CAAA;CACnB;AAED,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;AAGpD,eAAO,MAAM,mBAAmB,+EAStB,CAAA;AAEV,eAAO,MAAM,uBAAuB,EAAE,mBAAmB,EAKxD,CAAA;AAMD,+DAA+D;AAC/D,wBAAgB,sBAAsB,CAAC,KAAK,SAAK,GAAG,iBAAiB,CAEpE;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,GAAG;IAClE,UAAU,EAAE,WAAW,EAAE,CAAA;IACzB,WAAW,EAAE,WAAW,EAAE,CAAA;CAC7B,CASA;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC3B,QAAQ,EAAE,SAAS,EAAE,EACrB,gBAAgB,EAAE,WAAW,EAAE,GAChC;IAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"custom-stages.d.ts","sourceRoot":"","sources":["../src/custom-stages.tsx"],"names":[],"mappings":"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"}
|
package/dist/custom-stages.js
CHANGED
|
@@ -20,7 +20,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
20
20
|
// All UI text goes through t() with a Spanish defaultValue.
|
|
21
21
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
22
22
|
import { useTranslation } from 'react-i18next';
|
|
23
|
-
import { Plus, Trash2, Pencil, MoreVertical, Filter, GripVertical, X } from 'lucide-react';
|
|
23
|
+
import { Plus, Trash2, Pencil, MoreVertical, Filter, Flag, GripVertical, Lock, RotateCcw, X } from 'lucide-react';
|
|
24
24
|
import { toast } from 'sonner';
|
|
25
25
|
import { Badge, Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Skeleton, } from '@asteby/metacore-ui/primitives';
|
|
26
26
|
import { generateBadgeStyles, optionColor } from '@asteby/metacore-ui/lib';
|
|
@@ -85,6 +85,8 @@ export function mergeLaneStages(declared, customLaneStages) {
|
|
|
85
85
|
label: cs.label,
|
|
86
86
|
color: cs.color,
|
|
87
87
|
order: cs.position,
|
|
88
|
+
custom: true,
|
|
89
|
+
filters: cs.filters,
|
|
88
90
|
});
|
|
89
91
|
}
|
|
90
92
|
lanes.sort((a, b) => {
|
|
@@ -172,6 +174,42 @@ export function isCustomStageDraftValid(draft) {
|
|
|
172
174
|
}
|
|
173
175
|
return true;
|
|
174
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Whether a card passes a set of extra lane `filters` (the same conditions a
|
|
179
|
+
* smart lane uses, layered on top of a real stage). Ops:
|
|
180
|
+
* - eq → equal (string compare)
|
|
181
|
+
* - neq → not equal
|
|
182
|
+
* - contains → the card's value (array or string) includes the value
|
|
183
|
+
* - in → the card's value is one of the comma-separated candidates
|
|
184
|
+
* Empty/absent filters pass. Pure — a client-side belt-and-suspenders over the
|
|
185
|
+
* initial (unscoped) board page; the server scopes the per-lane top-up queries.
|
|
186
|
+
*/
|
|
187
|
+
export function cardMatchesStageFilters(card, filters) {
|
|
188
|
+
if (!filters || filters.length === 0)
|
|
189
|
+
return true;
|
|
190
|
+
return filters.every((f) => {
|
|
191
|
+
if (!f.field || String(f.value ?? '').trim() === '')
|
|
192
|
+
return true;
|
|
193
|
+
const target = String(f.value).trim();
|
|
194
|
+
const raw = card?.[f.field];
|
|
195
|
+
switch (f.op) {
|
|
196
|
+
case 'neq':
|
|
197
|
+
return String(raw ?? '') !== target;
|
|
198
|
+
case 'contains':
|
|
199
|
+
if (Array.isArray(raw))
|
|
200
|
+
return raw.map(String).includes(target);
|
|
201
|
+
return String(raw ?? '').includes(target);
|
|
202
|
+
case 'in':
|
|
203
|
+
return target
|
|
204
|
+
.split(',')
|
|
205
|
+
.map((s) => s.trim())
|
|
206
|
+
.includes(String(raw ?? ''));
|
|
207
|
+
case 'eq':
|
|
208
|
+
default:
|
|
209
|
+
return String(raw ?? '') === target;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
175
213
|
/** Slugify a label into a stable-ish lane key (used only when creating). */
|
|
176
214
|
export function slugifyStageKey(label) {
|
|
177
215
|
return (label
|
|
@@ -279,6 +317,36 @@ export function CustomStageLaneMenu({ stage, onEdit, onDelete, }) {
|
|
|
279
317
|
defaultValue: 'Opciones de la etapa',
|
|
280
318
|
}), "data-testid": `custom-stage-menu-${stage.key}`, children: _jsx(MoreVertical, { className: "h-3.5 w-3.5" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: () => onEdit(stage), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), t('dynamic.custom_stages.edit', { defaultValue: 'Editar' })] }), _jsxs(DropdownMenuItem, { className: "text-destructive focus:text-destructive", onClick: () => onDelete(stage), children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), t('dynamic.custom_stages.delete', { defaultValue: 'Eliminar' })] })] })] }));
|
|
281
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* The reusable field/operator/value condition builder. Emits `CustomStageFilter[]`
|
|
322
|
+
* through `onChange`. Same ops (eq/neq/contains/in) and testids the smart-lane
|
|
323
|
+
* editor has always used, so it drops in for both the custom-stage smart lane
|
|
324
|
+
* and the declared-stage config dialog.
|
|
325
|
+
*/
|
|
326
|
+
export function StageConditionBuilder({ filters, onChange, fieldChoices, label, }) {
|
|
327
|
+
const { t } = useTranslation();
|
|
328
|
+
const patchFilter = (i, patch) => onChange(filters.map((f, idx) => (idx === i ? { ...f, ...patch } : f)));
|
|
329
|
+
const addFilter = () => onChange([...filters, emptyCustomStageFilter(fieldChoices[0]?.key ?? '')]);
|
|
330
|
+
const removeFilter = (i) => onChange(filters.filter((_, idx) => idx !== i));
|
|
331
|
+
return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border bg-muted/30 p-3", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [_jsx(Filter, { className: "h-3.5 w-3.5" }), label ??
|
|
332
|
+
t('dynamic.custom_stages.conditions_label', {
|
|
333
|
+
defaultValue: 'Condiciones',
|
|
334
|
+
})] }), filters.map((f, i) => (_jsxs("div", { className: "grid grid-cols-[1fr_auto_1fr_auto] items-center gap-1.5", "data-testid": `custom-stage-condition-${i}`, children: [_jsxs(Select, { value: f.field, onValueChange: (v) => patchFilter(i, { field: v }), children: [_jsx(SelectTrigger, { className: "h-8 text-xs", "data-testid": `custom-stage-condition-field-${i}`, children: _jsx(SelectValue, { placeholder: t('dynamic.custom_stages.field_placeholder', { defaultValue: 'Campo' }) }) }), _jsx(SelectContent, { children: fieldChoices.map((c) => (_jsx(SelectItem, { value: c.key, className: "text-xs", children: t(c.label, { defaultValue: c.label }) }, c.key))) })] }), _jsxs(Select, { value: f.op, onValueChange: (v) => patchFilter(i, { op: v }), children: [_jsx(SelectTrigger, { className: "h-8 w-[104px] text-xs", "data-testid": `custom-stage-condition-op-${i}`, children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: CUSTOM_STAGE_FILTER_OPS.map((op) => (_jsx(SelectItem, { value: op, className: "text-xs", children: t(`dynamic.custom_stages.op.${op}`, {
|
|
335
|
+
defaultValue: op === 'eq'
|
|
336
|
+
? 'es igual'
|
|
337
|
+
: op === 'neq'
|
|
338
|
+
? 'distinto'
|
|
339
|
+
: op === 'contains'
|
|
340
|
+
? 'contiene'
|
|
341
|
+
: 'en lista',
|
|
342
|
+
}) }, op))) })] }), _jsx(Input, { value: f.value, onChange: (e) => patchFilter(i, { value: e.target.value }), placeholder: t('dynamic.custom_stages.value_placeholder', {
|
|
343
|
+
defaultValue: 'Valor',
|
|
344
|
+
}), className: "h-8 text-xs", "data-testid": `custom-stage-condition-value-${i}` }), _jsx("button", { type: "button", onClick: () => removeFilter(i), disabled: filters.length === 1, className: "flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-destructive disabled:opacity-40", "aria-label": t('dynamic.custom_stages.remove_condition', {
|
|
345
|
+
defaultValue: 'Quitar condición',
|
|
346
|
+
}), "data-testid": `custom-stage-condition-remove-${i}`, children: _jsx(X, { className: "h-3.5 w-3.5" }) })] }, i))), _jsxs(Button, { variant: "ghost", size: "sm", className: "h-7 justify-start gap-1 text-xs", onClick: addFilter, "data-testid": "custom-stage-add-condition", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), t('dynamic.custom_stages.add_condition', {
|
|
347
|
+
defaultValue: 'Agregar condición',
|
|
348
|
+
})] })] }));
|
|
349
|
+
}
|
|
282
350
|
export function CustomStageDialog({ open, onOpenChange, model, columns, initial, nextPosition, onCreate, onUpdate, }) {
|
|
283
351
|
const { t } = useTranslation();
|
|
284
352
|
const isDark = typeof document !== 'undefined' &&
|
|
@@ -308,12 +376,6 @@ export function CustomStageDialog({ open, onOpenChange, model, columns, initial,
|
|
|
308
376
|
setFilters([emptyCustomStageFilter(fieldChoices[0]?.key ?? '')]);
|
|
309
377
|
}
|
|
310
378
|
}, [open, initial, fieldChoices]);
|
|
311
|
-
const patchFilter = (i, patch) => setFilters((prev) => prev.map((f, idx) => (idx === i ? { ...f, ...patch } : f)));
|
|
312
|
-
const addFilter = () => setFilters((prev) => [
|
|
313
|
-
...prev,
|
|
314
|
-
emptyCustomStageFilter(fieldChoices[0]?.key ?? ''),
|
|
315
|
-
]);
|
|
316
|
-
const removeFilter = (i) => setFilters((prev) => prev.filter((_, idx) => idx !== i));
|
|
317
379
|
const valid = isCustomStageDraftValid({ label, type, filters });
|
|
318
380
|
const submit = async () => {
|
|
319
381
|
if (!valid || saving)
|
|
@@ -389,23 +451,7 @@ export function CustomStageDialog({ open, onOpenChange, model, columns, initial,
|
|
|
389
451
|
defaultValue: 'Etapa normal',
|
|
390
452
|
}) }), _jsx(SelectItem, { value: "smart", children: t('dynamic.custom_stages.type_smart', {
|
|
391
453
|
defaultValue: 'Etapa inteligente',
|
|
392
|
-
}) })] })] })] }), type === 'smart' && (
|
|
393
|
-
defaultValue: 'Condiciones',
|
|
394
|
-
})] }), filters.map((f, i) => (_jsxs("div", { className: "grid grid-cols-[1fr_auto_1fr_auto] items-center gap-1.5", "data-testid": `custom-stage-condition-${i}`, children: [_jsxs(Select, { value: f.field, onValueChange: (v) => patchFilter(i, { field: v }), children: [_jsx(SelectTrigger, { className: "h-8 text-xs", "data-testid": `custom-stage-condition-field-${i}`, children: _jsx(SelectValue, { placeholder: t('dynamic.custom_stages.field_placeholder', { defaultValue: 'Campo' }) }) }), _jsx(SelectContent, { children: fieldChoices.map((c) => (_jsx(SelectItem, { value: c.key, className: "text-xs", children: t(c.label, { defaultValue: c.label }) }, c.key))) })] }), _jsxs(Select, { value: f.op, onValueChange: (v) => patchFilter(i, { op: v }), children: [_jsx(SelectTrigger, { className: "h-8 w-[104px] text-xs", "data-testid": `custom-stage-condition-op-${i}`, children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: CUSTOM_STAGE_FILTER_OPS.map((op) => (_jsx(SelectItem, { value: op, className: "text-xs", children: t(`dynamic.custom_stages.op.${op}`, {
|
|
395
|
-
defaultValue: op === 'eq'
|
|
396
|
-
? 'es igual'
|
|
397
|
-
: op === 'neq'
|
|
398
|
-
? 'distinto'
|
|
399
|
-
: op === 'contains'
|
|
400
|
-
? 'contiene'
|
|
401
|
-
: 'en lista',
|
|
402
|
-
}) }, op))) })] }), _jsx(Input, { value: f.value, onChange: (e) => patchFilter(i, { value: e.target.value }), placeholder: t('dynamic.custom_stages.value_placeholder', {
|
|
403
|
-
defaultValue: 'Valor',
|
|
404
|
-
}), className: "h-8 text-xs", "data-testid": `custom-stage-condition-value-${i}` }), _jsx("button", { type: "button", onClick: () => removeFilter(i), disabled: filters.length === 1, className: "flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-destructive disabled:opacity-40", "aria-label": t('dynamic.custom_stages.remove_condition', {
|
|
405
|
-
defaultValue: 'Quitar condición',
|
|
406
|
-
}), "data-testid": `custom-stage-condition-remove-${i}`, children: _jsx(X, { className: "h-3.5 w-3.5" }) })] }, i))), _jsxs(Button, { variant: "ghost", size: "sm", className: "h-7 justify-start gap-1 text-xs", onClick: addFilter, "data-testid": "custom-stage-add-condition", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), t('dynamic.custom_stages.add_condition', {
|
|
407
|
-
defaultValue: 'Agregar condición',
|
|
408
|
-
})] })] }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: saving, children: t('dynamic.custom_stages.cancel', { defaultValue: 'Cancelar' }) }), _jsx(Button, { onClick: () => void submit(), disabled: !valid || saving, "data-testid": "custom-stage-save", children: initial
|
|
454
|
+
}) })] })] })] }), type === 'smart' && (_jsx(StageConditionBuilder, { filters: filters, onChange: setFilters, fieldChoices: fieldChoices }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: saving, children: t('dynamic.custom_stages.cancel', { defaultValue: 'Cancelar' }) }), _jsx(Button, { onClick: () => void submit(), disabled: !valid || saving, "data-testid": "custom-stage-save", children: initial
|
|
409
455
|
? t('dynamic.custom_stages.save', { defaultValue: 'Guardar' })
|
|
410
456
|
: t('dynamic.custom_stages.create', { defaultValue: 'Crear etapa' }) })] })] }) }));
|
|
411
457
|
}
|
|
@@ -478,6 +524,195 @@ export function CustomStageDeleteDialog({ open, onOpenChange, stage, reassignTar
|
|
|
478
524
|
defaultValue: 'Eliminar',
|
|
479
525
|
}) })] })] }) }));
|
|
480
526
|
}
|
|
527
|
+
/** A legible operator glyph for a condition chip: = / ≠ / contiene / en. */
|
|
528
|
+
export function stageFilterOpSymbol(op) {
|
|
529
|
+
switch (op) {
|
|
530
|
+
case 'neq':
|
|
531
|
+
return '≠';
|
|
532
|
+
case 'contains':
|
|
533
|
+
return 'contiene';
|
|
534
|
+
case 'in':
|
|
535
|
+
return 'en';
|
|
536
|
+
case 'eq':
|
|
537
|
+
default:
|
|
538
|
+
return '=';
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* The gear (⚙) dialog. Renames, recolors and attaches extra CONDITIONS to a
|
|
543
|
+
* lane. One UI, two backends: a DECLARED lane persists through `/stage-overrides`
|
|
544
|
+
* (with a "Restablecer etapa" that drops the override); a CUSTOM real stage
|
|
545
|
+
* persists through its own `/custom-stages` CRUD (and deletes via that flow). The
|
|
546
|
+
* conditions narrow which cards the lane shows/counts but never stop it being a
|
|
547
|
+
* drop target — dropping a card only sets the stage value.
|
|
548
|
+
*/
|
|
549
|
+
export function StageConfigDialog({ open, onOpenChange, columns, target, onSaveOverride, onResetOverride, onUpdateCustom, onDeleteCustom, }) {
|
|
550
|
+
const { t } = useTranslation();
|
|
551
|
+
const isDark = typeof document !== 'undefined' &&
|
|
552
|
+
document.documentElement.classList.contains('dark');
|
|
553
|
+
const fieldChoices = useMemo(() => customStageFilterFields(columns), [columns]);
|
|
554
|
+
const [label, setLabel] = useState('');
|
|
555
|
+
const [color, setColor] = useState(CUSTOM_STAGE_COLORS[0]);
|
|
556
|
+
const [filters, setFilters] = useState([]);
|
|
557
|
+
const [saving, setSaving] = useState(false);
|
|
558
|
+
const [resetting, setResetting] = useState(false);
|
|
559
|
+
// Two-step reset: the first click reveals a confirm panel listing exactly
|
|
560
|
+
// what reverts (the manifest original), the second actually drops the override.
|
|
561
|
+
const [confirmingReset, setConfirmingReset] = useState(false);
|
|
562
|
+
// Re-seed the WHOLE form from the lane's current (already-override-applied)
|
|
563
|
+
// metadata each time the dialog opens — the user always sees the live state,
|
|
564
|
+
// never a blank form. Label, color and every extra condition pre-fill editable.
|
|
565
|
+
useEffect(() => {
|
|
566
|
+
if (!open || !target)
|
|
567
|
+
return;
|
|
568
|
+
setLabel(target.label);
|
|
569
|
+
setColor(target.color || CUSTOM_STAGE_COLORS[0]);
|
|
570
|
+
setFilters(target.filters?.map((f) => ({ ...f })) ?? []);
|
|
571
|
+
setConfirmingReset(false);
|
|
572
|
+
}, [open, target]);
|
|
573
|
+
if (!target)
|
|
574
|
+
return null;
|
|
575
|
+
const valid = label.trim().length > 0;
|
|
576
|
+
// Only complete conditions are persisted; empty rows are dropped.
|
|
577
|
+
const cleanFilters = () => filters.filter((f) => f.field && String(f.value).trim() !== '');
|
|
578
|
+
// The conditions that make up the lane's EFFECTIVE query, for the chip row:
|
|
579
|
+
// the immovable stage scope first, then each extra (removable) condition.
|
|
580
|
+
const chipFilters = filters.filter((f) => f.field && String(f.value).trim() !== '');
|
|
581
|
+
const removeFilterAt = (i) => setFilters((prev) => prev.filter((_, idx) => idx !== i));
|
|
582
|
+
const submit = async () => {
|
|
583
|
+
if (!valid || saving)
|
|
584
|
+
return;
|
|
585
|
+
setSaving(true);
|
|
586
|
+
try {
|
|
587
|
+
if (target.kind === 'declared') {
|
|
588
|
+
await onSaveOverride(target.stageKey, {
|
|
589
|
+
label: label.trim(),
|
|
590
|
+
color,
|
|
591
|
+
filters: cleanFilters(),
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
else if (target.id != null) {
|
|
595
|
+
await onUpdateCustom(target.id, {
|
|
596
|
+
label: label.trim(),
|
|
597
|
+
color,
|
|
598
|
+
filters: cleanFilters(),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
toast.success(t('dynamic.stage_config.saved', {
|
|
602
|
+
defaultValue: 'Etapa configurada',
|
|
603
|
+
}));
|
|
604
|
+
onOpenChange(false);
|
|
605
|
+
}
|
|
606
|
+
catch {
|
|
607
|
+
// toast already surfaced by the caller's hook; keep the draft open.
|
|
608
|
+
}
|
|
609
|
+
finally {
|
|
610
|
+
setSaving(false);
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
const reset = async () => {
|
|
614
|
+
if (resetting)
|
|
615
|
+
return;
|
|
616
|
+
setResetting(true);
|
|
617
|
+
try {
|
|
618
|
+
await onResetOverride(target.stageKey);
|
|
619
|
+
toast.success(t('dynamic.stage_config.reset_done', {
|
|
620
|
+
defaultValue: 'Etapa restablecida',
|
|
621
|
+
}));
|
|
622
|
+
onOpenChange(false);
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
toast.error(t('dynamic.stage_config.reset_error', {
|
|
626
|
+
defaultValue: 'No se pudo restablecer la etapa',
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
629
|
+
finally {
|
|
630
|
+
setResetting(false);
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
const headerDot = generateBadgeStyles(color || optionColor(target.stageKey), { isDark });
|
|
634
|
+
// A one-line, human summary of the manifest original (for the reset confirm).
|
|
635
|
+
const original = target.original;
|
|
636
|
+
const originalConditions = original?.filters && original.filters.length > 0
|
|
637
|
+
? original.filters
|
|
638
|
+
.map((f) => `${f.field} ${stageFilterOpSymbol(f.op)} ${f.value}`)
|
|
639
|
+
.join(', ')
|
|
640
|
+
: t('dynamic.stage_config.original_no_conditions', {
|
|
641
|
+
defaultValue: 'sin condiciones',
|
|
642
|
+
});
|
|
643
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { className: "flex items-center gap-2", children: [_jsx("span", { className: "inline-block size-3 shrink-0 rounded-full", style: {
|
|
644
|
+
backgroundColor: headerDot.backgroundColor ||
|
|
645
|
+
headerDot.background,
|
|
646
|
+
}, "aria-hidden": true }), t('dynamic.stage_config.title', {
|
|
647
|
+
defaultValue: 'Configurar etapa',
|
|
648
|
+
}), _jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-[10px] font-normal text-muted-foreground", children: target.stageKey }), target.kind === 'declared' && target.overridden && (_jsx(Badge, { variant: "secondary", className: "ml-auto text-[10px] font-medium", "data-testid": "stage-config-personalizada", children: t('dynamic.stage_config.customized', {
|
|
649
|
+
defaultValue: 'Personalizada',
|
|
650
|
+
}) }))] }), _jsx(DialogDescription, { children: t('dynamic.stage_config.description', {
|
|
651
|
+
defaultValue: 'Renombra la columna, cambia su color y agrega condiciones para acotar qué tarjetas muestra y cuenta.',
|
|
652
|
+
}) })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: t('dynamic.stage_config.current_conditions_label', {
|
|
653
|
+
defaultValue: 'Condiciones actuales',
|
|
654
|
+
}) }), _jsxs("div", { className: "flex flex-wrap items-center gap-1.5", "data-testid": "stage-config-current-conditions", children: [_jsxs(Badge, { variant: "outline", className: "cursor-default gap-1 border-dashed text-[11px] font-normal text-muted-foreground opacity-80", title: t('dynamic.stage_config.base_chip_hint', {
|
|
655
|
+
defaultValue: 'Alcance base de la etapa: no se puede quitar.',
|
|
656
|
+
}), "data-testid": "stage-config-base-chip", children: [_jsx(Lock, { className: "h-3 w-3" }), t('dynamic.stage_config.base_chip', {
|
|
657
|
+
defaultValue: 'Etapa = {{label}}',
|
|
658
|
+
label: label || target.label,
|
|
659
|
+
})] }), target.isFinal && (_jsxs(Badge, { variant: "outline", className: "cursor-default gap-1 text-[11px] font-normal text-muted-foreground", title: t('dynamic.stage_config.final_chip_hint', {
|
|
660
|
+
defaultValue: 'Etapa terminal: al mover una tarjeta aquí se marca como cerrada (p. ej. en GitHub cierra el issue).',
|
|
661
|
+
}), "data-testid": "stage-config-final-chip", children: [_jsx(Flag, { className: "h-3 w-3" }), t('dynamic.stage_config.final_chip', {
|
|
662
|
+
defaultValue: 'Etapa final',
|
|
663
|
+
})] })), chipFilters.map((f) => {
|
|
664
|
+
// Chip index maps back to its position in `filters`.
|
|
665
|
+
const i = filters.indexOf(f);
|
|
666
|
+
return (_jsxs(Badge, { variant: "secondary", className: "gap-1 pr-1 text-[11px] font-normal", "data-testid": `stage-config-filter-chip-${i}`, children: [_jsx("span", { className: "font-mono", children: f.field }), _jsx("span", { className: "opacity-70", children: stageFilterOpSymbol(f.op) }), _jsx("span", { children: f.value }), _jsx("button", { type: "button", onClick: () => removeFilterAt(i), className: "ml-0.5 rounded p-0.5 hover:bg-background/60", "aria-label": t('dynamic.stage_config.remove_chip', { defaultValue: 'Quitar condición' }), "data-testid": `stage-config-filter-chip-remove-${i}`, children: _jsx(X, { className: "h-2.5 w-2.5" }) })] }, i));
|
|
667
|
+
})] })] }), _jsx("div", { className: "border-t" }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-xs", children: t('dynamic.stage_config.name_label', {
|
|
668
|
+
defaultValue: 'Nombre',
|
|
669
|
+
}) }), _jsx(Input, { value: label, onChange: (e) => setLabel(e.target.value), placeholder: t('dynamic.stage_config.name_placeholder', {
|
|
670
|
+
defaultValue: 'Nombre de la etapa',
|
|
671
|
+
}), "data-testid": "stage-config-name", autoFocus: true })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-xs", children: t('dynamic.stage_config.color_label', {
|
|
672
|
+
defaultValue: 'Color',
|
|
673
|
+
}) }), _jsx("div", { className: "flex flex-wrap gap-1.5", "data-testid": "stage-config-colors", children: CUSTOM_STAGE_COLORS.map((c) => {
|
|
674
|
+
const style = generateBadgeStyles(c, { isDark });
|
|
675
|
+
const selected = c === color;
|
|
676
|
+
return (_jsx("button", { type: "button", onClick: () => setColor(c), className: `size-6 rounded-full border-2 transition-transform hover:scale-110 ${selected
|
|
677
|
+
? 'border-foreground'
|
|
678
|
+
: 'border-transparent'}`, style: {
|
|
679
|
+
backgroundColor: style.backgroundColor ||
|
|
680
|
+
style.background,
|
|
681
|
+
}, "aria-label": c, "aria-pressed": selected, "data-testid": `stage-config-color-${c}` }, c));
|
|
682
|
+
}) })] }), _jsx(StageConditionBuilder, { filters: filters, onChange: setFilters, fieldChoices: fieldChoices, label: t('dynamic.stage_config.conditions_label', {
|
|
683
|
+
defaultValue: 'Condiciones (opcional)',
|
|
684
|
+
}) }), filters.length === 0 && (_jsxs(Button, { variant: "ghost", size: "sm", className: "-mt-2 h-7 justify-start gap-1 text-xs", onClick: () => setFilters([
|
|
685
|
+
emptyCustomStageFilter(fieldChoices[0]?.key ?? ''),
|
|
686
|
+
]), "data-testid": "stage-config-add-first-condition", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), t('dynamic.stage_config.add_condition', {
|
|
687
|
+
defaultValue: 'Agregar condición',
|
|
688
|
+
})] })), confirmingReset && (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-dashed bg-muted/30 p-3 text-xs", "data-testid": "stage-config-reset-confirm-panel", children: [_jsx("p", { className: "font-medium", children: t('dynamic.stage_config.reset_confirm_title', {
|
|
689
|
+
defaultValue: 'Se restablecerá la etapa a su versión original:',
|
|
690
|
+
}) }), _jsxs("ul", { className: "list-inside list-disc text-muted-foreground", children: [_jsx("li", { children: t('dynamic.stage_config.reset_confirm_label', {
|
|
691
|
+
defaultValue: 'Nombre: {{label}}',
|
|
692
|
+
label: original?.label ?? target.stageKey,
|
|
693
|
+
}) }), _jsx("li", { children: t('dynamic.stage_config.reset_confirm_color', {
|
|
694
|
+
defaultValue: 'Color: {{color}}',
|
|
695
|
+
color: original?.color ?? '—',
|
|
696
|
+
}) }), _jsx("li", { children: t('dynamic.stage_config.reset_confirm_conditions', {
|
|
697
|
+
defaultValue: 'Condiciones: {{list}}',
|
|
698
|
+
list: originalConditions,
|
|
699
|
+
}) })] }), _jsxs("div", { className: "flex justify-end gap-2 pt-1", children: [_jsx(Button, { variant: "outline", size: "sm", className: "h-7 text-xs", onClick: () => setConfirmingReset(false), disabled: resetting, children: t('dynamic.stage_config.cancel', {
|
|
700
|
+
defaultValue: 'Cancelar',
|
|
701
|
+
}) }), _jsx(Button, { variant: "destructive", size: "sm", className: "h-7 text-xs", onClick: () => void reset(), disabled: resetting, "data-testid": "stage-config-reset-confirm", children: t('dynamic.stage_config.reset_confirm', {
|
|
702
|
+
defaultValue: 'Restablecer al original',
|
|
703
|
+
}) })] })] }))] }), _jsxs(DialogFooter, { className: "sm:justify-between", children: [_jsxs("div", { children: [target.kind === 'declared' && target.overridden && (_jsxs(Button, { variant: "ghost", className: "gap-1.5 text-muted-foreground", onClick: () => setConfirmingReset(true), disabled: saving || resetting || confirmingReset, "data-testid": "stage-config-reset", children: [_jsx(RotateCcw, { className: "h-4 w-4" }), t('dynamic.stage_config.reset', {
|
|
704
|
+
defaultValue: 'Restablecer al original',
|
|
705
|
+
})] })), target.kind === 'custom' && target.customStage && (_jsxs(Button, { variant: "ghost", className: "gap-1.5 text-destructive hover:text-destructive", onClick: () => {
|
|
706
|
+
onOpenChange(false);
|
|
707
|
+
onDeleteCustom(target.customStage);
|
|
708
|
+
}, disabled: saving, "data-testid": "stage-config-delete", children: [_jsx(Trash2, { className: "h-4 w-4" }), t('dynamic.stage_config.delete', {
|
|
709
|
+
defaultValue: 'Eliminar etapa',
|
|
710
|
+
})] }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: saving || resetting, children: t('dynamic.stage_config.cancel', {
|
|
711
|
+
defaultValue: 'Cancelar',
|
|
712
|
+
}) }), _jsx(Button, { onClick: () => void submit(), disabled: !valid || saving || resetting, "data-testid": "stage-config-save", children: t('dynamic.stage_config.save', {
|
|
713
|
+
defaultValue: 'Guardar',
|
|
714
|
+
}) })] })] })] }) }));
|
|
715
|
+
}
|
|
481
716
|
/**
|
|
482
717
|
* A virtual lane defined by `filters`. It runs its OWN list query (the board's
|
|
483
718
|
* shared records don't include it), so it stays correct regardless of what the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type { ActionFieldDef, FieldOptionsConfig } from './types';
|
|
2
|
+
import type { ActionFieldDef, FieldOptionsConfig, OptionDef } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Apps register validator implementations by slug. The slug is the value
|
|
5
5
|
* `OrgConfig.validators[<key>]` returns for a $org.<key> reference.
|
|
@@ -80,6 +80,23 @@ export declare function getDependsOn(field: ActionFieldDef): string | undefined;
|
|
|
80
80
|
* field has no dependency or the depended-on value is empty/unset.
|
|
81
81
|
*/
|
|
82
82
|
export declare function resolveDependsValue(field: ActionFieldDef, formValues?: Record<string, any> | null, rowValues?: Record<string, any> | null): string;
|
|
83
|
+
/**
|
|
84
|
+
* Filters a STATIC enum's `options[]` by each option's `when` gate against the
|
|
85
|
+
* current form values. Pure — no React, no side effects.
|
|
86
|
+
*
|
|
87
|
+
* Rule per option:
|
|
88
|
+
* - No `when` → always included (retrocompat; existing enums untouched).
|
|
89
|
+
* - With `when`: the gating field is `when.field ?? dependsOn`. If neither is
|
|
90
|
+
* present the option is included (nothing to gate on). Otherwise the form's
|
|
91
|
+
* value for that field is compared AS STRING: included when (no `in`, or
|
|
92
|
+
* value ∈ `in`) AND (no `not_in`, or value ∉ `not_in`). Tolerates the
|
|
93
|
+
* snake_case `not_in` the kernel serves alongside camelCase `notIn`.
|
|
94
|
+
*
|
|
95
|
+
* `formValues` is the flat map of the surrounding form/row values the gating
|
|
96
|
+
* field is read from; `dependsOn` is the containing field's declared dependency
|
|
97
|
+
* used as the default gating field.
|
|
98
|
+
*/
|
|
99
|
+
export declare function applyOptionWhen(options: OptionDef[] | undefined, formValues: Record<string, any> | null | undefined, dependsOn?: string): OptionDef[];
|
|
83
100
|
/**
|
|
84
101
|
* Reads a field's enriched options-resolution config, tolerating the camelCase
|
|
85
102
|
* `optionsConfig` (authored SDK shape) and the snake_case `options_config` the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAmB,kBAAkB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"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"}
|