選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

index.js 38 KiB

6ヶ月前
6ヶ月前
2年前
2年前
11ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
2年前
2年前
2年前
2年前
2年前
2年前
2年前
2年前
11ヶ月前
11ヶ月前
2年前
2年前
2年前
2年前
2年前
2年前
2年前
2年前
2年前
2年前
2年前
2年前
2年前
2年前
2年前
2年前
6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
1ヶ月前
1ヶ月前
4ヶ月前
2年前
1ヶ月前
1ヶ月前
2年前
2年前
1ヶ月前
2年前
1ヶ月前
2年前
1ヶ月前
2年前
4ヶ月前
2年前
1ヶ月前
1ヶ月前
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001
  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/sys">
  478. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  479. System Settings
  480. </Typography>
  481. </Link>
  482. </li>
  483. <li>
  484. <Link className="systemSetting" to="/setting/auditLog">
  485. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2, mt: 1, mb: 1 }}>
  486. Audit Log
  487. </Typography>
  488. </Link>
  489. </li>
  490. </>
  491. ) : null}
  492. </ul>
  493. </li>
  494. <Box sx={{ display: { xs: "none", sm: "none", md: "block" } }}>
  495. <li>
  496. <Link className="logout" onClick={handleLogout}>
  497. <Typography variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  498. Logout
  499. </Typography>
  500. </Link>
  501. </li>
  502. </Box>
  503. </div>
  504. )}
  505. </div>
  506. ) : (
  507. // ===== Non-GLD login content (your original logic, only dropdown triggers changed to <button>) =====
  508. <div id="individualUserContent" ref={rootNavRef}>
  509. {isPasswordExpiry() ? (
  510. <div id="passwordExpiryedContent">
  511. <li>
  512. <Link className="manageUser" to={"/user/changePassword"}>
  513. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  514. <FormattedMessage id="userChangePassword" />
  515. </Typography>
  516. </Link>
  517. </li>
  518. </div>
  519. ) : (
  520. <div id="individualUserContentList">
  521. <li>
  522. <Link className="dashboard" to="/dashboard">
  523. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  524. <FormattedMessage id="mainPage" />
  525. </Typography>
  526. </Link>
  527. </li>
  528. <li>
  529. <Link className="myDocumet" to="/publicNotice">
  530. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  531. <FormattedMessage id="myPublicNotice" />
  532. </Typography>
  533. </Link>
  534. </li>
  535. <li>
  536. <Link className="documentRecord" to="/proof/search">
  537. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  538. <FormattedMessage id="proofRecord" />
  539. </Typography>
  540. </Link>
  541. </li>
  542. {/* ===== Payment History dropdown (non-admin) ===== */}
  543. <li>
  544. {(isCreditorLoggedIn() && haveOrgPaymentRecord()) ||
  545. (isORGLoggedIn() && haveOrgPaymentRecord()) ||
  546. (!isCreditorLoggedIn() && !isORGLoggedIn()) ? (
  547. <>
  548. <button
  549. type="button"
  550. className="navTrigger paymentRecord"
  551. aria-haspopup="true"
  552. aria-expanded={openMenu.paymentHistory}
  553. onFocus={() => openKey("paymentHistory")}
  554. onBlur={onTriggerBlur("paymentHistory")}
  555. onKeyDown={onMenuKeyDown("paymentHistory")}
  556. >
  557. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  558. <FormattedMessage id="paymentHistory" />
  559. </Typography>
  560. <KeyboardArrowDownIcon sx={{ fontSize: "1.0rem" }} />
  561. </button>
  562. <ul className="dropdown" data-submenu="paymentHistory">
  563. <li>
  564. <Link className="manageOrgUser" to="/paymentPage/search">
  565. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  566. <FormattedMessage id="onlinePaymentHistory" />
  567. </Typography>
  568. </Link>
  569. </li>
  570. {(isCreditorLoggedIn() && haveOrgPaymentRecord()) ||
  571. (isORGLoggedIn() && haveOrgPaymentRecord() && haveOrgDnRecord()) ||
  572. (!isCreditorLoggedIn() && !isORGLoggedIn() && haveOrgDnRecord()) ? (
  573. <li>
  574. <Link className="manageOrgUser" to="/paymentPage/demandNote">
  575. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  576. <FormattedMessage id="paymentInfoRecord" />
  577. </Typography>
  578. </Link>
  579. </li>
  580. ) : null}
  581. </ul>
  582. </>
  583. ) : (
  584. <Link className="manageOrgUser" to="/paymentPage/demandNote">
  585. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  586. <FormattedMessage id="paymentInfoRecord" />
  587. </Typography>
  588. </Link>
  589. )}
  590. </li>
  591. {/* ===== User Setting dropdown (non-admin) ===== */}
  592. <li>
  593. <button
  594. type="button"
  595. className="navTrigger userSetting"
  596. aria-haspopup="true"
  597. aria-expanded={openMenu.userSetting}
  598. onFocus={() => openKey("userSetting")}
  599. onBlur={onTriggerBlur("userSetting")}
  600. onKeyDown={onMenuKeyDown("userSetting")}
  601. >
  602. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 1 }}>
  603. <FormattedMessage id="setting" />
  604. </Typography>
  605. <KeyboardArrowDownIcon sx={{ fontSize: "1.0rem" }} />
  606. </button>
  607. <ul className="dropdown" style={{ width: "max-content" }} data-submenu="userSetting">
  608. {isPrimaryLoggedIn() ? (
  609. <>
  610. <li>
  611. <Link className="manageOrgUser" to="setting/manageUser">
  612. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  613. <FormattedMessage id="companyOrUserRecord" />
  614. </Typography>
  615. </Link>
  616. </li>
  617. <li>
  618. <Link className="manageUser" to={"/org"}>
  619. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  620. <FormattedMessage id="organizationProfile" />
  621. </Typography>
  622. </Link>
  623. </li>
  624. <li>
  625. <Link className="manageUser" to={"/orgUser"}>
  626. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  627. <FormattedMessage id="userProfile" />
  628. </Typography>
  629. </Link>
  630. </li>
  631. </>
  632. ) : isINDLoggedIn() ? (
  633. <li>
  634. <Link className="manageUser" to={"/indUser"}>
  635. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  636. <FormattedMessage id="userProfile" />
  637. </Typography>
  638. </Link>
  639. </li>
  640. ) : (
  641. <li>
  642. <Link className="manageUser" to={"/orgUser"}>
  643. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  644. <FormattedMessage id="userProfile" />
  645. </Typography>
  646. </Link>
  647. </li>
  648. )}
  649. <li>
  650. <Link className="manageUser" to={"/user/changePassword"}>
  651. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  652. <FormattedMessage id="userChangePassword" />
  653. </Typography>
  654. </Link>
  655. </li>
  656. </ul>
  657. </li>
  658. </div>
  659. )}
  660. <Box sx={{ display: { xs: "none", sm: "none", md: "block" } }}>
  661. <li>
  662. <Link className="logout" onClick={handleLogout}>
  663. <Typography variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  664. <FormattedMessage id="logout" />
  665. </Typography>
  666. </Link>
  667. </li>
  668. </Box>
  669. </div>
  670. );
  671. // =============================
  672. // Logged-out top nav content
  673. // =============================
  674. const logoutContent = (
  675. <div>
  676. <li>
  677. {!isAfterAboutSwitchDate() ? (
  678. <Link className="login" to="/aboutUs">
  679. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  680. <FormattedMessage id="aboutUs" />
  681. </Typography>
  682. </Link>
  683. ) : (
  684. <a className="login" href={getAboutExternalUrlByLocale()} target="_blank" rel="noopener noreferrer">
  685. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  686. <FormattedMessage id="aboutUs" />
  687. </Typography>
  688. </a>
  689. )}
  690. </li>
  691. <li>
  692. <Link className="login" to={"/userGuidePub"}>
  693. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  694. <FormattedMessage id="userGuide" />
  695. </Typography>
  696. </Link>
  697. </li>
  698. <li>
  699. <Link className="login" to="/login">
  700. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  701. <FormattedMessage id="login" />
  702. </Typography>
  703. </Link>
  704. </li>
  705. {sysSetting?.allowRegistration ? (
  706. <li>
  707. <Link className="register" to="/register">
  708. <Typography style={{ opacity: 0.9 }} variant={"pnspsHeaderTitle"} sx={{ ml: 2 }}>
  709. <FormattedMessage id="register" />
  710. </Typography>
  711. </Link>
  712. </li>
  713. ) : null}
  714. </div>
  715. );
  716. const drawer = isUserLoggedIn() ? (
  717. <Stack id="sidebar" direction="column" justifyContent="center" alignItems="center" sx={{ textAlign: "center", width: "300px" }}>
  718. <Box sx={{ mr: 2, mt: 1, display: { md: "none" } }}>
  719. <MobileLogo />
  720. <span style={{ color: checkSysEnv() !== "" ? "red" : "#1976d2" }} id="mobileTitle">
  721. PNSPS
  722. </span>
  723. </Box>
  724. <Divider />
  725. <ul id="sidebartop">{loginContent}</ul>
  726. <Divider />
  727. <ul id="sidebarbottom">
  728. <li>
  729. <Link className="logout" onClick={handleLogout}>
  730. <FormattedMessage id="logout" />
  731. </Link>
  732. </li>
  733. </ul>
  734. </Stack>
  735. ) : (
  736. <Stack id="sidebar" direction="column" justifyContent="center" alignItems="center" onClick={handleDrawerToggle} sx={{ textAlign: "center" }}>
  737. <Box sx={{ mr: 2, mt: 1, display: { md: "none" } }}>
  738. <MobileLogo />
  739. <span style={{ color: checkSysEnv() !== "" ? "red" : "#1976d2" }} id="mobileTitle">
  740. PNSPS
  741. </span>
  742. </Box>
  743. <Divider />
  744. <ul id="logoutContent">{logoutContent}</ul>
  745. <Divider />
  746. </Stack>
  747. );
  748. const container = window !== undefined ? () => window().document.body : undefined;
  749. // =============================
  750. // Render (your layout kept)
  751. // =============================
  752. return isUserLoggedIn() ? (
  753. <Box>
  754. <AppBar component="nav" elevation={0}>
  755. <Toolbar id="nav" width="100%">
  756. {isGLDLoggedIn() ? (
  757. <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={0} sx={{ width: { xs: "100%", md: "5%" } }}>
  758. <Box mt={0.5} sx={{ flexGrow: 1, display: { xs: "none", sm: "none", md: "block" } }}>
  759. <AdminLogo />
  760. </Box>
  761. <IconButton
  762. color="inherit"
  763. aria-label={intl.formatMessage({ id: "openMenu" })}
  764. aria-expanded={mobileOpen}
  765. edge="start"
  766. onClick={handleDrawerToggle}
  767. sx={{ mr: 2, display: { md: "none" } }}
  768. >
  769. <MenuIcon style={{ color: "#1976d2" }} />
  770. </IconButton>
  771. <Box sx={{ flexGrow: 1, mr: 2, display: { sm: "block", md: "none" } }}>
  772. <Stack direction="row" justifyContent="space-between" alignItems="center" width="100%">
  773. <MobileLogo />
  774. <Stack justifyContent="flex-start" alignItems="flex-start" width="100%" ml={2}>
  775. <span style={{ color: checkSysEnv() !== "" ? "red" : "#1976d2" }} id="mobileTitle">
  776. PNSPS
  777. </span>
  778. </Stack>
  779. <Stack justifyContent="flex-end" alignItems="center">
  780. <span style={{ color: "#B11B1B", fontWeight: "bold", fontSize: "15px" }}>RESTRICTED</span>
  781. </Stack>
  782. </Stack>
  783. </Box>
  784. </Stack>
  785. ) : (
  786. <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={0} sx={{ width: { xs: "100%", md: "25%" } }}>
  787. <Box sx={{ width: "450px", flexGrow: 1, display: { xs: "none", sm: "none", md: "block" } }}>
  788. <Stack direction="row" justifyContent="flex-start" alignItems="center">
  789. <Logo />
  790. <Stack justifyContent="flex-start" alignItems="center">
  791. <span style={{ color: checkSysEnv() !== "" ? "#B00020" : "#1976d2" }} id="systemTitle">
  792. <FormattedMessage id="PNSPS" />
  793. </span>
  794. </Stack>
  795. </Stack>
  796. </Box>
  797. <IconButton
  798. color="inherit"
  799. aria-label={intl.formatMessage({ id: "openMenu" })}
  800. aria-expanded={mobileOpen}
  801. edge="start"
  802. onClick={handleDrawerToggle}
  803. sx={{ mr: 2, display: { md: "none" } }}
  804. >
  805. <MenuIcon style={{ color: "#1976d2" }} />
  806. </IconButton>
  807. <Box sx={{ flexGrow: 1, mr: 2, display: { sm: "block", md: "none" } }}>
  808. <Stack direction="row" justifyContent="space-between" alignItems="center" width="100%">
  809. <MobileLogo />
  810. <Stack justifyContent="flex-start" alignItems="flex-start" width="100%" ml={2}>
  811. <span style={{ color: checkSysEnv() !== "" ? "red" : "#1976d2" }} id="mobileTitle">
  812. <FormattedMessage id="PNSPS" />
  813. </span>
  814. </Stack>
  815. <Stack justifyContent="flex-end" alignItems="center">
  816. <LocaleSelector />
  817. </Stack>
  818. </Stack>
  819. </Box>
  820. </Stack>
  821. )}
  822. <Box sx={{ display: { xs: "none", sm: "none", md: "block" }, width: "100%" }}>
  823. <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={1}>
  824. <ul id="navbar" width="100%">
  825. {loginContent}
  826. </ul>
  827. <Grid item>
  828. <Grid container direction="column" justifyContent="flex-end" alignItems="center">
  829. {isGLDLoggedIn() ? (
  830. <Grid item>
  831. <span style={{ color: "#B11B1B", fontWeight: "bold", fontSize: "15px" }}>RESTRICTED</span>
  832. </Grid>
  833. ) : (
  834. <Grid item>
  835. <LocaleSelector />
  836. </Grid>
  837. )}
  838. </Grid>
  839. </Grid>
  840. </Stack>
  841. </Box>
  842. </Toolbar>
  843. </AppBar>
  844. <Box component="nav">
  845. <Drawer
  846. container={container}
  847. variant="temporary"
  848. open={mobileOpen}
  849. onClose={handleDrawerToggle}
  850. ModalProps={{ keepMounted: true }}
  851. sx={{
  852. display: { sm: "block", md: "none" },
  853. "& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth },
  854. }}
  855. >
  856. {drawer}
  857. </Drawer>
  858. </Box>
  859. </Box>
  860. ) : (
  861. <Box>
  862. <AppBar component="nav" elevation={0}>
  863. <Toolbar id="nav" width="100%">
  864. <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={0} sx={{ width: { xs: "100%", md: "25%" } }}>
  865. <Box sx={{ flexGrow: 1, display: { xs: "none", sm: "none", md: "block" } }}>
  866. <Stack direction="row" justifyContent="flex-start" alignItems="center">
  867. <Logo />
  868. <Stack justifyContent="flex-start" alignItems="center">
  869. <span id="systemTitle">
  870. <FormattedMessage id="PNSPS" />
  871. </span>
  872. </Stack>
  873. </Stack>
  874. </Box>
  875. <IconButton
  876. color="inherit"
  877. aria-label={intl.formatMessage({ id: "openMenu" })}
  878. aria-expanded={mobileOpen}
  879. edge="start"
  880. onClick={handleDrawerToggle}
  881. sx={{ mr: 2, display: { md: "none" } }}
  882. >
  883. <MenuIcon style={{ color: "#1976d2" }} />
  884. </IconButton>
  885. <Box sx={{ flexGrow: 1, mr: 2, display: { sm: "block", md: "none" } }}>
  886. <Stack direction="row" justifyContent="space-between" alignItems="center" width="100%">
  887. <MobileLogo />
  888. <Stack justifyContent="flex-start" alignItems="flex-start" width="100%" ml={2}>
  889. <span style={{ color: checkSysEnv() !== "" ? "red" : "#1976d2" }} id="mobileTitle">
  890. <FormattedMessage id="PNSPS" />
  891. </span>
  892. </Stack>
  893. <Stack justifyContent="flex-end" alignItems="center">
  894. <LocaleSelector />
  895. </Stack>
  896. </Stack>
  897. </Box>
  898. </Stack>
  899. <Box sx={{ display: { xs: "none", sm: "none", md: "block" }, width: "75%" }}>
  900. <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={1}>
  901. <ul id="navbar" width="100%">
  902. {logoutContent}
  903. </ul>
  904. <LocaleSelector />
  905. </Stack>
  906. </Box>
  907. </Toolbar>
  908. </AppBar>
  909. <Box component="nav">
  910. <Drawer
  911. container={container}
  912. variant="temporary"
  913. open={mobileOpen}
  914. onClose={handleDrawerToggle}
  915. ModalProps={{ keepMounted: true }}
  916. sx={{
  917. display: { sm: "block", md: "none" },
  918. "& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth },
  919. }}
  920. >
  921. {drawer}
  922. </Drawer>
  923. </Box>
  924. </Box>
  925. );
  926. }
  927. Header.propTypes = {
  928. window: PropTypes.func,
  929. };
  930. export default Header;