This commit is contained in:
2026-07-10 16:41:22 +09:00
parent bbda38a013
commit 50d75fcfcd
47 changed files with 6309 additions and 350 deletions
+71 -44
View File
@@ -10,10 +10,18 @@
* ========================================================================== */
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
import { createButton, createInputField, createCard, showToast } from "@ui/ui_template_elements";
import {
createButton,
createInputField,
createSelectField,
createCard,
hideLoadingOverlay,
showLoadingOverlay,
showToast,
} from "@ui/ui_template_elements";
import { isBlank } from "@util/common_util_validate";
import { navigateTo } from "../A00_Common/router";
import { ROUTES } from "@config/config_frontend";
import { API_BASE_URL, CURRENT_PROJECT_ID_KEY, ROUTES } from "@config/config_frontend";
import "./B02_ProjRegister_UI_Style.css";
/** locale 헬퍼 */
@@ -21,37 +29,6 @@ function L(key: keyof typeof ui_locales): string {
return ui_locales[key][currentLanguageIndex];
}
/** select 필드 핸들 (공통 .ui-field/.ui-input 스타일 상속) */
interface SelectFieldHandle {
root: HTMLDivElement;
select: HTMLSelectElement;
}
/** 드롭다운 필드 생성 (공통 컴포넌트에 없어 로컬 정의, 공통 스타일 상속) */
function createSelectField(
label: string,
options: { value: string; text: string }[],
): SelectFieldHandle {
const root = document.createElement("div");
root.className = "ui-field";
const labelEl = document.createElement("label");
labelEl.className = "ui-field__label";
labelEl.textContent = label;
const select = document.createElement("select");
select.className = "ui-input b02-proj__select";
for (const opt of options) {
const optionEl = document.createElement("option");
optionEl.value = opt.value;
optionEl.textContent = opt.text;
select.append(optionEl);
}
root.append(labelEl, select);
return { root, select };
}
/* -----------------------------------------------------------------------------
* 페이지 진입점
* -------------------------------------------------------------------------- */
@@ -80,12 +57,15 @@ export function renderB02ProjRegister(root: HTMLElement): void {
placeholder: L("B02_Proj_Field_Region_Placeholder"),
required: true,
});
const roadTypeField = createSelectField(L("B02_Proj_Field_RoadType"), [
{ value: "main", text: L("B02_Proj_RoadType_Main") },
{ value: "branch", text: L("B02_Proj_RoadType_Branch") },
{ value: "fire", text: L("B02_Proj_RoadType_Fire") },
{ value: "stream", text: L("B02_Proj_RoadType_Stream") },
]);
const roadTypeField = createSelectField({
label: L("B02_Proj_Field_RoadType"),
options: [
{ value: "main", text: L("B02_Proj_RoadType_Main") },
{ value: "branch", text: L("B02_Proj_RoadType_Branch") },
{ value: "fire", text: L("B02_Proj_RoadType_Fire") },
{ value: "stream", text: L("B02_Proj_RoadType_Stream") },
],
});
const currentYear = new Date().getFullYear();
const yearField = createInputField({
label: L("B02_Proj_Field_Year"),
@@ -113,12 +93,18 @@ export function renderB02ProjRegister(root: HTMLElement): void {
});
submitBtn.classList.add("b02-proj__submit");
function onB02_Proj_Submit_Click(): void {
async function onB02_Proj_Submit_Click(): Promise<void> {
nameField.setError();
regionField.setError();
yearField.setError();
lengthField.setError();
// 1차 유효성: 필수값 검사
let hasError = false;
const projectYear = Number.parseInt(yearField.input.value, 10);
const estimatedLength = isBlank(lengthField.input.value)
? null
: Number.parseFloat(lengthField.input.value);
if (isBlank(nameField.input.value)) {
nameField.setError(L("B02_Proj_Error_Required"));
hasError = true;
@@ -127,12 +113,53 @@ export function renderB02ProjRegister(root: HTMLElement): void {
regionField.setError(L("B02_Proj_Error_Required"));
hasError = true;
}
if (!Number.isInteger(projectYear) || projectYear < 2000 || projectYear > 2100) {
yearField.setError(L("Common_Validation_NumberRange"));
hasError = true;
}
if (estimatedLength !== null && (!Number.isFinite(estimatedLength) || estimatedLength < 0)) {
lengthField.setError(L("Common_Validation_NumberRange"));
hasError = true;
}
if (hasError) return;
// TODO: POST /api/b02/project 연동 (로딩 오버레이 + 성공/실패 처리).
// 성공 시 반환된 프로젝트 ID로 storage 폴더 생성 후 파일 입력으로 이동.
showToast(L("B02_Proj_Success"), "success");
navigateTo(ROUTES.B03_FILE_INPUT);
showLoadingOverlay();
try {
const response = await fetch(`${API_BASE_URL}/b02/project`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({
name: nameField.input.value.trim(),
region: regionField.input.value.trim(),
road_type: roadTypeField.select.value,
project_year: projectYear,
estimated_length_m: estimatedLength,
memo: memoField.input.value.trim() || null,
}),
});
if (!response.ok) {
let detail = L("Common_Status_Error");
try {
const errorBody = (await response.json()) as { detail?: string; message?: string };
detail = errorBody.detail ?? errorBody.message ?? detail;
} catch {
detail = `${detail} (${response.status})`;
}
throw new Error(detail);
}
const data = (await response.json()) as { project_id: string };
localStorage.setItem(CURRENT_PROJECT_ID_KEY, data.project_id);
showToast(L("B02_Proj_Success"), "success");
navigateTo(ROUTES.B03_FILE_INPUT);
} catch (error) {
const message = error instanceof Error ? error.message : L("Common_Status_Error");
showToast(message, "error");
} finally {
hideLoadingOverlay();
}
}
const grid = document.createElement("div");