FPSMS-frontend
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 

787 行
27 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],
  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],
  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],
  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. },
  325. {
  326. id: "nav.settings.itemDefaultShelfLife",
  327. icon: <EventAvailable />,
  328. labelKey: "nav.settings.itemDefaultShelfLife",
  329. path: "/settings/itemDefaultShelfLife",
  330. },
  331. {
  332. id: "nav.settings.equipment",
  333. icon: <Build />,
  334. labelKey: "nav.settings.equipment",
  335. path: "/settings/equipment",
  336. },
  337. {
  338. id: "nav.settings.warehouse",
  339. icon: <Warehouse />,
  340. labelKey: "nav.settings.warehouse",
  341. path: "/settings/warehouse",
  342. },
  343. {
  344. id: "nav.settings.printer",
  345. icon: <Print />,
  346. labelKey: "nav.settings.printer",
  347. path: "/settings/printer",
  348. },
  349. {
  350. id: "nav.settings.priceInquiry",
  351. icon: <Assessment />,
  352. labelKey: "nav.settings.priceInquiry",
  353. path: "/settings/itemPrice",
  354. },
  355. {
  356. id: "nav.settings.qcItem",
  357. icon: <VerifiedUser />,
  358. labelKey: "nav.settings.qcItem",
  359. path: "/settings/qcItem",
  360. },
  361. {
  362. id: "nav.settings.qcCategory",
  363. icon: <Label />,
  364. labelKey: "nav.settings.qcCategory",
  365. path: "/settings/qcCategory",
  366. },
  367. {
  368. id: "nav.settings.qcItemAll",
  369. icon: <Checklist />,
  370. labelKey: "nav.settings.qcItemAll",
  371. path: "/settings/qcItemAll",
  372. },
  373. {
  374. id: "nav.settings.shopAndTruck",
  375. icon: <Storefront />,
  376. labelKey: "nav.settings.shopAndTruck",
  377. path: "/settings/shop/board",
  378. },
  379. {
  380. id: "nav.settings.deliveryOrderFloor",
  381. icon: <Layers />,
  382. labelKey: "nav.settings.deliveryOrderFloor",
  383. path: "/settings/deliveryOrderFloor",
  384. },
  385. {
  386. id: "nav.settings.demandForecast",
  387. icon: <TrendingUp />,
  388. labelKey: "nav.settings.demandForecast",
  389. path: "/settings/rss",
  390. },
  391. {
  392. id: "nav.settings.bomWeighting",
  393. icon: <ViewModule />,
  394. labelKey: "nav.settings.bomWeighting",
  395. path: "/settings/bomWeighting",
  396. },
  397. {
  398. id: "nav.settings.masterDataIssues",
  399. icon: <ReportProblem />,
  400. labelKey: "nav.settings.masterDataIssues",
  401. path: "/settings/masterDataIssues",
  402. requiredAbility: [AUTH.ADMIN],
  403. },
  404. {
  405. id: "nav.settings.qrCodeHandle",
  406. icon: <QrCodeIcon />,
  407. labelKey: "nav.settings.qrCodeHandle",
  408. path: "/settings/qrCodeHandle",
  409. },
  410. {
  411. id: "nav.settings.importTesting",
  412. icon: <Science />,
  413. labelKey: "nav.settings.importTesting",
  414. path: "/settings/m18ImportTesting",
  415. },
  416. {
  417. id: "nav.settings.importExcel",
  418. icon: <UploadFile />,
  419. labelKey: "nav.settings.importExcel",
  420. path: "/settings/importExcel",
  421. },
  422. {
  423. id: "nav.settings.importBom",
  424. icon: <UploadFile />,
  425. labelKey: "nav.settings.importBom",
  426. path: "/settings/importBom",
  427. },
  428. ],
  429. },
  430. ];
  431. const { t } = useTranslation("navigation");
  432. const pathname = usePathname();
  433. const abilitySet = new Set(abilities.map((a) => String(a).trim()));
  434. /** 採購入庫側欄紅點:TESTING / ADMIN / STOCK */
  435. const canSeePoAlerts =
  436. abilitySet.has(AUTH.TESTING) || abilitySet.has(AUTH.ADMIN) || abilitySet.has(AUTH.STOCK);
  437. /** 工單 QC/上架紅點:仍僅 TESTING */
  438. const canSeeJoFgAlerts = abilitySet.has(AUTH.TESTING);
  439. const canSeeMasterDataIssueBadge = abilitySet.has(AUTH.ADMIN);
  440. const [openItems, setOpenItems] = React.useState<string[]>([]);
  441. /** Keep parent sections expanded on deep links (e.g. /po/edit from nav red spot) so alerts stay visible. */
  442. React.useEffect(() => {
  443. const ensureOpen: string[] = [];
  444. if (pathname.startsWith("/chart")) {
  445. ensureOpen.push("nav.chartReports");
  446. }
  447. if (pathname === "/po" || pathname.startsWith("/po/")) {
  448. ensureOpen.push("nav.storeManagement");
  449. }
  450. if (pathname === "/productionProcess" || pathname.startsWith("/productionProcess/")) {
  451. ensureOpen.push("nav.jobOrderManagement");
  452. }
  453. if (
  454. pathname === "/jo/workbench" ||
  455. pathname.startsWith("/jo/workbench/") ||
  456. pathname === "/jodetail" ||
  457. pathname.startsWith("/jodetail/")
  458. ) {
  459. ensureOpen.push("nav.jobOrderManagement");
  460. }
  461. if (
  462. pathname === "/doworkbench" ||
  463. pathname.startsWith("/doworkbench/") ||
  464. pathname === "/doworkbenchsearch" ||
  465. pathname.startsWith("/doworkbenchsearch/")
  466. ) {
  467. ensureOpen.push("nav.storeManagement");
  468. }
  469. if (ensureOpen.length === 0) return;
  470. setOpenItems((prev) => {
  471. const set = new Set(prev);
  472. let changed = false;
  473. for (const id of ensureOpen) {
  474. if (!set.has(id)) {
  475. set.add(id);
  476. changed = true;
  477. }
  478. }
  479. return changed ? Array.from(set) : prev;
  480. });
  481. }, [pathname]);
  482. const toggleItem = (id: string) => {
  483. setOpenItems((prevOpenItems) =>
  484. prevOpenItems.includes(id)
  485. ? prevOpenItems.filter((item) => item !== id)
  486. : [...prevOpenItems, id],
  487. );
  488. };
  489. const selectedLeafPath = React.useMemo(() => {
  490. const leafPaths: string[] = [];
  491. const walk = (items: NavigationItem[]) => {
  492. for (const it of items) {
  493. if (it.isHidden) continue;
  494. if (!hasAbility(it.requiredAbility)) continue;
  495. if (it.path) leafPaths.push(it.path);
  496. if (it.children?.length) walk(it.children);
  497. }
  498. };
  499. walk(navigationItems);
  500. // Pick the most specific (longest) match to avoid double-highlighting
  501. const matches = leafPaths.filter((p) => {
  502. if (pathname === p) return true;
  503. if (!pathname.startsWith(p + "/")) return false;
  504. // `/doworkbench` must not claim `/doworkbenchsearch` (prefix without trailing slash)
  505. if (p === "/doworkbench" && pathname.startsWith("/doworkbenchsearch")) return false;
  506. // Shop sub-routes (detail, truckdetail, legacy tab page) share one nav entry → board
  507. if (
  508. p === "/settings/shop/board" &&
  509. (pathname === "/settings/shop" || pathname.startsWith("/settings/shop/"))
  510. ) {
  511. return true;
  512. }
  513. return true;
  514. });
  515. matches.sort((a, b) => b.length - a.length);
  516. return matches[0] ?? "";
  517. }, [hasAbility, navigationItems, pathname]);
  518. const renderNavigationItem = (item: NavigationItem) => {
  519. if (!hasAbility(item.requiredAbility)) {
  520. return null;
  521. }
  522. const isOpen = openItems.includes(item.id);
  523. const hasVisibleChildren = item.children?.some(child => hasAbility(child.requiredAbility));
  524. const isLeaf = Boolean(item.path);
  525. const isSelected = isLeaf && item.path
  526. ? item.path === selectedLeafPath
  527. : hasVisibleChildren && item.children?.some((c) => c.path && c.path === selectedLeafPath);
  528. const content = (
  529. <ListItemButton
  530. selected={isSelected}
  531. onClick={isLeaf ? undefined : () => toggleItem(item.id)}
  532. sx={{
  533. mx: 1,
  534. "&.Mui-selected .MuiListItemIcon-root": { color: "primary.main" },
  535. }}
  536. >
  537. <ListItemIcon sx={{ minWidth: 40 }}>{item.icon}</ListItemIcon>
  538. <ListItemText
  539. primary={t(item.labelKey)}
  540. primaryTypographyProps={{ fontWeight: isSelected ? 600 : 500 }}
  541. />
  542. </ListItemButton>
  543. );
  544. return (
  545. <Box key={item.id}>
  546. {isLeaf ? (
  547. <Link href={item.path!} style={{ textDecoration: "none", color: "inherit" }}>
  548. {content}
  549. </Link>
  550. ) : (
  551. content
  552. )}
  553. {item.children && isOpen && hasVisibleChildren && (
  554. <List sx={{ pl: 2, py: 0 }}>
  555. {item.children.map(
  556. (child) => !child.isHidden && hasAbility(child.requiredAbility) && (
  557. child.path === "/po" ? (
  558. <Box
  559. key={child.id}
  560. sx={{
  561. display: "flex",
  562. alignItems: "stretch",
  563. mx: 1,
  564. borderRadius: 1,
  565. overflow: "hidden",
  566. "&:hover": { bgcolor: "action.hover" },
  567. }}
  568. >
  569. <Box
  570. component={Link}
  571. href={child.path}
  572. sx={{
  573. flex: 1,
  574. minWidth: 0,
  575. textDecoration: "none",
  576. color: "inherit",
  577. display: "flex",
  578. }}
  579. >
  580. <ListItemButton
  581. selected={child.path === selectedLeafPath}
  582. sx={{
  583. flex: 1,
  584. py: 1,
  585. pr: 0.5,
  586. "&.Mui-selected .MuiListItemIcon-root": { color: "primary.main" },
  587. }}
  588. >
  589. <ListItemIcon sx={{ minWidth: 40 }}>{child.icon}</ListItemIcon>
  590. <ListItemText
  591. primary={t(child.labelKey)}
  592. primaryTypographyProps={{
  593. fontWeight:
  594. child.path === selectedLeafPath ? 600 : 500,
  595. fontSize: "0.875rem",
  596. }}
  597. />
  598. </ListItemButton>
  599. </Box>
  600. <PurchaseStockInNavAlerts enabled={canSeePoAlerts} />
  601. </Box>
  602. ) : child.path === "/settings/masterDataIssues" ? (
  603. <Box
  604. key={child.id}
  605. component={Link}
  606. href={child.path}
  607. sx={{
  608. display: "block",
  609. mx: 1,
  610. borderRadius: 1,
  611. textDecoration: "none",
  612. color: "inherit",
  613. "&:hover": { bgcolor: "action.hover" },
  614. }}
  615. >
  616. <ListItemButton
  617. selected={child.path === selectedLeafPath}
  618. sx={{
  619. py: 1,
  620. pr: 1.5,
  621. display: "flex",
  622. alignItems: "center",
  623. gap: 0.5,
  624. "&.Mui-selected .MuiListItemIcon-root": { color: "primary.main" },
  625. }}
  626. >
  627. <ListItemIcon sx={{ minWidth: 40 }}>{child.icon}</ListItemIcon>
  628. <ListItemText
  629. primary={t(child.labelKey)}
  630. sx={{ flex: 1, minWidth: 0, my: 0 }}
  631. primaryTypographyProps={{
  632. fontWeight:
  633. child.path === selectedLeafPath ? 600 : 500,
  634. fontSize: "0.875rem",
  635. }}
  636. />
  637. <MasterDataIssuesNavBadge enabled={canSeeMasterDataIssueBadge} />
  638. </ListItemButton>
  639. </Box>
  640. ) : child.path === "/productionProcess" ? (
  641. <Box
  642. key={child.id}
  643. sx={{
  644. display: "flex",
  645. alignItems: "stretch",
  646. mx: 1,
  647. borderRadius: 1,
  648. overflow: "hidden",
  649. "&:hover": { bgcolor: "action.hover" },
  650. }}
  651. >
  652. <Box
  653. component={Link}
  654. href={child.path}
  655. sx={{
  656. flex: 1,
  657. minWidth: 0,
  658. textDecoration: "none",
  659. color: "inherit",
  660. display: "flex",
  661. }}
  662. >
  663. <ListItemButton
  664. selected={child.path === selectedLeafPath}
  665. sx={{
  666. flex: 1,
  667. py: 1,
  668. pr: 0.5,
  669. "&.Mui-selected .MuiListItemIcon-root": { color: "primary.main" },
  670. }}
  671. >
  672. <ListItemIcon sx={{ minWidth: 40 }}>{child.icon}</ListItemIcon>
  673. <ListItemText
  674. primary={t(child.labelKey)}
  675. primaryTypographyProps={{
  676. fontWeight:
  677. child.path === selectedLeafPath ? 600 : 500,
  678. fontSize: "0.875rem",
  679. }}
  680. />
  681. </ListItemButton>
  682. </Box>
  683. <JobOrderFgStockInNavAlerts enabled={canSeeJoFgAlerts} />
  684. </Box>
  685. ) : (
  686. <Box
  687. key={child.id}
  688. component={Link}
  689. href={child.path}
  690. sx={{ textDecoration: "none", color: "inherit" }}
  691. >
  692. <ListItemButton
  693. selected={child.path === selectedLeafPath}
  694. sx={{
  695. mx: 1,
  696. py: 1,
  697. "&.Mui-selected .MuiListItemIcon-root": { color: "primary.main" },
  698. }}
  699. >
  700. <ListItemIcon sx={{ minWidth: 40 }}>{child.icon}</ListItemIcon>
  701. <ListItemText
  702. primary={t(child.labelKey)}
  703. primaryTypographyProps={{
  704. fontWeight:
  705. child.path === selectedLeafPath
  706. ? 600
  707. : 500,
  708. fontSize: "0.875rem",
  709. }}
  710. />
  711. </ListItemButton>
  712. </Box>
  713. )
  714. ),
  715. )}
  716. </List>
  717. )}
  718. </Box>
  719. );
  720. };
  721. if (status === "loading") {
  722. return <Box sx={{ width: NAVIGATION_CONTENT_WIDTH, p: 3 }}>Loading...</Box>;
  723. }
  724. return (
  725. <Box sx={{ width: NAVIGATION_CONTENT_WIDTH, height: "100%", display: "flex", flexDirection: "column" }}>
  726. <Box
  727. 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"
  728. sx={{
  729. mx: 1,
  730. mt: 1,
  731. mb: 1,
  732. px: 1.5,
  733. py: 2,
  734. flexShrink: 0,
  735. display: "flex",
  736. alignItems: "center",
  737. justifyContent: "flex-start",
  738. minHeight: 56,
  739. }}
  740. >
  741. <Stack direction="column" alignItems="flex-start" spacing={0.5}>
  742. <Logo height={42} />
  743. {process.env.NODE_ENV === "production" && (
  744. <Typography
  745. variant="body2"
  746. sx={{
  747. fontWeight: 700,
  748. color: "warning.dark",
  749. bgcolor: "warning.light",
  750. px: 1,
  751. py: 0.5,
  752. borderRadius: 1,
  753. border: "1px solid",
  754. borderColor: "warning.main",
  755. }}
  756. >
  757. {t("nav.productionServer")}
  758. </Typography>
  759. )}
  760. </Stack>
  761. </Box>
  762. <Box sx={{ borderTop: 1, borderColor: "divider" }} />
  763. <List component="nav" sx={{ flex: 1, overflow: "auto", py: 1, px: 0 }}>
  764. {navigationItems
  765. .filter(item => !item.isHidden)
  766. .map(renderNavigationItem)
  767. .filter(Boolean)}
  768. </List>
  769. </Box>
  770. );
  771. };
  772. export default NavigationContent;