Files
Aislo/B01_Dashboard/B01_Dashboard_UI_NewProject.ts
T
eomsangdonandClaude Opus 5.5 dc124d3108 feat(528-2): 대시보드 [+] 고르기 모달 — 카드 넷(예정노선 · 임도설계 · 사방설계 · 안정성평가)
- [+] 세 자리(시스템관리자 · 회사관리자 · 사용자) = 고르기 모달
- 그림 = SVG 직접(선 = 글자 색 · 밝음/어두움) · 산림청 아니면 예정노선 흐림 + 까닭 툴팁 · 누름 = 같은 알림
- 사방 = 준비 중 · 임도 = B02 · 예정노선 = D01 · 안정성평가 = E01
- 세션 is_forest_service 칸(SessionUser)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:34:39 +09:00

90 lines
4.1 KiB
TypeScript

/* =============================================================================
* B01_Dashboard_UI_NewProject.ts
* 대시보드 [+] 고르기 모달(요구 345 · PLAN 528-2) — 카드 넷(그림 + 제목) = 예정노선 · 임도설계 ·
* 사방설계 · 안정성평가. 그림 = SVG 직접(선 색 = 글자 색 → 밝음 · 어두움 그대로).
* 예정노선 = 산림청(세션 is_forest_service)만 · 아니면 흐림 + 까닭 툴팁(누름 = 같은 까닭 알림)
* 사방설계 = 「준비 중」 · 임도설계 = B02 · 예정노선 = D01 · 안정성평가 = E01
* ========================================================================== */
import "./B01_Dashboard_UI_NewProject.css";
import { ROUTES, type RoutePath } from "@config/config_frontend";
import { el, showToast } from "@ui/ui_template_elements";
import { t, type LocaleKey } from "@ui/ui_template_locale";
import { openModal } from "@ui/ui_template_modal";
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
import { navigateTo } from "../A00_Common/router";
/** 48 칸 그림 — 선 · 면은 currentColor */
const ICONS = {
plan: `<path d="M6 12l12-5 12 5 12-5v29l-12 5-12-5-12 5z"/><path d="M18 7v29M30 12v29"/><path d="M11 33c5-3 8-12 14-12s7 6 12-4" stroke-dasharray="3 3"/><circle cx="11" cy="33" r="2.5" fill="currentColor"/><circle cx="37" cy="17" r="2.5" fill="currentColor"/>`,
road: `<path d="M8 42c6-10 18-6 20-16S20 12 26 6"/><path d="M20 42c6-10 18-6 20-16S32 12 38 6"/><path d="M14 42c6-10 18-6 20-16S26 12 32 6" stroke-dasharray="3 4"/>`,
erosion: `<path d="M4 14c6 6 10 12 10 20M44 14c-6 6-10 12-10 20"/><path d="M12 26h24v8H12z"/><path d="M16 26v8M22 26v8M28 26v8M32 26v8"/><path d="M14 40c4-2 6 2 10 0s6 2 10 0" />`,
stability: `<path d="M4 40h40"/><path d="M4 40V30l12-2 14-16h14v28"/><path d="M10 29a16 16 0 0 0 28-17" stroke-dasharray="3 3"/><path d="M36 14l3-3 1 4" />`,
} as const;
interface CardSpec {
icon: keyof typeof ICONS;
title: LocaleKey;
route?: RoutePath;
/** 못 고르는 까닭(툴팁 · 누름 알림) — 없으면 고를 수 있음 */
reason?: LocaleKey;
soon?: boolean;
}
function svgIcon(kind: keyof typeof ICONS): HTMLElement {
const box = el("span", { className: "b01-new-project__icon", attrs: { "aria-hidden": "true" } });
box.innerHTML = `<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${ICONS[kind]}</svg>`;
return box;
}
function card(spec: CardSpec, close: () => void): HTMLButtonElement {
const button = el("button", {
className: "b01-new-project__card",
attrs: { type: "button" },
children: [svgIcon(spec.icon), el("span", { text: t(spec.title) })],
});
button.dataset.newProject = spec.icon;
if (spec.soon) {
button.classList.add("is-soon");
button.append(el("span", { className: "b01-new-project__badge", text: t("DE_New_Soon") }));
}
if (spec.reason) {
button.classList.add("is-dim");
button.title = t(spec.reason);
}
button.addEventListener("click", () => {
if (spec.reason || spec.soon) return void showToast(t(spec.reason ?? "DE_New_Soon"));
close();
navigateTo(spec.route!);
});
return button;
}
/** [+] — 세션의 산림청 판정으로 예정노선 카드 켬 · 흐림 */
export async function openNewProjectPicker(): Promise<void> {
const forest = Boolean((await fetchSessionUser())?.is_forest_service);
const specs: CardSpec[] = [
{
icon: "plan",
title: "DE_New_Plan",
route: ROUTES.D01_PLAN_REGISTER,
reason: forest ? undefined : "DE_New_PlanForestOnly",
},
{ icon: "road", title: "DE_New_Road", route: ROUTES.B02_PROJ_REGISTER },
{ icon: "erosion", title: "DE_New_Erosion", soon: true },
{ icon: "stability", title: "DE_New_Stability", route: ROUTES.E01_STAB_REGISTER },
];
openModal({
title: t("DE_New_Title"),
closeLabel: t("Common_Btn_Close"),
dialogClass: "b01-new-project",
mount: (body, close) =>
body.append(
el("div", {
className: "b01-new-project__grid",
children: specs.map((spec) => card(spec, close)),
}),
),
});
}