import { useEffect, useMemo, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'; import { apiClient } from '../lib/apiClient'; import { useTasks } from '../hooks/useTasks'; import { useBoardReferenceDate } from '../hooks/useBoardReferenceDate'; import { DashboardHeader } from '../components/dashboard/DashboardHeader'; import { DummyDepartmentColumn } from '../components/dummy/DummyDepartmentColumn'; import { HubColumn } from '../components/dashboard/HubColumn'; import { BoardConnectors } from '../components/dashboard/BoardConnectors'; import { isProjectTask, isRoutineTask } from '../lib/taskType'; import { hasVisibleIssue } from '../lib/taskIssues'; import { DEFAULT_STATUS_FILTERS, taskMatchesStatusFilters, toggleAllFilter, toggleCoreFilter, type CoreStatusFilter, } from '../lib/statusFilters'; import { LEGACY_COLUMN_KEYS, SECTIONS } from '../lib/sections'; import { BOARD_SLOT_ORDER, BOARD_SLOTS, getBoardSlot, slotSectionLabel, taskBelongsToBoardSlot, } from '../lib/boardLayout'; import '../styles/dummy-board.css'; function mergeCardOrders(primary: string | null | undefined, extras: (string | null | undefined)[]): string[] { const merged: string[] = []; const push = (raw: string | null | undefined) => { if (!raw) return; try { const ids = JSON.parse(raw) as string[]; for (const id of ids) { if (!merged.includes(id)) merged.push(id); } } catch { /* ignore */ } }; push(primary); extras.forEach(push); return merged; } export default function DummyDashboardPage() { const [activeFilters, setActiveFilters] = useState([...DEFAULT_STATUS_FILTERS]); const [filtersBeforeIssue, setFiltersBeforeIssue] = useState([...DEFAULT_STATUS_FILTERS]); const [issueFilterActive, setIssueFilterActive] = useState(false); const [teamPanelOpen, setTeamPanelOpen] = useState(false); const [columnOrders, setColumnOrders] = useState>({}); const { referenceDate, setReferenceDate, quarter } = useBoardReferenceDate(); const { data: tasks = [], isLoading } = useTasks({ quarter }); const { data: colConfigs } = useQuery({ queryKey: ['columns', 'all', ...SECTIONS], queryFn: async () => { const results = await Promise.all( SECTIONS.map(async (s) => { const main = await apiClient.get(`/columns/${encodeURIComponent(s)}`).then((r) => r.data); const legacy = await Promise.all( LEGACY_COLUMN_KEYS[s].map((key) => apiClient.get(`/columns/${encodeURIComponent(key)}`).then((r) => r.data).catch(() => null), ), ); const cardOrder = JSON.stringify( mergeCardOrders(main.cardOrder, legacy.map((l) => l?.cardOrder)), ); return { key: s, ...main, cardOrder }; }), ); return results; }, staleTime: 0, }); useEffect(() => { if (!colConfigs) return; setColumnOrders((prev) => { const next = { ...prev }; BOARD_SLOTS.forEach((slot) => { const label = slotSectionLabel(slot); if (next[label]) return; if (!slot.sectionKey) return; const cfg = colConfigs.find((c) => c.key === slot.sectionKey); if (cfg?.cardOrder) { try { next[label] = JSON.parse(cfg.cardOrder); } catch { /* ignore */ } } }); return next; }); }, [colConfigs]); useEffect(() => { BOARD_SLOTS.forEach((slot) => { const label = slotSectionLabel(slot); const ids = tasks.filter((t) => taskBelongsToBoardSlot(t, slot)).map((t) => t.id); setColumnOrders((prev) => { const existing = prev[label] ?? []; const merged = [ ...existing.filter((id) => ids.includes(id)), ...ids.filter((id) => !existing.includes(id)), ]; if (JSON.stringify(merged) === JSON.stringify(existing)) return prev; return { ...prev, [label]: merged }; }); }); }, [tasks]); const filtered = tasks.filter((t) => { if (issueFilterActive) return hasVisibleIssue(t); return taskMatchesStatusFilters(t, activeFilters); }); const boardProjectTasks = useMemo( () => filtered.filter( (t) => isProjectTask(t.taskType) && BOARD_SLOTS.some((slot) => taskBelongsToBoardSlot(t, slot)), ), [filtered], ); const stats = useMemo( () => ({ total: boardProjectTasks.length, inProgress: boardProjectTasks.filter((t) => t.status === 'IN_PROGRESS').length, review: boardProjectTasks.filter( (t) => t.status === 'REVIEW' || t.status === 'CANCELLED' || t.status === 'TODO', ).length, done: boardProjectTasks.filter((t) => t.status === 'DONE').length, issues: boardProjectTasks.filter((t) => hasVisibleIssue(t)).length, }), [boardProjectTasks], ); const routineTasks = useMemo( () => filtered.filter((t) => isRoutineTask(t.taskType)), [filtered], ); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 9999 } }), ); const renderDeptSlot = (slotId: typeof BOARD_SLOT_ORDER[number]) => { const slot = getBoardSlot(slotId); const label = slotSectionLabel(slot); return ( taskBelongsToBoardSlot(t, slot))} orderedIds={columnOrders[label] ?? []} /> ); }; if (isLoading) { return (
데이터를 불러오는 중...
); } return (
{ setIssueFilterActive(false); setActiveFilters((prev) => toggleAllFilter(prev)); }} onToggleStatus={(key: CoreStatusFilter) => { setIssueFilterActive(false); setActiveFilters((prev) => toggleCoreFilter(prev, key)); }} onToggleIssue={() => { if (issueFilterActive) { setIssueFilterActive(false); setActiveFilters([...filtersBeforeIssue]); return; } setFiltersBeforeIssue([...activeFilters]); setIssueFilterActive(true); }} onOpenDetailWindow={() => {}} onOpenTaskManager={() => {}} teamPanelOpen={teamPanelOpen} onToggleTeamPanel={() => setTeamPanelOpen((v) => !v)} />
{renderDeptSlot('hrm')} {renderDeptSlot('hrd')} {renderDeptSlot('ex')} {renderDeptSlot('ga')}
); }