FPSMS-frontend
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 

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