Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

1015 строки
38 KiB

  1. import PropTypes from "prop-types";
  2. import React, { useState, useContext, useEffect, useRef } from "react";
  3. import { useDispatch, useSelector } from "react-redux";
  4. import { useNavigate, Link } from "react-router-dom";
  5. import { SysContext } from "components/SysSettingProvider";
  6. import { checkIsOnlyOnlinePayment } from "../../../utils/Utils";
  7. // material-ui
  8. import {
  9. AppBar,
  10. Typography,
  11. Box,
  12. Stack,
  13. Toolbar,
  14. Divider,
  15. IconButton,
  16. Drawer,
  17. Grid,
  18. } from "@mui/material";
  19. import MenuIcon from "@mui/icons-material/Menu";
  20. import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
  21. // project import
  22. import Logo from "components/Logo";
  23. import AdminLogo from "components/AdminLogo";
  24. import MobileLogo from "components/MobileLogo";
  25. import "assets/style/navbarStyles.css";
  26. import {
  27. isUserLoggedIn,
  28. isGLDLoggedIn,
  29. isPrimaryLoggedIn,
  30. isCreditorLoggedIn,
  31. isINDLoggedIn,
  32. isPasswordExpiry,
  33. haveOrgPaymentRecord,
  34. haveOrgDnRecord,
  35. isORGLoggedIn,
  36. checkSysEnv,
  37. } from "utils/Utils";
  38. import { handleLogoutFunction } from "auth/index";
  39. import { isGranted, isGrantedAny } from "auth/utils";
  40. import LocaleSelector from "./HeaderContent/LocaleSelector";
  41. import { FormattedMessage, useIntl } from "react-intl";
  42. const drawerWidth = 300;
  43. const isAfterAboutSwitchDate = checkIsOnlyOnlinePayment;
  44. const getAboutExternalUrlByLocale = () => {
  45. const locale = (localStorage.getItem("locale") || "").toLowerCase();
  46. if (locale === "zh-hk") return "https://www.gld.gov.hk/zh-hk/our-services/printing/advertising-gov-gazette/";
  47. if (locale === "zh-cn") return "https://www.gld.gov.hk/zh-cn/our-services/printing/advertising-gov-gazette/";
  48. return "https://www.gld.gov.hk/en/our-services/printing/advertising-gov-gazette/";
  49. };
  50. /**
  51. * Accessible dropdown trigger:
  52. * - button is always focusable
  53. * - opens on focus (keyboard tab) via CSS :focus-within + optional state
  54. * - supports Enter/Space/ArrowDown to jump to first submenu item
  55. * - supports Escape to close + return focus
  56. */
  57. function Header(props) {
  58. const { sysSetting } = useContext(SysContext);
  59. const { window } = props;
  60. /** Subscribe to auth changes (LOGIN/LOGOUT/cross-tab storage); Header reads localStorage and must re-render. */
  61. useSelector((state) => state.authRevision);
  62. const [mobileOpen, setMobileOpen] = useState(false);
  63. const dispatch = useDispatch();
  64. const navigate = useNavigate();
  65. const intl = useIntl();
  66. // which dropdown is open (optional but useful for aria-expanded + closing)
  67. const [openMenu, setOpenMenu] = useState({
  68. payment: false,
  69. client: false,
  70. report: false,
  71. settings: false,
  72. paymentHistory: false,
  73. userSetting: false,
  74. });
  75. const rootNavRef = useRef(null);
  76. const handleDrawerToggle = () => setMobileOpen((prev) => !prev);
  77. const handleLogout = async () => {
  78. await dispatch(handleLogoutFunction());
  79. await navigate("/login");
  80. };
  81. const openKey = (key) => setOpenMenu((p) => ({ ...p, [key]: true }));
  82. const closeKey = (key) => setOpenMenu((p) => ({ ...p, [key]: false }));
  83. const closeAll = () =>
  84. setOpenMenu({
  85. payment: false,
  86. client: false,
  87. report: false,
  88. settings: false,
  89. paymentHistory: false,
  90. userSetting: false,
  91. });
  92. // close dropdowns on outside click / focus
  93. useEffect(() => {
  94. const onDocMouseDown = (e) => {
  95. if (!rootNavRef.current) return;
  96. if (!rootNavRef.current.contains(e.target)) closeAll();
  97. };
  98. document.addEventListener("mousedown", onDocMouseDown, true);
  99. return () => document.removeEventListener("mousedown", onDocMouseDown, true);
  100. }, []);
  101. const onMenuKeyDown = (key) => (e) => {
  102. if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
  103. e.preventDefault();
  104. openKey(key);
  105. requestAnimationFrame(() => {
  106. const first = document.querySelector(
  107. `[data-submenu="${key}"] a, [data-submenu="${key}"] button`
  108. );
  109. first?.focus?.();
  110. });
  111. } else if (e.key === "Escape") {
  112. e.preventDefault();
  113. closeKey(key);
  114. e.currentTarget?.focus?.();
  115. } else if (e.key === "ArrowUp") {
  116. // optional: close on ArrowUp when on trigger
  117. closeKey(key);
  118. }
  119. };
  120. const onTriggerBlur = (key) => (e) => {
  121. // close only when focus leaves the whole LI (trigger + submenu)
  122. const li = e.currentTarget.closest("li");
  123. if (li && li.contains(e.relatedTarget)) return;
  124. closeKey(key);
  125. };
  126. // =============================
  127. // Desktop top nav content (login)
  128. // =============================
  129. const loginContent = isGLDLoggedIn() ? (
  130. <div id="adminContent">
  131. {isPasswordExpiry() ? (
  132. <div id="passwordExpiryedContent">
  133. <li>
  134. <Link className="manageUser" to={"/user/changePassword"}>
  135. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  136. <FormattedMessage id="userChangePassword" />
  137. </Typography>
  138. </Link>
  139. </li>
  140. </div>
  141. ) : (
  142. <div id="adminContentList" ref={rootNavRef}>
  143. <li>
  144. <Link className="dashboard" to="/dashboard">
  145. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  146. Dashboard
  147. </Typography>
  148. </Link>
  149. </li>
  150. {isGrantedAny(["VIEW_APPLICATION", "MAINTAIN_APPLICATION"]) ? (
  151. <li>
  152. <Link className="application" to="/application/search">
  153. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  154. Application
  155. </Typography>
  156. </Link>
  157. </li>
  158. ) : null}
  159. {isGrantedAny(["VIEW_PROOF", "MAINTAIN_PROOF"]) ? (
  160. <li>
  161. <Link className="proof" to="/proof/search">
  162. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  163. Proof
  164. </Typography>
  165. </Link>
  166. </li>
  167. ) : null}
  168. {/* ===== Payment dropdown (admin) ===== */}
  169. {isGrantedAny([
  170. "MAINTAIN_PROOF",
  171. "MAINTAIN_PAYMENT",
  172. "MAINTAIN_RECON",
  173. "VIEW_DEMANDNOTE",
  174. "MAINTAIN_DEMANDNOTE",
  175. ]) ? (
  176. <li>
  177. <button
  178. type="button"
  179. className="navTrigger paymentTop"
  180. aria-haspopup="true"
  181. aria-expanded={openMenu.payment}
  182. onFocus={() => openKey("payment")}
  183. onBlur={onTriggerBlur("payment")}
  184. onKeyDown={onMenuKeyDown("payment")}
  185. >
  186. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  187. Payment
  188. </Typography>
  189. <KeyboardArrowDownIcon sx={{ fontSize: "1vw" }} />
  190. </button>
  191. <ul className="dropdown" data-submenu="payment">
  192. {isGranted("MAINTAIN_PROOF") ? (
  193. <li>
  194. <Link className="payment" to="/paymentPage/exportGDN">
  195. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  196. Export for GDN
  197. </Typography>
  198. </Link>
  199. </li>
  200. ) : null}
  201. {isGranted("MAINTAIN_PAYMENT") ? (
  202. <li>
  203. <Link className="payment" to="/application/markAsPaid/search">
  204. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  205. Mark Payment
  206. </Typography>
  207. </Link>
  208. </li>
  209. ) : null}
  210. {isGranted("MAINTAIN_PAYMENT") ? (
  211. <li>
  212. <Link className="payment" to="/paymentPage/search">
  213. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  214. Online Payment Record
  215. </Typography>
  216. </Link>
  217. </li>
  218. ) : null}
  219. {isGranted("MAINTAIN_RECON") ? (
  220. <li>
  221. <Link className="payment" to="/gfmis/search">
  222. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  223. GFMIS Generate XML
  224. </Typography>
  225. </Link>
  226. </li>
  227. ) : null}
  228. {isGranted("MAINTAIN_DEMANDNOTE") ? (
  229. <li>
  230. <Link className="payment" to="/paymentPage/createDemandNote">
  231. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  232. Create Demand Note
  233. </Typography>
  234. </Link>
  235. </li>
  236. ) : null}
  237. {isGrantedAny(["VIEW_DEMANDNOTE", "MAINTAIN_DEMANDNOTE"]) ? (
  238. <li>
  239. <Link className="payment" to="/paymentPage/demandNote">
  240. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  241. Demand Note
  242. </Typography>
  243. </Link>
  244. </li>
  245. ) : null}
  246. {isGranted("MAINTAIN_RECON") ? (
  247. <li>
  248. <Link className="payment" to="/paymentPage/reconReport">
  249. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  250. Recon Report
  251. </Typography>
  252. </Link>
  253. </li>
  254. ) : null}
  255. </ul>
  256. </li>
  257. ) : null}
  258. {/* ===== Client dropdown (admin) ===== */}
  259. {isGrantedAny([
  260. "VIEW_USER",
  261. "MAINTAIN_USER",
  262. "VIEW_ORG",
  263. "MAINTAIN_ORG",
  264. "VIEW_GROUP",
  265. "MAINTAIN_GROUP",
  266. "VIEW_GLD_USER",
  267. "VIEW_IND_USER",
  268. "VIEW_ORG_USER",
  269. "MAINTAIN_GLD_USER",
  270. "MAINTAIN_IND_USER",
  271. "MAINTAIN_ORG_USER",
  272. ]) ? (
  273. <li>
  274. <button
  275. type="button"
  276. className="navTrigger client"
  277. aria-haspopup="true"
  278. aria-expanded={openMenu.client}
  279. onFocus={() => openKey("client")}
  280. onBlur={onTriggerBlur("client")}
  281. onKeyDown={onMenuKeyDown("client")}
  282. >
  283. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  284. Client
  285. </Typography>
  286. <KeyboardArrowDownIcon sx={{ fontSize: "1vw" }} />
  287. </button>
  288. <ul className="dropdown" data-submenu="client">
  289. {isGrantedAny(["VIEW_USER", "MAINTAIN_USER"]) ? (
  290. <>
  291. <li>
  292. <Link className="user" to="/userSearchview">
  293. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  294. Users (GLD)
  295. </Typography>
  296. </Link>
  297. </li>
  298. <li>
  299. <Link className="user" to="/indUser">
  300. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  301. Users (Individual)
  302. </Typography>
  303. </Link>
  304. </li>
  305. <li>
  306. <Link className="user" to="/orgUser">
  307. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  308. Users (Organisation)
  309. </Typography>
  310. </Link>
  311. </li>
  312. </>
  313. ) : (
  314. <>
  315. {isGrantedAny(["VIEW_GLD_USER", "MAINTAIN_GLD_USER"]) ? (
  316. <li>
  317. <Link className="user" to="/userSearchview">
  318. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  319. Users (GLD)
  320. </Typography>
  321. </Link>
  322. </li>
  323. ) : null}
  324. {isGrantedAny(["VIEW_IND_USER", "MAINTAIN_IND_USER"]) ? (
  325. <li>
  326. <Link className="user" to="/indUser">
  327. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  328. Users (Individual)
  329. </Typography>
  330. </Link>
  331. </li>
  332. ) : null}
  333. {isGrantedAny(["VIEW_ORG_USER", "MAINTAIN_ORG_USER"]) ? (
  334. <li>
  335. <Link className="user" to="/orgUser">
  336. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  337. Users (Organisation)
  338. </Typography>
  339. </Link>
  340. </li>
  341. ) : null}
  342. </>
  343. )}
  344. {isGrantedAny(["VIEW_ORG", "MAINTAIN_ORG"]) ? (
  345. <li>
  346. <Link className="user" to="/org">
  347. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  348. Organisation
  349. </Typography>
  350. </Link>
  351. </li>
  352. ) : null}
  353. {isGrantedAny(["VIEW_GROUP", "MAINTAIN_GROUP"]) ? (
  354. <li>
  355. <Link className="user" to="/usergroupSearchview">
  356. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  357. User Group
  358. </Typography>
  359. </Link>
  360. </li>
  361. ) : null}
  362. </ul>
  363. </li>
  364. ) : null}
  365. {/* ===== Report dropdown (admin) ===== */}
  366. <li>
  367. <button
  368. type="button"
  369. className="navTrigger setting"
  370. aria-haspopup="true"
  371. aria-expanded={openMenu.report}
  372. onFocus={() => openKey("report")}
  373. onBlur={onTriggerBlur("report")}
  374. onKeyDown={onMenuKeyDown("report")}
  375. >
  376. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  377. Report
  378. </Typography>
  379. <KeyboardArrowDownIcon sx={{ fontSize: "1vw" }} />
  380. </button>
  381. <ul className="dropdown" data-submenu="report">
  382. <li>
  383. <Link className="report" to="/setting/report/summary">
  384. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  385. Summary of Gazette Notice
  386. </Typography>
  387. </Link>
  388. </li>
  389. <li>
  390. <Link className="report" to="/setting/report/fullList">
  391. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  392. Gazette Notice Full List
  393. </Typography>
  394. </Link>
  395. </li>
  396. </ul>
  397. </li>
  398. {/* ===== Settings dropdown (admin) ===== */}
  399. <li>
  400. <button
  401. type="button"
  402. className="navTrigger setting"
  403. aria-haspopup="true"
  404. aria-expanded={openMenu.settings}
  405. onFocus={() => openKey("settings")}
  406. onBlur={onTriggerBlur("settings")}
  407. onKeyDown={onMenuKeyDown("settings")}
  408. >
  409. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  410. Settings
  411. </Typography>
  412. <KeyboardArrowDownIcon sx={{ fontSize: "1vw" }} />
  413. </button>
  414. <ul className="dropdown" data-submenu="settings">
  415. <li>
  416. <Link className="systemSetting" to="/user/profile">
  417. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  418. My Profile
  419. </Typography>
  420. </Link>
  421. </li>
  422. <li>
  423. <Link className="manageUser" to={"/user/changePassword"}>
  424. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  425. <FormattedMessage id="userChangePassword" />
  426. </Typography>
  427. </Link>
  428. </li>
  429. {isGranted("VIEW_GAZETTE_ISSUE", "MAINTAIN_GAZETTE_ISSUE") ? (
  430. <>
  431. <li>
  432. <Link className="systemSetting" to="/setting/holiday">
  433. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  434. Holiday Settings
  435. </Typography>
  436. </Link>
  437. </li>
  438. <li>
  439. <Link className="systemSetting" to="/setting/gazetteissuepage">
  440. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  441. Gazette Issues
  442. </Typography>
  443. </Link>
  444. </li>
  445. </>
  446. ) : null}
  447. {isGranted("MAINTAIN_ANNOUNCEMENT") ? (
  448. <li>
  449. <Link className="systemSetting" to="/setting/announcement">
  450. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  451. Announcement
  452. </Typography>
  453. </Link>
  454. </li>
  455. ) : null}
  456. {isGranted("MAINTAIN_EMAIL") ? (
  457. <li>
  458. <Link className="systemSetting" to="/setting/emailTemplate">
  459. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  460. Email Template
  461. </Typography>
  462. </Link>
  463. </li>
  464. ) : null}
  465. {isGranted("MAINTAIN_DR") ? (
  466. <li>
  467. <Link className="systemSetting" to="/setting/drImport">
  468. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  469. DR Import
  470. </Typography>
  471. </Link>
  472. </li>
  473. ) : null}
  474. {isGranted("MAINTAIN_SETTING") ? (
  475. <>
  476. <li>
  477. <Link className="systemSetting" to="/setting/hkidKeyMigration">
  478. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  479. HKID Key Migration
  480. </Typography>
  481. </Link>
  482. </li>
  483. <li>
  484. <Link className="systemSetting" to="/setting/userPiiEncryption">
  485. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  486. User PII Encryption
  487. </Typography>
  488. </Link>
  489. </li>
  490. <li>
  491. <Link className="systemSetting" to="/setting/sys">
  492. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  493. System Settings
  494. </Typography>
  495. </Link>
  496. </li>
  497. <li>
  498. <Link className="systemSetting" to="/setting/auditLog">
  499. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  500. Audit Log
  501. </Typography>
  502. </Link>
  503. </li>
  504. </>
  505. ) : null}
  506. </ul>
  507. </li>
  508. <Box sx={{ display: { xs: "none", sm: "none", md: "block" } }}>
  509. <li>
  510. <Link className="logout" onClick={handleLogout}>
  511. <Typography variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  512. Logout
  513. </Typography>
  514. </Link>
  515. </li>
  516. </Box>
  517. </div>
  518. )}
  519. </div>
  520. ) : (
  521. // ===== Non-GLD login content (your original logic, only dropdown triggers changed to <button>) =====
  522. <div id="individualUserContent" ref={rootNavRef}>
  523. {isPasswordExpiry() ? (
  524. <div id="passwordExpiryedContent">
  525. <li>
  526. <Link className="manageUser" to={"/user/changePassword"}>
  527. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  528. <FormattedMessage id="userChangePassword" />
  529. </Typography>
  530. </Link>
  531. </li>
  532. </div>
  533. ) : (
  534. <div id="individualUserContentList">
  535. <li>
  536. <Link className="dashboard" to="/dashboard">
  537. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  538. <FormattedMessage id="mainPage" />
  539. </Typography>
  540. </Link>
  541. </li>
  542. <li>
  543. <Link className="myDocumet" to="/publicNotice">
  544. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  545. <FormattedMessage id="myPublicNotice" />
  546. </Typography>
  547. </Link>
  548. </li>
  549. <li>
  550. <Link className="documentRecord" to="/proof/search">
  551. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  552. <FormattedMessage id="proofRecord" />
  553. </Typography>
  554. </Link>
  555. </li>
  556. {/* ===== Payment History dropdown (non-admin) ===== */}
  557. <li>
  558. {(isCreditorLoggedIn() && haveOrgPaymentRecord()) ||
  559. (isORGLoggedIn() && haveOrgPaymentRecord()) ||
  560. (!isCreditorLoggedIn() && !isORGLoggedIn()) ? (
  561. <>
  562. <button
  563. type="button"
  564. className="navTrigger paymentRecord"
  565. aria-haspopup="true"
  566. aria-expanded={openMenu.paymentHistory}
  567. onFocus={() => openKey("paymentHistory")}
  568. onBlur={onTriggerBlur("paymentHistory")}
  569. onKeyDown={onMenuKeyDown("paymentHistory")}
  570. >
  571. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  572. <FormattedMessage id="paymentHistory" />
  573. </Typography>
  574. <KeyboardArrowDownIcon sx={{ fontSize: "1.0rem" }} />
  575. </button>
  576. <ul className="dropdown" data-submenu="paymentHistory">
  577. <li>
  578. <Link className="manageOrgUser" to="/paymentPage/search">
  579. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  580. <FormattedMessage id="onlinePaymentHistory" />
  581. </Typography>
  582. </Link>
  583. </li>
  584. {(isCreditorLoggedIn() && haveOrgPaymentRecord()) ||
  585. (isORGLoggedIn() && haveOrgPaymentRecord() && haveOrgDnRecord()) ||
  586. (!isCreditorLoggedIn() && !isORGLoggedIn() && haveOrgDnRecord()) ? (
  587. <li>
  588. <Link className="manageOrgUser" to="/paymentPage/demandNote">
  589. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  590. <FormattedMessage id="paymentInfoRecord" />
  591. </Typography>
  592. </Link>
  593. </li>
  594. ) : null}
  595. </ul>
  596. </>
  597. ) : (
  598. <Link className="manageOrgUser" to="/paymentPage/demandNote">
  599. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  600. <FormattedMessage id="paymentInfoRecord" />
  601. </Typography>
  602. </Link>
  603. )}
  604. </li>
  605. {/* ===== User Setting dropdown (non-admin) ===== */}
  606. <li>
  607. <button
  608. type="button"
  609. className="navTrigger userSetting"
  610. aria-haspopup="true"
  611. aria-expanded={openMenu.userSetting}
  612. onFocus={() => openKey("userSetting")}
  613. onBlur={onTriggerBlur("userSetting")}
  614. onKeyDown={onMenuKeyDown("userSetting")}
  615. >
  616. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  617. <FormattedMessage id="setting" />
  618. </Typography>
  619. <KeyboardArrowDownIcon sx={{ fontSize: "1.0rem" }} />
  620. </button>
  621. <ul className="dropdown" style={{ width: "max-content" }} data-submenu="userSetting">
  622. {isPrimaryLoggedIn() ? (
  623. <>
  624. <li>
  625. <Link className="manageOrgUser" to="setting/manageUser">
  626. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  627. <FormattedMessage id="companyOrUserRecord" />
  628. </Typography>
  629. </Link>
  630. </li>
  631. <li>
  632. <Link className="manageUser" to={"/org"}>
  633. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  634. <FormattedMessage id="organizationProfile" />
  635. </Typography>
  636. </Link>
  637. </li>
  638. <li>
  639. <Link className="manageUser" to={"/orgUser"}>
  640. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  641. <FormattedMessage id="userProfile" />
  642. </Typography>
  643. </Link>
  644. </li>
  645. </>
  646. ) : isINDLoggedIn() ? (
  647. <li>
  648. <Link className="manageUser" to={"/indUser"}>
  649. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  650. <FormattedMessage id="userProfile" />
  651. </Typography>
  652. </Link>
  653. </li>
  654. ) : (
  655. <li>
  656. <Link className="manageUser" to={"/orgUser"}>
  657. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  658. <FormattedMessage id="userProfile" />
  659. </Typography>
  660. </Link>
  661. </li>
  662. )}
  663. <li>
  664. <Link className="manageUser" to={"/user/changePassword"}>
  665. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  666. <FormattedMessage id="userChangePassword" />
  667. </Typography>
  668. </Link>
  669. </li>
  670. </ul>
  671. </li>
  672. </div>
  673. )}
  674. <Box sx={{ display: { xs: "none", sm: "none", md: "block" } }}>
  675. <li>
  676. <Link className="logout" onClick={handleLogout}>
  677. <Typography variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  678. <FormattedMessage id="logout" />
  679. </Typography>
  680. </Link>
  681. </li>
  682. </Box>
  683. </div>
  684. );
  685. // =============================
  686. // Logged-out top nav content
  687. // =============================
  688. const logoutContent = (
  689. <div>
  690. <li>
  691. {!isAfterAboutSwitchDate() ? (
  692. <Link className="login" to="/aboutUs">
  693. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  694. <FormattedMessage id="aboutUs" />
  695. </Typography>
  696. </Link>
  697. ) : (
  698. <a className="login" href={getAboutExternalUrlByLocale()} target="_blank" rel="noopener noreferrer">
  699. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  700. <FormattedMessage id="aboutUs" />
  701. </Typography>
  702. </a>
  703. )}
  704. </li>
  705. <li>
  706. <Link className="login" to={"/userGuidePub"}>
  707. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  708. <FormattedMessage id="userGuide" />
  709. </Typography>
  710. </Link>
  711. </li>
  712. <li>
  713. <Link className="login" to="/login">
  714. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  715. <FormattedMessage id="login" />
  716. </Typography>
  717. </Link>
  718. </li>
  719. {sysSetting?.allowRegistration ? (
  720. <li>
  721. <Link className="register" to="/register">
  722. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  723. <FormattedMessage id="register" />
  724. </Typography>
  725. </Link>
  726. </li>
  727. ) : null}
  728. </div>
  729. );
  730. const drawer = isUserLoggedIn() ? (
  731. <Stack id="sidebar" direction="column" justifyContent="center" alignItems="center" sx={{ textAlign: "center", width: "300px" }}>
  732. <Box sx={{ mr: 2, mt: 1, display: { md: "none" } }}>
  733. <MobileLogo />
  734. <span style={{ color: checkSysEnv() !== "" ? "red" : "#0c489e" }} id="mobileTitle">
  735. PNSPS
  736. </span>
  737. </Box>
  738. <Divider />
  739. <ul id="sidebartop">{loginContent}</ul>
  740. <Divider />
  741. <ul id="sidebarbottom">
  742. <li>
  743. <Link className="logout" onClick={handleLogout}>
  744. <FormattedMessage id="logout" />
  745. </Link>
  746. </li>
  747. </ul>
  748. </Stack>
  749. ) : (
  750. <Stack id="sidebar" direction="column" justifyContent="center" alignItems="center" onClick={handleDrawerToggle} sx={{ textAlign: "center" }}>
  751. <Box sx={{ mr: 2, mt: 1, display: { md: "none" } }}>
  752. <MobileLogo />
  753. <span style={{ color: checkSysEnv() !== "" ? "red" : "#0c489e" }} id="mobileTitle">
  754. PNSPS
  755. </span>
  756. </Box>
  757. <Divider />
  758. <ul id="logoutContent">{logoutContent}</ul>
  759. <Divider />
  760. </Stack>
  761. );
  762. const container = window !== undefined ? () => window().document.body : undefined;
  763. // =============================
  764. // Render (your layout kept)
  765. // =============================
  766. return isUserLoggedIn() ? (
  767. <Box>
  768. <AppBar component="nav" elevation={0}>
  769. <Toolbar id="nav" width="100%">
  770. {isGLDLoggedIn() ? (
  771. <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={0} sx={{ width: { xs: "100%", md: "5%" } }}>
  772. <Box mt={0.5} sx={{ flexGrow: 1, display: { xs: "none", sm: "none", md: "block" } }}>
  773. <AdminLogo />
  774. </Box>
  775. <IconButton
  776. color="inherit"
  777. aria-label={intl.formatMessage({ id: "openMenu" })}
  778. aria-expanded={mobileOpen}
  779. edge="start"
  780. onClick={handleDrawerToggle}
  781. sx={{ mr: 2, display: { md: "none" } }}
  782. >
  783. <MenuIcon style={{ color: "#0c489e" }} />
  784. </IconButton>
  785. <Box sx={{ flexGrow: 1, mr: 2, display: { sm: "block", md: "none" } }}>
  786. <Stack direction="row" justifyContent="space-between" alignItems="center" width="100%">
  787. <MobileLogo />
  788. <Stack justifyContent="flex-start" alignItems="flex-start" width="100%" ml={2}>
  789. <span style={{ color: checkSysEnv() !== "" ? "red" : "#0c489e" }} id="mobileTitle">
  790. PNSPS
  791. </span>
  792. </Stack>
  793. <Stack justifyContent="flex-end" alignItems="center">
  794. <span style={{ color: "#B11B1B", fontWeight: "bold", fontSize: "15px" }}>RESTRICTED</span>
  795. </Stack>
  796. </Stack>
  797. </Box>
  798. </Stack>
  799. ) : (
  800. <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={0} sx={{ width: { xs: "100%", md: "25%" } }}>
  801. <Box sx={{ width: "450px", flexGrow: 1, display: { xs: "none", sm: "none", md: "block" } }}>
  802. <Stack direction="row" justifyContent="flex-start" alignItems="center">
  803. <Logo />
  804. <Stack justifyContent="flex-start" alignItems="center">
  805. <span style={{ color: checkSysEnv() !== "" ? "#B00020" : "#0c489e" }} id="systemTitle">
  806. <FormattedMessage id="PNSPS" />
  807. </span>
  808. </Stack>
  809. </Stack>
  810. </Box>
  811. <IconButton
  812. color="inherit"
  813. aria-label={intl.formatMessage({ id: "openMenu" })}
  814. aria-expanded={mobileOpen}
  815. edge="start"
  816. onClick={handleDrawerToggle}
  817. sx={{ mr: 2, display: { md: "none" } }}
  818. >
  819. <MenuIcon style={{ color: "#0c489e" }} />
  820. </IconButton>
  821. <Box sx={{ flexGrow: 1, mr: 2, display: { sm: "block", md: "none" } }}>
  822. <Stack direction="row" justifyContent="space-between" alignItems="center" width="100%">
  823. <MobileLogo />
  824. <Stack justifyContent="flex-start" alignItems="flex-start" width="100%" ml={2}>
  825. <span style={{ color: checkSysEnv() !== "" ? "red" : "#000" }} id="mobileTitle">
  826. <FormattedMessage id="PNSPS" />
  827. </span>
  828. </Stack>
  829. <Stack justifyContent="flex-end" alignItems="center">
  830. <LocaleSelector />
  831. </Stack>
  832. </Stack>
  833. </Box>
  834. </Stack>
  835. )}
  836. <Box sx={{ display: { xs: "none", sm: "none", md: "block" }, width: "100%" }}>
  837. <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={1}>
  838. <ul id="navbar" width="100%">
  839. {loginContent}
  840. </ul>
  841. <Grid item>
  842. <Grid container direction="column" justifyContent="flex-end" alignItems="center">
  843. {isGLDLoggedIn() ? (
  844. <Grid item>
  845. <span style={{ color: "#B11B1B", fontWeight: "bold", fontSize: "15px" }}>RESTRICTED</span>
  846. </Grid>
  847. ) : (
  848. <Grid item>
  849. <LocaleSelector />
  850. </Grid>
  851. )}
  852. </Grid>
  853. </Grid>
  854. </Stack>
  855. </Box>
  856. </Toolbar>
  857. </AppBar>
  858. <Box component="nav">
  859. <Drawer
  860. container={container}
  861. variant="temporary"
  862. open={mobileOpen}
  863. onClose={handleDrawerToggle}
  864. ModalProps={{ keepMounted: true }}
  865. sx={{
  866. display: { sm: "block", md: "none" },
  867. "& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth },
  868. }}
  869. >
  870. {drawer}
  871. </Drawer>
  872. </Box>
  873. </Box>
  874. ) : (
  875. <Box>
  876. <AppBar component="nav" elevation={0}>
  877. <Toolbar id="nav" width="100%">
  878. <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={0} sx={{ width: { xs: "100%", md: "25%" } }}>
  879. <Box sx={{ flexGrow: 1, display: { xs: "none", sm: "none", md: "block" } }}>
  880. <Stack direction="row" justifyContent="flex-start" alignItems="center">
  881. <Logo />
  882. <Stack justifyContent="flex-start" alignItems="center">
  883. <span id="systemTitle">
  884. <FormattedMessage id="PNSPS" />
  885. </span>
  886. </Stack>
  887. </Stack>
  888. </Box>
  889. <IconButton
  890. color="inherit"
  891. aria-label={intl.formatMessage({ id: "openMenu" })}
  892. aria-expanded={mobileOpen}
  893. edge="start"
  894. onClick={handleDrawerToggle}
  895. sx={{ mr: 2, display: { md: "none" } }}
  896. >
  897. <MenuIcon style={{ color: "#0c489e" }} />
  898. </IconButton>
  899. <Box sx={{ flexGrow: 1, mr: 2, display: { sm: "block", md: "none" } }}>
  900. <Stack direction="row" justifyContent="space-between" alignItems="center" width="100%">
  901. <MobileLogo />
  902. <Stack justifyContent="flex-start" alignItems="flex-start" width="100%" ml={2}>
  903. <span style={{ color: checkSysEnv() !== "" ? "red" : "#0c489e" }} id="mobileTitle">
  904. <FormattedMessage id="PNSPS" />
  905. </span>
  906. </Stack>
  907. <Stack justifyContent="flex-end" alignItems="center">
  908. <LocaleSelector />
  909. </Stack>
  910. </Stack>
  911. </Box>
  912. </Stack>
  913. <Box sx={{ display: { xs: "none", sm: "none", md: "block" }, width: "75%" }}>
  914. <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={1}>
  915. <ul id="navbar" width="100%">
  916. {logoutContent}
  917. </ul>
  918. <LocaleSelector />
  919. </Stack>
  920. </Box>
  921. </Toolbar>
  922. </AppBar>
  923. <Box component="nav">
  924. <Drawer
  925. container={container}
  926. variant="temporary"
  927. open={mobileOpen}
  928. onClose={handleDrawerToggle}
  929. ModalProps={{ keepMounted: true }}
  930. sx={{
  931. display: { sm: "block", md: "none" },
  932. "& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth },
  933. }}
  934. >
  935. {drawer}
  936. </Drawer>
  937. </Box>
  938. </Box>
  939. );
  940. }
  941. Header.propTypes = {
  942. window: PropTypes.func,
  943. };
  944. export default Header;