import * as React from 'react'; import { summarizeFilterValues, translateOptionLabels } from './filter-chips'; import type { RowActionPredicate } from './dynamic-columns-shim'; import type { TableMetadata, ColumnDefinition, StageMeta, StageTransition } from './types'; export { summarizeFilterValues, translateOptionLabels }; /** * Resolves the board lanes for a kanban view. Prefers the model-level * `metadata.stages` (the kernel's `stages[]`); falls back to the `group_by` * column's `options` (the kernel projects the stage machine onto the status * display). Returns lanes sorted by `order` (then declared order). Empty when * neither source is present — the caller renders a "no stages" notice. */ export declare function deriveStages(metadata: TableMetadata): StageMeta[]; /** * Buckets records into a `stageKey → rows[]` map, one entry per declared stage * (in stage order), plus a trailing `__unassigned__` bucket for rows whose * stage value matches no declared lane (so nothing silently vanishes). Empty * lanes are kept so the board always shows every stage. */ export declare const UNASSIGNED_LANE = "__unassigned__"; export declare function groupByStage(records: any[], groupByKey: string, stages: StageMeta[]): Map; /** * Whether a card may move `from → to` — the same rule as the kernel's * `StageMachine.Allows`, so a lane the board offers never ends in a 422: * * - no `transitions` in the metadata → no stage machine, any move; * - a move to the same stage is a no-op, always allowed; * - a card with no stage yet — or one outside `stageKeys` (a value written * before the machine existed) — is being placed, not moved: any stage takes it; * - otherwise the move must be declared. An EMPTY list means the machine allows * no moves between its stages. * * `'*'` is a wildcard on either side. */ export declare function isTransitionAllowed(transitions: StageTransition[] | undefined, from: string, to: string, stageKeys?: ReadonlySet): boolean; /** * Returns a NEW grouping with `cardId` moved from `fromStage` to `toStage` * (appended to the destination lane). Pure — does not mutate the input map. * Used by the optimistic drop handler so the board updates before the PUT * resolves, and so the previous grouping can be restored on failure. */ export declare function applyOptimisticMove(grouped: Map, cardId: string | number, fromStage: string, toStage: string, groupByKey: string): Map; /** * Returns a NEW per-lane pagination map with the server totals adjusted for a * card moving `fromStage` → `toStage`: the source loses one, the destination * gains one. Lanes whose `total` is still unknown (`null`, not yet topped up) * are left alone. Pure — backs the optimistic drag so a partial lane's * `count/total` header stays truthful, and can be restored on PUT failure. */ export declare function applyLaneTotalsOnMove(pagination: Record, fromStage: string, toStage: string): Record; /** * Formats a lane header's count badge. Three cases: * - A lane filter/search is active → `shown/loaded` (the client-side narrowed * count over what this lane has loaded). * - Otherwise, when the stage's server total is known → `shown` alone once * everything is loaded (`shown >= total`), else `shown/total` (partial). * - Total still unknown → just `shown`. * Pure — exported for unit tests. */ export declare function formatLaneCount(shown: number, loaded: number, serverTotal: number | null, laneActive: boolean): string; /** * Picks the columns shown on a card: a `title` column and the secondary fields. * Excludes the group_by column (it's the lane itself) and any column hidden * from the table view (visibility modal/list, or `hidden`). * * A model can lay its card out explicitly through the columns' manifest * `display_config`: `card_title: true` picks the title, `card: true` marks each * field the card shows, in column order (`explicit` is then true and ALL the * flagged fields are shown — the card is no longer capped at `maxFields`). * Without any flag the legacy heuristic applies: the first searchable / text * column is the title and the next `maxFields` visible columns are the fields. */ export declare function selectCardColumns(metadata: TableMetadata, maxFields?: number): { title: ColumnDefinition | null; fields: ColumnDefinition[]; explicit: boolean; }; /** * Whether a card's date field is past due — the SDK paints it as a warning. * A column opts in with `display_config.overdue: true`; the optional * `display_config.overdue_unless` (`{ : [values…] }`) exempts cards whose * row matches (a work order already delivered is never late). Pure — exported * for unit tests. */ export declare function isCardOverdue(card: any, col: ColumnDefinition, now?: number): boolean; /** * The metadata regrouped by another column (a nav entry's `group_by`). A model * can serve several boards over the same records — by stage, by technician, by * bay — but the host projects ONE `group_by` per model, so the entry's own wins. * The stage machine (stages, transitions, smart lanes) belongs to the model's * stage column (`stage_field`; older hosts that do not serve it: the served * group_by): grouping by it keeps the machine, grouping by anything else drops * it and the lanes come from the new column. * * The served group_by is the LAST kanban nav entry's, which need not be the * stage column: a board opened without `?group_by=` (the list's view toggle) * groups by the stage column, and a board grouped by the served column keeps * the machine only when that column IS the stage column. Otherwise the stage * lanes were painted over another column (by bay: every card under «Sin etapa» * and a drop wrote `service_bay_id = 'diagnosis'`, 422). Returns the SAME * object when there is nothing to change. Pure — exported for unit tests. */ export declare function withGroupBy(metadata: TableMetadata | null, groupBy: string | undefined): TableMetadata | null; /** * Whether the board's lanes must come from the group_by column's REFERENCE * (one lane per technician, per bay, per owner…): the metadata declares no * stages, the column has no inline options, but it points at a record list. * Pure — exported for unit tests. */ export declare function refLaneSource(metadata: TableMetadata | null): { ref?: string; endpoint?: string; } | null; /** * The metadata with one lane per resolved option of a reference group_by column * (labels ordered alphabetically). No transitions are declared, so a card can * be dragged to any lane — i.e. re-assigned to another technician / bay. * Returns the metadata untouched when there are no options. Pure. */ export declare function withRefLanes(metadata: TableMetadata, options: { value: string | number; label: string; color?: string | null; }[]): TableMetadata; /** * The value a card cell should render for a column — same resolution the * table's cells apply. An FK column (`_id`) prefers the backend-resolved * sibling object (`card.`, e.g. `{ name }` / `{ value, label }`) over the * raw UUID; a zero-UUID FK counts as unset (renders the em-dash). Pure — * exported for unit tests. */ export declare function cardCellValue(card: any, col: ColumnDefinition): unknown; /** * Plain text of a card cell — what a person reads on the card. A resolved * relation reads as its label/name (never the raw FK uuid), so the lane search * and the drag announcements match what is on screen. Pure — exported for tests. */ export declare function cardCellText(card: any, col: ColumnDefinition): string; /** * Whether a card field has nothing worth a row: unset, blank, an empty list, * `false`, or an opaque id that no relation resolved to a name (a soft-ref * uuid column). The card hides those rows instead of painting "Campo: —". * Pure — exported for unit tests. */ export declare function isEmptyCardValue(card: any, col: ColumnDefinition): boolean; /** * The fields a card shows: the first `max` candidates that carry a value for * THIS card. Candidates come in metadata order, so a card with an empty * "Asignado a" shows the next useful field instead of a dash. Pure. */ export declare function visibleCardFields(card: any, candidates: ColumnDefinition[], max?: number): ColumnDefinition[]; /** * Records as the board paints them: each card with an unconfirmed move shows in * its destination lane. The fetched records stay as the server sent them, so a * rejected move just drops its override and the card is back where it was. * Pure — exported for unit tests. */ export declare function applyStageOverrides(records: any[], overrides: Record | undefined, groupByKey: string): any[]; /** Per-lane change an in-flight move makes to the server totals. */ export interface LaneDelta { count: number; sums: Record; } /** * How the unconfirmed moves shift each lane's server count and aggregate sums: * the source lane loses the card (and its values), the destination gains them. * Lets the header totals follow the drag live and snap back on a rollback * without touching the server figures. Pure — exported for unit tests. */ export declare function laneDeltasFromOverrides(records: any[], overrides: Record | undefined, groupByKey: string, aggregateKeys?: string[]): Record; /** * Returns a NEW per-lane aggregate map with a card's numeric values moved from * `fromStage` to `toStage`. Lanes with no known sums yet are left alone. Pure. */ export declare function applyLaneAggregatesOnMove(aggregates: Record>, card: any, fromStage: string, toStage: string, aggregateKeys: string[]): Record>; /** A lane's on-screen box, as the keyboard navigation reads it. */ export interface LaneRect { id: string; left: number; top: number; width: number; } /** * Keyboard drag: where the dragged card goes when the user presses ← or →. * Picks the nearest lane to the left/right of the card's current center and * returns the top-left coordinates that center the card in that lane, just * under its header. Undefined when there is no lane further in that direction. * Pure — exported for unit tests. */ export declare function nextLaneCoordinates(direction: 'left' | 'right', current: { left: number; top: number; width: number; }, lanes: LaneRect[]): { x: number; y: number; } | undefined; /** * Whether a card passes a lane funnel. Picked select/facet `values` match by * equality (IN — the card's field value must be one of them); a free-text * `text` matches by case-insensitive substring. No field / no criteria → passes. * Pure — exported for unit tests. */ export declare function cardMatchesLaneFunnel(card: any, filter: { field?: string; values?: string[]; text?: string; } | undefined): boolean; /** * Count of applied criteria on a lane funnel: the number of picked select/facet * `values`, else 1 for a free-text `text`, else 0. Drives the funnel's count * badge. Pure — exported for unit tests. */ export declare function laneFunnelCount(value: { values?: string[]; text?: string; } | undefined): number; /** * Whether a card matches a free-text lane search: a case-insensitive substring * over the card's title + every visible field value (`String(v)`). Empty query * matches everything. Pure — exported for unit tests. */ export declare function cardMatchesLaneQuery(card: any, cols: ColumnDefinition[], query: string): boolean; export interface DynamicKanbanProps { /** Model key as registered on the backend (e.g. "issue"). */ model: string; /** * Data endpoint base — the org-scoped LIST endpoint (e.g. * `/data//me`). The optimistic update PUTs to `/`. */ endpoint?: string; /** Bump to force a metadata + records refetch (same contract as DynamicTable). */ refreshTrigger?: any; /** * Refetch the board when the host's realtime client reports a data event * for this model (same contract as DynamicTable's `realtime`). Off by * default; `` flips the default. */ realtime?: boolean; /** Called when a card is clicked (outside its action menu). */ onCardClick?: (row: any) => void; /** * Host hook for `view`/`edit` card actions (STRING contract — same as * DynamicTable's `onAction`). When provided, `view`/`edit` route to the host * (e.g. its seeded record modal); when omitted they open the SDK's built-in * record dialog. `delete`/link/custom actions are always handled in-SDK. */ onAction?: (action: string, row: any) => void; /** * Consumer-side per-row gate for row actions, AND-ed with the metadata * gates (`requiresState` + `condition`): it can only hide more. Receives the * full action definition (adapt an `(actionKey, row)` predicate with * `(a, row) => legacy(a.key, row)`). A throw hides the action (fail-closed, * `console.error`). Memoize it (module-level fn or `useCallback`). * Omitted → behaviour unchanged. Not a substitute for backend authorization. */ isRowActionVisible?: RowActionPredicate; /** * Size of the INITIAL board page (one request, grouped into lanes). Each * lane then tops up incrementally on scroll (see `lanePageSize`). Defaults * to 50 — enough to fill the visible lanes without loading the whole board. */ pageSize?: number; /** * Page size for a lane's incremental top-up fetch (scoped by * `f_=`). Defaults to 25. */ lanePageSize?: number; /** IANA timezone for datetime card fields (org config). */ timeZone?: string; /** ISO 4217 currency for money card fields (org config). */ currency?: string; /** * Column to group the board by, when the host's nav entry asks for one * (`?group_by=`): a model can offer several boards over the same records — * by stage, by technician, by bay. Absent or equal to the served * `group_by` → the model's own stage machine. Otherwise the lanes come from * that column (its options, or its referenced records) with no transitions, * and the per-org stage customisations (custom lanes, overrides, lane order, * automations) are off: they belong to the stage column. */ groupBy?: string; /** * Static equality filters always applied to the board (never shown as a * removable chip). Same contract as DynamicTable's `defaultFilters`. */ defaultFilters?: Record; } /** * The optimistic card move runs on TanStack Query (useOptimisticMutation). A * host that mounts no QueryClientProvider still gets a working board: the * kanban brings its own client in that case. */ export declare function DynamicKanban(props: DynamicKanbanProps): React.JSX.Element; //# sourceMappingURL=dynamic-kanban.d.ts.map