- 위험 채움 토큰 밝음 #e5484d → #d6363c(흰 글 3.91 → 4.72) · 어두움 5.6 · 위험 글 #c0392b 그대로 - 새 부품 계산 줄(ui_template_calc_row) — B08 수리집수면적표 b08-hydro__row · 식 칸 배치를 대신함 - 표 부품 칸 램프(ui-data-table__lamp · --only · ✓) — B08 집계표 · 거리이정표 · B09 설계내역 램프 CSS 세 벌을 걷음(표지 class 는 풍선 · 가이드용으로 남김) - 모달 넓은 중간 창(wide + width 1100px · 1100 × 680) — B09 운반 창 손 인라인 크기를 걷음 - 값 목록 규칙 꼴(rule) — ☰ 설정 규칙 줄 ul/li 를 걷음 - 접이 구역 머리(createCollapsibleHead = 패널 머리 + 접는 제목) — B08 구조물도 「조합 연결」 제목(옛 b08-sheets__title)을 대신함 - 일반 화면 틀 좁은 폭 · 입력 칸 2열 · 끝 단추 줄 — D01 · E01 이 B02 CSS 를 빌리던 것을 끊음(가이드 장면 선택자 맞춤) - 부품 모음 견본 · 지킴 시험 test_557_2 · 낡은 시험(198_1 · 247_1 · 251_1 · 257_1 · 276_1 · 551_1)은 부품 CSS · 새 class 로 맞춤 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
168 lines
6.9 KiB
TypeScript
168 lines
6.9 KiB
TypeScript
/* =============================================================================
|
|
* D01_PlanRegister_UI_Page.ts
|
|
* D01 예정노선 등록(요구서 345 3-1 · PLAN 529-1) — B02 등록 화면 꼴 · 도면 칸 없음.
|
|
* 칸 = 사업연도 시작 · 끝 · 사업지역 · 종류 「노선 계획」(고정) · 임도 종류(B02 값) · 설계속도
|
|
* (종류별 고를 수 있는 값 · 기본 = 가장 낮은 값 = B05 resolve_design_speed) · 프로젝트명(B02 이름
|
|
* 조합기 · 연도 자리 = 「시작~끝」) · 노선 시작 · 종료 누가거리.
|
|
* [등록] = POST /api/d01/project → 현재 프로젝트 · D02 로.
|
|
* ========================================================================== */
|
|
|
|
import { ROUTES } from "@config/config_frontend";
|
|
import {
|
|
createButton,
|
|
createCard,
|
|
createInputField,
|
|
createSelectField,
|
|
} from "@ui/ui_template_elements";
|
|
import { createFormGrid, createGeneralLayout } from "@ui/ui_template_general_layout";
|
|
import { createHelpButton } from "@ui/ui_template_help";
|
|
import { createText } from "@ui/ui_template_text";
|
|
import { t } from "@ui/ui_template_locale";
|
|
import { stationLabel } from "../common_util/common_util_station";
|
|
import { createGroupProject } from "../A00_Common/group_workflow";
|
|
import { navigateTo } from "../A00_Common/router";
|
|
import { createNameComposer } from "../B02_ProjRegister/B02_ProjRegister_UI_Name";
|
|
import { SPEED_CHOICES } from "../B05_Profile/logic/B05_Profile_Grade_Criteria";
|
|
|
|
/** B02 임도 종류 → 설계속도 기준 등급(B05 grade_class_of 와 같음) */
|
|
const GRADE_CLASS: Record<string, string> = { main: "trunk", fire: "fire", work: "work" };
|
|
|
|
/** 측점 간격 — D03 `PLAN_STATION_INTERVAL_M` 와 같음 */
|
|
const STATION_INTERVAL_M = 20;
|
|
|
|
const numberOrNull = (text: string): number | null =>
|
|
text.trim() === "" ? null : Number.parseFloat(text);
|
|
|
|
export function renderD01PlanRegister(root: HTMLElement): void {
|
|
const year = new Date().getFullYear();
|
|
const yearStart = createInputField({
|
|
label: t("DE_Reg_YearStart"),
|
|
type: "number",
|
|
value: String(year),
|
|
min: 2000,
|
|
max: 2100,
|
|
});
|
|
const yearEnd = createInputField({
|
|
label: t("DE_Reg_YearEnd"),
|
|
type: "number",
|
|
value: String(year),
|
|
min: 2000,
|
|
max: 2100,
|
|
});
|
|
const region = createInputField({
|
|
label: t("B02_Proj_Field_Region"),
|
|
placeholder: t("B02_Proj_Field_Region_Placeholder"),
|
|
required: true,
|
|
});
|
|
const kind = createInputField({ label: t("DE_Reg_Kind"), value: t("DE_Reg_KindPlan") });
|
|
kind.input.readOnly = true;
|
|
const roadType = createSelectField({
|
|
label: t("B02_Proj_Field_RoadType"),
|
|
options: [
|
|
{ value: "main", text: t("B02_Proj_RoadType_Main") },
|
|
{ value: "fire", text: t("B02_Proj_RoadType_Fire") },
|
|
{ value: "work", text: t("B02_Proj_RoadType_Work") },
|
|
],
|
|
});
|
|
const speed = createSelectField({ label: t("DE_Reg_DesignSpeed"), options: [] });
|
|
const name = createInputField({ label: t("DE_Reg_Name"), required: true });
|
|
const startM = createInputField({ label: t("DE_Reg_StartM"), type: "number", min: 0 });
|
|
const endM = createInputField({ label: t("DE_Reg_EndM"), type: "number", min: 0 });
|
|
// 누가거리 칸 옆 즉시 측점 표기(저장값 · 서버 검사는 m 그대로)
|
|
for (const field of [startM, endM]) {
|
|
const shown = createText("", "muted");
|
|
shown.dataset.station = "";
|
|
field.root.insertBefore(shown, field.root.lastElementChild);
|
|
field.input.addEventListener("input", () => {
|
|
const value = numberOrNull(field.input.value);
|
|
shown.textContent =
|
|
value !== null && Number.isFinite(value) && value >= 0
|
|
? `= ${stationLabel(value, STATION_INTERVAL_M, 1)}`
|
|
: "";
|
|
});
|
|
}
|
|
|
|
// 설계속도 = 종류별 고를 수 있는 값 · 기본 = 가장 낮은 값(20)
|
|
const syncSpeeds = (): void => {
|
|
const choices = SPEED_CHOICES[GRADE_CLASS[roadType.select.value]] ?? [20];
|
|
speed.setOptions(
|
|
choices.map((kph) => ({ value: String(kph), text: `${kph} km/h` })),
|
|
String(Math.min(...choices)),
|
|
);
|
|
};
|
|
|
|
// 이름 = B02 조합기 그대로(손대기 전 = 자동 · 고친 뒤 = 조각만 갈아 끼움)
|
|
const composer = createNameComposer();
|
|
const years = (): string => {
|
|
const [a, b] = [yearStart.input.value.trim(), yearEnd.input.value.trim()];
|
|
return b && b !== a ? `${a}~${b}` : a;
|
|
};
|
|
const syncName = (): void => {
|
|
name.input.value = composer.update({
|
|
year: years(),
|
|
region: region.input.value,
|
|
type: roadType.select.selectedOptions[0]?.textContent ?? "",
|
|
});
|
|
};
|
|
name.input.addEventListener("input", () => composer.edit(name.input.value));
|
|
for (const field of [yearStart, yearEnd, region]) field.input.addEventListener("input", syncName);
|
|
roadType.select.addEventListener("change", () => (syncSpeeds(), syncName()));
|
|
syncSpeeds();
|
|
syncName();
|
|
|
|
const submit = (): void => {
|
|
const fields = [yearStart, yearEnd, region, name, startM, endM];
|
|
fields.forEach((field) => field.setError());
|
|
const y0 = Number.parseInt(yearStart.input.value, 10);
|
|
const y1 = Number.parseInt(yearEnd.input.value, 10);
|
|
const [s, e] = [numberOrNull(startM.input.value), numberOrNull(endM.input.value)];
|
|
const errors: Array<[(typeof fields)[number], string | false]> = [
|
|
[yearStart, !(y0 >= 2000 && y0 <= 2100) && t("Common_Validation_NumberRange")],
|
|
[yearEnd, !(y1 >= y0 && y1 <= 2100) && t("DE_Reg_YearOrder")],
|
|
[region, !region.input.value.trim() && t("B02_Proj_Error_Required")],
|
|
[name, !name.input.value.trim() && t("B02_Proj_Error_Required")],
|
|
[startM, s !== null && !(s >= 0) && t("Common_Validation_NumberRange")],
|
|
[endM, e !== null && s !== null && !(e > s) && t("DE_Reg_ChainOrder")],
|
|
];
|
|
const bad = errors.filter(([, message]) => message);
|
|
bad.forEach(([field, message]) => field.setError(message as string));
|
|
if (bad.length) return;
|
|
void createGroupProject(
|
|
"/d01/project",
|
|
{
|
|
project_name: name.input.value.trim(),
|
|
project_year: y0,
|
|
project_year_end: y1,
|
|
region: region.input.value.trim(),
|
|
road_type: roadType.select.value,
|
|
design_speed_kph: Number(speed.select.value),
|
|
start_chainage_m: s,
|
|
end_chainage_m: e,
|
|
},
|
|
ROUTES.D02_PLAN_BASE,
|
|
);
|
|
};
|
|
|
|
const grid = createFormGrid(
|
|
[yearStart, yearEnd, region, kind, roadType, speed, name, startM, endM].map((f) => f.root),
|
|
);
|
|
const actions = document.createElement("div");
|
|
actions.className = "ui-general-block__actions ui-general-block__actions--end";
|
|
actions.append(
|
|
createHelpButton(),
|
|
createButton({
|
|
label: t("Common_Btn_Cancel"),
|
|
variant: "ghost",
|
|
onClick: () => navigateTo(ROUTES.B01_ACCOUNT),
|
|
}),
|
|
createButton({ label: t("DE_Reg_Submit"), variant: "filled", onClick: submit }),
|
|
);
|
|
const layout = createGeneralLayout({
|
|
narrow: true,
|
|
title: t("DE_Reg_PlanTitle"),
|
|
subtitle: t("DE_Reg_PlanSubtitle"),
|
|
content: createCard({ body: [grid, actions], raised: true }),
|
|
});
|
|
root.append(layout.root);
|
|
}
|