Order Page
This commit is contained in:
@@ -39,7 +39,7 @@ export default async function ProtectedLayout({ children }: { children: ReactNod
|
|||||||
redirect(`/login?returnUrl=${encodeURIComponent("/dashboard")}`);
|
redirect(`/login?returnUrl=${encodeURIComponent("/dashboard")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nav = buildNavForRoles(merged.roles);
|
const nav = buildNavForRoles(merged.roles, [], { showAll: true });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell user={merged} nav={nav}>
|
<AppShell user={merged} nav={nav}>
|
||||||
|
|||||||
1194
src/app/(protected)/orders/page.tsx
Normal file
1194
src/app/(protected)/orders/page.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,7 @@ export default function AppShell({ user, nav, children }: Props) {
|
|||||||
{/* Sidebar (desktop) */}
|
{/* Sidebar (desktop) */}
|
||||||
<aside
|
<aside
|
||||||
className={[
|
className={[
|
||||||
"hidden md:flex sticky top-0 h-screen flex-col border-r border-neutral-200/70 bg-white/70 backdrop-blur",
|
"hidden md:flex sticky top-0 h-screen flex-col min-h-0 border-r border-neutral-200/70 bg-white/70 backdrop-blur",
|
||||||
"transition-[width] duration-300 ease-out",
|
"transition-[width] duration-300 ease-out",
|
||||||
sidebarOpen ? "w-72" : "w-20",
|
sidebarOpen ? "w-72" : "w-20",
|
||||||
"shadow-[inset_-1px_0_0_rgba(0,0,0,0.03)]",
|
"shadow-[inset_-1px_0_0_rgba(0,0,0,0.03)]",
|
||||||
@@ -37,7 +37,8 @@ export default function AppShell({ user, nav, children }: Props) {
|
|||||||
{/* Brand */}
|
{/* Brand */}
|
||||||
<div className="h-16 flex items-center px-4">
|
<div className="h-16 flex items-center px-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-9 w-9 rounded-2xl bg-neutral-900 text-white grid place-items-center font-semibold shadow-sm">
|
<div
|
||||||
|
className="h-9 w-9 rounded-2xl bg-neutral-900 text-white grid place-items-center font-semibold shadow-sm">
|
||||||
E
|
E
|
||||||
</div>
|
</div>
|
||||||
{sidebarOpen && (
|
{sidebarOpen && (
|
||||||
@@ -61,7 +62,8 @@ export default function AppShell({ user, nav, children }: Props) {
|
|||||||
].join(" ")}
|
].join(" ")}
|
||||||
title="Current tenant"
|
title="Current tenant"
|
||||||
>
|
>
|
||||||
<span className="inline-block h-2.5 w-2.5 rounded-full bg-emerald-500/90 shadow-[0_0_0_3px_rgba(16,185,129,0.12)]" />
|
<span
|
||||||
|
className="inline-block h-2.5 w-2.5 rounded-full bg-emerald-500/90 shadow-[0_0_0_3px_rgba(16,185,129,0.12)]"/>
|
||||||
{sidebarOpen ? (
|
{sidebarOpen ? (
|
||||||
<span className="truncate text-xs text-neutral-700">{user?.tenantKey ?? "—"}</span>
|
<span className="truncate text-xs text-neutral-700">{user?.tenantKey ?? "—"}</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -71,7 +73,7 @@ export default function AppShell({ user, nav, children }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Nav */}
|
{/* Nav */}
|
||||||
<nav className="mt-4 flex-1 space-y-1 px-2">
|
<nav className="mt-4 flex-1 overflow-y-auto space-y-1 px-2 pr-3">
|
||||||
{nav.map((item) => {
|
{nav.map((item) => {
|
||||||
const active = isActive(item);
|
const active = isActive(item);
|
||||||
return (
|
return (
|
||||||
@@ -127,8 +129,10 @@ export default function AppShell({ user, nav, children }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center gap-2 rounded-2xl border border-neutral-200/70 bg-white/70 px-2.5 py-1.5 shadow-sm">
|
<div
|
||||||
<div className="grid h-7 w-7 place-items-center rounded-xl bg-neutral-900 text-[11px] font-semibold text-white shadow-sm">
|
className="flex items-center gap-2 rounded-2xl border border-neutral-200/70 bg-white/70 px-2.5 py-1.5 shadow-sm">
|
||||||
|
<div
|
||||||
|
className="grid h-7 w-7 place-items-center rounded-xl bg-neutral-900 text-[11px] font-semibold text-white shadow-sm">
|
||||||
{initials}
|
{initials}
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden leading-tight sm:block">
|
<div className="hidden leading-tight sm:block">
|
||||||
@@ -142,14 +146,16 @@ export default function AppShell({ user, nav, children }: Props) {
|
|||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<main id="main" className="mx-auto w-full max-w-screen-2xl flex-1 p-4 sm:p-6">
|
<main id="main" className="mx-auto w-full max-w-screen-2xl flex-1 p-4 sm:p-6">
|
||||||
<div className="rounded-3xl border border-neutral-200/70 bg-white/80 p-4 shadow-[0_10px_30px_-12px_rgba(0,0,0,0.08)] sm:p-6">
|
<div
|
||||||
|
className="rounded-3xl border border-neutral-200/70 bg-white/80 p-4 shadow-[0_10px_30px_-12px_rgba(0,0,0,0.08)] sm:p-6">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="mt-auto border-t border-neutral-200/70 bg-white/60 backdrop-blur">
|
<footer className="mt-auto border-t border-neutral-200/70 bg-white/60 backdrop-blur">
|
||||||
<div className="mx-auto flex h-12 max-w-screen-2xl items-center justify-between px-4 text-xs text-neutral-500">
|
<div
|
||||||
|
className="mx-auto flex h-12 max-w-screen-2xl items-center justify-between px-4 text-xs text-neutral-500">
|
||||||
<span>© {year} EOP</span>
|
<span>© {year} EOP</span>
|
||||||
<span className="hidden sm:inline">Built for enterprise operations</span>
|
<span className="hidden sm:inline">Built for enterprise operations</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
430
src/lib/nav.ts
430
src/lib/nav.ts
@@ -1,16 +1,424 @@
|
|||||||
// lib/nav.ts
|
// nav.ts — รวมทุกอย่างไว้ไฟล์เดียว (อัปเดตให้ครอบคลุม “บริการ” ด้วย)
|
||||||
type NavItem = { label: string; href: string };
|
|
||||||
|
|
||||||
export function buildNavForRoles(roles: string[]): NavItem[] {
|
// ---------- Types ----------
|
||||||
const base: NavItem[] = [
|
export type Role =
|
||||||
{ label: "Dashboard", href: "/dashboard" },
|
| "owner"
|
||||||
];
|
| "admin"
|
||||||
if (roles.includes("admin")) {
|
| "ops"
|
||||||
base.push({ label: "Users", href: "/users" });
|
| "sales"
|
||||||
base.push({ label: "Settings", href: "/settings" });
|
| "finance"
|
||||||
|
| "warehouse"
|
||||||
|
| "procurement"
|
||||||
|
| "support"
|
||||||
|
| "marketing"
|
||||||
|
| "hr";
|
||||||
|
|
||||||
|
export type ModuleKey =
|
||||||
|
// Core commerce
|
||||||
|
| "CRM"
|
||||||
|
| "OMS"
|
||||||
|
| "WMS"
|
||||||
|
| "Returns"
|
||||||
|
| "Billing"
|
||||||
|
| "Accounting"
|
||||||
|
| "Loyalty"
|
||||||
|
| "Marketing"
|
||||||
|
| "Reports"
|
||||||
|
| "Audit"
|
||||||
|
| "Integrations"
|
||||||
|
| "CMS"
|
||||||
|
| "KMS"
|
||||||
|
| "AIChat"
|
||||||
|
| "Appointments"
|
||||||
|
// Service business (ใหม่)
|
||||||
|
| "ServiceCatalog" // ขายบริการ/แพ็กเกจ/ราคา
|
||||||
|
| "ServiceOrders" // Work Orders / Service Orders
|
||||||
|
| "ServiceProjects" // โครงการบริการ/PSA
|
||||||
|
| "Timesheets" // ลงเวลา/ต้นทุนแรงงาน
|
||||||
|
| "Contracts" // สัญญา/Entitlements/SLAs
|
||||||
|
| "InstalledBase" // สินทรัพย์ลูกค้า/Serial/ประวัติซ่อม
|
||||||
|
| "Warranty" // การรับประกัน/CLAIM
|
||||||
|
// Enablement & Compliance (แนะนำให้มี)
|
||||||
|
| "Documents" // เอกสาร/เทมเพลต
|
||||||
|
| "ESign" // ลายเซ็นอิเล็กทรอนิกส์
|
||||||
|
| "Compliance"; // PDPA/Consent/Retention/DLP
|
||||||
|
|
||||||
|
export type NavItem = { label: string; href: string };
|
||||||
|
|
||||||
|
export type NavTreeItem = {
|
||||||
|
label: string;
|
||||||
|
href?: string; // กลุ่มใหญ่ไม่ต้องมี href ก็ได้
|
||||||
|
children?: NavTreeItem[]; // เมนูย่อย
|
||||||
|
hidden?: boolean; // สำหรับซ่อนตามสิทธิ์/โมดูล
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- Helpers (เดิม) ----------
|
||||||
|
const hasAnyRole = (roles: string[], list: Role[]) =>
|
||||||
|
list.some((r) => roles.includes(r));
|
||||||
|
|
||||||
|
const modEnabled = (mods: ModuleKey[], key: ModuleKey) => mods.includes(key);
|
||||||
|
|
||||||
|
// ให้ generic รับได้ทั้งที่มี/ไม่มี label เพื่อกัน error
|
||||||
|
type NavLike = { href?: string | null; label?: string | null };
|
||||||
|
|
||||||
|
const uniqByHref = <T extends NavLike>(items: T[]) => {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
return items.filter((i, idx) => {
|
||||||
|
const key =
|
||||||
|
(i.href ?? undefined) ||
|
||||||
|
(i.label ? `__group__${i.label}` : `__idx__${idx}`);
|
||||||
|
if (seen.has(key)) return false;
|
||||||
|
seen.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- Flat nav ----------
|
||||||
|
/**
|
||||||
|
* สร้างเมนูแบบแบน (NavItem[]) สำหรับ topbar/mobile drawer
|
||||||
|
* - โหมดปกติ: เช็ค roles + modules
|
||||||
|
* - โหมดโชว์ทั้งหมด: opts.showAll = true (ไม่เช็คอะไรเลย)
|
||||||
|
*/
|
||||||
|
export function buildNavForRoles(
|
||||||
|
roles: string[],
|
||||||
|
modules: ModuleKey[] = [],
|
||||||
|
opts: { showAll?: boolean } = {}
|
||||||
|
): NavItem[] {
|
||||||
|
const showAll = !!opts.showAll;
|
||||||
|
|
||||||
|
// bypass เมื่อ showAll = true
|
||||||
|
const has = (list: Role[]) => (showAll ? true : hasAnyRole(roles, list));
|
||||||
|
const mod = (key: ModuleKey) => (showAll ? true : modEnabled(modules, key));
|
||||||
|
|
||||||
|
const base: NavItem[] = [{ label: "Dashboard", href: "/dashboard" }];
|
||||||
|
|
||||||
|
// Sales quick links
|
||||||
|
if (mod("OMS") && has(["sales", "ops", "admin", "owner"])) {
|
||||||
|
base.push(
|
||||||
|
{ label: "Orders", href: "/orders" },
|
||||||
|
{ label: "Products", href: "/products" }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (roles.includes("ops")) {
|
if (mod("CRM") && has(["sales", "support", "marketing", "admin", "owner"])) {
|
||||||
|
base.push({ label: "Customers", href: "/crm/customers" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Services quick links (ใหม่)
|
||||||
|
if (
|
||||||
|
(mod("ServiceOrders") || mod("Appointments") || mod("ServiceCatalog")) &&
|
||||||
|
has(["ops", "support", "admin", "owner"])
|
||||||
|
) {
|
||||||
|
base.push({ label: "Services", href: "/services" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ops/Warehouse
|
||||||
|
if (mod("WMS") && has(["warehouse", "ops", "admin", "owner"])) {
|
||||||
|
base.push({ label: "Inventory", href: "/inventory" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marketing
|
||||||
|
if (mod("Loyalty") && has(["marketing", "ops", "admin", "owner"])) {
|
||||||
base.push({ label: "Loyalty", href: "/loyalty" });
|
base.push({ label: "Loyalty", href: "/loyalty" });
|
||||||
}
|
}
|
||||||
return base;
|
|
||||||
|
// Support
|
||||||
|
if (has(["support", "admin", "owner"])) {
|
||||||
|
base.push({ label: "Tickets", href: "/support/tickets" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Knowledge/Chat
|
||||||
|
if (mod("KMS")) base.push({ label: "KMS", href: "/kms" });
|
||||||
|
if (mod("AIChat")) base.push({ label: "Chat", href: "/chat" });
|
||||||
|
|
||||||
|
// Finance
|
||||||
|
if ((mod("Billing") || mod("Accounting")) && has(["finance", "admin", "owner"])) {
|
||||||
|
base.push({ label: "Finance", href: "/finance" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin
|
||||||
|
if (has(["admin", "owner"])) {
|
||||||
|
base.push(
|
||||||
|
{ label: "Users", href: "/settings/users" },
|
||||||
|
{ label: "Settings", href: "/settings" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniqByHref(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Sidebar (grouped tree) ----------
|
||||||
|
/**
|
||||||
|
* Top level (≤ 9 หมวด):
|
||||||
|
* 1) Home
|
||||||
|
* 2) Sales — รวม CRM + Catalog + Orders + Returns
|
||||||
|
* 3) Services — Service Catalog + Work Orders + Schedule/Dispatch + Projects + Timesheets + Contracts + Assets + Warranty
|
||||||
|
* 4) Procurement
|
||||||
|
* 5) Warehouse
|
||||||
|
* 6) Support — Tickets/SLA/CSAT + (shortcut) AI Chat/KMS
|
||||||
|
* 7) Marketing — Campaigns/Coupons + Loyalty + CMS
|
||||||
|
* 8) Finance — Billing + Accounting
|
||||||
|
* 9) Analytics — Reports + Dashboards
|
||||||
|
* + Admin & Settings (Integrations, System, Audit, Compliance, Documents/ESign)
|
||||||
|
*/
|
||||||
|
export function buildSidebarTreeForRoles(
|
||||||
|
roles: string[],
|
||||||
|
modules: ModuleKey[] = [],
|
||||||
|
opts: { showAll?: boolean } = {}
|
||||||
|
): NavTreeItem[] {
|
||||||
|
const showAll = !!opts.showAll;
|
||||||
|
const has = (list: Role[]) => (showAll ? true : hasAnyRole(roles, list));
|
||||||
|
const mod = (key: ModuleKey) => (showAll ? true : modEnabled(modules, key));
|
||||||
|
const hide = (cond: boolean) => (showAll ? false : cond);
|
||||||
|
|
||||||
|
const tree: NavTreeItem[] = [
|
||||||
|
// 1) Home
|
||||||
|
{
|
||||||
|
label: "Home",
|
||||||
|
children: uniqByHref<NavTreeItem>([
|
||||||
|
{ label: "Dashboard", href: "/dashboard" },
|
||||||
|
{ label: "My Tasks", href: "/tasks" },
|
||||||
|
{ label: "Notifications", href: "/notifications" },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
// 2) Sales — รวม CRM + Catalog + Orders + Returns
|
||||||
|
{
|
||||||
|
label: "Sales",
|
||||||
|
hidden: hide(
|
||||||
|
!(
|
||||||
|
(mod("OMS") || mod("CRM") || mod("Returns")) &&
|
||||||
|
has(["sales", "ops", "admin", "owner", "marketing", "support"])
|
||||||
|
)
|
||||||
|
),
|
||||||
|
children: uniqByHref<NavTreeItem>([
|
||||||
|
// CRM
|
||||||
|
{ label: "Customers", href: "/crm/customers", hidden: hide(!mod("CRM")) },
|
||||||
|
{ label: "Contacts", href: "/crm/contacts", hidden: hide(!mod("CRM")) },
|
||||||
|
{ label: "Leads", href: "/crm/leads", hidden: hide(!mod("CRM")) },
|
||||||
|
// Sales core
|
||||||
|
{ label: "Orders", href: "/orders", hidden: hide(!mod("OMS")) },
|
||||||
|
{ label: "Quotes", href: "/sales/quotes", hidden: hide(!mod("OMS")) },
|
||||||
|
{ label: "Subscriptions", href: "/sales/subscriptions", hidden: hide(!mod("OMS")) },
|
||||||
|
// Catalog
|
||||||
|
{ label: "Products", href: "/products", hidden: hide(!mod("OMS")) },
|
||||||
|
{ label: "Bundles", href: "/products/bundles", hidden: hide(!mod("OMS")) },
|
||||||
|
{ label: "Pricing", href: "/products/pricing", hidden: hide(!mod("OMS")) },
|
||||||
|
// Returns
|
||||||
|
{ label: "Returns", href: "/returns", hidden: hide(!mod("Returns")) },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
// 3) Services — บริการปลายทางถึงปลายทาง
|
||||||
|
{
|
||||||
|
label: "Services",
|
||||||
|
hidden: hide(
|
||||||
|
!(
|
||||||
|
(mod("ServiceCatalog") ||
|
||||||
|
mod("ServiceOrders") ||
|
||||||
|
mod("ServiceProjects") ||
|
||||||
|
mod("Appointments") ||
|
||||||
|
mod("Contracts") ||
|
||||||
|
mod("Timesheets") ||
|
||||||
|
mod("InstalledBase") ||
|
||||||
|
mod("Warranty")) &&
|
||||||
|
has(["ops", "support", "admin", "owner", "hr", "finance"])
|
||||||
|
)
|
||||||
|
),
|
||||||
|
children: uniqByHref<NavTreeItem>([
|
||||||
|
// ขายบริการ/แพ็กเกจ
|
||||||
|
{ label: "Service Catalog", href: "/services/catalog", hidden: hide(!mod("ServiceCatalog")) },
|
||||||
|
{ label: "Service Pricing", href: "/services/pricing", hidden: hide(!mod("ServiceCatalog")) },
|
||||||
|
// ดำเนินงานบริการ
|
||||||
|
{ label: "Service Orders", href: "/services/orders", hidden: hide(!mod("ServiceOrders")) },
|
||||||
|
{ label: "Dispatch Board", href: "/services/dispatch", hidden: hide(!mod("ServiceOrders") && !mod("Appointments")) },
|
||||||
|
{ label: "Schedule", href: "/appointments/schedule", hidden: hide(!mod("Appointments")) },
|
||||||
|
{ label: "Resources", href: "/appointments/resources", hidden: hide(!mod("Appointments")) },
|
||||||
|
// โครงการบริการ/บุคลากร
|
||||||
|
{ label: "Projects", href: "/services/projects", hidden: hide(!mod("ServiceProjects")) },
|
||||||
|
{ label: "Timesheets", href: "/services/timesheets", hidden: hide(!mod("Timesheets")) },
|
||||||
|
// สัญญา/สิทธิ์/บริการตาม SLA
|
||||||
|
{ label: "Contracts & SLAs", href: "/services/contracts", hidden: hide(!mod("Contracts")) },
|
||||||
|
// สินทรัพย์ลูกค้า/ประวัติซ่อม
|
||||||
|
{ label: "Installed Base", href: "/services/installed-base", hidden: hide(!mod("InstalledBase")) },
|
||||||
|
{ label: "Warranty Claims", href: "/services/warranty", hidden: hide(!mod("Warranty")) },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
// 4) Procurement
|
||||||
|
{
|
||||||
|
label: "Procurement",
|
||||||
|
hidden: hide(!has(["procurement", "ops", "admin", "owner"])),
|
||||||
|
children: uniqByHref<NavTreeItem>([
|
||||||
|
{ label: "Suppliers", href: "/procurement/suppliers" },
|
||||||
|
{ label: "Purchase Orders", href: "/procurement/purchase-orders" },
|
||||||
|
{ label: "GRN (Receiving)", href: "/procurement/grn" },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
// 5) Warehouse
|
||||||
|
{
|
||||||
|
label: "Warehouse",
|
||||||
|
hidden: hide(!mod("WMS") || !has(["warehouse", "ops", "admin", "owner"])),
|
||||||
|
children: uniqByHref<NavTreeItem>([
|
||||||
|
{ label: "Inventory", href: "/inventory" },
|
||||||
|
{ label: "Receiving", href: "/warehouse/receiving" },
|
||||||
|
{ label: "Picking", href: "/warehouse/picking" },
|
||||||
|
{ label: "Packing", href: "/warehouse/packing" },
|
||||||
|
{ label: "Shipments", href: "/warehouse/shipments" },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
// 6) Support — เพิ่ม shortcut ไป Knowledge/AI Chat (ถ้ามี)
|
||||||
|
{
|
||||||
|
label: "Support",
|
||||||
|
hidden: hide(!has(["support", "admin", "owner", "ops"])),
|
||||||
|
children: uniqByHref<NavTreeItem>([
|
||||||
|
{ label: "Tickets", href: "/support/tickets" },
|
||||||
|
{ label: "SLA", href: "/support/sla" },
|
||||||
|
{ label: "CSAT", href: "/support/csat" },
|
||||||
|
{ label: "AI Chat", href: "/chat", hidden: hide(!mod("AIChat")) },
|
||||||
|
{ label: "Knowledge Base", href: "/kms", hidden: hide(!mod("KMS")) },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
// 7) Marketing — รวม Loyalty + CMS/Content
|
||||||
|
{
|
||||||
|
label: "Marketing",
|
||||||
|
hidden: hide(!has(["marketing", "admin", "owner"])),
|
||||||
|
children: uniqByHref<NavTreeItem>([
|
||||||
|
{ label: "Campaigns", href: "/marketing/campaigns", hidden: hide(!mod("Marketing")) },
|
||||||
|
{ label: "Coupons", href: "/marketing/coupons", hidden: hide(!mod("Marketing")) },
|
||||||
|
{ label: "Loyalty", href: "/loyalty", hidden: hide(!mod("Loyalty")) },
|
||||||
|
{ label: "News", href: "/cms/news", hidden: hide(!mod("CMS")) },
|
||||||
|
{ label: "Pages", href: "/cms/pages", hidden: hide(!mod("CMS")) },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
// 8) Finance
|
||||||
|
{
|
||||||
|
label: "Finance",
|
||||||
|
hidden: hide(
|
||||||
|
!((mod("Billing") || mod("Accounting")) && has(["finance", "admin", "owner"]))
|
||||||
|
),
|
||||||
|
children: uniqByHref<NavTreeItem>([
|
||||||
|
{ label: "Invoices", href: "/finance/invoices", hidden: hide(!mod("Billing")) },
|
||||||
|
{ label: "Payments", href: "/finance/payments", hidden: hide(!mod("Billing")) },
|
||||||
|
{ label: "Refunds", href: "/finance/refunds", hidden: hide(!mod("Billing")) },
|
||||||
|
{ label: "General Ledger", href: "/accounting/gl", hidden: hide(!mod("Accounting")) },
|
||||||
|
{ label: "Reconciliation", href: "/accounting/recon", hidden: hide(!mod("Accounting")) },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
// 9) Analytics
|
||||||
|
{
|
||||||
|
label: "Analytics",
|
||||||
|
hidden: hide(!mod("Reports") || !has(["admin", "owner", "ops", "sales", "finance"])),
|
||||||
|
children: uniqByHref<NavTreeItem>([
|
||||||
|
{ label: "Reports", href: "/reports" },
|
||||||
|
{ label: "Dashboards", href: "/reports/dashboards" },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Admin & Settings (รวม Integrations/Compliance/Documents/Audit/System)
|
||||||
|
{
|
||||||
|
label: "Admin",
|
||||||
|
hidden: hide(!has(["admin", "owner"])),
|
||||||
|
children: uniqByHref<NavTreeItem>([
|
||||||
|
// Identity & Tenant
|
||||||
|
{ label: "Users", href: "/settings/users" },
|
||||||
|
{ label: "Roles & Permissions", href: "/settings/roles" },
|
||||||
|
{ label: "Tenants", href: "/settings/tenants" },
|
||||||
|
{ label: "Settings", href: "/settings" },
|
||||||
|
|
||||||
|
// Integrations
|
||||||
|
{ label: "Flash Express", href: "/integrations/flash-express", hidden: hide(!mod("Integrations")) },
|
||||||
|
{ label: "TikTok Shop", href: "/integrations/tiktok-shop", hidden: hide(!mod("Integrations")) },
|
||||||
|
{ label: "PSP / QR", href: "/integrations/payments", hidden: hide(!mod("Integrations")) },
|
||||||
|
{ label: "Webhooks", href: "/integrations/webhooks", hidden: hide(!mod("Integrations")) },
|
||||||
|
{ label: "API Keys", href: "/integrations/api-keys", hidden: hide(!mod("Integrations")) },
|
||||||
|
|
||||||
|
// Compliance & Docs
|
||||||
|
{ label: "Compliance", href: "/compliance", hidden: hide(!mod("Compliance")) },
|
||||||
|
{ label: "Documents", href: "/documents", hidden: hide(!mod("Documents")) },
|
||||||
|
{ label: "eSign", href: "/documents/esign", hidden: hide(!mod("ESign")) },
|
||||||
|
|
||||||
|
// System
|
||||||
|
{ label: "Audit Trail", href: "/system/audit", hidden: hide(!mod("Audit")) },
|
||||||
|
{ label: "System Health", href: "/system/health" },
|
||||||
|
{ label: "Logs", href: "/system/logs" },
|
||||||
|
{ label: "Developer Sandbox", href: "/developer/sandbox" },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// กรองกลุ่มว่าง + child ที่ hidden
|
||||||
|
const cleaned = tree
|
||||||
|
.map((g) =>
|
||||||
|
!g.children?.length ? g : { ...g, children: g.children.filter((c) => !c.hidden) }
|
||||||
|
)
|
||||||
|
.filter((g) => !g.hidden && (g.children?.length ?? 0) > 0);
|
||||||
|
|
||||||
|
return cleaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Presets & shortcuts ----------
|
||||||
|
export const DEFAULT_MODULES: ModuleKey[] = [
|
||||||
|
// Core
|
||||||
|
"CRM",
|
||||||
|
"OMS",
|
||||||
|
"WMS",
|
||||||
|
"Returns",
|
||||||
|
"Billing",
|
||||||
|
"Accounting",
|
||||||
|
"Loyalty",
|
||||||
|
"Marketing",
|
||||||
|
"KMS",
|
||||||
|
"AIChat",
|
||||||
|
"Appointments",
|
||||||
|
"CMS",
|
||||||
|
"Integrations",
|
||||||
|
"Reports",
|
||||||
|
"Audit",
|
||||||
|
// Service business (ใหม่)
|
||||||
|
"ServiceCatalog",
|
||||||
|
"ServiceOrders",
|
||||||
|
"ServiceProjects",
|
||||||
|
"Timesheets",
|
||||||
|
"Contracts",
|
||||||
|
"InstalledBase",
|
||||||
|
"Warranty",
|
||||||
|
// Enablement & Compliance
|
||||||
|
"Documents",
|
||||||
|
"ESign",
|
||||||
|
"Compliance",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ALL_ROLES: Role[] = [
|
||||||
|
"owner",
|
||||||
|
"admin",
|
||||||
|
"ops",
|
||||||
|
"sales",
|
||||||
|
"finance",
|
||||||
|
"warehouse",
|
||||||
|
"procurement",
|
||||||
|
"support",
|
||||||
|
"marketing",
|
||||||
|
"hr",
|
||||||
|
];
|
||||||
|
|
||||||
|
// โหมดโชว์ครบ แบบไม่ต้องส่ง params
|
||||||
|
export const buildNavShowAll = () =>
|
||||||
|
buildNavForRoles(ALL_ROLES, DEFAULT_MODULES, { showAll: true });
|
||||||
|
|
||||||
|
export const buildSidebarShowAll = () =>
|
||||||
|
buildSidebarTreeForRoles(ALL_ROLES, DEFAULT_MODULES, { showAll: true });
|
||||||
|
|
||||||
|
/*
|
||||||
|
Usage:
|
||||||
|
const topNav = buildNavForRoles(user.roles, enabledModules);
|
||||||
|
const sideTree = buildSidebarTreeForRoles(user.roles, enabledModules);
|
||||||
|
|
||||||
|
// โหมดโชว์ครบ (debug/demo):
|
||||||
|
const topAll = buildNavShowAll();
|
||||||
|
const sideAll = buildSidebarShowAll();
|
||||||
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user