@asteby/metacore-runtime-react 23.8.0 → 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/CHANGELOG.md +33 -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-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +163 -12
- 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 +36 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- 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-kanban.tsx +210 -20
- package/src/index.ts +13 -0
- package/src/stage-overrides.ts +95 -0
- package/src/types.ts +28 -0
package/dist/dynamic-kanban.js
CHANGED
|
@@ -5,14 +5,15 @@ import { DndContext, DragOverlay, PointerSensor, useSensor, useSensors, useDragg
|
|
|
5
5
|
import { SortableContext, horizontalListSortingStrategy, useSortable, } from '@dnd-kit/sortable';
|
|
6
6
|
import { CSS } from '@dnd-kit/utilities';
|
|
7
7
|
import { arrayMove } from '@dnd-kit/sortable';
|
|
8
|
-
import { Calendar, CircleDot, GripVertical, Hash, ListFilter, MoreHorizontal, RotateCcw, Search, Tag, ToggleLeft, Type, X, } from 'lucide-react';
|
|
8
|
+
import { Calendar, CircleDot, GripVertical, Hash, ListFilter, MoreHorizontal, RotateCcw, Search, Settings2, Tag, ToggleLeft, Type, X, } from 'lucide-react';
|
|
9
9
|
import { toast } from 'sonner';
|
|
10
10
|
import { Badge, Button, Card, CardContent, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Input, Popover, PopoverContent, PopoverTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger, Skeleton, } from '@asteby/metacore-ui/primitives';
|
|
11
11
|
import { ColumnFilterControl, FilterValueCombobox } from '@asteby/metacore-ui/data-table';
|
|
12
12
|
import { generateBadgeStyles, optionColor } from '@asteby/metacore-ui/lib';
|
|
13
13
|
import { useApi } from './api-context';
|
|
14
14
|
import { useStageAutomations, StageAutomationsButton, } from './stage-automations';
|
|
15
|
-
import { useCustomStages, splitCustomStages, mergeLaneStages, resolveSmartLanes,
|
|
15
|
+
import { useCustomStages, splitCustomStages, mergeLaneStages, resolveSmartLanes, cardMatchesStageFilters, smartLaneParams, AddStageColumn, CustomStageDialog, CustomStageDeleteDialog, StageConfigDialog, SmartLane, } from './custom-stages';
|
|
16
|
+
import { useStageOverrides } from './stage-overrides';
|
|
16
17
|
import { useDynamicFilters } from './use-dynamic-filters';
|
|
17
18
|
import { FilterChipsRow, summarizeFilterValues, translateOptionLabels, } from './filter-chips';
|
|
18
19
|
import { dedupeById, useInfiniteScrollSentinel } from './use-infinite-scroll';
|
|
@@ -249,10 +250,16 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, on
|
|
|
249
250
|
// the host has no `/custom-stages` endpoint — the "+ Agregar etapa" column
|
|
250
251
|
// and lane menus simply don't render.
|
|
251
252
|
const customStages = useCustomStages(model);
|
|
253
|
+
// Per-org overrides for DECLARED lanes (rename/recolor/conditions). Degrades
|
|
254
|
+
// to no-op when the host has no `/stage-overrides` endpoint — the ⚙ gear then
|
|
255
|
+
// hides on declared lanes (custom lanes keep it via /custom-stages).
|
|
256
|
+
const stageOverrides = useStageOverrides(model);
|
|
252
257
|
// Dialog state: create/edit a stage, and the delete confirmation.
|
|
253
258
|
const [stageDialogOpen, setStageDialogOpen] = useState(false);
|
|
254
259
|
const [editingStage, setEditingStage] = useState(null);
|
|
255
260
|
const [deletingStage, setDeletingStage] = useState(null);
|
|
261
|
+
// The gear (⚙) "Configurar etapa" dialog — one UI for declared + custom lanes.
|
|
262
|
+
const [configTarget, setConfigTarget] = useState(null);
|
|
256
263
|
const openCreateStage = useCallback(() => {
|
|
257
264
|
setEditingStage(null);
|
|
258
265
|
setStageDialogOpen(true);
|
|
@@ -372,6 +379,7 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, on
|
|
|
372
379
|
const r = (await api.get(endpoint || `/data/${model}`, {
|
|
373
380
|
params: {
|
|
374
381
|
...filterParams,
|
|
382
|
+
...smartLaneParams(stage.filters),
|
|
375
383
|
page: 1,
|
|
376
384
|
per_page: 1,
|
|
377
385
|
[`f_${gb}`]: stage.key,
|
|
@@ -406,6 +414,24 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, on
|
|
|
406
414
|
// the shared records. Scoped by `f_<group_by>=<stage>` on top of the active
|
|
407
415
|
// filterParams (the stage scope wins over any global group_by filter).
|
|
408
416
|
const groupByKey = metadata?.group_by || '';
|
|
417
|
+
// Extra per-lane conditions (stage overrides) keyed by stage. A real lane
|
|
418
|
+
// that carries these queries its data — and counts its header — with the
|
|
419
|
+
// stage scope PLUS these filters (serialized like a smart lane's), so the
|
|
420
|
+
// top-up + eager-total requests below layer them on. Sourced from
|
|
421
|
+
// `metadata.stages` (the kernel applies declared + custom-real overrides).
|
|
422
|
+
const stageExtraFilters = useMemo(() => {
|
|
423
|
+
const m = new Map();
|
|
424
|
+
for (const s of metadata?.stages ?? []) {
|
|
425
|
+
if (s.filters && s.filters.length > 0) {
|
|
426
|
+
m.set(s.key, s.filters.map((f) => ({
|
|
427
|
+
field: f.field,
|
|
428
|
+
op: f.op,
|
|
429
|
+
value: f.value,
|
|
430
|
+
})));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return m;
|
|
434
|
+
}, [metadata?.stages]);
|
|
409
435
|
const loadMoreLane = useCallback(async (stageKey) => {
|
|
410
436
|
if (!metadata || !groupByKey)
|
|
411
437
|
return;
|
|
@@ -426,6 +452,7 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, on
|
|
|
426
452
|
const res = (await api.get(endpoint || `/data/${model}`, {
|
|
427
453
|
params: {
|
|
428
454
|
...filterParams,
|
|
455
|
+
...smartLaneParams(stageExtraFilters.get(stageKey)),
|
|
429
456
|
page: nextPage,
|
|
430
457
|
per_page: lanePageSize,
|
|
431
458
|
[`f_${groupByKey}`]: stageKey,
|
|
@@ -457,7 +484,7 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, on
|
|
|
457
484
|
},
|
|
458
485
|
}));
|
|
459
486
|
}
|
|
460
|
-
}, [api, endpoint, model, metadata, groupByKey, filterParams, lanePageSize, lanePagination]);
|
|
487
|
+
}, [api, endpoint, model, metadata, groupByKey, filterParams, lanePageSize, lanePagination, stageExtraFilters]);
|
|
461
488
|
// Refetch when metadata resolves, on an explicit refresh, or when the
|
|
462
489
|
// filters change. `fetchData` is stable while `filterParams` is unchanged
|
|
463
490
|
// (both memoized), so this only re-runs on real input changes. Debounced so
|
|
@@ -764,14 +791,82 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, on
|
|
|
764
791
|
const droppableAllowedFor = (stageKey) => !activeId ||
|
|
765
792
|
stageKey === activeStage ||
|
|
766
793
|
isTransitionAllowed(transitions, activeStage, stageKey);
|
|
794
|
+
// Opens the gear (⚙) "Configurar etapa" dialog for a lane, routing to the
|
|
795
|
+
// right backend by kind: a custom real stage edits through /custom-stages, a
|
|
796
|
+
// declared stage through /stage-overrides. Seeds the current label/color and
|
|
797
|
+
// any extra conditions the lane already carries.
|
|
798
|
+
const openConfigStage = (stage) => {
|
|
799
|
+
const custom = customStages.available
|
|
800
|
+
? customByKey.get(stage.key)
|
|
801
|
+
: undefined;
|
|
802
|
+
const raw = stageExtraFilters.get(stage.key) ??
|
|
803
|
+
custom?.filters ??
|
|
804
|
+
[];
|
|
805
|
+
const filters = raw.map((f) => ({
|
|
806
|
+
field: f.field,
|
|
807
|
+
op: f.op,
|
|
808
|
+
value: f.value,
|
|
809
|
+
}));
|
|
810
|
+
if (custom) {
|
|
811
|
+
setConfigTarget({
|
|
812
|
+
kind: 'custom',
|
|
813
|
+
stageKey: stage.key,
|
|
814
|
+
id: custom.id,
|
|
815
|
+
label: custom.label,
|
|
816
|
+
color: custom.color,
|
|
817
|
+
filters,
|
|
818
|
+
customStage: custom,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
const original = stage.original
|
|
823
|
+
? {
|
|
824
|
+
label: stage.original.label,
|
|
825
|
+
color: stage.original.color,
|
|
826
|
+
filters: stage.original.filters?.map((f) => ({
|
|
827
|
+
field: f.field,
|
|
828
|
+
op: f.op,
|
|
829
|
+
value: f.value,
|
|
830
|
+
})),
|
|
831
|
+
}
|
|
832
|
+
: undefined;
|
|
833
|
+
setConfigTarget({
|
|
834
|
+
kind: 'declared',
|
|
835
|
+
stageKey: stage.key,
|
|
836
|
+
label: t(stage.label, { defaultValue: stage.label }),
|
|
837
|
+
color: stage.color ?? 'slate',
|
|
838
|
+
filters,
|
|
839
|
+
overridden: !!stage.overridden,
|
|
840
|
+
isFinal: stage.is_final,
|
|
841
|
+
original,
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
// Whether a lane offers the gear: custom real stages always (their CRUD is
|
|
846
|
+
// wired); declared lanes only when the host wired /stage-overrides. Never on
|
|
847
|
+
// the synthetic "Sin etapa" lane.
|
|
848
|
+
const isConfigurable = (stage) => {
|
|
849
|
+
if (stage.key === UNASSIGNED_LANE)
|
|
850
|
+
return false;
|
|
851
|
+
return customByKey.has(stage.key)
|
|
852
|
+
? customStages.available
|
|
853
|
+
: stageOverrides.available;
|
|
854
|
+
};
|
|
767
855
|
// Builds every `KanbanLane` prop (minus the dnd wiring) for one stage —
|
|
768
856
|
// shared by the sortable real stages and the plain-droppable unassigned lane.
|
|
769
857
|
const buildLaneProps = (stage) => {
|
|
770
858
|
const allCards = grouped.get(stage.key) ?? [];
|
|
859
|
+
// Extra lane conditions (stage override): the server already scopes this
|
|
860
|
+
// lane's top-up + total queries by them, but the shared INITIAL board page
|
|
861
|
+
// is unscoped, so narrow those cards client-side too (belt-and-suspenders).
|
|
862
|
+
const extraFilters = stageExtraFilters.get(stage.key) ?? [];
|
|
771
863
|
// Per-lane client-side narrowing (instant, scoped to this stage). The
|
|
772
864
|
// funnel (field/value) and the lane search (query) are AND-combined.
|
|
773
865
|
const laneFilter = laneFilters[stage.key];
|
|
774
866
|
let cards = allCards;
|
|
867
|
+
if (extraFilters.length > 0) {
|
|
868
|
+
cards = cards.filter((c) => cardMatchesStageFilters(c, extraFilters));
|
|
869
|
+
}
|
|
775
870
|
if (laneFilter?.field) {
|
|
776
871
|
cards = cards.filter((c) => cardMatchesLaneFunnel(c, laneFilter));
|
|
777
872
|
}
|
|
@@ -806,11 +901,9 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, on
|
|
|
806
901
|
onAutomationCreate: automations.create,
|
|
807
902
|
onAutomationUpdate: automations.update,
|
|
808
903
|
onAutomationRemove: automations.remove,
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
onEditStage: openEditStage,
|
|
813
|
-
onDeleteStage: openDeleteStage,
|
|
904
|
+
extraFilters,
|
|
905
|
+
configurable: isConfigurable(stage),
|
|
906
|
+
onConfigure: () => openConfigStage(stage),
|
|
814
907
|
children: loadingData && cards.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-20 w-full" }), _jsx(Skeleton, { className: "h-20 w-full" })] })) : cards.length === 0 ? (_jsx("p", { className: "px-1 py-6 text-center text-xs text-muted-foreground", children: t('kanban.emptyLane', { defaultValue: 'Sin tarjetas' }) })) : (cards.map((card) => (_jsx(KanbanCard, { card: card, titleCol: titleCol, fieldCols: fieldCols, actions: rowActions, locale: i18n.language, timeZone: timeZone, currency: currency, onClick: onCardClick, onAction: handleInternalAction }, String(card.id))))),
|
|
815
908
|
};
|
|
816
909
|
};
|
|
@@ -845,7 +938,40 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, on
|
|
|
845
938
|
}, stage: deletingStage, reassignTargets: stages.map((s) => ({
|
|
846
939
|
key: s.key,
|
|
847
940
|
label: s.label,
|
|
848
|
-
})), onConfirm: (s, reassignTo) => customStages.remove(s.id, reassignTo) })] }))
|
|
941
|
+
})), onConfirm: (s, reassignTo) => customStages.remove(s.id, reassignTo) })] })), _jsx(StageConfigDialog, { open: !!configTarget, onOpenChange: (o) => {
|
|
942
|
+
if (!o)
|
|
943
|
+
setConfigTarget(null);
|
|
944
|
+
}, columns: metadata?.columns ?? [], target: configTarget, onSaveOverride: async (stageKey, patch) => {
|
|
945
|
+
await stageOverrides.save(stageKey, patch);
|
|
946
|
+
// The override changes the served label/color/filters — refetch
|
|
947
|
+
// metadata so the board repaints, and reload the cards.
|
|
948
|
+
try {
|
|
949
|
+
const res = await api.get(`/metadata/table/${model}`);
|
|
950
|
+
const body = res.data;
|
|
951
|
+
if (body.success) {
|
|
952
|
+
setMetadata(body.data);
|
|
953
|
+
cacheMetadata(model, body.data);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
catch {
|
|
957
|
+
/* keep the current metadata on a refetch miss */
|
|
958
|
+
}
|
|
959
|
+
void fetchData();
|
|
960
|
+
}, onResetOverride: async (stageKey) => {
|
|
961
|
+
await stageOverrides.reset(stageKey);
|
|
962
|
+
try {
|
|
963
|
+
const res = await api.get(`/metadata/table/${model}`);
|
|
964
|
+
const body = res.data;
|
|
965
|
+
if (body.success) {
|
|
966
|
+
setMetadata(body.data);
|
|
967
|
+
cacheMetadata(model, body.data);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
catch {
|
|
971
|
+
/* keep the current metadata on a refetch miss */
|
|
972
|
+
}
|
|
973
|
+
void fetchData();
|
|
974
|
+
}, onUpdateCustom: customStages.update, onDeleteCustom: openDeleteStage })] }));
|
|
849
975
|
}
|
|
850
976
|
/**
|
|
851
977
|
* A per-data-type glyph for the Filtros panel rows (and their popover header):
|
|
@@ -933,7 +1059,7 @@ function SortableSmartLane({ droppableDisabled, smartProps, }) {
|
|
|
933
1059
|
};
|
|
934
1060
|
return _jsx(SmartLane, { ...smartProps, dnd: dnd });
|
|
935
1061
|
}
|
|
936
|
-
function KanbanLane({ stage, count, totalCount, serverTotal, hasMore, loadingMore, onLoadMore, filterFields, laneFilter, onFunnelChange, onQueryChange, isDark, dimmed, dnd, model, columns, automationsAvailable, automationRules, onAutomationCreate, onAutomationUpdate, onAutomationRemove,
|
|
1062
|
+
function KanbanLane({ stage, count, totalCount, serverTotal, hasMore, loadingMore, onLoadMore, filterFields, laneFilter, onFunnelChange, onQueryChange, isDark, dimmed, dnd, model, columns, automationsAvailable, automationRules, onAutomationCreate, onAutomationUpdate, onAutomationRemove, extraFilters, configurable, onConfigure, children, }) {
|
|
937
1063
|
const { t } = useTranslation();
|
|
938
1064
|
// Infinite scroll: the sentinel lives at the bottom of the lane's own scroll
|
|
939
1065
|
// container; a load in flight or an exhausted stage disables it.
|
|
@@ -972,6 +1098,23 @@ function KanbanLane({ stage, count, totalCount, serverTotal, hasMore, loadingMor
|
|
|
972
1098
|
text: laneFilter.text,
|
|
973
1099
|
}
|
|
974
1100
|
: undefined;
|
|
1101
|
+
// Stage-override conditions active on this lane → a small filter dot in the
|
|
1102
|
+
// header, its `title` listing the conditions (e.g. "priority es igual high").
|
|
1103
|
+
const hasConditions = extraFilters.length > 0;
|
|
1104
|
+
const conditionsSummary = extraFilters
|
|
1105
|
+
.map((f) => {
|
|
1106
|
+
const opLabel = t(`dynamic.custom_stages.op.${f.op}`, {
|
|
1107
|
+
defaultValue: f.op === 'eq'
|
|
1108
|
+
? 'es igual'
|
|
1109
|
+
: f.op === 'neq'
|
|
1110
|
+
? 'distinto'
|
|
1111
|
+
: f.op === 'contains'
|
|
1112
|
+
? 'contiene'
|
|
1113
|
+
: 'en lista',
|
|
1114
|
+
});
|
|
1115
|
+
return `${f.field} ${opLabel} ${f.value}`;
|
|
1116
|
+
})
|
|
1117
|
+
.join(' · ');
|
|
975
1118
|
return (_jsxs("div", { ref: dnd.setNodeRef,
|
|
976
1119
|
// Fluid width: lanes grow (flex-1) to fill the board when they all
|
|
977
1120
|
// fit, capped at max-w so a couple of lanes don't stretch absurdly
|
|
@@ -982,9 +1125,17 @@ function KanbanLane({ stage, count, totalCount, serverTotal, hasMore, loadingMor
|
|
|
982
1125
|
outline: dnd.isOver ? '2px solid var(--ring, #3b82f6)' : 'none',
|
|
983
1126
|
outlineOffset: 2,
|
|
984
1127
|
...dnd.style,
|
|
985
|
-
}, "data-stage": stage.key, children: [_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2.5", children: [_jsxs("div", { ref: dnd.draggable ? dnd.handleRef : undefined, ...(dnd.draggable ? dnd.handleProps : {}), className: `flex min-w-0 items-center gap-1.5 ${dnd.draggable ? 'cursor-grab active:cursor-grabbing' : ''}`, children: [dnd.draggable && (_jsx(GripVertical, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground/40 opacity-0 transition-opacity group-hover/lane:opacity-70", "aria-hidden": true })), _jsx(Badge, { variant: "outline", className: "border-0 text-xs font-semibold", style: headerStyle, children: t(stage.label, { defaultValue: stage.label }) }), _jsx("span", { className: "text-xs font-medium tabular-nums text-muted-foreground", children: formatLaneCount(count, totalCount, serverTotal, laneActive) })
|
|
1128
|
+
}, "data-stage": stage.key, children: [_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2.5", children: [_jsxs("div", { ref: dnd.draggable ? dnd.handleRef : undefined, ...(dnd.draggable ? dnd.handleProps : {}), className: `flex min-w-0 items-center gap-1.5 ${dnd.draggable ? 'cursor-grab active:cursor-grabbing' : ''}`, children: [dnd.draggable && (_jsx(GripVertical, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground/40 opacity-0 transition-opacity group-hover/lane:opacity-70", "aria-hidden": true })), _jsx(Badge, { variant: "outline", className: "border-0 text-xs font-semibold", style: headerStyle, children: t(stage.label, { defaultValue: stage.label }) }), _jsx("span", { className: "text-xs font-medium tabular-nums text-muted-foreground", children: formatLaneCount(count, totalCount, serverTotal, laneActive) }), hasConditions && (_jsx("span", { className: "flex items-center text-primary", title: t('dynamic.stage_config.conditions_active', {
|
|
1129
|
+
defaultValue: 'Condiciones: {{list}}',
|
|
1130
|
+
list: conditionsSummary,
|
|
1131
|
+
}), "data-testid": `lane-conditions-${stage.key}`, "aria-label": t('dynamic.stage_config.conditions_active', {
|
|
1132
|
+
defaultValue: 'Condiciones: {{list}}',
|
|
1133
|
+
list: conditionsSummary,
|
|
1134
|
+
}), children: _jsx(ListFilter, { className: "h-3 w-3" }) }))] }), _jsxs("div", { className: "flex items-center gap-0.5", children: [_jsxs("button", { type: "button", onClick: () => setSearchOpen((o) => !o), className: `relative flex size-6 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-foreground ${queryActive ? 'text-primary' : 'text-muted-foreground'}`, "aria-label": t('kanban.searchLane', {
|
|
986
1135
|
defaultValue: 'Buscar en la columna',
|
|
987
|
-
}), children: [_jsx(Search, { className: "h-3.5 w-3.5" }), queryActive && (_jsx("span", { className: "absolute -right-0.5 -top-0.5 size-1.5 rounded-full bg-primary" }))] }), _jsx(LaneFilterButton, { fields: filterFields, value: funnelValue, onChange: onFunnelChange }), automationsAvailable && (_jsx(StageAutomationsButton, { model: model, stageKey: stage.key, stageLabel: stage.label, columns: columns, rules: automationRules, onCreate: onAutomationCreate, onUpdate: onAutomationUpdate, onRemove: onAutomationRemove })),
|
|
1136
|
+
}), children: [_jsx(Search, { className: "h-3.5 w-3.5" }), queryActive && (_jsx("span", { className: "absolute -right-0.5 -top-0.5 size-1.5 rounded-full bg-primary" }))] }), _jsx(LaneFilterButton, { fields: filterFields, value: funnelValue, onChange: onFunnelChange }), automationsAvailable && (_jsx(StageAutomationsButton, { model: model, stageKey: stage.key, stageLabel: stage.label, columns: columns, rules: automationRules, onCreate: onAutomationCreate, onUpdate: onAutomationUpdate, onRemove: onAutomationRemove })), configurable && (_jsx("button", { type: "button", onClick: onConfigure, className: `relative flex size-6 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-foreground ${hasConditions ? 'text-primary' : 'text-muted-foreground'}`, "aria-label": t('dynamic.stage_config.open', {
|
|
1137
|
+
defaultValue: 'Configurar etapa',
|
|
1138
|
+
}), "data-testid": `lane-config-${stage.key}`, children: _jsx(Settings2, { className: "h-3.5 w-3.5" }) }))] })] }), searchOpen && (_jsx("div", { className: "px-3 pb-1.5", children: _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { ref: searchRef, value: laneFilter?.query ?? '', onChange: (e) => onQueryChange(e.target.value), onKeyDown: (e) => {
|
|
988
1139
|
if (e.key === 'Escape') {
|
|
989
1140
|
onQueryChange('');
|
|
990
1141
|
setSearchOpen(false);
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,9 @@ export * from './options-context';
|
|
|
3
3
|
export * from './dynamic-table';
|
|
4
4
|
export { DynamicKanban, type DynamicKanbanProps, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
|
|
5
5
|
export { useStageAutomations, StageAutomationsButton, isTagColumn, automationFieldOptions, groupAutomationsByStage, activeAutomationCount, type StageAutomation, type StageAutomationAction, type StageAutomationActionType, type NewStageAutomation, type UseStageAutomationsResult, type StageAutomationsButtonProps, } from './stage-automations';
|
|
6
|
-
export { useCustomStages, splitCustomStages, mergeLaneStages, resolveSmartLanes, smartLaneParams, customStageFilterFields, isCustomStageDraftValid, slugifyStageKey, emptyCustomStageFilter, AddStageColumn, CustomStageLaneMenu, CustomStageDialog, CustomStageDeleteDialog, SmartLane, CUSTOM_STAGE_COLORS, CUSTOM_STAGE_FILTER_OPS, type CustomStage, type NewCustomStage, type CustomStageType, type CustomStageFilter, type CustomStageFilterOp, type UseCustomStagesResult, } from './custom-stages';
|
|
6
|
+
export { useCustomStages, splitCustomStages, mergeLaneStages, resolveSmartLanes, smartLaneParams, cardMatchesStageFilters, customStageFilterFields, isCustomStageDraftValid, slugifyStageKey, emptyCustomStageFilter, AddStageColumn, CustomStageLaneMenu, CustomStageDialog, CustomStageDeleteDialog, StageConditionBuilder, StageConfigDialog, stageFilterOpSymbol, SmartLane, CUSTOM_STAGE_COLORS, CUSTOM_STAGE_FILTER_OPS, type CustomStage, type NewCustomStage, type CustomStageType, type CustomStageFilter, type CustomStageFilterOp, type UseCustomStagesResult, type StageConfigTarget, type StageConfigKind, type StageConditionBuilderProps, type StageConfigDialogProps, } from './custom-stages';
|
|
7
7
|
export { useStageLayout, type StageLayout, type UseStageLayoutResult, } from './stage-layout';
|
|
8
|
+
export { useStageOverrides, type StageOverridePatch, type UseStageOverridesResult, } from './stage-overrides';
|
|
8
9
|
export { DynamicView, resolveViewRenderer, readViewFromSearch, resolveActiveView, type DynamicViewProps, } from './dynamic-view';
|
|
9
10
|
export { useDynamicFilters, type UseDynamicFiltersOptions, type UseDynamicFiltersResult, } from './use-dynamic-filters';
|
|
10
11
|
export * from './dynamic-form';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,EACvB,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,WAAW,EACX,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,GACnC,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,SAAS,EACT,mBAAmB,EACnB,uBAAuB,EACvB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -8,8 +8,9 @@ export * from './options-context';
|
|
|
8
8
|
export * from './dynamic-table';
|
|
9
9
|
export { DynamicKanban, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
|
|
10
10
|
export { useStageAutomations, StageAutomationsButton, isTagColumn, automationFieldOptions, groupAutomationsByStage, activeAutomationCount, } from './stage-automations';
|
|
11
|
-
export { useCustomStages, splitCustomStages, mergeLaneStages, resolveSmartLanes, smartLaneParams, customStageFilterFields, isCustomStageDraftValid, slugifyStageKey, emptyCustomStageFilter, AddStageColumn, CustomStageLaneMenu, CustomStageDialog, CustomStageDeleteDialog, SmartLane, CUSTOM_STAGE_COLORS, CUSTOM_STAGE_FILTER_OPS, } from './custom-stages';
|
|
11
|
+
export { useCustomStages, splitCustomStages, mergeLaneStages, resolveSmartLanes, smartLaneParams, cardMatchesStageFilters, customStageFilterFields, isCustomStageDraftValid, slugifyStageKey, emptyCustomStageFilter, AddStageColumn, CustomStageLaneMenu, CustomStageDialog, CustomStageDeleteDialog, StageConditionBuilder, StageConfigDialog, stageFilterOpSymbol, SmartLane, CUSTOM_STAGE_COLORS, CUSTOM_STAGE_FILTER_OPS, } from './custom-stages';
|
|
12
12
|
export { useStageLayout, } from './stage-layout';
|
|
13
|
+
export { useStageOverrides, } from './stage-overrides';
|
|
13
14
|
export { DynamicView, resolveViewRenderer, readViewFromSearch, resolveActiveView, } from './dynamic-view';
|
|
14
15
|
export { useDynamicFilters, } from './use-dynamic-filters';
|
|
15
16
|
export * from './dynamic-form';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CustomStageFilter } from './custom-stages';
|
|
2
|
+
/** Fields a stage override can carry. All optional — send only what changed. */
|
|
3
|
+
export interface StageOverridePatch {
|
|
4
|
+
label?: string;
|
|
5
|
+
color?: string;
|
|
6
|
+
/** Extra lane conditions (same shape/ops as smart-lane filters). */
|
|
7
|
+
filters?: CustomStageFilter[];
|
|
8
|
+
}
|
|
9
|
+
export interface UseStageOverridesResult {
|
|
10
|
+
/** True only after a successful GET — gates the per-lane gear on declared stages. */
|
|
11
|
+
available: boolean;
|
|
12
|
+
/** Upsert a declared lane's override (label/color/conditions). Throws on failure. */
|
|
13
|
+
save: (stageKey: string, patch: StageOverridePatch) => Promise<void>;
|
|
14
|
+
/** Reset a declared lane to its manifest default (drops the stored override). */
|
|
15
|
+
reset: (stageKey: string) => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Learns whether the host wired `/stage-overrides` (so the gear can appear on
|
|
19
|
+
* declared lanes) and exposes save/reset. A missing endpoint degrades to
|
|
20
|
+
* `available: false`; a real save/reset failure re-throws so the caller can
|
|
21
|
+
* surface an error + revert. The overridden VALUES themselves are read off
|
|
22
|
+
* `metadata.stages` (the kernel applies them), so this hook doesn't cache a list.
|
|
23
|
+
*/
|
|
24
|
+
export declare function useStageOverrides(model: string): UseStageOverridesResult;
|
|
25
|
+
//# sourceMappingURL=stage-overrides.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage-overrides.d.ts","sourceRoot":"","sources":["../src/stage-overrides.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExD,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oEAAoE;IACpE,OAAO,CAAC,EAAE,iBAAiB,EAAE,CAAA;CAChC;AAED,MAAM,WAAW,uBAAuB;IACpC,qFAAqF;IACrF,SAAS,EAAE,OAAO,CAAA;IAClB,qFAAqF;IACrF,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpE,iFAAiF;IACjF,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7C;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB,CA8CxE"}
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
/**
|
|
24
|
+
* Learns whether the host wired `/stage-overrides` (so the gear can appear on
|
|
25
|
+
* declared lanes) and exposes save/reset. A missing endpoint degrades to
|
|
26
|
+
* `available: false`; a real save/reset failure re-throws so the caller can
|
|
27
|
+
* surface an error + revert. The overridden VALUES themselves are read off
|
|
28
|
+
* `metadata.stages` (the kernel applies them), so this hook doesn't cache a list.
|
|
29
|
+
*/
|
|
30
|
+
export function useStageOverrides(model) {
|
|
31
|
+
const api = useApi();
|
|
32
|
+
const [available, setAvailable] = useState(false);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
let cancelled = false;
|
|
35
|
+
api
|
|
36
|
+
.get(`/stage-overrides?model=${encodeURIComponent(model)}`)
|
|
37
|
+
.then(() => {
|
|
38
|
+
if (!cancelled)
|
|
39
|
+
setAvailable(true);
|
|
40
|
+
})
|
|
41
|
+
.catch(() => {
|
|
42
|
+
// Endpoint absent or errored — leave the gear off on declared lanes.
|
|
43
|
+
if (!cancelled)
|
|
44
|
+
setAvailable(false);
|
|
45
|
+
});
|
|
46
|
+
return () => {
|
|
47
|
+
cancelled = true;
|
|
48
|
+
};
|
|
49
|
+
}, [api, model]);
|
|
50
|
+
const save = useCallback(async (stageKey, patch) => {
|
|
51
|
+
const res = (await api.put('/stage-overrides', {
|
|
52
|
+
model,
|
|
53
|
+
stage_key: stageKey,
|
|
54
|
+
...patch,
|
|
55
|
+
}));
|
|
56
|
+
if (res?.data && res.data.success === false) {
|
|
57
|
+
throw new Error(res.data.message || 'stage_override_save_failed');
|
|
58
|
+
}
|
|
59
|
+
}, [api, model]);
|
|
60
|
+
const reset = useCallback(async (stageKey) => {
|
|
61
|
+
await api.delete(`/stage-overrides?model=${encodeURIComponent(model)}&stage_key=${encodeURIComponent(stageKey)}`);
|
|
62
|
+
}, [api, model]);
|
|
63
|
+
return { available, save, reset };
|
|
64
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -73,6 +73,42 @@ export interface StageMeta {
|
|
|
73
73
|
* Editar/Eliminar menu on it.
|
|
74
74
|
*/
|
|
75
75
|
custom?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* True when a per-org stage override (label/color/conditions) has been
|
|
78
|
+
* applied to this DECLARED lane (ops stage-overrides). The kernel serves the
|
|
79
|
+
* lane already carrying the overridden label/color; this flag only drives the
|
|
80
|
+
* "Restablecer etapa" affordance in the config dialog. Absent on hosts without
|
|
81
|
+
* stage overrides — purely additive.
|
|
82
|
+
*/
|
|
83
|
+
overridden?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Extra per-lane conditions layered on top of the stage's own `group_by`
|
|
86
|
+
* scope (ops stage-overrides). When present the lane queries its data — and
|
|
87
|
+
* counts its header — with the stage filter PLUS these conditions (serialized
|
|
88
|
+
* the same way as smart-lane filters). The lane stays a normal drop target;
|
|
89
|
+
* dropping a card only sets the stage value. Absent → the lane behaves as a
|
|
90
|
+
* plain declared stage. Snake_case ops as the kernel serves them.
|
|
91
|
+
*/
|
|
92
|
+
filters?: {
|
|
93
|
+
field: string;
|
|
94
|
+
op: string;
|
|
95
|
+
value: string;
|
|
96
|
+
}[];
|
|
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?: {
|
|
107
|
+
field: string;
|
|
108
|
+
op: string;
|
|
109
|
+
value: string;
|
|
110
|
+
}[];
|
|
111
|
+
};
|
|
76
112
|
}
|
|
77
113
|
/**
|
|
78
114
|
* A virtual "smart" lane (ops #704) served in `TableMetadata.smart_lanes`. It's
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,YAAY,EAAE,CAAA;IAC1B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,aAAa,EAAE,CAAA;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,eAAe,EAAE,CAAA;CAClC;AAED;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,YAAY,EAAE,CAAA;IAC1B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,aAAa,EAAE,CAAA;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,eAAe,EAAE,CAAA;CAClC;AAED;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACxD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,OAAO,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAC3D,CAAA;CACJ;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC1D;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACb;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IACzB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,IAAI,EAAE,aAAa,GAAG,cAAc,CAAA;IACpC;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,IAAI,EAAE,QAAQ,GAAG,gBAAgB,GAAG,SAAS,GAAG,YAAY,GAAG,cAAc,GAAG,MAAM,CAAA;IACtF,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACrF,cAAc,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEjF,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EACE,MAAM,GACN,QAAQ,GACR,MAAM,GAGN,UAAU,GACV,WAAW,GACX,aAAa,GACb,QAAQ,GACR,QAAQ,GACR,qBAAqB,GACrB,QAAQ,GACR,SAAS,GACT,OAAO,GACP,eAAe,GACf,OAAO,GAEP,KAAK,GACL,MAAM,GACN,OAAO,GACP,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,QAAQ,GACR,MAAM,GACN,OAAO,GACP,MAAM,GACN,eAAe,GACf,SAAS,GACT,MAAM,GAKN,UAAU,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;IACnB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,gBAAgB,GAAG,SAAS,GAAG,YAAY,GAAG,cAAc,GAAG,MAAM,CAAA;IAC7F,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACjC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3E;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;IAC9B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,eAAe,EAAE,CAAA;CAClC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,QAAQ,CAAA;IACxC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAC3B;AASD,MAAM,WAAW,eAAe;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;CAClB;AAID,MAAM,MAAM,WAAW,GACjB,MAAM,GACN,UAAU,GACV,UAAU,GACV,OAAO,GACP,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,gBAAgB,GAChB,QAAQ,GACR,QAAQ,CAAA;AAEd,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC5C,YAAY,CAAC,EAAE,GAAG,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC7B;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAA;IAClC,0EAA0E;IAC1E,cAAc,CAAC,EAAE,kBAAkB,CAAA;IACnC;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,cAAc,EAAE,CAAA;IAC7B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,eAAe,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IAC/B,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;IACpD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;CACzC;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,CAAC,CAAA;IACP,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CAChB;AAKD,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;CACzC"}
|
package/package.json
CHANGED
|
@@ -495,6 +495,81 @@ describe('DynamicKanban filter bar', () => {
|
|
|
495
495
|
})
|
|
496
496
|
})
|
|
497
497
|
|
|
498
|
+
// ---------------------------------------------------------------------------
|
|
499
|
+
// Stage config gear (⚙) + stage-override conditions on a declared lane
|
|
500
|
+
// ---------------------------------------------------------------------------
|
|
501
|
+
|
|
502
|
+
describe('DynamicKanban stage config', () => {
|
|
503
|
+
it('renders the ⚙ gear on declared lanes when /stage-overrides is wired', async () => {
|
|
504
|
+
useMetadataCache.getState().setMetadata('issue', meta())
|
|
505
|
+
render(
|
|
506
|
+
<ApiProvider client={fakeApi()}>
|
|
507
|
+
<DynamicKanban model="issue" />
|
|
508
|
+
</ApiProvider>,
|
|
509
|
+
)
|
|
510
|
+
// fakeApi answers every GET ok, so /stage-overrides resolves → gear shows.
|
|
511
|
+
expect(await screen.findByTestId('lane-config-backlog')).toBeTruthy()
|
|
512
|
+
expect(screen.getByTestId('lane-config-done')).toBeTruthy()
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
it('hides the gear on declared lanes when /stage-overrides 404s', async () => {
|
|
516
|
+
useMetadataCache.getState().setMetadata('issue', meta())
|
|
517
|
+
const get = vi.fn(async (url: string) => {
|
|
518
|
+
if (url.startsWith('/metadata/table/'))
|
|
519
|
+
return { data: { success: true, data: meta() } }
|
|
520
|
+
if (url.startsWith('/stage-overrides'))
|
|
521
|
+
return Promise.reject(new Error('404'))
|
|
522
|
+
return { data: { success: true, data: CARDS } }
|
|
523
|
+
})
|
|
524
|
+
render(
|
|
525
|
+
<ApiProvider client={fakeApi({ get })}>
|
|
526
|
+
<DynamicKanban model="issue" />
|
|
527
|
+
</ApiProvider>,
|
|
528
|
+
)
|
|
529
|
+
await screen.findByText('Backlog')
|
|
530
|
+
await waitFor(() =>
|
|
531
|
+
expect(screen.queryByTestId('lane-config-backlog')).toBeNull(),
|
|
532
|
+
)
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
it('queries a lane that carries override filters with the extra f_ params + shows the conditions indicator', async () => {
|
|
536
|
+
// A declared "backlog" lane with an extra condition (priority = high).
|
|
537
|
+
const withFilters = meta({
|
|
538
|
+
stages: [
|
|
539
|
+
{ key: 'backlog', label: 'Backlog', color: 'slate', order: 0, overridden: true, filters: [{ field: 'priority', op: 'eq', value: 'high' }] },
|
|
540
|
+
{ key: 'done', label: 'Done', color: 'green', order: 1 },
|
|
541
|
+
],
|
|
542
|
+
})
|
|
543
|
+
useMetadataCache.getState().setMetadata('issue', withFilters)
|
|
544
|
+
const get = vi.fn(async (url: string) => {
|
|
545
|
+
if (url.startsWith('/metadata/table/'))
|
|
546
|
+
return { data: { success: true, data: withFilters } }
|
|
547
|
+
return { data: { success: true, data: CARDS, meta: { total: 1 } } }
|
|
548
|
+
})
|
|
549
|
+
render(
|
|
550
|
+
<ApiProvider client={fakeApi({ get })}>
|
|
551
|
+
<DynamicKanban model="issue" />
|
|
552
|
+
</ApiProvider>,
|
|
553
|
+
)
|
|
554
|
+
await screen.findByText('Backlog')
|
|
555
|
+
// The eager per-lane total request for backlog carries BOTH the stage
|
|
556
|
+
// scope and the override condition serialized like a smart-lane filter.
|
|
557
|
+
await waitFor(() =>
|
|
558
|
+
expect(get).toHaveBeenCalledWith(
|
|
559
|
+
'/data/issue',
|
|
560
|
+
expect.objectContaining({
|
|
561
|
+
params: expect.objectContaining({
|
|
562
|
+
f_stage: 'backlog',
|
|
563
|
+
f_priority: 'EQ:high',
|
|
564
|
+
}),
|
|
565
|
+
}),
|
|
566
|
+
),
|
|
567
|
+
)
|
|
568
|
+
// The header carries the conditions indicator (dot + tooltip).
|
|
569
|
+
expect(screen.getByTestId('lane-conditions-backlog')).toBeTruthy()
|
|
570
|
+
})
|
|
571
|
+
})
|
|
572
|
+
|
|
498
573
|
// ---------------------------------------------------------------------------
|
|
499
574
|
// 5. Per-lane search — the inline lane header search narrows ONLY that lane's
|
|
500
575
|
// already-fetched cards, client-side, by title + field values.
|