/* ============================================================================= * B01_Dashboard_UI_ProjectList_Parts.ts * 프로젝트 목록 · 상세 공용 조각(PLAN 548) — 종류 배지 · 단계 글 · 진행 막대 · 썸네일 선 · 칸반 칸 이름. * ========================================================================== */ import { el } from "@ui/ui_template_elements_base"; import { workflowSteps } from "../A00_Common/b_page_scaffold"; import { goToWorkflowStage, WORKFLOW_STEP_ROUTES } from "../A00_Common/b_workflow_nav"; import { groupFlowOf } from "../A00_Common/group_workflow"; import type { RoutePath } from "@config/config_frontend"; import type { BoardColumn, OverviewProject } from "./B01_Dashboard_Api_Projects"; import { agoText } from "../common_util/common_util_time"; import { L } from "./B01_Dashboard_UI_Common"; /** 종류 — 칩 · 배지 차례 · 사방설계 = 준비 중 */ export const KINDS = [ { key: "ROAD", label: () => L("DE_New_Road") }, { key: "PLAN", label: () => L("DE_New_Plan") }, { key: "STABILITY", label: () => L("DE_New_Stability") }, { key: "EROSION", label: () => L("DE_New_Erosion"), soon: true }, ] as const; export const kindLabel = (kind?: string | null): string => (KINDS.find((k) => k.key === (kind ?? "ROAD")) ?? KINDS[0]).label(); export function kindBadge(kind?: string | null): HTMLElement { const key = (kind ?? "ROAD").toLowerCase(); return el("span", { className: `b01-proj-badge b01-proj-badge--${key}`, text: kindLabel(kind) }); } export const BOARD_COLUMNS: { key: BoardColumn; label: string }[] = [ { key: "start", label: "시작 전" }, { key: "design", label: "설계 중" }, { key: "review", label: "검토" }, { key: "confirm", label: "확정 대기" }, { key: "done", label: "완료" }, ]; export const boardLabel = (key: BoardColumn): string => BOARD_COLUMNS.find((c) => c.key === key)?.label ?? key; /** 종류별 단계 이름 · 주소 */ export function stageFlow(kind?: string | null): { steps: string[]; routes: readonly RoutePath[]; } { const group = groupFlowOf(kind); return group ? { steps: group.steps, routes: group.routes } : { steps: workflowSteps(), routes: WORKFLOW_STEP_ROUTES }; } /** 지금 단계 번호(0 부터) · 마친 단계 수 */ export function stageProgress(project: OverviewProject): { current: number; done: number } { const stages = project.workflow_state?.stages ?? []; return { current: project.workflow_state?.current_stage ?? 0, done: stages.filter((s) => s.state === "COMPLETE").length, }; } /** 「횡단설계 4/7」 — 시작 전 = 0 · 완료 = 전체 */ export function stageText(project: OverviewProject): string { const { steps } = stageFlow(project.project_kind); const total = project.stage_total || steps.length; if (!total) return L("DE_New_Soon"); const { current } = stageProgress(project); if (project.board === "done") return `완료 ${total}/${total}`; const n = project.board === "start" ? 0 : current + 1; return `${steps[current] ?? ""} ${n}/${total}`; } export function progressBar(project: OverviewProject): HTMLElement { const total = project.stage_total || 1; const bar = el("span", { className: "b01-proj-bar" }); const fill = el("i"); fill.style.width = `${Math.round((stageProgress(project).done / total) * 100)}%`; bar.append(fill); return bar; } /** 단계 점 — 마침 · 지금 · 남음 */ export function stageDots(project: OverviewProject): HTMLElement { const total = project.stage_total; const { current } = stageProgress(project); const states = new Map((project.workflow_state?.stages ?? []).map((s) => [s.stage_no, s.state])); const row = el("span", { className: "b01-proj-dots" }); for (let i = 0; i < total; i += 1) { const state = states.get(i); const cls = state === "COMPLETE" ? "is-done" : state === "STALE" || state === "FAILED" ? "is-stale" : i === current && project.board !== "start" ? "is-cur" : ""; row.append(el("i", { className: cls })); } return row; } /** 단계 화면으로 — 종류별 주소 */ export function openStage(project: OverviewProject, index: number): void { const { routes } = stageFlow(project.project_kind); const route = routes[Math.max(0, Math.min(index, routes.length - 1))]; if (route) void goToWorkflowStage(project.id, route); } /** 썸네일 선 — 카드 요약(547-1) 0~1 칸 좌표(y 아래로) · 비율 그대로 가운데 · 없으면 빈 판 */ export function thumbSvg(points: number[][] | null | undefined): SVGElement { const ns = "http://www.w3.org/2000/svg"; const svg = document.createElementNS(ns, "svg"); svg.setAttribute("viewBox", "-0.1 -0.1 1.2 1.2"); svg.classList.add("b01-proj-thumb"); if (!points || points.length < 2) return svg; const path = document.createElementNS(ns, "path"); path.setAttribute("d", points.map(([x, y], i) => `${i ? "L" : "M"}${x} ${y}`).join(" ")); svg.append(path); return svg; } /** 「오늘」 · 「어제」 · 「n 일 전」 · 그 밖 날짜 */ export function ago(project: OverviewProject): string { return agoText(project.updated_at); }