import React from 'react'; import type { ApiClient } from './api-context'; import type { ColumnDefinition, StageMeta, SmartLaneMeta } from './types'; export type CustomStageType = 'stage' | 'smart'; export type CustomStageFilterOp = 'eq' | 'neq' | 'contains' | 'in'; export interface CustomStageFilter { field: string; op: CustomStageFilterOp; value: string; } export interface CustomStage { id: string | number; model: string; /** Stable lane key (also the value stored on a card for `type: "stage"`). */ key: string; label: string; /** Semantic palette name ('slate', 'blue', …) or a hex literal. */ color: string; /** Board position (lane order). */ position: number; type: CustomStageType; /** Only meaningful for `type: "smart"` — the virtual lane's funnel. */ filters: CustomStageFilter[]; enabled: boolean; } /** Draft for a new custom stage (no server id yet). */ export type NewCustomStage = Omit; export declare const CUSTOM_STAGE_COLORS: readonly ["slate", "blue", "green", "amber", "red", "purple", "pink", "cyan"]; export declare const CUSTOM_STAGE_FILTER_OPS: CustomStageFilterOp[]; /** A blank filter row for the smart-lane condition builder. */ export declare function emptyCustomStageFilter(field?: string): CustomStageFilter; /** Enabled custom stages split by flavor (disabled ones are dropped). */ export declare function splitCustomStages(stages: CustomStage[] | undefined): { laneStages: CustomStage[]; smartStages: CustomStage[]; }; /** * Merges `type: "stage"` custom stages into the model's declared lanes. A * custom stage whose key the metadata ALREADY carries (the backend surfaced it * in `metadata.stages`) is not duplicated — it's just tagged as custom so the * lane grows an edit/delete menu. Unknown-key custom stages are appended. * Returns the merged lanes (sorted by order) plus a `key → CustomStage` map so * the board can decorate the custom lanes. */ export declare function mergeLaneStages(declared: StageMeta[], customLaneStages: CustomStage[]): { lanes: StageMeta[]; customByKey: Map; }; /** * Serializes a smart lane's filters into the board's list query params. The ops * list endpoint (ops #704) takes a SINGLE `f_` param whose value carries * the operator as a `OP:value` prefix: * - eq → `f_=EQ:value` * - neq → `f_=NEQ:value` * - contains → `f_=HAS:value` (membership in a jsonb array — NOT the * text-substring ILIKE; that's why it's HAS, not CONTAINS) * - in → `f_=IN:v1,v2,...` (comma-separated after `IN:`) * Empty fields/values are skipped. */ export declare function smartLaneParams(filters: { field: string; op: string; value: string; }[] | undefined): Record; /** * Resolves the smart lanes to paint. The kernel's `metadata.smart_lanes` is the * source of truth for rendering (ops #704); the CRUD list only backs the * management dialog. So when metadata carries smart lanes we map those, folding * in the CRUD entry's `id` (matched by key) so the Editar menu can PUT/DELETE. * When metadata omits them (older host / metadata lag) we tolerate the gap and * fall back to the CRUD smart stages. Returns `CustomStage[]` either way. */ export declare function resolveSmartLanes(metaSmartLanes: SmartLaneMeta[] | undefined, crudSmartStages: CustomStage[], model: string): CustomStage[]; /** Columns offered in the condition builder: visible, non-id model columns. */ export declare function customStageFilterFields(columns: ColumnDefinition[]): ColumnDefinition[]; /** Whether a draft is complete enough to save. */ export declare function isCustomStageDraftValid(draft: { label: string; type: CustomStageType; filters: CustomStageFilter[]; }): boolean; /** * Whether a card passes a set of extra lane `filters` (the same conditions a * smart lane uses, layered on top of a real stage). Ops: * - eq → equal (string compare) * - neq → not equal * - contains → the card's value (array or string) includes the value * - in → the card's value is one of the comma-separated candidates * Empty/absent filters pass. Pure — a client-side belt-and-suspenders over the * initial (unscoped) board page; the server scopes the per-lane top-up queries. */ export declare function cardMatchesStageFilters(card: any, filters: CustomStageFilter[] | undefined): boolean; /** Slugify a label into a stable-ish lane key (used only when creating). */ export declare function slugifyStageKey(label: string): string; export interface UseCustomStagesResult { /** False when the endpoint is missing/errored — hide the whole feature. */ available: boolean; loading: boolean; stages: CustomStage[]; create: (draft: NewCustomStage) => Promise; update: (id: CustomStage['id'], patch: Partial) => Promise; /** * Deletes a stage. Pass `reassignTo` (a target lane key) to move a real * stage's cards first. Throws on 409 (cards still present, no reassign) so * the caller can read `meta.cards` and offer a reassignment target. */ remove: (id: CustomStage['id'], reassignTo?: string) => Promise; } /** * Loads a model's custom stages and exposes CRUD. A missing endpoint (404 / * network error) degrades to `available: false` so the kanban never breaks; * real mutation failures surface a toast and re-throw so the dialog keeps its * draft. A 409 on delete (existing cards) is re-thrown WITHOUT a generic toast * so the delete flow can show a targeted message. */ export declare function useCustomStages(model: string): UseCustomStagesResult; export interface AddStageColumnProps { onClick: () => void; } /** The dotted phantom lane at the end of the board (Bitrix/Trello pattern). */ export declare function AddStageColumn({ onClick }: AddStageColumnProps): React.JSX.Element; export interface CustomStageLaneMenuProps { stage: CustomStage; onEdit: (stage: CustomStage) => void; onDelete: (stage: CustomStage) => void; } /** The ⋮ menu shown in a custom lane's header. */ export declare function CustomStageLaneMenu({ stage, onEdit, onDelete, }: CustomStageLaneMenuProps): React.JSX.Element; export interface StageConditionBuilderProps { /** The current filter rows. */ filters: CustomStageFilter[]; /** Called with the next filter rows on any add/remove/patch. */ onChange: (filters: CustomStageFilter[]) => void; /** Columns offered in the field dropdown (visible, non-id). */ fieldChoices: ColumnDefinition[]; /** Optional heading shown above the rows. */ label?: string; } /** * The reusable field/operator/value condition builder. Emits `CustomStageFilter[]` * through `onChange`. Same ops (eq/neq/contains/in) and testids the smart-lane * editor has always used, so it drops in for both the custom-stage smart lane * and the declared-stage config dialog. */ export declare function StageConditionBuilder({ filters, onChange, fieldChoices, label, }: StageConditionBuilderProps): React.JSX.Element; export interface CustomStageDialogProps { open: boolean; onOpenChange: (open: boolean) => void; model: string; /** Columns for the smart-lane condition builder. */ columns: ColumnDefinition[]; /** Editing an existing stage, or null to create a new one. */ initial: CustomStage | null; /** Next board position for a newly created stage (appended at the end). */ nextPosition: number; onCreate: (draft: NewCustomStage) => Promise; onUpdate: (id: CustomStage['id'], patch: Partial) => Promise; } export declare function CustomStageDialog({ open, onOpenChange, model, columns, initial, nextPosition, onCreate, onUpdate, }: CustomStageDialogProps): React.JSX.Element; export interface CustomStageDeleteDialogProps { open: boolean; onOpenChange: (open: boolean) => void; stage: CustomStage | null; /** Other lanes a real stage's cards can be reassigned to (key excluded). */ reassignTargets?: { key: string; label: string; }[]; /** `reassignTo` is set on retry after a 409 (real stage with cards). */ onConfirm: (stage: CustomStage, reassignTo?: string) => Promise; } /** * Confirms deleting a custom lane. A 409 (the backend rejected because cards * still sit on a real stage) doesn't close the dialog — it reads `meta.cards` * from the response, shows the count, and offers a target lane to reassign the * cards to; confirming again retries the delete with `reassign_to`. */ export declare function CustomStageDeleteDialog({ open, onOpenChange, stage, reassignTargets, onConfirm, }: CustomStageDeleteDialogProps): React.JSX.Element | null; export type StageConfigKind = 'declared' | 'custom'; /** What the gear opens the config dialog against — a declared or custom lane. */ export interface StageConfigTarget { kind: StageConfigKind; /** Stable lane key. */ stageKey: string; /** Custom-stage id — required (and only used) when `kind === 'custom'`. */ id?: CustomStage['id']; label: string; color: string; filters: CustomStageFilter[]; /** * Declared kind: an override is currently applied → shows a "Personalizada" * badge + "Restablecer al original". Ignored for custom stages (they reset * via their own delete flow). */ overridden?: boolean; /** Terminal stage (e.g. "Done") — surfaces an "Etapa final" chip + tooltip. */ isFinal?: boolean; /** * The manifest ORIGINAL (pre-override) values, when the host serves them * (`metadata.stages[].original`). Drives the "Restablecer al original" confirm * so the user sees exactly what reverts. Absent → a generic confirm. */ original?: { label?: string; color?: string; filters?: CustomStageFilter[]; }; /** The backing CustomStage (custom kind) so "Eliminar" can trigger delete. */ customStage?: CustomStage; } /** A legible operator glyph for a condition chip: = / ≠ / contiene / en. */ export declare function stageFilterOpSymbol(op: string): string; export interface StageConfigDialogProps { open: boolean; onOpenChange: (open: boolean) => void; /** Columns for the condition builder. */ columns: ColumnDefinition[]; /** The lane being configured, or null. */ target: StageConfigTarget | null; /** Declared: upsert the lane override (PUT /stage-overrides). */ onSaveOverride: (stageKey: string, patch: { label: string; color: string; filters: CustomStageFilter[]; }) => Promise; /** Declared: reset the lane to its manifest default (DELETE /stage-overrides). */ onResetOverride: (stageKey: string) => Promise; /** Custom: update the stage via its own CRUD (PUT /custom-stages/:id). */ onUpdateCustom: (id: CustomStage['id'], patch: Partial) => Promise; /** Custom: hand off to the existing delete (reassign) flow. */ onDeleteCustom: (stage: CustomStage) => void; } /** * The gear (⚙) dialog. Renames, recolors and attaches extra CONDITIONS to a * lane. One UI, two backends: a DECLARED lane persists through `/stage-overrides` * (with a "Restablecer etapa" that drops the override); a CUSTOM real stage * persists through its own `/custom-stages` CRUD (and deletes via that flow). The * conditions narrow which cards the lane shows/counts but never stop it being a * drop target — dropping a card only sets the stage value. */ export declare function StageConfigDialog({ open, onOpenChange, columns, target, onSaveOverride, onResetOverride, onUpdateCustom, onDeleteCustom, }: StageConfigDialogProps): React.JSX.Element | null; export interface SmartLaneProps { stage: CustomStage; model: string; /** Org-scoped list endpoint base (same as the kanban's). */ endpoint?: string; /** Board-wide static filters always applied (never shown as a chip). */ defaultFilters?: Record; pageSize?: number; isDark: boolean; /** Renders one card the same way the board's real lanes do (read-only). */ renderCard: (card: any) => React.ReactNode; /** Refetch trigger — bump to re-run the lane's query. */ refreshTrigger?: any; onEdit: (stage: CustomStage) => void; onDelete: (stage: CustomStage) => void; /** * Optional drag-and-drop wiring (from the kanban's sortable wrapper) so a * smart lane can be reordered by its header like a real stage. Absent → the * lane is static. */ dnd?: { setNodeRef: (el: HTMLElement | null) => void; style?: React.CSSProperties; isDragging?: boolean; handleRef?: (el: HTMLElement | null) => void; handleProps?: Record; }; } /** * A virtual lane defined by `filters`. It runs its OWN list query (the board's * shared records don't include it), so it stays correct regardless of what the * main board page loaded. Cards render read-only — a smart lane is a saved view, * not a drop target — and the header carries a funnel glyph + the custom menu. */ export declare function SmartLane({ stage, model, endpoint, defaultFilters, pageSize, isDark, renderCard, refreshTrigger, onEdit, onDelete, dnd, }: SmartLaneProps): React.JSX.Element; export type { ApiClient }; //# sourceMappingURL=custom-stages.d.ts.map