740 lines
23 KiB
TypeScript
740 lines
23 KiB
TypeScript
import { CURRENT_PROJECT_ID_KEY, ROUTES, type RoutePath } from "@config/config_frontend";
|
|
import { isBlank } from "@util/common_util_validate";
|
|
import { navigateTo } from "../A00_Common/router";
|
|
import { workflowSteps } from "../A00_Common/b_page_scaffold";
|
|
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
|
import {
|
|
createButton,
|
|
createCard,
|
|
createInputField,
|
|
createTag,
|
|
hideLoadingOverlay,
|
|
showLoadingOverlay,
|
|
showToast,
|
|
} from "@ui/ui_template_elements";
|
|
import {
|
|
changePassword,
|
|
fetchAllCompanies,
|
|
fetchAllJoinRequests,
|
|
fetchAllProjects,
|
|
fetchAllUsers,
|
|
fetchAuditLogs,
|
|
fetchCompanyJoinRequests,
|
|
fetchCompanyMembers,
|
|
fetchCompanyProjects,
|
|
fetchDashboardMe,
|
|
fetchSystemResources,
|
|
fetchUserCompany,
|
|
fetchUserProjects,
|
|
processJoinRequest,
|
|
updateUserProfile,
|
|
type AuditLog,
|
|
type CompanyInfo,
|
|
type DashboardUser,
|
|
type JoinRequest,
|
|
type Member,
|
|
type ProjectItem,
|
|
type ResourceData,
|
|
} from "./B01_Dashboard_Api_Fetch";
|
|
import { buildResourcePanel } from "./B01_Dashboard_UI_Resources";
|
|
import {
|
|
canEditProject,
|
|
canDeleteProject,
|
|
canChangeRole,
|
|
canDeleteUser,
|
|
canManageAutomation,
|
|
} from "./B01_Dashboard_UI_Helper";
|
|
import {
|
|
openEditProjectModal,
|
|
openDeleteProjectModal,
|
|
openEditUserModal,
|
|
openChangeRoleModal,
|
|
openDeleteUserModal,
|
|
openAutomationModal,
|
|
openCreateCompanyModal,
|
|
openFindCompanyModal,
|
|
openAddMemberModal,
|
|
} from "./B01_Dashboard_UI_Modals";
|
|
import "./B01_Dashboard_UI_Style.css";
|
|
|
|
const PASSWORD_MIN_LENGTH = 8;
|
|
|
|
function L(key: keyof typeof ui_locales): string {
|
|
return ui_locales[key][currentLanguageIndex];
|
|
}
|
|
|
|
interface DashboardState {
|
|
user: DashboardUser;
|
|
userProjects: ProjectItem[];
|
|
companyProjects: ProjectItem[];
|
|
allProjects: ProjectItem[];
|
|
company: CompanyInfo | null;
|
|
members: Member[];
|
|
joinRequests: JoinRequest[];
|
|
allCompanies: CompanyInfo[];
|
|
allUsers: DashboardUser[];
|
|
allJoinRequests: JoinRequest[];
|
|
auditLogs: AuditLog[];
|
|
resources: ResourceData | null;
|
|
}
|
|
|
|
export async function renderB01Dashboard(root: HTMLElement): Promise<void> {
|
|
showLoadingOverlay();
|
|
try {
|
|
const user = await fetchDashboardMe();
|
|
const state: DashboardState = {
|
|
user,
|
|
userProjects: [],
|
|
companyProjects: [],
|
|
allProjects: [],
|
|
company: null,
|
|
members: [],
|
|
joinRequests: [],
|
|
allCompanies: [],
|
|
allUsers: [],
|
|
allJoinRequests: [],
|
|
auditLogs: [],
|
|
resources: null,
|
|
};
|
|
await loadRoleData(state);
|
|
root.innerHTML = "";
|
|
root.append(buildPage(state));
|
|
} catch (error) {
|
|
showToast(error instanceof Error ? error.message : L("B01_Dashboard_LoadFailed"), "error");
|
|
} finally {
|
|
hideLoadingOverlay();
|
|
}
|
|
}
|
|
|
|
async function loadRoleData(state: DashboardState): Promise<void> {
|
|
if (state.user.role === "SYSTEM_ADMIN") {
|
|
const results = await Promise.allSettled([
|
|
fetchAllCompanies(),
|
|
fetchAllUsers(),
|
|
fetchAllJoinRequests(),
|
|
fetchAuditLogs(),
|
|
fetchSystemResources(7),
|
|
fetchAllProjects(),
|
|
]);
|
|
state.allCompanies = results[0].status === "fulfilled" ? results[0].value : [];
|
|
state.allUsers = results[1].status === "fulfilled" ? results[1].value : [];
|
|
state.allJoinRequests = results[2].status === "fulfilled" ? results[2].value : [];
|
|
state.auditLogs = results[3].status === "fulfilled" ? results[3].value : [];
|
|
state.resources = results[4].status === "fulfilled" ? results[4].value : null;
|
|
state.allProjects = results[5].status === "fulfilled" ? results[5].value : [];
|
|
|
|
results.forEach((res, idx) => {
|
|
if (res.status === "rejected") {
|
|
console.error(`System Admin API failed at index ${idx}:`, res.reason);
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
const [projects, company] = await Promise.all([
|
|
fetchUserProjects().catch(() => []),
|
|
fetchUserCompany().catch(() => null),
|
|
]);
|
|
state.userProjects = projects;
|
|
state.company = company;
|
|
if (state.user.role === "ADMIN") {
|
|
const [members, requests, companyProjects] = await Promise.all([
|
|
fetchCompanyMembers().catch(() => []),
|
|
fetchCompanyJoinRequests().catch(() => []),
|
|
fetchCompanyProjects().catch(() => []),
|
|
]);
|
|
state.members = members;
|
|
state.joinRequests = requests;
|
|
state.companyProjects = companyProjects;
|
|
}
|
|
}
|
|
|
|
function buildSectionHeader(titleText: string, actionButtons: HTMLElement[] = []): HTMLElement {
|
|
const header = document.createElement("div");
|
|
header.className = "b01-dashboard__section-header";
|
|
header.style.display = "flex";
|
|
header.style.justifyContent = "space-between";
|
|
header.style.alignItems = "center";
|
|
header.style.marginBottom = "var(--spacing-16)";
|
|
|
|
const title = document.createElement("h3");
|
|
title.className = "ui-card__title";
|
|
title.style.margin = "0";
|
|
title.textContent = titleText;
|
|
|
|
const actions = document.createElement("div");
|
|
actions.className = "b01-dashboard__actions";
|
|
actions.append(...actionButtons);
|
|
|
|
header.append(title, actions);
|
|
return header;
|
|
}
|
|
|
|
function section(
|
|
title: string,
|
|
body: HTMLElement,
|
|
wide = false,
|
|
actions: HTMLElement[] = [],
|
|
): HTMLElement {
|
|
const header = buildSectionHeader(title, actions);
|
|
const card = createCard({ body: [header, body], raised: true });
|
|
card.classList.add("b01-dashboard__section");
|
|
if (wide) card.classList.add("b01-dashboard__section--wide");
|
|
return card;
|
|
}
|
|
|
|
function buildPage(state: DashboardState): HTMLElement {
|
|
const page = document.createElement("div");
|
|
page.className = "b01-dashboard";
|
|
page.append(buildHeader(state.user));
|
|
|
|
const grid = document.createElement("div");
|
|
grid.className = "b01-dashboard__grid";
|
|
|
|
if (state.user.role === "SYSTEM_ADMIN") {
|
|
grid.append(
|
|
section(L("B01_Dashboard_Resources"), buildResourcePanel(state.resources), true),
|
|
section(L("B01_Dashboard_Projects"), projectTable(state.allProjects, state.user), true, [
|
|
createButton({
|
|
label: "+",
|
|
onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER),
|
|
}),
|
|
]),
|
|
section(L("B01_Dashboard_Users"), userTable(state.allUsers, state.user), true, [
|
|
createButton({
|
|
label: "+",
|
|
onClick: () => openAddMemberModal(),
|
|
}),
|
|
]),
|
|
section(L("B01_Dashboard_JoinRequests"), joinRequestTable(state.allJoinRequests, true), true),
|
|
section(L("B01_Dashboard_Companies"), companyTable(state.allCompanies), true, [
|
|
createButton({
|
|
label: "+",
|
|
onClick: () => openCreateCompanyModal(),
|
|
}),
|
|
]),
|
|
section(L("B01_Dashboard_AuditLogs"), auditLogTable(state.auditLogs), true),
|
|
);
|
|
} else if (state.user.role === "ADMIN") {
|
|
grid.append(
|
|
section(L("B01_Dashboard_Projects"), projectTable(state.companyProjects, state.user), true, [
|
|
createButton({
|
|
label: "+",
|
|
onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER),
|
|
}),
|
|
]),
|
|
section(L("B01_Dashboard_Members"), memberTable(state.members, state.user), true, [
|
|
createButton({
|
|
label: "+",
|
|
onClick: () => openAddMemberModal(),
|
|
}),
|
|
]),
|
|
section(L("B01_Dashboard_JoinRequests"), joinRequestTable(state.joinRequests, false), true),
|
|
section(L("B01_Dashboard_Company"), buildCompanyPanel(state), true),
|
|
);
|
|
} else {
|
|
grid.append(
|
|
section(L("B01_Dashboard_Projects"), projectTable(state.userProjects, state.user), true, [
|
|
createButton({
|
|
label: "+",
|
|
onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER),
|
|
}),
|
|
]),
|
|
section(L("B01_Dashboard_Company"), buildCompanyPanel(state), true),
|
|
);
|
|
}
|
|
|
|
grid.append(
|
|
section(L("B01_Dashboard_Profile"), buildProfileForm(state.user)),
|
|
section(L("B01_Account_Section_Security"), buildSecurityForm()),
|
|
);
|
|
page.append(grid);
|
|
return page;
|
|
}
|
|
|
|
function buildHeader(user: DashboardUser): HTMLElement {
|
|
const header = document.createElement("header");
|
|
header.className = "b01-dashboard__header";
|
|
const text = document.createElement("div");
|
|
const title = document.createElement("h1");
|
|
title.className = "b01-dashboard__title";
|
|
title.textContent = L("B01_Dashboard_Title");
|
|
const subtitle = document.createElement("p");
|
|
subtitle.className = "b01-dashboard__subtitle";
|
|
subtitle.textContent = L("B01_Dashboard_Subtitle");
|
|
text.append(title, subtitle);
|
|
const tag = createTag(roleLabel(user.role), user.role === "SYSTEM_ADMIN" ? "accent" : "neutral");
|
|
tag.classList.add("b01-dashboard__role");
|
|
header.append(text, tag);
|
|
return header;
|
|
}
|
|
|
|
function roleLabel(role: DashboardUser["role"]): string {
|
|
if (role === "SYSTEM_ADMIN") return L("B01_Dashboard_Role_SystemAdmin");
|
|
if (role === "ADMIN") return L("B01_Dashboard_Role_Admin");
|
|
return L("B01_Dashboard_Role_User");
|
|
}
|
|
|
|
function table(headers: string[], rows: HTMLElement[][]): HTMLElement {
|
|
if (!rows.length) {
|
|
const empty = document.createElement("p");
|
|
empty.className = "b01-dashboard__empty";
|
|
empty.textContent = L("Common_Status_Empty");
|
|
return empty;
|
|
}
|
|
const wrap = document.createElement("div");
|
|
wrap.className = "b01-dashboard__table-wrap";
|
|
const tableEl = document.createElement("table");
|
|
tableEl.className = "b01-dashboard__table";
|
|
const thead = document.createElement("thead");
|
|
const headRow = document.createElement("tr");
|
|
for (const header of headers) {
|
|
const th = document.createElement("th");
|
|
th.textContent = header;
|
|
headRow.append(th);
|
|
}
|
|
thead.append(headRow);
|
|
const tbody = document.createElement("tbody");
|
|
for (const row of rows) {
|
|
const tr = document.createElement("tr");
|
|
for (const cell of row) {
|
|
const td = document.createElement("td");
|
|
td.append(cell);
|
|
tr.append(td);
|
|
}
|
|
tbody.append(tr);
|
|
}
|
|
tableEl.append(thead, tbody);
|
|
wrap.append(tableEl);
|
|
return wrap;
|
|
}
|
|
|
|
function text(value: unknown): HTMLElement {
|
|
const span = document.createElement("span");
|
|
span.textContent = value == null || value === "" ? "-" : String(value);
|
|
return span;
|
|
}
|
|
|
|
function projectTable(projects: ProjectItem[], currentUser: DashboardUser): HTMLElement {
|
|
return table(
|
|
[
|
|
L("B01_Dashboard_Table_Project"),
|
|
L("B01_Dashboard_Table_Region"),
|
|
L("B01_Dashboard_Table_Progress"),
|
|
L("B01_Dashboard_Table_Workflow"),
|
|
L("B01_Dashboard_Table_Updated"),
|
|
L("B01_Dashboard_Table_Action"),
|
|
],
|
|
projects.map((project) => {
|
|
const actCell = document.createElement("div");
|
|
actCell.className = "b01-dashboard__actions";
|
|
|
|
if (canEditProject(currentUser, project)) {
|
|
actCell.append(
|
|
createButton({
|
|
label: L("Common_Btn_Edit"),
|
|
variant: "ghost",
|
|
onClick: () => openEditProjectModal(currentUser, project),
|
|
}),
|
|
);
|
|
}
|
|
if (canDeleteProject(currentUser)) {
|
|
actCell.append(
|
|
createButton({
|
|
label: L("Common_Btn_Delete"),
|
|
variant: "danger",
|
|
onClick: () => openDeleteProjectModal(project),
|
|
}),
|
|
);
|
|
}
|
|
if (canManageAutomation(currentUser, project)) {
|
|
actCell.append(
|
|
createButton({
|
|
label: L("B01_Dashboard_AutomationLogic"),
|
|
variant: "ghost",
|
|
onClick: () => openAutomationModal(project),
|
|
}),
|
|
);
|
|
}
|
|
|
|
return [
|
|
text(project.name),
|
|
text(project.region),
|
|
text(`${project.progress_percent}%`),
|
|
workflow(project),
|
|
text(formatDate(project.updated_at)),
|
|
actCell,
|
|
];
|
|
}),
|
|
);
|
|
}
|
|
|
|
function workflow(project: ProjectItem): HTMLElement {
|
|
const routes: RoutePath[] = [
|
|
ROUTES.B03_FILE_INPUT,
|
|
ROUTES.B04_WF1_SURFACE,
|
|
ROUTES.B05_WF2_ROUTE,
|
|
ROUTES.B06_WF3_PROFILE_CROSS,
|
|
ROUTES.B07_WF4_DESIGN_DETAIL,
|
|
ROUTES.B08_WF5_QUANTITY,
|
|
ROUTES.B09_WF6_ESTIMATION,
|
|
];
|
|
const box = document.createElement("div");
|
|
box.className = "b01-dashboard__workflow";
|
|
|
|
const stages = project.workflow_state?.stages;
|
|
const stepLabels = workflowSteps();
|
|
|
|
routes.forEach((route, index) => {
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = "b01-dashboard__step";
|
|
button.textContent = stepLabels[index] ?? `B${String(index + 3).padStart(2, "0")}`;
|
|
|
|
// 스텝바는 항상 자유롭게 이동 가능(게이팅하지 않음).
|
|
// 단계 완료/무효화 판정은 각 페이지의 액션(업로드·분석 실행 등) 시 백엔드가
|
|
// 계산·DB 갱신하며, 여기서는 그 결과를 색상/툴팁으로 표시만 한다.
|
|
button.classList.add("is-enabled");
|
|
button.addEventListener("click", () => {
|
|
localStorage.setItem(CURRENT_PROJECT_ID_KEY, project.id);
|
|
navigateTo(route);
|
|
});
|
|
|
|
if (stages && stages[index]) {
|
|
const state = stages[index].state;
|
|
button.classList.add(`state-${state.toLowerCase()}`);
|
|
if (state === "STALE") {
|
|
button.title = "Stale (하위 단계 변경으로 무효화됨)";
|
|
} else if (state === "FAILED") {
|
|
button.title = "Failed (실패)";
|
|
} else if (state === "COMPLETE") {
|
|
button.title = "Complete (완료)";
|
|
} else if (state === "IN_PROGRESS") {
|
|
button.title = "In Progress (진행 중)";
|
|
} else {
|
|
button.title = "Not Started (미실행)";
|
|
}
|
|
}
|
|
|
|
box.append(button);
|
|
});
|
|
return box;
|
|
}
|
|
|
|
function buildCompanyPanel(state: DashboardState): HTMLElement {
|
|
const wrap = document.createElement("div");
|
|
wrap.className = "b01-dashboard__actions";
|
|
if (!state.company) {
|
|
wrap.append(
|
|
createTag(L("B01_Dashboard_NoCompany"), "warning"),
|
|
createButton({
|
|
label: L("B01_Dashboard_CreateCompany"),
|
|
onClick: () => openCreateCompanyModal(),
|
|
}),
|
|
createButton({
|
|
label: L("B01_Dashboard_FindCompany"),
|
|
variant: "ghost",
|
|
onClick: () => openFindCompanyModal(),
|
|
}),
|
|
);
|
|
return wrap;
|
|
}
|
|
wrap.append(
|
|
createTag(`${state.company.name} (${state.user.status})`, "success"),
|
|
text(`${L("B01_Dashboard_Metric_ActiveUsers")}: ${state.company.user_count ?? 0}`),
|
|
text(`${L("B01_Dashboard_Projects")}: ${state.company.project_count ?? 0}`),
|
|
);
|
|
return wrap;
|
|
}
|
|
|
|
function memberTable(members: Member[], currentUser: DashboardUser): HTMLElement {
|
|
return table(
|
|
[
|
|
L("B01_Dashboard_Table_Email"),
|
|
L("B01_Dashboard_Table_Name"),
|
|
L("B01_Dashboard_Table_Position"),
|
|
L("B01_Dashboard_Table_Department"),
|
|
L("B01_Dashboard_Table_Role"),
|
|
L("B01_Dashboard_Table_Action"),
|
|
],
|
|
members.map((member) => {
|
|
const actionsEl = document.createElement("div");
|
|
actionsEl.className = "b01-dashboard__actions";
|
|
|
|
actionsEl.append(
|
|
createButton({
|
|
label: L("Common_Btn_Edit"),
|
|
variant: "ghost",
|
|
onClick: () => openEditUserModal(currentUser, member),
|
|
}),
|
|
);
|
|
|
|
if (canChangeRole(currentUser, member)) {
|
|
actionsEl.append(
|
|
createButton({
|
|
label: L("B01_Dashboard_ChangeRole"),
|
|
variant: "ghost",
|
|
onClick: () => openChangeRoleModal(member),
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (canDeleteUser(currentUser, member)) {
|
|
actionsEl.append(
|
|
createButton({
|
|
label: L("B01_Dashboard_RemoveMember"),
|
|
variant: "danger",
|
|
onClick: () => openDeleteUserModal(member),
|
|
}),
|
|
);
|
|
}
|
|
|
|
return [
|
|
text(member.email),
|
|
text(member.name),
|
|
text(member.position),
|
|
text(member.department),
|
|
text(member.role),
|
|
actionsEl,
|
|
];
|
|
}),
|
|
);
|
|
}
|
|
|
|
function joinRequestTable(requests: JoinRequest[], systemMode: boolean): HTMLElement {
|
|
return table(
|
|
[
|
|
L("B01_Dashboard_Table_Email"),
|
|
...(systemMode ? [L("B01_Dashboard_Table_Company")] : []),
|
|
L("B01_Dashboard_Table_Requested"),
|
|
L("B01_Dashboard_Table_Status"),
|
|
L("B01_Dashboard_Table_Action"),
|
|
],
|
|
requests.map((request) => [
|
|
text(request.user_email),
|
|
...(systemMode ? [text(request.company_name)] : []),
|
|
text(formatDate(request.requested_at)),
|
|
text(request.status),
|
|
actionPair(
|
|
() => onB01_JoinRequest_Process_Click(request.id, "APPROVE", systemMode),
|
|
() => onB01_JoinRequest_Process_Click(request.id, "REJECT", systemMode),
|
|
),
|
|
]),
|
|
);
|
|
}
|
|
|
|
function actionPair(approve: () => void, reject: () => void): HTMLElement {
|
|
const row = document.createElement("div");
|
|
row.className = "b01-dashboard__actions";
|
|
row.append(
|
|
createButton({ label: L("B01_Dashboard_Approve"), variant: "ghost", onClick: approve }),
|
|
createButton({ label: L("B01_Dashboard_Reject"), variant: "danger", onClick: reject }),
|
|
);
|
|
return row;
|
|
}
|
|
|
|
function companyTable(companies: CompanyInfo[]): HTMLElement {
|
|
return table(
|
|
[
|
|
L("B01_Dashboard_Table_Company"),
|
|
L("B01_Dashboard_Field_BusinessNumber"),
|
|
L("B01_Dashboard_Table_Status"),
|
|
],
|
|
companies.map((company) => [
|
|
text(company.name),
|
|
text(company.business_registration_number),
|
|
text(company.business_status),
|
|
]),
|
|
);
|
|
}
|
|
|
|
function userTable(users: DashboardUser[], currentUser: DashboardUser): HTMLElement {
|
|
return table(
|
|
[
|
|
L("B01_Dashboard_Table_Email"),
|
|
L("B01_Dashboard_Table_Name"),
|
|
L("B01_Dashboard_Table_Position"),
|
|
L("B01_Dashboard_Table_Department"),
|
|
L("B01_Account_Field_Phone"),
|
|
L("B01_Dashboard_Table_Role"),
|
|
L("B01_Dashboard_Table_Status"),
|
|
L("B01_Dashboard_Table_Action"),
|
|
],
|
|
users.map((user) => {
|
|
const actionsEl = document.createElement("div");
|
|
actionsEl.className = "b01-dashboard__actions";
|
|
|
|
actionsEl.append(
|
|
createButton({
|
|
label: L("Common_Btn_Edit"),
|
|
variant: "ghost",
|
|
onClick: () => openEditUserModal(currentUser, user),
|
|
}),
|
|
);
|
|
|
|
if (canChangeRole(currentUser, user)) {
|
|
actionsEl.append(
|
|
createButton({
|
|
label: L("B01_Dashboard_ChangeRole"),
|
|
variant: "ghost",
|
|
onClick: () => openChangeRoleModal(user),
|
|
}),
|
|
);
|
|
}
|
|
|
|
return [
|
|
text(user.email),
|
|
text(user.name),
|
|
text(user.position),
|
|
text(user.department),
|
|
text(user.phone),
|
|
text(user.role),
|
|
text(user.status),
|
|
actionsEl,
|
|
];
|
|
}),
|
|
);
|
|
}
|
|
|
|
function auditLogTable(logs: AuditLog[]): HTMLElement {
|
|
return table(
|
|
[
|
|
L("B01_Dashboard_Table_Email"),
|
|
L("B01_Dashboard_Table_Action"),
|
|
L("B01_Dashboard_Table_Updated"),
|
|
],
|
|
logs.map((log) => [text(log.email), text(log.action), text(formatDate(log.timestamp))]),
|
|
);
|
|
}
|
|
|
|
function buildProfileForm(user: DashboardUser): HTMLElement {
|
|
const name = createInputField({
|
|
label: L("B01_Account_Field_Name"),
|
|
value: user.name,
|
|
required: true,
|
|
});
|
|
const position = createInputField({
|
|
label: L("B01_Dashboard_Table_Position"),
|
|
value: user.position ?? "",
|
|
});
|
|
const department = createInputField({
|
|
label: L("B01_Dashboard_Table_Department"),
|
|
value: user.department ?? "",
|
|
});
|
|
const phone = createInputField({ label: L("B01_Account_Field_Phone"), value: user.phone ?? "" });
|
|
const grid = document.createElement("div");
|
|
grid.className = "b01-dashboard__form-grid";
|
|
grid.append(name.root, position.root, department.root, phone.root);
|
|
const save = createButton({
|
|
label: L("B01_Dashboard_SaveProfile"),
|
|
onClick: async function onB01_Profile_Save_Click() {
|
|
name.setError();
|
|
if (isBlank(name.input.value)) {
|
|
name.setError(L("Common_Msg_RequiredField"));
|
|
return;
|
|
}
|
|
await runRequest(() =>
|
|
updateUserProfile({
|
|
name: name.input.value.trim(),
|
|
position: position.input.value.trim() || null,
|
|
department: department.input.value.trim() || null,
|
|
phone: phone.input.value.trim() || null,
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
const wrap = document.createElement("div");
|
|
wrap.append(grid, save);
|
|
return wrap;
|
|
}
|
|
|
|
function buildSecurityForm(): HTMLElement {
|
|
const currentPassword = createInputField({
|
|
label: L("B01_Account_Field_CurrentPw"),
|
|
type: "password",
|
|
required: true,
|
|
});
|
|
const newPassword = createInputField({
|
|
label: L("B01_Account_Field_NewPw"),
|
|
type: "password",
|
|
required: true,
|
|
});
|
|
const confirmPassword = createInputField({
|
|
label: L("B01_Account_Field_ConfirmPw"),
|
|
type: "password",
|
|
required: true,
|
|
});
|
|
const grid = document.createElement("div");
|
|
grid.className = "b01-dashboard__form-grid";
|
|
grid.append(currentPassword.root, newPassword.root, confirmPassword.root);
|
|
const save = createButton({
|
|
label: L("B01_Account_Save_Password"),
|
|
variant: "ghost",
|
|
onClick: async function onB01_Password_Save_Click() {
|
|
currentPassword.setError();
|
|
newPassword.setError();
|
|
confirmPassword.setError();
|
|
|
|
const currentValue = currentPassword.input.value;
|
|
const nextValue = newPassword.input.value;
|
|
const confirmValue = confirmPassword.input.value;
|
|
if (isBlank(currentValue) || isBlank(nextValue) || isBlank(confirmValue)) {
|
|
currentPassword.setError(isBlank(currentValue) ? L("Common_Msg_RequiredField") : undefined);
|
|
newPassword.setError(isBlank(nextValue) ? L("Common_Msg_RequiredField") : undefined);
|
|
confirmPassword.setError(isBlank(confirmValue) ? L("Common_Msg_RequiredField") : undefined);
|
|
return;
|
|
}
|
|
if (nextValue.length < PASSWORD_MIN_LENGTH) {
|
|
newPassword.setError(L("B01_Account_Error_PwLength"));
|
|
return;
|
|
}
|
|
if (nextValue !== confirmValue) {
|
|
confirmPassword.setError(L("B01_Account_Error_PwMismatch"));
|
|
return;
|
|
}
|
|
await runRequest(() =>
|
|
changePassword({
|
|
current_password: currentValue,
|
|
new_password: nextValue,
|
|
new_password_confirm: confirmValue,
|
|
logout_all: false,
|
|
}),
|
|
);
|
|
currentPassword.input.value = "";
|
|
newPassword.input.value = "";
|
|
confirmPassword.input.value = "";
|
|
},
|
|
});
|
|
const wrap = document.createElement("div");
|
|
wrap.append(grid, save);
|
|
return wrap;
|
|
}
|
|
|
|
async function onB01_JoinRequest_Process_Click(
|
|
requestId: number,
|
|
action: "APPROVE" | "REJECT",
|
|
systemMode: boolean,
|
|
): Promise<void> {
|
|
await runRequest(() =>
|
|
systemMode && action === "APPROVE"
|
|
? import("./B01_Dashboard_Api_Fetch").then((api) => api.systemApproveJoinRequest(requestId))
|
|
: processJoinRequest(requestId, action),
|
|
);
|
|
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
}
|
|
|
|
async function runRequest(action: () => Promise<unknown>): Promise<void> {
|
|
showLoadingOverlay();
|
|
try {
|
|
await action();
|
|
showToast(L("B01_Dashboard_Saved"), "success");
|
|
} catch (error) {
|
|
showToast(error instanceof Error ? error.message : L("B01_Dashboard_RequestFailed"), "error");
|
|
} finally {
|
|
hideLoadingOverlay();
|
|
}
|
|
}
|
|
|
|
function formatDate(value?: string | null): string {
|
|
return value ? value.slice(0, 10) : "-";
|
|
}
|