diff --git a/src/assets/style/navbarStyles.css b/src/assets/style/navbarStyles.css index 9d5d352..d282fa9 100644 --- a/src/assets/style/navbarStyles.css +++ b/src/assets/style/navbarStyles.css @@ -1,175 +1,358 @@ +/* assets/style/navbarStyles.css */ + +/* ===== FIX typo ===== */ #nav{ - display: flex; - align-items: center; - justify-content: space-between; - background-color: white; - /* padding: 20px 80px; */ - box-shadow: 0 5px 15px rgba(0,0 0,0,0.06); - position:fixed; - top: 0px; - width: 100%; - z-index: 9999; - border-bottom: 3px solid #0C489E; + display: flex; + align-items: center; + justify-content: space-between; + background-color: white; + box-shadow: 0 5px 15px rgba(0,0,0,0.06); /* <-- fixed */ + position: fixed; + top: 0px; + width: 100%; + z-index: 9999; + border-bottom: 3px solid #0C489E; + min-height: 77px; +} + +#navbar > div > li { + height: 77px; /* one single source of truth */ + display: flex; + align-items: stretch; + padding: 0; /* IMPORTANT: stop li padding making different widths */ + margin: 0; +} + +#navbar div li{ + padding: 0; /* override your earlier padding: 0 1vw */ +} + +/* Make dropdown button identical to anchor */ +#navbar > div > li > button.navTrigger { + background: transparent; + border: 0; + padding: 0; /* remove default button padding */ + margin: 0; + font: inherit; /* inherit font from li */ + line-height: 1; /* normalize */ + height: auto; + display: inline-flex; + align-items: center; + cursor: pointer; +} + +/* Make both anchor and button same vertical box */ +#navbar > div > li > a, +#navbar > div > li > button.navTrigger { + height: 100% !important; /* override the old 72px */ + padding: 0 24px !important; /* bigger hit area */ + display: flex; + align-items: center; + box-sizing: border-box; } + +/* Make ONLY the dropdown button not bold */ +#navbar > div > li > button.navTrigger, +#navbar > div > li > button.navTrigger .MuiTypography-root { + font-weight: 400 !important; +} + +/* (optional) keep links bold */ +#navbar > div > li > a, +#navbar > div > li > a .MuiTypography-root { + font-weight: 600; +} + #navbar div{ - display: flex; - align-items: center; - justify-content: center; - + display: flex; + align-items: center; + justify-content: center; } + #navbar div li{ - list-style: none; - padding: 0 1vw; - position: relative; + list-style: none; + padding: 0 1vw; + position: relative; } + +/* keep your styling */ #navbar div li a{ - text-decoration: none; - font-size: 1.2rem; - font-weight: 600; - /* font-family: 微軟正黑體; */ - color: black; - transition: 0.3s ease-in-out; + text-decoration: none; + font-size: 1.2rem; + font-weight: 600; + color: black; + transition: 0.3s ease-in-out; } -#navbar div li a span{ - font-size: 1vw !important; +/* NEW: style dropdown trigger buttons to look like links */ +#navbar div li button.navTrigger{ + background: transparent; + border: 0; + padding: 0; + cursor: pointer; + + text-decoration: none; + font-size: 1.2rem; + font-weight: 600; + color: black; + transition: 0.3s ease-in-out; + + display: inline-flex; + align-items: center; + gap: 0.25rem; } +#navbar div li a span, #navbar div li a svg{ - font-size: 1vw !important; + font-size: 1vw !important; } -#navbar div li a:hover{ - color: #0C489E; +#navbar div li a:hover, +#navbar div li button.navTrigger:hover{ + color: #0C489E; } + #navbar div li a:hover::after, -#navbar div li a:focus-visible::after{ - content: ""; - width: 80%; - height: 3px; - background:#0C489E; - position: absolute; - bottom: -5px; - left: 20px; -} -#navbar div li ul { +#navbar div li a:focus-visible::after, +#navbar div li button.navTrigger:hover::after, +#navbar div li button.navTrigger:focus-visible::after{ + content: ""; + width: 80%; + height: 3px; + background:#0C489E; + position: absolute; + bottom: -5px; + left: 20px; +} + +/* submenu base */ +#navbar div li ul{ background: white; visibility: hidden; opacity: 0; min-width: 18rem; position: absolute; - /* transition: all 0.5s ease; */ left: 0; display: none; padding-left: 0px; padding-bottom: 7px; - /* border: 1px solid #0C489E; */ background-clip: padding-box; border: 1px solid rgba(0,0,0,.15); border-radius: 0.25rem; } -#navbar div li:hover > ul{ - visibility: visible; - opacity: 1; - display: block - } -/* Navbar: don't show focus ring on mouse click */ -#navbar a:focus { +/* hover open */ +#navbar div li:hover > ul{ + visibility: visible; + opacity: 1; + display: block; +} + +/* keyboard open */ +#navbar div li:focus-within > ul{ + visibility: visible; + opacity: 1; + display: block; +} + +/* Navbar focus ring */ +#navbar a:focus, +#navbar button.navTrigger:focus{ outline: none; } -/* Navbar: show focus ring for keyboard navigation */ -#navbar a:focus-visible { +#navbar a:focus-visible, +#navbar button.navTrigger:focus-visible{ outline: 3px solid #0C489E; outline-offset: 2px; - border-radius: 10px; /* tweak to match your design */ + border-radius: 10px; } -/* #navbar div li:focus-within > ul, -#navbar div li ul:hover, -#navbar div li ul:focus { - visibility: visible; - opacity: 1; - display: block -} */ +#navbar a.dashboard, +#navbar a.application, +#navbar a.proof, +#navbar a.myDocumet, +#navbar a.documentRecord, +#navbar a.manageOrgUser, +#navbar a.manageUser, +#navbar a.systemSetting, +#navbar a.report, +#navbar a.payment, +#navbar a.user, +#navbar a.logout { + font-size: 1.1rem !important; + font-weight: 600 !important; +} + +#navbar a.dashboard .MuiTypography-root, +#navbar a.application .MuiTypography-root, +#navbar a.proof .MuiTypography-root, +#navbar a.myDocumet .MuiTypography-root, +#navbar a.documentRecord .MuiTypography-root, +#navbar a.manageOrgUser .MuiTypography-root, +#navbar a.manageUser .MuiTypography-root, +#navbar a.systemSetting .MuiTypography-root, +#navbar a.report .MuiTypography-root, +#navbar a.payment .MuiTypography-root, +#navbar a.user .MuiTypography-root, +#navbar a.logout .MuiTypography-root { + font-size: 1.1rem !important; + font-weight: 600 !important; +} + +/* Make them bigger + bold */ +#navbar a.login, +#navbar a.register, +#navbar a.login .MuiTypography-root, +#navbar a.register .MuiTypography-root { + font-size: 1.1rem !important; + font-weight: 600 !important; +} + +/* ===== your existing sidebar (unchanged) ===== */ #systemTitle{ - text-decoration: none; - font-size: 1.3rem; - font-weight: 600; - color: #0C489E; - transition: 0.3s ease-in-out; - /* font-family: 微軟正黑體; */ - text-align: center; + text-decoration: none; + font-size: 1.1rem; + font-weight: 600; + color: #0C489E; + transition: 0.3s ease-in-out; + text-align: center; } #mobileTitle{ - text-decoration: none; - font-size: 1.2rem; - font-weight: 600; - color: #0C489E; - transition: 0.3s ease-in-out; - /* font-family: 微軟正黑體; */ - text-align: center; + text-decoration: none; + font-size: 1.1rem; + font-weight: 600; + color: #0C489E; + transition: 0.3s ease-in-out; + text-align: center; } #sidebar{ - font-size: 1.3rem; - font-weight: 600; - /* font-family: 微軟正黑體; */ + font-size: 1.1rem; + font-weight: 600; } #sidebartop{ - align-items: center; - justify-content: start; - padding: 0; - display: flex; - width: 100%; + align-items: center; + justify-content: start; + padding: 0; + display: flex; + width: 100%; } #logoutContent{ - align-items: center; - justify-content: center; - padding: 0; - display: flex; - width: 100%; + align-items: center; + justify-content: center; + padding: 0; + display: flex; + width: 100%; } #sidebarbottom{ - align-items: center; - justify-content: center; - padding: 0; - display: flex; + align-items: center; + justify-content: center; + padding: 0; + display: flex; } #sidebar li{ - list-style: none; - padding: 0 20px; - position: relative; - text-align: left; + list-style: none; + padding: 0 20px; + position: relative; + text-align: left; } #sidebar li a{ - text-decoration: none; - font-size: 1.3rem; - font-weight: 600; - /* font-family: 微軟正黑體; */ - color: black; - transition: 0.3s ease-in-out; + text-decoration: none; + font-size: 1.1rem; + font-weight: 600; + color: black; + transition: 0.3s ease-in-out; } #sidebar li a:hover{ - color: #0C489E; -} -#sidebar div li ul { - background: white; - visibility: hidden; - opacity: 0; - min-width: 16rem; - position: relative; - /* transition: all 0.5s ease; */ - left: 0; - display: none; - padding-left: 0px; - /* border: 1px solid #0C489E; */ - } - #sidebar div li:hover > ul, - #sidebar div li:focus-within > ul, - #sidebar div li ul:hover, - #sidebar div li ul:focus { - visibility: visible; - opacity: 1; - display: block - } \ No newline at end of file + color: #0C489E; +} +#sidebar div li ul{ + background: white; + visibility: hidden; + opacity: 0; + min-width: 16rem; + position: relative; + left: 0; + display: none; + padding-left: 0px; +} +#sidebar div li:hover > ul, +#sidebar div li:focus-within > ul, +#sidebar div li ul:hover, +#sidebar div li ul:focus{ + visibility: visible; + opacity: 1; + display: block; +} + +/* ===== Mobile Drawer / Sidebar menu styling ===== */ +#sidebar ul { + width: 100%; + padding: 0; + margin: 0; +} + +#sidebar li{ + width: 100%; + padding: 0; /* remove the old 0 20px */ + margin: 0; +} + +/* Make BOTH links and dropdown buttons look the same */ +#sidebar li > a, +#sidebar li > button.navTrigger{ + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; /* text left, arrow right */ + gap: 12px; + + background: transparent; + border: 0; /* kill grey border */ + box-shadow: none; + outline: none; + + padding: 14px 20px; + text-align: left; + + font-size: 1.3rem; + font-weight: 600; + color: black; + cursor: pointer; +} + +/* Hover / focus */ +#sidebar li > a:hover, +#sidebar li > button.navTrigger:hover{ + color: #0C489E; +} + +#sidebar li > a:focus-visible, +#sidebar li > button.navTrigger:focus-visible{ + outline: 3px solid #0C489E; + outline-offset: 2px; + border-radius: 10px; +} + +/* Submenu (indent + full width) */ +#sidebar li ul{ + width: 100%; + padding: 6px 0 6px 0; + margin: 0; + display: none; + visibility: hidden; + opacity: 0; +} + +/* Open on focus-within (tap/click focuses button) */ +#sidebar li:focus-within > ul{ + display: block; + visibility: visible; + opacity: 1; +} + +#sidebar li ul li > a{ + padding: 12px 20px 12px 40px; /* indent submenu */ + font-size: 1.15rem; + font-weight: 600; +} \ No newline at end of file diff --git a/src/auth/utils.js b/src/auth/utils.js index 23b94a4..b6d0357 100644 --- a/src/auth/utils.js +++ b/src/auth/utils.js @@ -171,3 +171,6 @@ export const getPaymentMethodByCode = (code) => { return "other"; }; +export function setPageTitle(title) { + document.title = `${title} - PNSPS | Government Logistics Department`; +} \ No newline at end of file diff --git a/src/components/AdminLogo/index.js b/src/components/AdminLogo/index.js index f847e3b..4fa7a84 100644 --- a/src/components/AdminLogo/index.js +++ b/src/components/AdminLogo/index.js @@ -15,21 +15,23 @@ import { checkSysEnv } from "utils/Utils"; +import {useIntl} from "react-intl"; // ==============================|| MAIN LOGO ||============================== // const LogoSection = ({ sx, to }) => { const { defaultId } = useSelector((state) => state.menu); const dispatch = useDispatch(); + const intl = useIntl(); + return ( - - dispatch(activeItem({ openItem: [defaultId] }))} to={!to ? config.defaultPath : to} + aria-label={intl.formatMessage({ id: "PNSPS", defaultMessage: "PNSPS" })} sx={{ ...sx, - /* ✅ WCAG 2.4.7 focus indicator */ '&:focus-visible': { outline: '3px solid #0C489E', @@ -38,10 +40,11 @@ const LogoSection = ({ sx, to }) => { } }} > - + + + PNSPS + - PNSPS - ); }; diff --git a/src/components/FiDataGrid.js b/src/components/FiDataGrid.js index 6b28f0f..89c3e25 100644 --- a/src/components/FiDataGrid.js +++ b/src/components/FiDataGrid.js @@ -1,5 +1,5 @@ // material-ui -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { Box } from "@mui/material"; import { DataGrid, GridOverlay, @@ -202,9 +202,49 @@ export function FiDataGrid({ rows, columns, sx, autoHeight = true, }); } + const gridRootRef = useRef(null); + + useEffect(() => { + const root = gridRootRef.current; + if (!root) return; + + const sortText = intl.formatMessage({ id: "sort", defaultMessage: "Sort" }); + + const apply = () => { + // 1) Make ALL column headers tabbable (optional; DataGrid already manages focus well) + root + .querySelectorAll('.MuiDataGrid-columnHeaders [role="columnheader"]') + .forEach((el) => { + if (el.getAttribute("tabindex") !== "0") el.setAttribute("tabindex", "0"); + }); + + // 2) Localize sort icon button label (handles "sort"/"Sort"/any old value) + const sortButtons = root.querySelectorAll( + '.MuiDataGrid-columnHeaders button.MuiIconButton-root' + ); + + sortButtons.forEach((btn) => { + const al = (btn.getAttribute("aria-label") || "").trim().toLowerCase(); + const ti = (btn.getAttribute("title") || "").trim().toLowerCase(); + + // Only rewrite the ones that are the sort icon buttons + if (al === "sort" || ti === "sort") { + btn.setAttribute("aria-label", sortText); + btn.setAttribute("title", sortText); + } + }); + }; + + apply(); + + const obs = new MutationObserver(apply); + obs.observe(root, { childList: true, subtree: true }); + + return () => obs.disconnect(); + }, [intl]); return ( - + { const { defaultId } = useSelector((state) => state.menu); const dispatch = useDispatch(); + const intl = useIntl(); + return ( dispatch(activeItem({ openItem: [defaultId] }))} to={!to ? config.defaultPath : to} + aria-label={intl.formatMessage({ id: "PNSPS", defaultMessage: "PNSPS" })} sx={sx} > diff --git a/src/components/MobileLogo/index.js b/src/components/MobileLogo/index.js index d5386ff..da2ff62 100644 --- a/src/components/MobileLogo/index.js +++ b/src/components/MobileLogo/index.js @@ -9,10 +9,13 @@ import { useDispatch, useSelector } from 'react-redux'; import Logo from './MobileLogo'; import config from 'config'; import { activeItem } from 'store/reducers/menu'; +import {useIntl} from "react-intl"; // ==============================|| MAIN LOGO ||============================== // const LogoSection = ({ sx, to }) => { + const intl = useIntl(); + const { defaultId } = useSelector((state) => state.menu); const dispatch = useDispatch(); return ( @@ -21,6 +24,7 @@ const LogoSection = ({ sx, to }) => { component={Link} onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} to={!to ? config.defaultPath : to} + aria-label={intl.formatMessage({ id: "PNSPS" })} sx={{ ...sx, diff --git a/src/components/usePageTitle.js b/src/components/usePageTitle.js new file mode 100644 index 0000000..8656c27 --- /dev/null +++ b/src/components/usePageTitle.js @@ -0,0 +1,23 @@ +import { useEffect } from "react"; +import { useIntl } from "react-intl"; + +export default function usePageTitle(messageIdOrText) { + const intl = useIntl(); + + useEffect(() => { + let pageTitle; + let systemName; + let gldName; + + // If string looks like an intl id, try translate + try { + pageTitle = intl.formatMessage({ id: messageIdOrText }); + systemName = intl.formatMessage({ id: "PNSPS_fullname" }); + gldName = intl.formatMessage({ id: "HKGLD" }); + } catch { + pageTitle = messageIdOrText; + } + + document.title = `${pageTitle} - ${systemName} | ${gldName}`; + }, [messageIdOrText, intl]); +} \ No newline at end of file diff --git a/src/layout/MainLayout/Header/index.js b/src/layout/MainLayout/Header/index.js index f1f8fcd..5840641 100644 --- a/src/layout/MainLayout/Header/index.js +++ b/src/layout/MainLayout/Header/index.js @@ -1,49 +1,30 @@ -import PropTypes from 'prop-types'; -import React -, { useState, useContext } - from 'react'; +import PropTypes from "prop-types"; +import React, { useState, useContext, useEffect, useRef } from "react"; import { useDispatch } from "react-redux"; -import { useNavigate } from "react-router-dom"; -import { SysContext } from "components/SysSettingProvider" - -import { checkIsOnlyOnlinePayment } from '../../../utils/Utils'; +import { useNavigate, Link } from "react-router-dom"; +import { SysContext } from "components/SysSettingProvider"; +import { checkIsOnlyOnlinePayment } from "../../../utils/Utils"; // material-ui -// import { useTheme } from '@mui/material/styles'; import { AppBar, - // Container, Typography, Box, Stack, - // IconButton, - // Menu, - // MenuItem, - // Button, - // Tooltip, - // Avatar, - // Stack, Toolbar, Divider, - // List, - // ListItem, - // ListItemButton, - // ListItemText, IconButton, - Drawer, Grid, - // useMediaQuery -} from '@mui/material'; -import MenuIcon from '@mui/icons-material/Menu'; -import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; + Drawer, + Grid, +} from "@mui/material"; +import MenuIcon from "@mui/icons-material/Menu"; +import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; // project import -// import AppBarStyled from './AppBarStyled'; -// import HeaderContent from './HeaderContent'; -import Logo from 'components/Logo'; -import AdminLogo from 'components/AdminLogo'; -import MobileLogo from 'components/MobileLogo'; -//import Profile from './HeaderContent/Profile'; +import Logo from "components/Logo"; +import AdminLogo from "components/AdminLogo"; +import MobileLogo from "components/MobileLogo"; import "assets/style/navbarStyles.css"; import { isUserLoggedIn, @@ -55,574 +36,703 @@ import { haveOrgPaymentRecord, haveOrgDnRecord, isORGLoggedIn, - checkSysEnv - // getUserId + checkSysEnv, } from "utils/Utils"; -import { handleLogoutFunction } from 'auth/index'; +import { handleLogoutFunction } from "auth/index"; import { isGranted, isGrantedAny } from "auth/utils"; -// assets -// import { MenuFoldOutlined,MenuOutlined } from '@ant-design/icons'; -// import { AppBar } from '../../../../node_modules/@mui/material/index'; -import { Link } from "react-router-dom"; import LocaleSelector from "./HeaderContent/LocaleSelector"; import { FormattedMessage, useIntl } from "react-intl"; const drawerWidth = 300; - const isAfterAboutSwitchDate = checkIsOnlyOnlinePayment; - const getAboutExternalUrlByLocale = () => { const locale = (localStorage.getItem("locale") || "").toLowerCase(); - - if (locale === "zh-hk") { - // TChinese - return "https://www.gld.gov.hk/zh-hk/our-services/printing/advertising-gov-gazette/"; - } - if (locale === "zh-cn") { - // Simplified Chinese (你未貼,我用 zh-cn 版) - return "https://www.gld.gov.hk/zh-cn/our-services/printing/advertising-gov-gazette/"; - } - // English (en-us) / fallback - return "https://www.gld.gov.hk/en/our-services/printing/advertising-gov-gazette/"; + if (locale === "zh-hk") return "https://www.gld.gov.hk/zh-hk/our-services/printing/advertising-gov-gazette/"; + if (locale === "zh-cn") return "https://www.gld.gov.hk/zh-cn/our-services/printing/advertising-gov-gazette/"; + return "https://www.gld.gov.hk/en/our-services/printing/advertising-gov-gazette/"; }; - -// const navItems = ['Home', 'About', 'Contact']; -// ==============================|| MAIN LAYOUT - HEADER ||============================== // - +/** + * Accessible dropdown trigger: + * - button is always focusable + * - opens on focus (keyboard tab) via CSS :focus-within + optional state + * - supports Enter/Space/ArrowDown to jump to first submenu item + * - supports Escape to close + return focus + */ function Header(props) { const { sysSetting } = useContext(SysContext); const { window } = props; - const [mobileOpen, setMobileOpen] = useState(false); - const dispatch = useDispatch() - const navigate = useNavigate() + const [mobileOpen, setMobileOpen] = useState(false); + const dispatch = useDispatch(); + const navigate = useNavigate(); const intl = useIntl(); - const handleDrawerToggle = () => { - setMobileOpen((prevState) => !prevState); - }; + // which dropdown is open (optional but useful for aria-expanded + closing) + const [openMenu, setOpenMenu] = useState({ + payment: false, + client: false, + report: false, + settings: false, + paymentHistory: false, + userSetting: false, + }); + + const rootNavRef = useRef(null); + + const handleDrawerToggle = () => setMobileOpen((prev) => !prev); const handleLogout = async () => { await dispatch(handleLogoutFunction()); - //await handleLogoutFunction(); + await navigate("/login"); + }; - await navigate('/login'); + const openKey = (key) => setOpenMenu((p) => ({ ...p, [key]: true })); + const closeKey = (key) => setOpenMenu((p) => ({ ...p, [key]: false })); + + const closeAll = () => + setOpenMenu({ + payment: false, + client: false, + report: false, + settings: false, + paymentHistory: false, + userSetting: false, + }); + + // close dropdowns on outside click / focus + useEffect(() => { + const onDocMouseDown = (e) => { + if (!rootNavRef.current) return; + if (!rootNavRef.current.contains(e.target)) closeAll(); + }; + document.addEventListener("mousedown", onDocMouseDown, true); + return () => document.removeEventListener("mousedown", onDocMouseDown, true); + }, []); + + const onMenuKeyDown = (key) => (e) => { + if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") { + e.preventDefault(); + openKey(key); + requestAnimationFrame(() => { + const first = document.querySelector( + `[data-submenu="${key}"] a, [data-submenu="${key}"] button` + ); + first?.focus?.(); + }); + } else if (e.key === "Escape") { + e.preventDefault(); + closeKey(key); + e.currentTarget?.focus?.(); + } else if (e.key === "ArrowUp") { + // optional: close on ArrowUp when on trigger + closeKey(key); + } }; - const loginContent = ( - isGLDLoggedIn() ? -
- {isPasswordExpiry() ? -
+ const onTriggerBlur = (key) => (e) => { + // close only when focus leaves the whole LI (trigger + submenu) + const li = e.currentTarget.closest("li"); + if (li && li.contains(e.relatedTarget)) return; + closeKey(key); + }; + + // ============================= + // Desktop top nav content (login) + // ============================= + const loginContent = isGLDLoggedIn() ? ( +
+ {isPasswordExpiry() ? ( +
+
  • + + + + + +
  • +
    + ) : ( +
    +
  • + + + Dashboard + + +
  • + + {isGrantedAny(["VIEW_APPLICATION", "MAINTAIN_APPLICATION"]) ? (
  • - - - + + + Application
  • -
    - : -
    + ) : null} + + {isGrantedAny(["VIEW_PROOF", "MAINTAIN_PROOF"]) ? (
  • - - - Dashboard + + + Proof
  • - { - isGrantedAny(["VIEW_APPLICATION", "MAINTAIN_APPLICATION"]) ? -
  • - Application -
  • - : <> - } - { - isGrantedAny(["VIEW_PROOF", "MAINTAIN_PROOF"]) ? -
  • - Proof -
  • - : <> - } + ) : null} + + {/* ===== Payment dropdown (admin) ===== */} + {isGrantedAny([ + "MAINTAIN_PROOF", + "MAINTAIN_PAYMENT", + "MAINTAIN_RECON", + "VIEW_DEMANDNOTE", + "MAINTAIN_DEMANDNOTE", + ]) ? ( +
  • + - { - isGrantedAny(["MAINTAIN_PROOF", "MAINTAIN_PAYMENT", "MAINTAIN_RECON", "VIEW_DEMANDNOTE", "MAINTAIN_DEMANDNOTE"]) ? -
  • - Payment -
      - { - isGranted("MAINTAIN_PROOF") ? -
    • - Export for GDN -
    • - : - <> - } - - { - isGranted("MAINTAIN_PAYMENT") ? -
    • - Mark Payment -
    • - : - <> - } - - { - isGranted("MAINTAIN_PAYMENT") ? -
    • - Online Payment Record -
    • - : - <> - } - - { - isGranted("MAINTAIN_RECON") ? - <> -
    • - GFMIS Generate XML -
    • - - - : - <> - } - - { - isGranted("MAINTAIN_DEMANDNOTE") ? -
    • - Create Demand Note -
    • - : - <> - } - { - isGrantedAny(["VIEW_DEMANDNOTE", "MAINTAIN_DEMANDNOTE"]) ? -
    • - Demand Note -
    • - : - <> - } - { - isGranted("MAINTAIN_RECON") ? - <> -
    • - Recon Report -
    • - - - : - <> - } -
    -
  • - : - <> - } +
      + {isGranted("MAINTAIN_PROOF") ? ( +
    • + + + Export for GDN + + +
    • + ) : null} - { - isGrantedAny(["VIEW_USER", "MAINTAIN_USER", "VIEW_ORG", "MAINTAIN_ORG", "VIEW_GROUP", "MAINTAIN_GROUP", "VIEW_GLD_USER", "VIEW_IND_USER", "VIEW_ORG_USER", "MAINTAIN_GLD_USER", "MAINTAIN_IND_USER", "MAINTAIN_ORG_USER"]) ? -
    • - Client -
        - { - isGrantedAny(["VIEW_USER","MAINTAIN_USER"]) ? - <> -
      • - Users (GLD) -
      • -
      • - Users (Individual) -
      • -
      • - Users (Organisation) -
      • - - : - <> - { - isGrantedAny(["VIEW_GLD_USER" ,"MAINTAIN_GLD_USER"]) ? -
      • - Users (GLD) -
      • : <> - } - { - isGrantedAny(["VIEW_IND_USER", "MAINTAIN_IND_USER"]) ? -
      • - Users (Individual) -
      • : <> - } - { - isGrantedAny(["VIEW_ORG_USER", "MAINTAIN_ORG_USER"]) ? -
      • - Users (Organisation) -
      • : <> - } - - } - { - isGrantedAny(["VIEW_ORG", "MAINTAIN_ORG"]) ? -
      • - Organisation -
      • - : - <> - } - { - isGrantedAny(["VIEW_GROUP", "MAINTAIN_GROUP"]) ? -
      • - User Group -
      • - : - <> - } - -
      -
    • - : - <> - } -
    • - Report -
        -
      • - - - Summary of Gazette Notice - - -
      • -
      • - - - Gazette Notice Full List - - -
      • -
      -
    • -
    • - Settings -
        -
      • - My Profile -
      • -
      • - - - - - -
      • - { - isGranted("VIEW_GAZETTE_ISSUE", "MAINTAIN_GAZETTE_ISSUE") ? - <> -
      • - Holiday Settings -
      • -
      • - Gazette Issues -
      • - - : - <> - } + {isGranted("MAINTAIN_PAYMENT") ? ( +
      • + + + Mark Payment + + +
      • + ) : null} - { - isGranted("MAINTAIN_ANNOUNCEMENT") ? -
      • - Announcement -
      • - : - <> - } + {isGranted("MAINTAIN_PAYMENT") ? ( +
      • + + + Online Payment Record + + +
      • + ) : null} - {isGranted("MAINTAIN_EMAIL") ? + {isGranted("MAINTAIN_RECON") ? (
      • - Email Template + + + GFMIS Generate XML + +
      • - : - <> - } + ) : null} - { - isGranted("MAINTAIN_DR") ? -
      • - DR Import -
      • - : - <> - } + {isGranted("MAINTAIN_DEMANDNOTE") ? ( +
      • + + + Create Demand Note + + +
      • + ) : null} - { - isGranted("MAINTAIN_SETTING") ? -
      • - System Settings -
      • - : - <> - } - { - isGranted("MAINTAIN_SETTING") ? -
      • - Audit Log -
      • - : - <> - } -
      -
    • - -
    • - Logout -
    • -
      -
    - } -
    - : -
    - {isPasswordExpiry() ? -
    -
  • - - - - - -
  • -
    - : -
    -
  • - - - -
  • -
  • - - - -
  • -
  • - - - -
  • -
  • - {isCreditorLoggedIn() ? - haveOrgPaymentRecord() ? - <> - + {isGrantedAny(["VIEW_DEMANDNOTE", "MAINTAIN_DEMANDNOTE"]) ? ( +
  • + - + Demand Note - -
      -
    • - - - -
    • -
    • - - - -
    • -
    - - : - - - - : - isORGLoggedIn() ? - haveOrgPaymentRecord() ? - <> - - - - - - -
      -
    • - - - -
    • - {haveOrgDnRecord()? -
    • - - - -
    • :null - } -
    - - : - - - - : - <> - +
  • + ) : null} + + {isGranted("MAINTAIN_RECON") ? ( +
  • + - + Recon Report - -
      -
    • - - - -
    • - {haveOrgDnRecord()? -
    • - - - -
    • :null - } -
    - - } +
  • + ) : null} + + ) : null} + + {/* ===== Client dropdown (admin) ===== */} + {isGrantedAny([ + "VIEW_USER", + "MAINTAIN_USER", + "VIEW_ORG", + "MAINTAIN_ORG", + "VIEW_GROUP", + "MAINTAIN_GROUP", + "VIEW_GLD_USER", + "VIEW_IND_USER", + "VIEW_ORG_USER", + "MAINTAIN_GLD_USER", + "MAINTAIN_IND_USER", + "MAINTAIN_ORG_USER", + ]) ? (
  • + - {isPrimaryLoggedIn() ? - <> - - console.log(event)}> - - - - -
      -
    • - - - - - -
    • +
        + {isGrantedAny(["VIEW_USER", "MAINTAIN_USER"]) ? ( + <>
      • - - - {/* */} - + + + Users (GLD)
      • - - - {/* */} - + + + Users (Individual)
      • - - - + + + Users (Organisation)
      • -
      - - : - isINDLoggedIn() ? + + ) : ( <> - - console.log(event)}> - - - - -
        -
      • - - - {/* */} - - - -
      • + {isGrantedAny(["VIEW_GLD_USER", "MAINTAIN_GLD_USER"]) ? (
      • - - - + + + Users (GLD)
      • -
      - - : - <> - - console.log(event)}> - - - - -
        + ) : null} + {isGrantedAny(["VIEW_IND_USER", "MAINTAIN_IND_USER"]) ? (
      • - - - + + + Users (Individual)
      • + ) : null} + {isGrantedAny(["VIEW_ORG_USER", "MAINTAIN_ORG_USER"]) ? (
      • - - - + + + Users (Organisation)
      • -
      + ) : null} - } + )} + + {isGrantedAny(["VIEW_ORG", "MAINTAIN_ORG"]) ? ( +
    • + + + Organisation + + +
    • + ) : null} + + {isGrantedAny(["VIEW_GROUP", "MAINTAIN_GROUP"]) ? ( +
    • + + + User Group + + +
    • + ) : null} +
  • -
    - } - + ) : null} + + {/* ===== Report dropdown (admin) ===== */}
  • - - - + + +
      +
    • + + + Summary of Gazette Notice + + +
    • +
    • + + + Gazette Notice Full List + + +
    • +
  • -
    -
    + + {/* ===== Settings dropdown (admin) ===== */} +
  • + + +
      +
    • + + + My Profile + + +
    • + +
    • + + + + + +
    • + + {isGranted("VIEW_GAZETTE_ISSUE", "MAINTAIN_GAZETTE_ISSUE") ? ( + <> +
    • + + + Holiday Settings + + +
    • +
    • + + + Gazette Issues + + +
    • + + ) : null} + + {isGranted("MAINTAIN_ANNOUNCEMENT") ? ( +
    • + + + Announcement + + +
    • + ) : null} + + {isGranted("MAINTAIN_EMAIL") ? ( +
    • + + + Email Template + + +
    • + ) : null} + + {isGranted("MAINTAIN_DR") ? ( +
    • + + + DR Import + + +
    • + ) : null} + + {isGranted("MAINTAIN_SETTING") ? ( + <> +
    • + + + System Settings + + +
    • +
    • + + + Audit Log + + +
    • + + ) : null} +
    +
  • + + +
  • + + + Logout + + +
  • +
    +
    + )} +
    + ) : ( + // ===== Non-GLD login content (your original logic, only dropdown triggers changed to + +
      +
    • + + + + + +
    • + + {(isCreditorLoggedIn() && haveOrgPaymentRecord()) || + (isORGLoggedIn() && haveOrgPaymentRecord() && haveOrgDnRecord()) || + (!isCreditorLoggedIn() && !isORGLoggedIn() && haveOrgDnRecord()) ? ( +
    • + + + + + +
    • + ) : null} +
    + + ) : ( + + + + + + )} + + + {/* ===== User Setting dropdown (non-admin) ===== */} +
  • + + +
      + {isPrimaryLoggedIn() ? ( + <> +
    • + + + + + +
    • +
    • + + + + + +
    • +
    • + + + + + +
    • + + ) : isINDLoggedIn() ? ( +
    • + + + + + +
    • + ) : ( +
    • + + + + + +
    • + )} + +
    • + + + + + +
    • +
    +
  • + + )} + + +
  • + + + + + +
  • +
    + ); + // ============================= + // Logged-out top nav content + // ============================= const logoutContent = (
  • {!isAfterAboutSwitchDate() ? ( - // On or before 28/1/2026 ) : ( - // After 28/1/2026 - + @@ -631,297 +741,258 @@ function Header(props) {
  • - +
  • +
  • - +
  • - { - sysSetting?.allowRegistration ? -
  • - - - - - -
  • - : - <> - } + + {sysSetting?.allowRegistration ? ( +
  • + + + + + +
  • + ) : null}
    ); - const drawer = ( - isUserLoggedIn() ? - - {/* + const drawer = isUserLoggedIn() ? ( + + + + PNSPS - */} - - - PNSPS - - -
      - {loginContent} -
    - -
      -
    • - - - -
    • -
    -
    - : - - - - PNSPS - - -
      - {logoutContent} -
    - -
    + + + + + +
      +
    • + + + +
    • +
    + + ) : ( + + + + + PNSPS + + + +
      {logoutContent}
    + +
    ); const container = window !== undefined ? () => window().document.body : undefined; - return ( - isUserLoggedIn() ? - // User Login success - - - - {isGLDLoggedIn() - ? - - - - - - - - - - - PNSPS - - - RESTRICTED - - - - : - + + + {isGLDLoggedIn() ? ( + + + + + - - - - - {/*公共啟事提交*/} - {/*及繳費系統*/} - - - - + + + + + + + + PNSPS + - - - - - - - - - - - - - - - - + + RESTRICTED - - - } - - - - - - - - { - isGLDLoggedIn() ? - - RESTRICTED - - : - - - - } - {/**/} - - - - - - - - - {drawer} - - - : - - - - - - + + + + ) : ( + + + - + + - + - + - + - - - - - + )} + + + + - {/**/} + + + {isGLDLoggedIn() ? ( + + RESTRICTED + + ) : ( + + + + )} + + + + + + + + + + {drawer} + + + + ) : ( + + + + + + + + + + + + - - - - - {drawer} - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {drawer} + + ); } + Header.propTypes = { - /** - * Injected by the documentation to work in an iframe. - * You won't need it on your project. - */ window: PropTypes.func, }; -export default Header; + +export default Header; \ No newline at end of file diff --git a/src/pages/Announcement/Search_Public/index.js b/src/pages/Announcement/Search_Public/index.js index 46ca7c1..b282c39 100644 --- a/src/pages/Announcement/Search_Public/index.js +++ b/src/pages/Announcement/Search_Public/index.js @@ -15,6 +15,7 @@ const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' import { FormattedMessage } from "react-intl"; import { getSearchCriteria } from "auth/utils"; +import usePageTitle from "components/usePageTitle"; const BackgroundHead = { backgroundImage: `url(${titleBackgroundImg})`, @@ -29,7 +30,7 @@ const BackgroundHead = { // ==============================|| DASHBOARD - DEFAULT ||============================== // const UserSearchPage_Individual = () => { - + usePageTitle("announcement"); const [searchCriteria, setSearchCriteria] = React.useState({}); const [onReady, setOnReady] = React.useState(false); const [onGridReady, setGridOnReady] = React.useState(false); diff --git a/src/pages/DemandNote/Search_Public/index.js b/src/pages/DemandNote/Search_Public/index.js index 7abee35..8f174a1 100644 --- a/src/pages/DemandNote/Search_Public/index.js +++ b/src/pages/DemandNote/Search_Public/index.js @@ -18,6 +18,7 @@ const SearchForm = Loadable(React.lazy(() => import('./SearchForm'))); const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' import {FormattedMessage} from "react-intl"; +import usePageTitle from "components/usePageTitle"; const BackgroundHead = { backgroundImage: `url(${titleBackgroundImg})`, @@ -32,7 +33,7 @@ const BackgroundHead = { // ==============================|| DASHBOARD - DEFAULT ||============================== // const SearchPage_DemandNote_Pub = () => { - + usePageTitle("paymentInfoRecord"); const [orgCombo, setOrgCombo] = React.useState([]); const [issueCombo, setIssueCombo] = React.useState([]); const [searchCriteria, setSearchCriteria] = React.useState({}); diff --git a/src/pages/Message/Details/index.js b/src/pages/Message/Details/index.js index e7eadeb..117c9a2 100644 --- a/src/pages/Message/Details/index.js +++ b/src/pages/Message/Details/index.js @@ -17,6 +17,8 @@ const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/Loa import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' import { FormattedMessage } from "react-intl"; +import usePageTitle from 'components/usePageTitle'; + const BackgroundHead = { backgroundImage: `url(${titleBackgroundImg})`, width: '100%', @@ -30,6 +32,8 @@ const BackgroundHead = { // ==============================|| DASHBOARD - DEFAULT ||============================== // const Index = () => { + usePageTitle("msgDetails"); + const params = useParams(); const navigate = useNavigate() diff --git a/src/pages/Message/Search/index.js b/src/pages/Message/Search/index.js index 5745347..c994f28 100644 --- a/src/pages/Message/Search/index.js +++ b/src/pages/Message/Search/index.js @@ -17,7 +17,7 @@ const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' import {FormattedMessage} from "react-intl"; import { getSearchCriteria } from "auth/utils"; - +import usePageTitle from "components/usePageTitle"; const BackgroundHead = { backgroundImage: `url(${titleBackgroundImg})`, width: '100%', @@ -31,6 +31,7 @@ const BackgroundHead = { // ==============================|| DASHBOARD - DEFAULT ||============================== // const Index = () => { + usePageTitle("systemMessage"); const [searchCriteria, setSearchCriteria] = React.useState({}); const [onReady, setOnReady] = React.useState(false); diff --git a/src/pages/Payment/Details_Public/index.js b/src/pages/Payment/Details_Public/index.js index c611f7b..cc62f7e 100644 --- a/src/pages/Payment/Details_Public/index.js +++ b/src/pages/Payment/Details_Public/index.js @@ -20,6 +20,7 @@ const DataGrid = Loadable(React.lazy(() => import('./DataGrid'))); import ForwardIcon from '@mui/icons-material/Forward'; import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' import {FormattedMessage,useIntl} from "react-intl"; +import usePageTitle from "components/usePageTitle"; const BackgroundHead = { backgroundImage: `url(${titleBackgroundImg})`, width: '100%', @@ -33,6 +34,8 @@ const BackgroundHead = { // ==============================|| DASHBOARD - DEFAULT ||============================== // const Index = () => { + usePageTitle("payDetail"); + const params = useParams(); const navigate = useNavigate() const intl = useIntl(); diff --git a/src/pages/Payment/Search_Public/index.js b/src/pages/Payment/Search_Public/index.js index d426395..278e445 100644 --- a/src/pages/Payment/Search_Public/index.js +++ b/src/pages/Payment/Search_Public/index.js @@ -15,6 +15,7 @@ const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' import {FormattedMessage} from "react-intl"; import { getSearchCriteria } from "auth/utils"; +import usePageTitle from "components/usePageTitle"; const BackgroundHead = { backgroundImage: `url(${titleBackgroundImg})`, @@ -29,6 +30,7 @@ const BackgroundHead = { // ==============================|| DASHBOARD - DEFAULT ||============================== // const Index = () => { + usePageTitle("onlinePaymentHistory"); const [searchCriteria, setSearchCriteria] = React.useState({ dateTo: DateUtils.dateValue(new Date()), dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), diff --git a/src/pages/Proof/Reply_Public/index.js b/src/pages/Proof/Reply_Public/index.js index ee00046..80a80d2 100644 --- a/src/pages/Proof/Reply_Public/index.js +++ b/src/pages/Proof/Reply_Public/index.js @@ -21,6 +21,7 @@ const ProofForm = Loadable(React.lazy(() => import('./ProofForm'))); import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' import MainCard from "../../../components/MainCard"; import {FormattedMessage} from "react-intl"; +import usePageTitle from "components/usePageTitle"; const BackgroundHead = { backgroundImage: `url(${titleBackgroundImg})`, width: '100%', @@ -35,6 +36,8 @@ import {useIntl} from "react-intl"; // ==============================|| DASHBOARD - DEFAULT ||============================== // const Index = () => { + usePageTitle("proofRecord"); + const params = useParams(); const navigate = useNavigate() const intl = useIntl(); diff --git a/src/pages/Proof/Search_Public/index.js b/src/pages/Proof/Search_Public/index.js index d7348cb..3f5f85e 100644 --- a/src/pages/Proof/Search_Public/index.js +++ b/src/pages/Proof/Search_Public/index.js @@ -17,6 +17,7 @@ const SearchForm = Loadable(React.lazy(() => import('./SearchForm'))); const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' import {FormattedMessage} from "react-intl"; +import usePageTitle from "components/usePageTitle"; const BackgroundHead = { backgroundImage: `url(${titleBackgroundImg})`, @@ -31,7 +32,8 @@ const BackgroundHead = { // ==============================|| DASHBOARD - DEFAULT ||============================== // const UserSearchPage_Individual = () => { - + usePageTitle("proofRecord"); + const [issueCombo,setIssueCombo] = React.useState([]); const [searchCriteria, setSearchCriteria] = React.useState({ dateTo: DateUtils.dateValue(new Date()), diff --git a/src/pages/PublicNotice/ApplyForm/PublicNoticeApplyForm.js b/src/pages/PublicNotice/ApplyForm/PublicNoticeApplyForm.js index 5574dae..41745f3 100644 --- a/src/pages/PublicNotice/ApplyForm/PublicNoticeApplyForm.js +++ b/src/pages/PublicNotice/ApplyForm/PublicNoticeApplyForm.js @@ -545,6 +545,7 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => label: intl.formatMessage({ id: 'careOf' }) + ":", valueName: "careOf", form: formik, + inputProps: { "aria-label": intl.formatMessage({ id: 'careOf' }) } // disabled: true })} @@ -572,7 +573,7 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => label: intl.formatMessage({ id: 'extraMark' }) + ":", valueName: "remarks", form: formik, - inputProps: { maxLength: 255 } + inputProps: { maxLength: 255, "aria-label": intl.formatMessage({ id: 'extraMark' }) } })} } @@ -601,6 +602,9 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => name="tickAccept" color="primary" size="small" + inputProps={{ + "aria-label": intl.formatMessage({ id: "applyTickStr" }) + }} />
    @@ -629,9 +633,6 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => - - - ) : null} diff --git a/src/pages/PublicNotice/ApplyForm/index.js b/src/pages/PublicNotice/ApplyForm/index.js index ddb1692..fa59aed 100644 --- a/src/pages/PublicNotice/ApplyForm/index.js +++ b/src/pages/PublicNotice/ApplyForm/index.js @@ -16,7 +16,7 @@ import Loadable from 'components/Loadable'; import { lazy } from 'react'; const LoadingComponent = Loadable(lazy(() => import('../../extra-pages/LoadingComponent'))); const PublicNoticeApplyForm = Loadable(lazy(() => import('./PublicNoticeApplyForm'))); - +import usePageTitle from "components/usePageTitle"; import { // isORGLoggedIn, isDummyLoggedIn, @@ -27,6 +27,8 @@ import { // ==============================|| DASHBOARD - DEFAULT ||============================== // const ApplyForm = () => { + usePageTitle("applyPublicNotice"); + const [userData, setUserData] = React.useState(null); const [gazetteIssueList, setGazetteIssueList] = React.useState([]); diff --git a/src/pages/PublicNotice/Details_Public/index.js b/src/pages/PublicNotice/Details_Public/index.js index e73704f..831b940 100644 --- a/src/pages/PublicNotice/Details_Public/index.js +++ b/src/pages/PublicNotice/Details_Public/index.js @@ -32,10 +32,12 @@ import { useNavigate } from "react-router-dom"; import ForwardIcon from '@mui/icons-material/Forward'; import { notifyActionSuccess } from "utils/CommonFunction"; import { FormattedMessage, useIntl } from "react-intl"; - +import usePageTitle from "components/usePageTitle"; // ==============================|| Body - DEFAULT ||============================== // const DashboardDefault = () => { + usePageTitle("myPublicNotice"); + const params = useParams(); const [applicationDetailData, setApplicationDetailData] = useState({}); const [appNo, setAapNo] = useState(""); diff --git a/src/pages/PublicNotice/ListPanel/index.js b/src/pages/PublicNotice/ListPanel/index.js index 0c38de9..192ff9c 100644 --- a/src/pages/PublicNotice/ListPanel/index.js +++ b/src/pages/PublicNotice/ListPanel/index.js @@ -31,11 +31,12 @@ import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' import { PNSPS_LONG_BUTTON_THEME } from "../../../themes/buttonConst"; import { ThemeProvider } from "@emotion/react"; import { FormattedMessage, useIntl } from "react-intl"; - - +import usePageTitle from 'components/usePageTitle'; // ==============================|| DASHBOARD - DEFAULT ||============================== // const PublicNotice = () => { + usePageTitle("myPublicNotice"); + const [submittedCount, setSubmittedCount] = useState(0); const [pendingPaymentCount, setPendingPaymentCount] = useState(0); const [pendingPublishCount, setPendingPublishCount] = useState(0); diff --git a/src/pages/User/ChangePasswordPage/index.js b/src/pages/User/ChangePasswordPage/index.js index 4e8cea4..978895a 100644 --- a/src/pages/User/ChangePasswordPage/index.js +++ b/src/pages/User/ChangePasswordPage/index.js @@ -34,9 +34,12 @@ import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; import { useDispatch } from "react-redux"; import { handleLogoutFunction} from 'auth/index'; import { isPasswordExpiry } from "utils/Utils"; +import usePageTitle from "components/usePageTitle"; // ==============================|| DASHBOARD - DEFAULT ||============================== // const Index = () => { + usePageTitle("userChangePassword"); + const dispatch = useDispatch() const navigate = useNavigate() const [showPassword, setShowPassword] = React.useState(false); diff --git a/src/pages/User/DetailsPage_Individual/index.js b/src/pages/User/DetailsPage_Individual/index.js index c0fe119..bdcb18f 100644 --- a/src/pages/User/DetailsPage_Individual/index.js +++ b/src/pages/User/DetailsPage_Individual/index.js @@ -36,10 +36,12 @@ import { isORGLoggedIn } from "utils/Utils"; import {FormattedMessage, useIntl} from "react-intl"; +import usePageTitle from "components/usePageTitle"; // ==============================|| DASHBOARD - DEFAULT ||============================== // const UserMaintainPage_Individual = () => { + usePageTitle("userProfile"); const intl = useIntl(); const params = useParams(); diff --git a/src/pages/User/DetailsPage_Organization/index.js b/src/pages/User/DetailsPage_Organization/index.js index c7cf53b..9551c44 100644 --- a/src/pages/User/DetailsPage_Organization/index.js +++ b/src/pages/User/DetailsPage_Organization/index.js @@ -40,11 +40,13 @@ import { isINDLoggedIn } from "utils/Utils"; import {FormattedMessage, useIntl} from "react-intl"; - +import usePageTitle from "components/usePageTitle"; // ==============================|| DASHBOARD - DEFAULT ||============================== // const UserMaintainPage_Organization = () => { + usePageTitle("userProfile"); + const params = useParams(); const navigate = useNavigate(); const [userData, setUserData] = useState({}) @@ -121,7 +123,7 @@ const UserMaintainPage_Organization = () => { // window.location.reload(false); // } - const loadData = () => { + const loadData = () => { const reqId = ++reqIdRef.current; setLoding(true); if (isGLDLoggedIn()){ diff --git a/src/pages/authentication/ForgotPassword/AuthCallback/index.js b/src/pages/authentication/ForgotPassword/AuthCallback/index.js index 43a7d26..0d6406c 100644 --- a/src/pages/authentication/ForgotPassword/AuthCallback/index.js +++ b/src/pages/authentication/ForgotPassword/AuthCallback/index.js @@ -343,6 +343,7 @@ const Index = () => { aria-label={intl.formatMessage({ id: showConfirmPassword ? "ariaHidePassword" : "ariaShowPassword" })} + aria-describedby="helper-text-confirmPassword-signup" onClick={handleClickShowConfirmPassword} onMouseDown={handleMouseDownPassword} edge="end" diff --git a/src/pages/authentication/ForgotPassword/ForgotPasswordApplyForm.js b/src/pages/authentication/ForgotPassword/ForgotPasswordApplyForm.js index a350ff4..c75b704 100644 --- a/src/pages/authentication/ForgotPassword/ForgotPasswordApplyForm.js +++ b/src/pages/authentication/ForgotPassword/ForgotPasswordApplyForm.js @@ -150,6 +150,8 @@ const ForgotPasswordApplyForm = () => { onBlur={formik.handleBlur} autoFocus inputProps={{ + "aria-label": intl.formatMessage({id: 'userLoginName'}), + "aria-describedby" : 'standard-weight-helper-text-username-login', maxLength: 50, onKeyDown: (e) => { if (e.key === 'Enter') { diff --git a/src/pages/authentication/ForgotUsername/AuthCallback/index.js b/src/pages/authentication/ForgotUsername/AuthCallback/index.js index b7bf798..e33b162 100644 --- a/src/pages/authentication/ForgotUsername/AuthCallback/index.js +++ b/src/pages/authentication/ForgotUsername/AuthCallback/index.js @@ -406,6 +406,7 @@ const Index = () => { aria-label={intl.formatMessage({ id: showConfirmPassword ? "ariaHidePassword" : "ariaShowPassword" })} + aria-describedby="helper-text-confirmPassword-signup" onClick={handleClickShowConfirmPassword} onMouseDown={handleMouseDownPassword} edge="end" diff --git a/src/pages/authentication/ForgotUsername/ForgotUsernameApplyForm.js b/src/pages/authentication/ForgotUsername/ForgotUsernameApplyForm.js index 524e700..e67e0d4 100644 --- a/src/pages/authentication/ForgotUsername/ForgotUsernameApplyForm.js +++ b/src/pages/authentication/ForgotUsername/ForgotUsernameApplyForm.js @@ -149,6 +149,8 @@ const ForgotUsernameApplyForm = () => { error={Boolean(formik.touched.emailAddress && formik.errors.emailAddress)} onBlur={formik.handleBlur} inputProps={{ + "aria-describedby" : 'standard-weight-helper-text-emailAddress-login', + "aria-label": intl.formatMessage({id: 'userContactEmail'}), maxLength: 50, onKeyDown: (e) => { if (e.key === 'Enter') { diff --git a/src/pages/authentication/auth-forms/AuthLoginCustom.js b/src/pages/authentication/auth-forms/AuthLoginCustom.js index 38970c2..cd5aae3 100644 --- a/src/pages/authentication/auth-forms/AuthLoginCustom.js +++ b/src/pages/authentication/auth-forms/AuthLoginCustom.js @@ -291,6 +291,7 @@ const AuthLoginCustom = () => { error={Boolean(formik.touched.username && formik.errors.username)} onBlur={formik.handleBlur} inputProps={{ + "aria-describedby": 'standard-weight-helper-text-username-login', maxLength: 50, onKeyDown: (e) => { if (e.key === 'Enter') { @@ -336,6 +337,9 @@ const AuthLoginCustom = () => { } placeholder="" + inputProps={{ + "aria-describedby": 'standard-weight-helper-text-password-login', + }} /> {formik.touched.password && formik.errors.password && ( diff --git a/src/pages/authentication/auth-forms/BusCustomFormWizard.js b/src/pages/authentication/auth-forms/BusCustomFormWizard.js index 8f26137..28a3303 100644 --- a/src/pages/authentication/auth-forms/BusCustomFormWizard.js +++ b/src/pages/authentication/auth-forms/BusCustomFormWizard.js @@ -687,7 +687,7 @@ const BusCustomFormWizard = (props) => { { error={Boolean((formik.touched.username && formik.errors.username) || checkUsername)} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "userLoginName" }), + "aria-describedby": 'helper-text-username-login', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -762,6 +764,8 @@ const BusCustomFormWizard = (props) => { placeholder={intl.formatMessage({id: 'userPassword'})} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "userPassword" }), + "aria-describedby": 'helper-text-password-signup', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -797,7 +801,7 @@ const BusCustomFormWizard = (props) => { { // changePassword(e.target.value); }} inputProps={{ + "aria-label": intl.formatMessage({ id: "confirmPassword" }), + "aria-describedby": 'helper-text-confirmPassword-signup', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -879,7 +885,7 @@ const BusCustomFormWizard = (props) => { { error={Boolean(formik.touched.enCompanyName && formik.errors.enCompanyName && selectedAddress5 !== "內地")} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "businessEngName" }), + "aria-describedby": 'helper-text-enCompanyName-signup', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -921,6 +929,8 @@ const BusCustomFormWizard = (props) => { placeholder={intl.formatMessage({id: 'sameAsBusinessRegistrationCert'})} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "businessChName" }), + "aria-describedby": 'helper-text-chCompanyName-signup', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -954,6 +964,8 @@ const BusCustomFormWizard = (props) => { onBlur={formik.handleBlur} placeholder={intl.formatMessage({id: 'sameAsBusinessRegistrationCert'})} inputProps={{ + "aria-label": intl.formatMessage({ id: "businessRegCertNumber" }), + "aria-describedby": 'helper-text-brNo-signup', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -987,6 +999,8 @@ const BusCustomFormWizard = (props) => { onBlur={formik.handleBlur} placeholder={intl.formatMessage({id: 'sameAsBusinessRegistrationCert'})} inputProps={{ + "aria-label": intl.formatMessage({ id: "businessRegCertExpiryDate" }), + "aria-describedby": 'helper-text-brExpiryDate-signup', max: "2099-12-31", min: new Date().toISOString().split("T")[0], onKeyDown: (e) => { @@ -1021,6 +1035,7 @@ const BusCustomFormWizard = (props) => { placeholder={intl.formatMessage({id: 'addressLine1'})} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "addressLine1" }), onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1038,6 +1053,7 @@ const BusCustomFormWizard = (props) => { onChange={formik.handleChange} placeholder={intl.formatMessage({id: 'addressLine2'})} inputProps={{ + "aria-label": intl.formatMessage({ id: "addressLine2" }), onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1055,6 +1071,7 @@ const BusCustomFormWizard = (props) => { onChange={formik.handleChange} placeholder={intl.formatMessage({id: 'addressLine3'})} inputProps={{ + "aria-label": intl.formatMessage({ id: "addressLine3" }), onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1177,7 +1194,7 @@ const BusCustomFormWizard = (props) => { { error={Boolean(formik.touched.enName && formik.errors.enName)} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "userContactName" }), + "aria-describedby": 'helper-text-enName-signup', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1214,7 +1233,7 @@ const BusCustomFormWizard = (props) => { { placeholder={intl.formatMessage({id: 'userContactEmail'})} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "userContactEmail" }), + "aria-describedby": 'helper-text-email-signup', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1252,7 +1273,7 @@ const BusCustomFormWizard = (props) => { { onCopy={handleCCPChange} onPaste={handleCCPChange} inputProps={{ + "aria-label": intl.formatMessage({ id: "confirmEmail" }), + "aria-describedby": 'helper-text-emailConfirm-signup', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1294,7 +1317,7 @@ const BusCustomFormWizard = (props) => { { error={Boolean(formik.touched.phone && formik.errors.phone)} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "phoneCountryCode" }), maxLength: 3, onKeyDown: (e) => { if (e.key === 'Enter') { @@ -1322,7 +1346,7 @@ const BusCustomFormWizard = (props) => { sx={{ width: '33%', mr:1 }} /> { placeholder={intl.formatMessage({id: 'userContactNumber'})} error={Boolean(formik.touched.phone && formik.errors.phone)} inputProps={{ + "aria-label": intl.formatMessage({ id: "userContactNumber" }), + "aria-describedby": 'helper-text-phone-signup', maxLength: 11, onKeyDown: (e) => { if (e.key === 'Enter') { @@ -1369,7 +1395,7 @@ const BusCustomFormWizard = (props) => { { placeholder={intl.formatMessage({id: 'dialingCode'})} endAdornment={-} inputProps={{ + "aria-label": intl.formatMessage({ id: "faxCountryCode" }), maxLength: 3, onKeyDown: (e) => { if (e.key === 'Enter') { @@ -1395,7 +1422,7 @@ const BusCustomFormWizard = (props) => { sx={{ width: '33%', mr:1 }} /> { }} placeholder={intl.formatMessage({id: 'userFaxNumber'})} inputProps={{ + "aria-label": intl.formatMessage({ id: "userFaxNumber" }), maxLength: 8, onKeyDown: (e) => { if (e.key === 'Enter') { @@ -1493,6 +1521,9 @@ const BusCustomFormWizard = (props) => { name="termsAndConAccept" color="primary" size="small" + inputProps={{ + "aria-label": intl.formatMessage({ id: "iConfirm" }) + }} /> @@ -1509,6 +1540,9 @@ const BusCustomFormWizard = (props) => { name="termsAndConNotAccept" color="primary" size="small" + inputProps={{ + "aria-label": intl.formatMessage({ id: "rejectTerms" }) + }} /> @@ -1553,6 +1587,10 @@ const BusCustomFormWizard = (props) => { formik.setFieldValue("captchaField", value); }} sx={{ width: '75%' }} + inputProps={{ + "aria-label": intl.formatMessage({ id: "verify" }), + "aria-describedby": 'helper-text-captcha-signup' + }} /> diff --git a/src/pages/authentication/auth-forms/CustomFormWizard.js b/src/pages/authentication/auth-forms/CustomFormWizard.js index f84767c..f423115 100644 --- a/src/pages/authentication/auth-forms/CustomFormWizard.js +++ b/src/pages/authentication/auth-forms/CustomFormWizard.js @@ -1,7 +1,5 @@ import { useEffect, useState } from 'react'; - -// material-ui import { Box, Button, Checkbox @@ -918,7 +916,7 @@ const CustomFormWizard = (props) => { { error={Boolean((formik.touched.username && formik.errors.username) || checkUsername)} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "userLoginName" }), + "aria-describedby": 'helper-text-username-signup', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -993,6 +993,8 @@ const CustomFormWizard = (props) => { placeholder={intl.formatMessage({ id: 'userPassword' })} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "userPassword" }), + "aria-describedby": 'helper-text-password-signup', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1028,7 +1030,7 @@ const CustomFormWizard = (props) => { { // changePassword(e.target.value); }} inputProps={{ + "aria-label": intl.formatMessage({ id: "confirmPassword" }), + "aria-describedby": 'helper-text-confirmPassword-signup', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1119,6 +1123,7 @@ const CustomFormWizard = (props) => { { error={Boolean(formik.touched.idNo && formik.errors.idNo || checkIdDocNumber)} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "idDocNumber" }), + "aria-describedby": 'helper-text-idNo-signup', maxLength: selectedIdDocType.type === 'HKID' ? 8 : 18, onKeyDown: (e) => { // console.log(e) @@ -1280,6 +1287,8 @@ const CustomFormWizard = (props) => { error={Boolean(formik.touched.idNo && formik.errors.idNo || checkIdDocNumber)} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "idDocNumber" }), + "aria-describedby": 'helper-text-idNo-signup', maxLength: 18, onKeyDown: (e) => { if (e.key === 'Enter') { @@ -1317,7 +1326,7 @@ const CustomFormWizard = (props) => { { error={Boolean(formik.touched.enName && formik.errors.enName && selectedIdDocType.type !== "CNID")} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "userEnglishName" }), + "aria-describedby": 'helper-text-enName-signup', onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1360,6 +1371,8 @@ const CustomFormWizard = (props) => { placeholder={intl.formatMessage({ id: 'sameAsYourIdDoc' })} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "userChineseName" }), + "aria-describedby": 'helper-text-chName-signup', maxLength: 6, onKeyDown: (e) => { if (e.key === 'Enter') { @@ -1393,6 +1406,7 @@ const CustomFormWizard = (props) => { placeholder={intl.formatMessage({ id: 'addressLine1' })} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "addressLine1" }), onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1410,6 +1424,7 @@ const CustomFormWizard = (props) => { onBlur={formik.handleBlur} placeholder={intl.formatMessage({ id: 'addressLine2' })} inputProps={{ + "aria-label": intl.formatMessage({ id: "addressLine2" }), onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1427,6 +1442,7 @@ const CustomFormWizard = (props) => { onBlur={formik.handleBlur} placeholder={intl.formatMessage({ id: 'addressLine3' })} inputProps={{ + "aria-label": intl.formatMessage({ id: "addressLine3" }), onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1554,7 +1570,7 @@ const CustomFormWizard = (props) => { { placeholder={intl.formatMessage({ id: 'userContactEmail' })} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "userContactEmail" }), + "aria-describedby": "helper-text-email-signup", onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1592,7 +1610,7 @@ const CustomFormWizard = (props) => { { onCopy={handleCCPChange} onPaste={handleCCPChange} inputProps={{ + "aria-label": intl.formatMessage({ id: "confirmEmail" }), + "aria-describedby": "helper-text-emailConfirm-signup", onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -1634,7 +1654,7 @@ const CustomFormWizard = (props) => { { onBlur={formik.handleBlur} endAdornment={-} inputProps={{ + "aria-label": intl.formatMessage({ id: "dialingCode" }), maxLength: 3, onKeyDown: (e) => { if (e.key === 'Enter') { @@ -1662,7 +1683,7 @@ const CustomFormWizard = (props) => { sx={{ width: '33%', mr: 1 }} /> { error={Boolean(formik.touched.phone && formik.errors.phone)} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "userContactNumber" }), + "aria-describedby": 'helper-text-phone-signup', maxLength: 11, onKeyDown: (e) => { if (e.key === 'Enter') { @@ -1710,7 +1733,7 @@ const CustomFormWizard = (props) => { { onBlur={formik.handleBlur} endAdornment={-} inputProps={{ + "aria-label": intl.formatMessage({ id: "faxCountryCode" }), maxLength: 3, onKeyDown: (e) => { if (e.key === 'Enter') { @@ -1736,7 +1760,7 @@ const CustomFormWizard = (props) => { sx={{ width: '33%', mr: 1 }} /> { }} placeholder={intl.formatMessage({ id: 'userFaxNumber' })} inputProps={{ + "aria-label": intl.formatMessage({ id: "userFaxNumber" }), maxLength: 8, onKeyDown: (e) => { if (e.key === 'Enter') { @@ -1835,6 +1860,9 @@ const CustomFormWizard = (props) => { name="termsAndConAccept" color="primary" size="small" + inputProps={{ + "aria-label": intl.formatMessage({ id: "iConfirm" }) + }} /> @@ -1851,6 +1879,9 @@ const CustomFormWizard = (props) => { name="termsAndConNotAccept" color="primary" size="small" + inputProps={{ + "aria-label": intl.formatMessage({ id: "rejectTerms" }) + }} /> @@ -1895,6 +1926,10 @@ const CustomFormWizard = (props) => { formik.setFieldValue("captchaField", value); }} sx={{ width: '75%' }} + inputProps={{ + "aria-label": intl.formatMessage({ id: "verify" }), + "aria-describedby": 'helper-text-captcha-signup' + }} /> diff --git a/src/pages/authentication/auth-forms/IAmSmartFormWizard.js b/src/pages/authentication/auth-forms/IAmSmartFormWizard.js index 105c644..d0a9a14 100644 --- a/src/pages/authentication/auth-forms/IAmSmartFormWizard.js +++ b/src/pages/authentication/auth-forms/IAmSmartFormWizard.js @@ -746,6 +746,8 @@ const CustomFormWizard = (props) => { placeholder={intl.formatMessage({ id: 'userContactEmail' })} onBlur={formik.handleBlur} inputProps={{ + "aria-describedby": "helper-text-email-signup", + "aria-label": intl.formatMessage({ id: "userContactEmail" }), onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -792,6 +794,8 @@ const CustomFormWizard = (props) => { onCopy={handleCCPChange} onPaste={handleCCPChange} inputProps={{ + "aria-label": intl.formatMessage({ id: "confirmEmail" }), + "aria-describedby": "helper-text-emailConfirm-signup", onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -870,6 +874,8 @@ const CustomFormWizard = (props) => { error={Boolean(formik.touched.phone && formik.errors.phone)} onBlur={formik.handleBlur} inputProps={{ + "aria-label": intl.formatMessage({ id: "userContactNumber" }), + "aria-describedby": 'helper-text-phone-signup', maxLength: 11, onKeyDown: (e) => { if (e.key === 'Enter') { diff --git a/src/pages/dashboard/Public/index.js b/src/pages/dashboard/Public/index.js index 29a7fa6..4e97036 100644 --- a/src/pages/dashboard/Public/index.js +++ b/src/pages/dashboard/Public/index.js @@ -27,10 +27,12 @@ const Notice = Loadable(React.lazy(() => import('./Notice'))); const LoadingComponent = Loadable(React.lazy(() => import('../../extra-pages/LoadingComponent'))); import { useNavigate } from "react-router-dom"; +import usePageTitle from "components/usePageTitle"; // ==============================|| DASHBOARD - DEFAULT ||============================== // const DashboardDefault = () => { + usePageTitle("Dashboard"); const navigate = useNavigate() const intl = useIntl(); const userData = JSON.parse(localStorage.getItem("userData")); diff --git a/src/pages/extra-pages/UserMenuPub1/index.js b/src/pages/extra-pages/UserMenuPub1/index.js index 40094a7..481caae 100644 --- a/src/pages/extra-pages/UserMenuPub1/index.js +++ b/src/pages/extra-pages/UserMenuPub1/index.js @@ -1,207 +1,276 @@ -import { Grid, Typography, Stack, } from '@mui/material'; -import { useState, useEffect, lazy } from "react"; +// File: src/pages/userGuide/UserMenuPub1.jsx +// Notes: +// - No new libraries. +// - Fixes: target="_blank", rel="noopener noreferrer" +// - Adds aria-label for icon-only download links (WCAG 2.4.4 / 1.1.1 friendly) +// - Fixes invalid HTML: no

    wrapping +// - Adds /, scope, and React keys -import Loadable from 'components/Loadable'; +import { Grid, Typography, Stack } from "@mui/material"; +import { useState, useEffect, lazy } from "react"; +import Loadable from "components/Loadable"; import { useIntl, FormattedMessage } from "react-intl"; -import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' +import DownloadIcon from "@mui/icons-material/Download"; +import titleBackgroundImg from "assets/images/dashboard/gazette-bar.png"; + +const LoadingComponent = Loadable(lazy(() => import("pages/extra-pages/LoadingComponent"))); + const BackgroundHead = { - backgroundImage: `url(${titleBackgroundImg})`, - width: 'auto', - height: 'auto', - backgroundSize: 'contain', - backgroundRepeat: 'no-repeat', - backgroundColor: '#0C489E', - backgroundPosition: 'right' -} + backgroundImage: `url(${titleBackgroundImg})`, + width: "auto", + height: "auto", + backgroundSize: "contain", + backgroundRepeat: "no-repeat", + backgroundColor: "#0C489E", + backgroundPosition: "right", +}; -const LoadingComponent = Loadable(lazy(() => import('pages/extra-pages/LoadingComponent'))); +const tableStyle = { + fontFamily: "arial, sans-serif", + borderCollapse: "collapse", + width: "100%", +}; -import DownloadIcon from '@mui/icons-material/Download'; +const cellStyle = { + border: "1px solid #dddddd", + textAlign: "left", + padding: "8px", +}; const UserMenuPub1 = () => { - const intl = useIntl(); - const { locale } = intl; - const [onReady, setOnReady] = useState(false); + const intl = useIntl(); + const { locale } = intl; + + const [onReady, setOnReady] = useState(false); - useEffect(() => { - setOnReady(true); - }, [locale]); + useEffect(() => { + setOnReady(true); + }, [locale]); - const tableStyle = { - fontFamily: "arial, sans-serif", - borderCollapse: "collapse", - width: "100%", - } + const pnspsurl = `https://${window.location.hostname}`; - const cellStyle = { - border: "1px solid #dddddd", - textAlign: "left", - padding: "8px" - }; + const pickByLocale = (en, zhHK, zhCN) => (locale === "zh-HK" ? zhHK : locale === "en" ? en : zhCN); - const getRow = ({ title, orgEn, orgZh, orgCn, indEn, indZh, indCn }) => { + // If you already created downloadPdfAria in i18n, keep it. + // Example en: "Download {guide} PDF ({userType})" + const buildAria = (guideId, userTypeId) => { + const guideTitle = intl.formatMessage({ id: guideId }); + const userType = intl.formatMessage({ id: userTypeId }); - return <> - - - - - - - ; - } + return intl.formatMessage( + { id: "downloadPdfAria", defaultMessage: "Download {guide} PDF ({userType})" }, + { guide: guideTitle, userType } + ); + }; - const pnspsurl = "https://"+window.location.hostname; + const rows = [ + { + id: "userGuide1", + org: { + en: `${pnspsurl}/user-guide-pub-1/eng/01 - Create account - c 1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/01c - Create account - c 1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/01sc - Create account - c 1.pdf`, + }, + ind: { + en: `${pnspsurl}/user-guide-pub-1/eng/01 - Create account - p 1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/01c - Create account - p 1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/01sc - Create account - p 1.pdf`, + }, + }, + { + id: "userGuide2", + org: { + en: `${pnspsurl}/user-guide-pub-1/eng/02 - Login - c 1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/02c - Login - c 1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/02sc - Login - c 1.pdf`, + }, + ind: { + en: `${pnspsurl}/user-guide-pub-1/eng/02 - Login - p 1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/02c - Login - p 1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/02sc - Login - p 1.pdf`, + }, + }, + { + id: "userGuide3", // UPDATED TO v2 + org: { + en: `${pnspsurl}/user-guide-pub-1/eng/03 - Application for publishing a Public Notice in the Gazette - c 2.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/03c - Application for publishing a Public Notice in the Gazette - c 2.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/03sc - Application for publishing a Public Notice in the Gazette - c 2.pdf`, + }, + ind: { + en: `${pnspsurl}/user-guide-pub-1/eng/03 - Application for publishing a Public Notice in the Gazette - p 2.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/03c - Application for publishing a Public Notice in the Gazette - p 2.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/03sc - Application for publishing a Public Notice in the Gazette - p 2.pdf`, + }, + }, + { + id: "userGuide4", + org: { + en: `${pnspsurl}/user-guide-pub-1/eng/04 - Proofreading reply (with correction) - c 2.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/04c - Proofreading reply (with correction) - c 2.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/04sc - Proofreading reply (with correction) - c 2.pdf`, + }, + ind: { + en: `${pnspsurl}/user-guide-pub-1/eng/04 - Proofreading reply (with correction) - p 2.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/04c - Proofreading reply (with correction) - p 2.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/04sc - Proofreading reply (with correction) - p 2.pdf`, + }, + }, + { + id: "userGuide5", + org: { + en: `${pnspsurl}/user-guide-pub-1/eng/05 - Proofreading reply (pass for printing) - c 2.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/05c - Proofreading reply (pass for printing) - c 2.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/05sc - Proofreading reply (pass for printing) - c 2.pdf`, + }, + ind: { + en: `${pnspsurl}/user-guide-pub-1/eng/05 - Proofreading reply (pass for printing) - p 2.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/05c - Proofreading reply (pass for printing) - p 2.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/05sc - Proofreading reply (pass for printing) - p 2.pdf`, + }, + }, + { + id: "userGuide6", + org: { + en: `${pnspsurl}/user-guide-pub-1/eng/06 - Cancellation of application for publishing a Public Notice in the Gazette - c 1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/06c - Cancellation of application for publishing a Public Notice in the Gazette - c 1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/06sc - Cancellation of application for publishing a Public Notice in the Gazette - c 1.pdf`, + }, + ind: { + en: `${pnspsurl}/user-guide-pub-1/eng/06 - Cancellation of application for publishing a Public Notice in the Gazette - p 1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/06c - Cancellation of application for publishing a Public Notice in the Gazette - p 1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/06sc - Cancellation of application for publishing a Public Notice in the Gazette - p 1.pdf`, + }, + }, + { + id: "userGuide7", + org: { + en: `${pnspsurl}/user-guide-pub-1/eng/07 - Forgot password - c 1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/07c - Forgot password - c 1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/07sc - Forgot password - c 1.pdf`, + }, + ind: { + en: `${pnspsurl}/user-guide-pub-1/eng/07 - Forgot password - p 1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/07c - Forgot password - p 1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/07sc - Forgot password - p 1.pdf`, + }, + }, + { + id: "userGuide8", + org: { + en: `${pnspsurl}/user-guide-pub-1/eng/08 - Change password - c 1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/08c - Change password - c 1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/08sc - Change password - c 1.pdf`, + }, + ind: { + en: `${pnspsurl}/user-guide-pub-1/eng/08 - Change password - p 1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/08c - Change password - p 1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/08sc - Change password - p 1.pdf`, + }, + }, + { + id: "userGuide9", + org: { + en: `${pnspsurl}/user-guide-pub-1/eng/09 - Language of email notification - c 1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/09c - Language of email notification - c 1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/09sc - Language of email notification - c 1.pdf`, + }, + ind: { + en: `${pnspsurl}/user-guide-pub-1/eng/09 - Language of email notification - p 1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/09c - Language of email notification - p 1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/09sc - Language of email notification - p 1.pdf`, + }, + }, + { + id: "userGuidePub10", + org: { + en: `${pnspsurl}/user-guide-pub-1/eng/10-Payment-c1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/10c-Payment-c1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/10sc-Payment-c1.pdf`, + }, + ind: { + en: `${pnspsurl}/user-guide-pub-1/eng/10-Payment-p1.pdf`, + zhHK: `${pnspsurl}/user-guide-pub-1/cht/10c-Payment-p1.pdf`, + zhCN: `${pnspsurl}/user-guide-pub-1/chs/10sc-Payment-p1.pdf`, + }, + }, + ]; + const renderDownloadLink = (href, ariaLabel) => ( + + + ); + if (!onReady) { return ( - !onReady ? - - - - - - : - ( - - -
    - - - - - -
    -
    - - -
    -

    - -

    {title}
    - - - - - - - {getRow({ - title: , - orgEn: pnspsurl + "/user-guide-pub-1/eng/01 - Create account - c 1.pdf", - orgZh: pnspsurl + "/user-guide-pub-1/cht/01c - Create account - c 1.pdf", - orgCn: pnspsurl + "/user-guide-pub-1/chs/01sc - Create account - c 1.pdf", - indEn: pnspsurl + "/user-guide-pub-1/eng/01 - Create account - p 1.pdf", - indZh: pnspsurl + "/user-guide-pub-1/cht/01c - Create account - p 1.pdf", - indCn: pnspsurl + "/user-guide-pub-1/chs/01sc - Create account - p 1.pdf" - })} - - {getRow({ - title: , - orgEn: pnspsurl + "/user-guide-pub-1/eng/02 - Login - c 1.pdf", - orgZh: pnspsurl + "/user-guide-pub-1/cht/02c - Login - c 1.pdf", - orgCn: pnspsurl + "/user-guide-pub-1/chs/02sc - Login - c 1.pdf", - indEn: pnspsurl + "/user-guide-pub-1/eng/02 - Login - p 1.pdf", - indZh: pnspsurl + "/user-guide-pub-1/cht/02c - Login - p 1.pdf", - indCn: pnspsurl + "/user-guide-pub-1/chs/02sc - Login - p 1.pdf" - })} - - {/* UPDATED TO v2 */} - {getRow({ - title: , - orgEn: pnspsurl + "/user-guide-pub-1/eng/03 - Application for publishing a Public Notice in the Gazette - c 2.pdf", - orgZh: pnspsurl + "/user-guide-pub-1/cht/03c - Application for publishing a Public Notice in the Gazette - c 2.pdf", - orgCn: pnspsurl + "/user-guide-pub-1/chs/03sc - Application for publishing a Public Notice in the Gazette - c 2.pdf", - indEn: pnspsurl + "/user-guide-pub-1/eng/03 - Application for publishing a Public Notice in the Gazette - p 2.pdf", - indZh: pnspsurl + "/user-guide-pub-1/cht/03c - Application for publishing a Public Notice in the Gazette - p 2.pdf", - indCn: pnspsurl + "/user-guide-pub-1/chs/03sc - Application for publishing a Public Notice in the Gazette - p 2.pdf" - })} - - {getRow({ - title: , - orgEn: pnspsurl + "/user-guide-pub-1/eng/04 - Proofreading reply (with correction) - c 2.pdf", - orgZh: pnspsurl + "/user-guide-pub-1/cht/04c - Proofreading reply (with correction) - c 2.pdf", - orgCn: pnspsurl + "/user-guide-pub-1/chs/04sc - Proofreading reply (with correction) - c 2.pdf", - indEn: pnspsurl + "/user-guide-pub-1/eng/04 - Proofreading reply (with correction) - p 2.pdf", - indZh: pnspsurl + "/user-guide-pub-1/cht/04c - Proofreading reply (with correction) - p 2.pdf", - indCn: pnspsurl + "/user-guide-pub-1/chs/04sc - Proofreading reply (with correction) - p 2.pdf" - })} - - {getRow({ - title: , - orgEn: pnspsurl + "/user-guide-pub-1/eng/05 - Proofreading reply (pass for printing) - c 2.pdf", - orgZh: pnspsurl + "/user-guide-pub-1/cht/05c - Proofreading reply (pass for printing) - c 2.pdf", - orgCn: pnspsurl + "/user-guide-pub-1/chs/05sc - Proofreading reply (pass for printing) - c 2.pdf", - indEn: pnspsurl + "/user-guide-pub-1/eng/05 - Proofreading reply (pass for printing) - p 2.pdf", - indZh: pnspsurl + "/user-guide-pub-1/cht/05c - Proofreading reply (pass for printing) - p 2.pdf", - indCn: pnspsurl + "/user-guide-pub-1/chs/05sc - Proofreading reply (pass for printing) - p 2.pdf" - })} - - {getRow({ - title: , - orgEn: pnspsurl + "/user-guide-pub-1/eng/06 - Cancellation of application for publishing a Public Notice in the Gazette - c 1.pdf", - orgZh: pnspsurl + "/user-guide-pub-1/cht/06c - Cancellation of application for publishing a Public Notice in the Gazette - c 1.pdf", - orgCn: pnspsurl + "/user-guide-pub-1/chs/06sc - Cancellation of application for publishing a Public Notice in the Gazette - c 1.pdf", - indEn: pnspsurl + "/user-guide-pub-1/eng/06 - Cancellation of application for publishing a Public Notice in the Gazette - p 1.pdf", - indZh: pnspsurl + "/user-guide-pub-1/cht/06c - Cancellation of application for publishing a Public Notice in the Gazette - p 1.pdf", - indCn: pnspsurl + "/user-guide-pub-1/chs/06sc - Cancellation of application for publishing a Public Notice in the Gazette - p 1.pdf" - })} - - {getRow({ - title: , - orgEn: pnspsurl + "/user-guide-pub-1/eng/07 - Forgot password - c 1.pdf", - orgZh: pnspsurl + "/user-guide-pub-1/cht/07c - Forgot password - c 1.pdf", - orgCn: pnspsurl + "/user-guide-pub-1/chs/07sc - Forgot password - c 1.pdf", - indEn: pnspsurl + "/user-guide-pub-1/eng/07 - Forgot password - p 1.pdf", - indZh: pnspsurl + "/user-guide-pub-1/cht/07c - Forgot password - p 1.pdf", - indCn: pnspsurl + "/user-guide-pub-1/chs/07sc - Forgot password - p 1.pdf" - })} - - {getRow({ - title: , - orgEn: pnspsurl + "/user-guide-pub-1/eng/08 - Change password - c 1.pdf", - orgZh: pnspsurl + "/user-guide-pub-1/cht/08c - Change password - c 1.pdf", - orgCn: pnspsurl + "/user-guide-pub-1/chs/08sc - Change password - c 1.pdf", - indEn: pnspsurl + "/user-guide-pub-1/eng/08 - Change password - p 1.pdf", - indZh: pnspsurl + "/user-guide-pub-1/cht/08c - Change password - p 1.pdf", - indCn: pnspsurl + "/user-guide-pub-1/chs/08sc - Change password - p 1.pdf" - })} - - {getRow({ - title: , - orgEn: pnspsurl + "/user-guide-pub-1/eng/09 - Language of email notification - c 1.pdf", - orgZh: pnspsurl + "/user-guide-pub-1/cht/09c - Language of email notification - c 1.pdf", - orgCn: pnspsurl + "/user-guide-pub-1/chs/09sc - Language of email notification - c 1.pdf", - indEn: pnspsurl + "/user-guide-pub-1/eng/09 - Language of email notification - p 1.pdf", - indZh: pnspsurl + "/user-guide-pub-1/cht/09c - Language of email notification - p 1.pdf", - indCn: pnspsurl + "/user-guide-pub-1/chs/09sc - Language of email notification - p 1.pdf" - })} - - {getRow({ - title: , - orgEn: pnspsurl + "/user-guide-pub-1/eng/10-Payment-c1.pdf", - orgZh: pnspsurl + "/user-guide-pub-1/cht/10c-Payment-c1.pdf", - orgCn: pnspsurl + "/user-guide-pub-1/chs/10sc-Payment-c1.pdf", - indEn: pnspsurl + "/user-guide-pub-1/eng/10-Payment-p1.pdf", - indZh: pnspsurl + "/user-guide-pub-1/cht/10c-Payment-p1.pdf", - indCn: pnspsurl + "/user-guide-pub-1/chs/10sc-Payment-p1.pdf" - })} - - -
    - -

    -
    - - - - ) + + + + + ); + } + + return ( + + +
    + + + + + +
    +
    + + + +
    + + + + + + + + + + + {rows.map((r) => { + const orgHref = pickByLocale(r.org.en, r.org.zhHK, r.org.zhCN); + const indHref = pickByLocale(r.ind.en, r.ind.zhHK, r.ind.zhCN); + + const guideTitle = intl.formatMessage({ id: r.id }); + + return ( + + + + + + ); + })} + +
    + + + +
    {guideTitle} + {renderDownloadLink(orgHref, buildAria(r.id, "forOrgUser"))} + + {renderDownloadLink(indHref, buildAria(r.id, "forIndUser"))} +
    -} +

    + +

    +
    +
    +
    +
    + ); +}; export default UserMenuPub1; \ No newline at end of file diff --git a/src/translations/en.json b/src/translations/en.json index be58bcd..29d714c 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -626,5 +626,7 @@ "muiNoOptions": "No options", "paginationPrev": "Go to previous page", - "paginationNext": "Go to next page" + "paginationNext": "Go to next page", + + "sort": "Sort" } \ No newline at end of file diff --git a/src/translations/zh-CN.json b/src/translations/zh-CN.json index 7f19d12..6f5b738 100644 --- a/src/translations/zh-CN.json +++ b/src/translations/zh-CN.json @@ -622,5 +622,7 @@ "muiNoOptions": "没有选项", "paginationPrev": "上一页", - "paginationNext": "下一页" + "paginationNext": "下一页", + + "sort": "排序" } \ No newline at end of file diff --git a/src/translations/zh-HK.json b/src/translations/zh-HK.json index 195eb60..6b42cff 100644 --- a/src/translations/zh-HK.json +++ b/src/translations/zh-HK.json @@ -623,5 +623,7 @@ "muiNoOptions": "沒有選項", "paginationPrev": "上一頁", - "paginationNext": "下一頁" + "paginationNext": "下一頁", + + "sort": "排序" } \ No newline at end of file diff --git a/src/utils/CommonFunction.js b/src/utils/CommonFunction.js index 4955a1f..2137e61 100644 --- a/src/utils/CommonFunction.js +++ b/src/utils/CommonFunction.js @@ -208,7 +208,7 @@ export const notifyActionSuccess = (actionMsg) => { export const notifyActionError = (actionMsg) => { toast.error(`${actionMsg}`, { position: "bottom-right", - autoClose: 5000, + autoClose: 60000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true,