| @@ -0,0 +1,51 @@ | |||
| # Content-Security-Policy (Apache) | |||
| Self-hosted fonts: no `fonts.googleapis.com` or `fonts.gstatic.com`. Static assets (including `woff2`) are served from `'self'`. | |||
| Adjust `report-uri` if your API base path or host differs (example below targets UAT). | |||
| ## Enforcing | |||
| ```apache | |||
| Header always set Content-Security-Policy "default-src 'self'; \ | |||
| base-uri 'self'; \ | |||
| object-src 'none'; \ | |||
| frame-ancestors 'none'; \ | |||
| form-action 'self'; \ | |||
| script-src 'self'; \ | |||
| style-src 'self' 'unsafe-inline'; \ | |||
| style-src-elem 'self' 'unsafe-inline'; \ | |||
| img-src 'self' data: https://www.w3.org https://w3.org; \ | |||
| media-src 'self' blob:; \ | |||
| font-src 'self' data:; \ | |||
| connect-src 'self'; \ | |||
| upgrade-insecure-requests" | |||
| ``` | |||
| ## Report-Only | |||
| Same policy plus violation reporting: | |||
| ```apache | |||
| Header always set Content-Security-Policy-Report-Only "default-src 'self'; \ | |||
| base-uri 'self'; \ | |||
| object-src 'none'; \ | |||
| frame-ancestors 'none'; \ | |||
| form-action 'self'; \ | |||
| script-src 'self'; \ | |||
| style-src 'self' 'unsafe-inline'; \ | |||
| style-src-elem 'self' 'unsafe-inline'; \ | |||
| img-src 'self' data: https://www.w3.org https://w3.org; \ | |||
| media-src 'self' blob:; \ | |||
| font-src 'self' data:; \ | |||
| connect-src 'self'; \ | |||
| upgrade-insecure-requests; \ | |||
| report-uri https://pnspsuat.gld.gov.hk/api/csp-report" | |||
| ``` | |||
| ## Notes | |||
| - **`style-src-elem`**: Explicit, alongside `style-src`, for `<link rel="stylesheet">` behaviour in modern browsers. | |||
| - **`img-src`**: Includes `https://www.w3.org` and `https://w3.org` so W3C URLs that redirect between hosts are allowed. | |||
| - **`font-src`**: `'self' data:` covers bundled fonts and `data:` URLs if used. | |||
| - Add origins to the relevant directive only if you introduce third-party scripts, styles, fonts, or APIs. | |||
| @@ -10,6 +10,9 @@ | |||
| "@emotion/cache": "^11.10.3", | |||
| "@emotion/react": "^11.10.4", | |||
| "@emotion/styled": "^11.10.4", | |||
| "@fontsource/noto-sans-hk": "^5.2.9", | |||
| "@fontsource/noto-sans-sc": "^5.2.9", | |||
| "@fontsource/public-sans": "^5.2.7", | |||
| "@material-ui/pickers": "^3.3.10", | |||
| "@mui/icons-material": "^5.14.1", | |||
| "@mui/lab": "^5.0.0-alpha.139", | |||
| @@ -8,10 +8,10 @@ | |||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |||
| <meta name="theme-color" content="#ffffff" /> | |||
| <meta name="msapplication-TileColor" content="#da532c"> | |||
| <meta name="title" content="PNSPS" /> | |||
| <meta name="title" content="PNSPS - Public Notice Submission and Payment System" /> | |||
| <meta | |||
| name="description" | |||
| content="The Government of the Hong Kong Special Administrative Region Gazette Public Notice Submission and Payment System." | |||
| content="Public Notice Submission and Payment System - Government Logistics Department" | |||
| /> | |||
| <meta | |||
| name="keywords" | |||
| @@ -19,12 +19,7 @@ | |||
| /> | |||
| <meta name="author" content="Government Logistics Department" /> | |||
| <link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" /> | |||
| <title>PNSPS</title> | |||
| <link rel="preconnect" href="https://fonts.gstatic.com" /> | |||
| <link | |||
| href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap&family=Public+Sans:wght@400;500;600;700" | |||
| rel="stylesheet" | |||
| /> | |||
| <title>PNSPS - Public Notice Submission and Payment System</title> | |||
| </head> | |||
| <body> | |||
| <noscript>You need to enable JavaScript to run this app.</noscript> | |||
| @@ -2,24 +2,23 @@ | |||
| import Routes from 'routes'; | |||
| import ThemeCustomization from 'themes'; | |||
| import ScrollTop from 'components/ScrollTop'; | |||
| import {ToastContainer} from "react-toastify"; | |||
| import { PNSPS_THEME } from './themes/themeConst'; | |||
| import { ThemeProvider } from '@emotion/react'; | |||
| import { ToastContainer } from 'react-toastify'; | |||
| import 'react-toastify/dist/ReactToastify.css'; | |||
| import {PNSPS_THEME} from "./themes/themeConst"; | |||
| import {ThemeProvider} from "@emotion/react"; | |||
| //import {isUserLoggedIn} from 'utils/Utils'; | |||
| //import {DefaultRoute} from 'routes/index' | |||
| // ==============================|| APP - THEME, ROUTER, LOCAL ||============================== // | |||
| const App = () => ( | |||
| <ThemeCustomization> | |||
| <ThemeProvider theme={PNSPS_THEME}> | |||
| <ScrollTop> | |||
| <Routes> | |||
| </Routes> | |||
| </ScrollTop> | |||
| </ThemeProvider> | |||
| <ToastContainer/> | |||
| </ThemeCustomization> | |||
| <ThemeCustomization> | |||
| <ThemeProvider theme={PNSPS_THEME}> | |||
| <ScrollTop> | |||
| <Routes /> | |||
| </ScrollTop> | |||
| </ThemeProvider> | |||
| <ToastContainer /> | |||
| </ThemeCustomization> | |||
| ); | |||
| export default App; | |||
| @@ -0,0 +1,19 @@ | |||
| /* Self-hosted fonts (bundled via @fontsource; no Google Fonts CDN — CSP-friendly). */ | |||
| /* Public Sans — MUI theme (`themeConst.js`), charts, config */ | |||
| @import "@fontsource/public-sans/400.css"; | |||
| @import "@fontsource/public-sans/500.css"; | |||
| @import "@fontsource/public-sans/600.css"; | |||
| @import "@fontsource/public-sans/700.css"; | |||
| /* Noto Sans HK — `styles.css` / navbar (Latin + Hong Kong glyphs) */ | |||
| @import "@fontsource/noto-sans-hk/latin-400.css"; | |||
| @import "@fontsource/noto-sans-hk/chinese-hongkong-400.css"; | |||
| @import "@fontsource/noto-sans-hk/latin-600.css"; | |||
| @import "@fontsource/noto-sans-hk/chinese-hongkong-600.css"; | |||
| /* Noto Sans SC — fallback for simplified Chinese */ | |||
| @import "@fontsource/noto-sans-sc/latin-400.css"; | |||
| @import "@fontsource/noto-sans-sc/chinese-simplified-400.css"; | |||
| @import "@fontsource/noto-sans-sc/latin-600.css"; | |||
| @import "@fontsource/noto-sans-sc/chinese-simplified-600.css"; | |||
| @@ -1,162 +1,378 @@ | |||
| /* 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; | |||
| } | |||
| /* Submenu triggers: same font as app (Noto Sans), bold */ | |||
| #navbar > div > li > button.navTrigger, | |||
| #navbar > div > li > button.navTrigger .MuiTypography-root { | |||
| font-weight: 600 !important; | |||
| font-family: "Noto Sans HK", "Noto Sans SC", sans-serif !important; | |||
| font-size: 1.1rem !important; | |||
| } | |||
| /* 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 <a> 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; | |||
| /* Dropdown trigger buttons: Noto Sans, bold */ | |||
| #navbar div li button.navTrigger{ | |||
| background: transparent; | |||
| border: 0; | |||
| padding: 0; | |||
| cursor: pointer; | |||
| text-decoration: none; | |||
| font-size: 1.1rem; | |||
| font-weight: 600; | |||
| font-family: "Noto Sans HK", "Noto Sans SC", sans-serif; | |||
| 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::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 div li:focus-within > ul, | |||
| #navbar div li ul:hover, | |||
| #navbar div li ul: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 | |||
| } */ | |||
| display: block; | |||
| } | |||
| /* Navbar focus ring */ | |||
| #navbar a:focus, | |||
| #navbar button.navTrigger:focus{ | |||
| outline: none; | |||
| } | |||
| #navbar a:focus-visible, | |||
| #navbar button.navTrigger:focus-visible{ | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: 2px; | |||
| border-radius: 10px; | |||
| } | |||
| /* Top-level links (Dashboard, Application, Proof, Logout, etc.) */ | |||
| #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; | |||
| } | |||
| /* Submenu triggers: Noto Sans, 1.1rem, bold */ | |||
| #navbar button.navTrigger.paymentTop, | |||
| #navbar button.navTrigger.client, | |||
| #navbar button.navTrigger.setting, | |||
| #navbar button.navTrigger.paymentRecord, | |||
| #navbar button.navTrigger.userSetting, | |||
| #navbar button.navTrigger.paymentTop .MuiTypography-root, | |||
| #navbar button.navTrigger.client .MuiTypography-root, | |||
| #navbar button.navTrigger.setting .MuiTypography-root, | |||
| #navbar button.navTrigger.paymentRecord .MuiTypography-root, | |||
| #navbar button.navTrigger.userSetting .MuiTypography-root { | |||
| font-size: 1.1rem !important; | |||
| font-weight: 600 !important; | |||
| font-family: "Noto Sans HK", "Noto Sans SC", sans-serif !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 | |||
| } | |||
| 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; | |||
| } | |||
| @@ -1,6 +1,3 @@ | |||
| @import url('https://fonts.googleapis.com/css?family=Noto+Sans+HK|Noto+Sans+SC&display=swap'); | |||
| @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC&display=swap'); | |||
| html, | |||
| body, | |||
| #root, | |||
| @@ -52,26 +49,97 @@ img | |||
| img:hover{-webkit-filter:none;} | |||
| a:link { | |||
| color: #1890ff; | |||
| a:link, | |||
| a:visited, | |||
| a:hover, | |||
| a:active { | |||
| color: #005FCC; /* WCAG AA compliant */ | |||
| background-color: transparent; | |||
| text-decoration: none; | |||
| text-decoration: underline; | |||
| } | |||
| a:visited { | |||
| color: #1890ff; | |||
| background-color: transparent; | |||
| text-decoration: none; | |||
| /* iframe#webpack-dev-server-client-overlay{display:none!important} */ | |||
| /* ===== WCAG 2.4.7 Focus Visible (Keyboard only) ===== */ | |||
| :where( | |||
| a, | |||
| button, | |||
| input, | |||
| select, | |||
| textarea, | |||
| summary, | |||
| [role="button"], | |||
| [role="link"], | |||
| [tabindex]:not([tabindex="-1"]) | |||
| ):focus-visible { | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: 2px; | |||
| border-radius: 4px; | |||
| } | |||
| a:hover { | |||
| color: #1890ff; | |||
| background-color: transparent; | |||
| text-decoration: none; | |||
| /* Suppress mouse focus */ | |||
| :where( | |||
| a, | |||
| button, | |||
| input, | |||
| select, | |||
| textarea, | |||
| summary, | |||
| [role="button"], | |||
| [role="link"], | |||
| [tabindex]:not([tabindex="-1"]) | |||
| ):focus:not(:focus-visible) { | |||
| outline: none; | |||
| } | |||
| a:active { | |||
| color: #1890ff; | |||
| background-color: transparent; | |||
| text-decoration: none; | |||
| /* ===== MUI DataGrid keyboard focus (WCAG 2.4.7 / 2.4.11) ===== */ | |||
| .MuiDataGrid-columnHeader:focus-visible, | |||
| .MuiDataGrid-cell:focus-visible { | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: -2px; | |||
| box-shadow: 0 0 0 3px rgba(12, 72, 158, 0.25); | |||
| } | |||
| /* Contained buttons only */ | |||
| .MuiButton-contained { | |||
| box-shadow: none; | |||
| } | |||
| .MuiButton-contained:hover { | |||
| } | |||
| /* iAM Smart button — keep green border */ | |||
| .MuiButton-outlinedIAmSmart { | |||
| color: #2E7D32; /* green text */ | |||
| border: 1px solid #2E7D32; /* keep your green */ | |||
| } | |||
| .MuiButton-outlinedIAmSmart:hover { | |||
| color: #2E7D32; /* green text */ | |||
| border: 1px solid #2E7D32; | |||
| background-color: rgba(46, 125, 50, 0.08); /* optional */ | |||
| } | |||
| /* ===== Outlined button focus ===== */ | |||
| .MuiButton-outlined:focus-visible { | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: 2px; | |||
| } | |||
| /* Step icon number color (text inside the circle) */ | |||
| .MuiStepIcon-text { | |||
| fill: #1A1A1A; /* dark text, high contrast */ | |||
| } | |||
| /* Placeholder text contrast */ | |||
| .MuiInputBase-input::placeholder { | |||
| color: #6B6B6B; /* passes 4.5:1 on white */ | |||
| opacity: 1; /* IMPORTANT */ | |||
| } | |||
| /* iframe#webpack-dev-server-client-overlay{display:none!important} */ | |||
| /* WCAG-safe error text color */ | |||
| .Mui-error, | |||
| .MuiFormHelperText-root.Mui-error, | |||
| .MuiTypography-errorMessage1 { | |||
| color: #B00020; /* dark red, AA compliant */ | |||
| opacity: 1; /* remove faded appearance */ | |||
| } | |||
| @@ -13,6 +13,12 @@ export const windowCount = 'windowCount' | |||
| import {useNavigate} from "react-router-dom"; | |||
| import {useDispatch} from "react-redux"; | |||
| import { REFRESH_TOKEN } from 'utils/ApiPathConst'; | |||
| import { getMessage } from 'utils/getI18nMessage'; | |||
| // Guard so we only register interceptors once (ThemeRoutes re-renders add duplicate handlers otherwise) | |||
| let axiosInterceptorsSetup = false; | |||
| // In-memory guard so only one 401/logout flow shows the alert (avoids race when multiple 401s or interceptors run) | |||
| let expiredAlertShownInMemory = false; | |||
| // ** Handle User Login | |||
| export const handleLogin = data => { | |||
| @@ -39,6 +45,8 @@ export const handleLogin = data => { | |||
| localStorage.setItem(windowCount, '0') | |||
| localStorage.setItem(predictProductionQty, '0') | |||
| localStorage.setItem(predictUsageCount, '0') | |||
| localStorage.removeItem('expiredAlertShown') | |||
| expiredAlertShownInMemory = false | |||
| } | |||
| } | |||
| @@ -94,7 +102,9 @@ export const handleLogoutFunction = () => { | |||
| localStorage.removeItem('transactionid') | |||
| localStorage.removeItem('searchCriteria') | |||
| //localStorage.removeItem(config.storageUserRoleKeyName) | |||
| localStorage.removeItem('expiredAlertShown') | |||
| // Do not clear expiredAlertShown / expiredAlertShownInMemory here: logout runs right after | |||
| // the token-expired alert, and parallel 401s would each show another popup if we reset. | |||
| // Reset those only on successful login (handleLogin). | |||
| localStorage.removeItem(refreshIntervalName) | |||
| localStorage.removeItem(windowCount) | |||
| localStorage.removeItem(predictProductionQty) | |||
| @@ -109,7 +119,13 @@ export const SetupAxiosInterceptors = () => { | |||
| const dispatch = useDispatch(); | |||
| //const updateLastRequestTime = useContext(TimerContext); | |||
| let isRefreshToken= false; | |||
| // Avoid stacking interceptors on every ThemeRoutes re-render (would cause multiple alerts on 401) | |||
| if (axiosInterceptorsSetup) { | |||
| return; | |||
| } | |||
| axiosInterceptorsSetup = true; | |||
| axios.interceptors.request.use( | |||
| config => { | |||
| // ** Get token from localStorage | |||
| @@ -134,7 +150,17 @@ export const SetupAxiosInterceptors = () => { | |||
| }, | |||
| async (error) => { | |||
| // const { config, response: { status } } = error | |||
| if (error.response.status === 401 && error.config.url !== apiPath + REFRESH_TOKEN) { | |||
| const status = error.response?.status | |||
| const reqUrl = error.config?.url || '' | |||
| // iAM Smart registration: HKID already linked — caller must show /iamsmart/pleaseLogin (not refresh/logout) | |||
| if ( | |||
| status === 401 && | |||
| reqUrl.includes('/smart/getProfile') && | |||
| error.response?.data?.exception === 'msg: account created' | |||
| ) { | |||
| return Promise.reject(error) | |||
| } | |||
| if (status === 401 && error.config?.url !== apiPath + REFRESH_TOKEN) { | |||
| // Make a request to refresh the access token | |||
| const refreshToken = localStorage.getItem('refreshToken'); | |||
| if (isRefreshToken) { | |||
| @@ -156,17 +182,23 @@ export const SetupAxiosInterceptors = () => { | |||
| } | |||
| }) | |||
| .catch((refreshError) => { | |||
| isRefreshToken = false; | |||
| if (!expiredAlertShownInMemory && localStorage.getItem("expiredAlertShown") === null) { | |||
| expiredAlertShownInMemory = true; | |||
| localStorage.setItem("expiredAlertShown", "true"); | |||
| alert(getMessage("autoLogout")); | |||
| } | |||
| dispatch(handleLogoutFunction()); | |||
| navigate('/login'); | |||
| isRefreshToken = false; | |||
| window.location.reload(); | |||
| throw refreshError; | |||
| }); | |||
| } else { | |||
| if (error.response.status === 401) { | |||
| if (localStorage.getItem("expiredAlertShown") === null) { | |||
| localStorage.setItem("expiredAlertShown", true) | |||
| alert("登入驗證已過期,請重新登入。") | |||
| if (error.response && error.response.status === 401) { | |||
| if (!expiredAlertShownInMemory && localStorage.getItem("expiredAlertShown") === null) { | |||
| expiredAlertShownInMemory = true; | |||
| localStorage.setItem("expiredAlertShown", "true"); | |||
| alert(getMessage("autoLogout")); | |||
| } | |||
| } | |||
| @@ -191,7 +223,7 @@ export const SetupAxiosInterceptors = () => { | |||
| await window.location.reload(); | |||
| } | |||
| if (error.response.status === 500){ | |||
| if (error.response?.status === 500){ | |||
| //setIsUploading(false); | |||
| } | |||
| // console.log(error) | |||
| @@ -171,3 +171,6 @@ export const getPaymentMethodByCode = (code) => { | |||
| return "other"; | |||
| }; | |||
| export function setPageTitle(title) { | |||
| document.title = `${title} - PNSPS | Government Logistics Department`; | |||
| } | |||
| @@ -5,7 +5,7 @@ import { motion } from 'framer-motion'; | |||
| // ==============================|| ANIMATION BUTTON ||============================== // | |||
| export default function AnimateButton({ children, type }) { | |||
| export default function AnimateButton({ children, type = 'scale' }) { | |||
| switch (type) { | |||
| case 'rotate': // only available in paid version | |||
| case 'slide': // only available in paid version | |||
| @@ -23,7 +23,3 @@ AnimateButton.propTypes = { | |||
| children: PropTypes.node, | |||
| type: PropTypes.oneOf(['slide', 'scale', 'rotate']) | |||
| }; | |||
| AnimateButton.defaultProps = { | |||
| type: 'scale' | |||
| }; | |||
| @@ -15,24 +15,36 @@ 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 ( | |||
| <Stack direction="column" justifyContent="center" alignItems="center" > | |||
| <ButtonBase | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| sx={sx} | |||
| > | |||
| <ButtonBase | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => 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', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| > | |||
| <Stack direction="column" justifyContent="center" alignItems="center" > | |||
| <Logo /> | |||
| </ButtonBase> | |||
| <span style={{ color: checkSysEnv()!=''?'red':'#0C489E'}} id="systemTitle">PNSPS</span> | |||
| </Stack> | |||
| <span style={{ color: checkSysEnv()!=''?'red':'#0C489E'}} id="systemTitle">PNSPS</span> | |||
| </Stack> | |||
| </ButtonBase> | |||
| ); | |||
| }; | |||
| @@ -1,8 +1,9 @@ | |||
| import React, { createContext, useState, useEffect } from 'react'; | |||
| import React, { createContext, useState, useEffect, useRef } from 'react'; | |||
| import {useNavigate} from "react-router-dom"; | |||
| import {useIdleTimer} from "react-idle-timer"; | |||
| import { handleLogoutFunction } from 'auth/index'; | |||
| import { useDispatch } from "react-redux"; | |||
| import { useIntl } from "react-intl"; | |||
| import { | |||
| isUserLoggedIn, | |||
| isGLDLoggedIn, | |||
| @@ -12,9 +13,11 @@ import { | |||
| const TimerContext = createContext(); | |||
| const AutoLogoutProvider = ({ children }) => { | |||
| const intl = useIntl(); | |||
| const [lastRequestTime, setLastRequestTime] = useState(Date.now()); | |||
| const navigate = useNavigate(); | |||
| const [logoutInterval, setLogoutInterval] = useState(1); | |||
| const idleLogoutTriggeredRef = useRef(false); | |||
| // const [remainingInterval] = useState(5); | |||
| const [state, setState] = useState('Active'); | |||
| const dispatch = useDispatch() | |||
| @@ -93,8 +96,13 @@ const AutoLogoutProvider = ({ children }) => { | |||
| // console.log(remainingInterval * 60); | |||
| // console.log(logoutInterval * 60 * 1000 - timeElapsed) | |||
| if (timeElapsed >= logoutInterval * 60 * 1000) { | |||
| if(isUserLoggedIn()){ | |||
| alert("登入驗證已過期,請重新登入。") | |||
| if (isUserLoggedIn() && !idleLogoutTriggeredRef.current) { | |||
| idleLogoutTriggeredRef.current = true; | |||
| // Skip alert if token-expiry (axios 401) already showed one, to avoid duplicate alerts | |||
| if (localStorage.getItem("expiredAlertShown") === null) { | |||
| localStorage.setItem("expiredAlertShown", "true"); | |||
| alert(intl.formatMessage({ id: "autoLogout" })); | |||
| } | |||
| dispatch(handleLogoutFunction()); | |||
| navigate('/login'); | |||
| window.location.reload(); | |||
| @@ -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 ( | |||
| <Box sx={containerSx}> | |||
| <Box sx={containerSx} ref={gridRootRef} role="table"> | |||
| <DataGrid | |||
| {...props} | |||
| rows={_rows} | |||
| @@ -239,16 +279,26 @@ export function FiDataGrid({ rows, columns, sx, autoHeight = true, | |||
| ? { | |||
| Pagination: () => ( | |||
| <TablePagination | |||
| count={rowCount || 0} | |||
| page={page} | |||
| rowsPerPage={pageSize} | |||
| rowsPerPageOptions={_pageSizeOptions} | |||
| labelDisplayedRows={() => | |||
| `${(_rows?.length ? page * pageSize + 1 : 0)}-${page * pageSize + (_rows?.length ?? 0)} ${intl.formatMessage({ id: "of" })} ${rowCount}` | |||
| } | |||
| labelRowsPerPage={intl.formatMessage({ id: "rowsPerPage" }) + ":"} | |||
| onPageChange={handleChangePage} | |||
| onRowsPerPageChange={handleChangePageSize} | |||
| component="div" | |||
| count={rowCount || 0} | |||
| page={page} | |||
| rowsPerPage={pageSize} | |||
| rowsPerPageOptions={_pageSizeOptions} | |||
| labelDisplayedRows={() => | |||
| `${(_rows?.length ? page * pageSize + 1 : 0)}-${page * pageSize + (_rows?.length ?? 0)} ${intl.formatMessage({ id: "of" })} ${rowCount}` | |||
| } | |||
| labelRowsPerPage={intl.formatMessage({ id: "rowsPerPage" }) + ":"} | |||
| getItemAriaLabel={(type) => { | |||
| if (type === 'previous') { | |||
| return intl.formatMessage({ id: 'paginationPrev' }); | |||
| } | |||
| if (type === 'next') { | |||
| return intl.formatMessage({ id: 'paginationNext' }); | |||
| } | |||
| return ''; | |||
| }} | |||
| onPageChange={handleChangePage} | |||
| onRowsPerPageChange={handleChangePageSize} | |||
| /> | |||
| ), | |||
| } | |||
| @@ -9,18 +9,22 @@ import { useDispatch, useSelector } from 'react-redux'; | |||
| import Logo from './Logo'; | |||
| import config from 'config'; | |||
| import { activeItem } from 'store/reducers/menu'; | |||
| import {useIntl} from "react-intl"; | |||
| // ==============================|| MAIN LOGO ||============================== // | |||
| const LogoSection = ({ sx, to }) => { | |||
| const { defaultId } = useSelector((state) => state.menu); | |||
| const dispatch = useDispatch(); | |||
| const intl = useIntl(); | |||
| return ( | |||
| <ButtonBase | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| aria-label={intl.formatMessage({ id: "PNSPS", defaultMessage: "PNSPS" })} | |||
| sx={sx} | |||
| > | |||
| <Logo /> | |||
| @@ -1,5 +1,6 @@ | |||
| import PropTypes from 'prop-types'; | |||
| import { forwardRef } from 'react'; | |||
| import omit from 'lodash/omit'; | |||
| // material-ui | |||
| import { useTheme } from '@mui/material/styles'; | |||
| @@ -8,6 +9,29 @@ import { Card, CardContent, CardHeader, Divider, Typography, Grid } from '@mui/m | |||
| // project import | |||
| import Highlighter from './third-party/Highlighter'; | |||
| /** Props often mistaken for Card / Grid / sx — must not reach the underlying DOM node via `{...others}`. */ | |||
| const PROPS_OMIT_FROM_CARD = [ | |||
| 'backgroundColor', | |||
| 'bgcolor', | |||
| 'xs', | |||
| 'sm', | |||
| 'md', | |||
| 'lg', | |||
| 'xl', | |||
| 'item', | |||
| 'container', | |||
| 'spacing', | |||
| 'direction', | |||
| 'justify', | |||
| 'alignItems', | |||
| 'alignContent', | |||
| 'flexWrap', | |||
| 'wrap', | |||
| 'zeroMinWidth', | |||
| 'rowSpacing', | |||
| 'columnSpacing' | |||
| ]; | |||
| // header style | |||
| const headerSX = { | |||
| p: 2.5, | |||
| @@ -38,6 +62,8 @@ const MainCard = forwardRef( | |||
| const theme = useTheme(); | |||
| boxShadow = theme.palette.mode === 'dark' ? boxShadow || true : boxShadow; | |||
| const cardProps = omit(others, PROPS_OMIT_FROM_CARD); | |||
| return ( | |||
| <Grid container direction="column" | |||
| // alignItems="center" | |||
| @@ -46,7 +72,7 @@ const MainCard = forwardRef( | |||
| <Card | |||
| elevation={elevation || 0} | |||
| ref={ref} | |||
| {...others} | |||
| {...cardProps} | |||
| sx={{ | |||
| alignItems: "center", | |||
| border: border ? '1px solid' : 'none', | |||
| @@ -9,22 +9,35 @@ 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 ( | |||
| <ButtonBase | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| sx={sx} | |||
| > | |||
| <Logo /> | |||
| </ButtonBase> | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| aria-label={intl.formatMessage({ id: "PNSPS" })} | |||
| sx={{ | |||
| ...sx, | |||
| /* WCAG 2.4.7 – visible keyboard focus */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| > | |||
| <Logo /> | |||
| </ButtonBase> | |||
| ); | |||
| }; | |||
| @@ -2,6 +2,7 @@ | |||
| import { useMediaQuery, Container, Link, Typography, Stack } from '@mui/material'; | |||
| import bhkLogo from 'assets/images/BHK_logo_rgb_zh-hk.png'; | |||
| import {FormattedMessage} from "react-intl"; | |||
| import {useIntl} from "react-intl"; | |||
| import { | |||
| isGLDLoggedIn, | |||
| } from "utils/Utils"; | |||
| @@ -9,9 +10,14 @@ import { | |||
| const AuthFooter = () => { | |||
| const matchDownSM = useMediaQuery((theme) => theme.breakpoints.down('sm')); | |||
| const intl = useIntl(); | |||
| const bhkAlt = intl.formatMessage({ id: "bhkLogoAlt" }); | |||
| const wcagAlt = intl.formatMessage({ id: "wcagAaAlt" }); | |||
| return ( | |||
| <Container maxWidth= "xl" sx={{minHeight: '5vh'}}> | |||
| <Container maxWidth="xl" sx={{ minHeight: '5vh' }}> | |||
| <Stack | |||
| direction={matchDownSM ? 'column' : 'row'} | |||
| justifyContent={matchDownSM ? 'center' : 'flex-start'} | |||
| @@ -19,7 +25,11 @@ const AuthFooter = () => { | |||
| textAlign={matchDownSM ? 'center' : 'inherit'} | |||
| alignItems="center" | |||
| > | |||
| <Typography variant="subtitle2" color="secondary" component="span"> | |||
| <Typography | |||
| variant="subtitle2" | |||
| component="span" | |||
| sx={{ color: '#4A4A4A' }} | |||
| > | |||
| 2024 © <FormattedMessage id="HKGLD" /> | |||
| </Typography> | |||
| <Typography | |||
| @@ -44,22 +54,29 @@ const AuthFooter = () => { | |||
| </Typography> | |||
| </Stack> | |||
| <Stack direction={matchDownSM ? 'column' : 'row'} spacing={matchDownSM ? 1 : 3} textAlign={matchDownSM ? 'center' : 'inherit'} justifyContent={matchDownSM?"center":"flex-end"}> | |||
| {!isGLDLoggedIn()? | |||
| <a href="https://www.w3.org/WAI/WCAG2AA-Conformance" | |||
| title="Explanation of WCAG 2 Level AA conformance"> | |||
| <img height="32" width="88" | |||
| src="https://www.w3.org/WAI/wcag2AA" | |||
| alt="Level AA conformance, | |||
| W3C WAI Web Content Accessibility Guidelines 2.0"/> | |||
| </a>:null | |||
| } | |||
| <a href="https://www.brandhk.gov.hk/zh-hk" | |||
| title="Brand Hong Kong"> | |||
| <img src={bhkLogo} alt="logo" height="32" width="88" | |||
| {!isGLDLoggedIn()? ( | |||
| <a | |||
| href="https://www.w3.org/WAI/WCAG2AA-Conformance" | |||
| title="Explanation of WCAG 2 Level AA conformance" | |||
| > | |||
| <img | |||
| height="32" | |||
| width="88" | |||
| src="https://www.w3.org/WAI/wcag2AA" | |||
| alt={wcagAlt} | |||
| /> | |||
| </a> | |||
| </Stack> | |||
| ) : null} | |||
| <a href="https://www.brandhk.gov.hk/zh-hk" title="Brand Hong Kong"> | |||
| <img | |||
| src={bhkLogo} | |||
| alt={bhkAlt} | |||
| height="32" | |||
| width="88" | |||
| /> | |||
| </a> | |||
| </Stack> | |||
| </Container> | |||
| ); | |||
| }; | |||
| @@ -1,5 +1,6 @@ | |||
| // material-ui | |||
| import {useState, useEffect} from 'react'; | |||
| import { useIntl } from 'react-intl'; | |||
| import iAmSmartICon from 'assets/images/icons/icon_iAmSmart.png'; | |||
| import { | |||
| Button, | |||
| @@ -9,7 +10,7 @@ import { | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export function IAmSmartButton({ label, onClickFun, fullWidth }) { | |||
| const intl = useIntl(); | |||
| const [_label, set_label] = useState(""); | |||
| useEffect(()=>{ | |||
| @@ -23,7 +24,7 @@ export function IAmSmartButton({ label, onClickFun, fullWidth }) { | |||
| } | |||
| return ( | |||
| <Button onClick={()=>doOnClick()} sx={{textTransform: 'none'}} color="iAmSmart" fullWidth={fullWidth} size="large" variant="outlined" startIcon={<img src={iAmSmartICon} alt="iAM Smart" width="30" />}> | |||
| <Button onClick={()=>doOnClick()} sx={{textTransform: 'none'}} color="iAmSmart" fullWidth={fullWidth} size="large" variant="outlined" startIcon={<img src={iAmSmartICon} alt={intl.formatMessage({ id: 'iAmSmartAlt' })} width="30" />}> | |||
| <Typography variant="h5"> | |||
| {_label} | |||
| </Typography> | |||
| @@ -0,0 +1,42 @@ | |||
| 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; | |||
| } | |||
| const fullTitle = `${pageTitle} - ${systemName} | ${gldName}`; | |||
| // Update document title (tab title) | |||
| document.title = fullTitle; | |||
| // Update <meta name="title"> and <meta name="description"> so they're language-dependent too | |||
| const metaTitle = document.querySelector('meta[name="title"]'); | |||
| if (metaTitle) { | |||
| metaTitle.setAttribute("content", fullTitle); | |||
| } | |||
| const metaDescription = document.querySelector('meta[name="description"]'); | |||
| if (metaDescription) { | |||
| metaDescription.setAttribute("content", fullTitle); | |||
| } | |||
| // Keep <html lang="..."> in sync with current locale | |||
| if (document.documentElement) { | |||
| document.documentElement.lang = intl.locale || "en"; | |||
| } | |||
| }, [messageIdOrText, intl]); | |||
| } | |||
| @@ -1,7 +1,8 @@ | |||
| import { StrictMode, useEffect, useContext } from 'react'; | |||
| import { createRoot } from 'react-dom/client'; | |||
| import { BrowserRouter } from 'react-router-dom'; | |||
| import "./assets/style/styles.css" | |||
| import './assets/fonts.css'; | |||
| import './assets/style/styles.css'; | |||
| // scroll bar | |||
| import 'simplebar/src/simplebar.css'; | |||
| @@ -4,6 +4,7 @@ import { useMemo } from 'react'; | |||
| // material-ui | |||
| import { useTheme } from '@mui/material/styles'; | |||
| import { Box, Drawer, useMediaQuery } from '@mui/material'; | |||
| import { useIntl } from 'react-intl'; | |||
| // project import | |||
| import DrawerHeader from './DrawerHeader'; | |||
| @@ -15,6 +16,7 @@ import { drawerWidth } from 'config'; | |||
| const MainDrawer = ({ open, handleDrawerToggle, window }) => { | |||
| const theme = useTheme(); | |||
| const intl = useIntl(); | |||
| const matchDownMD = useMediaQuery(theme.breakpoints.down('lg')); | |||
| // responsive drawer container | |||
| @@ -25,7 +27,7 @@ const MainDrawer = ({ open, handleDrawerToggle, window }) => { | |||
| const drawerHeader = useMemo(() => <DrawerHeader open={open} />, [open]); | |||
| return ( | |||
| <Box component="nav" sx={{ flexShrink: { md: 0 }, zIndex: 1300 }} aria-label="mailbox folders"> | |||
| <Box component="nav" sx={{ flexShrink: { md: 0 }, zIndex: 1300 }} aria-label={intl.formatMessage({ id: 'ariaMailboxFolders' })}> | |||
| {!matchDownMD ? ( | |||
| <MiniDrawerStyled variant="permanent" open={open}> | |||
| {drawerHeader} | |||
| @@ -16,7 +16,7 @@ import { | |||
| import Transitions from 'components/@extended/Transitions'; | |||
| import LanguageIcon from '@mui/icons-material/Language'; | |||
| import {FormattedMessage} from "react-intl"; | |||
| import {FormattedMessage, useIntl} from "react-intl"; | |||
| import * as React from "react"; | |||
| import LocaleContext from "components/I18nProvider"; | |||
| @@ -27,6 +27,8 @@ const LocaleSelector = () => { | |||
| const matchesXs = useMediaQuery(theme.breakpoints.down('md')); | |||
| const { setLocale } = useContext(LocaleContext); | |||
| const intl = useIntl(); | |||
| const anchorRef = useRef(null); | |||
| const [open, setOpen] = useState(false); | |||
| const handleToggle = () => { | |||
| @@ -47,16 +49,26 @@ const LocaleSelector = () => { | |||
| return ( | |||
| <Box sx={{ flexShrink: 0, ml: 0.75 }}> | |||
| <IconButton | |||
| disableRipple | |||
| color="secondary" | |||
| sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor }} | |||
| aria-label="open profile" | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'profile-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| disableRipple | |||
| color="secondary" | |||
| sx={{ | |||
| color: 'text.primary', | |||
| bgcolor: open ? iconBackColorOpen : iconBackColor, | |||
| /* ✅ WCAG 2.4.7 focus indicator */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| aria-label={intl.formatMessage({id: 'openLanguage'})} | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'profile-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| > | |||
| <LanguageIcon /> | |||
| <LanguageIcon /> | |||
| </IconButton> | |||
| <Popper | |||
| placement={matchesXs ? 'bottom' : 'bottom-end'} | |||
| @@ -45,19 +45,26 @@ const MobileSection = () => { | |||
| <> | |||
| <Box sx={{ flexShrink: 0, ml: 0.75 }}> | |||
| <IconButton | |||
| component="span" | |||
| disableRipple | |||
| sx={{ | |||
| bgcolor: open ? 'grey.300' : 'grey.100' | |||
| }} | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'menu-list-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| color="inherit" | |||
| > | |||
| <MoreOutlined /> | |||
| </IconButton> | |||
| component="span" | |||
| disableRipple | |||
| sx={{ | |||
| bgcolor: open ? 'grey.300' : 'grey.100', | |||
| /* WCAG 2.4.7 – visible keyboard focus */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'menu-list-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| color="inherit" | |||
| > | |||
| <MoreOutlined /> | |||
| </IconButton> | |||
| </Box> | |||
| <Popper | |||
| placement="bottom-end" | |||
| @@ -71,17 +71,27 @@ const Notification = () => { | |||
| <IconButton | |||
| disableRipple | |||
| color="secondary" | |||
| sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor }} | |||
| aria-label="open profile" | |||
| sx={{ | |||
| color: 'text.primary', | |||
| bgcolor: open ? iconBackColorOpen : iconBackColor, | |||
| /* ✅ WCAG 2.4.7 focus indicator */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| aria-label={intl.formatMessage({id: 'openLanguage'})} | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'profile-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| > | |||
| <Badge badgeContent={4} color="primary"> | |||
| <BellOutlined /> | |||
| </Badge> | |||
| </IconButton> | |||
| <Badge badgeContent={4} color="primary"> | |||
| <BellOutlined /> | |||
| </Badge> | |||
| </IconButton> | |||
| <Popper | |||
| placement={matchesXs ? 'bottom' : 'bottom-end'} | |||
| open={open} | |||
| @@ -33,6 +33,7 @@ import { LogoutOutlined, | |||
| import { handleLogoutFunction } from 'auth/index'; | |||
| import {useNavigate} from "react-router-dom"; | |||
| import {useDispatch} from "react-redux"; | |||
| import { useIntl } from 'react-intl'; | |||
| import AccountCircleIcon from '@mui/icons-material/AccountCircle'; | |||
| // tab panel wrapper | |||
| @@ -61,6 +62,7 @@ TabPanel.propTypes = { | |||
| const Profile = () => { | |||
| const theme = useTheme(); | |||
| const intl = useIntl(); | |||
| const navigate = useNavigate() | |||
| const dispatch = useDispatch() | |||
| @@ -101,7 +103,7 @@ const Profile = () => { | |||
| borderRadius: 1, | |||
| '&:hover': { bgcolor: 'secondary.lighter' } | |||
| }} | |||
| aria-label="open profile" | |||
| aria-label={intl.formatMessage({id: 'openLanguage'})} | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'profile-grow' : undefined} | |||
| aria-haspopup="true" | |||
| @@ -170,7 +172,7 @@ const Profile = () => { | |||
| {/* {open && ( | |||
| <> | |||
| <Box sx={{ borderBottom: 1, borderColor: 'divider' }}> | |||
| <Tabs variant="fullWidth" value={value} onChange={handleChange} aria-label="profile tabs"> | |||
| <Tabs variant="fullWidth" value={value} onChange={handleChange} aria-label={intl.formatMessage({ id: 'ariaProfileTabs' })}> | |||
| <Tab | |||
| sx={{ | |||
| display: 'flex', | |||
| @@ -62,7 +62,7 @@ const MainLayout = () => { | |||
| <Box sx={{backgroundColor:'#ffffff', display: 'flex', width: '100%', flexDirection: "column", paddingTop: { xs: "5px", sm: "25px", md: "43px" }}}> | |||
| <Header/> | |||
| {/* <Drawer open={open} handleDrawerToggle={handleDrawerToggle} /> */} | |||
| <Box style={{ width: '100%', flexGrow: 1 } } sx={{ paddingTop: "38px" }}> | |||
| <Box style={{ width: '100%', flexGrow: 1 }} sx={{ paddingTop: "36px" }}> | |||
| {/* <Toolbar /> */} | |||
| {/* <Breadcrumbs navigation={navigation} title /> */} | |||
| <Outlet /> | |||
| @@ -143,7 +143,7 @@ const AnnouncementForm = ({ loadedData }) => { | |||
| </Grid> | |||
| </Grid> | |||
| <Grid item xs={12} md={12} sx={{ mt: 2 }}><Divider fullWidth></Divider></Grid> | |||
| <Grid item xs={12} md={12} sx={{ mt: 2 }}><Divider sx={{ width: '100%' }} /></Grid> | |||
| <Grid item xs={12} md={12}><Typography variant="h5">English</Typography></Grid> | |||
| <Grid item xs={12} md={12} > | |||
| <Grid container alignItems={"center"} xs={12} sm={12} md={12} lg={12} sx={{ mb: 2 }}> | |||
| @@ -202,7 +202,7 @@ const AnnouncementForm = ({ loadedData }) => { | |||
| </Grid> | |||
| </Grid> | |||
| <Grid item xs={12} md={12} sx={{ mt: 2 }}><Divider fullWidth></Divider></Grid> | |||
| <Grid item xs={12} md={12} sx={{ mt: 2 }}><Divider sx={{ width: '100%' }} /></Grid> | |||
| <Grid item xs={12} md={12}><Typography variant="h5">Traditional Chinese</Typography></Grid> | |||
| <Grid item xs={12} md={12}> | |||
| <Grid container alignItems={"center"} xs={12} sm={12} md={12} lg={12} sx={{ mb: 2 }}> | |||
| @@ -263,7 +263,7 @@ const AnnouncementForm = ({ loadedData }) => { | |||
| <Grid item xs={12} md={12} sx={{ mt: 2 }}><Divider fullWidth></Divider></Grid> | |||
| <Grid item xs={12} md={12} sx={{ mt: 2 }}><Divider sx={{ width: '100%' }} /></Grid> | |||
| <Grid item xs={12} md={12}><Typography variant="h5">Simplified Chinese</Typography></Grid> | |||
| <Grid item xs={12} md={12}> | |||
| <Grid container alignItems={"center"} xs={12} sm={12} md={12} lg={12} sx={{ mb: 2 }}> | |||
| @@ -116,7 +116,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| <Grid item xs={12} s={6} md={6} lg={4} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <Grid container> | |||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateFrom" | |||
| @@ -148,7 +148,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| </Grid> | |||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateTo" | |||
| @@ -195,7 +195,9 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| <Grid item sx={{ ml: 3 }}> | |||
| <Button | |||
| variant="contained" | |||
| color="cancel" | |||
| onClick={resetForm} | |||
| aria-label={intl.formatMessage({ id: 'reset' })} | |||
| > | |||
| <FormattedMessage id="reset"></FormattedMessage> | |||
| </Button> | |||
| @@ -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); | |||
| @@ -74,7 +75,7 @@ const UserSearchPage_Individual = () => { | |||
| <Grid item xs={12}> | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{ "textShadow": "0px 0px 25px #0C489E" }}><FormattedMessage id="announcement" /></Typography> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{ "textShadow": "0px 0px 25px #0C489E" }}><FormattedMessage id="announcement" /></Typography> | |||
| </Stack> | |||
| </div> | |||
| </Grid> | |||
| @@ -43,7 +43,7 @@ const AuditLogSearchForm = ({ applySearch, searchCriteria, onGridReady}) => { | |||
| const marginBottom = 2.5; | |||
| const { reset, register, handleSubmit } = useForm() | |||
| const { reset, register, handleSubmit, getValues } = useForm() | |||
| React.useEffect(() => { | |||
| setFromDateValue(minDate); | |||
| @@ -53,21 +53,26 @@ const AuditLogSearchForm = ({ applySearch, searchCriteria, onGridReady}) => { | |||
| setToDateValue(maxDate); | |||
| }, [maxDate]); | |||
| const onSubmit = (data) => { | |||
| /** Same filter fields as the list API; must reflect current form/UI state (not parent `searchCriteria`). */ | |||
| const getCurrentFilterParams = () => { | |||
| let sentDateFrom = ""; | |||
| let sentDateTo = ""; | |||
| if (fromDateValue != "dd / mm / yyyy" && toDateValue != "dd / mm / yyyy") { | |||
| sentDateFrom = DateUtils.dateValue(fromDateValue) | |||
| sentDateTo = DateUtils.dateValue(toDateValue) | |||
| sentDateFrom = DateUtils.dateValue(fromDateValue); | |||
| sentDateTo = DateUtils.dateValue(toDateValue); | |||
| } | |||
| const temp = { | |||
| username: data.userName, | |||
| return { | |||
| username: getValues("userName"), | |||
| modifiedTo: sentDateTo, | |||
| modifiedFrom: sentDateFrom, | |||
| start:0, | |||
| limit:10 | |||
| }; | |||
| }; | |||
| const onSubmit = () => { | |||
| const temp = { | |||
| ...getCurrentFilterParams(), | |||
| start: 0, | |||
| limit: 10, | |||
| }; | |||
| applySearch(temp); | |||
| }; | |||
| @@ -81,7 +86,7 @@ const AuditLogSearchForm = ({ applySearch, searchCriteria, onGridReady}) => { | |||
| setOnDownload(true) | |||
| HttpUtils.fileDownload({ | |||
| url: UrlUtils.AUDIT_LOG_EXPORT, | |||
| params: searchCriteria, | |||
| params: getCurrentFilterParams(), | |||
| onResponse:()=>{ | |||
| setOnDownload(false) | |||
| }, | |||
| @@ -13,7 +13,7 @@ import * as DateUtils from "utils/DateUtils"; | |||
| import * as UrlUtils from "utils/ApiPathConst"; | |||
| import * as HttpUtils from "utils/HttpUtils"; | |||
| import { useNavigate } from "react-router-dom"; | |||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||
| import { notifyActionError } from 'utils/CommonFunction'; | |||
| import { PNSPS_BUTTON_THEME } from "../../../themes/buttonConst"; | |||
| import { ThemeProvider } from "@emotion/react"; | |||
| import { useIntl } from "react-intl"; | |||
| @@ -118,8 +118,11 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData, _paymentCount, _p | |||
| params: { | |||
| "dnIdList": dnIdList | |||
| }, | |||
| onSuccess: function () { | |||
| notifyDownloadSuccess(); | |||
| onResponse: function () { | |||
| // 200: browser handles save; no success toast | |||
| }, | |||
| onError: function () { | |||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||
| } | |||
| }); | |||
| } | |||
| @@ -186,6 +189,10 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData, _paymentCount, _p | |||
| }} | |||
| /> | |||
| )} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| <Grid item > | |||
| @@ -194,7 +201,7 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData, _paymentCount, _p | |||
| variant="contained" | |||
| onClick={onSubmit} | |||
| color="success" | |||
| minWidth={150} | |||
| sx={{ minWidth: 150 }} | |||
| > | |||
| Create | |||
| </Button> | |||
| @@ -15,11 +15,12 @@ import * as StatusUtils from "utils/statusUtils/PublicNoteStatusUtils"; | |||
| import * as HttpUtils from "utils/HttpUtils"; | |||
| import DownloadIcon from '@mui/icons-material/Download'; | |||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||
| import { notifyActionError } from 'utils/CommonFunction'; | |||
| import { useIntl } from 'react-intl'; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const ApplicationDetailCard = ({ data }) => { | |||
| const intl = useIntl(); | |||
| const [appDetail, setAppDetails] = React.useState({}); | |||
| React.useEffect(() => { | |||
| @@ -33,8 +34,11 @@ const ApplicationDetailCard = ({ data }) => { | |||
| fileId: appDetail.appFileId, | |||
| skey: appDetail.appSkey, | |||
| filename: appDetail.appFilename, | |||
| onResponse: function () {}, | |||
| onError: function () { | |||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||
| } | |||
| }); | |||
| notifyDownloadSuccess(); | |||
| }; | |||
| return ( | |||
| @@ -14,12 +14,13 @@ import Loadable from 'components/Loadable'; | |||
| const MainCard = Loadable(React.lazy(() => import('components/MainCard'))); | |||
| import DownloadIcon from '@mui/icons-material/Download'; | |||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||
| import { notifyActionError } from 'utils/CommonFunction'; | |||
| import { useIntl } from 'react-intl'; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const DnDetailCard = ({ data }) => { | |||
| const intl = useIntl(); | |||
| const [dnData, setDnData] = React.useState({}); | |||
| React.useEffect(() => { | |||
| @@ -33,8 +34,9 @@ const DnDetailCard = ({ data }) => { | |||
| fileId: dnData.fileId, | |||
| skey: dnData.skey, | |||
| filename: dnData.filename, | |||
| onResponse: function () { | |||
| notifyDownloadSuccess(); | |||
| onResponse: function () {}, | |||
| onError: function () { | |||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||
| } | |||
| }); | |||
| }; | |||
| @@ -187,6 +187,10 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData }) => { | |||
| }} | |||
| /> | |||
| )} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| @@ -197,7 +201,7 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData }) => { | |||
| onClick={onSubmit} | |||
| color="success" | |||
| disabled={waitDownload} | |||
| minWidth={150} | |||
| sx={{ minWidth: 150 }} | |||
| > | |||
| Export | |||
| </Button> | |||
| @@ -13,7 +13,7 @@ import * as FormatUtils from "utils/FormatUtils"; | |||
| import * as StatusUtils from "utils/statusUtils/DnStatus"; | |||
| import { useNavigate } from "react-router-dom"; | |||
| import { FiDataGrid } from "components/FiDataGrid"; | |||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||
| import { notifyActionError } from 'utils/CommonFunction'; | |||
| import { | |||
| DEMAND_NOTE_EXPORT, | |||
| DEMAND_NOTE_SEND, | |||
| @@ -26,10 +26,11 @@ import * as HttpUtils from "utils/HttpUtils"; | |||
| import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | |||
| import { ThemeProvider } from "@emotion/react"; | |||
| import { isGrantedAny } from "auth/utils"; | |||
| import { useIntl } from "react-intl"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function SearchDemandNote({ applySearch, searchCriteria, applyGridOnReady }) { | |||
| const intl = useIntl(); | |||
| const [isConfirmPopUp, setConfirmPopUp] = useState(false); | |||
| const [isRevokePopUp, setRevokePopUp] = useState(false); | |||
| const [isSendPopUp, setSendPopUp] = useState(false); | |||
| @@ -80,8 +81,9 @@ export default function SearchDemandNote({ applySearch, searchCriteria, applyGri | |||
| params: { | |||
| dnIdList: idList | |||
| }, | |||
| onSuccess: function () { | |||
| notifyDownloadSuccess(); | |||
| onResponse: function () {}, | |||
| onError: function () { | |||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||
| } | |||
| }); | |||
| } | |||
| @@ -92,11 +94,12 @@ export default function SearchDemandNote({ applySearch, searchCriteria, applyGri | |||
| fileId: params.row.fileId, | |||
| skey: params.row.skey, | |||
| filename: params.row.filename, | |||
| onResponse:()=>{ | |||
| setOnDownload(false) | |||
| onResponse: () => { | |||
| setOnDownload(false); | |||
| }, | |||
| onError:()=>{ | |||
| setOnDownload(false) | |||
| onError: () => { | |||
| setOnDownload(false); | |||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||
| } | |||
| }); | |||
| }; | |||
| @@ -224,6 +224,10 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| }} | |||
| /> | |||
| )} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| @@ -282,6 +286,10 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| {params.children} | |||
| </Grid> | |||
| )} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| : <></> | |||
| @@ -435,7 +443,7 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| renderInput={(params) => ( | |||
| <TextField | |||
| {...params} | |||
| @@ -445,6 +453,10 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| }} | |||
| /> | |||
| )} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| @@ -66,8 +66,7 @@ export default function SearchDemandNote({ searchCriteria, applyGridOnReady,appl | |||
| width: isMdOrLg ? 'auto' : 175, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderCell: (params) => { | |||
| return [StatusUtils.getStatus_i18n(params, locale) ] | |||
| return StatusUtils.getStatus_i18n(params, locale); | |||
| }, | |||
| }, | |||
| { | |||
| @@ -180,6 +180,10 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData, onG | |||
| }} | |||
| /> | |||
| )} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| @@ -210,7 +214,7 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData, onG | |||
| </Grid> | |||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateFrom" | |||
| @@ -237,7 +241,7 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData, onG | |||
| </Grid> | |||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateTo" | |||
| @@ -288,11 +292,13 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData, onG | |||
| <TextField | |||
| {...params} | |||
| label={intl.formatMessage({ id: 'status' })} | |||
| InputLabelProps={{ shrink: true }} | |||
| /> | |||
| )} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| @@ -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({}); | |||
| @@ -101,7 +102,7 @@ const SearchPage_DemandNote_Pub = () => { | |||
| <Grid item xs={12}> | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <FormattedMessage id="paymentInfoRecord" /> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -180,7 +180,7 @@ const SearchPublicNoticeForm = ({ applySearch, generateXML, searchCriteria, onGr | |||
| filterOptions={(options) => options} | |||
| options={ComboData.payMethod} | |||
| value={payMethod} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| inputValue={payMethod?.label ? payMethod?.label : ""} | |||
| onChange={(event, newValue) => { | |||
| if(newValue==null){ | |||
| @@ -197,11 +197,9 @@ const SearchPublicNoticeForm = ({ applySearch, generateXML, searchCriteria, onGr | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label="Payment Method" | |||
| InputLabelProps={{ shrink: true }} | |||
| /> | |||
| )} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| /> | |||
| </Grid> | |||
| @@ -4,6 +4,7 @@ import { | |||
| Typography, | |||
| Stack, | |||
| Button, | |||
| CircularProgress, | |||
| Dialog, DialogTitle, DialogContent, DialogActions, | |||
| } from '@mui/material'; | |||
| import MainCard from "components/MainCard"; | |||
| @@ -55,6 +56,8 @@ const Index = () => { | |||
| const [isPreviewLoading, setIsPreviewLoading] = React.useState(false); | |||
| const [isPopUp, setIsPopUp] = React.useState(false); | |||
| const [isXmlDialogSubmitting, setIsXmlDialogSubmitting] = React.useState(false); | |||
| const xmlDownloadInFlightRef = React.useRef(false); | |||
| const [downloadInput, setDownloadInput] = React.useState(); | |||
| const [selectedIds, setSelectedIds] = React.useState([]); | |||
| @@ -66,6 +69,13 @@ const Index = () => { | |||
| setInputDateValue(inputDate); | |||
| }, [inputDate]); | |||
| React.useEffect(() => { | |||
| if (!isPopUp) { | |||
| xmlDownloadInFlightRef.current = false; | |||
| setIsXmlDialogSubmitting(false); | |||
| } | |||
| }, [isPopUp]); | |||
| React.useEffect(() => { | |||
| setOnReady(true); | |||
| }, [searchCriteria]); | |||
| @@ -94,66 +104,61 @@ const Index = () => { | |||
| function downloadXML() { | |||
| console.log(selectedIds.join(',')) | |||
| setIsPopUp(false) | |||
| if (xmlDownloadInFlightRef.current) return; | |||
| xmlDownloadInFlightRef.current = true; | |||
| console.log(selectedIds.join(',')); | |||
| setIsXmlDialogSubmitting(true); | |||
| let sentDateFrom = ""; | |||
| if (inputDateValue != "dd / mm / yyyy") { | |||
| sentDateFrom = DateUtils.dateValue(inputDateValue) | |||
| sentDateFrom = DateUtils.dateValue(inputDateValue); | |||
| } | |||
| HttpUtils.get({ | |||
| url: GEN_GFMIS_XML + "/today", | |||
| params:{ | |||
| params: { | |||
| dateTo: downloadInput.dateTo, | |||
| dateFrom: downloadInput.dateFrom, | |||
| inputDate: sentDateFrom, | |||
| paymentId: selectedIds.join(',') | |||
| }, | |||
| onSuccess: (responseData) => { | |||
| // console.log(responseData) | |||
| const parser = new DOMParser(); | |||
| const xmlDoc = parser.parseFromString(responseData, 'application/xml'); | |||
| // Get the DCBHeader element | |||
| const dcbHeader = xmlDoc.querySelector("DCBHeader"); | |||
| // Get the Receipt and Allocation elements | |||
| const receiptElement = dcbHeader.querySelector("Receipt"); | |||
| const allocationElement = dcbHeader.querySelector("Allocation"); | |||
| const paymentMethodElements = Array.from(dcbHeader.querySelectorAll("PaymentMethod")); | |||
| // Remove existing elements from DCBHeader | |||
| dcbHeader.innerHTML = ""; | |||
| dcbHeader.appendChild(receiptElement); | |||
| dcbHeader.appendChild(allocationElement); | |||
| if (paymentMethodElements) { | |||
| paymentMethodElements.forEach((paymentMethodElement) => { | |||
| dcbHeader.appendChild(paymentMethodElement); | |||
| }); | |||
| dcbHeader.appendChild(paymentMethodElement); | |||
| }); | |||
| } | |||
| const updatedXmlString = new XMLSerializer().serializeToString(xmlDoc); | |||
| const filename = xmlDoc.querySelector('FileHeader').getAttribute('H_Filename'); | |||
| // console.log(updatedXmlString) | |||
| const blob = new Blob([updatedXmlString], { type: 'application/xml' }); | |||
| // Create a download link | |||
| const link = document.createElement('a'); | |||
| link.href = URL.createObjectURL(blob); | |||
| link.download = filename+'.xml'; | |||
| // Append the link to the document body | |||
| link.download = filename + '.xml'; | |||
| document.body.appendChild(link); | |||
| // Programmatically click the link to trigger the download | |||
| link.click(); | |||
| // Clean up the link | |||
| document.body.removeChild(link); | |||
| setIsPopUp(false); | |||
| }, | |||
| onFinally: () => { | |||
| xmlDownloadInFlightRef.current = false; | |||
| setIsXmlDialogSubmitting(false); | |||
| } | |||
| }); | |||
| // open(UrlUtils.GEN_GFMIS_XML + "/today?online=true") | |||
| } | |||
| }); | |||
| } | |||
| function applySearch(input) { | |||
| @@ -263,7 +268,11 @@ const Index = () => { | |||
| </Grid> | |||
| <Dialog | |||
| open={isPopUp} | |||
| onClose={() => setIsPopUp(false)} | |||
| onClose={() => { | |||
| if (isXmlDialogSubmitting) return; | |||
| setIsPopUp(false); | |||
| }} | |||
| disableEscapeKeyDown={isXmlDialogSubmitting} | |||
| PaperProps={{ | |||
| sx: { | |||
| minWidth: '40vw', | |||
| @@ -300,8 +309,22 @@ const Index = () => { | |||
| </LocalizationProvider> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setIsPopUp(false)}><Typography variant="h5">Cancel</Typography></Button> | |||
| <Button onClick={() => downloadXML()}><Typography variant="h5">Confirm</Typography></Button> | |||
| <Button | |||
| onClick={() => { | |||
| if (isXmlDialogSubmitting) return; | |||
| setIsPopUp(false); | |||
| }} | |||
| disabled={isXmlDialogSubmitting} | |||
| > | |||
| <Typography variant="h5">Cancel</Typography> | |||
| </Button> | |||
| <Button | |||
| onClick={() => downloadXML()} | |||
| disabled={isXmlDialogSubmitting} | |||
| startIcon={isXmlDialogSubmitting ? <CircularProgress color="inherit" size={20} /> : null} | |||
| > | |||
| <Typography variant="h5">Confirm</Typography> | |||
| </Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| </Grid> | |||
| @@ -76,7 +76,11 @@ const SearchGazetteIssueForm = ({ applyExport, comboData, waitDownload}) => { | |||
| // defaultValue={selectedYear} | |||
| options={comboList} | |||
| // disabled={checkCountry} | |||
| getOptionLabel={(option) => option.label ? option.label : ""} | |||
| getOptionLabel={(option) => | |||
| option != null && typeof option === "object" && option.label != null | |||
| ? String(option.label) | |||
| : "" | |||
| } | |||
| onChange={(event, newValue) => { | |||
| setSelectedYear(newValue); | |||
| }} | |||
| @@ -74,7 +74,11 @@ const SearchGazetteIssueForm = ({ applySearch, comboData, onGridReady}) => { | |||
| // defaultValue={selectedYear} | |||
| options={comboList} | |||
| // disabled={checkCountry} | |||
| getOptionLabel={(option) => option.label ? option.label : ""} | |||
| getOptionLabel={(option) => | |||
| option != null && typeof option === "object" && option.label != null | |||
| ? String(option.label) | |||
| : "" | |||
| } | |||
| onChange={(event, newValue) => { | |||
| setSelectedYear(newValue); | |||
| }} | |||
| @@ -31,10 +31,12 @@ import { ThemeProvider } from "@emotion/react"; | |||
| import { dateStr_Year } from "utils/DateUtils"; | |||
| import { notifySaveSuccess } from 'utils/CommonFunction'; | |||
| import { isGrantedAny } from "auth/utils"; | |||
| import { useIntl } from 'react-intl'; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const Index = () => { | |||
| const intl = useIntl(); | |||
| const [comboData, setComboData] = React.useState([]); | |||
| const [holidayComboData, setHolidayComboData] = React.useState([]); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| @@ -50,6 +52,7 @@ const Index = () => { | |||
| const [waitDownload, setWaitDownload] = React.useState(false); | |||
| const [isWarningPopUp, setIsWarningPopUp] = React.useState(false); | |||
| const [warningText, setWarningText] = React.useState(""); | |||
| const fileInputRef = React.useRef(null); | |||
| React.useEffect(() => { | |||
| setOnSearchReady(false); | |||
| @@ -184,13 +187,27 @@ const Index = () => { | |||
| <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={2} sx={{ ml: 2, mt: 1 }}> | |||
| <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | |||
| <Button | |||
| component="label" | |||
| variant="contained" | |||
| size="large" | |||
| disabled={waitImport} | |||
| type="button" | |||
| onClick={() => { | |||
| if (fileInputRef.current) { | |||
| fileInputRef.current.click(); | |||
| } | |||
| }} | |||
| onKeyDown={(event) => { | |||
| if (event.key === 'Enter' || event.key === ' ') { | |||
| event.preventDefault(); | |||
| if (fileInputRef.current) { | |||
| fileInputRef.current.click(); | |||
| } | |||
| } | |||
| }} | |||
| > | |||
| <Typography variant="h5">Upload Files</Typography> | |||
| <input | |||
| </Button> | |||
| <input | |||
| id="uploadFileBtn" | |||
| name="file" | |||
| type="file" | |||
| @@ -198,9 +215,9 @@ const Index = () => { | |||
| hidden | |||
| disabled={waitImport} | |||
| onChange={readFile} | |||
| aria-label="Upload Excel file (.xlsx)" | |||
| /> | |||
| </Button> | |||
| aria-label={intl.formatMessage({ id: 'ariaUploadExcelFile' })} | |||
| ref={fileInputRef} | |||
| /> | |||
| </ThemeProvider> | |||
| </Stack> | |||
| </Grid> | |||
| @@ -9,14 +9,19 @@ import { dateStr } from "utils/DateUtils"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| function holidayRowsFromResponse(recordList) { | |||
| if (Array.isArray(recordList)) return recordList; | |||
| if (recordList && Array.isArray(recordList.records)) return recordList.records; | |||
| return []; | |||
| } | |||
| export default function HolidayTable({ recordList, applyGridOnReady }) { | |||
| const [rows, setRows] = React.useState(recordList); | |||
| const [rows, setRows] = React.useState(() => holidayRowsFromResponse(recordList)); | |||
| // const navigate = useNavigate() | |||
| useEffect(() => { | |||
| // console.log(recordList) | |||
| setRows(recordList.records); | |||
| setRows(holidayRowsFromResponse(recordList)); | |||
| }, [recordList]); | |||
| const columns = [ | |||
| @@ -17,7 +17,7 @@ import {ThemeProvider} from "@emotion/react"; | |||
| const SearchHolidayForm = ({ applySearch, comboData, onGridReady}) => { | |||
| const [selectedYear, setSelectedYear] = React.useState([]); | |||
| const [selectedYear, setSelectedYear] = React.useState(null); | |||
| // const [defaultYear, setDefaultYear] = React.useState(searchCriteria.year); | |||
| const [comboList, setComboList] = React.useState([]); | |||
| // const [onReady, setOnReady] = React.useState(false); | |||
| @@ -27,7 +27,7 @@ const SearchHolidayForm = ({ applySearch, comboData, onGridReady}) => { | |||
| handleSubmit } = useForm() | |||
| const onSubmit = () => { | |||
| if (selectedYear !=null){ | |||
| if (selectedYear != null) { | |||
| const temp = { | |||
| year: selectedYear.label, | |||
| }; | |||
| @@ -40,8 +40,8 @@ const SearchHolidayForm = ({ applySearch, comboData, onGridReady}) => { | |||
| // console.log(comboData) | |||
| // const labelValue = comboData.find(obj => obj.label === searchCriteria.year); | |||
| // console.log(labelValue) | |||
| if(selectedYear.length == 0){ | |||
| setSelectedYear(comboData[0]) | |||
| if (!selectedYear) { | |||
| setSelectedYear(comboData[0]); | |||
| } | |||
| setComboList(comboData) | |||
| // setSelectedYear(searchCriteria.dateFrom) | |||
| @@ -74,7 +74,11 @@ const SearchHolidayForm = ({ applySearch, comboData, onGridReady}) => { | |||
| // defaultValue={selectedYear} | |||
| options={comboList} | |||
| // disabled={checkCountry} | |||
| getOptionLabel={(option) => option.label ? option.label : ""} | |||
| getOptionLabel={(option) => | |||
| option != null && typeof option === "object" && option.label != null | |||
| ? String(option.label) | |||
| : "" | |||
| } | |||
| onChange={(event, newValue) => { | |||
| setSelectedYear(newValue); | |||
| }} | |||
| @@ -31,11 +31,12 @@ import { ThemeProvider } from "@emotion/react"; | |||
| import { dateStr_Year } from "utils/DateUtils"; | |||
| import { notifySaveSuccess } from 'utils/CommonFunction'; | |||
| import { isGrantedAny } from "auth/utils"; | |||
| import { useIntl } from 'react-intl'; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const Index = () => { | |||
| const intl = useIntl(); | |||
| const [record, setRecord] = React.useState([]); | |||
| const [comboData, setComboData] = React.useState([]); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| @@ -50,6 +51,7 @@ const Index = () => { | |||
| const [waitDownload, setWaitDownload] = React.useState(false); | |||
| const [isWarningPopUp, setIsWarningPopUp] = React.useState(false); | |||
| const [warningText, setWarningText] = React.useState(""); | |||
| const fileInputRef = React.useRef(null); | |||
| React.useEffect(() => { | |||
| setOnSearchReady(false); | |||
| @@ -172,7 +174,7 @@ const Index = () => { | |||
| size="large" | |||
| disabled={waitDownload} | |||
| onClick={doExport} | |||
| aria-label="Export holiday template" | |||
| aria-label={intl.formatMessage({ id: 'ariaExportHolidayTemplate' })} | |||
| > | |||
| <Typography variant="h5">Export</Typography> | |||
| </Button> | |||
| @@ -180,13 +182,27 @@ const Index = () => { | |||
| {isGrantedAny(["MAINTAIN_GAZETTE_ISSUE"]) ? | |||
| <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | |||
| <Button | |||
| component="label" | |||
| variant="contained" | |||
| size="large" | |||
| disabled={waitImport} | |||
| type="button" | |||
| onClick={() => { | |||
| if (fileInputRef.current) { | |||
| fileInputRef.current.click(); | |||
| } | |||
| }} | |||
| onKeyDown={(event) => { | |||
| if (event.key === 'Enter' || event.key === ' ') { | |||
| event.preventDefault(); | |||
| if (fileInputRef.current) { | |||
| fileInputRef.current.click(); | |||
| } | |||
| } | |||
| }} | |||
| > | |||
| <Typography variant="h5">Upload Files</Typography> | |||
| <input | |||
| </Button> | |||
| <input | |||
| id="uploadFileBtn" | |||
| name="file" | |||
| type="file" | |||
| @@ -194,9 +210,9 @@ const Index = () => { | |||
| hidden | |||
| disabled={waitImport} | |||
| onChange={readFile} | |||
| aria-label="Upload Excel file (.xlsx)" | |||
| /> | |||
| </Button> | |||
| aria-label={intl.formatMessage({ id: 'ariaUploadExcelFile' })} | |||
| ref={fileInputRef} | |||
| /> | |||
| </ThemeProvider> | |||
| : null | |||
| } | |||
| @@ -11,7 +11,7 @@ import { | |||
| Button | |||
| } from '@mui/material'; | |||
| import * as React from "react"; | |||
| import { GET_JVM_INFO } from "utils/ApiPathConst"; | |||
| import { GET_JVM_INFO, GET_NOTIFICATION_QUEUE_STATUS } from "utils/ApiPathConst"; | |||
| import axios from "axios"; | |||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | |||
| @@ -20,7 +20,11 @@ const JVMDefault = () => { | |||
| const [jvmInfo, setJvmInfo] = React.useState(null); | |||
| const [loading, setLoading] = React.useState(true); | |||
| const [error, setError] = React.useState(null); | |||
| const [queueStatus, setQueueStatus] = React.useState(null); | |||
| const [queueLoading, setQueueLoading] = React.useState(false); | |||
| const [queueError, setQueueError] = React.useState(null); | |||
| const fetchJvmInfo = () => { | |||
| setLoading(true); | |||
| setError(null); | |||
| @@ -37,6 +41,24 @@ const JVMDefault = () => { | |||
| }); | |||
| }; | |||
| const fetchNotificationQueueStatus = () => { | |||
| setQueueLoading(true); | |||
| setQueueError(null); | |||
| setQueueStatus(null); | |||
| axios.get(`${GET_NOTIFICATION_QUEUE_STATUS}`) | |||
| .then((response) => { | |||
| if (response.status === 200) { | |||
| setQueueStatus(response.data); | |||
| } | |||
| }) | |||
| .catch(err => { | |||
| setQueueError(err); | |||
| }) | |||
| .finally(() => { | |||
| setQueueLoading(false); | |||
| }); | |||
| }; | |||
| React.useEffect(() => { | |||
| localStorage.setItem('searchCriteria', ""); | |||
| setLoading(false); | |||
| @@ -66,25 +88,40 @@ const JVMDefault = () => { | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="space-between" alignItems="center"> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{ "textShadow": "0px 0px 25px #0C489E" }}> | |||
| JVM Information | |||
| System Background Status | |||
| </Typography> | |||
| </Stack> | |||
| </div> | |||
| </Grid> | |||
| <Grid item xs={12} ml={15} mb={2} mt={2}> | |||
| <Button | |||
| size="large" | |||
| variant="contained" | |||
| type="submit" | |||
| sx={{ | |||
| textTransform: 'capitalize', | |||
| alignItems: 'end' | |||
| }} | |||
| onClick={fetchJvmInfo} | |||
| disabled={loading} | |||
| > | |||
| <Typography variant="h5">JVM Info</Typography> | |||
| </Button> | |||
| <Stack direction="row" spacing={2}> | |||
| <Button | |||
| size="large" | |||
| variant="contained" | |||
| type="submit" | |||
| sx={{ | |||
| textTransform: 'capitalize', | |||
| alignItems: 'end' | |||
| }} | |||
| onClick={fetchJvmInfo} | |||
| disabled={loading} | |||
| > | |||
| <Typography variant="h5">JVM Info</Typography> | |||
| </Button> | |||
| <Button | |||
| size="large" | |||
| variant="contained" | |||
| type="button" | |||
| sx={{ | |||
| textTransform: 'capitalize', | |||
| alignItems: 'end' | |||
| }} | |||
| onClick={fetchNotificationQueueStatus} | |||
| disabled={queueLoading} | |||
| > | |||
| <Typography variant="h5">Notification Queue Status</Typography> | |||
| </Button> | |||
| </Stack> | |||
| </Grid> | |||
| <Grid item xs={12} ml={15} mb={2} mt={2}> | |||
| <Paper elevation={3} sx={{ p: 2, bgcolor: 'background.paper' }}> | |||
| @@ -114,6 +151,35 @@ const JVMDefault = () => { | |||
| )} | |||
| </Paper> | |||
| </Grid> | |||
| <Grid item xs={12} ml={15} mb={2} mt={2}> | |||
| <Paper elevation={3} sx={{ p: 2, bgcolor: 'background.paper' }}> | |||
| <Typography variant="h6" gutterBottom>Notification Queue Status</Typography> | |||
| {queueLoading ? ( | |||
| <Box display="flex" justifyContent="center" alignItems="center" minHeight={120}> | |||
| <CircularProgress /> | |||
| </Box> | |||
| ) : queueError ? ( | |||
| <Typography color="error">Error: {queueError.message}</Typography> | |||
| ) : queueStatus ? ( | |||
| <Box | |||
| component="pre" | |||
| sx={{ | |||
| p: 2, | |||
| borderRadius: 1, | |||
| bgcolor: 'grey.100', | |||
| overflow: 'auto', | |||
| maxHeight: 300, | |||
| fontSize: '0.875rem', | |||
| lineHeight: 1.6 | |||
| }} | |||
| > | |||
| {JSON.stringify(queueStatus, null, 2)} | |||
| </Box> | |||
| ) : ( | |||
| <Typography color="text.secondary">Click "Notification Queue Status" to load data.</Typography> | |||
| )} | |||
| </Paper> | |||
| </Grid> | |||
| </Grid> | |||
| ); | |||
| }; | |||
| @@ -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() | |||
| @@ -72,7 +76,7 @@ const Index = () => { | |||
| <Grid item xs={12} width="100%"> | |||
| <div style={BackgroundHead} width="100%"> | |||
| <Stack direction="row" height='70px'> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{ pt: 2 }}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{ pt: 2 }}> | |||
| <FormattedMessage id="msgDetails" /> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -83,7 +87,7 @@ const Index = () => { | |||
| <Grid container justifyContent="flex-start" alignItems="center" > | |||
| <center> | |||
| <Grid item xs={12} md={12} sx={{p:2}} > | |||
| <Typography variant="h3" sx={{ textAlign: "left", borderBottom: "1px solid black" }}> | |||
| <Typography component="h2" variant="h3" sx={{ textAlign: "left", borderBottom: "1px solid black" }}> | |||
| {record?.subject} | |||
| </Typography> | |||
| <Typography sx={{p:1}} align="justify">{DateUtils.datetimeStr(record?.sentDate)}</Typography> | |||
| @@ -91,7 +95,7 @@ const Index = () => { | |||
| <div dangerouslySetInnerHTML={{__html: record?.content}}></div> | |||
| </Typography> | |||
| <Typography variant="h4" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | |||
| <Typography component="h3" variant="h4" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | |||
| <Button | |||
| component="span" | |||
| variant="contained" | |||
| @@ -119,7 +119,7 @@ const SearchForm = ({ applySearch, searchCriteria, onGridReady }) => { | |||
| <Grid item xs={12} s={6} md={6} lg={4} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <Grid container> | |||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateFrom" | |||
| @@ -151,7 +151,7 @@ const SearchForm = ({ applySearch, searchCriteria, onGridReady }) => { | |||
| </Grid> | |||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateTo" | |||
| @@ -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); | |||
| @@ -87,7 +88,7 @@ const Index = () => { | |||
| <Grid item xs={12}> | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <FormattedMessage id="systemMessage"/> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -2,7 +2,7 @@ | |||
| import { | |||
| Grid, Button, Checkbox, FormControlLabel, Typography, | |||
| Dialog, DialogTitle, DialogContent, DialogActions, | |||
| FormHelperText, TextField, | |||
| FormHelperText, TextField, CircularProgress, | |||
| } from '@mui/material'; | |||
| // import { FormControlLabel } from '@material-ui/core'; | |||
| import MainCard from "components/MainCard"; | |||
| @@ -111,56 +111,58 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| onSubmit: (values) => { | |||
| if (values.country == null) { | |||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInCountry' })) | |||
| } else { | |||
| if (values.country.type == "hongKong" && values.district == null) { | |||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInDistrict' })) | |||
| } else { | |||
| let sentDateFrom = ""; | |||
| if (fromDateValue == null) { | |||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInBusinessRegCertValidityDate' })) | |||
| } else { | |||
| sentDateFrom = DateUtils.dateValue(fromDateValue) | |||
| HttpUtils.post({ | |||
| url: UrlUtils.POST_ORG_SAVE_PATH, | |||
| params: { | |||
| id: id > 0 ? id : null, | |||
| enCompanyName: values.enCompanyName, | |||
| chCompanyName: values.chCompanyName, | |||
| orgShortName: values.orgShortName === "N/A" ? "" : values.orgShortName, | |||
| brNo: values.brNo, | |||
| // brExpiryDate: values.brExpiryDate, | |||
| brExpiryDate: sentDateFrom, | |||
| enCompanyNameTemp: values.enCompanyNameTemp, | |||
| chCompanyNameTemp: values.chCompanyNameTemp, | |||
| brExpiryDateTemp: values.brExpiryDateTemp, | |||
| contactPerson: values.contactPerson, | |||
| contactTel: { | |||
| countryCode: values.tel_countryCode, | |||
| phoneNumber: values.phoneNumber | |||
| }, | |||
| faxNo: { | |||
| countryCode: values.fax_countryCode, | |||
| faxNumber: values.faxNumber | |||
| }, | |||
| addressTemp: { | |||
| country: values.country.type, | |||
| district: values.district?.type, | |||
| addressLine1: values.addressLine1, | |||
| addressLine2: values.addressLine2, | |||
| addressLine3: values.addressLine3, | |||
| }, | |||
| creditor: values.creditor, | |||
| }, | |||
| onSuccess: function () { | |||
| notifySaveSuccess() | |||
| loadDataFun(); | |||
| setEditMode(false); | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| return; | |||
| } | |||
| if (values.country.type == "hongKong" && values.district == null) { | |||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInDistrict' })) | |||
| return; | |||
| } | |||
| if (fromDateValue == null) { | |||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInBusinessRegCertValidityDate' })) | |||
| return; | |||
| } | |||
| const sentDateFrom = DateUtils.dateValue(fromDateValue); | |||
| return new Promise((resolve, reject) => { | |||
| HttpUtils.post({ | |||
| url: UrlUtils.POST_ORG_SAVE_PATH, | |||
| params: { | |||
| id: id > 0 ? id : null, | |||
| enCompanyName: values.enCompanyName, | |||
| chCompanyName: values.chCompanyName, | |||
| orgShortName: values.orgShortName === "N/A" ? "" : values.orgShortName, | |||
| brNo: values.brNo, | |||
| brExpiryDate: sentDateFrom, | |||
| enCompanyNameTemp: values.enCompanyNameTemp, | |||
| chCompanyNameTemp: values.chCompanyNameTemp, | |||
| brExpiryDateTemp: values.brExpiryDateTemp, | |||
| contactPerson: values.contactPerson, | |||
| contactTel: { | |||
| countryCode: values.tel_countryCode, | |||
| phoneNumber: values.phoneNumber | |||
| }, | |||
| faxNo: { | |||
| countryCode: values.fax_countryCode, | |||
| faxNumber: values.faxNumber | |||
| }, | |||
| addressTemp: { | |||
| country: values.country.type, | |||
| district: values.district?.type, | |||
| addressLine1: values.addressLine1, | |||
| addressLine2: values.addressLine2, | |||
| addressLine3: values.addressLine3, | |||
| }, | |||
| creditor: values.creditor, | |||
| }, | |||
| onSuccess: function () { | |||
| notifySaveSuccess() | |||
| loadDataFun(); | |||
| setEditMode(false); | |||
| resolve(); | |||
| }, | |||
| onFail: () => reject(), | |||
| onError: () => reject(), | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| @@ -267,6 +269,8 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| variant="contained" | |||
| type="submit" | |||
| color="success" | |||
| disabled={formik.isSubmitting} | |||
| startIcon={formik.isSubmitting ? <CircularProgress color="inherit" size={18} /> : null} | |||
| > | |||
| Create | |||
| </Button> | |||
| @@ -289,6 +293,8 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| variant="contained" | |||
| type="submit" | |||
| color="success" | |||
| disabled={formik.isSubmitting} | |||
| startIcon={formik.isSubmitting ? <CircularProgress color="inherit" size={18} /> : null} | |||
| > | |||
| Save | |||
| </Button> | |||
| @@ -440,7 +446,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| value={fromDate != null ? DateUtils.dateStr(fromDate) : DateUtils.dateStr(currentFromDate)} | |||
| disabled={true} | |||
| /> : | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="brExpiryDate" | |||
| @@ -4,7 +4,7 @@ import { | |||
| // Checkbox, FormControlLabel, | |||
| Typography, | |||
| Dialog, DialogTitle, DialogContent, DialogActions, | |||
| FormHelperText | |||
| FormHelperText, CircularProgress, | |||
| } from '@mui/material'; | |||
| // import { FormControlLabel } from '@material-ui/core'; | |||
| import MainCard from "components/MainCard"; | |||
| @@ -61,42 +61,46 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| phoneNumber: yup.string().min(8, displayErrorMsg(intl.formatMessage({ id: 'requiredValidNumber' }))).required(displayErrorMsg(intl.formatMessage({ id: 'requireContactNumber' }))), | |||
| faxNumber: yup.string().min(8, displayErrorMsg(intl.formatMessage({ id: 'require8Number' }))).nullable(), | |||
| }), | |||
| onSubmit: values => { | |||
| onSubmit: (values) => { | |||
| if (values.country == null) { | |||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInCountry' })) | |||
| } else { | |||
| if (values.country.type == "hongKong" && values.district == null) { | |||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInDistrict' })) | |||
| } else { | |||
| HttpUtils.post({ | |||
| url: UrlUtils.POST_PUB_ORG_SAVE_PATH, | |||
| params: { | |||
| contactPerson: values.contactPerson, | |||
| contactTel: { | |||
| countryCode: values.tel_countryCode, | |||
| phoneNumber: values.phoneNumber | |||
| }, | |||
| faxNo: { | |||
| countryCode: values.fax_countryCode, | |||
| faxNumber: values.faxNumber | |||
| }, | |||
| addressTemp: { | |||
| country: values.country.type, | |||
| district: values.district?.type, | |||
| addressLine1: values.addressLine1, | |||
| addressLine2: values.addressLine2, | |||
| addressLine3: values.addressLine3, | |||
| }, | |||
| //creditor: values.creditor, | |||
| }, | |||
| onSuccess: function () { | |||
| notifySaveSuccess() | |||
| loadDataFun(); | |||
| setEditMode(false); | |||
| } | |||
| }); | |||
| } | |||
| return; | |||
| } | |||
| if (values.country.type == "hongKong" && values.district == null) { | |||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInDistrict' })) | |||
| return; | |||
| } | |||
| return new Promise((resolve, reject) => { | |||
| HttpUtils.post({ | |||
| url: UrlUtils.POST_PUB_ORG_SAVE_PATH, | |||
| params: { | |||
| contactPerson: values.contactPerson, | |||
| contactTel: { | |||
| countryCode: values.tel_countryCode, | |||
| phoneNumber: values.phoneNumber | |||
| }, | |||
| faxNo: { | |||
| countryCode: values.fax_countryCode, | |||
| faxNumber: values.faxNumber | |||
| }, | |||
| addressTemp: { | |||
| country: values.country.type, | |||
| district: values.district?.type, | |||
| addressLine1: values.addressLine1, | |||
| addressLine2: values.addressLine2, | |||
| addressLine3: values.addressLine3, | |||
| }, | |||
| }, | |||
| onSuccess: function () { | |||
| notifySaveSuccess() | |||
| loadDataFun(); | |||
| setEditMode(false); | |||
| resolve(); | |||
| }, | |||
| onFail: () => reject(), | |||
| onError: () => reject(), | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| @@ -147,6 +151,8 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| variant="contained" | |||
| type="submit" | |||
| color="success" | |||
| disabled={formik.isSubmitting} | |||
| startIcon={formik.isSubmitting ? <CircularProgress color="inherit" size={18} /> : null} | |||
| > | |||
| <FormattedMessage id="create" /> | |||
| </Button> | |||
| @@ -171,6 +177,8 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| variant="contained" | |||
| type="submit" | |||
| color="success" | |||
| disabled={formik.isSubmitting} | |||
| startIcon={formik.isSubmitting ? <CircularProgress color="inherit" size={18} /> : null} | |||
| > | |||
| <FormattedMessage id="save" /> | |||
| </Button> | |||
| @@ -22,6 +22,7 @@ import { | |||
| isORGLoggedIn, | |||
| isPrimaryLoggedIn | |||
| } from "utils/Utils"; | |||
| import usePageTitle from "components/usePageTitle"; | |||
| const BackgroundHead = { | |||
| backgroundImage: `url(${titleBackgroundImg})`, | |||
| @@ -42,6 +43,9 @@ import { | |||
| const OrganizationDetailPage = () => { | |||
| // Localized document title/meta for organisation details (GLD) | |||
| usePageTitle("organizationProfile"); | |||
| const params = useParams(); | |||
| const [formData, setFormData] = React.useState({}) | |||
| const [list, setList] = React.useState([]) | |||
| @@ -136,11 +140,11 @@ const OrganizationDetailPage = () => { | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| {isGLDLoggedIn()? | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| Maintain Organisation | |||
| </Typography> | |||
| : | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <FormattedMessage id="organizationProfile" /> | |||
| </Typography> | |||
| } | |||
| @@ -233,7 +233,7 @@ const OrganizationCard_loadFromUser = ({ userData, userId }) => { | |||
| <Typography variant="pnspsFormParagraphBold">{FieldUtils.notNullFieldLabel("Expiry Date:")}</Typography> | |||
| </Grid> | |||
| <Grid item xs={12} md={6} lg={6}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="brExpiryDate" | |||
| @@ -25,10 +25,14 @@ const BackgroundHead = { | |||
| backgroundColor: '#0C489E', | |||
| backgroundPosition: 'right' | |||
| } | |||
| import usePageTitle from "components/usePageTitle"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const OrganizationDetailPage_FromUser = () => { | |||
| // Localized document title/meta for organisation details (from user) | |||
| usePageTitle("organizationProfile"); | |||
| const params = useParams(); | |||
| const [formData, setFormData] = useState({}) | |||
| const [isLoading, setLoding] = useState(true); | |||
| @@ -154,7 +154,7 @@ const OrganizationSearchForm = ({ applySearch, onGridReady, searchCriteria }) => | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| renderInput={(params) => ( | |||
| <TextField | |||
| {...params} | |||
| @@ -6,6 +6,7 @@ import MainCard from "components/MainCard"; | |||
| import { useEffect, useState } from "react"; | |||
| import * as React from "react"; | |||
| import { getSearchCriteria } from "auth/utils"; | |||
| import usePageTitle from "components/usePageTitle"; | |||
| // import LoadingComponent from "../extra-pages/LoadingComponent"; | |||
| // import SearchForm from "./OrganizationSearchForm"; | |||
| @@ -29,6 +30,8 @@ const BackgroundHead = { | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const OrganizationSearchPage = () => { | |||
| // Localized document title/meta for organisation search | |||
| usePageTitle("organizationProfile"); | |||
| const [searchCriteria, setSearchCriteria] = useState({}); | |||
| const [onReady, setOnReady] = useState(false); | |||
| @@ -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(); | |||
| @@ -144,7 +147,7 @@ const Index = () => { | |||
| <Grid className="printHidden" item xs={12} width="100%"> | |||
| <div style={BackgroundHead} width="100%"> | |||
| <Stack direction="row" height='70px'> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{ pt: 2 }}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{ pt: 2 }}> | |||
| <FormattedMessage id="payDetail"/> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -160,7 +163,7 @@ const Index = () => { | |||
| </Button> | |||
| </Grid> | |||
| {/*row 1*/} | |||
| <Grid item xs={12} md={12} spacing={2} sx={{ textAlign: "center" }}> | |||
| <Grid item xs={12} md={12} sx={{ textAlign: "center" }}> | |||
| <Grid container justifyContent="center" direction="column" spacing={2} sx={{ p: 2 }} alignitems="stretch" > | |||
| <Grid item className="printOrder" xs={12} md={12} sx={{ pt: 2 }} style={{ height: '100%', order: 1 }}> | |||
| <Box xs={12} md={12} sx={{ border: '3px solid #eee', borderRadius: '10px' }} > | |||
| @@ -222,7 +222,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| filterOptions={(options) => options} | |||
| options={ComboData.paymentStatus} | |||
| value={status} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| inputValue={status?.label ? status?.label : ""} | |||
| onChange={(event, newValue) => { | |||
| if(newValue==null){ | |||
| @@ -239,11 +239,9 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label="Status" | |||
| InputLabelProps={{ shrink: true }} | |||
| /> | |||
| )} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| /> | |||
| </Grid> | |||
| @@ -256,7 +254,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| filterOptions={(options) => options} | |||
| options={ComboData.payMethod} | |||
| value={payMethod} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| inputValue={payMethod?.label ? payMethod?.label : ""} | |||
| onChange={(event, newValue) => { | |||
| if(newValue==null){ | |||
| @@ -273,11 +271,9 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label="Payment Method" | |||
| InputLabelProps={{ shrink: true }} | |||
| /> | |||
| )} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -124,7 +124,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| <Grid item xs={12} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <Grid container spacing={1}> | |||
| <Grid item xs={6}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateFrom" | |||
| @@ -152,7 +152,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateTo" | |||
| @@ -206,13 +206,16 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| <TextField | |||
| {...params} | |||
| label={intl.formatMessage({id: 'status'})} | |||
| InputLabelProps={{ ...params.InputLabelProps, shrink: true }} | |||
| /> | |||
| )} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| @@ -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)), | |||
| @@ -76,7 +78,7 @@ const Index = () => { | |||
| <Grid item xs={12}> | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <FormattedMessage id="onlinePaymentHistory"/> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -19,12 +19,23 @@ import * as ComboData from "utils/ComboData"; | |||
| import * as React from "react"; | |||
| import { useFormik } from 'formik'; | |||
| import { useNavigate } from "react-router-dom"; | |||
| import { useIntl } from 'react-intl'; | |||
| import Loadable from 'components/Loadable'; | |||
| import { notifySaveSuccess } from 'utils/CommonFunction'; | |||
| const UploadFileTable = Loadable(React.lazy(() => import('./UploadFileTable'))); | |||
| const FormPanel = ({ formData }) => { | |||
| /** Keeps Formik fields defined so inputs stay controlled before API data loads. */ | |||
| const proofFormInitialValues = { | |||
| reviseDeadline: '', | |||
| proofPaymentDeadline: '', | |||
| length: 0, | |||
| noOfPages: 0, | |||
| fee: 0, | |||
| groupType: '' | |||
| }; | |||
| const FormPanel = ({ formData }) => { | |||
| const intl = useIntl(); | |||
| const [data, setData] = React.useState({}); | |||
| const [columnPrice, setColumnPrice] = React.useState(ComboData.proofPrice[0]); | |||
| const [attachments, setAttachments] = React.useState([]); | |||
| @@ -41,16 +52,24 @@ const FormPanel = ({ formData }) => { | |||
| const [proofPaymentDeadlineMin, setProofPaymentDeadlineMin] = React.useState({}); | |||
| const [reviseDeadlineMin, setReviseDeadlineMin] = React.useState({}); | |||
| const fileInputRef = React.useRef(null); | |||
| const navigate = useNavigate() | |||
| React.useEffect(() => { | |||
| if (formData) { | |||
| setData(formData); | |||
| const normalizedFormData = { | |||
| ...formData, | |||
| length: formData.length ?? 0, | |||
| noOfPages: formData.noOfPages ?? 0, | |||
| fee: formData.fee ?? 0 | |||
| }; | |||
| if (formData.groupType == "Private Bill") { | |||
| setColumnPrice(ComboData.proofPrice[1]) | |||
| formData['length'] = 18; | |||
| normalizedFormData['length'] = 18; | |||
| } | |||
| setData(normalizedFormData); | |||
| setProofPaymentDeadlineMin(formData.proofPaymentDeadline); | |||
| setReviseDeadlineMin(formData.reviseDeadline); | |||
| setExpectedCode(formData.groupNo.substr(1,formData.groupNo.length)+"-"+formData.issueNo+"-"+formData.issueYear.toString().substr(2, formData.issueYear.toString().length)); | |||
| @@ -121,7 +140,7 @@ const FormPanel = ({ formData }) => { | |||
| const formik = useFormik({ | |||
| enableReinitialize: true, | |||
| initialValues: data, | |||
| initialValues: { ...proofFormInitialValues, ...data }, | |||
| onSubmit: values => { | |||
| setSaving(true); | |||
| if (!attachments || attachments.length <= 0) { | |||
| @@ -167,7 +186,9 @@ const FormPanel = ({ formData }) => { | |||
| if (msg === "haveActiveProof") { | |||
| msg = "Action Failed: There is already a pending payment and proofreading record for client review." | |||
| } else if (msg === "haveProofed") { | |||
| msg = "Action Failed: Already proofed." | |||
| msg = "Action Failed: An active proof is already created for this application." | |||
| } else { | |||
| msg = intl.formatMessage({ id: msg }); | |||
| } | |||
| setWarningText(msg); | |||
| setIsWarningPopUp(true); | |||
| @@ -203,7 +224,7 @@ const FormPanel = ({ formData }) => { | |||
| } | |||
| return ( | |||
| <MainCard xs={12} md={12} lg={12} | |||
| <MainCard | |||
| border={false} | |||
| content={false}> | |||
| @@ -275,12 +296,26 @@ const FormPanel = ({ formData }) => { | |||
| </Grid> | |||
| <Grid item xs={12} md={12}> | |||
| <Button | |||
| component="label" | |||
| variant="contained" | |||
| size="large" | |||
| disabled={attachments.length >= (formik.values.groupType == "Private Bill" ? 2 : 1)} | |||
| type="button" | |||
| onClick={() => { | |||
| if (fileInputRef.current) { | |||
| fileInputRef.current.click(); | |||
| } | |||
| }} | |||
| onKeyDown={(event) => { | |||
| if (event.key === 'Enter' || event.key === ' ') { | |||
| event.preventDefault(); | |||
| if (fileInputRef.current) { | |||
| fileInputRef.current.click(); | |||
| } | |||
| } | |||
| }} | |||
| > | |||
| <Typography variant="h5">Upload Files</Typography> | |||
| <Typography variant="h5">Upload Files</Typography> | |||
| </Button> | |||
| <input | |||
| id="uploadFileBtn" | |||
| name="file" | |||
| @@ -289,9 +324,9 @@ const FormPanel = ({ formData }) => { | |||
| hidden | |||
| disabled={attachments.length >= (formik.values.groupType == "Private Bill" ? 2 : 1)} | |||
| onChange={readFile} | |||
| aria-label="Upload PDF file" | |||
| aria-label={intl.formatMessage({ id: 'ariaUploadPdfFile' })} | |||
| ref={fileInputRef} | |||
| /> | |||
| </Button> | |||
| </Grid> | |||
| <Grid item xs={12} md={12}> | |||
| <UploadFileTable | |||
| @@ -407,7 +442,9 @@ const FormPanel = ({ formData }) => { | |||
| options={ComboData.proofPrice} | |||
| value={columnPrice} | |||
| inputValue={(columnPrice?.label) ? columnPrice?.label : ""} | |||
| getOptionLabel={(option) => option.label ? option.label : ""} | |||
| getOptionLabel={(option) => | |||
| option != null && option.label != null ? String(option.label) : "" | |||
| } | |||
| onChange={(event, newValue) => { | |||
| setColumnPrice(newValue) | |||
| formik.values["fee"] = newValue.value * formik.values.length; | |||
| @@ -494,7 +531,7 @@ const FormPanel = ({ formData }) => { | |||
| } | |||
| }} | |||
| > | |||
| <DialogTitle><Typography variant="h3">Warning</Typography></DialogTitle> | |||
| <DialogTitle><Typography component="span" variant="h3">Warning</Typography></DialogTitle> | |||
| <DialogContent style={{ display: 'flex', }}> | |||
| <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | |||
| </DialogContent> | |||
| @@ -75,7 +75,7 @@ const Index = () => { | |||
| </Grid> | |||
| </Grid> | |||
| : | |||
| <Grid container sx={{ minHeight: '85vh', backgroundColor: "backgroundColor.default" }} direction="column" spacing={1} > | |||
| <Grid container sx={{ minHeight: '85vh', bgcolor: 'background.default' }} direction="column" spacing={1} > | |||
| <Grid item xs={12}> | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| @@ -88,7 +88,7 @@ const Index = () => { | |||
| border={false} | |||
| content={false} | |||
| sx={{ | |||
| backgroundColor: "backgroundColor.default" | |||
| bgcolor: 'background.default' | |||
| }} | |||
| > | |||
| @@ -126,7 +126,7 @@ const Index = () => { | |||
| <MainCard elevation={0} | |||
| border={false} | |||
| content={false} | |||
| backgroundColor={"backgroundColor.default"} | |||
| sx={{ bgcolor: 'background.default' }} | |||
| > | |||
| <Box xs={12} ml={4} mt={3} sx={{ p: 1, borderRadius: '10px', backgroundColor: "#fff" }}> | |||
| <ProofForm | |||
| @@ -8,11 +8,13 @@ import { | |||
| Button, | |||
| Stack, | |||
| Dialog, DialogTitle, DialogContent, DialogActions, | |||
| CircularProgress, | |||
| } from '@mui/material'; | |||
| import { useFormik } from 'formik'; | |||
| import { useIntl } from 'react-intl'; | |||
| import {isGranted} from "auth/utils"; | |||
| import {useState,useEffect,lazy} from "react"; | |||
| import {useState, useEffect, useRef, lazy} from "react"; | |||
| import * as HttpUtils from "utils/HttpUtils" | |||
| import * as UrlUtils from "utils/ApiPathConst" | |||
| import * as DateUtils from "utils/DateUtils" | |||
| @@ -31,10 +33,15 @@ const ApplicationDetailCard = ({ | |||
| }) => { | |||
| const params = useParams(); | |||
| const intl = useIntl(); | |||
| const [data, setData] = useState({}); | |||
| const [cancelPopUp, setCancelPopUp] = useState(false); | |||
| const [cancelLoading, setCancelLoading] = useState(false); | |||
| const cancellingRef = useRef(false); | |||
| const [onDownload, setOnDownload] = useState(false); | |||
| const [alertMsg, setAlertMsg] = useState(''); | |||
| const [showAlert, setShowAlert] = useState(false); | |||
| useEffect(() => { | |||
| if (formData) { | |||
| @@ -94,11 +101,30 @@ const ApplicationDetailCard = ({ | |||
| } | |||
| const confirmCancel = () => { | |||
| setCancelPopUp(false); | |||
| if (cancellingRef.current) return; | |||
| cancellingRef.current = true; | |||
| setCancelLoading(true); | |||
| HttpUtils.get({ | |||
| url: UrlUtils.CANCEL_PROOF + "/" + params.id, | |||
| onSuccess: function () { | |||
| window.location.reload(false); | |||
| onSuccess: function (responseData) { | |||
| cancellingRef.current = false; | |||
| setCancelLoading(false); | |||
| if (responseData && responseData.success === false) { | |||
| setCancelPopUp(false); | |||
| const msg = responseData.msg ? intl.formatMessage({ id: responseData.msg }) : intl.formatMessage({ id: 'proofAlreadyCancelled' }); | |||
| setAlertMsg(msg); | |||
| setShowAlert(true); | |||
| } else { | |||
| window.location.reload(false); | |||
| } | |||
| }, | |||
| onFail: () => { | |||
| cancellingRef.current = false; | |||
| setCancelLoading(false); | |||
| }, | |||
| onError: () => { | |||
| cancellingRef.current = false; | |||
| setCancelLoading(false); | |||
| } | |||
| }); | |||
| } | |||
| @@ -189,7 +215,7 @@ const ApplicationDetailCard = ({ | |||
| <Grid item xs={12} md={9} lg={9} sx={{ display: 'flex', alignItems: 'center' }}> | |||
| <FormControl variant="outlined"> | |||
| {StatusUtils.getStatusByText(data.appStatus)} | |||
| {StatusUtils.getStatusByTextEng(data.appStatus, data.creditor)} | |||
| </FormControl> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -338,15 +364,33 @@ const ApplicationDetailCard = ({ | |||
| <div> | |||
| <Dialog | |||
| open={cancelPopUp} | |||
| onClose={() => setCancelPopUp(false)} | |||
| onClose={() => { | |||
| if (cancelLoading) return; | |||
| setCancelPopUp(false); | |||
| }} | |||
| > | |||
| <DialogTitle><Typography variant="h3">Confirm</Typography></DialogTitle> | |||
| <DialogContent style={{ display: 'flex', }}> | |||
| <Typography variant="h4" style={{ padding: '16px' }}>Are you sure you want to cancel this proof?</Typography> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setCancelPopUp(false)}><Typography variant="h5">Cancel</Typography></Button> | |||
| <Button onClick={() => confirmCancel()}><Typography variant="h5">Confirm</Typography></Button> | |||
| <Button onClick={() => setCancelPopUp(false)} disabled={cancelLoading}><Typography variant="h5">Cancel</Typography></Button> | |||
| <Button | |||
| onClick={() => confirmCancel()} | |||
| disabled={cancelLoading} | |||
| startIcon={cancelLoading ? <CircularProgress color="inherit" size={20} /> : null} | |||
| > | |||
| <Typography variant="h5">Confirm</Typography> | |||
| </Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| <Dialog open={showAlert} onClose={() => setShowAlert(false)}> | |||
| <DialogTitle><Typography variant="h3">{intl.formatMessage({ id: 'attention' })}</Typography></DialogTitle> | |||
| <DialogContent> | |||
| <Typography variant="h4" style={{ padding: '16px' }}>{alertMsg}</Typography> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setShowAlert(false)}><Typography variant="h5">OK</Typography></Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| </div> | |||
| @@ -55,6 +55,7 @@ const FormPanel = ({ formData }) => { | |||
| const [isSubmitting, setIsSubmitting] = React.useState(false); | |||
| const [isOnlyOnlinePayment, setOnlyOnlinePayment] = React.useState(); | |||
| const [isNoPayment, setNoPayment] = React.useState(); | |||
| const fileInputRef = React.useRef(null); | |||
| const navigate = useNavigate() | |||
| const params = useParams(); | |||
| @@ -166,14 +167,16 @@ const FormPanel = ({ formData }) => { | |||
| onFail: function (response) { | |||
| setIsSubmitting(false); | |||
| setWarningTitle(intl.formatMessage({ id: "attention" })) | |||
| setWarningText(intl.formatMessage({ id: 'actionFail' })); | |||
| const msg = response?.data?.msg ? intl.formatMessage({ id: response.data.msg }) : intl.formatMessage({ id: 'actionFail' }); | |||
| setWarningText(msg); | |||
| setIsWarningPopUp(true); | |||
| console.log(response); | |||
| }, | |||
| onError: function (error) { | |||
| setIsSubmitting(false); | |||
| setWarningTitle(intl.formatMessage({ id: "attention" })) | |||
| setWarningText(intl.formatMessage({ id: 'actionFail' })); | |||
| const msg = error?.response?.data?.msg ? intl.formatMessage({ id: error.response.data.msg }) : intl.formatMessage({ id: 'actionFail' }); | |||
| setWarningText(msg); | |||
| setIsWarningPopUp(true); | |||
| console.log(error); | |||
| } | |||
| @@ -628,6 +631,30 @@ const FormPanel = ({ formData }) => { | |||
| </Grid> | |||
| <Grid item xs={12} md={12} textAlign="left"> | |||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||
| <Button | |||
| color="save" | |||
| variant="contained" | |||
| type="button" | |||
| aria-label={intl.formatMessage({ id: 'upload' })} | |||
| disabled={attachments.length >= (formik.values.groupType === "Private Bill" ? 2 : 1)} | |||
| onClick={() => { | |||
| if (fileInputRef.current) { | |||
| fileInputRef.current.click(); | |||
| } | |||
| }} | |||
| onKeyDown={(event) => { | |||
| if (event.key === 'Enter' || event.key === ' ') { | |||
| event.preventDefault(); | |||
| if (fileInputRef.current) { | |||
| fileInputRef.current.click(); | |||
| } | |||
| } | |||
| }} | |||
| > | |||
| <FormattedMessage id="upload" /> | |||
| </Button> | |||
| </ThemeProvider> | |||
| <input | |||
| id="uploadFileBtn" | |||
| name="file" | |||
| @@ -638,20 +665,8 @@ const FormPanel = ({ formData }) => { | |||
| onChange={(event) => { | |||
| readFile(event) | |||
| }} | |||
| ref={fileInputRef} | |||
| /> | |||
| <label htmlFor="uploadFileBtn"> | |||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||
| <Button | |||
| color="save" | |||
| component="span" | |||
| variant="contained" | |||
| aria-label={intl.formatMessage({ id: 'upload' })} | |||
| disabled={attachments.length >= (formik.values.groupType === "Private Bill" ? 2 : 1)} | |||
| > | |||
| <FormattedMessage id="upload" /> | |||
| </Button> | |||
| </ThemeProvider> | |||
| </label> | |||
| </Grid> | |||
| @@ -700,13 +715,16 @@ const FormPanel = ({ formData }) => { | |||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||
| <Button | |||
| variant="contained" | |||
| color="success" | |||
| type="submit" | |||
| disabled={(actionValue == false && isOverReviseDeadline()) || isSubmitting} | |||
| startIcon={isSubmitting ? <CircularProgress size={20} color="inherit" /> : null} | |||
| aria-label={intl.formatMessage({ id: 'submitReply' })} | |||
| sx={{ | |||
| backgroundColor: '#0C489E', | |||
| color: '#FFFFFF', | |||
| '&:hover': { backgroundColor: '#093A7A' }, | |||
| }} | |||
| > | |||
| <FormattedMessage id="submitReply" /> | |||
| {isSubmitting ? <FormattedMessage id="loading" /> : <FormattedMessage id="submitReply" />} | |||
| </Button> | |||
| </ThemeProvider> | |||
| </Grid> | |||
| @@ -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(); | |||
| @@ -92,7 +95,7 @@ const Index = () => { | |||
| <Grid item xs={12} width="100%"> | |||
| <div style={BackgroundHead} width="100%"> | |||
| <Stack direction="row" height='70px'> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }, pt:2}}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }, pt:2}}> | |||
| <FormattedMessage id="proofRecord"/> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -111,12 +114,10 @@ const Index = () => { | |||
| <Grid item xs={12} sm={12} md={12} lg={12} sx={{ width:'100%', mt:2, mb: -3}}> | |||
| <MainCard | |||
| sx={{ | |||
| mr:2, | |||
| mr: 2, | |||
| boxShadow: 1, | |||
| border: 1, | |||
| borderColor: '#DDD', | |||
| border: '1px groove #DDD', | |||
| }} | |||
| border= '1px groove grey' | |||
| > | |||
| <ApplicationDetails | |||
| formData={record} | |||
| @@ -129,11 +130,8 @@ const Index = () => { | |||
| <MainCard | |||
| sx={{ | |||
| boxShadow: 1, | |||
| border: 1, | |||
| borderColor: '#DDD', | |||
| border: '1px groove #DDD', | |||
| }} | |||
| border= '1px groove grey' | |||
| // sx={..._sx} | |||
| > | |||
| <ProofForm | |||
| formData={record} | |||
| @@ -46,10 +46,14 @@ export default function SearchPublicNoticeTable({searchCriteria, applyGridOnRead | |||
| }, | |||
| }, | |||
| { | |||
| id: 'actions', | |||
| id: 'proofStatus', | |||
| field: 'proofStatus', | |||
| headerName: 'Status', | |||
| flex: 1, | |||
| minWidth: 150, | |||
| sortable: false, | |||
| filterable: false, | |||
| valueGetter: () => '', | |||
| renderCell: (params) => { | |||
| return ProofStatus.getStatus_Eng(params); | |||
| }, | |||
| @@ -26,11 +26,15 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||
| const [type, setType] = React.useState([]); | |||
| const [status, setStatus] = React.useState(searchCriteria.statusKey!=undefined?ComboData.proofStatus_GLD[searchCriteria.statusKey]:ComboData.proofStatus_GLD[0]); | |||
| const [orgSelected, setOrgSelected] = React.useState({}); | |||
| const [orgSelected, setOrgSelected] = React.useState(null); | |||
| const [orgCombo, setOrgCombo] = React.useState(); | |||
| const [issueSelected, setIssueSelected] = React.useState({}); | |||
| const [issueSelected, setIssueSelected] = React.useState(null); | |||
| const [issueCombo, setIssueCombo] = React.useState([]); | |||
| const [groupSelected, setGroupSelected] = React.useState(searchCriteria.gazettGroup!=undefined?ComboData.groupTitle.find(item => item.code === searchCriteria.gazettGroup):{}); | |||
| const [groupSelected, setGroupSelected] = React.useState( | |||
| searchCriteria.gazettGroup != undefined | |||
| ? ComboData.groupTitle.find(item => item.code === searchCriteria.gazettGroup) ?? null | |||
| : null | |||
| ); | |||
| const [minDate, setMinDate] = React.useState(searchCriteria.dateFrom); | |||
| const [maxDate, setMaxDate] = React.useState(searchCriteria.dateTo); | |||
| @@ -136,9 +140,9 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||
| function resetForm() { | |||
| setType([]); | |||
| setStatus(ComboData.proofStatus[0]); | |||
| setOrgSelected({}); | |||
| setIssueSelected({}); | |||
| setGroupSelected({}); | |||
| setOrgSelected(null); | |||
| setIssueSelected(null); | |||
| setGroupSelected(null); | |||
| setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | |||
| setMaxDate(DateUtils.dateValue(new Date())) | |||
| reset({ | |||
| @@ -257,7 +261,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||
| options={ComboData.groupTitle} | |||
| value={groupSelected} | |||
| inputValue={(groupSelected?.label) ? groupSelected?.label : ""} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| onChange={(event, newValue) => { | |||
| setGroupSelected(newValue); | |||
| }} | |||
| @@ -371,13 +375,12 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| <TextField | |||
| {...params} | |||
| label="Status" | |||
| InputLabelProps={{ shrink: true }} | |||
| /> | |||
| )} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| /> | |||
| </Grid> | |||
| @@ -396,11 +399,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||
| inputValue={orgSelected ? orgSelected.name!=undefined?orgSelected.name:"" : ""} | |||
| onChange={(event, newValue) => { | |||
| if (newValue !== null) { | |||
| setOrgSelected(newValue); | |||
| }else{ | |||
| setOrgSelected({}); | |||
| } | |||
| setOrgSelected(newValue ?? null); | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| @@ -100,6 +100,10 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| } | |||
| const renderHeaderWithAria = (params) => ( | |||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||
| ); | |||
| const columns = [ | |||
| { | |||
| field: 'actions', | |||
| @@ -107,6 +111,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| width: isMdOrLg ? 'auto' : 200, | |||
| flex: isMdOrLg ? 1.5 : undefined, | |||
| cellClassName: 'actions', | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return clickableLink('/proof/reply/' + params.row.id, params.row.refNo); | |||
| }, | |||
| @@ -117,6 +122,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: isORGLoggedIn() ? intl.formatMessage({ id: 'gazetteCount3' }) : intl.formatMessage({ id: 'gazetteCount2' }), | |||
| width: isMdOrLg ? 'auto' : 330, | |||
| flex: isMdOrLg ? 2 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| // let appNo = params.row.appNo; | |||
| // let code = params.row.groupNo; | |||
| @@ -132,6 +138,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: intl.formatMessage({ id: 'proofDate' }), | |||
| width: isMdOrLg ? 'auto' : 200, | |||
| flex: isMdOrLg ? 1.5 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return DateUtils.datetimeStr(params?.value); | |||
| } | |||
| @@ -142,6 +149,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: intl.formatMessage({ id: 'replyBefore' }), | |||
| width: isMdOrLg ? 'auto' : 200, | |||
| flex: isMdOrLg ? 1.5 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| const proofPaymentDeadline = DateUtils.convertToDate(params?.value); | |||
| return DateUtils.datetimeStr( | |||
| @@ -156,15 +164,17 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: intl.formatMessage({ id: 'replyDate' }), | |||
| width: isMdOrLg ? 'auto' : 200, | |||
| flex: isMdOrLg ? 1.5 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return params?.value ? DateUtils.datetimeStr(params?.value) : ""; | |||
| } | |||
| }, | |||
| { | |||
| id: 'actions', | |||
| field: 'proofStatus', | |||
| headerName: intl.formatMessage({ id: 'status' }), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return locale === 'en' ? ProofStatus.getStatus_Eng(params) : locale === 'zh-HK' ? ProofStatus.getStatus_Cht(params) : ProofStatus.getStatus_Cn(params); | |||
| }, | |||
| @@ -175,6 +185,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: intl.formatMessage({ id: 'fee' }), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return (params?.value) ? "$ " + FormatUtils.currencyFormat(params?.value) : ""; | |||
| } | |||
| @@ -29,7 +29,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||
| const [type, setType] = React.useState([]); | |||
| const [status, setStatus] = React.useState(searchCriteria.statusKey!=undefined?ComboData.proofStatusFull[searchCriteria.statusKey]:ComboData.proofStatusFull[0]); | |||
| const [issueSelected, setIssueSelected] = React.useState({}); | |||
| const [issueSelected, setIssueSelected] = React.useState(null); | |||
| const [issueCombo, setIssueCombo] = React.useState([]); | |||
| const [groupSelected, setGroupSelected] = React.useState(searchCriteria.gazettGroup!=undefined?ComboData.groupTitle.find(item => item.code === searchCriteria.gazettGroup):{}); | |||
| @@ -110,7 +110,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||
| if (issueComboData && issueComboData.length > 0) { | |||
| setIssueCombo(issueComboData); | |||
| if(searchCriteria.issueId!=undefined){ | |||
| setIssueSelected(issueComboData.find(item => item.id === searchCriteria.issueId)) | |||
| setIssueSelected(issueComboData.find(item => item.id === searchCriteria.issueId) ?? null) | |||
| } | |||
| } | |||
| }, [issueComboData]); | |||
| @@ -118,7 +118,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||
| function resetForm() { | |||
| setType([]); | |||
| setStatus(ComboData.proofStatusFull[0]); | |||
| setIssueSelected({}); | |||
| setIssueSelected(null); | |||
| setGroupSelected({}); | |||
| setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | |||
| setMaxDate(DateUtils.dateValue(new Date())) | |||
| @@ -205,8 +205,9 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||
| id="issueId" | |||
| options={issueCombo} | |||
| value={issueSelected} | |||
| isOptionEqualToValue={(option, value) => option?.id === value?.id} | |||
| inputValue={(issueSelected?.id) ? getIssueLabel(issueSelected) : ""} | |||
| getOptionLabel={(option) => getIssueLabel(option)} | |||
| getOptionLabel={(option) => (option?.id ? getIssueLabel(option) : "")} | |||
| onChange={(event, newValue) => { | |||
| setIssueSelected(newValue); | |||
| }} | |||
| @@ -223,6 +224,10 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||
| }} | |||
| /> | |||
| )} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| @@ -252,7 +257,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||
| <Grid item xs={12} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <Grid container spacing={1}> | |||
| <Grid item xs={6}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateFrom" | |||
| @@ -279,7 +284,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateTo" | |||
| @@ -348,11 +353,16 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label={intl.formatMessage({id: 'status'})} | |||
| InputLabelProps={{ | |||
| ...params.InputLabelProps, | |||
| shrink: true | |||
| }} | |||
| /> | |||
| )} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| @@ -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()), | |||
| @@ -87,7 +89,7 @@ const UserSearchPage_Individual = () => { | |||
| <Grid item xs={12}> | |||
| <div style={BackgroundHead} > | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center" > | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <FormattedMessage id="proofRecord"/> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -282,7 +282,7 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||
| <Grid item xs={12} md={12} width="100%" > | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{ display: { xs: 'none', sm: 'none', md: 'block' } }}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{ display: { xs: 'none', sm: 'none', md: 'block' } }}> | |||
| <FormattedMessage id="applyPublicNotice" /> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -566,6 +566,7 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||
| label: intl.formatMessage({ id: 'careOf' }) + ":", | |||
| valueName: "careOf", | |||
| form: formik, | |||
| inputProps: { "aria-label": intl.formatMessage({ id: 'careOf' }) } | |||
| // disabled: true | |||
| })} | |||
| </Grid> | |||
| @@ -593,7 +594,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' }) } | |||
| })} | |||
| </Grid> | |||
| } | |||
| @@ -622,6 +623,9 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||
| name="tickAccept" | |||
| color="primary" | |||
| size="small" | |||
| inputProps={{ | |||
| "aria-label": intl.formatMessage({ id: "applyTickStr" }) | |||
| }} | |||
| /> | |||
| <Typography variant="h6" height="100%" > | |||
| <div style={{ padding: 12, textAlign: 'justify' }} dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "applyTickStr" }) }} /> | |||
| @@ -651,9 +655,6 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||
| </Typography> | |||
| </Grid> | |||
| </Grid> | |||
| </form> | |||
| ) : null} | |||
| @@ -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([]); | |||
| @@ -62,7 +64,7 @@ const ApplyForm = () => { | |||
| for (var i = 0; i < response?.gazetteIssueList?.length; i++) { | |||
| let data = response.gazetteIssueList[i]; | |||
| //let label = getIssueLabel(data); | |||
| selection.push(<FormControlLabel value={data.id} control={<Radio />} label={getIssueLabel(data)} />); | |||
| selection.push(<FormControlLabel key={data.id} value={data.id} control={<Radio />} label={getIssueLabel(data)} />); | |||
| } | |||
| setGazetteIssueList(response?.gazetteIssueList); | |||
| setSelection(selection); | |||
| @@ -78,7 +80,7 @@ const ApplyForm = () => { | |||
| for (var i = 0; i < gazetteIssueList?.length; i++) { | |||
| let data = gazetteIssueList[i]; | |||
| let label = getIssueLabel(data); | |||
| selection.push(<FormControlLabel value={data.id} control={<Radio />} label={label} />); | |||
| selection.push(<FormControlLabel key={data.id} value={data.id} control={<Radio />} label={label} />); | |||
| } | |||
| setSelection(selection); | |||
| } | |||
| @@ -9,6 +9,7 @@ import { | |||
| Stack, | |||
| Dialog, DialogTitle, DialogContent, DialogActions, InputAdornment, Autocomplete | |||
| } from '@mui/material'; | |||
| import LoadingButton from '@mui/lab/LoadingButton'; | |||
| import { isGranted, delBugMode, getPaymentMethodGLD} from "auth/utils"; | |||
| const MainCard = Loadable(lazy(() => import('components/MainCard'))); | |||
| import { useForm } from "react-hook-form"; | |||
| @@ -33,18 +34,46 @@ import CloseIcon from '@mui/icons-material/Close'; | |||
| import EditNoteIcon from '@mui/icons-material/EditNote'; | |||
| import DownloadIcon from '@mui/icons-material/Download'; | |||
| import ReplayIcon from '@mui/icons-material/Replay'; | |||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||
| import { notifyActionError } from 'utils/CommonFunction'; | |||
| import { isGrantedAny } from "auth/utils"; | |||
| // import { useIntl } from "react-intl"; | |||
| import { useIntl } from "react-intl"; | |||
| /** Contained buttons with custom bg must restyle disabled/loading or they stay green/orange. */ | |||
| const publishWithdrawLoadingSx = (mainBg, hoverBg) => (theme) => ({ | |||
| textTransform: 'capitalize', | |||
| alignItems: 'end', | |||
| backgroundColor: mainBg, | |||
| color: '#fff', | |||
| '&:hover:not(.Mui-disabled):not(.MuiLoadingButton-loading)': { | |||
| backgroundColor: hoverBg, | |||
| }, | |||
| '&.Mui-disabled, &.MuiLoadingButton-loading': { | |||
| backgroundColor: theme.palette.action.disabledBackground, | |||
| color: theme.palette.action.disabled, | |||
| }, | |||
| '&.Mui-disabled .MuiSvgIcon-root, &.MuiLoadingButton-loading .MuiSvgIcon-root': { | |||
| color: theme.palette.action.disabled, | |||
| }, | |||
| '&.Mui-disabled .MuiTypography-root, &.MuiLoadingButton-loading .MuiTypography-root': { | |||
| color: `${theme.palette.action.disabled} !important`, | |||
| }, | |||
| }); | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const ApplicationDetailCard = ( | |||
| { applicationDetailData, | |||
| setStatus, | |||
| setUploadStatus | |||
| setUploadStatus, | |||
| statusDialogOpen = false, | |||
| statusDialogKind = "", | |||
| statusActionLoading = false, | |||
| } | |||
| ) => { | |||
| const publishWithdrawBusy = | |||
| statusActionLoading || | |||
| (statusDialogOpen && (statusDialogKind === 'publish' || statusDialogKind === 'withdraw')); | |||
| const [currentApplicationDetailData, setCurrentApplicationDetailData] = useState({}); | |||
| const [companyName, setCompanyName] = useState({}); | |||
| const [orgDetail, setOrgDetail] = useState({}); | |||
| @@ -59,13 +88,15 @@ const ApplicationDetailCard = ( | |||
| const [mode, setMode] = useState(""); | |||
| const { register, handleSubmit } = useForm() | |||
| // const intl = useIntl(); | |||
| const intl = useIntl(); | |||
| const [isWarningPopUp, setIsWarningPopUp] = useState(false); | |||
| const [warningText, setWarningText] = useState(""); | |||
| const [remarksPopUp, setRemarksPopUp] = useState(false); | |||
| const [onDownload, setOnDownload] = useState(false); | |||
| // eslint-disable-next-line no-unused-vars -- isProofCheckLoading in onProofClick + Button disabled; setIsProofCheckLoading in onProofClick callbacks | |||
| const [isProofCheckLoading, setIsProofCheckLoading] = useState(false); | |||
| useEffect(() => { | |||
| //if user data from parent are not null | |||
| @@ -125,12 +156,12 @@ const ApplicationDetailCard = ( | |||
| fileId: fileDetail?.id, | |||
| skey: fileDetail?.skey, | |||
| filename: fileDetail?.filename, | |||
| onResponse:()=>{ | |||
| setOnDownload(false) | |||
| notifyDownloadSuccess() | |||
| onResponse: () => { | |||
| setOnDownload(false); | |||
| }, | |||
| onError:()=>{ | |||
| setOnDownload(false) | |||
| onError: () => { | |||
| setOnDownload(false); | |||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||
| } | |||
| }); | |||
| setUploadStatus(true) | |||
| @@ -166,10 +197,12 @@ const ApplicationDetailCard = ( | |||
| }; | |||
| const withdrawnClick = () => () => { | |||
| if (publishWithdrawBusy) return; | |||
| setStatus("withdraw") | |||
| }; | |||
| const doPublish = () => () => { | |||
| if (publishWithdrawBusy) return; | |||
| setStatus("publish") | |||
| } | |||
| @@ -178,10 +211,13 @@ const ApplicationDetailCard = ( | |||
| }; | |||
| const onProofClick = () => { | |||
| if (isProofCheckLoading) return; | |||
| if (applicationDetailData.data.groupNo) { | |||
| setIsProofCheckLoading(true); | |||
| HttpUtils.get({ | |||
| url: CHECK_CREATE_PROOF + "/" + currentApplicationDetailData.id, | |||
| onSuccess: function (responeData) { | |||
| setIsProofCheckLoading(false); | |||
| if (responeData.success == true) { | |||
| window.open("/proof/create/" + currentApplicationDetailData.id, "_blank", "noreferrer"); | |||
| window.addEventListener("focus", onFocus) | |||
| @@ -190,12 +226,16 @@ const ApplicationDetailCard = ( | |||
| if (msg === "haveActiveProof") { | |||
| msg = "Action Failed: There is already a pending payment and proofreading record for client review." | |||
| } else if (msg === "haveProofed") { | |||
| msg = "Action Failed: Already proofed." | |||
| msg = "Action Failed: An active proof is already created for this application." | |||
| } else { | |||
| msg = intl.formatMessage({ id: msg }); | |||
| } | |||
| setWarningText(msg); | |||
| setIsWarningPopUp(true); | |||
| } | |||
| } | |||
| }, | |||
| onFail: () => setIsProofCheckLoading(false), | |||
| onError: () => setIsProofCheckLoading(false) | |||
| }); | |||
| } else { | |||
| setWarningText("Please generate Gazette Code before Create Proof."); | |||
| @@ -256,6 +296,7 @@ const ApplicationDetailCard = ( | |||
| <Button | |||
| // size="large" | |||
| variant="contained" | |||
| disabled={isProofCheckLoading} | |||
| onClick={() => { onProofClick() }} | |||
| sx={{ | |||
| textTransform: 'capitalize', | |||
| @@ -324,31 +365,26 @@ const ApplicationDetailCard = ( | |||
| </> : | |||
| (currentApplicationDetailData.status == "confirmed" && currentApplicationDetailData.creditor == 1) ? | |||
| <> | |||
| <Button | |||
| <LoadingButton | |||
| // size="large" | |||
| variant="contained" | |||
| onClick={doPublish()} | |||
| disabled={setCompleteDisable()} | |||
| sx={{ | |||
| textTransform: 'capitalize', | |||
| alignItems: 'end', | |||
| backgroundColor: '#52b202' | |||
| }}> | |||
| disabled={setCompleteDisable() || publishWithdrawBusy} | |||
| loading={statusActionLoading && statusDialogKind === 'publish'} | |||
| sx={publishWithdrawLoadingSx('#52b202', '#489f04')}> | |||
| <DoneIcon /> | |||
| <Typography ml={1} variant="h5">Publish</Typography> | |||
| </Button> | |||
| <Button | |||
| </LoadingButton> | |||
| <LoadingButton | |||
| // size="large" | |||
| variant="contained" | |||
| onClick={withdrawnClick()} | |||
| sx={{ | |||
| textTransform: 'capitalize', | |||
| alignItems: 'end', | |||
| backgroundColor: '#ffa733' | |||
| }}> | |||
| disabled={publishWithdrawBusy} | |||
| loading={statusActionLoading && statusDialogKind === 'withdraw'} | |||
| sx={publishWithdrawLoadingSx('#ffa733', '#e8982e')}> | |||
| <CloseIcon /> | |||
| <Typography ml={1} variant="h5">Withdraw</Typography> | |||
| </Button> | |||
| </LoadingButton> | |||
| </> | |||
| : | |||
| ( | |||
| @@ -382,18 +418,16 @@ const ApplicationDetailCard = ( | |||
| <DoneIcon /> | |||
| <Typography ml={1} variant="h5">Publish</Typography> | |||
| </Button> | |||
| <Button | |||
| <LoadingButton | |||
| // size="large" | |||
| variant="contained" | |||
| onClick={withdrawnClick()} | |||
| sx={{ | |||
| textTransform: 'capitalize', | |||
| alignItems: 'end', | |||
| backgroundColor: '#ffa733' | |||
| }}> | |||
| disabled={publishWithdrawBusy} | |||
| loading={statusActionLoading && statusDialogKind === 'withdraw'} | |||
| sx={publishWithdrawLoadingSx('#ffa733', '#e8982e')}> | |||
| <CloseIcon /> | |||
| <Typography ml={1} variant="h5">Withdraw</Typography> | |||
| </Button> | |||
| </LoadingButton> | |||
| </> : null | |||
| ) | |||
| } | |||
| @@ -715,7 +749,11 @@ const ApplicationDetailCard = ( | |||
| sx={{ | |||
| textTransform: 'capitalize', | |||
| alignItems: 'end', | |||
| }}> | |||
| backgroundColor: '#0C489E', | |||
| color: '#FFFFFF', | |||
| '&:hover': { backgroundColor: '#093A7A' }, | |||
| }} | |||
| > | |||
| <DownloadIcon /> | |||
| </Button> | |||
| </Grid> | |||
| @@ -841,7 +879,7 @@ const ApplicationDetailCard = ( | |||
| } | |||
| }} | |||
| > | |||
| <DialogTitle><Typography variant="h3">Warning</Typography></DialogTitle> | |||
| <DialogTitle component="div"><Typography variant="h3">Warning</Typography></DialogTitle> | |||
| <DialogContent style={{ display: 'flex', }}> | |||
| <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | |||
| </DialogContent> | |||
| @@ -863,7 +901,7 @@ const ApplicationDetailCard = ( | |||
| }} | |||
| > | |||
| <form onSubmit={handleSubmit(onSubmit)}> | |||
| <DialogTitle><Typography variant="h3">Remarks</Typography></DialogTitle> | |||
| <DialogTitle component="div"><Typography variant="h3">Remarks</Typography></DialogTitle> | |||
| <DialogContent style={{ display: 'flex', }}> | |||
| <Grid container direction="column"> | |||
| <Grid item sx={{ padding: '16px' }}> | |||
| @@ -914,7 +952,7 @@ const ApplicationDetailCard = ( | |||
| filterOptions={(options) => options} | |||
| options={ComboData.paymentMeans} | |||
| value={paymentMeans} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| inputValue={paymentMeans?.label ? paymentMeans?.label : ""} | |||
| onChange={(event, newValue) => { | |||
| setPaymentMeans(newValue); | |||
| @@ -927,11 +965,9 @@ const ApplicationDetailCard = ( | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label="" | |||
| InputLabelProps={{ shrink: true }} | |||
| /> | |||
| )} | |||
| InputLabelProps={{ | |||
| shrink: true, | |||
| }} | |||
| disableClearable={true} | |||
| /> | |||
| </Grid> | |||
| @@ -20,12 +20,14 @@ const LoadingComponent = Loadable(lazy(() => import('../../extra-pages/LoadingCo | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import EditNoteIcon from '@mui/icons-material/EditNote'; | |||
| import { isGrantedAny } from "auth/utils"; | |||
| import { useIntl } from "react-intl"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const GazetteDetailCard = ( | |||
| { applicationDetailData, | |||
| setStatus | |||
| } | |||
| ) => { | |||
| const intl = useIntl(); | |||
| const [onReady, setOnReady] = useState(false); | |||
| const [issueNum, setIssueNum] = useState(""); | |||
| const [issueDate, setIssueDate] = useState(""); | |||
| @@ -51,11 +53,11 @@ const GazetteDetailCard = ( | |||
| setIssueNum(applicationDetailData.gazetteIssueDetail.volume + "/" + applicationDetailData.gazetteIssueDetail.issueYear | |||
| + " No. " + applicationDetailData.gazetteIssueDetail.issueNo); | |||
| setIssueDate(DateUtils.dateFormat(applicationDetailData.gazetteIssueDetail.issueDate, "D MMM YYYY (ddd)")); | |||
| setGazetteCode(applicationDetailData.data.groupNo) | |||
| setGazetteCode(applicationDetailData.data.groupNo ?? '') | |||
| // console.log(applicationDetailData) | |||
| setSysType(applicationDetailData.userData.sysType) | |||
| setCareOf(applicationDetailData.data.careOf) | |||
| setGroupTitle(applicationDetailData.data.groupTitle) | |||
| setCareOf(applicationDetailData.data.careOf ?? '') | |||
| setGroupTitle(applicationDetailData.data.groupTitle ?? '') | |||
| if (applicationDetailData.data.mode != null){ | |||
| setMode(applicationDetailData.data.mode); | |||
| } | |||
| @@ -71,7 +73,11 @@ const GazetteDetailCard = ( | |||
| }, [issueNum]); | |||
| const groupDetailClick = () => () => { | |||
| if (gazetteCode == null) { | |||
| // groupNo is normalized to '' when absent, so check empty string — not only null | |||
| const hasGazetteCode = | |||
| gazetteCode != null && | |||
| String(gazetteCode).trim() !== ""; | |||
| if (!hasGazetteCode) { | |||
| setStatus("genGazetteCode"); | |||
| return; | |||
| } | |||
| @@ -275,6 +281,7 @@ const GazetteDetailCard = ( | |||
| })} | |||
| value={careOf} | |||
| id='careOf' | |||
| inputProps={{ 'aria-label': intl.formatMessage({ id: 'careOf' }) }} | |||
| sx={{ | |||
| "& .MuiInputBase-input.Mui-disabled": { | |||
| WebkitTextFillColor: "#000000", | |||
| @@ -303,7 +310,9 @@ const GazetteDetailCard = ( | |||
| maxHeight: { xs: '90vh', s: '70vh', m: '70vh', lg: '60vh' } | |||
| } | |||
| }}> | |||
| <DialogTitle><Typography variant="h3">Warning</Typography></DialogTitle> | |||
| <DialogTitle component="div"> | |||
| <Typography variant="h3" component="h2">Warning</Typography> | |||
| </DialogTitle> | |||
| <DialogContent style={{ display: 'flex', }}> | |||
| <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | |||
| </DialogContent> | |||
| @@ -1,6 +1,7 @@ | |||
| import { | |||
| useEffect, | |||
| useState | |||
| useState, | |||
| useRef | |||
| } from "react"; | |||
| // material-ui | |||
| @@ -16,8 +17,9 @@ import { | |||
| FormLabel, | |||
| Autocomplete, | |||
| TextField, | |||
| Grid | |||
| Grid, | |||
| } from '@mui/material'; | |||
| import LoadingButton from '@mui/lab/LoadingButton'; | |||
| import * as ComboData from "utils/ComboData"; | |||
| import { useFormik, FormikProvider } from 'formik'; | |||
| @@ -30,8 +32,15 @@ const StatusChangeDialog = (props) => { | |||
| const [remarks, setRemarks] = useState(""); | |||
| const [helperText, setHelperText] = useState(""); | |||
| const [comboInputValue, setComboInputValue] = useState({}); | |||
| const [positiveSubmitting, setPositiveSubmitting] = useState(false); | |||
| const positiveOnceRef = useRef(false); | |||
| const groupTitleComboList = ComboData.groupTitle; | |||
| const confirmLoading = Boolean(props.confirmLoading) || positiveSubmitting; | |||
| const gazetteGroupMissing = | |||
| props.getStatus === "genGazetteCode" && | |||
| Object.keys(props.selectedGazetteGroup ?? {}).length === 0; | |||
| useEffect(() => { | |||
| setComboInputValue({}); | |||
| if (props.getStatus == "genGazetteCode") { | |||
| @@ -58,22 +67,40 @@ const StatusChangeDialog = (props) => { | |||
| } | |||
| }, [props.getStatus]); | |||
| useEffect(() => { | |||
| if (!props.open) { | |||
| positiveOnceRef.current = false; | |||
| setPositiveSubmitting(false); | |||
| } | |||
| }, [props.open]); | |||
| const wasConfirmLoadingRef = useRef(false); | |||
| useEffect(() => { | |||
| if (wasConfirmLoadingRef.current && !props.confirmLoading) { | |||
| positiveOnceRef.current = false; | |||
| setPositiveSubmitting(false); | |||
| } | |||
| wasConfirmLoadingRef.current = Boolean(props.confirmLoading); | |||
| }, [props.confirmLoading]); | |||
| const acceptedHandle = () => () => { | |||
| const getStatus = props.getStatus.status; | |||
| if (getStatus == "notAccepted") { | |||
| if (!remarks || remarks == "") | |||
| if (confirmLoading) return; | |||
| if (positiveOnceRef.current) return; | |||
| const statusKey = props.getStatus; | |||
| if (statusKey === "notAccepted" || statusKey === "resubmit") { | |||
| if (!remarks || remarks === "") { | |||
| setHelperText("Please enter reason"); | |||
| } | |||
| if (!helperText) { | |||
| props.setReason({ "reason": remarks }); | |||
| if (remarks != null && remarks != "") { | |||
| // console.log(remarks) | |||
| // props.setStatusWindowAccepted(true); | |||
| return; | |||
| } | |||
| setHelperText(""); | |||
| props.setReason({ "reason": remarks }); | |||
| } | |||
| if (getStatus != "notAccepted") { | |||
| props.setStatusWindowAccepted(true); | |||
| } | |||
| positiveOnceRef.current = true; | |||
| setPositiveSubmitting(true); | |||
| props.setStatusWindowAccepted(true); | |||
| }; | |||
| @@ -147,6 +174,11 @@ const StatusChangeDialog = (props) => { | |||
| id="gazetteGroup" | |||
| options={groupTitleComboList} | |||
| filterOptions={(options) => options} | |||
| getOptionLabel={(option) => { | |||
| if (option == null) return ""; | |||
| if (typeof option === "string") return option; | |||
| return option.label != null ? String(option.label) : ""; | |||
| }} | |||
| inputValue={comboInputValue.label} | |||
| onChange={(event, newValue) => { | |||
| if (newValue != null && newValue != {}) { | |||
| @@ -156,7 +188,6 @@ const StatusChangeDialog = (props) => { | |||
| props.setSelectedGazetteGroup(newValue); | |||
| formik.setFieldValue("", "") | |||
| } else { | |||
| gazetteGroup | |||
| props.setSelectedGazetteGroupInputType(""); | |||
| } | |||
| }} | |||
| @@ -177,7 +208,10 @@ const StatusChangeDialog = (props) => { | |||
| return ( | |||
| <Dialog | |||
| open={props.open} | |||
| onClose={props.handleClose} | |||
| onClose={() => { | |||
| if (confirmLoading) return; | |||
| props.handleClose(); | |||
| }} | |||
| fullWidth={true} | |||
| maxWidth={'md'} | |||
| > | |||
| @@ -195,7 +229,7 @@ const StatusChangeDialog = (props) => { | |||
| <FormikProvider value={formik}> | |||
| <form> | |||
| <DialogContent> | |||
| <DialogContentText> | |||
| <DialogContentText component="div"> | |||
| {content} | |||
| </DialogContentText> | |||
| </DialogContent> | |||
| @@ -203,18 +237,24 @@ const StatusChangeDialog = (props) => { | |||
| </FormikProvider> | |||
| <Stack direction="row" justifyContent="space-around"> | |||
| <DialogActions> | |||
| <Button variant="contained" onClick={props.handleClose} autoFocus > | |||
| <Button variant="contained" onClick={props.handleClose} autoFocus disabled={confirmLoading}> | |||
| <Typography variant="h5"> | |||
| Cancel | |||
| </Typography> | |||
| </Button> | |||
| </DialogActions> | |||
| <DialogActions> | |||
| <Button variant="contained" color="success" onClick={acceptedHandle()} autoFocus disabled={Object.keys(props.selectedGazetteGroup).length === 0 && props.getStatus === "genGazetteCode"}> | |||
| <Typography variant="h5"> | |||
| <LoadingButton | |||
| variant="contained" | |||
| color="success" | |||
| onClick={acceptedHandle()} | |||
| loading={confirmLoading} | |||
| disabled={gazetteGroupMissing} | |||
| > | |||
| <Typography variant="h5" component="span"> | |||
| {prositiveBtnText} | |||
| </Typography> | |||
| </Button> | |||
| </LoadingButton> | |||
| </DialogActions> | |||
| </Stack> | |||
| </Dialog> | |||
| @@ -70,6 +70,7 @@ const PublicNoticeDetail_GLD = () => { | |||
| const [open, setOpen] = useState(false); | |||
| const [getStatus, setStatus] = useState(""); | |||
| const [statusWindowAccepted, setStatusWindowAccepted] = useState(false); | |||
| const [statusConfirmLoading, setStatusConfirmLoading] = useState(false); | |||
| const [selectedGazetteGroup, setSelectedGazetteGroup] = useState({}); | |||
| const [selectedGazetteGroupInputType, setSelectedGazetteGroupInputType] = useState(""); | |||
| const [getReason, setReason] = useState({}); | |||
| @@ -182,24 +183,30 @@ const PublicNoticeDetail_GLD = () => { | |||
| }; | |||
| useEffect(() => { | |||
| if (statusWindowAccepted) { | |||
| if (getStatus == "genGazetteCode") { | |||
| onAcceptedClick() | |||
| } else if (getStatus == "complete") { | |||
| onComplatedClick() | |||
| } else if (getStatus == "withdraw") { | |||
| onWithdrawnClick() | |||
| } else if (getStatus == "notAccepted") { | |||
| onNotAcceptClick(getReason); | |||
| } else if (getStatus == "resubmit") { | |||
| onReSubmitClick(getReason); | |||
| } else if (getStatus == "publish") { | |||
| onPublishClick(); | |||
| } else if (getStatus == "revoke") { | |||
| onRevokeClick(); | |||
| } else if(getStatus == "paid"){ | |||
| onPaidClick(); | |||
| } | |||
| if (!statusWindowAccepted) { | |||
| setStatusConfirmLoading(false); | |||
| return; | |||
| } | |||
| setStatusConfirmLoading(true); | |||
| if (getStatus == "genGazetteCode") { | |||
| onAcceptedClick() | |||
| } else if (getStatus == "complete") { | |||
| onComplatedClick() | |||
| } else if (getStatus == "withdraw") { | |||
| onWithdrawnClick() | |||
| } else if (getStatus == "notAccepted") { | |||
| onNotAcceptClick(getReason); | |||
| } else if (getStatus == "resubmit") { | |||
| onReSubmitClick(getReason); | |||
| } else if (getStatus == "publish") { | |||
| onPublishClick(); | |||
| } else if (getStatus == "revoke") { | |||
| onRevokeClick(); | |||
| } else if(getStatus == "paid"){ | |||
| onPaidClick(); | |||
| } else { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| } | |||
| }, [statusWindowAccepted]); | |||
| @@ -223,12 +230,23 @@ const PublicNoticeDetail_GLD = () => { | |||
| .catch(error => { | |||
| console.log(error); | |||
| return false; | |||
| }) | |||
| .finally(() => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| }); | |||
| } else { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| } | |||
| }; | |||
| const onNotAcceptClick = (reason) => { | |||
| if (params.id <= 0) return; | |||
| if (params.id <= 0) { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| return; | |||
| } | |||
| HttpUtils.post({ | |||
| url: `${SET_PUBLIC_NOTICE_STATUS_NOT_ACCEPT}/${params.id}`, | |||
| params: reason, | |||
| @@ -238,12 +256,24 @@ const PublicNoticeDetail_GLD = () => { | |||
| // location.reload(); | |||
| loadApplicationDetail() | |||
| notifySaveSuccess() | |||
| }, | |||
| onFail: () => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| }, | |||
| onError: () => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| } | |||
| }); | |||
| } | |||
| const onPublishClick = () => { | |||
| if (params.id <= 0) return; | |||
| if (params.id <= 0) { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| return; | |||
| } | |||
| HttpUtils.get({ | |||
| url: `${SET_PUBLIC_NOTICE_STATUS_PUBLISH}/${params.id}`, | |||
| onSuccess: function () { | |||
| @@ -251,6 +281,18 @@ const PublicNoticeDetail_GLD = () => { | |||
| handleClose(); | |||
| loadApplicationDetail() | |||
| notifySaveSuccess() | |||
| }, | |||
| onFail: () => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| }, | |||
| onError: () => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| }, | |||
| onFinally: () => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| } | |||
| }); | |||
| } | |||
| @@ -270,7 +312,14 @@ const PublicNoticeDetail_GLD = () => { | |||
| .catch(error => { | |||
| console.log(error); | |||
| return false; | |||
| }) | |||
| .finally(() => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| }); | |||
| } else { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| } | |||
| }; | |||
| @@ -289,7 +338,14 @@ const PublicNoticeDetail_GLD = () => { | |||
| .catch(error => { | |||
| console.log(error); | |||
| return false; | |||
| }) | |||
| .finally(() => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| }); | |||
| } else { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| } | |||
| }; | |||
| @@ -307,7 +363,14 @@ const PublicNoticeDetail_GLD = () => { | |||
| .catch(error => { | |||
| console.log(error); | |||
| return false; | |||
| }) | |||
| .finally(() => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| }); | |||
| } else { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| } | |||
| }; | |||
| @@ -322,6 +385,14 @@ const PublicNoticeDetail_GLD = () => { | |||
| // location.reload(); | |||
| loadApplicationDetail() | |||
| notifySaveSuccess() | |||
| }, | |||
| onFail: () => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| }, | |||
| onError: () => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| } | |||
| }); | |||
| // axios.post(`${SET_PUBLIC_NOTICE_STATUS_RESUBMIT}/${params.id}`) | |||
| @@ -338,11 +409,18 @@ const PublicNoticeDetail_GLD = () => { | |||
| // console.log(error); | |||
| // return false; | |||
| // }); | |||
| } else { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| } | |||
| }; | |||
| const onRevokeClick = () => { | |||
| if (params.id <= 0) return; | |||
| if (params.id <= 0) { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| return; | |||
| } | |||
| HttpUtils.get({ | |||
| url: `${SET_PUBLIC_NOTICE_STATUS_REVOKE}/${params.id}`, | |||
| onSuccess: function () { | |||
| @@ -350,6 +428,18 @@ const PublicNoticeDetail_GLD = () => { | |||
| handleClose(); | |||
| loadApplicationDetail() | |||
| notifySaveSuccess() | |||
| }, | |||
| onFail: () => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| }, | |||
| onError: () => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| }, | |||
| onFinally: () => { | |||
| setStatusConfirmLoading(false); | |||
| setStatusWindowAccepted(false); | |||
| } | |||
| }); | |||
| } | |||
| @@ -383,6 +473,7 @@ const PublicNoticeDetail_GLD = () => { | |||
| issueDate={issueDate} | |||
| issueNum={issueNum} | |||
| gazetteIssue={gazetteIssue} | |||
| confirmLoading={statusConfirmLoading} | |||
| //combo value | |||
| selectedGazetteGroup={selectedGazetteGroup} | |||
| setSelectedGazetteGroup={setSelectedGazetteGroup} | |||
| @@ -425,6 +516,9 @@ const PublicNoticeDetail_GLD = () => { | |||
| setUpdateApplicationObject={setUpdateApplicationObject} | |||
| isEditMode={isEditMode} | |||
| setiIsSave={setiIsSave} | |||
| statusDialogOpen={open} | |||
| statusDialogKind={getStatus} | |||
| statusActionLoading={statusConfirmLoading} | |||
| // isNewRecord={isNewRecord} | |||
| /> | |||
| } | |||
| @@ -17,6 +17,10 @@ export default function SubmittedTab({ appId, setCount }) { | |||
| const theme = useTheme(); | |||
| const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | |||
| const renderHeaderWithAria = (params) => ( | |||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||
| ); | |||
| const columns = [ | |||
| { | |||
| field: 'actions', | |||
| @@ -24,6 +28,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| cellClassName: 'actions', | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return clickableLink('/paymentPage/details/' + params.row.id, params.row.transNo); | |||
| }, | |||
| @@ -34,6 +39,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||
| headerName: 'Trans. Date', | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return DateUtils.datetimeStr(params.value); | |||
| } | |||
| @@ -44,6 +50,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||
| headerName: 'Status', | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return PaymentStatus.getStatus_Eng(params); | |||
| } | |||
| @@ -53,6 +60,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||
| field: 'payAmount', | |||
| headerName: 'Amount', | |||
| width: 150, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return (params?.value) ? "$ " + FormatUtils.currencyFormat(params?.value) : ""; | |||
| } | |||
| @@ -28,6 +28,10 @@ export default function ProofTab({appId, setCount}) { | |||
| }); | |||
| }; | |||
| const renderHeaderWithAria = (params) => ( | |||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||
| ); | |||
| const columns = [ | |||
| { | |||
| @@ -36,6 +40,7 @@ export default function ProofTab({appId, setCount}) { | |||
| width: isMdOrLg ? 'auto' : 200, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| cellClassName: 'actions', | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return clickableLink('/proof/reply/' + params.row.id, params.row.refNo); | |||
| }, | |||
| @@ -45,6 +50,7 @@ export default function ProofTab({appId, setCount}) { | |||
| headerName: 'Status', | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return ProofStatus.getStatus_Eng(params); | |||
| }, | |||
| @@ -54,7 +60,7 @@ export default function ProofTab({appId, setCount}) { | |||
| headerName: 'Proof Issue Date', | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return DateUtils.datetimeStr(params?.value); | |||
| } | |||
| @@ -64,6 +70,7 @@ export default function ProofTab({appId, setCount}) { | |||
| headerName: 'Confirmed/Return Date', | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return params?.value?DateUtils.datetimeStr(params?.value):""; | |||
| } | |||
| @@ -73,15 +80,17 @@ export default function ProofTab({appId, setCount}) { | |||
| headerName: 'Fee', | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return (params?.value)?"$ "+FormatUtils.currencyFormat(params?.value):""; | |||
| } | |||
| }, | |||
| { | |||
| field: 'actions', | |||
| type: 'actions', | |||
| headerName: 'Proof Slip', | |||
| width: 100, | |||
| renderHeader: renderHeaderWithAria, | |||
| cellClassName: 'actions', | |||
| getActions: (params) => { | |||
| if(params.row.action == null) return[]; | |||
| @@ -13,6 +13,10 @@ export default function StatusHistoryTab({appId, setCount}) { | |||
| const theme = useTheme(); | |||
| const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | |||
| const renderHeaderWithAria = (params) => ( | |||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||
| ); | |||
| const columns = [ | |||
| { | |||
| id: 'created', | |||
| @@ -20,6 +24,7 @@ export default function StatusHistoryTab({appId, setCount}) { | |||
| headerName: 'Date', | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return DateUtils.datetimeStr(params?.value); | |||
| } | |||
| @@ -31,6 +36,7 @@ export default function StatusHistoryTab({appId, setCount}) { | |||
| headerName: 'Changed By', | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| }, | |||
| { | |||
| id: 'status', | |||
| @@ -38,8 +44,9 @@ export default function StatusHistoryTab({appId, setCount}) { | |||
| headerName: 'Status', | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return [StatusUtils.getStatusEng(params)] | |||
| return StatusUtils.getStatusEng(params); | |||
| }, | |||
| }, | |||
| ]; | |||
| @@ -9,6 +9,7 @@ import { | |||
| import { TabPanel, TabContext, TabList } from '@mui/lab'; | |||
| import {useState, useEffect, lazy} from "react"; | |||
| import { useIntl } from 'react-intl'; | |||
| import Loadable from 'components/Loadable'; | |||
| const LoadingComponent = Loadable(lazy(() => import('../../../extra-pages/LoadingComponent'))); | |||
| @@ -20,7 +21,7 @@ const StatusHistoryTab = Loadable(lazy(() => import('./StatusHistoryTab'))); | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const PublicNotice = ({ appId, proofCount, paymentCount, statusHistoryCount, setProofCount, setPaymentCount, setStatusHistoryCount }) => { | |||
| const intl = useIntl(); | |||
| const [onReady, setOnReady] = useState(false); | |||
| const [selectedTab, setSelectedTab] = useState("1"); | |||
| @@ -41,10 +42,10 @@ const PublicNotice = ({ appId, proofCount, paymentCount, statusHistoryCount, set | |||
| <Grid item xs={12}> | |||
| <TabContext value={selectedTab}> | |||
| <Box sx={{ borderBottom: 1, borderColor: 'divider', overflowX: 'auto' }}> | |||
| <TabList onChange={handleChange} aria-label="lab API tabs example"> | |||
| <Tab renderActiveOnly={false} label={"Proof (" + proofCount + ") "} value="1" /> | |||
| <Tab renderActiveOnly={false} label={"Online Payment (" + paymentCount + ") "} value="2" /> | |||
| <Tab renderActiveOnly={false} label={"Status History (" + statusHistoryCount + ") "} value="3" /> | |||
| <TabList onChange={handleChange} aria-label={intl.formatMessage({ id: 'ariaRelatedRecords' })}> | |||
| <Tab label={"Proof (" + proofCount + ") "} value="1" /> | |||
| <Tab label={"Online Payment (" + paymentCount + ") "} value="2" /> | |||
| <Tab label={"Status History (" + statusHistoryCount + ") "} value="3" /> | |||
| </TabList> | |||
| </Box> | |||
| <TabPanel value="1" sx={{ p: 0 }}> | |||
| @@ -222,7 +222,6 @@ const ApplicationDetailCard = ( | |||
| <Button | |||
| variant="contained" | |||
| onClick={cancelledClick()} | |||
| color="edit" | |||
| disabled={currentApplicationDetailData.status == "rejected" | |||
| || currentApplicationDetailData.status == "cancelled" | |||
| || currentApplicationDetailData.status == "withdrawn" | |||
| @@ -239,6 +238,11 @@ const ApplicationDetailCard = ( | |||
| title={intl.formatMessage({ id: 'cancelApp' })} | |||
| startIcon={<CloseIcon />} | |||
| aria-label={intl.formatMessage({ id: 'cancelApp' })} | |||
| sx={{ | |||
| backgroundColor: '#0C489E', | |||
| color: '#FFFFFF', | |||
| '&:hover': { backgroundColor: '#093A7A' }, | |||
| }} | |||
| > | |||
| <FormattedMessage id="cancelApp" /> | |||
| </Button> | |||
| @@ -651,20 +655,24 @@ const ApplicationDetailCard = ( | |||
| <Grid container direction="row" alignItems="center" justifyContent="flex-start"> | |||
| <Grid item xs={12} sm={12} md={12} lg={12} sx={{ wordBreak: 'break-word', }}> | |||
| <Typography | |||
| fullWidth | |||
| id='fileName' | |||
| variant="pnspsFormParagraph" | |||
| sx={{ width: '100%' }} | |||
| > | |||
| {fileDetail?.filename} | |||
| </Typography> | |||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||
| <Button | |||
| sx={{ ml: 3 }} | |||
| sx={{ | |||
| ml: 3, | |||
| backgroundColor: '#0C489E', | |||
| color: '#FFFFFF', | |||
| '&:hover': { backgroundColor: '#093A7A' }, | |||
| }} | |||
| variant="contained" | |||
| onClick={onDownloadClick()} | |||
| aria-label={intl.formatMessage({ id: 'download' })} | |||
| title={intl.formatMessage({ id: 'download' })} | |||
| color="save" | |||
| disabled={!fileDetail?.filename||onDownload} | |||
| startIcon={<DownloadIcon sx={{ alignItems: "center" }} />} | |||
| > | |||
| @@ -690,7 +698,9 @@ const ApplicationDetailCard = ( | |||
| <FormControl variant="outlined" sx={{ width: '100%' }} disabled> | |||
| <OutlinedInput | |||
| size="small" | |||
| id="careOf" | |||
| value={currentApplicationDetailData.careOf} | |||
| inputProps={{ 'aria-label': intl.formatMessage({ id: 'careOf' }) }} | |||
| sx={{ | |||
| "& .MuiInputBase-input.Mui-disabled": { | |||
| WebkitTextFillColor: "#000000", | |||
| @@ -1,10 +1,12 @@ | |||
| import { | |||
| useEffect, | |||
| useState | |||
| useState, | |||
| useRef | |||
| } from "react"; | |||
| // material-ui | |||
| import { | |||
| Box, | |||
| Button, | |||
| // Link, | |||
| Stack, | |||
| @@ -14,7 +16,7 @@ import { | |||
| DialogContent, | |||
| DialogContentText, | |||
| DialogTitle, | |||
| FormLabel, | |||
| CircularProgress, | |||
| } from '@mui/material'; | |||
| import { useFormik,FormikProvider } from 'formik'; | |||
| import * as yup from 'yup'; | |||
| @@ -26,6 +28,7 @@ import {useIntl} from "react-intl"; | |||
| const StatusChangeDialog = (props) => { | |||
| const [status, setStatus] = useState(""); | |||
| const intl = useIntl(); | |||
| const confirmOnceRef = useRef(false); | |||
| useEffect(() => { | |||
| // console.log(Object.keys(!props.selectedGazetteGroup).length) | |||
| @@ -33,9 +36,25 @@ const StatusChangeDialog = (props) => { | |||
| setStatus(intl.formatMessage({id: 'cancel'})) | |||
| } | |||
| }, [props.getStatus]); | |||
| useEffect(() => { | |||
| if (!props.open) { | |||
| confirmOnceRef.current = false; | |||
| } | |||
| }, [props.open]); | |||
| const wasConfirmLoadingRef = useRef(false); | |||
| useEffect(() => { | |||
| if (wasConfirmLoadingRef.current && !props.confirmLoading) { | |||
| confirmOnceRef.current = false; | |||
| } | |||
| wasConfirmLoadingRef.current = props.confirmLoading; | |||
| }, [props.confirmLoading]); | |||
| const acceptedHandle = () => () =>{ | |||
| // console.log(selectedGazetteGroup) | |||
| if (props.confirmLoading) return; | |||
| if (confirmOnceRef.current) return; | |||
| confirmOnceRef.current = true; | |||
| props.setStatusWindowAccepted(true) | |||
| }; | |||
| @@ -60,19 +79,20 @@ const StatusChangeDialog = (props) => { | |||
| fullWidth={true} | |||
| maxWidth={'xs'} | |||
| > | |||
| <DialogTitle > | |||
| <Typography variant="h4"> | |||
| <DialogTitle component="div"> | |||
| <Typography variant="h4" component="h2"> | |||
| {status} {intl.formatMessage({id: 'publicNotice'})} | |||
| </Typography> | |||
| </DialogTitle> | |||
| <FormikProvider value={formik}> | |||
| <form> | |||
| <DialogContent> | |||
| <DialogContentText> | |||
| <FormLabel sx={{fontSize: "18px", color:"#000000",textAlign:"center"}}> | |||
| <Typography variant="h5"> | |||
| {intl.formatMessage({id: 'confirmTo'})}{status} {intl.formatMessage({id: 'publicNoticeApp'})}?</Typography> | |||
| </FormLabel> | |||
| <DialogContentText component="div"> | |||
| <Box sx={{ fontSize: '18px', color: '#000000', textAlign: 'center' }}> | |||
| <Typography variant="h5" component="p" sx={{ m: 0 }}> | |||
| {intl.formatMessage({id: 'confirmTo'})}{status} {intl.formatMessage({id: 'publicNoticeApp'})}? | |||
| </Typography> | |||
| </Box> | |||
| </DialogContentText> | |||
| </DialogContent> | |||
| </form> | |||
| @@ -85,6 +105,7 @@ const StatusChangeDialog = (props) => { | |||
| onClick={props.handleClose} | |||
| autoFocus | |||
| color="cancel" | |||
| disabled={props.confirmLoading} | |||
| > | |||
| <FormattedMessage id="cancel"/> | |||
| </Button> | |||
| @@ -95,6 +116,8 @@ const StatusChangeDialog = (props) => { | |||
| color="save" | |||
| onClick={acceptedHandle()} | |||
| autoFocus | |||
| disabled={props.confirmLoading} | |||
| startIcon={props.confirmLoading ? <CircularProgress color="inherit" size={20} /> : null} | |||
| > | |||
| <FormattedMessage id="confirm"/> | |||
| </Button> | |||
| @@ -1,6 +1,7 @@ | |||
| import React, { | |||
| useEffect, | |||
| useState, | |||
| useRef, | |||
| lazy | |||
| } from "react"; | |||
| @@ -32,10 +33,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(""); | |||
| @@ -47,6 +50,8 @@ const DashboardDefault = () => { | |||
| const [open, setOpen] = useState(false); | |||
| const [getStatus, setStatus] = useState(""); | |||
| const [statusWindowAccepted, setStatusWindowAccepted] = useState(false); | |||
| const [cancelLoading, setCancelLoading] = useState(false); | |||
| const cancellingRef = useRef(false); | |||
| const [proofCount, setProofCount] = useState(0); | |||
| const [paymentCount, setPaymentCount] = useState(0); | |||
| @@ -135,7 +140,10 @@ const DashboardDefault = () => { | |||
| }, [getStatus]); | |||
| const onCancelledClick = () => { | |||
| if (cancellingRef.current) return; | |||
| if (params.id > 0) { | |||
| cancellingRef.current = true; | |||
| setCancelLoading(true); | |||
| axios.get(`${SET_PUBLIC_NOTICE_STATUS_CANCELLED}/${params.id}`) | |||
| .then((response) => { | |||
| if (response.status === 204) { | |||
| @@ -149,17 +157,27 @@ const DashboardDefault = () => { | |||
| .catch(error => { | |||
| console.log(error); | |||
| return false; | |||
| }) | |||
| .finally(() => { | |||
| cancellingRef.current = false; | |||
| setCancelLoading(false); | |||
| }); | |||
| } | |||
| }; | |||
| return ( | |||
| <Grid container sx={{ backgroundColor: '#ffffff' }} direction="column"> | |||
| <StatusChangeDialog open={open} handleClose={handleClose} setStatusWindowAccepted={setStatusWindowAccepted} getStatus={getStatus} /> | |||
| <StatusChangeDialog | |||
| open={open} | |||
| handleClose={handleClose} | |||
| setStatusWindowAccepted={setStatusWindowAccepted} | |||
| getStatus={getStatus} | |||
| confirmLoading={cancelLoading} | |||
| /> | |||
| <Grid item xs={12}> | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{ display: { xs: 'none', sm: 'none', md: 'block' } }}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{ display: { xs: 'none', sm: 'none', md: 'block' } }}> | |||
| <FormattedMessage id="myPublicNotice" /> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -177,7 +195,7 @@ const DashboardDefault = () => { | |||
| > | |||
| <ForwardIcon style={{ height: 30, width: 50, transform: "rotate(180deg)" }} /> | |||
| </Button> | |||
| <Typography ml={3} mt={3} variant="h4">{title}</Typography> | |||
| <Typography component="h2" ml={3} mt={3} variant="h4">{title}</Typography> | |||
| </Stack> | |||
| </Grid> | |||
| <Grid item width={{ md: "60%", xs: "90%" }}> | |||
| @@ -21,6 +21,10 @@ export default function SubmittedTab({ appId, setCount }) { | |||
| const { locale } = intl; | |||
| const renderHeaderWithAria = (params) => ( | |||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||
| ); | |||
| const columns = [ | |||
| { | |||
| field: 'actions', | |||
| @@ -28,6 +32,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| cellClassName: 'actions', | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return clickableLink('/paymentPage/details/' + params.row.id, params.row.transNo); | |||
| }, | |||
| @@ -38,6 +43,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||
| headerName: intl.formatMessage({id: 'payDate'}), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return DateUtils.datetimeStr(params.value); | |||
| } | |||
| @@ -48,6 +54,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||
| headerName: intl.formatMessage({id: 'payStatus'}), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return locale === 'en' ? PaymentStatus.getStatus_Eng(params):PaymentStatus.getStatus_Cht(params); | |||
| } | |||
| @@ -57,6 +64,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||
| field: 'payAmount', | |||
| headerName: intl.formatMessage({id: 'fee'}), | |||
| width: 150, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return (params?.value) ? "$ " + FormatUtils.currencyFormat(params?.value) : ""; | |||
| } | |||
| @@ -19,23 +19,27 @@ export default function ProofTab({appId, setCount}) { | |||
| const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | |||
| const { locale } = intl; | |||
| const renderHeaderWithAria = (params) => ( | |||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||
| ); | |||
| const columns = [ | |||
| { | |||
| field: 'actions', | |||
| field: 'refNo', | |||
| headerName: intl.formatMessage({id: 'proofId'}), | |||
| width: 200, | |||
| cellClassName: 'actions', | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return clickableLink('/proof/reply/' + params.row.id, params.row.refNo); | |||
| }, | |||
| }, | |||
| { | |||
| id: 'actions', | |||
| field: 'status', | |||
| headerName: intl.formatMessage({id: 'status'}), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return locale === 'en' ? ProofStatus.getStatus_Eng(params) : ProofStatus.getStatus_Cht(params); | |||
| }, | |||
| @@ -46,6 +50,7 @@ export default function ProofTab({appId, setCount}) { | |||
| headerName: intl.formatMessage({id: 'proofDate'}), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return DateUtils.datetimeStr(params?.value); | |||
| } | |||
| @@ -56,6 +61,7 @@ export default function ProofTab({appId, setCount}) { | |||
| headerName: intl.formatMessage({id: 'replyDate'}), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return params?.value?DateUtils.datetimeStr(params?.value):""; | |||
| } | |||
| @@ -66,6 +72,7 @@ export default function ProofTab({appId, setCount}) { | |||
| headerName: intl.formatMessage({id: 'fee'}), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return (params?.value)?"$ "+FormatUtils.currencyFormat(params?.value):""; | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| // material-ui | |||
| import * as React from 'react'; | |||
| import { useTheme, useMediaQuery } from '@mui/material'; | |||
| import {FiDataGrid} from "components/FiDataGrid"; | |||
| import * as DateUtils from "utils/DateUtils" | |||
| import * as StatusUtils from "utils/statusUtils/PublicNoteStatusUtils"; | |||
| @@ -9,7 +10,7 @@ import {GET_PUBLIC_NOTICE_APPLY_DETAIL_STATUS_HISTORY } from "utils/ApiPathConst | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function StatusHistoryTab({appId, setCount}) { | |||
| const { useState, useEffect } = React; | |||
| const theme = useTheme(); | |||
| const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | |||
| @@ -21,6 +22,10 @@ export default function StatusHistoryTab({appId, setCount}) { | |||
| set_appId(appId); | |||
| }, []); | |||
| const renderHeaderWithAria = (params) => ( | |||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||
| ); | |||
| const columns = [ | |||
| { | |||
| id: 'created', | |||
| @@ -28,6 +33,7 @@ export default function StatusHistoryTab({appId, setCount}) { | |||
| headerName: 'Date', | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return DateUtils.datetimeStr(params?.value); | |||
| } | |||
| @@ -39,6 +45,7 @@ export default function StatusHistoryTab({appId, setCount}) { | |||
| headerName: 'Changed By', | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| }, | |||
| { | |||
| id: 'status', | |||
| @@ -46,8 +53,9 @@ export default function StatusHistoryTab({appId, setCount}) { | |||
| headerName: 'Status', | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return [StatusUtils.getStatusEng(params)] | |||
| return StatusUtils.getStatusEng(params); | |||
| }, | |||
| }, | |||
| ]; | |||
| @@ -43,7 +43,7 @@ const PublicNotice = ({ appId, proofCount, paymentCount, setProofCount, setPayme | |||
| <Grid item xs={12}> | |||
| <TabContext value={selectedTab}> | |||
| <Box sx={{ borderBottom: 1, borderColor: 'divider' }}> | |||
| <TabList onChange={handleChange} aria-label="lab API tabs example"> | |||
| <TabList onChange={handleChange} aria-label={intl.formatMessage({ id: 'ariaRelatedRecords' })}> | |||
| <Tab | |||
| aria-label={intl.formatMessage({ id: 'proofRecord' })} | |||
| label={ | |||
| @@ -28,6 +28,9 @@ export default function BaseGrid({setCount, url}) { | |||
| navigate('/publicNotice/'+ params.id); | |||
| }; | |||
| const renderHeaderWithAria = (params) => ( | |||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||
| ); | |||
| const columns = [ | |||
| { | |||
| @@ -36,8 +39,9 @@ export default function BaseGrid({setCount, url}) { | |||
| headerName: intl.formatMessage({id: 'applicationId'}), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return [params.row.appNo+getModeIntl(params,intl)] | |||
| return params.row.appNo + getModeIntl(params, intl); | |||
| }, | |||
| }, | |||
| { | |||
| @@ -46,6 +50,7 @@ export default function BaseGrid({setCount, url}) { | |||
| headerName: intl.formatMessage({id: 'submitDate'}), | |||
| width: isMdOrLg ? 'auto' : 300, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter:(params)=>{ | |||
| return DateUtils.datetimeStr(params?.value); | |||
| } | |||
| @@ -57,6 +62,7 @@ export default function BaseGrid({setCount, url}) { | |||
| headerName: isORGLoggedIn()? intl.formatMessage({id: 'gazetteCount2_1'}) : intl.formatMessage({id: 'myRemarks'}), | |||
| width: isMdOrLg ? 'auto' : 400, | |||
| flex: isMdOrLg ? 3 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => ( | |||
| isORGLoggedIn()? | |||
| isDummyLoggedIn()? | |||
| @@ -88,14 +94,16 @@ export default function BaseGrid({setCount, url}) { | |||
| headerName: intl.formatMessage({id: 'status'}), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return [getStatusIntl(params,intl)] | |||
| return getStatusIntl(params, intl); | |||
| }, | |||
| }, | |||
| { | |||
| field: 'actions', | |||
| headerName: '', | |||
| width: 160, | |||
| renderHeader: renderHeaderWithAria, | |||
| cellClassName: 'actions', | |||
| renderCell: (params) => { | |||
| return <Button aria-label={intl.formatMessage({id: 'viewDetail'})} onClick={handleDetailClick(params)}> | |||
| @@ -81,6 +81,10 @@ export default function SubmittedTab({ setCount, url }) { | |||
| navigate('/publicNotice/' + params.id); | |||
| }; | |||
| const renderHeaderWithAria = (params) => ( | |||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||
| ); | |||
| const handlePaymentBtn = () => { | |||
| let appIdList = []; | |||
| let paymentCheckList = []; | |||
| @@ -175,6 +179,7 @@ export default function SubmittedTab({ setCount, url }) { | |||
| headerName: intl.formatMessage({ id: 'applicationId' }), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| }, | |||
| { | |||
| id: 'created', | |||
| @@ -182,6 +187,7 @@ export default function SubmittedTab({ setCount, url }) { | |||
| headerName: intl.formatMessage({ id: 'submitDate' }), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return DateUtils.datetimeStr(params.value); | |||
| } | |||
| @@ -192,6 +198,7 @@ export default function SubmittedTab({ setCount, url }) { | |||
| headerName: isORGLoggedIn() ? intl.formatMessage({ id: 'gazetteCount2_1' }) : intl.formatMessage({ id: 'myRemarks' }), | |||
| width: isMdOrLg ? 'auto' : 400, | |||
| flex: isMdOrLg ? 3 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => ( | |||
| isORGLoggedIn() ? | |||
| isDummyLoggedIn()? | |||
| @@ -222,6 +229,7 @@ export default function SubmittedTab({ setCount, url }) { | |||
| headerName: intl.formatMessage({ id: 'price' }), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return FormatUtils.currencyFormat(params.row.fee) | |||
| }, | |||
| @@ -230,6 +238,7 @@ export default function SubmittedTab({ setCount, url }) { | |||
| id: 'paymentMethodAndDeadLine', | |||
| field: 'paymentMethodAndDeadLine', | |||
| headerName: intl.formatMessage({ id: 'paymentMethodAndDeadLine' }), | |||
| renderHeader: renderHeaderWithAria, | |||
| width: isMdOrLg ? 'auto' : 250, | |||
| flex: isMdOrLg ? 2 : undefined, | |||
| renderCell: (params) => ( | |||
| @@ -287,8 +296,9 @@ export default function SubmittedTab({ setCount, url }) { | |||
| headerName: intl.formatMessage({ id: 'status' }), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return [StatusUtils.getStatusIntl(params, intl)] | |||
| return StatusUtils.getStatusIntl(params, intl); | |||
| }, | |||
| }, | |||
| { | |||
| @@ -296,6 +306,7 @@ export default function SubmittedTab({ setCount, url }) { | |||
| type: 'actions', | |||
| headerName: '', | |||
| width: 150, | |||
| renderHeader: renderHeaderWithAria, | |||
| cellClassName: 'actions', | |||
| renderCell: (params) => { | |||
| return <Button aria-label={intl.formatMessage({ id: 'viewDetail' })} onClick={handleDetailClick(params)}> | |||
| @@ -313,23 +324,27 @@ export default function SubmittedTab({ setCount, url }) { | |||
| selectedRowItems.includes(row.id) | |||
| ); | |||
| for (var i = 0; i < datas?.length; i++) { | |||
| content.push(<> | |||
| <Stack direction="row" justifyContent="space-between"> | |||
| <Typography variant="h5"> | |||
| <FormattedMessage id="applicationId" />: {datas[i].appNo} | |||
| </Typography> | |||
| ({DateUtils.datetimeStr(datas[i].created)}) | |||
| </Stack> | |||
| <FormattedMessage id="extraMark" />: {datas[i].remarks} | |||
| <br /><br /> | |||
| </>); | |||
| content.push( | |||
| <React.Fragment key={datas[i].id}> | |||
| <Stack direction="row" justifyContent="space-between"> | |||
| <Typography variant="h5"> | |||
| <FormattedMessage id="applicationId" />: {datas[i].appNo} | |||
| </Typography> | |||
| ({DateUtils.datetimeStr(datas[i].created)}) | |||
| </Stack> | |||
| <FormattedMessage id="extraMark" />: {datas[i].remarks} | |||
| <br /><br /> | |||
| </React.Fragment> | |||
| ); | |||
| totalAmount += datas[i].fee; | |||
| } | |||
| content.push(<Typography variant="h5"> | |||
| <FormattedMessage id="totalAmount" /> ($): {FormatUtils.currencyFormat(totalAmount)} | |||
| <br /><br /> | |||
| </Typography>); | |||
| content.push( | |||
| <Typography key="payment-total" variant="h5"> | |||
| <FormattedMessage id="totalAmount" /> ($): {FormatUtils.currencyFormat(totalAmount)} | |||
| <br /><br /> | |||
| </Typography> | |||
| ); | |||
| return content; | |||
| } | |||
| @@ -391,6 +406,11 @@ export default function SubmittedTab({ setCount, url }) { | |||
| id="careOfCombo" | |||
| value={selectedCareOf === null ? null : selectedCareOf} | |||
| options={careOfComboList} | |||
| getOptionLabel={(option) => { | |||
| if (option == null) return ""; | |||
| if (typeof option === "string") return option; | |||
| return option.label != null ? String(option.label) : ""; | |||
| }} | |||
| onChange={(event, newValue) => { | |||
| // console.log(newValue) | |||
| setSelectedCareOf(newValue); | |||
| @@ -400,7 +420,11 @@ export default function SubmittedTab({ setCount, url }) { | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => <TextField {...params} />} | |||
| renderInput={(params) => <TextField {...params} inputProps={{ ...params.inputProps, 'aria-label': intl.formatMessage({ id: 'careOf' }) }} />} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| </Grid> : null | |||
| @@ -427,7 +451,6 @@ export default function SubmittedTab({ setCount, url }) { | |||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||
| <Button | |||
| color="create" | |||
| variant="contained" | |||
| aria-label={intl.formatMessage({ id: 'payOnlineBtn' })} | |||
| onClick={() => { handlePaymentBtn() }} | |||
| @@ -138,7 +138,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| </Grid> | |||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateFrom" | |||
| @@ -166,7 +166,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| </Grid> | |||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateTo" | |||
| @@ -234,7 +234,11 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| } | |||
| value={status} | |||
| // inputValue={status?.labelCht} | |||
| getOptionLabel={(option) => intl.formatMessage({id: option.label})} | |||
| getOptionLabel={(option) => { | |||
| if (option == null || option.label == null) return ""; | |||
| const s = intl.formatMessage({ id: option.label }); | |||
| return typeof s === "string" ? s : String(s ?? ""); | |||
| }} | |||
| onChange={(event, newValue) => { | |||
| if(newValue ==null){ | |||
| setStatus(localStorage.getItem('userData').creditor?ComboData.publicNoticeStatic_Creditor[0]:ComboData.publicNoticeStatic[0]); | |||
| @@ -256,6 +260,10 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| }} | |||
| /> | |||
| )} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| // InputLabelProps={{ | |||
| // shrink: true | |||
| // }} | |||
| @@ -276,7 +284,11 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||
| } | |||
| value={status} | |||
| // inputValue={status?.labelCht} | |||
| getOptionLabel={(option) => intl.formatMessage({id: option.label})} | |||
| getOptionLabel={(option) => { | |||
| if (option == null || option.label == null) return ""; | |||
| const s = intl.formatMessage({ id: option.label }); | |||
| return typeof s === "string" ? s : String(s ?? ""); | |||
| }} | |||
| onChange={(event, newValue) => { | |||
| console.log(newValue) | |||
| const findAllIndex = newValue.findIndex((ele) => { | |||
| @@ -35,6 +35,10 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| navigate('/publicNotice/' + params.id); | |||
| }; | |||
| const renderHeaderWithAria = (params) => ( | |||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||
| ); | |||
| const columns = [ | |||
| { | |||
| id: 'appNo', | |||
| @@ -42,8 +46,9 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: intl.formatMessage({ id: 'applicationId' }), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return [params.row.appNo+getModeIntl(params,intl)] | |||
| return params.row.appNo + getModeIntl(params, intl); | |||
| }, | |||
| }, | |||
| { | |||
| @@ -52,6 +57,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: intl.formatMessage({ id: 'submitDate' }), | |||
| width: isMdOrLg ? 'auto' : 160, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return DateUtils.datetimeStr(params?.value); | |||
| } | |||
| @@ -88,6 +94,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: isORGLoggedIn() ? intl.formatMessage({ id: 'gazetteCount2_1' }) : intl.formatMessage({ id: 'myRemarks' }), | |||
| width: isMdOrLg ? 'auto' : 400, | |||
| flex: isMdOrLg ? 3 : undefined, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => ( | |||
| isORGLoggedIn() ? | |||
| isDummyLoggedIn()? | |||
| @@ -117,8 +124,9 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| field: 'status', | |||
| headerName: intl.formatMessage({ id: 'status' }), | |||
| width: 200, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return [StatusUtils.getStatusIntl(params, intl)] | |||
| return StatusUtils.getStatusIntl(params, intl); | |||
| }, | |||
| }, | |||
| { | |||
| @@ -126,6 +134,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| type: 'actions', | |||
| headerName: '', | |||
| width: 150, | |||
| renderHeader: renderHeaderWithAria, | |||
| cellClassName: 'actions', | |||
| renderCell: (params) => { | |||
| return <Button onClick={handleDetailClick(params)} | |||
| @@ -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); | |||
| @@ -113,7 +114,7 @@ const PublicNotice = () => { | |||
| <Grid item xs={12}> | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{ display: { xs: 'none', sm: 'none', md: 'block' } }}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{ display: { xs: 'none', sm: 'none', md: 'block' } }}> | |||
| <FormattedMessage id="myPublicNotice" /> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -123,7 +124,11 @@ const PublicNotice = () => { | |||
| <Stack direction="row" justifyContent="flex-end" alignItems="center"> | |||
| <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | |||
| <Box sx={{ mr: { md: "47px" } }}> | |||
| <Button aria-label={intl.formatMessage({ id: 'applyPublicNotice' })} variant="contained" onClick={() => { onBtnClick() }}> | |||
| <Button | |||
| aria-label={intl.formatMessage({ id: 'applyPublicNotice' })} | |||
| variant="contained" | |||
| onClick={() => { onBtnClick() }} | |||
| > | |||
| <FormattedMessage id="applyPublicNotice" /> | |||
| </Button> | |||
| </Box> | |||
| @@ -137,7 +142,7 @@ const PublicNotice = () => { | |||
| <Grid item xs={12} sm={12} md={12} lg={12} sx={{ height: '100%', maxWidth: '100%', width: "-webkit-fill-available", backgroundColor: "#fff", mt: 3, mr: { xs: 1, md: 3 }, ml: { xs: 1, md: 3 }, mb: 3, ..._sx }}> | |||
| <TabContext value={selectedTab}> | |||
| <Box sx={{ borderBottom: 1, borderColor: 'divider', overflowX: 'auto', overflowY: 'auto' }}> | |||
| <TabList variant="scrollable" onChange={handleChange} aria-label="lab API tabs example" sx={{ display: 'flex', flexDirection: 'row' }}> | |||
| <TabList variant="scrollable" onChange={handleChange} aria-label={intl.formatMessage({ id: 'ariaRelatedRecords' })} sx={{ display: 'flex', flexDirection: 'row' }}> | |||
| <Tab aria-label={intl.formatMessage({ id: 'processing' })} label={intl.formatMessage({ id: 'processing' }) + " (" + submittedCount + ")"} value="1" /> | |||
| <Tab aria-label={intl.formatMessage({ id: 'pendingPublish' })} label={intl.formatMessage({ id: 'pendingPublish' }) + " (" + pendingPublishCount + ")"} value="3" /> | |||
| <Tab aria-label={intl.formatMessage({ id: 'pendingPayment' })} label={intl.formatMessage({ id: 'pendingPayment' }) + " (" + pendingPaymentCount + ")"} value="4" /> | |||
| @@ -171,7 +176,7 @@ const PublicNotice = () => { | |||
| <Grid item xs={12} sx={{ minHeight: '80vh', height: "100%", maxHeight: '300vh', maxWidth: '95%', width: "-webkit-fill-available", backgroundColor: "#fff", mt: 3, mr: { xs: 1, md: 3 }, ml: { xs: 1, md: 3 }, mb: 3, ..._sx }}> | |||
| <TabContext value={selectedTab}> | |||
| <Box sx={{ borderBottom: 1, borderColor: 'divider' }}> | |||
| <TabList variant="scrollable" onChange={handleChange} aria-label="lab API tabs example"> | |||
| <TabList variant="scrollable" onChange={handleChange} aria-label={intl.formatMessage({ id: 'ariaApplicationGroup' })}> | |||
| <Tab aria-label={intl.formatMessage({ id: 'processing' })} label={intl.formatMessage({ id: 'processing' }) + " (" + submittedCount + ")"} value="1" /> | |||
| <Tab aria-label={intl.formatMessage({ id: 'pendingPayment' })} label={intl.formatMessage({ id: 'pendingPayment' }) + " (" + pendingPaymentCount + ")"} value="3" /> | |||
| <Tab aria-label={intl.formatMessage({ id: 'pendingPublish' })} label={intl.formatMessage({ id: 'pendingPublish' }) + " (" + pendingPublishCount + ")"} value="4" /> | |||
| @@ -53,6 +53,10 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| return groupNo | |||
| } | |||
| const renderHeaderWithAria = (params) => ( | |||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||
| ); | |||
| const columns = [ | |||
| { | |||
| field: 'actions', | |||
| @@ -60,6 +64,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| sortable: false, | |||
| width: 150, | |||
| cellClassName: 'actions', | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return clickableLink('/application/' + params.id, params.row.appNo); | |||
| }, | |||
| @@ -70,8 +75,9 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: 'Mode', | |||
| sortable: false, | |||
| width: 100, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return [StatusUtils.getModeEng(params)] | |||
| return StatusUtils.getModeEng(params); | |||
| }, | |||
| }, | |||
| { | |||
| @@ -80,8 +86,9 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: 'Status', | |||
| sortable: false, | |||
| width: 240, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return [StatusUtils.getStatusEng(params)] | |||
| return StatusUtils.getStatusEng(params); | |||
| }, | |||
| }, | |||
| { | |||
| @@ -90,8 +97,9 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: 'With Proof', | |||
| sortable: false, | |||
| width: 120, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return [params.row.proofId != null ? "Yes" : ""] | |||
| return params.row.proofId != null ? "Yes" : ""; | |||
| }, | |||
| }, | |||
| { | |||
| @@ -101,6 +109,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| sortable: false, | |||
| flex: 1, | |||
| minWidth: 200, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return DateUtils.datetimeStr(params?.value); | |||
| } | |||
| @@ -112,6 +121,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| sortable: false, | |||
| minWidth: 250, | |||
| flex: 2, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| let company = params.row.enCompanyName != null ? params.row.enCompanyName : params.row.chCompanyName; | |||
| company = company != null ? company : ""; | |||
| @@ -131,6 +141,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| sortable: false, | |||
| flex: 1.5, | |||
| minWidth: 350, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => ( | |||
| <div> | |||
| {genIssueNo(params)} | |||
| @@ -279,7 +279,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||
| setSelectedStatus(newValue); | |||
| } | |||
| }} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| @@ -317,7 +317,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||
| setSelectedLabelsString(selectedLabelsString); | |||
| } | |||
| }} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| renderInput={(params) => ( | |||
| <TextField | |||
| {...params} | |||
| @@ -419,7 +419,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||
| options={ComboData.groupTitle} | |||
| value={groupSelected} | |||
| inputValue={(groupSelected?.label) ? groupSelected?.label : ""} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| onChange={(event, newValue) => { | |||
| setGroupSelected(newValue); | |||
| }} | |||
| @@ -470,7 +470,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| renderInput={(params) => ( | |||
| <TextField | |||
| {...params} | |||
| @@ -115,6 +115,10 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| }; | |||
| const renderHeaderWithAria = (params) => ( | |||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||
| ); | |||
| const columns = [ | |||
| { | |||
| field: 'actions', | |||
| @@ -122,6 +126,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| sortable: false, | |||
| width: 150, | |||
| cellClassName: 'actions', | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| return clickableLink('/application/' + params.id, params.row.appNo); | |||
| }, | |||
| @@ -132,6 +137,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: 'Customer Name', | |||
| flex: 1, | |||
| minWidth: 50, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| let company = params.row.enCompanyName != null ? params.row.enCompanyName : params.row.chCompanyName; | |||
| company = company != null ? company : ""; | |||
| @@ -150,6 +156,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| sortable: false, | |||
| flex: 1.5, | |||
| minWidth: 350, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => ( | |||
| <div> | |||
| {genIssueNo(params)} | |||
| @@ -165,6 +172,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| sortable: false, | |||
| minWidth: 250, | |||
| flex: 2, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| let paymentMethod = params.row.paymentMethod!=null?intl.formatMessage({ id: utils.getPaymentMethod(params.row.paymentMethod)}):"" | |||
| return (<> | |||
| @@ -178,6 +186,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: 'Amount($)', | |||
| flex: 1, | |||
| minWidth: 100, | |||
| renderHeader: renderHeaderWithAria, | |||
| valueGetter: (params) => { | |||
| return FormatUtils.currencyFormat(params?.value); | |||
| } | |||
| @@ -188,6 +197,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||
| headerName: 'Remarks', | |||
| flex: 2, | |||
| minWidth: 200, | |||
| renderHeader: renderHeaderWithAria, | |||
| renderCell: (params) => { | |||
| const handleBlur = (event) => { | |||
| const newValue = event.target.value; | |||
| @@ -266,7 +266,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||
| setSelectedStatus(newValue); | |||
| } | |||
| }} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| @@ -304,7 +304,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||
| setSelectedLabelsString(selectedLabelsString); | |||
| } | |||
| }} | |||
| getOptionLabel={(option) => option.label} | |||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||
| renderInput={(params) => ( | |||
| <TextField | |||
| {...params} | |||