Files
Aislo/B01_Dashboard/B01_Dashboard_UI_Profile.ts
T
eomsangdonandClaude Sonnet 5.5 70786cef9e feat(B01): 시스템 관리 · 내 정보 세부 페이지 여섯 — 개요 · 회사 · 사용자 · 설계 기본값 · 로그 · 내 정보 (PLAN 549-2)
- 개요 = 숫자 카드 넷 + CPU · 메모리 · 디스크 게이지(70 % 주황 · 90 % 빨강) + 7 일 SVG 그래프
- 회사 = 목록(찾기 · ⋯) → 상세(정보 · 사람 · 프로젝트 탭) · 사용자 = 회사 · 역할 · 찾기 거르기 · ⋯
- 설계 기본값 = 설정 카드를 묶음 제목 + 줄로 · 시스템 로그 = 동작 · 기간 · 찾기 거르기 + 이전 기록 더 보기
- 내 정보 = Linear 꼴 설정 줄(기본정보 · 비밀번호 · 테마 · 내 설계 기본값)
- 옛 구역(리소스 패널 · 옛 내 정보 폼) 지움 · 지킴 시험 test_549 추가 · test_546 stub 맞춤

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 18:19:47 +09:00

133 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(".b01-pg-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]);
}