FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

804 regels
28 KiB

  1. import { useSession } from "next-auth/react";
  2. import Box from "@mui/material/Box";
  3. import Stack from "@mui/material/Stack";
  4. import Typography from "@mui/material/Typography";
  5. import React from "react";
  6. import List from "@mui/material/List";
  7. import ListItemButton from "@mui/material/ListItemButton";
  8. import ListItemText from "@mui/material/ListItemText";
  9. import ListItemIcon from "@mui/material/ListItemIcon";
  10. import Dashboard from "@mui/icons-material/Dashboard";
  11. import Storefront from "@mui/icons-material/Storefront";
  12. import LocalShipping from "@mui/icons-material/LocalShipping";
  13. import Assignment from "@mui/icons-material/Assignment";
  14. import Inventory from "@mui/icons-material/Inventory";
  15. import AssignmentTurnedIn from "@mui/icons-material/AssignmentTurnedIn";
  16. import ReportProblem from "@mui/icons-material/ReportProblem";
  17. import QrCodeIcon from "@mui/icons-material/QrCode";
  18. import ViewModule from "@mui/icons-material/ViewModule";
  19. import Description from "@mui/icons-material/Description";
  20. import CalendarMonth from "@mui/icons-material/CalendarMonth";
  21. import Factory from "@mui/icons-material/Factory";
  22. import PostAdd from "@mui/icons-material/PostAdd";
  23. import Kitchen from "@mui/icons-material/Kitchen";
  24. import Inventory2 from "@mui/icons-material/Inventory2";
  25. import Print from "@mui/icons-material/Print";
  26. import Assessment from "@mui/icons-material/Assessment";
  27. import ShowChart from "@mui/icons-material/ShowChart";
  28. import Settings from "@mui/icons-material/Settings";
  29. import Person from "@mui/icons-material/Person";
  30. import Group from "@mui/icons-material/Group";
  31. import Category from "@mui/icons-material/Category";
  32. import TrendingUp from "@mui/icons-material/TrendingUp";
  33. import Build from "@mui/icons-material/Build";
  34. import Warehouse from "@mui/icons-material/Warehouse";
  35. import VerifiedUser from "@mui/icons-material/VerifiedUser";
  36. import Label from "@mui/icons-material/Label";
  37. import Checklist from "@mui/icons-material/Checklist";
  38. import Science from "@mui/icons-material/Science";
  39. import UploadFile from "@mui/icons-material/UploadFile";
  40. import Sync from "@mui/icons-material/Sync";
  41. import Layers from "@mui/icons-material/Layers";
  42. import Devices from "@mui/icons-material/Devices";
  43. import EventAvailable from "@mui/icons-material/EventAvailable";
  44. import { useTranslation } from "react-i18next";
  45. import { usePathname } from "next/navigation";
  46. import Link from "next/link";
  47. import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig";
  48. import Logo from "../Logo";
  49. import { AUTH } from "../../authorities";
  50. import { isMonitoringEnabled } from "@/config/monitoring";
  51. import PurchaseStockInNavAlerts from "./PurchaseStockInNavAlerts";
  52. import JobOrderFgStockInNavAlerts from "./JobOrderFgStockInNavAlerts";
  53. import MasterDataIssuesNavBadge from "./MasterDataIssuesNavBadge";
  54. interface NavigationItem {
  55. id: string;
  56. icon: React.ReactNode;
  57. labelKey: string;
  58. path: string;
  59. children?: NavigationItem[];
  60. isHidden?: boolean | undefined;
  61. requiredAbility?: string | string[];
  62. }
  63. /** FP-MTMS Version Checklist | Functions Ref. No. 67 | v1.0.0 | 2026-08-13 */
  64. const NavigationContent: React.FC = () => {
  65. const { data: session, status } = useSession();
  66. const abilities = session?.user?.abilities ?? [];
  67. // Helper: check if user has required permission
  68. const hasAbility = (required?: string | string[]): boolean => {
  69. if (!required) return true; // no requirement → always show
  70. if (Array.isArray(required)) {
  71. return required.some(ability => abilities.includes(ability));
  72. }
  73. return abilities.includes(required);
  74. };
  75. const navigationItems: NavigationItem[] = [
  76. {
  77. id: "nav.dashboard",
  78. icon: <Dashboard />,
  79. labelKey: "nav.dashboard",
  80. path: "/dashboard",
  81. },
  82. {
  83. id: "nav.storeManagement",
  84. icon: <Storefront />,
  85. labelKey: "nav.storeManagement",
  86. path: "",
  87. requiredAbility: [AUTH.PURCHASE, AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.STOCK_FG, AUTH.STOCK_IN_BIND, AUTH.ITEM_TRACING, AUTH.ADMIN],
  88. children: [
  89. {
  90. id: "nav.store.purchaseOrder",
  91. icon: <LocalShipping />,
  92. labelKey: "nav.store.purchaseOrder",
  93. requiredAbility: [AUTH.PURCHASE, AUTH.ADMIN],
  94. path: "/po",
  95. },
  96. {
  97. id: "nav.store.pickOrder",
  98. icon: <Assignment />,
  99. labelKey: "nav.store.pickOrder",
  100. requiredAbility: [AUTH.STOCK, AUTH.ADMIN],
  101. path: "/pickOrder",
  102. },
  103. {
  104. id: "nav.store.inventoryLedger",
  105. icon: <Inventory />,
  106. labelKey: "nav.store.inventoryLedger",
  107. requiredAbility: [AUTH.STOCK, AUTH.ADMIN],
  108. path: "/inventory",
  109. },
  110. {
  111. id: "nav.store.itemTracing",
  112. icon: <QrCodeIcon />,
  113. labelKey: "nav.store.itemTracing",
  114. requiredAbility: [AUTH.ITEM_TRACING],
  115. path: "/itemTracing",
  116. },
  117. {
  118. id: "nav.store.stockTake",
  119. icon: <AssignmentTurnedIn />,
  120. labelKey: "nav.store.stockTake",
  121. requiredAbility: [AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.ADMIN],
  122. path: "/stocktakemanagement",
  123. },
  124. {
  125. id: "nav.store.stockIssue",
  126. icon: <ReportProblem />,
  127. labelKey: "nav.store.stockIssue",
  128. requiredAbility: [AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.ADMIN],
  129. path: "/stockIssue",
  130. },
  131. {
  132. id: "nav.store.putAwayScan",
  133. icon: <QrCodeIcon />,
  134. labelKey: "nav.store.putAwayScan",
  135. requiredAbility: [AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.STOCK_IN_BIND, AUTH.ADMIN],
  136. path: "/putAway",
  137. },
  138. {
  139. id: "nav.store.finishedGoodManagement",
  140. icon: <ViewModule />,
  141. labelKey: "nav.store.finishedGoodManagement",
  142. requiredAbility: [AUTH.ADMIN],
  143. path: "/finishedGood/management",
  144. },
  145. {
  146. id: "nav.store.stockRecord",
  147. icon: <Description />,
  148. labelKey: "nav.store.stockRecord",
  149. requiredAbility: [AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.STOCK_IN_BIND, AUTH.STOCK_FG, AUTH.ADMIN],
  150. path: "/stockRecord",
  151. },
  152. {
  153. id: "nav.store.doWorkbench",
  154. icon: <Description />,
  155. labelKey: "nav.store.doWorkbench",
  156. requiredAbility: [AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.STOCK_IN_BIND, AUTH.STOCK_FG, AUTH.ADMIN],
  157. path: "/doworkbench",
  158. },
  159. ],
  160. },
  161. {
  162. id: "nav.deliveryOrder",
  163. icon: <LocalShipping />,
  164. labelKey: "nav.deliveryOrder",
  165. path: "/do",
  166. requiredAbility: [AUTH.STOCK_FG, AUTH.ADMIN],
  167. },
  168. {
  169. id: "nav.scheduling",
  170. icon: <CalendarMonth />,
  171. labelKey: "nav.scheduling",
  172. path: "/ps",
  173. requiredAbility: [AUTH.FORECAST, AUTH.ADMIN],
  174. isHidden: false,
  175. },
  176. {
  177. id: "nav.jobOrderManagement",
  178. icon: <Factory />,
  179. labelKey: "nav.jobOrderManagement",
  180. path: "",
  181. requiredAbility: [AUTH.JOB_CREATE, AUTH.JOB_PICK, AUTH.JOB_PROD, AUTH.ADMIN],
  182. children: [
  183. {
  184. id: "nav.jobOrder.searchCreate",
  185. icon: <PostAdd />,
  186. labelKey: "nav.jobOrder.searchCreate",
  187. requiredAbility: [AUTH.JOB_CREATE, AUTH.ADMIN],
  188. path: "/jo",
  189. },
  190. {
  191. id: "nav.jobOrder.pickExecution",
  192. icon: <Inventory />,
  193. labelKey: "nav.jobOrder.pickExecution",
  194. requiredAbility: [AUTH.JOB_PICK, AUTH.JOB_MAT, AUTH.ADMIN],
  195. path: "/jodetail",
  196. },
  197. {
  198. id: "nav.jobOrder.productionProcess",
  199. icon: <Kitchen />,
  200. labelKey: "nav.jobOrder.productionProcess",
  201. requiredAbility: [AUTH.JOB_PROD, AUTH.ADMIN],
  202. path: "/productionProcess",
  203. },
  204. {
  205. id: "nav.jobOrder.bagUsage",
  206. icon: <Inventory2 />,
  207. labelKey: "nav.jobOrder.bagUsage",
  208. requiredAbility: [AUTH.JOB_PROD, AUTH.ADMIN],
  209. path: "/bag",
  210. },
  211. ],
  212. },
  213. {
  214. id: "nav.bagPrint",
  215. icon: <Print />,
  216. labelKey: "nav.bagPrint",
  217. path: "/bagPrint",
  218. requiredAbility: [AUTH.JOB_PROD, AUTH.ADMIN],
  219. isHidden: false,
  220. },
  221. {
  222. id: "nav.laserPrint",
  223. icon: <Print />,
  224. labelKey: "nav.laserPrint",
  225. path: "/laserPrint",
  226. requiredAbility: [AUTH.JOB_PROD, AUTH.ADMIN],
  227. isHidden: false,
  228. },
  229. {
  230. id: "nav.report",
  231. icon: <Assessment />,
  232. labelKey: "nav.report",
  233. path: "/report",
  234. requiredAbility: [AUTH.REPORT_MGMT, AUTH.ADMIN],
  235. isHidden: false,
  236. },
  237. {
  238. id: "nav.m18Sync",
  239. icon: <Sync />,
  240. labelKey: "nav.m18Sync",
  241. path: "/m18Syn",
  242. requiredAbility: [AUTH.M18_SYNC, AUTH.ADMIN],
  243. isHidden: false,
  244. },
  245. {
  246. id: "nav.chartReports",
  247. icon: <ShowChart />,
  248. labelKey: "nav.chartReports",
  249. path: "",
  250. requiredAbility: [AUTH.TESTING, AUTH.ADMIN, AUTH.PERFORMANCE],
  251. isHidden: false,
  252. children: [
  253. {
  254. id: "nav.chart.purchase",
  255. icon: <Storefront />,
  256. labelKey: "nav.chart.purchase",
  257. path: "/chart/purchase",
  258. requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
  259. },
  260. {
  261. id: "nav.chart.jobOrder",
  262. icon: <Assignment />,
  263. labelKey: "nav.chart.jobOrder",
  264. path: "/chart/joborder",
  265. requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
  266. },
  267. {
  268. id: "nav.chart.jobOrderBoard",
  269. icon: <ViewModule />,
  270. labelKey: "nav.chart.jobOrderBoard",
  271. path: "/chart/joborder/board",
  272. requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
  273. },
  274. {
  275. id: "nav.chart.delivery",
  276. icon: <LocalShipping />,
  277. labelKey: "nav.chart.delivery",
  278. path: "/chart/delivery",
  279. requiredAbility: [AUTH.TESTING, AUTH.ADMIN, AUTH.PERFORMANCE],
  280. },
  281. {
  282. id: "nav.chart.warehouse",
  283. icon: <Warehouse />,
  284. labelKey: "nav.chart.warehouse",
  285. path: "/chart/warehouse",
  286. requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
  287. },
  288. {
  289. id: "nav.chart.forecast",
  290. icon: <TrendingUp />,
  291. labelKey: "nav.chart.forecast",
  292. path: "/chart/forecast",
  293. requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
  294. },
  295. ],
  296. },
  297. {
  298. id: "nav.settings",
  299. icon: <Settings />,
  300. labelKey: "nav.settings",
  301. path: "",
  302. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN, AUTH.TRUCK_LANE],
  303. children: [
  304. {
  305. id: "nav.settings.user",
  306. icon: <Person />,
  307. labelKey: "nav.settings.user",
  308. path: "/settings/user",
  309. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  310. },
  311. {
  312. id: "nav.settings.clientMonitor",
  313. icon: <Devices />,
  314. labelKey: "nav.settings.clientMonitor",
  315. path: "/settings/clientMonitor",
  316. requiredAbility: [AUTH.ADMIN, AUTH.TESTING],
  317. isHidden: !isMonitoringEnabled,
  318. },
  319. {
  320. id: "nav.settings.items",
  321. icon: <Category />,
  322. labelKey: "nav.settings.items",
  323. path: "/settings/items",
  324. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  325. },
  326. {
  327. id: "nav.settings.itemDefaultShelfLife",
  328. icon: <EventAvailable />,
  329. labelKey: "nav.settings.itemDefaultShelfLife",
  330. path: "/settings/itemDefaultShelfLife",
  331. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  332. },
  333. {
  334. id: "nav.settings.equipment",
  335. icon: <Build />,
  336. labelKey: "nav.settings.equipment",
  337. path: "/settings/equipment",
  338. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  339. },
  340. {
  341. id: "nav.settings.warehouse",
  342. icon: <Warehouse />,
  343. labelKey: "nav.settings.warehouse",
  344. path: "/settings/warehouse",
  345. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  346. },
  347. {
  348. id: "nav.settings.printer",
  349. icon: <Print />,
  350. labelKey: "nav.settings.printer",
  351. path: "/settings/printer",
  352. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  353. },
  354. {
  355. id: "nav.settings.priceInquiry",
  356. icon: <Assessment />,
  357. labelKey: "nav.settings.priceInquiry",
  358. path: "/settings/itemPrice",
  359. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  360. },
  361. {
  362. id: "nav.settings.qcItem",
  363. icon: <VerifiedUser />,
  364. labelKey: "nav.settings.qcItem",
  365. path: "/settings/qcItem",
  366. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  367. },
  368. {
  369. id: "nav.settings.qcCategory",
  370. icon: <Label />,
  371. labelKey: "nav.settings.qcCategory",
  372. path: "/settings/qcCategory",
  373. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  374. },
  375. {
  376. id: "nav.settings.qcItemAll",
  377. icon: <Checklist />,
  378. labelKey: "nav.settings.qcItemAll",
  379. path: "/settings/qcItemAll",
  380. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  381. },
  382. {
  383. id: "nav.settings.shopAndTruck",
  384. icon: <Storefront />,
  385. labelKey: "nav.settings.shopAndTruck",
  386. path: "/settings/shop/board",
  387. requiredAbility: [AUTH.TRUCK_LANE, AUTH.ADMIN],
  388. },
  389. {
  390. id: "nav.settings.deliveryOrderFloor",
  391. icon: <Layers />,
  392. labelKey: "nav.settings.deliveryOrderFloor",
  393. path: "/settings/deliveryOrderFloor",
  394. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  395. },
  396. {
  397. id: "nav.settings.demandForecast",
  398. icon: <TrendingUp />,
  399. labelKey: "nav.settings.demandForecast",
  400. path: "/settings/rss",
  401. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  402. },
  403. {
  404. id: "nav.settings.bomWeighting",
  405. icon: <ViewModule />,
  406. labelKey: "nav.settings.bomWeighting",
  407. path: "/settings/bomWeighting",
  408. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  409. },
  410. {
  411. id: "nav.settings.masterDataIssues",
  412. icon: <ReportProblem />,
  413. labelKey: "nav.settings.masterDataIssues",
  414. path: "/settings/masterDataIssues",
  415. requiredAbility: [AUTH.ADMIN],
  416. },
  417. {
  418. id: "nav.settings.qrCodeHandle",
  419. icon: <QrCodeIcon />,
  420. labelKey: "nav.settings.qrCodeHandle",
  421. path: "/settings/qrCodeHandle",
  422. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  423. },
  424. {
  425. id: "nav.settings.importTesting",
  426. icon: <Science />,
  427. labelKey: "nav.settings.importTesting",
  428. path: "/settings/m18ImportTesting",
  429. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  430. },
  431. {
  432. id: "nav.settings.importExcel",
  433. icon: <UploadFile />,
  434. labelKey: "nav.settings.importExcel",
  435. path: "/settings/importExcel",
  436. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  437. },
  438. {
  439. id: "nav.settings.importBom",
  440. icon: <UploadFile />,
  441. labelKey: "nav.settings.importBom",
  442. path: "/settings/importBom",
  443. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  444. },
  445. ],
  446. },
  447. ];
  448. const { t } = useTranslation("navigation");
  449. const pathname = usePathname();
  450. const abilitySet = new Set(abilities.map((a) => String(a).trim()));
  451. /** 採購入庫側欄紅點:TESTING / ADMIN / STOCK */
  452. const canSeePoAlerts =
  453. abilitySet.has(AUTH.TESTING) || abilitySet.has(AUTH.ADMIN) || abilitySet.has(AUTH.STOCK);
  454. /** 工單 QC/上架紅點:仍僅 TESTING */
  455. const canSeeJoFgAlerts = abilitySet.has(AUTH.TESTING);
  456. const canSeeMasterDataIssueBadge = abilitySet.has(AUTH.ADMIN);
  457. const [openItems, setOpenItems] = React.useState<string[]>([]);
  458. /** Keep parent sections expanded on deep links (e.g. /po/edit from nav red spot) so alerts stay visible. */
  459. React.useEffect(() => {
  460. const ensureOpen: string[] = [];
  461. if (pathname.startsWith("/chart")) {
  462. ensureOpen.push("nav.chartReports");
  463. }
  464. if (pathname === "/po" || pathname.startsWith("/po/")) {
  465. ensureOpen.push("nav.storeManagement");
  466. }
  467. if (pathname === "/productionProcess" || pathname.startsWith("/productionProcess/")) {
  468. ensureOpen.push("nav.jobOrderManagement");
  469. }
  470. if (
  471. pathname === "/jo/workbench" ||
  472. pathname.startsWith("/jo/workbench/") ||
  473. pathname === "/jodetail" ||
  474. pathname.startsWith("/jodetail/")
  475. ) {
  476. ensureOpen.push("nav.jobOrderManagement");
  477. }
  478. if (
  479. pathname === "/doworkbench" ||
  480. pathname.startsWith("/doworkbench/") ||
  481. pathname === "/doworkbenchsearch" ||
  482. pathname.startsWith("/doworkbenchsearch/")
  483. ) {
  484. ensureOpen.push("nav.storeManagement");
  485. }
  486. if (ensureOpen.length === 0) return;
  487. setOpenItems((prev) => {
  488. const set = new Set(prev);
  489. let changed = false;
  490. for (const id of ensureOpen) {
  491. if (!set.has(id)) {
  492. set.add(id);
  493. changed = true;
  494. }
  495. }
  496. return changed ? Array.from(set) : prev;
  497. });
  498. }, [pathname]);
  499. const toggleItem = (id: string) => {
  500. setOpenItems((prevOpenItems) =>
  501. prevOpenItems.includes(id)
  502. ? prevOpenItems.filter((item) => item !== id)
  503. : [...prevOpenItems, id],
  504. );
  505. };
  506. const selectedLeafPath = React.useMemo(() => {
  507. const leafPaths: string[] = [];
  508. const walk = (items: NavigationItem[]) => {
  509. for (const it of items) {
  510. if (it.isHidden) continue;
  511. if (!hasAbility(it.requiredAbility)) continue;
  512. if (it.path) leafPaths.push(it.path);
  513. if (it.children?.length) walk(it.children);
  514. }
  515. };
  516. walk(navigationItems);
  517. // Pick the most specific (longest) match to avoid double-highlighting
  518. const matches = leafPaths.filter((p) => {
  519. if (pathname === p) return true;
  520. if (!pathname.startsWith(p + "/")) return false;
  521. // `/doworkbench` must not claim `/doworkbenchsearch` (prefix without trailing slash)
  522. if (p === "/doworkbench" && pathname.startsWith("/doworkbenchsearch")) return false;
  523. // Shop sub-routes (detail, truckdetail, legacy tab page) share one nav entry → board
  524. if (
  525. p === "/settings/shop/board" &&
  526. (pathname === "/settings/shop" || pathname.startsWith("/settings/shop/"))
  527. ) {
  528. return true;
  529. }
  530. return true;
  531. });
  532. matches.sort((a, b) => b.length - a.length);
  533. return matches[0] ?? "";
  534. }, [hasAbility, navigationItems, pathname]);
  535. const renderNavigationItem = (item: NavigationItem) => {
  536. if (!hasAbility(item.requiredAbility)) {
  537. return null;
  538. }
  539. const isOpen = openItems.includes(item.id);
  540. const hasVisibleChildren = item.children?.some(child => hasAbility(child.requiredAbility));
  541. const isLeaf = Boolean(item.path);
  542. const isSelected = isLeaf && item.path
  543. ? item.path === selectedLeafPath
  544. : hasVisibleChildren && item.children?.some((c) => c.path && c.path === selectedLeafPath);
  545. const content = (
  546. <ListItemButton
  547. selected={isSelected}
  548. onClick={isLeaf ? undefined : () => toggleItem(item.id)}
  549. sx={{
  550. mx: 1,
  551. "&.Mui-selected .MuiListItemIcon-root": { color: "primary.main" },
  552. }}
  553. >
  554. <ListItemIcon sx={{ minWidth: 40 }}>{item.icon}</ListItemIcon>
  555. <ListItemText
  556. primary={t(item.labelKey)}
  557. primaryTypographyProps={{ fontWeight: isSelected ? 600 : 500 }}
  558. />
  559. </ListItemButton>
  560. );
  561. return (
  562. <Box key={item.id}>
  563. {isLeaf ? (
  564. <Link href={item.path!} style={{ textDecoration: "none", color: "inherit" }}>
  565. {content}
  566. </Link>
  567. ) : (
  568. content
  569. )}
  570. {item.children && isOpen && hasVisibleChildren && (
  571. <List sx={{ pl: 2, py: 0 }}>
  572. {item.children.map(
  573. (child) => !child.isHidden && hasAbility(child.requiredAbility) && (
  574. child.path === "/po" ? (
  575. <Box
  576. key={child.id}
  577. sx={{
  578. display: "flex",
  579. alignItems: "stretch",
  580. mx: 1,
  581. borderRadius: 1,
  582. overflow: "hidden",
  583. "&:hover": { bgcolor: "action.hover" },
  584. }}
  585. >
  586. <Box
  587. component={Link}
  588. href={child.path}
  589. sx={{
  590. flex: 1,
  591. minWidth: 0,
  592. textDecoration: "none",
  593. color: "inherit",
  594. display: "flex",
  595. }}
  596. >
  597. <ListItemButton
  598. selected={child.path === selectedLeafPath}
  599. sx={{
  600. flex: 1,
  601. py: 1,
  602. pr: 0.5,
  603. "&.Mui-selected .MuiListItemIcon-root": { color: "primary.main" },
  604. }}
  605. >
  606. <ListItemIcon sx={{ minWidth: 40 }}>{child.icon}</ListItemIcon>
  607. <ListItemText
  608. primary={t(child.labelKey)}
  609. primaryTypographyProps={{
  610. fontWeight:
  611. child.path === selectedLeafPath ? 600 : 500,
  612. fontSize: "0.875rem",
  613. }}
  614. />
  615. </ListItemButton>
  616. </Box>
  617. <PurchaseStockInNavAlerts enabled={canSeePoAlerts} />
  618. </Box>
  619. ) : child.path === "/settings/masterDataIssues" ? (
  620. <Box
  621. key={child.id}
  622. component={Link}
  623. href={child.path}
  624. sx={{
  625. display: "block",
  626. mx: 1,
  627. borderRadius: 1,
  628. textDecoration: "none",
  629. color: "inherit",
  630. "&:hover": { bgcolor: "action.hover" },
  631. }}
  632. >
  633. <ListItemButton
  634. selected={child.path === selectedLeafPath}
  635. sx={{
  636. py: 1,
  637. pr: 1.5,
  638. display: "flex",
  639. alignItems: "center",
  640. gap: 0.5,
  641. "&.Mui-selected .MuiListItemIcon-root": { color: "primary.main" },
  642. }}
  643. >
  644. <ListItemIcon sx={{ minWidth: 40 }}>{child.icon}</ListItemIcon>
  645. <ListItemText
  646. primary={t(child.labelKey)}
  647. sx={{ flex: 1, minWidth: 0, my: 0 }}
  648. primaryTypographyProps={{
  649. fontWeight:
  650. child.path === selectedLeafPath ? 600 : 500,
  651. fontSize: "0.875rem",
  652. }}
  653. />
  654. <MasterDataIssuesNavBadge enabled={canSeeMasterDataIssueBadge} />
  655. </ListItemButton>
  656. </Box>
  657. ) : child.path === "/productionProcess" ? (
  658. <Box
  659. key={child.id}
  660. sx={{
  661. display: "flex",
  662. alignItems: "stretch",
  663. mx: 1,
  664. borderRadius: 1,
  665. overflow: "hidden",
  666. "&:hover": { bgcolor: "action.hover" },
  667. }}
  668. >
  669. <Box
  670. component={Link}
  671. href={child.path}
  672. sx={{
  673. flex: 1,
  674. minWidth: 0,
  675. textDecoration: "none",
  676. color: "inherit",
  677. display: "flex",
  678. }}
  679. >
  680. <ListItemButton
  681. selected={child.path === selectedLeafPath}
  682. sx={{
  683. flex: 1,
  684. py: 1,
  685. pr: 0.5,
  686. "&.Mui-selected .MuiListItemIcon-root": { color: "primary.main" },
  687. }}
  688. >
  689. <ListItemIcon sx={{ minWidth: 40 }}>{child.icon}</ListItemIcon>
  690. <ListItemText
  691. primary={t(child.labelKey)}
  692. primaryTypographyProps={{
  693. fontWeight:
  694. child.path === selectedLeafPath ? 600 : 500,
  695. fontSize: "0.875rem",
  696. }}
  697. />
  698. </ListItemButton>
  699. </Box>
  700. <JobOrderFgStockInNavAlerts enabled={canSeeJoFgAlerts} />
  701. </Box>
  702. ) : (
  703. <Box
  704. key={child.id}
  705. component={Link}
  706. href={child.path}
  707. sx={{ textDecoration: "none", color: "inherit" }}
  708. >
  709. <ListItemButton
  710. selected={child.path === selectedLeafPath}
  711. sx={{
  712. mx: 1,
  713. py: 1,
  714. "&.Mui-selected .MuiListItemIcon-root": { color: "primary.main" },
  715. }}
  716. >
  717. <ListItemIcon sx={{ minWidth: 40 }}>{child.icon}</ListItemIcon>
  718. <ListItemText
  719. primary={t(child.labelKey)}
  720. primaryTypographyProps={{
  721. fontWeight:
  722. child.path === selectedLeafPath
  723. ? 600
  724. : 500,
  725. fontSize: "0.875rem",
  726. }}
  727. />
  728. </ListItemButton>
  729. </Box>
  730. )
  731. ),
  732. )}
  733. </List>
  734. )}
  735. </Box>
  736. );
  737. };
  738. if (status === "loading") {
  739. return <Box sx={{ width: NAVIGATION_CONTENT_WIDTH, p: 3 }}>Loading...</Box>;
  740. }
  741. return (
  742. <Box sx={{ width: NAVIGATION_CONTENT_WIDTH, height: "100%", display: "flex", flexDirection: "column" }}>
  743. <Box
  744. className="bg-gradient-to-br from-blue-500/15 via-slate-100 to-slate-50 dark:from-blue-500/20 dark:via-slate-800 dark:to-slate-900"
  745. sx={{
  746. mx: 1,
  747. mt: 1,
  748. mb: 1,
  749. px: 1.5,
  750. py: 2,
  751. flexShrink: 0,
  752. display: "flex",
  753. alignItems: "center",
  754. justifyContent: "flex-start",
  755. minHeight: 56,
  756. }}
  757. >
  758. <Stack direction="column" alignItems="flex-start" spacing={0.5}>
  759. <Logo height={42} />
  760. {process.env.NODE_ENV === "production" && (
  761. <Typography
  762. variant="body2"
  763. sx={{
  764. fontWeight: 700,
  765. color: "warning.dark",
  766. bgcolor: "warning.light",
  767. px: 1,
  768. py: 0.5,
  769. borderRadius: 1,
  770. border: "1px solid",
  771. borderColor: "warning.main",
  772. }}
  773. >
  774. {t("nav.productionServer")}
  775. </Typography>
  776. )}
  777. </Stack>
  778. </Box>
  779. <Box sx={{ borderTop: 1, borderColor: "divider" }} />
  780. <List component="nav" sx={{ flex: 1, overflow: "auto", py: 1, px: 0 }}>
  781. {navigationItems
  782. .filter(item => !item.isHidden)
  783. .map(renderNavigationItem)
  784. .filter(Boolean)}
  785. </List>
  786. </Box>
  787. );
  788. };
  789. export default NavigationContent;