Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1
This commit is contained in:
@@ -12,12 +12,19 @@ import { icon, type IconName } from "@ui/ui_template_icons";
|
||||
import { currentLanguageIndex, setLanguage } from "@ui/ui_template_locale";
|
||||
import { readTouchMode, setTouchMode, type TouchMode } from "../A00_Common/device_context";
|
||||
import { currentQuery, navigateTo, type MountedPage } from "../A00_Common/router";
|
||||
import { fetchAllCompanies, type CompanyInfo, type DashboardUser } from "./B01_Dashboard_Api_Fetch";
|
||||
import {
|
||||
fetchAllCompanies,
|
||||
fetchCompanyJoinRequests,
|
||||
type CompanyInfo,
|
||||
type DashboardUser,
|
||||
} from "./B01_Dashboard_Api_Fetch";
|
||||
import { fetchTempBatches } from "./B01_Dashboard_Api_Temp";
|
||||
import { fetchAssignedPlans } from "./B01_Dashboard_UI_AssignedPlans";
|
||||
import { roleLabel } from "./B01_Dashboard_UI_Common";
|
||||
import { openCompanyPicker } from "./B01_Dashboard_UI_CompanyPick";
|
||||
import { buildKnowledgeChat } from "./B01_Dashboard_UI_Knowledge_Chat";
|
||||
import { ADMIN_COMPANY_KEY, pickAdminCompany } from "./B01_Dashboard_UI_Legacy";
|
||||
import { buildNav, viewHref } from "./B01_Dashboard_UI_Nav";
|
||||
import { buildNav, viewHref, type DashboardNav } from "./B01_Dashboard_UI_Nav";
|
||||
import { roleOf, viewFor, VIEWS, type ViewCtx } from "./B01_Dashboard_UI_Views";
|
||||
import "./B01_Dashboard_UI_Style_Shell.css";
|
||||
|
||||
@@ -116,14 +123,19 @@ function buildUserMenu(ctx: ViewCtx, roleText: string): HTMLElement {
|
||||
menu.hidden = !open;
|
||||
trigger.setAttribute("aria-expanded", String(open));
|
||||
};
|
||||
const dark = document.documentElement.getAttribute("data-theme") === "dark";
|
||||
// 테마 줄 = 지금과 반대 화면 이름 · 바꾼 뒤 다시 씀
|
||||
const themeItem = (): HTMLButtonElement => {
|
||||
const dark = document.documentElement.getAttribute("data-theme") === "dark";
|
||||
return menuItem(dark ? "sun" : "moon", dark ? "밝은 화면" : "어두운 화면", () => {
|
||||
toggleTheme();
|
||||
theme.replaceWith((theme = themeItem()));
|
||||
});
|
||||
};
|
||||
let theme = themeItem();
|
||||
menu.append(
|
||||
who,
|
||||
menuItem("userCircle", "내 정보", () => ctx.go("me")),
|
||||
menuItem(dark ? "sun" : "moon", dark ? "밝은 화면" : "어두운 화면", () => {
|
||||
toggleTheme();
|
||||
setOpen(false);
|
||||
}),
|
||||
theme,
|
||||
menuItem("world", currentLanguageIndex === 0 ? "English" : "한국어", () => {
|
||||
const next = currentLanguageIndex === 0 ? "en" : "ko";
|
||||
setLanguage(next);
|
||||
@@ -183,6 +195,39 @@ function buildCompany(
|
||||
return b;
|
||||
}
|
||||
|
||||
/** 보관함 만료 임박 = 7 일 안 */
|
||||
const EXPIRE_SOON_MS = 7 * 24 * 3600 * 1000;
|
||||
|
||||
/** 메뉴 배지 · 알림 수 — 가입 요청(회사 관리자 · 시스템 관리자) · 받은 예정노선 미연결 · 업데이트 ·
|
||||
* 보관함 묶음 수(만료 임박은 처리할 일) · 처리할 일 = 빨강 배지 합 · 실패한 것은 0 */
|
||||
async function refreshBadges(ctx: ViewCtx, nav: DashboardNav, bell: HTMLElement): Promise<void> {
|
||||
const admin = ctx.role === "ADMIN" || ctx.role === "SYSTEM_ADMIN";
|
||||
const [joins, plans, temp] = await Promise.all([
|
||||
admin
|
||||
? fetchCompanyJoinRequests(ctx.role === "SYSTEM_ADMIN" ? ctx.companyId : undefined).catch(
|
||||
() => [],
|
||||
)
|
||||
: [],
|
||||
ctx.role === "FOREST" ? [] : fetchAssignedPlans().catch(() => []),
|
||||
fetchTempBatches()
|
||||
.then((r) => r.batches)
|
||||
.catch(() => []),
|
||||
]);
|
||||
const newPlans = plans.filter((p) => !p.linked_project_id || p.update_available).length;
|
||||
const now = Date.now();
|
||||
const expiring = temp.filter(
|
||||
(b) => b.expires_at && Date.parse(b.expires_at) - now < EXPIRE_SOON_MS,
|
||||
).length;
|
||||
nav.setBadge("people", joins.length, true);
|
||||
nav.setBadge("assigned", newPlans, true);
|
||||
nav.setBadge("temp", temp.length);
|
||||
const todo = joins.length + newPlans + expiring;
|
||||
const badge = bell.querySelector<HTMLElement>(".b01-shell__badge")!;
|
||||
badge.hidden = todo <= 0;
|
||||
badge.textContent = String(todo);
|
||||
bell.title = bell.ariaLabel = `처리할 일 ${todo} 건`;
|
||||
}
|
||||
|
||||
export async function mountDashboardShell(
|
||||
root: HTMLElement,
|
||||
me: DashboardUser,
|
||||
@@ -218,8 +263,12 @@ export async function mountDashboardShell(
|
||||
spacer.className = "b01-shell__spacer";
|
||||
const help = createHelpButton();
|
||||
help.classList.add("b01-shell__help-btn");
|
||||
const bell = iconButton("bell", "알림 (준비 중)");
|
||||
bell.classList.add("b01-shell__bell");
|
||||
// 알림 = 처리할 일 수 · 누르면 홈(처리할 일 목록 자리)
|
||||
const bell = iconButton("bell", "처리할 일", () => ctx.go("home"));
|
||||
const bellBadge = document.createElement("span");
|
||||
bellBadge.className = "b01-shell__badge is-alert";
|
||||
bellBadge.hidden = true;
|
||||
bell.append(bellBadge);
|
||||
header.append(
|
||||
brand,
|
||||
buildCompany(ctx, companies, (id) => {
|
||||
@@ -252,6 +301,7 @@ export async function mountDashboardShell(
|
||||
const mine = ++seq;
|
||||
const view = viewFor(currentQuery().get("view"), role);
|
||||
nav.setActive(view.group === "hidden" && view.key === "project" ? "projects" : view.key);
|
||||
void refreshBadges(ctx, nav, bell);
|
||||
const host = document.createElement("div");
|
||||
host.className = "b01-shell__page";
|
||||
host.dataset.view = view.key;
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
--color-border: var(--b01-line);
|
||||
--color-primary: var(--b01-accent);
|
||||
--color-primary-text: var(--b01-accent-text);
|
||||
--color-text: var(--b01-text);
|
||||
--color-text-body: var(--b01-text);
|
||||
--color-text-secondary: var(--b01-muted);
|
||||
--color-text-muted: var(--b01-muted);
|
||||
|
||||
min-height: 100dvh;
|
||||
display: grid;
|
||||
|
||||
@@ -104,3 +104,16 @@ def test_셸_한_벌():
|
||||
assert ':root[data-theme="dark"] .b01-shell {' in css
|
||||
icons = _text(ROOT / "ui_template" / "ui_template_icons.ts")
|
||||
assert "MIT License" in icons and "Paweł Kuna" in icons
|
||||
|
||||
|
||||
def test_메뉴_배지_알림():
|
||||
shell = _text(B01 / "B01_Dashboard_UI_Shell.ts")
|
||||
badges = shell.split("async function refreshBadges")[1].split("\n}\n")[0]
|
||||
assert 'nav.setBadge("people", joins.length, true);' in badges # 가입 요청 = 빨강
|
||||
assert "!p.linked_project_id || p.update_available" in badges # 받은 예정노선 새로 · 업데이트
|
||||
assert 'nav.setBadge("temp", temp.length);' in badges
|
||||
assert "const todo = joins.length + newPlans + expiring;" in badges # 알림 = 처리할 일 합
|
||||
assert "void refreshBadges(ctx, nav, bell);" in shell # 페이지 바뀔 때마다 다시 셈
|
||||
css = _text(B01 / "B01_Dashboard_UI_Style_Shell.css")
|
||||
assert "@media (max-width: 1024px)" in css # 작은 화면 = 아이콘 레일
|
||||
assert "html:has(.b01-shell) .app-header" in css # 옛 ☰ 걷음
|
||||
|
||||
Reference in New Issue
Block a user