import { KANBAN_GROUPS } from "../../shared/constants.js"; import { DropdownField } from "../../shared/DropdownField.jsx"; import { FilterIcon, RefreshIcon } from "../../shared/icons.jsx"; import { fallbackStatusGroup, fmtKanbanDate, resolveDeadlineTone, statusLabel } from "../../shared/utils.js"; export function KanbanBoard({ loading, columns, rows, role, actorId, filters, onRefresh, onOpenFilter, onRemoveFilter, onEditFilter, getFilterChipLabel, onOpenSort, sortActive, onOpenRequest, onClaimRequest, onMoveRequest, status, FilterToolbarComponent, StatusLineComponent, }) { const { useMemo, useState } = React; const [draggingId, setDraggingId] = useState(""); const [dragOverGroup, setDragOverGroup] = useState(""); const safeColumns = Array.isArray(columns) && columns.length ? columns : KANBAN_GROUPS; const grouped = useMemo(() => { const map = {}; safeColumns.forEach((column) => { map[String(column.key)] = []; }); (rows || []).forEach((row) => { const group = String(row?.status_group || fallbackStatusGroup(row?.status_code)); if (!map[group]) map[group] = []; map[group].push(row); }); return map; }, [rows, safeColumns]); const rowMap = useMemo(() => { const map = new Map(); (rows || []).forEach((row) => { if (!row?.id) return; map.set(String(row.id), row); }); return map; }, [rows]); const onDropToGroup = (event, groupKey) => { event.preventDefault(); const requestId = String(event.dataTransfer.getData("text/plain") || draggingId || ""); setDragOverGroup(""); setDraggingId(""); if (!requestId) return; const row = rowMap.get(requestId); if (!row) return; onMoveRequest(row, String(groupKey || "")); }; const FilterToolbar = FilterToolbarComponent; const StatusLine = StatusLineComponent; return (

Канбан заявок

Группировка по группам статусов и серверная фильтрация карточек.

{FilterToolbar ? ( ) : null}
{safeColumns.map((column) => { const key = String(column.key || ""); const cards = grouped[key] || []; const isOver = dragOverGroup === key; return (
{ event.preventDefault(); setDragOverGroup(key); }} onDragLeave={(event) => { if (event.currentTarget.contains(event.relatedTarget)) return; setDragOverGroup((prev) => (prev === key ? "" : prev)); }} onDrop={(event) => onDropToGroup(event, key)} >
{column.label || key} {Number(column.total ?? cards.length)}
{cards.length ? ( cards.map((row) => { const requestId = String(row.id || ""); const isUnassigned = !String(row.assigned_lawyer_id || "").trim(); const canClaim = role === "LAWYER" && isUnassigned; const canMove = role === "ADMIN" || (!isUnassigned && String(row.assigned_lawyer_id || "").trim() === String(actorId || "").trim()); const transitionOptions = Array.isArray(row.available_transitions) ? row.available_transitions : []; const deadline = row.sla_deadline_at || row.case_deadline_at || ""; const deadlineTone = resolveDeadlineTone(deadline); const unreadTypes = new Set(); if (role === "LAWYER") { if (row.lawyer_has_unread_updates && row.lawyer_unread_event_type) unreadTypes.add(String(row.lawyer_unread_event_type).toUpperCase()); } else { if (row.client_has_unread_updates && row.client_unread_event_type) unreadTypes.add(String(row.client_unread_event_type).toUpperCase()); if (row.lawyer_has_unread_updates && row.lawyer_unread_event_type) unreadTypes.add(String(row.lawyer_unread_event_type).toUpperCase()); } const hasUnreadMessage = unreadTypes.has("MESSAGE"); const hasUnreadAttachment = unreadTypes.has("ATTACHMENT"); return (
onOpenRequest(requestId, event)} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); onOpenRequest(requestId, event); } }} onDragStart={(event) => { if (!canMove) { event.preventDefault(); return; } setDraggingId(requestId); event.dataTransfer.effectAllowed = "move"; event.dataTransfer.setData("text/plain", requestId); }} onDragEnd={() => { setDraggingId(""); setDragOverGroup(""); }} >
{row.status_name || statusLabel(row.status_code)}

{String(row.description || "Описание не заполнено")}

{row.client_name || "-"} {fmtKanbanDate(row.created_at)}
{row.topic_code || "-"} {row.assigned_lawyer_name || (isUnassigned ? "Не назначено" : row.assigned_lawyer_id || "-")}
💬 📎
{deadline ? fmtKanbanDate(deadline) : "—"}
event.stopPropagation()} onMouseDown={(event) => event.stopPropagation()} > {canClaim ? ( ) : null} {canMove && transitionOptions.length ? (
event.stopPropagation()}> { const targetStatus = String(nextValue || ""); if (!targetStatus) return; onMoveRequest(row, "", targetStatus); }} options={transitionOptions.map((transition) => ({ value: String(transition.to_status), label: String(transition.to_status_name || transition.to_status), }))} />
) : null}
); }) ) : (

Пусто

)}
); })}
{StatusLine ? : null}
); } export default KanbanBoard;