@asteby/metacore-runtime-react 23.5.1 → 23.6.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 +6 -0
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +7 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/stage-automations.d.ts +75 -0
- package/dist/stage-automations.d.ts.map +1 -0
- package/dist/stage-automations.js +264 -0
- package/package.json +1 -1
- package/src/__tests__/stage-automations.test.tsx +243 -0
- package/src/dynamic-kanban.tsx +50 -0
- package/src/index.ts +14 -0
- package/src/stage-automations.tsx +580 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 23.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 88214d1: DynamicKanban: automatizaciones de etapa (estilo Bitrix). Cada lane suma un botón ⚡ que abre un diálogo para configurar reglas "al entrar a esta etapa → agregar tag / quitar tag / setear campo = valor", genéricas para cualquier modelo. Las reglas viven en el backend vía el cliente HTTP existente (`/stage-automations`), con toggle de activación, eliminación e indicador de conteo activo en el header del lane. La feature es no-intrusiva: si el endpoint no existe (404/error) el afford ⚡ simplemente no se muestra y el tablero sigue funcionando. Todo el texto pasa por `t('dynamic.automations.*')` con `defaultValue` en español.
|
|
8
|
+
|
|
3
9
|
## 23.5.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-kanban.d.ts","sourceRoot":"","sources":["../src/dynamic-kanban.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-kanban.d.ts","sourceRoot":"","sources":["../src/dynamic-kanban.tsx"],"names":[],"mappings":"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"}
|
package/dist/dynamic-kanban.js
CHANGED
|
@@ -8,6 +8,7 @@ import { Badge, Button, Card, CardContent, DropdownMenu, DropdownMenuContent, Dr
|
|
|
8
8
|
import { ColumnFilterControl, FilterValueCombobox } from '@asteby/metacore-ui/data-table';
|
|
9
9
|
import { generateBadgeStyles, optionColor } from '@asteby/metacore-ui/lib';
|
|
10
10
|
import { useApi } from './api-context';
|
|
11
|
+
import { useStageAutomations, StageAutomationsButton, } from './stage-automations';
|
|
11
12
|
import { useDynamicFilters } from './use-dynamic-filters';
|
|
12
13
|
import { FilterChipsRow, summarizeFilterValues, translateOptionLabels, } from './filter-chips';
|
|
13
14
|
import { dedupeById, useInfiniteScrollSentinel } from './use-infinite-scroll';
|
|
@@ -236,6 +237,9 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, on
|
|
|
236
237
|
const { t, i18n } = useTranslation();
|
|
237
238
|
const api = useApi();
|
|
238
239
|
const isDark = useIsDarkTheme();
|
|
240
|
+
// Stage automations (Bitrix-style per-lane rules). Degrades to no-op when
|
|
241
|
+
// the host has no `/stage-automations` endpoint — the ⚡ affordance hides.
|
|
242
|
+
const automations = useStageAutomations(model);
|
|
239
243
|
const { getMetadata, setMetadata: cacheMetadata } = useMetadataCache();
|
|
240
244
|
const cachedMeta = getMetadata(model);
|
|
241
245
|
const [metadata, setMetadata] = useState(cachedMeta || null);
|
|
@@ -640,7 +644,7 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, on
|
|
|
640
644
|
field: f?.field,
|
|
641
645
|
values: f?.values,
|
|
642
646
|
text: f?.text,
|
|
643
|
-
}), onQueryChange: (q) => updateLaneFilter(stage.key, { query: q }), isDark: isDark, dimmed: !!activeId && !droppableAllowed, disabled: !!activeId && !droppableAllowed, 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))))) }, stage.key));
|
|
647
|
+
}), onQueryChange: (q) => updateLaneFilter(stage.key, { query: q }), isDark: isDark, dimmed: !!activeId && !droppableAllowed, disabled: !!activeId && !droppableAllowed, model: model, columns: metadata?.columns ?? [], automationsAvailable: automations.available && stage.key !== UNASSIGNED_LANE, automationRules: automations.byStage.get(stage.key) ?? [], onAutomationCreate: automations.create, onAutomationUpdate: automations.update, onAutomationRemove: automations.remove, 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))))) }, stage.key));
|
|
644
648
|
}) }), _jsx(DragOverlay, { children: activeCard ? (_jsx(CardPreview, { card: activeCard, titleCol: titleCol, fieldCols: fieldCols, locale: i18n.language, timeZone: timeZone, currency: currency })) : null }), rowActionDialogs] })] }));
|
|
645
649
|
}
|
|
646
650
|
/**
|
|
@@ -670,7 +674,7 @@ function SheetFilterRow({ field, isStage, }) {
|
|
|
670
674
|
const summary = summarizeFilterValues(field.config.selectedValues, field.config.options);
|
|
671
675
|
return (_jsx(ColumnFilterControl, { variant: "row", align: "end", icon: filterTypeIcon(field.config.filterType, isStage), label: field.label, valueSummary: summary, filterKey: field.config.filterKey, filterType: field.config.filterType, filterOptions: field.config.options, filterLoading: field.config.loading, filterSearchEndpoint: field.config.searchEndpoint, selectedValues: field.config.selectedValues, onFilterChange: field.config.onFilterChange, loadOptions: field.config.loadOptions }));
|
|
672
676
|
}
|
|
673
|
-
function KanbanLane({ stage, count, totalCount, serverTotal, hasMore, loadingMore, onLoadMore, filterFields, laneFilter, onFunnelChange, onQueryChange, isDark, dimmed, disabled, children, }) {
|
|
677
|
+
function KanbanLane({ stage, count, totalCount, serverTotal, hasMore, loadingMore, onLoadMore, filterFields, laneFilter, onFunnelChange, onQueryChange, isDark, dimmed, disabled, model, columns, automationsAvailable, automationRules, onAutomationCreate, onAutomationUpdate, onAutomationRemove, children, }) {
|
|
674
678
|
const { t } = useTranslation();
|
|
675
679
|
const { setNodeRef, isOver } = useDroppable({ id: stage.key, disabled });
|
|
676
680
|
// Infinite scroll: the sentinel lives at the bottom of the lane's own scroll
|
|
@@ -721,7 +725,7 @@ function KanbanLane({ stage, count, totalCount, serverTotal, hasMore, loadingMor
|
|
|
721
725
|
outlineOffset: 2,
|
|
722
726
|
}, "data-stage": stage.key, "data-disabled": disabled || undefined, children: [_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2.5", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_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) })] }), _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', {
|
|
723
727
|
defaultValue: 'Buscar en la columna',
|
|
724
|
-
}), 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 })] })] }), 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) => {
|
|
728
|
+
}), 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 }))] })] }), 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) => {
|
|
725
729
|
if (e.key === 'Escape') {
|
|
726
730
|
onQueryChange('');
|
|
727
731
|
setSearchOpen(false);
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './types';
|
|
|
2
2
|
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
|
+
export { useStageAutomations, StageAutomationsButton, isTagColumn, automationFieldOptions, groupAutomationsByStage, activeAutomationCount, type StageAutomation, type StageAutomationAction, type StageAutomationActionType, type NewStageAutomation, type UseStageAutomationsResult, type StageAutomationsButtonProps, } from './stage-automations';
|
|
5
6
|
export { DynamicView, resolveViewRenderer, readViewFromSearch, resolveActiveView, type DynamicViewProps, } from './dynamic-view';
|
|
6
7
|
export { useDynamicFilters, type UseDynamicFiltersOptions, type UseDynamicFiltersResult, } from './use-dynamic-filters';
|
|
7
8
|
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":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from './types';
|
|
|
7
7
|
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
|
+
export { useStageAutomations, StageAutomationsButton, isTagColumn, automationFieldOptions, groupAutomationsByStage, activeAutomationCount, } from './stage-automations';
|
|
10
11
|
export { DynamicView, resolveViewRenderer, readViewFromSearch, resolveActiveView, } from './dynamic-view';
|
|
11
12
|
export { useDynamicFilters, } from './use-dynamic-filters';
|
|
12
13
|
export * from './dynamic-form';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ApiClient } from './api-context';
|
|
3
|
+
import type { ColumnDefinition } from './types';
|
|
4
|
+
export type StageAutomationActionType = 'add_tag' | 'remove_tag' | 'set_field';
|
|
5
|
+
export interface StageAutomationAction {
|
|
6
|
+
type: StageAutomationActionType;
|
|
7
|
+
field: string;
|
|
8
|
+
value: string;
|
|
9
|
+
}
|
|
10
|
+
export interface StageAutomation {
|
|
11
|
+
id: string | number;
|
|
12
|
+
model: string;
|
|
13
|
+
/** Source stage the card is leaving. `'*'` = any stage. */
|
|
14
|
+
from_stage: string;
|
|
15
|
+
/** Destination stage that triggers the rule (the lane's stage key). */
|
|
16
|
+
to_stage: string;
|
|
17
|
+
actions: StageAutomationAction[];
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Draft for a new rule — a single action, keyed to a destination stage. */
|
|
21
|
+
export interface NewStageAutomation {
|
|
22
|
+
model: string;
|
|
23
|
+
from_stage: string;
|
|
24
|
+
to_stage: string;
|
|
25
|
+
actions: StageAutomationAction[];
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** Whether a column holds a tag/json array (valid target for add/remove_tag). */
|
|
29
|
+
export declare function isTagColumn(col: ColumnDefinition): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Columns a given action type may target:
|
|
32
|
+
* - add_tag / remove_tag → only tag/json columns
|
|
33
|
+
* - set_field → any editable (non-readonly, non-hidden) column
|
|
34
|
+
* Hidden and readonly columns are never offered.
|
|
35
|
+
*/
|
|
36
|
+
export declare function automationFieldOptions(columns: ColumnDefinition[], actionType: StageAutomationActionType): ColumnDefinition[];
|
|
37
|
+
/** Rules grouped by their destination stage key (only what the lane needs). */
|
|
38
|
+
export declare function groupAutomationsByStage(rules: StageAutomation[]): Map<string, StageAutomation[]>;
|
|
39
|
+
/** Count of ENABLED rules for a stage — drives the lane's ⚡ indicator. */
|
|
40
|
+
export declare function activeAutomationCount(rules: StageAutomation[] | undefined): number;
|
|
41
|
+
export interface UseStageAutomationsResult {
|
|
42
|
+
/** False when the endpoint is missing/errored — hide the ⚡ affordance. */
|
|
43
|
+
available: boolean;
|
|
44
|
+
loading: boolean;
|
|
45
|
+
/** Destination-stage-keyed rules. */
|
|
46
|
+
byStage: Map<string, StageAutomation[]>;
|
|
47
|
+
create: (draft: NewStageAutomation) => Promise<void>;
|
|
48
|
+
update: (id: StageAutomation['id'], patch: Partial<StageAutomation>) => Promise<void>;
|
|
49
|
+
remove: (id: StageAutomation['id']) => Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Loads a model's stage automations and exposes CRUD. Swallows a missing
|
|
53
|
+
* endpoint (404 / network error) into `available: false` so the kanban never
|
|
54
|
+
* breaks; real mutation failures surface a toast and re-throw so the dialog can
|
|
55
|
+
* keep its draft.
|
|
56
|
+
*/
|
|
57
|
+
export declare function useStageAutomations(model: string): UseStageAutomationsResult;
|
|
58
|
+
export interface StageAutomationsButtonProps {
|
|
59
|
+
model: string;
|
|
60
|
+
/** The lane's stage key + human label (destination that triggers rules). */
|
|
61
|
+
stageKey: string;
|
|
62
|
+
stageLabel: string;
|
|
63
|
+
columns: ColumnDefinition[];
|
|
64
|
+
rules: StageAutomation[];
|
|
65
|
+
onCreate: (draft: NewStageAutomation) => Promise<void>;
|
|
66
|
+
onUpdate: (id: StageAutomation['id'], patch: Partial<StageAutomation>) => Promise<void>;
|
|
67
|
+
onRemove: (id: StageAutomation['id']) => Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The per-lane ⚡ button: shows an active-rule count badge and opens the rule
|
|
71
|
+
* editor for THIS stage. Rendered only when the endpoint is available.
|
|
72
|
+
*/
|
|
73
|
+
export declare function StageAutomationsButton({ model, stageKey, stageLabel, columns, rules, onCreate, onUpdate, onRemove, }: StageAutomationsButtonProps): React.JSX.Element;
|
|
74
|
+
export type { ApiClient };
|
|
75
|
+
//# sourceMappingURL=stage-automations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage-automations.d.ts","sourceRoot":"","sources":["../src/stage-automations.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// Stage automations — a Bitrix-style "when a card enters this stage, run these
|
|
3
|
+
// actions" rule editor, surfaced per-lane in DynamicKanban. The feature is
|
|
4
|
+
// generic over any model: rules live server-side (the host wires the
|
|
5
|
+
// `/stage-automations` REST endpoints against the same api client the rest of
|
|
6
|
+
// the dynamic runtime uses) and reference the model's own columns.
|
|
7
|
+
//
|
|
8
|
+
// Non-intrusive by design: if the endpoint 404s or errors, the hook reports
|
|
9
|
+
// `available: false` and the lane simply omits the ⚡ affordance — the board
|
|
10
|
+
// keeps working. All UI text goes through t() with a Spanish defaultValue so a
|
|
11
|
+
// host that ships no `dynamic.automations.*` keys still renders in Spanish.
|
|
12
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
13
|
+
import { useTranslation } from 'react-i18next';
|
|
14
|
+
import { Zap, Trash2, Plus } from 'lucide-react';
|
|
15
|
+
import { toast } from 'sonner';
|
|
16
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, } from '@asteby/metacore-ui/primitives';
|
|
17
|
+
import { useApi } from './api-context';
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Pure helpers (exported for unit tests — no React, no transport)
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
const ACTION_TYPES = [
|
|
22
|
+
'add_tag',
|
|
23
|
+
'remove_tag',
|
|
24
|
+
'set_field',
|
|
25
|
+
];
|
|
26
|
+
/** Whether a column holds a tag/json array (valid target for add/remove_tag). */
|
|
27
|
+
export function isTagColumn(col) {
|
|
28
|
+
const t = col.type;
|
|
29
|
+
const style = col.cellStyle;
|
|
30
|
+
return t === 'tags' || t === 'json' || style === 'tags';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Columns a given action type may target:
|
|
34
|
+
* - add_tag / remove_tag → only tag/json columns
|
|
35
|
+
* - set_field → any editable (non-readonly, non-hidden) column
|
|
36
|
+
* Hidden and readonly columns are never offered.
|
|
37
|
+
*/
|
|
38
|
+
export function automationFieldOptions(columns, actionType) {
|
|
39
|
+
return columns.filter((c) => {
|
|
40
|
+
if (c.hidden)
|
|
41
|
+
return false;
|
|
42
|
+
const ro = !!c.readonly ||
|
|
43
|
+
!!c.read_only;
|
|
44
|
+
if (ro)
|
|
45
|
+
return false;
|
|
46
|
+
if (actionType === 'add_tag' || actionType === 'remove_tag') {
|
|
47
|
+
return isTagColumn(c);
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** Rules grouped by their destination stage key (only what the lane needs). */
|
|
53
|
+
export function groupAutomationsByStage(rules) {
|
|
54
|
+
const map = new Map();
|
|
55
|
+
for (const r of rules) {
|
|
56
|
+
const arr = map.get(r.to_stage) ?? [];
|
|
57
|
+
arr.push(r);
|
|
58
|
+
map.set(r.to_stage, arr);
|
|
59
|
+
}
|
|
60
|
+
return map;
|
|
61
|
+
}
|
|
62
|
+
/** Count of ENABLED rules for a stage — drives the lane's ⚡ indicator. */
|
|
63
|
+
export function activeAutomationCount(rules) {
|
|
64
|
+
if (!rules)
|
|
65
|
+
return 0;
|
|
66
|
+
return rules.filter((r) => r.enabled).length;
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Data hook
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
function unwrap(res) {
|
|
72
|
+
const body = res?.data;
|
|
73
|
+
// Envelope {success, data}; tolerate a bare array/object too.
|
|
74
|
+
if (body && typeof body === 'object' && 'data' in body)
|
|
75
|
+
return body.data;
|
|
76
|
+
return body;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Loads a model's stage automations and exposes CRUD. Swallows a missing
|
|
80
|
+
* endpoint (404 / network error) into `available: false` so the kanban never
|
|
81
|
+
* breaks; real mutation failures surface a toast and re-throw so the dialog can
|
|
82
|
+
* keep its draft.
|
|
83
|
+
*/
|
|
84
|
+
export function useStageAutomations(model) {
|
|
85
|
+
const api = useApi();
|
|
86
|
+
const { t } = useTranslation();
|
|
87
|
+
const [available, setAvailable] = useState(true);
|
|
88
|
+
const [loading, setLoading] = useState(true);
|
|
89
|
+
const [rules, setRules] = useState([]);
|
|
90
|
+
const load = useCallback(async () => {
|
|
91
|
+
setLoading(true);
|
|
92
|
+
try {
|
|
93
|
+
const res = await api.get(`/stage-automations?model=${encodeURIComponent(model)}`);
|
|
94
|
+
const data = unwrap(res);
|
|
95
|
+
setRules(Array.isArray(data) ? data : []);
|
|
96
|
+
setAvailable(true);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Endpoint absent or errored — degrade silently.
|
|
100
|
+
setRules([]);
|
|
101
|
+
setAvailable(false);
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
setLoading(false);
|
|
105
|
+
}
|
|
106
|
+
}, [api, model]);
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
void load();
|
|
109
|
+
}, [load]);
|
|
110
|
+
const create = useCallback(async (draft) => {
|
|
111
|
+
try {
|
|
112
|
+
const res = await api.post('/stage-automations', draft);
|
|
113
|
+
const created = unwrap(res);
|
|
114
|
+
await load();
|
|
115
|
+
if (!created)
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
toast.error(t('dynamic.automations.saveError', {
|
|
120
|
+
defaultValue: 'No se pudo guardar la automatización',
|
|
121
|
+
}));
|
|
122
|
+
throw e;
|
|
123
|
+
}
|
|
124
|
+
}, [api, load, t]);
|
|
125
|
+
const update = useCallback(async (id, patch) => {
|
|
126
|
+
try {
|
|
127
|
+
await api.put(`/stage-automations/${id}`, patch);
|
|
128
|
+
await load();
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
toast.error(t('dynamic.automations.saveError', {
|
|
132
|
+
defaultValue: 'No se pudo guardar la automatización',
|
|
133
|
+
}));
|
|
134
|
+
throw e;
|
|
135
|
+
}
|
|
136
|
+
}, [api, load, t]);
|
|
137
|
+
const remove = useCallback(async (id) => {
|
|
138
|
+
try {
|
|
139
|
+
await api.delete(`/stage-automations/${id}`);
|
|
140
|
+
await load();
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
toast.error(t('dynamic.automations.deleteError', {
|
|
144
|
+
defaultValue: 'No se pudo eliminar la automatización',
|
|
145
|
+
}));
|
|
146
|
+
throw e;
|
|
147
|
+
}
|
|
148
|
+
}, [api, load, t]);
|
|
149
|
+
const byStage = useMemo(() => groupAutomationsByStage(rules), [rules]);
|
|
150
|
+
return { available, loading, byStage, create, update, remove };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The per-lane ⚡ button: shows an active-rule count badge and opens the rule
|
|
154
|
+
* editor for THIS stage. Rendered only when the endpoint is available.
|
|
155
|
+
*/
|
|
156
|
+
export function StageAutomationsButton({ model, stageKey, stageLabel, columns, rules, onCreate, onUpdate, onRemove, }) {
|
|
157
|
+
const { t } = useTranslation();
|
|
158
|
+
const [open, setOpen] = useState(false);
|
|
159
|
+
const activeCount = activeAutomationCount(rules);
|
|
160
|
+
return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => setOpen(true), className: `relative flex size-6 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-foreground ${activeCount > 0 ? 'text-primary' : 'text-muted-foreground'}`, "aria-label": t('dynamic.automations.open', {
|
|
161
|
+
defaultValue: 'Automatizaciones de la etapa',
|
|
162
|
+
}), "data-testid": `automations-trigger-${stageKey}`, children: [_jsx(Zap, { className: "h-3.5 w-3.5" }), activeCount > 0 && (_jsx("span", { className: "absolute -right-1 -top-1 flex min-w-3.5 items-center justify-center rounded-full bg-primary px-1 text-[9px] font-semibold leading-none text-primary-foreground", children: activeCount }))] }), open && (_jsx(StageAutomationsDialog, { open: open, onOpenChange: setOpen, model: model, stageKey: stageKey, stageLabel: stageLabel, columns: columns, rules: rules, onCreate: onCreate, onUpdate: onUpdate, onRemove: onRemove }))] }));
|
|
163
|
+
}
|
|
164
|
+
function describeAction(action, columns, t) {
|
|
165
|
+
const col = columns.find((c) => c.key === action.field);
|
|
166
|
+
const fieldLabel = col ? t(col.label, { defaultValue: col.label }) : action.field;
|
|
167
|
+
switch (action.type) {
|
|
168
|
+
case 'add_tag':
|
|
169
|
+
return t('dynamic.automations.summary.addTag', {
|
|
170
|
+
defaultValue: 'Agregar tag "{{value}}" a {{field}}',
|
|
171
|
+
value: action.value,
|
|
172
|
+
field: fieldLabel,
|
|
173
|
+
});
|
|
174
|
+
case 'remove_tag':
|
|
175
|
+
return t('dynamic.automations.summary.removeTag', {
|
|
176
|
+
defaultValue: 'Quitar tag "{{value}}" de {{field}}',
|
|
177
|
+
value: action.value,
|
|
178
|
+
field: fieldLabel,
|
|
179
|
+
});
|
|
180
|
+
default:
|
|
181
|
+
return t('dynamic.automations.summary.setField', {
|
|
182
|
+
defaultValue: 'Setear {{field}} = "{{value}}"',
|
|
183
|
+
value: action.value,
|
|
184
|
+
field: fieldLabel,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function StageAutomationsDialog({ open, onOpenChange, model, stageKey, stageLabel, columns, rules, onCreate, onUpdate, onRemove, }) {
|
|
189
|
+
const { t } = useTranslation();
|
|
190
|
+
const [actionType, setActionType] = useState('add_tag');
|
|
191
|
+
const [field, setField] = useState('');
|
|
192
|
+
const [value, setValue] = useState('');
|
|
193
|
+
const [saving, setSaving] = useState(false);
|
|
194
|
+
const fieldChoices = useMemo(() => automationFieldOptions(columns, actionType), [columns, actionType]);
|
|
195
|
+
// Keep the selected field valid for the current action type.
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (!fieldChoices.some((c) => c.key === field)) {
|
|
198
|
+
setField(fieldChoices[0]?.key ?? '');
|
|
199
|
+
}
|
|
200
|
+
}, [fieldChoices, field]);
|
|
201
|
+
const label = t(stageLabel, { defaultValue: stageLabel });
|
|
202
|
+
const canSubmit = !!field && !!value.trim() && !saving;
|
|
203
|
+
const submit = async () => {
|
|
204
|
+
if (!canSubmit)
|
|
205
|
+
return;
|
|
206
|
+
setSaving(true);
|
|
207
|
+
try {
|
|
208
|
+
await onCreate({
|
|
209
|
+
model,
|
|
210
|
+
from_stage: '*',
|
|
211
|
+
to_stage: stageKey,
|
|
212
|
+
actions: [{ type: actionType, field, value: value.trim() }],
|
|
213
|
+
enabled: true,
|
|
214
|
+
});
|
|
215
|
+
setValue('');
|
|
216
|
+
toast.success(t('dynamic.automations.saved', {
|
|
217
|
+
defaultValue: 'Automatización guardada',
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// toast already surfaced by the hook; keep the draft.
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
setSaving(false);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: t('dynamic.automations.title', {
|
|
228
|
+
defaultValue: 'Automatizaciones · {{stage}}',
|
|
229
|
+
stage: label,
|
|
230
|
+
}) }), _jsx(DialogDescription, { children: t('dynamic.automations.description', {
|
|
231
|
+
defaultValue: 'Al entrar una tarjeta a esta etapa se ejecutan estas acciones.',
|
|
232
|
+
}) })] }), _jsx("div", { className: "flex flex-col gap-2", children: rules.length === 0 ? (_jsx("p", { className: "rounded-md border border-dashed px-3 py-6 text-center text-sm text-muted-foreground", children: t('dynamic.automations.empty', {
|
|
233
|
+
defaultValue: 'Sin automatizaciones para esta etapa.',
|
|
234
|
+
}) })) : (rules.map((rule) => (_jsxs("div", { className: "flex items-center gap-2 rounded-md border px-3 py-2", "data-testid": `automation-rule-${rule.id}`, children: [_jsx("div", { className: "min-w-0 flex-1", children: rule.actions.map((a, i) => (_jsx("p", { className: `truncate text-sm ${rule.enabled ? '' : 'text-muted-foreground line-through'}`, children: describeAction(a, columns, t) }, i))) }), _jsx(Switch, { checked: rule.enabled, onCheckedChange: (checked) => void onUpdate(rule.id, { enabled: checked }), "aria-label": t('dynamic.automations.toggle', {
|
|
235
|
+
defaultValue: 'Activar automatización',
|
|
236
|
+
}), "data-testid": `automation-toggle-${rule.id}` }), _jsx(Button, { variant: "ghost", size: "icon", className: "size-7 shrink-0 text-muted-foreground hover:text-destructive", onClick: () => {
|
|
237
|
+
void onRemove(rule.id).then(() => toast.success(t('dynamic.automations.deleted', {
|
|
238
|
+
defaultValue: 'Automatización eliminada',
|
|
239
|
+
})));
|
|
240
|
+
}, "aria-label": t('dynamic.automations.delete', {
|
|
241
|
+
defaultValue: 'Eliminar automatización',
|
|
242
|
+
}), "data-testid": `automation-delete-${rule.id}`, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, String(rule.id))))) }), _jsxs("div", { className: "grid grid-cols-1 gap-3 rounded-md border bg-muted/30 p-3 sm:grid-cols-3", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-xs", children: t('dynamic.automations.actionLabel', {
|
|
243
|
+
defaultValue: 'Acción',
|
|
244
|
+
}) }), _jsxs(Select, { value: actionType, onValueChange: (v) => setActionType(v), children: [_jsx(SelectTrigger, { "data-testid": "automation-action-select", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: ACTION_TYPES.map((at) => (_jsx(SelectItem, { value: at, children: t(`dynamic.automations.action.${at}`, {
|
|
245
|
+
defaultValue: at === 'add_tag'
|
|
246
|
+
? 'Agregar tag'
|
|
247
|
+
: at === 'remove_tag'
|
|
248
|
+
? 'Quitar tag'
|
|
249
|
+
: 'Setear campo',
|
|
250
|
+
}) }, at))) })] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-xs", children: t('dynamic.automations.fieldLabel', {
|
|
251
|
+
defaultValue: 'Campo',
|
|
252
|
+
}) }), _jsxs(Select, { value: field, onValueChange: setField, disabled: fieldChoices.length === 0, children: [_jsx(SelectTrigger, { "data-testid": "automation-field-select", children: _jsx(SelectValue, { placeholder: t('dynamic.automations.noFields', {
|
|
253
|
+
defaultValue: 'Sin campos',
|
|
254
|
+
}) }) }), _jsx(SelectContent, { children: fieldChoices.map((c) => (_jsx(SelectItem, { value: c.key, children: t(c.label, { defaultValue: c.label }) }, c.key))) })] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-xs", children: t('dynamic.automations.valueLabel', {
|
|
255
|
+
defaultValue: 'Valor',
|
|
256
|
+
}) }), _jsx(Input, { value: value, onChange: (e) => setValue(e.target.value), onKeyDown: (e) => {
|
|
257
|
+
if (e.key === 'Enter')
|
|
258
|
+
void submit();
|
|
259
|
+
}, placeholder: t('dynamic.automations.valuePlaceholder', {
|
|
260
|
+
defaultValue: 'Valor...',
|
|
261
|
+
}), "data-testid": "automation-value-input" })] })] }), _jsx(DialogFooter, { children: _jsxs(Button, { onClick: () => void submit(), disabled: !canSubmit, className: "gap-1", "data-testid": "automation-add", children: [_jsx(Plus, { className: "h-4 w-4" }), t('dynamic.automations.add', {
|
|
262
|
+
defaultValue: 'Agregar regla',
|
|
263
|
+
})] }) })] }) }));
|
|
264
|
+
}
|