/* ============================================================================= * 「프로젝트」 탭(92장) — 프로젝트 표(찾기 · 거르기 · 쪽 넘김) + 진행 현황 + 임시 보관함. * ========================================================================== */ import { ROUTES } from "@config/config_frontend"; import { createButton, createInputField, createSelectField, createTag, type TagVariant, } from "@ui/ui_template_elements"; import { section, text } from "@ui/ui_template_general_blocks"; import { workflowSteps } from "../A00_Common/b_page_scaffold"; import { currentQuery, navigateTo } from "../A00_Common/router"; import type { ProjectItem, WorkflowStageState } from "./B01_Dashboard_Api_Fetch"; import { formatDate, formatTime, L, stackedCell } from "./B01_Dashboard_UI_Common"; import { createPagedTable } from "./B01_Dashboard_UI_Paging"; import type { DashboardState } from "./B01_Dashboard_UI_Page"; import { projectHeaders, projectRow } from "./B01_Dashboard_UI_Projects"; import { matchesFilter, matchesQuery, parseFilter, progressStage, STEP_COUNT, type ProjectFilter, } from "./B01_Dashboard_UI_ProjectStats"; import { buildTempUploadSection } from "./B01_Dashboard_UI_TempUpload"; const STATE_TAG: Record = { NOT_STARTED: "neutral", IN_PROGRESS: "accent", COMPLETE: "success", FAILED: "danger", STALE: "warning", }; function filterOptions(): { value: string; text: string }[] { const steps = workflowSteps(); return [ { value: "all", text: L("B01_Pj_FilterAll") }, { value: "failed", text: L("B01_Ov_Failed") }, { value: "stale", text: L("B01_Ov_Stale") }, ...Array.from({ length: STEP_COUNT }, (_, i) => ({ value: `stage-${i}`, text: `${L("B01_Pj_Stage")} · ${steps[i]}`, })), ]; } function progressRow(project: ProjectItem): HTMLElement[] { const stage = progressStage(project); if (!stage) return [ text(project.name), text(null), text(null), text(null), text(null), text(null), text(null), ]; const when = (value?: string | null): HTMLElement => value ? stackedCell(formatDate(value), formatTime(value)) : text(null); return [ text(project.name), text(workflowSteps()[stage.stage_no] ?? stage.stage_key), createTag(L(`B01_Pj_State_${stage.state}`), STATE_TAG[stage.state]), text(`${Math.round(stage.progress_percent)}%`), text(stage.message), when(stage.started_at), when(stage.completed_at), ]; } function progressHeaders(): string[] { return [ L("B01_Dashboard_Table_Project"), L("B01_Pj_Stage"), L("B01_Pj_State"), L("B01_Pj_Percent"), L("B01_Pj_Message"), L("B01_Pj_Started"), L("B01_Pj_Done"), ]; } /** `projects` = 역할별 프로젝트 목록 · 거르기 처음 값은 해시 `filter=`(개요에서 눌러 올 때) */ export function buildProjectsTab(state: DashboardState, projects: ProjectItem[]): HTMLElement[] { const user = state.user; const list = createPagedTable( projectHeaders(), (p: ProjectItem) => projectRow(p, user), projects, ); const progress = createPagedTable(progressHeaders(), progressRow, projects); let filter: ProjectFilter = parseFilter(currentQuery().get("filter")); const query = createInputField({ label: L("B01_Pj_Search"), placeholder: L("B01_Pj_SearchHint"), }); const filterField = createSelectField({ label: L("B01_Pj_Filter"), options: filterOptions(), value: filter, onChange: (value) => { filter = parseFilter(value); apply(); }, }); function apply(): void { const shown = projects.filter( (p) => matchesFilter(p, filter) && matchesQuery(p, query.input.value), ); list.setItems(shown); progress.setItems(shown); } query.input.addEventListener("input", apply); apply(); const filters = document.createElement("div"); filters.className = "b01-dashboard__filters"; filters.append(query.root, filterField.root); const body = document.createElement("div"); body.append(filters, list.root); return [ section(L("B01_Dashboard_Projects"), body, true, [ createButton({ label: "+", onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER) }), ]), section(L("B01_Pj_Progress"), progress.root, true), buildTempUploadSection(), ]; }