feat(kanban): tiempo maximo por columna con borde rojo (issue 0089)
Adds column-level max time limit. Cards whose time_in_column_ms exceeds the limit show a red border + red halo. Columns marked as Done never trigger the visual regardless of the limit (per spec). Backend: - Migration 011_column_max_time.sql adds columns.max_time_minutes INTEGER NOT NULL DEFAULT 0 (0 = no limit). Aditiva, idempotente. - Column struct + ColumnPatch + UpdateColumn handle the new field; negatives clamp to 0; listing query includes it. - handleUpdateColumn (PATCH /api/columns/:id) accepts max_time_minutes in the JSON body. Frontend: - Column TS interface + UpdateColumnInput updated. - KanbanColumn context menu: new entry "Tiempo maximo" using window.prompt for low-friction config; shows current value when >0. - KanbanCard receives columnOverdue prop calculated from the column state and card.time_in_column_ms; renders red border (var --mantine-color-red-6) with 2px width + 2px red halo when overdue. - data-card-id, data-column-overdue, data-locked attributes on the card paper element so e2e tests / scripts can query state. Tests: TestColumnMaxTimeMinutes_Defaults + _Update verify the schema default, the clamp on negative input, and that updating max_time leaves other fields untouched. Visual regression of the red border kept out of automated e2e because it requires either clock control or real cards aged > N minutes; will be verified manually after merge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,7 @@ interface Props {
|
||||
users: User[];
|
||||
assignee?: User;
|
||||
inDoneColumn?: boolean;
|
||||
columnOverdue?: boolean;
|
||||
isOverlay?: boolean;
|
||||
highlight?: boolean;
|
||||
}
|
||||
@@ -86,6 +87,7 @@ function KanbanCardImpl({
|
||||
users,
|
||||
assignee,
|
||||
inDoneColumn,
|
||||
columnOverdue,
|
||||
isOverlay,
|
||||
highlight,
|
||||
}: Props) {
|
||||
@@ -162,14 +164,25 @@ function KanbanCardImpl({
|
||||
onRemoveSticker?.(card.id, index);
|
||||
};
|
||||
|
||||
const borderColorPicked = highlight
|
||||
? "var(--mantine-color-blue-5)"
|
||||
: columnOverdue
|
||||
? "var(--mantine-color-red-6)"
|
||||
: card.locked
|
||||
? "var(--mantine-color-yellow-6)"
|
||||
: colorBorder(card.color);
|
||||
const style: React.CSSProperties = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.4 : 1,
|
||||
background: colorBg(card.color),
|
||||
borderColor: highlight ? "var(--mantine-color-blue-5)" : card.locked ? "var(--mantine-color-yellow-6)" : colorBorder(card.color),
|
||||
borderWidth: highlight || card.locked ? 2 : 1,
|
||||
boxShadow: highlight ? "0 0 0 3px var(--mantine-color-blue-4)" : undefined,
|
||||
borderColor: borderColorPicked,
|
||||
borderWidth: highlight || card.locked || columnOverdue ? 2 : 1,
|
||||
boxShadow: highlight
|
||||
? "0 0 0 3px var(--mantine-color-blue-4)"
|
||||
: columnOverdue
|
||||
? "0 0 0 2px var(--mantine-color-red-3)"
|
||||
: undefined,
|
||||
filter: isDone ? "brightness(0.55) saturate(0.7)" : undefined,
|
||||
};
|
||||
|
||||
@@ -433,6 +446,9 @@ function KanbanCardImpl({
|
||||
p="xs"
|
||||
shadow={isOverlay ? "lg" : "xs"}
|
||||
radius="md"
|
||||
data-card-id={card.id}
|
||||
data-column-overdue={columnOverdue ? "true" : "false"}
|
||||
data-locked={card.locked ? "true" : "false"}
|
||||
onContextMenu={onContextMenu}
|
||||
onClick={onCardClickAddSticker}
|
||||
onDoubleClick={(e) => {
|
||||
|
||||
Reference in New Issue
Block a user