FPSMS-frontend
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

1 рік тому
1 рік тому
1 рік тому
1 рік тому
2 місяці тому
1 рік тому
2 місяці тому
1 рік тому
1 рік тому
8 місяці тому
1 рік тому
6 місяці тому
1 рік тому
2 місяці тому
1 рік тому
1 рік тому
2 місяці тому
1 рік тому
1 рік тому
2 місяці тому
8 місяці тому
1 рік тому
10 місяці тому
2 місяці тому
10 місяці тому
1 рік тому
5 місяці тому
5 місяці тому
5 місяці тому
2 місяці тому
2 місяці тому
2 місяці тому
2 місяці тому
7 місяці тому
7 місяці тому
2 місяці тому
7 місяці тому
6 місяці тому
6 місяці тому
2 місяці тому
6 місяці тому
2 місяці тому
2 місяці тому
2 місяці тому
1 рік тому
2 місяці тому
1 рік тому
2 місяці тому
4 місяці тому
2 місяці тому
4 місяці тому
2 місяці тому
6 місяці тому
6 місяці тому
2 місяці тому
6 місяці тому
3 місяці тому
3 місяці тому
2 місяці тому
3 місяці тому
2 місяці тому
2 місяці тому
2 місяці тому
2 місяці тому
1 місяць тому
2 місяці тому
2 місяці тому
2 місяці тому
2 місяці тому
1 рік тому
2 місяці тому
1 рік тому
1 рік тому
2 місяці тому
1 рік тому
4 тижднів тому
11 місяці тому
11 місяці тому
1 рік тому
4 тижднів тому
2 місяці тому
1 рік тому
1 рік тому
9 місяці тому
1 рік тому
1 рік тому
1 рік тому
2 місяці тому
2 місяці тому
4 тижднів тому
1 рік тому
1 рік тому
1 рік тому
1 рік тому
1 рік тому
2 місяці тому
2 місяці тому
2 місяці тому
2 місяці тому
9 місяці тому
3 тижднів тому
1 рік тому
1 рік тому
1 рік тому
1 рік тому
1 рік тому
1 рік тому
8 місяці тому
8 місяці тому
1 рік тому
1 рік тому
1 рік тому
2 місяці тому
1 рік тому
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. import { useSession } from "next-auth/react";
  2. import Box from "@mui/material/Box";
  3. import React from "react";
  4. import List from "@mui/material/List";
  5. import ListItemButton from "@mui/material/ListItemButton";
  6. import ListItemText from "@mui/material/ListItemText";
  7. import ListItemIcon from "@mui/material/ListItemIcon";
  8. import Dashboard from "@mui/icons-material/Dashboard";
  9. import Storefront from "@mui/icons-material/Storefront";
  10. import LocalShipping from "@mui/icons-material/LocalShipping";
  11. import Assignment from "@mui/icons-material/Assignment";
  12. import Inventory from "@mui/icons-material/Inventory";
  13. import AssignmentTurnedIn from "@mui/icons-material/AssignmentTurnedIn";
  14. import ReportProblem from "@mui/icons-material/ReportProblem";
  15. import QrCodeIcon from "@mui/icons-material/QrCode";
  16. import ViewModule from "@mui/icons-material/ViewModule";
  17. import Description from "@mui/icons-material/Description";
  18. import CalendarMonth from "@mui/icons-material/CalendarMonth";
  19. import Factory from "@mui/icons-material/Factory";
  20. import PostAdd from "@mui/icons-material/PostAdd";
  21. import Kitchen from "@mui/icons-material/Kitchen";
  22. import Inventory2 from "@mui/icons-material/Inventory2";
  23. import Print from "@mui/icons-material/Print";
  24. import Assessment from "@mui/icons-material/Assessment";
  25. import Settings from "@mui/icons-material/Settings";
  26. import Person from "@mui/icons-material/Person";
  27. import Group from "@mui/icons-material/Group";
  28. import Category from "@mui/icons-material/Category";
  29. import TrendingUp from "@mui/icons-material/TrendingUp";
  30. import Build from "@mui/icons-material/Build";
  31. import Warehouse from "@mui/icons-material/Warehouse";
  32. import VerifiedUser from "@mui/icons-material/VerifiedUser";
  33. import Label from "@mui/icons-material/Label";
  34. import Checklist from "@mui/icons-material/Checklist";
  35. import Science from "@mui/icons-material/Science";
  36. import UploadFile from "@mui/icons-material/UploadFile";
  37. import { useTranslation } from "react-i18next";
  38. import { usePathname } from "next/navigation";
  39. import Link from "next/link";
  40. import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig";
  41. import Logo from "../Logo";
  42. import { AUTH } from "../../authorities";
  43. interface NavigationItem {
  44. icon: React.ReactNode;
  45. label: string;
  46. path: string;
  47. children?: NavigationItem[];
  48. isHidden?: boolean | undefined;
  49. requiredAbility?: string | string[];
  50. }
  51. const NavigationContent: React.FC = () => {
  52. const { data: session, status } = useSession();
  53. const abilities = session?.user?.abilities ?? [];
  54. // Helper: check if user has required permission
  55. const hasAbility = (required?: string | string[]): boolean => {
  56. if (!required) return true; // no requirement → always show
  57. if (Array.isArray(required)) {
  58. return required.some(ability => abilities.includes(ability));
  59. }
  60. return abilities.includes(required);
  61. };
  62. const navigationItems: NavigationItem[] = [
  63. {
  64. icon: <Dashboard />,
  65. label: "Dashboard",
  66. path: "/dashboard",
  67. },
  68. {
  69. icon: <Storefront />,
  70. label: "Store Management",
  71. path: "",
  72. requiredAbility: [AUTH.PURCHASE, AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.STOCK_FG, AUTH.STOCK_IN_BIND, AUTH.ADMIN],
  73. children: [
  74. {
  75. icon: <LocalShipping />,
  76. label: "Purchase Order",
  77. requiredAbility: [AUTH.PURCHASE, AUTH.ADMIN],
  78. path: "/po",
  79. },
  80. {
  81. icon: <Assignment />,
  82. label: "Pick Order",
  83. requiredAbility: [AUTH.STOCK, AUTH.ADMIN],
  84. path: "/pickOrder",
  85. },
  86. {
  87. icon: <Inventory />,
  88. label: "View item In-out And inventory Ledger",
  89. requiredAbility: [AUTH.STOCK, AUTH.ADMIN],
  90. path: "/inventory",
  91. },
  92. {
  93. icon: <AssignmentTurnedIn />,
  94. label: "Stock Take Management",
  95. requiredAbility: [AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.ADMIN],
  96. path: "/stocktakemanagement",
  97. },
  98. {
  99. icon: <ReportProblem />,
  100. label: "Stock Issue",
  101. requiredAbility: [AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.ADMIN],
  102. path: "/stockIssue",
  103. },
  104. {
  105. icon: <QrCodeIcon />,
  106. label: "Put Away Scan",
  107. requiredAbility: [AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.STOCK_IN_BIND, AUTH.ADMIN],
  108. path: "/putAway",
  109. },
  110. {
  111. icon: <ViewModule />,
  112. label: "Finished Good Order",
  113. requiredAbility: [AUTH.STOCK_FG, AUTH.ADMIN],
  114. path: "/finishedGood",
  115. },
  116. {
  117. icon: <Description />,
  118. label: "Stock Record",
  119. requiredAbility: [AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.STOCK_IN_BIND, AUTH.STOCK_FG, AUTH.ADMIN],
  120. path: "/stockRecord",
  121. },
  122. ],
  123. },
  124. {
  125. icon: <LocalShipping />,
  126. label: "Delivery Order",
  127. path: "/do",
  128. requiredAbility: [AUTH.STOCK_FG, AUTH.ADMIN],
  129. },
  130. {
  131. icon: <CalendarMonth />,
  132. label: "Scheduling",
  133. path: "/ps",
  134. requiredAbility: [AUTH.FORECAST, AUTH.ADMIN],
  135. isHidden: false,
  136. },
  137. {
  138. icon: <Factory />,
  139. label: "Management Job Order",
  140. path: "",
  141. requiredAbility: [AUTH.JOB_CREATE, AUTH.JOB_PICK, AUTH.JOB_PROD, AUTH.ADMIN],
  142. children: [
  143. {
  144. icon: <PostAdd />,
  145. label: "Search Job Order/ Create Job Order",
  146. requiredAbility: [AUTH.JOB_CREATE, AUTH.ADMIN],
  147. path: "/jo",
  148. },
  149. {
  150. icon: <Inventory />,
  151. label: "Job Order Pickexcution",
  152. requiredAbility: [AUTH.JOB_PICK, AUTH.JOB_MAT, AUTH.ADMIN],
  153. path: "/jodetail",
  154. },
  155. {
  156. icon: <Kitchen />,
  157. label: "Job Order Production Process",
  158. requiredAbility: [AUTH.JOB_PROD, AUTH.ADMIN],
  159. path: "/productionProcess",
  160. },
  161. {
  162. icon: <Inventory2 />,
  163. label: "Bag Usage",
  164. requiredAbility: [AUTH.JOB_PROD, AUTH.ADMIN],
  165. path: "/bag",
  166. },
  167. ],
  168. },
  169. {
  170. icon: <Print />,
  171. label: "打袋機列印",
  172. path: "/testing",
  173. requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
  174. isHidden: false,
  175. },
  176. {
  177. icon: <Assessment />,
  178. label: "報告管理",
  179. path: "/report",
  180. requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
  181. isHidden: false,
  182. },
  183. {
  184. icon: <Settings />,
  185. label: "Settings",
  186. path: "",
  187. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  188. children: [
  189. {
  190. icon: <Person />,
  191. label: "User",
  192. path: "/settings/user",
  193. requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
  194. },
  195. //{
  196. // icon: <Group />,
  197. // label: "User Group",
  198. // path: "/settings/user",
  199. // requiredAbility: [AUTH.VIEW_GROUP, AUTH.ADMIN],
  200. //},
  201. {
  202. icon: <Category />,
  203. label: "Items",
  204. path: "/settings/items",
  205. },
  206. {
  207. icon: <ViewModule />,
  208. label: "BOM Weighting Score List",
  209. path: "/settings/bomWeighting",
  210. },
  211. {
  212. icon: <Storefront />,
  213. label: "ShopAndTruck",
  214. path: "/settings/shop",
  215. },
  216. {
  217. icon: <TrendingUp />,
  218. label: "Demand Forecast Setting",
  219. path: "/settings/rss",
  220. },
  221. {
  222. icon: <Build />,
  223. label: "Equipment",
  224. path: "/settings/equipment",
  225. },
  226. {
  227. icon: <Warehouse />,
  228. label: "Warehouse",
  229. path: "/settings/warehouse",
  230. },
  231. {
  232. icon: <Print />,
  233. label: "Printer",
  234. path: "/settings/printer",
  235. },
  236. //{
  237. // icon: <Person />,
  238. // label: "Customer",
  239. // path: "/settings/user",
  240. //},
  241. {
  242. icon: <VerifiedUser />,
  243. label: "QC Check Item",
  244. path: "/settings/qcItem",
  245. },
  246. {
  247. icon: <Label />,
  248. label: "QC Category",
  249. path: "/settings/qcCategory",
  250. },
  251. {
  252. icon: <Checklist />,
  253. label: "QC Item All",
  254. path: "/settings/qcItemAll",
  255. },
  256. {
  257. icon: <QrCodeIcon />,
  258. label: "QR Code Handle",
  259. path: "/settings/qrCodeHandle",
  260. },
  261. {
  262. icon: <Science />,
  263. label: "Import Testing",
  264. path: "/settings/m18ImportTesting",
  265. },
  266. {
  267. icon: <UploadFile />,
  268. label: "Import Excel",
  269. path: "/settings/importExcel",
  270. },
  271. {
  272. icon: <UploadFile />,
  273. label: "Import BOM",
  274. path: "/settings/importBom",
  275. },
  276. ],
  277. },
  278. ];
  279. const { t } = useTranslation("common");
  280. const pathname = usePathname();
  281. const [openItems, setOpenItems] = React.useState<string[]>([]);
  282. const toggleItem = (label: string) => {
  283. setOpenItems((prevOpenItems) =>
  284. prevOpenItems.includes(label)
  285. ? prevOpenItems.filter((item) => item !== label)
  286. : [...prevOpenItems, label],
  287. );
  288. };
  289. const renderNavigationItem = (item: NavigationItem) => {
  290. if (!hasAbility(item.requiredAbility)) {
  291. return null;
  292. }
  293. const isOpen = openItems.includes(item.label);
  294. const hasVisibleChildren = item.children?.some(child => hasAbility(child.requiredAbility));
  295. const isLeaf = Boolean(item.path);
  296. const isSelected = isLeaf && item.path
  297. ? pathname === item.path || pathname.startsWith(item.path + "/")
  298. : hasVisibleChildren && item.children?.some(
  299. (c) => c.path && (pathname === c.path || pathname.startsWith(c.path + "/"))
  300. );
  301. const content = (
  302. <ListItemButton
  303. selected={isSelected}
  304. onClick={isLeaf ? undefined : () => toggleItem(item.label)}
  305. sx={{
  306. mx: 1,
  307. "&.Mui-selected .MuiListItemIcon-root": { color: "primary.main" },
  308. }}
  309. >
  310. <ListItemIcon sx={{ minWidth: 40 }}>{item.icon}</ListItemIcon>
  311. <ListItemText
  312. primary={t(item.label)}
  313. primaryTypographyProps={{ fontWeight: isSelected ? 600 : 500 }}
  314. />
  315. </ListItemButton>
  316. );
  317. return (
  318. <Box key={`${item.label}-${item.path}`}>
  319. {isLeaf ? (
  320. <Link href={item.path!} style={{ textDecoration: "none", color: "inherit" }}>
  321. {content}
  322. </Link>
  323. ) : (
  324. content
  325. )}
  326. {item.children && isOpen && hasVisibleChildren && (
  327. <List sx={{ pl: 2, py: 0 }}>
  328. {item.children.map(
  329. (child) => !child.isHidden && hasAbility(child.requiredAbility) && (
  330. <Box
  331. key={`${child.label}-${child.path}`}
  332. component={Link}
  333. href={child.path}
  334. sx={{ textDecoration: "none", color: "inherit" }}
  335. >
  336. <ListItemButton
  337. selected={pathname === child.path || (child.path && pathname.startsWith(child.path + "/"))}
  338. sx={{
  339. mx: 1,
  340. py: 1,
  341. "&.Mui-selected .MuiListItemIcon-root": { color: "primary.main" },
  342. }}
  343. >
  344. <ListItemIcon sx={{ minWidth: 40 }}>{child.icon}</ListItemIcon>
  345. <ListItemText
  346. primary={t(child.label)}
  347. primaryTypographyProps={{
  348. fontWeight: pathname === child.path || (child.path && pathname.startsWith(child.path + "/")) ? 600 : 500,
  349. fontSize: "0.875rem",
  350. }}
  351. />
  352. </ListItemButton>
  353. </Box>
  354. ),
  355. )}
  356. </List>
  357. )}
  358. </Box>
  359. );
  360. };
  361. if (status === "loading") {
  362. return <Box sx={{ width: NAVIGATION_CONTENT_WIDTH, p: 3 }}>Loading...</Box>;
  363. }
  364. return (
  365. <Box sx={{ width: NAVIGATION_CONTENT_WIDTH, height: "100%", display: "flex", flexDirection: "column" }}>
  366. <Box
  367. 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"
  368. sx={{
  369. mx: 1,
  370. mt: 1,
  371. mb: 1,
  372. px: 1.5,
  373. py: 2,
  374. flexShrink: 0,
  375. display: "flex",
  376. alignItems: "center",
  377. justifyContent: "flex-start",
  378. minHeight: 56,
  379. }}
  380. >
  381. <Logo height={42} />
  382. </Box>
  383. <Box sx={{ borderTop: 1, borderColor: "divider" }} />
  384. <List component="nav" sx={{ flex: 1, overflow: "auto", py: 1, px: 0 }}>
  385. {navigationItems
  386. .filter(item => !item.isHidden)
  387. .map(renderNavigationItem)
  388. .filter(Boolean)}
  389. </List>
  390. </Box>
  391. );
  392. };
  393. export default NavigationContent;