- 새 부품 파일: ui_template_table · setting_row · drop · createRowMenu(context_menu) · 모달 폭 단 · askConfirm · askText(window.prompt 대신) · confirmPanel - 단추 = 552-2 단추 부품(⋯ 아이콘 단추 28 · 띠 알림 닫기 20 · 놓기 칸 [파일 고르기] 28) - 대시보드 PageKit 의 dataTable · tableShell · tableRow · rowMenu · settingRow · settingCard · valueText = 부품을 다시 내보냄(한 벌 · 부름 꼴 그대로) · B01 옛 CSS 걷음 - showConfirmDialog 는 모달 부품으로 옮기고 본체가 다시 내보냄 · 모달 가림막 옛 보라 → --color-scrim - 시험: test_552_3_* 다섯 더함 · 옮긴 함수 자리를 읽던 test_290_1 · test_505_2 고침 · 부품 CSS import 를 못 읽던 시험 도우미 넷(130 · 153 · 280_5 · 311)이 .css 를 건너뜀 - 전후 촬영: B01 여덟 화면 자리 같음 · ⋯ 아이콘만 1 px 왼쪽(단추 부품 가운데 맞춤) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
134 lines
5.5 KiB
TypeScript
134 lines
5.5 KiB
TypeScript
/* =============================================================================
|
|
* B01_Dashboard_UI_Profile.ts
|
|
* 내 정보 카드(549-2) — 기본정보(이름 · 직급 · 부서 · 전화 · 이메일 · 서명) · 비밀번호 변경.
|
|
* 모양 = 설정 줄(제목 · 설명 · 오른쪽 조작) · 저장 길은 옛 폼과 같음(`updateUserProfile` · `changePassword`).
|
|
* ========================================================================== */
|
|
|
|
import { isBlank } from "@util/common_util_validate";
|
|
import { createButton } from "@ui/ui_template_elements";
|
|
import {
|
|
changePassword,
|
|
fetchCompanyAssets,
|
|
updateCompanyAsset,
|
|
updateUserProfile,
|
|
type DashboardUser,
|
|
} from "./B01_Dashboard_Api_Fetch";
|
|
import { createAssetField } from "./B01_Dashboard_UI_AssetPicker";
|
|
import { L, runRequest } from "./B01_Dashboard_UI_Common";
|
|
import { settingCard, settingRow, valueText } from "./B01_Dashboard_UI_PageKit";
|
|
import { el } from "./B01_Dashboard_UI_View_Parts";
|
|
|
|
const PASSWORD_MIN_LENGTH = 8;
|
|
|
|
function input(value: string, type = "text", label = ""): HTMLInputElement {
|
|
const box = el("input", "b01-sys-input");
|
|
box.type = type;
|
|
box.value = value;
|
|
if (label) box.ariaLabel = label;
|
|
return box;
|
|
}
|
|
|
|
/** 입력 줄 아래에 오류 글 — 비우면 지움 */
|
|
function fieldError(row: HTMLElement, message?: string): void {
|
|
row.querySelector(".b01-sys-error")?.remove();
|
|
if (message)
|
|
row.querySelector(".ui-setting-row__text")?.append(el("span", "b01-sys-error", message));
|
|
}
|
|
|
|
export function buildProfileCard(user: DashboardUser): HTMLElement {
|
|
const name = input(user.name, "text", "이름");
|
|
const position = input(user.position ?? "", "text", "직급");
|
|
const department = input(user.department ?? "", "text", "부서");
|
|
const phone = input(user.phone ?? "", "tel", "전화");
|
|
const nameRow = settingRow("이름", "도면 표제란 · 보고서에 들어갑니다.", name);
|
|
// 본인 서명 — 도면 표제란이 이 사람 자리를 채울 때 그대로 실림(회사 소속일 때만)
|
|
const signature = el("div", "b01-sys-assetslot");
|
|
if (user.company_id) {
|
|
void fetchCompanyAssets(user.company_id).then((assets) => {
|
|
const owned = assets.find((a) => a.kind === "SIGNATURE" && a.user_id === user.id);
|
|
signature.append(
|
|
createAssetField("서명", "SIGNATURE", assets, owned?.id ?? null, user.company_id!, user, {
|
|
owner: { id: user.id, name: user.name },
|
|
onChange: async (assetId) => {
|
|
if (assetId === null) return;
|
|
const picked = assets.find((a) => a.id === assetId);
|
|
if (picked)
|
|
await updateCompanyAsset(assetId, { label: picked.label, user_id: user.id });
|
|
},
|
|
}).root,
|
|
);
|
|
});
|
|
}
|
|
const save = createButton({
|
|
label: L("B01_Dashboard_SaveProfile"),
|
|
onClick: async function onB01_Profile_Save_Click() {
|
|
fieldError(nameRow);
|
|
if (!name.value.trim()) return fieldError(nameRow, L("Common_Msg_RequiredField"));
|
|
await runRequest(() =>
|
|
updateUserProfile({
|
|
name: name.value.trim(),
|
|
position: position.value.trim() || null,
|
|
department: department.value.trim() || null,
|
|
phone: phone.value.trim() || null,
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
const rows = [
|
|
nameRow,
|
|
settingRow("직급", "", position),
|
|
settingRow("부서", "", department),
|
|
settingRow("전화", "", phone),
|
|
settingRow("이메일", "로그인에 쓰는 계정이라 바꿀 수 없습니다.", valueText(user.email)),
|
|
];
|
|
if (user.company_id)
|
|
rows.push(settingRow("서명", "보고서 서명란 · 도면 결재란에 들어갑니다.", signature));
|
|
rows.push(settingRow("", "", save));
|
|
return settingCard(rows);
|
|
}
|
|
|
|
/** 비밀번호 — [변경] 을 누르면 입력 줄이 펼쳐짐 */
|
|
export function buildPasswordCard(): HTMLElement {
|
|
const current = input("", "password", "현재 비밀번호");
|
|
const next = input("", "password", "새 비밀번호");
|
|
const confirm = input("", "password", "새 비밀번호 확인");
|
|
const rows = [
|
|
settingRow("현재 비밀번호", "", current),
|
|
settingRow("새 비밀번호", `${PASSWORD_MIN_LENGTH} 자 이상`, next),
|
|
settingRow("새 비밀번호 확인", "", confirm),
|
|
];
|
|
const form = el("div");
|
|
form.hidden = true;
|
|
const save = createButton({
|
|
label: L("B01_Account_Save_Password"),
|
|
onClick: async function onB01_Password_Save_Click() {
|
|
for (const r of rows) fieldError(r);
|
|
const required = L("Common_Msg_RequiredField");
|
|
if (isBlank(current.value)) return fieldError(rows[0], required);
|
|
if (isBlank(next.value)) return fieldError(rows[1], required);
|
|
if (isBlank(confirm.value)) return fieldError(rows[2], required);
|
|
if (next.value.length < PASSWORD_MIN_LENGTH)
|
|
return fieldError(rows[1], L("B01_Account_Error_PwLength"));
|
|
if (next.value !== confirm.value)
|
|
return fieldError(rows[2], L("B01_Account_Error_PwMismatch"));
|
|
await runRequest(() =>
|
|
changePassword({
|
|
current_password: current.value,
|
|
new_password: next.value,
|
|
new_password_confirm: confirm.value,
|
|
logout_all: false,
|
|
}),
|
|
);
|
|
current.value = next.value = confirm.value = "";
|
|
form.hidden = true;
|
|
},
|
|
});
|
|
form.append(...rows, settingRow("", "", save));
|
|
const toggle = createButton({
|
|
label: "변경",
|
|
variant: "ghost",
|
|
onClick: () => (form.hidden = !form.hidden),
|
|
});
|
|
return settingCard([settingRow("비밀번호", "주기적으로 바꾸면 안전합니다.", toggle), form]);
|
|
}
|