- 개요 = 숫자 카드 넷 + CPU · 메모리 · 디스크 게이지(70 % 주황 · 90 % 빨강) + 7 일 SVG 그래프 - 회사 = 목록(찾기 · ⋯) → 상세(정보 · 사람 · 프로젝트 탭) · 사용자 = 회사 · 역할 · 찾기 거르기 · ⋯ - 설계 기본값 = 설정 카드를 묶음 제목 + 줄로 · 시스템 로그 = 동작 · 기간 · 찾기 거르기 + 이전 기록 더 보기 - 내 정보 = Linear 꼴 설정 줄(기본정보 · 비밀번호 · 테마 · 내 설계 기본값) - 옛 구역(리소스 패널 · 옛 내 정보 폼) 지움 · 지킴 시험 test_549 추가 · test_546 stub 맞춤 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
133 lines
5.5 KiB
TypeScript
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]);
|
|
}
|