/** * portal/ui/layout.mjs — 고객 포털 공통 화면 틀(§2.1·§2.3). * * 서버가 HTML 뼈대(상단: 현재 그룹·알림·도움말·사용자 메뉴 / 왼쪽: 메뉴 / 본문: 제목·현재 위치·주요 작업 버튼·주의 항목)를 내고, * 각 화면의 데이터는 같은 origin 의 /api/* 를 fetch 하는 작은 화면 스크립트(/ui/app.js + 영역별 js)가 채운다. * 모든 화면은 '불러오는 중·비어 있음·정보 지연·부분 실패·전체 실패·권한 없음' 상태를 공통 컴포넌트(aw-state)로 보인다. * 색만으로 상태를 구분하지 않는다: 상태 배지는 항상 글자(라벨)와 아이콘 문자를 함께 쓴다. 360px 너비에서 표의 핵심 값과 버튼이 가려지지 않게 카드형으로 접힌다. */ import { esc } from '../../lib/http.mjs'; import { t } from '../../lib/i18n.mjs'; const MENU = [ ['dashboard', '/', '▦'], ['sites', '/sites', '▣'], ['mail', '/mail', '✉'], ['domains', '/domains', '◎'], ['backup', '/backup', '⟲'], ['billing', '/billing', '₩'], ['usage', '/usage', '▥'], ['group', '/group', '⚇'], ]; /** * 전체 화면 HTML. * @param {object} o { title, nav(현재 메뉴 키), crumbs:[{label,href}], actions:[{label,href|onclick,kind}], user, group, groups:[], unread, body(html), scripts:[js 경로], alerts:[{severity,text,href}] } */ export function page(o) { const user = o.user || null; const group = o.group || null; const groups = o.groups || []; const nav = MENU.map(([k, href, ic]) => `${esc(t('nav.' + k))}`).join(''); const crumbs = (o.crumbs || []).map((c, i, a) => (i === a.length - 1 || !c.href) ? `${esc(c.label)}` : `${esc(c.label)}`).join(' '); const actions = (o.actions || []).map((a) => a.href ? `${esc(a.label)}` : ``).join(''); const alerts = (o.alerts || []).map((a) => `${a.severity === 'critical' ? '⚠ ' : a.severity === 'info' ? 'ⓘ ' : '! '}${esc(a.text)}`).join(''); return `
${esc(text)}
` : ''}${href ? `이동` : ''}${retry ? `` : ''}