| @@ -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/cache": "^11.10.3", | ||||
| "@emotion/react": "^11.10.4", | "@emotion/react": "^11.10.4", | ||||
| "@emotion/styled": "^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", | "@material-ui/pickers": "^3.3.10", | ||||
| "@mui/icons-material": "^5.14.1", | "@mui/icons-material": "^5.14.1", | ||||
| "@mui/lab": "^5.0.0-alpha.139", | "@mui/lab": "^5.0.0-alpha.139", | ||||
| @@ -8,10 +8,10 @@ | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
| <meta name="theme-color" content="#ffffff" /> | <meta name="theme-color" content="#ffffff" /> | ||||
| <meta name="msapplication-TileColor" content="#da532c"> | <meta name="msapplication-TileColor" content="#da532c"> | ||||
| <meta name="title" content="PNSPS" /> | |||||
| <meta name="title" content="PNSPS - Public Notice Submission and Payment System" /> | |||||
| <meta | <meta | ||||
| name="description" | 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 | <meta | ||||
| name="keywords" | name="keywords" | ||||
| @@ -19,12 +19,7 @@ | |||||
| /> | /> | ||||
| <meta name="author" content="Government Logistics Department" /> | <meta name="author" content="Government Logistics Department" /> | ||||
| <link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" /> | <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> | </head> | ||||
| <body> | <body> | ||||
| <noscript>You need to enable JavaScript to run this app.</noscript> | <noscript>You need to enable JavaScript to run this app.</noscript> | ||||
| @@ -2,24 +2,23 @@ | |||||
| import Routes from 'routes'; | import Routes from 'routes'; | ||||
| import ThemeCustomization from 'themes'; | import ThemeCustomization from 'themes'; | ||||
| import ScrollTop from 'components/ScrollTop'; | 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 'react-toastify/dist/ReactToastify.css'; | ||||
| import {PNSPS_THEME} from "./themes/themeConst"; | |||||
| import {ThemeProvider} from "@emotion/react"; | |||||
| //import {isUserLoggedIn} from 'utils/Utils'; | //import {isUserLoggedIn} from 'utils/Utils'; | ||||
| //import {DefaultRoute} from 'routes/index' | //import {DefaultRoute} from 'routes/index' | ||||
| // ==============================|| APP - THEME, ROUTER, LOCAL ||============================== // | // ==============================|| APP - THEME, ROUTER, LOCAL ||============================== // | ||||
| const App = () => ( | 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; | 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{ | #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{ | #navbar div{ | ||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| } | } | ||||
| #navbar div li{ | #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{ | #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{ | #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: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; | background: white; | ||||
| visibility: hidden; | visibility: hidden; | ||||
| opacity: 0; | opacity: 0; | ||||
| min-width: 18rem; | min-width: 18rem; | ||||
| position: absolute; | position: absolute; | ||||
| /* transition: all 0.5s ease; */ | |||||
| left: 0; | left: 0; | ||||
| display: none; | display: none; | ||||
| padding-left: 0px; | padding-left: 0px; | ||||
| padding-bottom: 7px; | padding-bottom: 7px; | ||||
| /* border: 1px solid #0C489E; */ | |||||
| background-clip: padding-box; | background-clip: padding-box; | ||||
| border: 1px solid rgba(0,0,0,.15); | border: 1px solid rgba(0,0,0,.15); | ||||
| border-radius: 0.25rem; | 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; | visibility: visible; | ||||
| opacity: 1; | 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{ | #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{ | #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{ | #sidebar{ | ||||
| font-size: 1.3rem; | |||||
| font-weight: 600; | |||||
| /* font-family: 微軟正黑體; */ | |||||
| font-size: 1.1rem; | |||||
| font-weight: 600; | |||||
| } | } | ||||
| #sidebartop{ | #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{ | #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{ | #sidebarbottom{ | ||||
| align-items: center; | |||||
| justify-content: center; | |||||
| padding: 0; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| padding: 0; | |||||
| display: flex; | |||||
| } | } | ||||
| #sidebar li{ | #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{ | #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{ | #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, | html, | ||||
| body, | body, | ||||
| #root, | #root, | ||||
| @@ -52,26 +49,97 @@ img | |||||
| img:hover{-webkit-filter:none;} | 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; | 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 {useNavigate} from "react-router-dom"; | ||||
| import {useDispatch} from "react-redux"; | import {useDispatch} from "react-redux"; | ||||
| import { REFRESH_TOKEN } from 'utils/ApiPathConst'; | 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 | // ** Handle User Login | ||||
| export const handleLogin = data => { | export const handleLogin = data => { | ||||
| @@ -39,6 +45,8 @@ export const handleLogin = data => { | |||||
| localStorage.setItem(windowCount, '0') | localStorage.setItem(windowCount, '0') | ||||
| localStorage.setItem(predictProductionQty, '0') | localStorage.setItem(predictProductionQty, '0') | ||||
| localStorage.setItem(predictUsageCount, '0') | localStorage.setItem(predictUsageCount, '0') | ||||
| localStorage.removeItem('expiredAlertShown') | |||||
| expiredAlertShownInMemory = false | |||||
| } | } | ||||
| } | } | ||||
| @@ -94,7 +102,9 @@ export const handleLogoutFunction = () => { | |||||
| localStorage.removeItem('transactionid') | localStorage.removeItem('transactionid') | ||||
| localStorage.removeItem('searchCriteria') | localStorage.removeItem('searchCriteria') | ||||
| //localStorage.removeItem(config.storageUserRoleKeyName) | //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(refreshIntervalName) | ||||
| localStorage.removeItem(windowCount) | localStorage.removeItem(windowCount) | ||||
| localStorage.removeItem(predictProductionQty) | localStorage.removeItem(predictProductionQty) | ||||
| @@ -109,7 +119,13 @@ export const SetupAxiosInterceptors = () => { | |||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| //const updateLastRequestTime = useContext(TimerContext); | //const updateLastRequestTime = useContext(TimerContext); | ||||
| let isRefreshToken= false; | 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( | axios.interceptors.request.use( | ||||
| config => { | config => { | ||||
| // ** Get token from localStorage | // ** Get token from localStorage | ||||
| @@ -134,7 +150,17 @@ export const SetupAxiosInterceptors = () => { | |||||
| }, | }, | ||||
| async (error) => { | async (error) => { | ||||
| // const { config, response: { status } } = 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 | // Make a request to refresh the access token | ||||
| const refreshToken = localStorage.getItem('refreshToken'); | const refreshToken = localStorage.getItem('refreshToken'); | ||||
| if (isRefreshToken) { | if (isRefreshToken) { | ||||
| @@ -156,17 +182,23 @@ export const SetupAxiosInterceptors = () => { | |||||
| } | } | ||||
| }) | }) | ||||
| .catch((refreshError) => { | .catch((refreshError) => { | ||||
| isRefreshToken = false; | |||||
| if (!expiredAlertShownInMemory && localStorage.getItem("expiredAlertShown") === null) { | |||||
| expiredAlertShownInMemory = true; | |||||
| localStorage.setItem("expiredAlertShown", "true"); | |||||
| alert(getMessage("autoLogout")); | |||||
| } | |||||
| dispatch(handleLogoutFunction()); | dispatch(handleLogoutFunction()); | ||||
| navigate('/login'); | navigate('/login'); | ||||
| isRefreshToken = false; | |||||
| window.location.reload(); | window.location.reload(); | ||||
| throw refreshError; | throw refreshError; | ||||
| }); | }); | ||||
| } else { | } 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(); | await window.location.reload(); | ||||
| } | } | ||||
| if (error.response.status === 500){ | |||||
| if (error.response?.status === 500){ | |||||
| //setIsUploading(false); | //setIsUploading(false); | ||||
| } | } | ||||
| // console.log(error) | // console.log(error) | ||||
| @@ -171,3 +171,6 @@ export const getPaymentMethodByCode = (code) => { | |||||
| return "other"; | return "other"; | ||||
| }; | }; | ||||
| export function setPageTitle(title) { | |||||
| document.title = `${title} - PNSPS | Government Logistics Department`; | |||||
| } | |||||
| @@ -5,7 +5,7 @@ import { motion } from 'framer-motion'; | |||||
| // ==============================|| ANIMATION BUTTON ||============================== // | // ==============================|| ANIMATION BUTTON ||============================== // | ||||
| export default function AnimateButton({ children, type }) { | |||||
| export default function AnimateButton({ children, type = 'scale' }) { | |||||
| switch (type) { | switch (type) { | ||||
| case 'rotate': // only available in paid version | case 'rotate': // only available in paid version | ||||
| case 'slide': // only available in paid version | case 'slide': // only available in paid version | ||||
| @@ -23,7 +23,3 @@ AnimateButton.propTypes = { | |||||
| children: PropTypes.node, | children: PropTypes.node, | ||||
| type: PropTypes.oneOf(['slide', 'scale', 'rotate']) | type: PropTypes.oneOf(['slide', 'scale', 'rotate']) | ||||
| }; | }; | ||||
| AnimateButton.defaultProps = { | |||||
| type: 'scale' | |||||
| }; | |||||
| @@ -15,24 +15,36 @@ import { | |||||
| checkSysEnv | checkSysEnv | ||||
| } from "utils/Utils"; | } from "utils/Utils"; | ||||
| import {useIntl} from "react-intl"; | |||||
| // ==============================|| MAIN LOGO ||============================== // | // ==============================|| MAIN LOGO ||============================== // | ||||
| const LogoSection = ({ sx, to }) => { | const LogoSection = ({ sx, to }) => { | ||||
| const { defaultId } = useSelector((state) => state.menu); | const { defaultId } = useSelector((state) => state.menu); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const intl = useIntl(); | |||||
| return ( | 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 /> | <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 {useNavigate} from "react-router-dom"; | ||||
| import {useIdleTimer} from "react-idle-timer"; | import {useIdleTimer} from "react-idle-timer"; | ||||
| import { handleLogoutFunction } from 'auth/index'; | import { handleLogoutFunction } from 'auth/index'; | ||||
| import { useDispatch } from "react-redux"; | import { useDispatch } from "react-redux"; | ||||
| import { useIntl } from "react-intl"; | |||||
| import { | import { | ||||
| isUserLoggedIn, | isUserLoggedIn, | ||||
| isGLDLoggedIn, | isGLDLoggedIn, | ||||
| @@ -12,9 +13,11 @@ import { | |||||
| const TimerContext = createContext(); | const TimerContext = createContext(); | ||||
| const AutoLogoutProvider = ({ children }) => { | const AutoLogoutProvider = ({ children }) => { | ||||
| const intl = useIntl(); | |||||
| const [lastRequestTime, setLastRequestTime] = useState(Date.now()); | const [lastRequestTime, setLastRequestTime] = useState(Date.now()); | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const [logoutInterval, setLogoutInterval] = useState(1); | const [logoutInterval, setLogoutInterval] = useState(1); | ||||
| const idleLogoutTriggeredRef = useRef(false); | |||||
| // const [remainingInterval] = useState(5); | // const [remainingInterval] = useState(5); | ||||
| const [state, setState] = useState('Active'); | const [state, setState] = useState('Active'); | ||||
| const dispatch = useDispatch() | const dispatch = useDispatch() | ||||
| @@ -93,8 +96,13 @@ const AutoLogoutProvider = ({ children }) => { | |||||
| // console.log(remainingInterval * 60); | // console.log(remainingInterval * 60); | ||||
| // console.log(logoutInterval * 60 * 1000 - timeElapsed) | // console.log(logoutInterval * 60 * 1000 - timeElapsed) | ||||
| if (timeElapsed >= logoutInterval * 60 * 1000) { | 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()); | dispatch(handleLogoutFunction()); | ||||
| navigate('/login'); | navigate('/login'); | ||||
| window.location.reload(); | window.location.reload(); | ||||
| @@ -1,5 +1,5 @@ | |||||
| // material-ui | // material-ui | ||||
| import { useState, useEffect } from 'react'; | |||||
| import { useState, useEffect, useRef } from 'react'; | |||||
| import { Box } from "@mui/material"; | import { Box } from "@mui/material"; | ||||
| import { | import { | ||||
| DataGrid, GridOverlay, | 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 ( | return ( | ||||
| <Box sx={containerSx}> | |||||
| <Box sx={containerSx} ref={gridRootRef} role="table"> | |||||
| <DataGrid | <DataGrid | ||||
| {...props} | {...props} | ||||
| rows={_rows} | rows={_rows} | ||||
| @@ -239,16 +279,26 @@ export function FiDataGrid({ rows, columns, sx, autoHeight = true, | |||||
| ? { | ? { | ||||
| Pagination: () => ( | Pagination: () => ( | ||||
| <TablePagination | <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 Logo from './Logo'; | ||||
| import config from 'config'; | import config from 'config'; | ||||
| import { activeItem } from 'store/reducers/menu'; | import { activeItem } from 'store/reducers/menu'; | ||||
| import {useIntl} from "react-intl"; | |||||
| // ==============================|| MAIN LOGO ||============================== // | // ==============================|| MAIN LOGO ||============================== // | ||||
| const LogoSection = ({ sx, to }) => { | const LogoSection = ({ sx, to }) => { | ||||
| const { defaultId } = useSelector((state) => state.menu); | const { defaultId } = useSelector((state) => state.menu); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const intl = useIntl(); | |||||
| return ( | return ( | ||||
| <ButtonBase | <ButtonBase | ||||
| disableRipple | disableRipple | ||||
| component={Link} | component={Link} | ||||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | ||||
| to={!to ? config.defaultPath : to} | to={!to ? config.defaultPath : to} | ||||
| aria-label={intl.formatMessage({ id: "PNSPS", defaultMessage: "PNSPS" })} | |||||
| sx={sx} | sx={sx} | ||||
| > | > | ||||
| <Logo /> | <Logo /> | ||||
| @@ -1,5 +1,6 @@ | |||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||
| import { forwardRef } from 'react'; | import { forwardRef } from 'react'; | ||||
| import omit from 'lodash/omit'; | |||||
| // material-ui | // material-ui | ||||
| import { useTheme } from '@mui/material/styles'; | import { useTheme } from '@mui/material/styles'; | ||||
| @@ -8,6 +9,29 @@ import { Card, CardContent, CardHeader, Divider, Typography, Grid } from '@mui/m | |||||
| // project import | // project import | ||||
| import Highlighter from './third-party/Highlighter'; | 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 | // header style | ||||
| const headerSX = { | const headerSX = { | ||||
| p: 2.5, | p: 2.5, | ||||
| @@ -38,6 +62,8 @@ const MainCard = forwardRef( | |||||
| const theme = useTheme(); | const theme = useTheme(); | ||||
| boxShadow = theme.palette.mode === 'dark' ? boxShadow || true : boxShadow; | boxShadow = theme.palette.mode === 'dark' ? boxShadow || true : boxShadow; | ||||
| const cardProps = omit(others, PROPS_OMIT_FROM_CARD); | |||||
| return ( | return ( | ||||
| <Grid container direction="column" | <Grid container direction="column" | ||||
| // alignItems="center" | // alignItems="center" | ||||
| @@ -46,7 +72,7 @@ const MainCard = forwardRef( | |||||
| <Card | <Card | ||||
| elevation={elevation || 0} | elevation={elevation || 0} | ||||
| ref={ref} | ref={ref} | ||||
| {...others} | |||||
| {...cardProps} | |||||
| sx={{ | sx={{ | ||||
| alignItems: "center", | alignItems: "center", | ||||
| border: border ? '1px solid' : 'none', | border: border ? '1px solid' : 'none', | ||||
| @@ -9,22 +9,35 @@ import { useDispatch, useSelector } from 'react-redux'; | |||||
| import Logo from './MobileLogo'; | import Logo from './MobileLogo'; | ||||
| import config from 'config'; | import config from 'config'; | ||||
| import { activeItem } from 'store/reducers/menu'; | import { activeItem } from 'store/reducers/menu'; | ||||
| import {useIntl} from "react-intl"; | |||||
| // ==============================|| MAIN LOGO ||============================== // | // ==============================|| MAIN LOGO ||============================== // | ||||
| const LogoSection = ({ sx, to }) => { | const LogoSection = ({ sx, to }) => { | ||||
| const intl = useIntl(); | |||||
| const { defaultId } = useSelector((state) => state.menu); | const { defaultId } = useSelector((state) => state.menu); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| return ( | return ( | ||||
| <ButtonBase | <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 { useMediaQuery, Container, Link, Typography, Stack } from '@mui/material'; | ||||
| import bhkLogo from 'assets/images/BHK_logo_rgb_zh-hk.png'; | import bhkLogo from 'assets/images/BHK_logo_rgb_zh-hk.png'; | ||||
| import {FormattedMessage} from "react-intl"; | import {FormattedMessage} from "react-intl"; | ||||
| import {useIntl} from "react-intl"; | |||||
| import { | import { | ||||
| isGLDLoggedIn, | isGLDLoggedIn, | ||||
| } from "utils/Utils"; | } from "utils/Utils"; | ||||
| @@ -9,9 +10,14 @@ import { | |||||
| const AuthFooter = () => { | const AuthFooter = () => { | ||||
| const matchDownSM = useMediaQuery((theme) => theme.breakpoints.down('sm')); | const matchDownSM = useMediaQuery((theme) => theme.breakpoints.down('sm')); | ||||
| const intl = useIntl(); | |||||
| const bhkAlt = intl.formatMessage({ id: "bhkLogoAlt" }); | |||||
| const wcagAlt = intl.formatMessage({ id: "wcagAaAlt" }); | |||||
| return ( | return ( | ||||
| <Container maxWidth= "xl" sx={{minHeight: '5vh'}}> | |||||
| <Container maxWidth="xl" sx={{ minHeight: '5vh' }}> | |||||
| <Stack | <Stack | ||||
| direction={matchDownSM ? 'column' : 'row'} | direction={matchDownSM ? 'column' : 'row'} | ||||
| justifyContent={matchDownSM ? 'center' : 'flex-start'} | justifyContent={matchDownSM ? 'center' : 'flex-start'} | ||||
| @@ -19,7 +25,11 @@ const AuthFooter = () => { | |||||
| textAlign={matchDownSM ? 'center' : 'inherit'} | textAlign={matchDownSM ? 'center' : 'inherit'} | ||||
| alignItems="center" | alignItems="center" | ||||
| > | > | ||||
| <Typography variant="subtitle2" color="secondary" component="span"> | |||||
| <Typography | |||||
| variant="subtitle2" | |||||
| component="span" | |||||
| sx={{ color: '#4A4A4A' }} | |||||
| > | |||||
| 2024 © <FormattedMessage id="HKGLD" /> | 2024 © <FormattedMessage id="HKGLD" /> | ||||
| </Typography> | </Typography> | ||||
| <Typography | <Typography | ||||
| @@ -44,22 +54,29 @@ const AuthFooter = () => { | |||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| <Stack direction={matchDownSM ? 'column' : 'row'} spacing={matchDownSM ? 1 : 3} textAlign={matchDownSM ? 'center' : 'inherit'} justifyContent={matchDownSM?"center":"flex-end"}> | <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> | </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> | </Container> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,5 +1,6 @@ | |||||
| // material-ui | // material-ui | ||||
| import {useState, useEffect} from 'react'; | import {useState, useEffect} from 'react'; | ||||
| import { useIntl } from 'react-intl'; | |||||
| import iAmSmartICon from 'assets/images/icons/icon_iAmSmart.png'; | import iAmSmartICon from 'assets/images/icons/icon_iAmSmart.png'; | ||||
| import { | import { | ||||
| Button, | Button, | ||||
| @@ -9,7 +10,7 @@ import { | |||||
| // ==============================|| EVENT TABLE ||============================== // | // ==============================|| EVENT TABLE ||============================== // | ||||
| export function IAmSmartButton({ label, onClickFun, fullWidth }) { | export function IAmSmartButton({ label, onClickFun, fullWidth }) { | ||||
| const intl = useIntl(); | |||||
| const [_label, set_label] = useState(""); | const [_label, set_label] = useState(""); | ||||
| useEffect(()=>{ | useEffect(()=>{ | ||||
| @@ -23,7 +24,7 @@ export function IAmSmartButton({ label, onClickFun, fullWidth }) { | |||||
| } | } | ||||
| return ( | 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"> | <Typography variant="h5"> | ||||
| {_label} | {_label} | ||||
| </Typography> | </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 { StrictMode, useEffect, useContext } from 'react'; | ||||
| import { createRoot } from 'react-dom/client'; | import { createRoot } from 'react-dom/client'; | ||||
| import { BrowserRouter } from 'react-router-dom'; | import { BrowserRouter } from 'react-router-dom'; | ||||
| import "./assets/style/styles.css" | |||||
| import './assets/fonts.css'; | |||||
| import './assets/style/styles.css'; | |||||
| // scroll bar | // scroll bar | ||||
| import 'simplebar/src/simplebar.css'; | import 'simplebar/src/simplebar.css'; | ||||
| @@ -4,6 +4,7 @@ import { useMemo } from 'react'; | |||||
| // material-ui | // material-ui | ||||
| import { useTheme } from '@mui/material/styles'; | import { useTheme } from '@mui/material/styles'; | ||||
| import { Box, Drawer, useMediaQuery } from '@mui/material'; | import { Box, Drawer, useMediaQuery } from '@mui/material'; | ||||
| import { useIntl } from 'react-intl'; | |||||
| // project import | // project import | ||||
| import DrawerHeader from './DrawerHeader'; | import DrawerHeader from './DrawerHeader'; | ||||
| @@ -15,6 +16,7 @@ import { drawerWidth } from 'config'; | |||||
| const MainDrawer = ({ open, handleDrawerToggle, window }) => { | const MainDrawer = ({ open, handleDrawerToggle, window }) => { | ||||
| const theme = useTheme(); | const theme = useTheme(); | ||||
| const intl = useIntl(); | |||||
| const matchDownMD = useMediaQuery(theme.breakpoints.down('lg')); | const matchDownMD = useMediaQuery(theme.breakpoints.down('lg')); | ||||
| // responsive drawer container | // responsive drawer container | ||||
| @@ -25,7 +27,7 @@ const MainDrawer = ({ open, handleDrawerToggle, window }) => { | |||||
| const drawerHeader = useMemo(() => <DrawerHeader open={open} />, [open]); | const drawerHeader = useMemo(() => <DrawerHeader open={open} />, [open]); | ||||
| return ( | 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 ? ( | {!matchDownMD ? ( | ||||
| <MiniDrawerStyled variant="permanent" open={open}> | <MiniDrawerStyled variant="permanent" open={open}> | ||||
| {drawerHeader} | {drawerHeader} | ||||
| @@ -16,7 +16,7 @@ import { | |||||
| import Transitions from 'components/@extended/Transitions'; | import Transitions from 'components/@extended/Transitions'; | ||||
| import LanguageIcon from '@mui/icons-material/Language'; | import LanguageIcon from '@mui/icons-material/Language'; | ||||
| import {FormattedMessage} from "react-intl"; | |||||
| import {FormattedMessage, useIntl} from "react-intl"; | |||||
| import * as React from "react"; | import * as React from "react"; | ||||
| import LocaleContext from "components/I18nProvider"; | import LocaleContext from "components/I18nProvider"; | ||||
| @@ -27,6 +27,8 @@ const LocaleSelector = () => { | |||||
| const matchesXs = useMediaQuery(theme.breakpoints.down('md')); | const matchesXs = useMediaQuery(theme.breakpoints.down('md')); | ||||
| const { setLocale } = useContext(LocaleContext); | const { setLocale } = useContext(LocaleContext); | ||||
| const intl = useIntl(); | |||||
| const anchorRef = useRef(null); | const anchorRef = useRef(null); | ||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const handleToggle = () => { | const handleToggle = () => { | ||||
| @@ -47,16 +49,26 @@ const LocaleSelector = () => { | |||||
| return ( | return ( | ||||
| <Box sx={{ flexShrink: 0, ml: 0.75 }}> | <Box sx={{ flexShrink: 0, ml: 0.75 }}> | ||||
| <IconButton | <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> | </IconButton> | ||||
| <Popper | <Popper | ||||
| placement={matchesXs ? 'bottom' : 'bottom-end'} | placement={matchesXs ? 'bottom' : 'bottom-end'} | ||||
| @@ -45,19 +45,26 @@ const MobileSection = () => { | |||||
| <> | <> | ||||
| <Box sx={{ flexShrink: 0, ml: 0.75 }}> | <Box sx={{ flexShrink: 0, ml: 0.75 }}> | ||||
| <IconButton | <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> | </Box> | ||||
| <Popper | <Popper | ||||
| placement="bottom-end" | placement="bottom-end" | ||||
| @@ -71,17 +71,27 @@ const Notification = () => { | |||||
| <IconButton | <IconButton | ||||
| disableRipple | disableRipple | ||||
| color="secondary" | 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} | ref={anchorRef} | ||||
| aria-controls={open ? 'profile-grow' : undefined} | aria-controls={open ? 'profile-grow' : undefined} | ||||
| aria-haspopup="true" | aria-haspopup="true" | ||||
| onClick={handleToggle} | onClick={handleToggle} | ||||
| > | > | ||||
| <Badge badgeContent={4} color="primary"> | |||||
| <BellOutlined /> | |||||
| </Badge> | |||||
| </IconButton> | |||||
| <Badge badgeContent={4} color="primary"> | |||||
| <BellOutlined /> | |||||
| </Badge> | |||||
| </IconButton> | |||||
| <Popper | <Popper | ||||
| placement={matchesXs ? 'bottom' : 'bottom-end'} | placement={matchesXs ? 'bottom' : 'bottom-end'} | ||||
| open={open} | open={open} | ||||
| @@ -33,6 +33,7 @@ import { LogoutOutlined, | |||||
| import { handleLogoutFunction } from 'auth/index'; | import { handleLogoutFunction } from 'auth/index'; | ||||
| import {useNavigate} from "react-router-dom"; | import {useNavigate} from "react-router-dom"; | ||||
| import {useDispatch} from "react-redux"; | import {useDispatch} from "react-redux"; | ||||
| import { useIntl } from 'react-intl'; | |||||
| import AccountCircleIcon from '@mui/icons-material/AccountCircle'; | import AccountCircleIcon from '@mui/icons-material/AccountCircle'; | ||||
| // tab panel wrapper | // tab panel wrapper | ||||
| @@ -61,6 +62,7 @@ TabPanel.propTypes = { | |||||
| const Profile = () => { | const Profile = () => { | ||||
| const theme = useTheme(); | const theme = useTheme(); | ||||
| const intl = useIntl(); | |||||
| const navigate = useNavigate() | const navigate = useNavigate() | ||||
| const dispatch = useDispatch() | const dispatch = useDispatch() | ||||
| @@ -101,7 +103,7 @@ const Profile = () => { | |||||
| borderRadius: 1, | borderRadius: 1, | ||||
| '&:hover': { bgcolor: 'secondary.lighter' } | '&:hover': { bgcolor: 'secondary.lighter' } | ||||
| }} | }} | ||||
| aria-label="open profile" | |||||
| aria-label={intl.formatMessage({id: 'openLanguage'})} | |||||
| ref={anchorRef} | ref={anchorRef} | ||||
| aria-controls={open ? 'profile-grow' : undefined} | aria-controls={open ? 'profile-grow' : undefined} | ||||
| aria-haspopup="true" | aria-haspopup="true" | ||||
| @@ -170,7 +172,7 @@ const Profile = () => { | |||||
| {/* {open && ( | {/* {open && ( | ||||
| <> | <> | ||||
| <Box sx={{ borderBottom: 1, borderColor: 'divider' }}> | <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 | <Tab | ||||
| sx={{ | sx={{ | ||||
| display: 'flex', | 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" }}}> | <Box sx={{backgroundColor:'#ffffff', display: 'flex', width: '100%', flexDirection: "column", paddingTop: { xs: "5px", sm: "25px", md: "43px" }}}> | ||||
| <Header/> | <Header/> | ||||
| {/* <Drawer open={open} handleDrawerToggle={handleDrawerToggle} /> */} | {/* <Drawer open={open} handleDrawerToggle={handleDrawerToggle} /> */} | ||||
| <Box style={{ width: '100%', flexGrow: 1 } } sx={{ paddingTop: "38px" }}> | |||||
| <Box style={{ width: '100%', flexGrow: 1 }} sx={{ paddingTop: "36px" }}> | |||||
| {/* <Toolbar /> */} | {/* <Toolbar /> */} | ||||
| {/* <Breadcrumbs navigation={navigation} title /> */} | {/* <Breadcrumbs navigation={navigation} title /> */} | ||||
| <Outlet /> | <Outlet /> | ||||
| @@ -143,7 +143,7 @@ const AnnouncementForm = ({ loadedData }) => { | |||||
| </Grid> | </Grid> | ||||
| </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}><Typography variant="h5">English</Typography></Grid> | ||||
| <Grid item xs={12} md={12} > | <Grid item xs={12} md={12} > | ||||
| <Grid container alignItems={"center"} xs={12} sm={12} md={12} lg={12} sx={{ mb: 2 }}> | <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> | </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}><Typography variant="h5">Traditional Chinese</Typography></Grid> | ||||
| <Grid item xs={12} md={12}> | <Grid item xs={12} md={12}> | ||||
| <Grid container alignItems={"center"} xs={12} sm={12} md={12} lg={12} sx={{ mb: 2 }}> | <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}><Typography variant="h5">Simplified Chinese</Typography></Grid> | ||||
| <Grid item xs={12} md={12}> | <Grid item xs={12} md={12}> | ||||
| <Grid container alignItems={"center"} xs={12} sm={12} md={12} lg={12} sx={{ mb: 2 }}> | <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 item xs={12} s={6} md={6} lg={4} sx={{ ml: 3, mr: 3, mb: 3 }}> | ||||
| <Grid container> | <Grid container> | ||||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | <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']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="dateFrom" | id="dateFrom" | ||||
| @@ -148,7 +148,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | <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']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="dateTo" | id="dateTo" | ||||
| @@ -195,7 +195,9 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||||
| <Grid item sx={{ ml: 3 }}> | <Grid item sx={{ ml: 3 }}> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| color="cancel" | |||||
| onClick={resetForm} | onClick={resetForm} | ||||
| aria-label={intl.formatMessage({ id: 'reset' })} | |||||
| > | > | ||||
| <FormattedMessage id="reset"></FormattedMessage> | <FormattedMessage id="reset"></FormattedMessage> | ||||
| </Button> | </Button> | ||||
| @@ -15,6 +15,7 @@ const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); | |||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| import { FormattedMessage } from "react-intl"; | import { FormattedMessage } from "react-intl"; | ||||
| import { getSearchCriteria } from "auth/utils"; | import { getSearchCriteria } from "auth/utils"; | ||||
| import usePageTitle from "components/usePageTitle"; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| @@ -29,7 +30,7 @@ const BackgroundHead = { | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const UserSearchPage_Individual = () => { | const UserSearchPage_Individual = () => { | ||||
| usePageTitle("announcement"); | |||||
| const [searchCriteria, setSearchCriteria] = React.useState({}); | const [searchCriteria, setSearchCriteria] = React.useState({}); | ||||
| const [onReady, setOnReady] = React.useState(false); | const [onReady, setOnReady] = React.useState(false); | ||||
| const [onGridReady, setGridOnReady] = React.useState(false); | const [onGridReady, setGridOnReady] = React.useState(false); | ||||
| @@ -74,7 +75,7 @@ const UserSearchPage_Individual = () => { | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <div style={BackgroundHead}> | <div style={BackgroundHead}> | ||||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | <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> | </Stack> | ||||
| </div> | </div> | ||||
| </Grid> | </Grid> | ||||
| @@ -43,7 +43,7 @@ const AuditLogSearchForm = ({ applySearch, searchCriteria, onGridReady}) => { | |||||
| const marginBottom = 2.5; | const marginBottom = 2.5; | ||||
| const { reset, register, handleSubmit } = useForm() | |||||
| const { reset, register, handleSubmit, getValues } = useForm() | |||||
| React.useEffect(() => { | React.useEffect(() => { | ||||
| setFromDateValue(minDate); | setFromDateValue(minDate); | ||||
| @@ -53,21 +53,26 @@ const AuditLogSearchForm = ({ applySearch, searchCriteria, onGridReady}) => { | |||||
| setToDateValue(maxDate); | setToDateValue(maxDate); | ||||
| }, [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 sentDateFrom = ""; | ||||
| let sentDateTo = ""; | let sentDateTo = ""; | ||||
| if (fromDateValue != "dd / mm / yyyy" && toDateValue != "dd / mm / yyyy") { | 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, | modifiedTo: sentDateTo, | ||||
| modifiedFrom: sentDateFrom, | modifiedFrom: sentDateFrom, | ||||
| start:0, | |||||
| limit:10 | |||||
| }; | |||||
| }; | |||||
| const onSubmit = () => { | |||||
| const temp = { | |||||
| ...getCurrentFilterParams(), | |||||
| start: 0, | |||||
| limit: 10, | |||||
| }; | }; | ||||
| applySearch(temp); | applySearch(temp); | ||||
| }; | }; | ||||
| @@ -81,7 +86,7 @@ const AuditLogSearchForm = ({ applySearch, searchCriteria, onGridReady}) => { | |||||
| setOnDownload(true) | setOnDownload(true) | ||||
| HttpUtils.fileDownload({ | HttpUtils.fileDownload({ | ||||
| url: UrlUtils.AUDIT_LOG_EXPORT, | url: UrlUtils.AUDIT_LOG_EXPORT, | ||||
| params: searchCriteria, | |||||
| params: getCurrentFilterParams(), | |||||
| onResponse:()=>{ | onResponse:()=>{ | ||||
| setOnDownload(false) | setOnDownload(false) | ||||
| }, | }, | ||||
| @@ -13,7 +13,7 @@ import * as DateUtils from "utils/DateUtils"; | |||||
| import * as UrlUtils from "utils/ApiPathConst"; | import * as UrlUtils from "utils/ApiPathConst"; | ||||
| import * as HttpUtils from "utils/HttpUtils"; | import * as HttpUtils from "utils/HttpUtils"; | ||||
| import { useNavigate } from "react-router-dom"; | import { useNavigate } from "react-router-dom"; | ||||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||||
| import { notifyActionError } from 'utils/CommonFunction'; | |||||
| import { PNSPS_BUTTON_THEME } from "../../../themes/buttonConst"; | import { PNSPS_BUTTON_THEME } from "../../../themes/buttonConst"; | ||||
| import { ThemeProvider } from "@emotion/react"; | import { ThemeProvider } from "@emotion/react"; | ||||
| import { useIntl } from "react-intl"; | import { useIntl } from "react-intl"; | ||||
| @@ -118,8 +118,11 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData, _paymentCount, _p | |||||
| params: { | params: { | ||||
| "dnIdList": dnIdList | "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> | ||||
| <Grid item > | <Grid item > | ||||
| @@ -194,7 +201,7 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData, _paymentCount, _p | |||||
| variant="contained" | variant="contained" | ||||
| onClick={onSubmit} | onClick={onSubmit} | ||||
| color="success" | color="success" | ||||
| minWidth={150} | |||||
| sx={{ minWidth: 150 }} | |||||
| > | > | ||||
| Create | Create | ||||
| </Button> | </Button> | ||||
| @@ -15,11 +15,12 @@ import * as StatusUtils from "utils/statusUtils/PublicNoteStatusUtils"; | |||||
| import * as HttpUtils from "utils/HttpUtils"; | import * as HttpUtils from "utils/HttpUtils"; | ||||
| import DownloadIcon from '@mui/icons-material/Download'; | import DownloadIcon from '@mui/icons-material/Download'; | ||||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||||
| import { notifyActionError } from 'utils/CommonFunction'; | |||||
| import { useIntl } from 'react-intl'; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const ApplicationDetailCard = ({ data }) => { | const ApplicationDetailCard = ({ data }) => { | ||||
| const intl = useIntl(); | |||||
| const [appDetail, setAppDetails] = React.useState({}); | const [appDetail, setAppDetails] = React.useState({}); | ||||
| React.useEffect(() => { | React.useEffect(() => { | ||||
| @@ -33,8 +34,11 @@ const ApplicationDetailCard = ({ data }) => { | |||||
| fileId: appDetail.appFileId, | fileId: appDetail.appFileId, | ||||
| skey: appDetail.appSkey, | skey: appDetail.appSkey, | ||||
| filename: appDetail.appFilename, | filename: appDetail.appFilename, | ||||
| onResponse: function () {}, | |||||
| onError: function () { | |||||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||||
| } | |||||
| }); | }); | ||||
| notifyDownloadSuccess(); | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| @@ -14,12 +14,13 @@ import Loadable from 'components/Loadable'; | |||||
| const MainCard = Loadable(React.lazy(() => import('components/MainCard'))); | const MainCard = Loadable(React.lazy(() => import('components/MainCard'))); | ||||
| import DownloadIcon from '@mui/icons-material/Download'; | import DownloadIcon from '@mui/icons-material/Download'; | ||||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||||
| import { notifyActionError } from 'utils/CommonFunction'; | |||||
| import { useIntl } from 'react-intl'; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const DnDetailCard = ({ data }) => { | const DnDetailCard = ({ data }) => { | ||||
| const intl = useIntl(); | |||||
| const [dnData, setDnData] = React.useState({}); | const [dnData, setDnData] = React.useState({}); | ||||
| React.useEffect(() => { | React.useEffect(() => { | ||||
| @@ -33,8 +34,9 @@ const DnDetailCard = ({ data }) => { | |||||
| fileId: dnData.fileId, | fileId: dnData.fileId, | ||||
| skey: dnData.skey, | skey: dnData.skey, | ||||
| filename: dnData.filename, | 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> | </Grid> | ||||
| @@ -197,7 +201,7 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData }) => { | |||||
| onClick={onSubmit} | onClick={onSubmit} | ||||
| color="success" | color="success" | ||||
| disabled={waitDownload} | disabled={waitDownload} | ||||
| minWidth={150} | |||||
| sx={{ minWidth: 150 }} | |||||
| > | > | ||||
| Export | Export | ||||
| </Button> | </Button> | ||||
| @@ -13,7 +13,7 @@ import * as FormatUtils from "utils/FormatUtils"; | |||||
| import * as StatusUtils from "utils/statusUtils/DnStatus"; | import * as StatusUtils from "utils/statusUtils/DnStatus"; | ||||
| import { useNavigate } from "react-router-dom"; | import { useNavigate } from "react-router-dom"; | ||||
| import { FiDataGrid } from "components/FiDataGrid"; | import { FiDataGrid } from "components/FiDataGrid"; | ||||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||||
| import { notifyActionError } from 'utils/CommonFunction'; | |||||
| import { | import { | ||||
| DEMAND_NOTE_EXPORT, | DEMAND_NOTE_EXPORT, | ||||
| DEMAND_NOTE_SEND, | DEMAND_NOTE_SEND, | ||||
| @@ -26,10 +26,11 @@ import * as HttpUtils from "utils/HttpUtils"; | |||||
| import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | ||||
| import { ThemeProvider } from "@emotion/react"; | import { ThemeProvider } from "@emotion/react"; | ||||
| import { isGrantedAny } from "auth/utils"; | import { isGrantedAny } from "auth/utils"; | ||||
| import { useIntl } from "react-intl"; | |||||
| // ==============================|| EVENT TABLE ||============================== // | // ==============================|| EVENT TABLE ||============================== // | ||||
| export default function SearchDemandNote({ applySearch, searchCriteria, applyGridOnReady }) { | export default function SearchDemandNote({ applySearch, searchCriteria, applyGridOnReady }) { | ||||
| const intl = useIntl(); | |||||
| const [isConfirmPopUp, setConfirmPopUp] = useState(false); | const [isConfirmPopUp, setConfirmPopUp] = useState(false); | ||||
| const [isRevokePopUp, setRevokePopUp] = useState(false); | const [isRevokePopUp, setRevokePopUp] = useState(false); | ||||
| const [isSendPopUp, setSendPopUp] = useState(false); | const [isSendPopUp, setSendPopUp] = useState(false); | ||||
| @@ -80,8 +81,9 @@ export default function SearchDemandNote({ applySearch, searchCriteria, applyGri | |||||
| params: { | params: { | ||||
| dnIdList: idList | 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, | fileId: params.row.fileId, | ||||
| skey: params.row.skey, | skey: params.row.skey, | ||||
| filename: params.row.filename, | 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> | </Grid> | ||||
| @@ -282,6 +286,10 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||||
| {params.children} | {params.children} | ||||
| </Grid> | </Grid> | ||||
| )} | )} | ||||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| : <></> | : <></> | ||||
| @@ -435,7 +443,7 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | ||||
| '& .MuiOutlinedInput-root': { height: 40 } | '& .MuiOutlinedInput-root': { height: 40 } | ||||
| }} | }} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| renderInput={(params) => ( | renderInput={(params) => ( | ||||
| <TextField | <TextField | ||||
| {...params} | {...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> | </Grid> | ||||
| @@ -66,8 +66,7 @@ export default function SearchDemandNote({ searchCriteria, applyGridOnReady,appl | |||||
| width: isMdOrLg ? 'auto' : 175, | width: isMdOrLg ? 'auto' : 175, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderCell: (params) => { | 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> | </Grid> | ||||
| @@ -210,7 +214,7 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData, onG | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | <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']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="dateFrom" | id="dateFrom" | ||||
| @@ -237,7 +241,7 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData, onG | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | <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']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="dateTo" | id="dateTo" | ||||
| @@ -288,11 +292,13 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData, onG | |||||
| <TextField | <TextField | ||||
| {...params} | {...params} | ||||
| label={intl.formatMessage({ id: 'status' })} | 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> | </Grid> | ||||
| @@ -18,6 +18,7 @@ const SearchForm = Loadable(React.lazy(() => import('./SearchForm'))); | |||||
| const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); | const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); | ||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| import {FormattedMessage} from "react-intl"; | import {FormattedMessage} from "react-intl"; | ||||
| import usePageTitle from "components/usePageTitle"; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| @@ -32,7 +33,7 @@ const BackgroundHead = { | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const SearchPage_DemandNote_Pub = () => { | const SearchPage_DemandNote_Pub = () => { | ||||
| usePageTitle("paymentInfoRecord"); | |||||
| const [orgCombo, setOrgCombo] = React.useState([]); | const [orgCombo, setOrgCombo] = React.useState([]); | ||||
| const [issueCombo, setIssueCombo] = React.useState([]); | const [issueCombo, setIssueCombo] = React.useState([]); | ||||
| const [searchCriteria, setSearchCriteria] = React.useState({}); | const [searchCriteria, setSearchCriteria] = React.useState({}); | ||||
| @@ -101,7 +102,7 @@ const SearchPage_DemandNote_Pub = () => { | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <div style={BackgroundHead}> | <div style={BackgroundHead}> | ||||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | <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" /> | <FormattedMessage id="paymentInfoRecord" /> | ||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| @@ -180,7 +180,7 @@ const SearchPublicNoticeForm = ({ applySearch, generateXML, searchCriteria, onGr | |||||
| filterOptions={(options) => options} | filterOptions={(options) => options} | ||||
| options={ComboData.payMethod} | options={ComboData.payMethod} | ||||
| value={payMethod} | value={payMethod} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| inputValue={payMethod?.label ? payMethod?.label : ""} | inputValue={payMethod?.label ? payMethod?.label : ""} | ||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| if(newValue==null){ | if(newValue==null){ | ||||
| @@ -197,11 +197,9 @@ const SearchPublicNoticeForm = ({ applySearch, generateXML, searchCriteria, onGr | |||||
| renderInput={(params) => ( | renderInput={(params) => ( | ||||
| <TextField {...params} | <TextField {...params} | ||||
| label="Payment Method" | label="Payment Method" | ||||
| InputLabelProps={{ shrink: true }} | |||||
| /> | /> | ||||
| )} | )} | ||||
| InputLabelProps={{ | |||||
| shrink: true | |||||
| }} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| @@ -4,6 +4,7 @@ import { | |||||
| Typography, | Typography, | ||||
| Stack, | Stack, | ||||
| Button, | Button, | ||||
| CircularProgress, | |||||
| Dialog, DialogTitle, DialogContent, DialogActions, | Dialog, DialogTitle, DialogContent, DialogActions, | ||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| import MainCard from "components/MainCard"; | import MainCard from "components/MainCard"; | ||||
| @@ -55,6 +56,8 @@ const Index = () => { | |||||
| const [isPreviewLoading, setIsPreviewLoading] = React.useState(false); | const [isPreviewLoading, setIsPreviewLoading] = React.useState(false); | ||||
| const [isPopUp, setIsPopUp] = 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 [downloadInput, setDownloadInput] = React.useState(); | ||||
| const [selectedIds, setSelectedIds] = React.useState([]); | const [selectedIds, setSelectedIds] = React.useState([]); | ||||
| @@ -66,6 +69,13 @@ const Index = () => { | |||||
| setInputDateValue(inputDate); | setInputDateValue(inputDate); | ||||
| }, [inputDate]); | }, [inputDate]); | ||||
| React.useEffect(() => { | |||||
| if (!isPopUp) { | |||||
| xmlDownloadInFlightRef.current = false; | |||||
| setIsXmlDialogSubmitting(false); | |||||
| } | |||||
| }, [isPopUp]); | |||||
| React.useEffect(() => { | React.useEffect(() => { | ||||
| setOnReady(true); | setOnReady(true); | ||||
| }, [searchCriteria]); | }, [searchCriteria]); | ||||
| @@ -94,66 +104,61 @@ const Index = () => { | |||||
| function downloadXML() { | function downloadXML() { | ||||
| console.log(selectedIds.join(',')) | |||||
| setIsPopUp(false) | |||||
| if (xmlDownloadInFlightRef.current) return; | |||||
| xmlDownloadInFlightRef.current = true; | |||||
| console.log(selectedIds.join(',')); | |||||
| setIsXmlDialogSubmitting(true); | |||||
| let sentDateFrom = ""; | let sentDateFrom = ""; | ||||
| if (inputDateValue != "dd / mm / yyyy") { | if (inputDateValue != "dd / mm / yyyy") { | ||||
| sentDateFrom = DateUtils.dateValue(inputDateValue) | |||||
| sentDateFrom = DateUtils.dateValue(inputDateValue); | |||||
| } | } | ||||
| HttpUtils.get({ | HttpUtils.get({ | ||||
| url: GEN_GFMIS_XML + "/today", | url: GEN_GFMIS_XML + "/today", | ||||
| params:{ | |||||
| params: { | |||||
| dateTo: downloadInput.dateTo, | dateTo: downloadInput.dateTo, | ||||
| dateFrom: downloadInput.dateFrom, | dateFrom: downloadInput.dateFrom, | ||||
| inputDate: sentDateFrom, | inputDate: sentDateFrom, | ||||
| paymentId: selectedIds.join(',') | paymentId: selectedIds.join(',') | ||||
| }, | }, | ||||
| onSuccess: (responseData) => { | onSuccess: (responseData) => { | ||||
| // console.log(responseData) | |||||
| const parser = new DOMParser(); | const parser = new DOMParser(); | ||||
| const xmlDoc = parser.parseFromString(responseData, 'application/xml'); | const xmlDoc = parser.parseFromString(responseData, 'application/xml'); | ||||
| // Get the DCBHeader element | |||||
| const dcbHeader = xmlDoc.querySelector("DCBHeader"); | const dcbHeader = xmlDoc.querySelector("DCBHeader"); | ||||
| // Get the Receipt and Allocation elements | |||||
| const receiptElement = dcbHeader.querySelector("Receipt"); | const receiptElement = dcbHeader.querySelector("Receipt"); | ||||
| const allocationElement = dcbHeader.querySelector("Allocation"); | const allocationElement = dcbHeader.querySelector("Allocation"); | ||||
| const paymentMethodElements = Array.from(dcbHeader.querySelectorAll("PaymentMethod")); | const paymentMethodElements = Array.from(dcbHeader.querySelectorAll("PaymentMethod")); | ||||
| // Remove existing elements from DCBHeader | |||||
| dcbHeader.innerHTML = ""; | dcbHeader.innerHTML = ""; | ||||
| dcbHeader.appendChild(receiptElement); | dcbHeader.appendChild(receiptElement); | ||||
| dcbHeader.appendChild(allocationElement); | dcbHeader.appendChild(allocationElement); | ||||
| if (paymentMethodElements) { | if (paymentMethodElements) { | ||||
| paymentMethodElements.forEach((paymentMethodElement) => { | paymentMethodElements.forEach((paymentMethodElement) => { | ||||
| dcbHeader.appendChild(paymentMethodElement); | |||||
| }); | |||||
| dcbHeader.appendChild(paymentMethodElement); | |||||
| }); | |||||
| } | } | ||||
| const updatedXmlString = new XMLSerializer().serializeToString(xmlDoc); | const updatedXmlString = new XMLSerializer().serializeToString(xmlDoc); | ||||
| const filename = xmlDoc.querySelector('FileHeader').getAttribute('H_Filename'); | const filename = xmlDoc.querySelector('FileHeader').getAttribute('H_Filename'); | ||||
| // console.log(updatedXmlString) | |||||
| const blob = new Blob([updatedXmlString], { type: 'application/xml' }); | const blob = new Blob([updatedXmlString], { type: 'application/xml' }); | ||||
| // Create a download link | |||||
| const link = document.createElement('a'); | const link = document.createElement('a'); | ||||
| link.href = URL.createObjectURL(blob); | link.href = URL.createObjectURL(blob); | ||||
| link.download = filename+'.xml'; | |||||
| // Append the link to the document body | |||||
| link.download = filename + '.xml'; | |||||
| document.body.appendChild(link); | document.body.appendChild(link); | ||||
| // Programmatically click the link to trigger the download | |||||
| link.click(); | link.click(); | ||||
| // Clean up the link | |||||
| document.body.removeChild(link); | document.body.removeChild(link); | ||||
| setIsPopUp(false); | |||||
| }, | |||||
| onFinally: () => { | |||||
| xmlDownloadInFlightRef.current = false; | |||||
| setIsXmlDialogSubmitting(false); | |||||
| } | } | ||||
| }); | |||||
| // open(UrlUtils.GEN_GFMIS_XML + "/today?online=true") | |||||
| } | |||||
| }); | |||||
| } | |||||
| function applySearch(input) { | function applySearch(input) { | ||||
| @@ -263,7 +268,11 @@ const Index = () => { | |||||
| </Grid> | </Grid> | ||||
| <Dialog | <Dialog | ||||
| open={isPopUp} | open={isPopUp} | ||||
| onClose={() => setIsPopUp(false)} | |||||
| onClose={() => { | |||||
| if (isXmlDialogSubmitting) return; | |||||
| setIsPopUp(false); | |||||
| }} | |||||
| disableEscapeKeyDown={isXmlDialogSubmitting} | |||||
| PaperProps={{ | PaperProps={{ | ||||
| sx: { | sx: { | ||||
| minWidth: '40vw', | minWidth: '40vw', | ||||
| @@ -300,8 +309,22 @@ const Index = () => { | |||||
| </LocalizationProvider> | </LocalizationProvider> | ||||
| </DialogContent> | </DialogContent> | ||||
| <DialogActions> | <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> | </DialogActions> | ||||
| </Dialog> | </Dialog> | ||||
| </Grid> | </Grid> | ||||
| @@ -76,7 +76,11 @@ const SearchGazetteIssueForm = ({ applyExport, comboData, waitDownload}) => { | |||||
| // defaultValue={selectedYear} | // defaultValue={selectedYear} | ||||
| options={comboList} | options={comboList} | ||||
| // disabled={checkCountry} | // disabled={checkCountry} | ||||
| getOptionLabel={(option) => option.label ? option.label : ""} | |||||
| getOptionLabel={(option) => | |||||
| option != null && typeof option === "object" && option.label != null | |||||
| ? String(option.label) | |||||
| : "" | |||||
| } | |||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| setSelectedYear(newValue); | setSelectedYear(newValue); | ||||
| }} | }} | ||||
| @@ -74,7 +74,11 @@ const SearchGazetteIssueForm = ({ applySearch, comboData, onGridReady}) => { | |||||
| // defaultValue={selectedYear} | // defaultValue={selectedYear} | ||||
| options={comboList} | options={comboList} | ||||
| // disabled={checkCountry} | // disabled={checkCountry} | ||||
| getOptionLabel={(option) => option.label ? option.label : ""} | |||||
| getOptionLabel={(option) => | |||||
| option != null && typeof option === "object" && option.label != null | |||||
| ? String(option.label) | |||||
| : "" | |||||
| } | |||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| setSelectedYear(newValue); | setSelectedYear(newValue); | ||||
| }} | }} | ||||
| @@ -31,10 +31,12 @@ import { ThemeProvider } from "@emotion/react"; | |||||
| import { dateStr_Year } from "utils/DateUtils"; | import { dateStr_Year } from "utils/DateUtils"; | ||||
| import { notifySaveSuccess } from 'utils/CommonFunction'; | import { notifySaveSuccess } from 'utils/CommonFunction'; | ||||
| import { isGrantedAny } from "auth/utils"; | import { isGrantedAny } from "auth/utils"; | ||||
| import { useIntl } from 'react-intl'; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const Index = () => { | const Index = () => { | ||||
| const intl = useIntl(); | |||||
| const [comboData, setComboData] = React.useState([]); | const [comboData, setComboData] = React.useState([]); | ||||
| const [holidayComboData, setHolidayComboData] = React.useState([]); | const [holidayComboData, setHolidayComboData] = React.useState([]); | ||||
| const [onReady, setOnReady] = React.useState(false); | const [onReady, setOnReady] = React.useState(false); | ||||
| @@ -50,6 +52,7 @@ const Index = () => { | |||||
| const [waitDownload, setWaitDownload] = React.useState(false); | const [waitDownload, setWaitDownload] = React.useState(false); | ||||
| const [isWarningPopUp, setIsWarningPopUp] = React.useState(false); | const [isWarningPopUp, setIsWarningPopUp] = React.useState(false); | ||||
| const [warningText, setWarningText] = React.useState(""); | const [warningText, setWarningText] = React.useState(""); | ||||
| const fileInputRef = React.useRef(null); | |||||
| React.useEffect(() => { | React.useEffect(() => { | ||||
| setOnSearchReady(false); | setOnSearchReady(false); | ||||
| @@ -184,13 +187,27 @@ const Index = () => { | |||||
| <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={2} sx={{ ml: 2, mt: 1 }}> | <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={2} sx={{ ml: 2, mt: 1 }}> | ||||
| <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | ||||
| <Button | <Button | ||||
| component="label" | |||||
| variant="contained" | variant="contained" | ||||
| size="large" | size="large" | ||||
| disabled={waitImport} | 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> | <Typography variant="h5">Upload Files</Typography> | ||||
| <input | |||||
| </Button> | |||||
| <input | |||||
| id="uploadFileBtn" | id="uploadFileBtn" | ||||
| name="file" | name="file" | ||||
| type="file" | type="file" | ||||
| @@ -198,9 +215,9 @@ const Index = () => { | |||||
| hidden | hidden | ||||
| disabled={waitImport} | disabled={waitImport} | ||||
| onChange={readFile} | onChange={readFile} | ||||
| aria-label="Upload Excel file (.xlsx)" | |||||
| /> | |||||
| </Button> | |||||
| aria-label={intl.formatMessage({ id: 'ariaUploadExcelFile' })} | |||||
| ref={fileInputRef} | |||||
| /> | |||||
| </ThemeProvider> | </ThemeProvider> | ||||
| </Stack> | </Stack> | ||||
| </Grid> | </Grid> | ||||
| @@ -9,14 +9,19 @@ import { dateStr } from "utils/DateUtils"; | |||||
| // ==============================|| EVENT TABLE ||============================== // | // ==============================|| 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 }) { | export default function HolidayTable({ recordList, applyGridOnReady }) { | ||||
| const [rows, setRows] = React.useState(recordList); | |||||
| const [rows, setRows] = React.useState(() => holidayRowsFromResponse(recordList)); | |||||
| // const navigate = useNavigate() | // const navigate = useNavigate() | ||||
| useEffect(() => { | useEffect(() => { | ||||
| // console.log(recordList) | |||||
| setRows(recordList.records); | |||||
| setRows(holidayRowsFromResponse(recordList)); | |||||
| }, [recordList]); | }, [recordList]); | ||||
| const columns = [ | const columns = [ | ||||
| @@ -17,7 +17,7 @@ import {ThemeProvider} from "@emotion/react"; | |||||
| const SearchHolidayForm = ({ applySearch, comboData, onGridReady}) => { | const SearchHolidayForm = ({ applySearch, comboData, onGridReady}) => { | ||||
| const [selectedYear, setSelectedYear] = React.useState([]); | |||||
| const [selectedYear, setSelectedYear] = React.useState(null); | |||||
| // const [defaultYear, setDefaultYear] = React.useState(searchCriteria.year); | // const [defaultYear, setDefaultYear] = React.useState(searchCriteria.year); | ||||
| const [comboList, setComboList] = React.useState([]); | const [comboList, setComboList] = React.useState([]); | ||||
| // const [onReady, setOnReady] = React.useState(false); | // const [onReady, setOnReady] = React.useState(false); | ||||
| @@ -27,7 +27,7 @@ const SearchHolidayForm = ({ applySearch, comboData, onGridReady}) => { | |||||
| handleSubmit } = useForm() | handleSubmit } = useForm() | ||||
| const onSubmit = () => { | const onSubmit = () => { | ||||
| if (selectedYear !=null){ | |||||
| if (selectedYear != null) { | |||||
| const temp = { | const temp = { | ||||
| year: selectedYear.label, | year: selectedYear.label, | ||||
| }; | }; | ||||
| @@ -40,8 +40,8 @@ const SearchHolidayForm = ({ applySearch, comboData, onGridReady}) => { | |||||
| // console.log(comboData) | // console.log(comboData) | ||||
| // const labelValue = comboData.find(obj => obj.label === searchCriteria.year); | // const labelValue = comboData.find(obj => obj.label === searchCriteria.year); | ||||
| // console.log(labelValue) | // console.log(labelValue) | ||||
| if(selectedYear.length == 0){ | |||||
| setSelectedYear(comboData[0]) | |||||
| if (!selectedYear) { | |||||
| setSelectedYear(comboData[0]); | |||||
| } | } | ||||
| setComboList(comboData) | setComboList(comboData) | ||||
| // setSelectedYear(searchCriteria.dateFrom) | // setSelectedYear(searchCriteria.dateFrom) | ||||
| @@ -74,7 +74,11 @@ const SearchHolidayForm = ({ applySearch, comboData, onGridReady}) => { | |||||
| // defaultValue={selectedYear} | // defaultValue={selectedYear} | ||||
| options={comboList} | options={comboList} | ||||
| // disabled={checkCountry} | // disabled={checkCountry} | ||||
| getOptionLabel={(option) => option.label ? option.label : ""} | |||||
| getOptionLabel={(option) => | |||||
| option != null && typeof option === "object" && option.label != null | |||||
| ? String(option.label) | |||||
| : "" | |||||
| } | |||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| setSelectedYear(newValue); | setSelectedYear(newValue); | ||||
| }} | }} | ||||
| @@ -31,11 +31,12 @@ import { ThemeProvider } from "@emotion/react"; | |||||
| import { dateStr_Year } from "utils/DateUtils"; | import { dateStr_Year } from "utils/DateUtils"; | ||||
| import { notifySaveSuccess } from 'utils/CommonFunction'; | import { notifySaveSuccess } from 'utils/CommonFunction'; | ||||
| import { isGrantedAny } from "auth/utils"; | import { isGrantedAny } from "auth/utils"; | ||||
| import { useIntl } from 'react-intl'; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const Index = () => { | const Index = () => { | ||||
| const intl = useIntl(); | |||||
| const [record, setRecord] = React.useState([]); | const [record, setRecord] = React.useState([]); | ||||
| const [comboData, setComboData] = React.useState([]); | const [comboData, setComboData] = React.useState([]); | ||||
| const [onReady, setOnReady] = React.useState(false); | const [onReady, setOnReady] = React.useState(false); | ||||
| @@ -50,6 +51,7 @@ const Index = () => { | |||||
| const [waitDownload, setWaitDownload] = React.useState(false); | const [waitDownload, setWaitDownload] = React.useState(false); | ||||
| const [isWarningPopUp, setIsWarningPopUp] = React.useState(false); | const [isWarningPopUp, setIsWarningPopUp] = React.useState(false); | ||||
| const [warningText, setWarningText] = React.useState(""); | const [warningText, setWarningText] = React.useState(""); | ||||
| const fileInputRef = React.useRef(null); | |||||
| React.useEffect(() => { | React.useEffect(() => { | ||||
| setOnSearchReady(false); | setOnSearchReady(false); | ||||
| @@ -172,7 +174,7 @@ const Index = () => { | |||||
| size="large" | size="large" | ||||
| disabled={waitDownload} | disabled={waitDownload} | ||||
| onClick={doExport} | onClick={doExport} | ||||
| aria-label="Export holiday template" | |||||
| aria-label={intl.formatMessage({ id: 'ariaExportHolidayTemplate' })} | |||||
| > | > | ||||
| <Typography variant="h5">Export</Typography> | <Typography variant="h5">Export</Typography> | ||||
| </Button> | </Button> | ||||
| @@ -180,13 +182,27 @@ const Index = () => { | |||||
| {isGrantedAny(["MAINTAIN_GAZETTE_ISSUE"]) ? | {isGrantedAny(["MAINTAIN_GAZETTE_ISSUE"]) ? | ||||
| <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | ||||
| <Button | <Button | ||||
| component="label" | |||||
| variant="contained" | variant="contained" | ||||
| size="large" | size="large" | ||||
| disabled={waitImport} | 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> | <Typography variant="h5">Upload Files</Typography> | ||||
| <input | |||||
| </Button> | |||||
| <input | |||||
| id="uploadFileBtn" | id="uploadFileBtn" | ||||
| name="file" | name="file" | ||||
| type="file" | type="file" | ||||
| @@ -194,9 +210,9 @@ const Index = () => { | |||||
| hidden | hidden | ||||
| disabled={waitImport} | disabled={waitImport} | ||||
| onChange={readFile} | onChange={readFile} | ||||
| aria-label="Upload Excel file (.xlsx)" | |||||
| /> | |||||
| </Button> | |||||
| aria-label={intl.formatMessage({ id: 'ariaUploadExcelFile' })} | |||||
| ref={fileInputRef} | |||||
| /> | |||||
| </ThemeProvider> | </ThemeProvider> | ||||
| : null | : null | ||||
| } | } | ||||
| @@ -11,7 +11,7 @@ import { | |||||
| Button | Button | ||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| import * as React from "react"; | 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 axios from "axios"; | ||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| @@ -20,7 +20,11 @@ const JVMDefault = () => { | |||||
| const [jvmInfo, setJvmInfo] = React.useState(null); | const [jvmInfo, setJvmInfo] = React.useState(null); | ||||
| const [loading, setLoading] = React.useState(true); | const [loading, setLoading] = React.useState(true); | ||||
| const [error, setError] = React.useState(null); | 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 = () => { | const fetchJvmInfo = () => { | ||||
| setLoading(true); | setLoading(true); | ||||
| setError(null); | 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(() => { | React.useEffect(() => { | ||||
| localStorage.setItem('searchCriteria', ""); | localStorage.setItem('searchCriteria', ""); | ||||
| setLoading(false); | setLoading(false); | ||||
| @@ -66,25 +88,40 @@ const JVMDefault = () => { | |||||
| <div style={BackgroundHead}> | <div style={BackgroundHead}> | ||||
| <Stack direction="row" height='70px' justifyContent="space-between" alignItems="center"> | <Stack direction="row" height='70px' justifyContent="space-between" alignItems="center"> | ||||
| <Typography ml={15} color='#FFF' variant="h4" sx={{ "textShadow": "0px 0px 25px #0C489E" }}> | <Typography ml={15} color='#FFF' variant="h4" sx={{ "textShadow": "0px 0px 25px #0C489E" }}> | ||||
| JVM Information | |||||
| System Background Status | |||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| </div> | </div> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12} ml={15} mb={2} mt={2}> | <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> | ||||
| <Grid item xs={12} ml={15} mb={2} mt={2}> | <Grid item xs={12} ml={15} mb={2} mt={2}> | ||||
| <Paper elevation={3} sx={{ p: 2, bgcolor: 'background.paper' }}> | <Paper elevation={3} sx={{ p: 2, bgcolor: 'background.paper' }}> | ||||
| @@ -114,6 +151,35 @@ const JVMDefault = () => { | |||||
| )} | )} | ||||
| </Paper> | </Paper> | ||||
| </Grid> | </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> | </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 titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| import { FormattedMessage } from "react-intl"; | import { FormattedMessage } from "react-intl"; | ||||
| import usePageTitle from 'components/usePageTitle'; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| width: '100%', | width: '100%', | ||||
| @@ -30,6 +32,8 @@ const BackgroundHead = { | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const Index = () => { | const Index = () => { | ||||
| usePageTitle("msgDetails"); | |||||
| const params = useParams(); | const params = useParams(); | ||||
| const navigate = useNavigate() | const navigate = useNavigate() | ||||
| @@ -72,7 +76,7 @@ const Index = () => { | |||||
| <Grid item xs={12} width="100%"> | <Grid item xs={12} width="100%"> | ||||
| <div style={BackgroundHead} width="100%"> | <div style={BackgroundHead} width="100%"> | ||||
| <Stack direction="row" height='70px'> | <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" /> | <FormattedMessage id="msgDetails" /> | ||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| @@ -83,7 +87,7 @@ const Index = () => { | |||||
| <Grid container justifyContent="flex-start" alignItems="center" > | <Grid container justifyContent="flex-start" alignItems="center" > | ||||
| <center> | <center> | ||||
| <Grid item xs={12} md={12} sx={{p:2}} > | <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} | {record?.subject} | ||||
| </Typography> | </Typography> | ||||
| <Typography sx={{p:1}} align="justify">{DateUtils.datetimeStr(record?.sentDate)}</Typography> | <Typography sx={{p:1}} align="justify">{DateUtils.datetimeStr(record?.sentDate)}</Typography> | ||||
| @@ -91,7 +95,7 @@ const Index = () => { | |||||
| <div dangerouslySetInnerHTML={{__html: record?.content}}></div> | <div dangerouslySetInnerHTML={{__html: record?.content}}></div> | ||||
| </Typography> | </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 | <Button | ||||
| component="span" | component="span" | ||||
| variant="contained" | 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 item xs={12} s={6} md={6} lg={4} sx={{ ml: 3, mr: 3, mb: 3 }}> | ||||
| <Grid container> | <Grid container> | ||||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | <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']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="dateFrom" | id="dateFrom" | ||||
| @@ -151,7 +151,7 @@ const SearchForm = ({ applySearch, searchCriteria, onGridReady }) => { | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | <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']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="dateTo" | id="dateTo" | ||||
| @@ -17,7 +17,7 @@ const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); | |||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| import {FormattedMessage} from "react-intl"; | import {FormattedMessage} from "react-intl"; | ||||
| import { getSearchCriteria } from "auth/utils"; | import { getSearchCriteria } from "auth/utils"; | ||||
| import usePageTitle from "components/usePageTitle"; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| width: '100%', | width: '100%', | ||||
| @@ -31,6 +31,7 @@ const BackgroundHead = { | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const Index = () => { | const Index = () => { | ||||
| usePageTitle("systemMessage"); | |||||
| const [searchCriteria, setSearchCriteria] = React.useState({}); | const [searchCriteria, setSearchCriteria] = React.useState({}); | ||||
| const [onReady, setOnReady] = React.useState(false); | const [onReady, setOnReady] = React.useState(false); | ||||
| @@ -87,7 +88,7 @@ const Index = () => { | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <div style={BackgroundHead}> | <div style={BackgroundHead}> | ||||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | <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"/> | <FormattedMessage id="systemMessage"/> | ||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| @@ -2,7 +2,7 @@ | |||||
| import { | import { | ||||
| Grid, Button, Checkbox, FormControlLabel, Typography, | Grid, Button, Checkbox, FormControlLabel, Typography, | ||||
| Dialog, DialogTitle, DialogContent, DialogActions, | Dialog, DialogTitle, DialogContent, DialogActions, | ||||
| FormHelperText, TextField, | |||||
| FormHelperText, TextField, CircularProgress, | |||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| // import { FormControlLabel } from '@material-ui/core'; | // import { FormControlLabel } from '@material-ui/core'; | ||||
| import MainCard from "components/MainCard"; | import MainCard from "components/MainCard"; | ||||
| @@ -111,56 +111,58 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| onSubmit: (values) => { | onSubmit: (values) => { | ||||
| if (values.country == null) { | if (values.country == null) { | ||||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInCountry' })) | 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" | variant="contained" | ||||
| type="submit" | type="submit" | ||||
| color="success" | color="success" | ||||
| disabled={formik.isSubmitting} | |||||
| startIcon={formik.isSubmitting ? <CircularProgress color="inherit" size={18} /> : null} | |||||
| > | > | ||||
| Create | Create | ||||
| </Button> | </Button> | ||||
| @@ -289,6 +293,8 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| variant="contained" | variant="contained" | ||||
| type="submit" | type="submit" | ||||
| color="success" | color="success" | ||||
| disabled={formik.isSubmitting} | |||||
| startIcon={formik.isSubmitting ? <CircularProgress color="inherit" size={18} /> : null} | |||||
| > | > | ||||
| Save | Save | ||||
| </Button> | </Button> | ||||
| @@ -440,7 +446,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| value={fromDate != null ? DateUtils.dateStr(fromDate) : DateUtils.dateStr(currentFromDate)} | value={fromDate != null ? DateUtils.dateStr(fromDate) : DateUtils.dateStr(currentFromDate)} | ||||
| disabled={true} | disabled={true} | ||||
| /> : | /> : | ||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||||
| <DemoItem components={['DatePicker']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="brExpiryDate" | id="brExpiryDate" | ||||
| @@ -4,7 +4,7 @@ import { | |||||
| // Checkbox, FormControlLabel, | // Checkbox, FormControlLabel, | ||||
| Typography, | Typography, | ||||
| Dialog, DialogTitle, DialogContent, DialogActions, | Dialog, DialogTitle, DialogContent, DialogActions, | ||||
| FormHelperText | |||||
| FormHelperText, CircularProgress, | |||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| // import { FormControlLabel } from '@material-ui/core'; | // import { FormControlLabel } from '@material-ui/core'; | ||||
| import MainCard from "components/MainCard"; | 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' }))), | 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(), | faxNumber: yup.string().min(8, displayErrorMsg(intl.formatMessage({ id: 'require8Number' }))).nullable(), | ||||
| }), | }), | ||||
| onSubmit: values => { | |||||
| onSubmit: (values) => { | |||||
| if (values.country == null) { | if (values.country == null) { | ||||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInCountry' })) | 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" | variant="contained" | ||||
| type="submit" | type="submit" | ||||
| color="success" | color="success" | ||||
| disabled={formik.isSubmitting} | |||||
| startIcon={formik.isSubmitting ? <CircularProgress color="inherit" size={18} /> : null} | |||||
| > | > | ||||
| <FormattedMessage id="create" /> | <FormattedMessage id="create" /> | ||||
| </Button> | </Button> | ||||
| @@ -171,6 +177,8 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| variant="contained" | variant="contained" | ||||
| type="submit" | type="submit" | ||||
| color="success" | color="success" | ||||
| disabled={formik.isSubmitting} | |||||
| startIcon={formik.isSubmitting ? <CircularProgress color="inherit" size={18} /> : null} | |||||
| > | > | ||||
| <FormattedMessage id="save" /> | <FormattedMessage id="save" /> | ||||
| </Button> | </Button> | ||||
| @@ -22,6 +22,7 @@ import { | |||||
| isORGLoggedIn, | isORGLoggedIn, | ||||
| isPrimaryLoggedIn | isPrimaryLoggedIn | ||||
| } from "utils/Utils"; | } from "utils/Utils"; | ||||
| import usePageTitle from "components/usePageTitle"; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| @@ -42,6 +43,9 @@ import { | |||||
| const OrganizationDetailPage = () => { | const OrganizationDetailPage = () => { | ||||
| // Localized document title/meta for organisation details (GLD) | |||||
| usePageTitle("organizationProfile"); | |||||
| const params = useParams(); | const params = useParams(); | ||||
| const [formData, setFormData] = React.useState({}) | const [formData, setFormData] = React.useState({}) | ||||
| const [list, setList] = React.useState([]) | const [list, setList] = React.useState([]) | ||||
| @@ -136,11 +140,11 @@ const OrganizationDetailPage = () => { | |||||
| <div style={BackgroundHead}> | <div style={BackgroundHead}> | ||||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | ||||
| {isGLDLoggedIn()? | {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 | Maintain Organisation | ||||
| </Typography> | </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" /> | <FormattedMessage id="organizationProfile" /> | ||||
| </Typography> | </Typography> | ||||
| } | } | ||||
| @@ -233,7 +233,7 @@ const OrganizationCard_loadFromUser = ({ userData, userId }) => { | |||||
| <Typography variant="pnspsFormParagraphBold">{FieldUtils.notNullFieldLabel("Expiry Date:")}</Typography> | <Typography variant="pnspsFormParagraphBold">{FieldUtils.notNullFieldLabel("Expiry Date:")}</Typography> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12} md={6} lg={6}> | <Grid item xs={12} md={6} lg={6}> | ||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||||
| <DemoItem components={['DatePicker']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="brExpiryDate" | id="brExpiryDate" | ||||
| @@ -25,10 +25,14 @@ const BackgroundHead = { | |||||
| backgroundColor: '#0C489E', | backgroundColor: '#0C489E', | ||||
| backgroundPosition: 'right' | backgroundPosition: 'right' | ||||
| } | } | ||||
| import usePageTitle from "components/usePageTitle"; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const OrganizationDetailPage_FromUser = () => { | const OrganizationDetailPage_FromUser = () => { | ||||
| // Localized document title/meta for organisation details (from user) | |||||
| usePageTitle("organizationProfile"); | |||||
| const params = useParams(); | const params = useParams(); | ||||
| const [formData, setFormData] = useState({}) | const [formData, setFormData] = useState({}) | ||||
| const [isLoading, setLoding] = useState(true); | const [isLoading, setLoding] = useState(true); | ||||
| @@ -154,7 +154,7 @@ const OrganizationSearchForm = ({ applySearch, onGridReady, searchCriteria }) => | |||||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | ||||
| '& .MuiOutlinedInput-root': { height: 40 } | '& .MuiOutlinedInput-root': { height: 40 } | ||||
| }} | }} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| renderInput={(params) => ( | renderInput={(params) => ( | ||||
| <TextField | <TextField | ||||
| {...params} | {...params} | ||||
| @@ -6,6 +6,7 @@ import MainCard from "components/MainCard"; | |||||
| import { useEffect, useState } from "react"; | import { useEffect, useState } from "react"; | ||||
| import * as React from "react"; | import * as React from "react"; | ||||
| import { getSearchCriteria } from "auth/utils"; | import { getSearchCriteria } from "auth/utils"; | ||||
| import usePageTitle from "components/usePageTitle"; | |||||
| // import LoadingComponent from "../extra-pages/LoadingComponent"; | // import LoadingComponent from "../extra-pages/LoadingComponent"; | ||||
| // import SearchForm from "./OrganizationSearchForm"; | // import SearchForm from "./OrganizationSearchForm"; | ||||
| @@ -29,6 +30,8 @@ const BackgroundHead = { | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const OrganizationSearchPage = () => { | const OrganizationSearchPage = () => { | ||||
| // Localized document title/meta for organisation search | |||||
| usePageTitle("organizationProfile"); | |||||
| const [searchCriteria, setSearchCriteria] = useState({}); | const [searchCriteria, setSearchCriteria] = useState({}); | ||||
| const [onReady, setOnReady] = useState(false); | const [onReady, setOnReady] = useState(false); | ||||
| @@ -20,6 +20,7 @@ const DataGrid = Loadable(React.lazy(() => import('./DataGrid'))); | |||||
| import ForwardIcon from '@mui/icons-material/Forward'; | import ForwardIcon from '@mui/icons-material/Forward'; | ||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| import {FormattedMessage,useIntl} from "react-intl"; | import {FormattedMessage,useIntl} from "react-intl"; | ||||
| import usePageTitle from "components/usePageTitle"; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| width: '100%', | width: '100%', | ||||
| @@ -33,6 +34,8 @@ const BackgroundHead = { | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const Index = () => { | const Index = () => { | ||||
| usePageTitle("payDetail"); | |||||
| const params = useParams(); | const params = useParams(); | ||||
| const navigate = useNavigate() | const navigate = useNavigate() | ||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| @@ -144,7 +147,7 @@ const Index = () => { | |||||
| <Grid className="printHidden" item xs={12} width="100%"> | <Grid className="printHidden" item xs={12} width="100%"> | ||||
| <div style={BackgroundHead} width="100%"> | <div style={BackgroundHead} width="100%"> | ||||
| <Stack direction="row" height='70px'> | <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"/> | <FormattedMessage id="payDetail"/> | ||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| @@ -160,7 +163,7 @@ const Index = () => { | |||||
| </Button> | </Button> | ||||
| </Grid> | </Grid> | ||||
| {/*row 1*/} | {/*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 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 }}> | <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' }} > | <Box xs={12} md={12} sx={{ border: '3px solid #eee', borderRadius: '10px' }} > | ||||
| @@ -222,7 +222,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||||
| filterOptions={(options) => options} | filterOptions={(options) => options} | ||||
| options={ComboData.paymentStatus} | options={ComboData.paymentStatus} | ||||
| value={status} | value={status} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| inputValue={status?.label ? status?.label : ""} | inputValue={status?.label ? status?.label : ""} | ||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| if(newValue==null){ | if(newValue==null){ | ||||
| @@ -239,11 +239,9 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||||
| renderInput={(params) => ( | renderInput={(params) => ( | ||||
| <TextField {...params} | <TextField {...params} | ||||
| label="Status" | label="Status" | ||||
| InputLabelProps={{ shrink: true }} | |||||
| /> | /> | ||||
| )} | )} | ||||
| InputLabelProps={{ | |||||
| shrink: true | |||||
| }} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| @@ -256,7 +254,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||||
| filterOptions={(options) => options} | filterOptions={(options) => options} | ||||
| options={ComboData.payMethod} | options={ComboData.payMethod} | ||||
| value={payMethod} | value={payMethod} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| inputValue={payMethod?.label ? payMethod?.label : ""} | inputValue={payMethod?.label ? payMethod?.label : ""} | ||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| if(newValue==null){ | if(newValue==null){ | ||||
| @@ -273,11 +271,9 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||||
| renderInput={(params) => ( | renderInput={(params) => ( | ||||
| <TextField {...params} | <TextField {...params} | ||||
| label="Payment Method" | label="Payment Method" | ||||
| InputLabelProps={{ shrink: true }} | |||||
| /> | /> | ||||
| )} | )} | ||||
| InputLabelProps={{ | |||||
| shrink: true | |||||
| }} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| </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 item xs={12} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | ||||
| <Grid container spacing={1}> | <Grid container spacing={1}> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||||
| <DemoItem components={['DatePicker']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="dateFrom" | id="dateFrom" | ||||
| @@ -152,7 +152,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||||
| <DemoItem components={['DatePicker']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="dateTo" | id="dateTo" | ||||
| @@ -206,13 +206,16 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||||
| '& .MuiOutlinedInput-root': { height: 40 } | '& .MuiOutlinedInput-root': { height: 40 } | ||||
| }} | }} | ||||
| renderInput={(params) => ( | renderInput={(params) => ( | ||||
| <TextField {...params} | |||||
| <TextField | |||||
| {...params} | |||||
| label={intl.formatMessage({id: 'status'})} | 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> | </Grid> | ||||
| @@ -15,6 +15,7 @@ const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); | |||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| import {FormattedMessage} from "react-intl"; | import {FormattedMessage} from "react-intl"; | ||||
| import { getSearchCriteria } from "auth/utils"; | import { getSearchCriteria } from "auth/utils"; | ||||
| import usePageTitle from "components/usePageTitle"; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| @@ -29,6 +30,7 @@ const BackgroundHead = { | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const Index = () => { | const Index = () => { | ||||
| usePageTitle("onlinePaymentHistory"); | |||||
| const [searchCriteria, setSearchCriteria] = React.useState({ | const [searchCriteria, setSearchCriteria] = React.useState({ | ||||
| dateTo: DateUtils.dateValue(new Date()), | dateTo: DateUtils.dateValue(new Date()), | ||||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | ||||
| @@ -76,7 +78,7 @@ const Index = () => { | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <div style={BackgroundHead}> | <div style={BackgroundHead}> | ||||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | <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"/> | <FormattedMessage id="onlinePaymentHistory"/> | ||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| @@ -19,12 +19,23 @@ import * as ComboData from "utils/ComboData"; | |||||
| import * as React from "react"; | import * as React from "react"; | ||||
| import { useFormik } from 'formik'; | import { useFormik } from 'formik'; | ||||
| import { useNavigate } from "react-router-dom"; | import { useNavigate } from "react-router-dom"; | ||||
| import { useIntl } from 'react-intl'; | |||||
| import Loadable from 'components/Loadable'; | import Loadable from 'components/Loadable'; | ||||
| import { notifySaveSuccess } from 'utils/CommonFunction'; | import { notifySaveSuccess } from 'utils/CommonFunction'; | ||||
| const UploadFileTable = Loadable(React.lazy(() => import('./UploadFileTable'))); | 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 [data, setData] = React.useState({}); | ||||
| const [columnPrice, setColumnPrice] = React.useState(ComboData.proofPrice[0]); | const [columnPrice, setColumnPrice] = React.useState(ComboData.proofPrice[0]); | ||||
| const [attachments, setAttachments] = React.useState([]); | const [attachments, setAttachments] = React.useState([]); | ||||
| @@ -41,16 +52,24 @@ const FormPanel = ({ formData }) => { | |||||
| const [proofPaymentDeadlineMin, setProofPaymentDeadlineMin] = React.useState({}); | const [proofPaymentDeadlineMin, setProofPaymentDeadlineMin] = React.useState({}); | ||||
| const [reviseDeadlineMin, setReviseDeadlineMin] = React.useState({}); | const [reviseDeadlineMin, setReviseDeadlineMin] = React.useState({}); | ||||
| const fileInputRef = React.useRef(null); | |||||
| const navigate = useNavigate() | const navigate = useNavigate() | ||||
| React.useEffect(() => { | React.useEffect(() => { | ||||
| if (formData) { | if (formData) { | ||||
| setData(formData); | |||||
| const normalizedFormData = { | |||||
| ...formData, | |||||
| length: formData.length ?? 0, | |||||
| noOfPages: formData.noOfPages ?? 0, | |||||
| fee: formData.fee ?? 0 | |||||
| }; | |||||
| if (formData.groupType == "Private Bill") { | if (formData.groupType == "Private Bill") { | ||||
| setColumnPrice(ComboData.proofPrice[1]) | setColumnPrice(ComboData.proofPrice[1]) | ||||
| formData['length'] = 18; | |||||
| normalizedFormData['length'] = 18; | |||||
| } | } | ||||
| setData(normalizedFormData); | |||||
| setProofPaymentDeadlineMin(formData.proofPaymentDeadline); | setProofPaymentDeadlineMin(formData.proofPaymentDeadline); | ||||
| setReviseDeadlineMin(formData.reviseDeadline); | setReviseDeadlineMin(formData.reviseDeadline); | ||||
| setExpectedCode(formData.groupNo.substr(1,formData.groupNo.length)+"-"+formData.issueNo+"-"+formData.issueYear.toString().substr(2, formData.issueYear.toString().length)); | 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({ | const formik = useFormik({ | ||||
| enableReinitialize: true, | enableReinitialize: true, | ||||
| initialValues: data, | |||||
| initialValues: { ...proofFormInitialValues, ...data }, | |||||
| onSubmit: values => { | onSubmit: values => { | ||||
| setSaving(true); | setSaving(true); | ||||
| if (!attachments || attachments.length <= 0) { | if (!attachments || attachments.length <= 0) { | ||||
| @@ -167,7 +186,9 @@ const FormPanel = ({ formData }) => { | |||||
| if (msg === "haveActiveProof") { | if (msg === "haveActiveProof") { | ||||
| msg = "Action Failed: There is already a pending payment and proofreading record for client review." | msg = "Action Failed: There is already a pending payment and proofreading record for client review." | ||||
| } else if (msg === "haveProofed") { | } 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); | setWarningText(msg); | ||||
| setIsWarningPopUp(true); | setIsWarningPopUp(true); | ||||
| @@ -203,7 +224,7 @@ const FormPanel = ({ formData }) => { | |||||
| } | } | ||||
| return ( | return ( | ||||
| <MainCard xs={12} md={12} lg={12} | |||||
| <MainCard | |||||
| border={false} | border={false} | ||||
| content={false}> | content={false}> | ||||
| @@ -275,12 +296,26 @@ const FormPanel = ({ formData }) => { | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12} md={12}> | <Grid item xs={12} md={12}> | ||||
| <Button | <Button | ||||
| component="label" | |||||
| variant="contained" | variant="contained" | ||||
| size="large" | size="large" | ||||
| disabled={attachments.length >= (formik.values.groupType == "Private Bill" ? 2 : 1)} | 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 | <input | ||||
| id="uploadFileBtn" | id="uploadFileBtn" | ||||
| name="file" | name="file" | ||||
| @@ -289,9 +324,9 @@ const FormPanel = ({ formData }) => { | |||||
| hidden | hidden | ||||
| disabled={attachments.length >= (formik.values.groupType == "Private Bill" ? 2 : 1)} | disabled={attachments.length >= (formik.values.groupType == "Private Bill" ? 2 : 1)} | ||||
| onChange={readFile} | onChange={readFile} | ||||
| aria-label="Upload PDF file" | |||||
| aria-label={intl.formatMessage({ id: 'ariaUploadPdfFile' })} | |||||
| ref={fileInputRef} | |||||
| /> | /> | ||||
| </Button> | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12} md={12}> | <Grid item xs={12} md={12}> | ||||
| <UploadFileTable | <UploadFileTable | ||||
| @@ -407,7 +442,9 @@ const FormPanel = ({ formData }) => { | |||||
| options={ComboData.proofPrice} | options={ComboData.proofPrice} | ||||
| value={columnPrice} | value={columnPrice} | ||||
| inputValue={(columnPrice?.label) ? columnPrice?.label : ""} | inputValue={(columnPrice?.label) ? columnPrice?.label : ""} | ||||
| getOptionLabel={(option) => option.label ? option.label : ""} | |||||
| getOptionLabel={(option) => | |||||
| option != null && option.label != null ? String(option.label) : "" | |||||
| } | |||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| setColumnPrice(newValue) | setColumnPrice(newValue) | ||||
| formik.values["fee"] = newValue.value * formik.values.length; | 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', }}> | <DialogContent style={{ display: 'flex', }}> | ||||
| <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | ||||
| </DialogContent> | </DialogContent> | ||||
| @@ -75,7 +75,7 @@ const Index = () => { | |||||
| </Grid> | </Grid> | ||||
| </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}> | <Grid item xs={12}> | ||||
| <div style={BackgroundHead}> | <div style={BackgroundHead}> | ||||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | ||||
| @@ -88,7 +88,7 @@ const Index = () => { | |||||
| border={false} | border={false} | ||||
| content={false} | content={false} | ||||
| sx={{ | sx={{ | ||||
| backgroundColor: "backgroundColor.default" | |||||
| bgcolor: 'background.default' | |||||
| }} | }} | ||||
| > | > | ||||
| @@ -126,7 +126,7 @@ const Index = () => { | |||||
| <MainCard elevation={0} | <MainCard elevation={0} | ||||
| border={false} | border={false} | ||||
| content={false} | content={false} | ||||
| backgroundColor={"backgroundColor.default"} | |||||
| sx={{ bgcolor: 'background.default' }} | |||||
| > | > | ||||
| <Box xs={12} ml={4} mt={3} sx={{ p: 1, borderRadius: '10px', backgroundColor: "#fff" }}> | <Box xs={12} ml={4} mt={3} sx={{ p: 1, borderRadius: '10px', backgroundColor: "#fff" }}> | ||||
| <ProofForm | <ProofForm | ||||
| @@ -8,11 +8,13 @@ import { | |||||
| Button, | Button, | ||||
| Stack, | Stack, | ||||
| Dialog, DialogTitle, DialogContent, DialogActions, | Dialog, DialogTitle, DialogContent, DialogActions, | ||||
| CircularProgress, | |||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| import { useFormik } from 'formik'; | import { useFormik } from 'formik'; | ||||
| import { useIntl } from 'react-intl'; | |||||
| import {isGranted} from "auth/utils"; | 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 HttpUtils from "utils/HttpUtils" | ||||
| import * as UrlUtils from "utils/ApiPathConst" | import * as UrlUtils from "utils/ApiPathConst" | ||||
| import * as DateUtils from "utils/DateUtils" | import * as DateUtils from "utils/DateUtils" | ||||
| @@ -31,10 +33,15 @@ const ApplicationDetailCard = ({ | |||||
| }) => { | }) => { | ||||
| const params = useParams(); | const params = useParams(); | ||||
| const intl = useIntl(); | |||||
| const [data, setData] = useState({}); | const [data, setData] = useState({}); | ||||
| const [cancelPopUp, setCancelPopUp] = useState(false); | const [cancelPopUp, setCancelPopUp] = useState(false); | ||||
| const [cancelLoading, setCancelLoading] = useState(false); | |||||
| const cancellingRef = useRef(false); | |||||
| const [onDownload, setOnDownload] = useState(false); | const [onDownload, setOnDownload] = useState(false); | ||||
| const [alertMsg, setAlertMsg] = useState(''); | |||||
| const [showAlert, setShowAlert] = useState(false); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (formData) { | if (formData) { | ||||
| @@ -94,11 +101,30 @@ const ApplicationDetailCard = ({ | |||||
| } | } | ||||
| const confirmCancel = () => { | const confirmCancel = () => { | ||||
| setCancelPopUp(false); | |||||
| if (cancellingRef.current) return; | |||||
| cancellingRef.current = true; | |||||
| setCancelLoading(true); | |||||
| HttpUtils.get({ | HttpUtils.get({ | ||||
| url: UrlUtils.CANCEL_PROOF + "/" + params.id, | 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' }}> | <Grid item xs={12} md={9} lg={9} sx={{ display: 'flex', alignItems: 'center' }}> | ||||
| <FormControl variant="outlined"> | <FormControl variant="outlined"> | ||||
| {StatusUtils.getStatusByText(data.appStatus)} | |||||
| {StatusUtils.getStatusByTextEng(data.appStatus, data.creditor)} | |||||
| </FormControl> | </FormControl> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| @@ -338,15 +364,33 @@ const ApplicationDetailCard = ({ | |||||
| <div> | <div> | ||||
| <Dialog | <Dialog | ||||
| open={cancelPopUp} | open={cancelPopUp} | ||||
| onClose={() => setCancelPopUp(false)} | |||||
| onClose={() => { | |||||
| if (cancelLoading) return; | |||||
| setCancelPopUp(false); | |||||
| }} | |||||
| > | > | ||||
| <DialogTitle><Typography variant="h3">Confirm</Typography></DialogTitle> | <DialogTitle><Typography variant="h3">Confirm</Typography></DialogTitle> | ||||
| <DialogContent style={{ display: 'flex', }}> | <DialogContent style={{ display: 'flex', }}> | ||||
| <Typography variant="h4" style={{ padding: '16px' }}>Are you sure you want to cancel this proof?</Typography> | <Typography variant="h4" style={{ padding: '16px' }}>Are you sure you want to cancel this proof?</Typography> | ||||
| </DialogContent> | </DialogContent> | ||||
| <DialogActions> | <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> | </DialogActions> | ||||
| </Dialog> | </Dialog> | ||||
| </div> | </div> | ||||
| @@ -55,6 +55,7 @@ const FormPanel = ({ formData }) => { | |||||
| const [isSubmitting, setIsSubmitting] = React.useState(false); | const [isSubmitting, setIsSubmitting] = React.useState(false); | ||||
| const [isOnlyOnlinePayment, setOnlyOnlinePayment] = React.useState(); | const [isOnlyOnlinePayment, setOnlyOnlinePayment] = React.useState(); | ||||
| const [isNoPayment, setNoPayment] = React.useState(); | const [isNoPayment, setNoPayment] = React.useState(); | ||||
| const fileInputRef = React.useRef(null); | |||||
| const navigate = useNavigate() | const navigate = useNavigate() | ||||
| const params = useParams(); | const params = useParams(); | ||||
| @@ -166,14 +167,16 @@ const FormPanel = ({ formData }) => { | |||||
| onFail: function (response) { | onFail: function (response) { | ||||
| setIsSubmitting(false); | setIsSubmitting(false); | ||||
| setWarningTitle(intl.formatMessage({ id: "attention" })) | 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); | setIsWarningPopUp(true); | ||||
| console.log(response); | console.log(response); | ||||
| }, | }, | ||||
| onError: function (error) { | onError: function (error) { | ||||
| setIsSubmitting(false); | setIsSubmitting(false); | ||||
| setWarningTitle(intl.formatMessage({ id: "attention" })) | 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); | setIsWarningPopUp(true); | ||||
| console.log(error); | console.log(error); | ||||
| } | } | ||||
| @@ -628,6 +631,30 @@ const FormPanel = ({ formData }) => { | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12} md={12} textAlign="left"> | <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 | <input | ||||
| id="uploadFileBtn" | id="uploadFileBtn" | ||||
| name="file" | name="file" | ||||
| @@ -638,20 +665,8 @@ const FormPanel = ({ formData }) => { | |||||
| onChange={(event) => { | onChange={(event) => { | ||||
| readFile(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> | </Grid> | ||||
| @@ -700,13 +715,16 @@ const FormPanel = ({ formData }) => { | |||||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | <ThemeProvider theme={PNSPS_BUTTON_THEME}> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| color="success" | |||||
| type="submit" | type="submit" | ||||
| disabled={(actionValue == false && isOverReviseDeadline()) || isSubmitting} | disabled={(actionValue == false && isOverReviseDeadline()) || isSubmitting} | ||||
| startIcon={isSubmitting ? <CircularProgress size={20} color="inherit" /> : null} | |||||
| aria-label={intl.formatMessage({ id: 'submitReply' })} | 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> | </Button> | ||||
| </ThemeProvider> | </ThemeProvider> | ||||
| </Grid> | </Grid> | ||||
| @@ -21,6 +21,7 @@ const ProofForm = Loadable(React.lazy(() => import('./ProofForm'))); | |||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| import MainCard from "../../../components/MainCard"; | import MainCard from "../../../components/MainCard"; | ||||
| import {FormattedMessage} from "react-intl"; | import {FormattedMessage} from "react-intl"; | ||||
| import usePageTitle from "components/usePageTitle"; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| width: '100%', | width: '100%', | ||||
| @@ -35,6 +36,8 @@ import {useIntl} from "react-intl"; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const Index = () => { | const Index = () => { | ||||
| usePageTitle("proofRecord"); | |||||
| const params = useParams(); | const params = useParams(); | ||||
| const navigate = useNavigate() | const navigate = useNavigate() | ||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| @@ -92,7 +95,7 @@ const Index = () => { | |||||
| <Grid item xs={12} width="100%"> | <Grid item xs={12} width="100%"> | ||||
| <div style={BackgroundHead} width="100%"> | <div style={BackgroundHead} width="100%"> | ||||
| <Stack direction="row" height='70px'> | <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"/> | <FormattedMessage id="proofRecord"/> | ||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| @@ -111,12 +114,10 @@ const Index = () => { | |||||
| <Grid item xs={12} sm={12} md={12} lg={12} sx={{ width:'100%', mt:2, mb: -3}}> | <Grid item xs={12} sm={12} md={12} lg={12} sx={{ width:'100%', mt:2, mb: -3}}> | ||||
| <MainCard | <MainCard | ||||
| sx={{ | sx={{ | ||||
| mr:2, | |||||
| mr: 2, | |||||
| boxShadow: 1, | boxShadow: 1, | ||||
| border: 1, | |||||
| borderColor: '#DDD', | |||||
| border: '1px groove #DDD', | |||||
| }} | }} | ||||
| border= '1px groove grey' | |||||
| > | > | ||||
| <ApplicationDetails | <ApplicationDetails | ||||
| formData={record} | formData={record} | ||||
| @@ -129,11 +130,8 @@ const Index = () => { | |||||
| <MainCard | <MainCard | ||||
| sx={{ | sx={{ | ||||
| boxShadow: 1, | boxShadow: 1, | ||||
| border: 1, | |||||
| borderColor: '#DDD', | |||||
| border: '1px groove #DDD', | |||||
| }} | }} | ||||
| border= '1px groove grey' | |||||
| // sx={..._sx} | |||||
| > | > | ||||
| <ProofForm | <ProofForm | ||||
| formData={record} | formData={record} | ||||
| @@ -46,10 +46,14 @@ export default function SearchPublicNoticeTable({searchCriteria, applyGridOnRead | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| id: 'actions', | |||||
| id: 'proofStatus', | |||||
| field: 'proofStatus', | |||||
| headerName: 'Status', | headerName: 'Status', | ||||
| flex: 1, | flex: 1, | ||||
| minWidth: 150, | minWidth: 150, | ||||
| sortable: false, | |||||
| filterable: false, | |||||
| valueGetter: () => '', | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return ProofStatus.getStatus_Eng(params); | return ProofStatus.getStatus_Eng(params); | ||||
| }, | }, | ||||
| @@ -26,11 +26,15 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||||
| const [type, setType] = React.useState([]); | const [type, setType] = React.useState([]); | ||||
| const [status, setStatus] = React.useState(searchCriteria.statusKey!=undefined?ComboData.proofStatus_GLD[searchCriteria.statusKey]:ComboData.proofStatus_GLD[0]); | 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 [orgCombo, setOrgCombo] = React.useState(); | ||||
| const [issueSelected, setIssueSelected] = React.useState({}); | |||||
| const [issueSelected, setIssueSelected] = React.useState(null); | |||||
| const [issueCombo, setIssueCombo] = React.useState([]); | 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 [minDate, setMinDate] = React.useState(searchCriteria.dateFrom); | ||||
| const [maxDate, setMaxDate] = React.useState(searchCriteria.dateTo); | const [maxDate, setMaxDate] = React.useState(searchCriteria.dateTo); | ||||
| @@ -136,9 +140,9 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||||
| function resetForm() { | function resetForm() { | ||||
| setType([]); | setType([]); | ||||
| setStatus(ComboData.proofStatus[0]); | setStatus(ComboData.proofStatus[0]); | ||||
| setOrgSelected({}); | |||||
| setIssueSelected({}); | |||||
| setGroupSelected({}); | |||||
| setOrgSelected(null); | |||||
| setIssueSelected(null); | |||||
| setGroupSelected(null); | |||||
| setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | ||||
| setMaxDate(DateUtils.dateValue(new Date())) | setMaxDate(DateUtils.dateValue(new Date())) | ||||
| reset({ | reset({ | ||||
| @@ -257,7 +261,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||||
| options={ComboData.groupTitle} | options={ComboData.groupTitle} | ||||
| value={groupSelected} | value={groupSelected} | ||||
| inputValue={(groupSelected?.label) ? groupSelected?.label : ""} | inputValue={(groupSelected?.label) ? groupSelected?.label : ""} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| setGroupSelected(newValue); | setGroupSelected(newValue); | ||||
| }} | }} | ||||
| @@ -371,13 +375,12 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||||
| '& .MuiOutlinedInput-root': { height: 40 } | '& .MuiOutlinedInput-root': { height: 40 } | ||||
| }} | }} | ||||
| renderInput={(params) => ( | renderInput={(params) => ( | ||||
| <TextField {...params} | |||||
| <TextField | |||||
| {...params} | |||||
| label="Status" | label="Status" | ||||
| InputLabelProps={{ shrink: true }} | |||||
| /> | /> | ||||
| )} | )} | ||||
| InputLabelProps={{ | |||||
| shrink: true | |||||
| }} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| @@ -396,11 +399,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||||
| inputValue={orgSelected ? orgSelected.name!=undefined?orgSelected.name:"" : ""} | inputValue={orgSelected ? orgSelected.name!=undefined?orgSelected.name:"" : ""} | ||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| if (newValue !== null) { | |||||
| setOrgSelected(newValue); | |||||
| }else{ | |||||
| setOrgSelected({}); | |||||
| } | |||||
| setOrgSelected(newValue ?? null); | |||||
| }} | }} | ||||
| sx={{ | sx={{ | ||||
| '& .MuiInputBase-root': { alignItems: 'center' }, | '& .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 = [ | const columns = [ | ||||
| { | { | ||||
| field: 'actions', | field: 'actions', | ||||
| @@ -107,6 +111,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| width: isMdOrLg ? 'auto' : 200, | width: isMdOrLg ? 'auto' : 200, | ||||
| flex: isMdOrLg ? 1.5 : undefined, | flex: isMdOrLg ? 1.5 : undefined, | ||||
| cellClassName: 'actions', | cellClassName: 'actions', | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return clickableLink('/proof/reply/' + params.row.id, params.row.refNo); | 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' }), | headerName: isORGLoggedIn() ? intl.formatMessage({ id: 'gazetteCount3' }) : intl.formatMessage({ id: 'gazetteCount2' }), | ||||
| width: isMdOrLg ? 'auto' : 330, | width: isMdOrLg ? 'auto' : 330, | ||||
| flex: isMdOrLg ? 2 : undefined, | flex: isMdOrLg ? 2 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| // let appNo = params.row.appNo; | // let appNo = params.row.appNo; | ||||
| // let code = params.row.groupNo; | // let code = params.row.groupNo; | ||||
| @@ -132,6 +138,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| headerName: intl.formatMessage({ id: 'proofDate' }), | headerName: intl.formatMessage({ id: 'proofDate' }), | ||||
| width: isMdOrLg ? 'auto' : 200, | width: isMdOrLg ? 'auto' : 200, | ||||
| flex: isMdOrLg ? 1.5 : undefined, | flex: isMdOrLg ? 1.5 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return DateUtils.datetimeStr(params?.value); | return DateUtils.datetimeStr(params?.value); | ||||
| } | } | ||||
| @@ -142,6 +149,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| headerName: intl.formatMessage({ id: 'replyBefore' }), | headerName: intl.formatMessage({ id: 'replyBefore' }), | ||||
| width: isMdOrLg ? 'auto' : 200, | width: isMdOrLg ? 'auto' : 200, | ||||
| flex: isMdOrLg ? 1.5 : undefined, | flex: isMdOrLg ? 1.5 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| const proofPaymentDeadline = DateUtils.convertToDate(params?.value); | const proofPaymentDeadline = DateUtils.convertToDate(params?.value); | ||||
| return DateUtils.datetimeStr( | return DateUtils.datetimeStr( | ||||
| @@ -156,15 +164,17 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| headerName: intl.formatMessage({ id: 'replyDate' }), | headerName: intl.formatMessage({ id: 'replyDate' }), | ||||
| width: isMdOrLg ? 'auto' : 200, | width: isMdOrLg ? 'auto' : 200, | ||||
| flex: isMdOrLg ? 1.5 : undefined, | flex: isMdOrLg ? 1.5 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return params?.value ? DateUtils.datetimeStr(params?.value) : ""; | return params?.value ? DateUtils.datetimeStr(params?.value) : ""; | ||||
| } | } | ||||
| }, | }, | ||||
| { | { | ||||
| id: 'actions', | |||||
| field: 'proofStatus', | |||||
| headerName: intl.formatMessage({ id: 'status' }), | headerName: intl.formatMessage({ id: 'status' }), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return locale === 'en' ? ProofStatus.getStatus_Eng(params) : locale === 'zh-HK' ? ProofStatus.getStatus_Cht(params) : ProofStatus.getStatus_Cn(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' }), | headerName: intl.formatMessage({ id: 'fee' }), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return (params?.value) ? "$ " + FormatUtils.currencyFormat(params?.value) : ""; | return (params?.value) ? "$ " + FormatUtils.currencyFormat(params?.value) : ""; | ||||
| } | } | ||||
| @@ -29,7 +29,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||||
| const [type, setType] = React.useState([]); | const [type, setType] = React.useState([]); | ||||
| const [status, setStatus] = React.useState(searchCriteria.statusKey!=undefined?ComboData.proofStatusFull[searchCriteria.statusKey]:ComboData.proofStatusFull[0]); | 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 [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):{}); | ||||
| @@ -110,7 +110,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||||
| if (issueComboData && issueComboData.length > 0) { | if (issueComboData && issueComboData.length > 0) { | ||||
| setIssueCombo(issueComboData); | setIssueCombo(issueComboData); | ||||
| if(searchCriteria.issueId!=undefined){ | if(searchCriteria.issueId!=undefined){ | ||||
| setIssueSelected(issueComboData.find(item => item.id === searchCriteria.issueId)) | |||||
| setIssueSelected(issueComboData.find(item => item.id === searchCriteria.issueId) ?? null) | |||||
| } | } | ||||
| } | } | ||||
| }, [issueComboData]); | }, [issueComboData]); | ||||
| @@ -118,7 +118,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||||
| function resetForm() { | function resetForm() { | ||||
| setType([]); | setType([]); | ||||
| setStatus(ComboData.proofStatusFull[0]); | setStatus(ComboData.proofStatusFull[0]); | ||||
| setIssueSelected({}); | |||||
| setIssueSelected(null); | |||||
| setGroupSelected({}); | setGroupSelected({}); | ||||
| setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | ||||
| setMaxDate(DateUtils.dateValue(new Date())) | setMaxDate(DateUtils.dateValue(new Date())) | ||||
| @@ -205,8 +205,9 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||||
| id="issueId" | id="issueId" | ||||
| options={issueCombo} | options={issueCombo} | ||||
| value={issueSelected} | value={issueSelected} | ||||
| isOptionEqualToValue={(option, value) => option?.id === value?.id} | |||||
| inputValue={(issueSelected?.id) ? getIssueLabel(issueSelected) : ""} | inputValue={(issueSelected?.id) ? getIssueLabel(issueSelected) : ""} | ||||
| getOptionLabel={(option) => getIssueLabel(option)} | |||||
| getOptionLabel={(option) => (option?.id ? getIssueLabel(option) : "")} | |||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| setIssueSelected(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> | </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 item xs={12} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | ||||
| <Grid container spacing={1}> | <Grid container spacing={1}> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||||
| <DemoItem components={['DatePicker']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="dateFrom" | id="dateFrom" | ||||
| @@ -279,7 +284,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||||
| <DemoItem components={['DatePicker']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="dateTo" | id="dateTo" | ||||
| @@ -348,11 +353,16 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o | |||||
| renderInput={(params) => ( | renderInput={(params) => ( | ||||
| <TextField {...params} | <TextField {...params} | ||||
| label={intl.formatMessage({id: 'status'})} | 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> | </Grid> | ||||
| @@ -17,6 +17,7 @@ const SearchForm = Loadable(React.lazy(() => import('./SearchForm'))); | |||||
| const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); | const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); | ||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| import {FormattedMessage} from "react-intl"; | import {FormattedMessage} from "react-intl"; | ||||
| import usePageTitle from "components/usePageTitle"; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| @@ -31,7 +32,8 @@ const BackgroundHead = { | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const UserSearchPage_Individual = () => { | const UserSearchPage_Individual = () => { | ||||
| usePageTitle("proofRecord"); | |||||
| const [issueCombo,setIssueCombo] = React.useState([]); | const [issueCombo,setIssueCombo] = React.useState([]); | ||||
| const [searchCriteria, setSearchCriteria] = React.useState({ | const [searchCriteria, setSearchCriteria] = React.useState({ | ||||
| dateTo: DateUtils.dateValue(new Date()), | dateTo: DateUtils.dateValue(new Date()), | ||||
| @@ -87,7 +89,7 @@ const UserSearchPage_Individual = () => { | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <div style={BackgroundHead} > | <div style={BackgroundHead} > | ||||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center" > | <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"/> | <FormattedMessage id="proofRecord"/> | ||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| @@ -282,7 +282,7 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||||
| <Grid item xs={12} md={12} width="100%" > | <Grid item xs={12} md={12} width="100%" > | ||||
| <div style={BackgroundHead}> | <div style={BackgroundHead}> | ||||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | <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" /> | <FormattedMessage id="applyPublicNotice" /> | ||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| @@ -566,6 +566,7 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||||
| label: intl.formatMessage({ id: 'careOf' }) + ":", | label: intl.formatMessage({ id: 'careOf' }) + ":", | ||||
| valueName: "careOf", | valueName: "careOf", | ||||
| form: formik, | form: formik, | ||||
| inputProps: { "aria-label": intl.formatMessage({ id: 'careOf' }) } | |||||
| // disabled: true | // disabled: true | ||||
| })} | })} | ||||
| </Grid> | </Grid> | ||||
| @@ -593,7 +594,7 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||||
| label: intl.formatMessage({ id: 'extraMark' }) + ":", | label: intl.formatMessage({ id: 'extraMark' }) + ":", | ||||
| valueName: "remarks", | valueName: "remarks", | ||||
| form: formik, | form: formik, | ||||
| inputProps: { maxLength: 255 } | |||||
| inputProps: { maxLength: 255, "aria-label": intl.formatMessage({ id: 'extraMark' }) } | |||||
| })} | })} | ||||
| </Grid> | </Grid> | ||||
| } | } | ||||
| @@ -622,6 +623,9 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||||
| name="tickAccept" | name="tickAccept" | ||||
| color="primary" | color="primary" | ||||
| size="small" | size="small" | ||||
| inputProps={{ | |||||
| "aria-label": intl.formatMessage({ id: "applyTickStr" }) | |||||
| }} | |||||
| /> | /> | ||||
| <Typography variant="h6" height="100%" > | <Typography variant="h6" height="100%" > | ||||
| <div style={{ padding: 12, textAlign: 'justify' }} dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "applyTickStr" }) }} /> | <div style={{ padding: 12, textAlign: 'justify' }} dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "applyTickStr" }) }} /> | ||||
| @@ -651,9 +655,6 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| </form> | </form> | ||||
| ) : null} | ) : null} | ||||
| @@ -16,7 +16,7 @@ import Loadable from 'components/Loadable'; | |||||
| import { lazy } from 'react'; | import { lazy } from 'react'; | ||||
| const LoadingComponent = Loadable(lazy(() => import('../../extra-pages/LoadingComponent'))); | const LoadingComponent = Loadable(lazy(() => import('../../extra-pages/LoadingComponent'))); | ||||
| const PublicNoticeApplyForm = Loadable(lazy(() => import('./PublicNoticeApplyForm'))); | const PublicNoticeApplyForm = Loadable(lazy(() => import('./PublicNoticeApplyForm'))); | ||||
| import usePageTitle from "components/usePageTitle"; | |||||
| import { | import { | ||||
| // isORGLoggedIn, | // isORGLoggedIn, | ||||
| isDummyLoggedIn, | isDummyLoggedIn, | ||||
| @@ -27,6 +27,8 @@ import { | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const ApplyForm = () => { | const ApplyForm = () => { | ||||
| usePageTitle("applyPublicNotice"); | |||||
| const [userData, setUserData] = React.useState(null); | const [userData, setUserData] = React.useState(null); | ||||
| const [gazetteIssueList, setGazetteIssueList] = React.useState([]); | const [gazetteIssueList, setGazetteIssueList] = React.useState([]); | ||||
| @@ -62,7 +64,7 @@ const ApplyForm = () => { | |||||
| for (var i = 0; i < response?.gazetteIssueList?.length; i++) { | for (var i = 0; i < response?.gazetteIssueList?.length; i++) { | ||||
| let data = response.gazetteIssueList[i]; | let data = response.gazetteIssueList[i]; | ||||
| //let label = getIssueLabel(data); | //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); | setGazetteIssueList(response?.gazetteIssueList); | ||||
| setSelection(selection); | setSelection(selection); | ||||
| @@ -78,7 +80,7 @@ const ApplyForm = () => { | |||||
| for (var i = 0; i < gazetteIssueList?.length; i++) { | for (var i = 0; i < gazetteIssueList?.length; i++) { | ||||
| let data = gazetteIssueList[i]; | let data = gazetteIssueList[i]; | ||||
| let label = getIssueLabel(data); | 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); | setSelection(selection); | ||||
| } | } | ||||
| @@ -9,6 +9,7 @@ import { | |||||
| Stack, | Stack, | ||||
| Dialog, DialogTitle, DialogContent, DialogActions, InputAdornment, Autocomplete | Dialog, DialogTitle, DialogContent, DialogActions, InputAdornment, Autocomplete | ||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| import LoadingButton from '@mui/lab/LoadingButton'; | |||||
| import { isGranted, delBugMode, getPaymentMethodGLD} from "auth/utils"; | import { isGranted, delBugMode, getPaymentMethodGLD} from "auth/utils"; | ||||
| const MainCard = Loadable(lazy(() => import('components/MainCard'))); | const MainCard = Loadable(lazy(() => import('components/MainCard'))); | ||||
| import { useForm } from "react-hook-form"; | 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 EditNoteIcon from '@mui/icons-material/EditNote'; | ||||
| import DownloadIcon from '@mui/icons-material/Download'; | import DownloadIcon from '@mui/icons-material/Download'; | ||||
| import ReplayIcon from '@mui/icons-material/Replay'; | import ReplayIcon from '@mui/icons-material/Replay'; | ||||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||||
| import { notifyActionError } from 'utils/CommonFunction'; | |||||
| import { isGrantedAny } from "auth/utils"; | 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 ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const ApplicationDetailCard = ( | const ApplicationDetailCard = ( | ||||
| { applicationDetailData, | { applicationDetailData, | ||||
| setStatus, | setStatus, | ||||
| setUploadStatus | |||||
| setUploadStatus, | |||||
| statusDialogOpen = false, | |||||
| statusDialogKind = "", | |||||
| statusActionLoading = false, | |||||
| } | } | ||||
| ) => { | ) => { | ||||
| const publishWithdrawBusy = | |||||
| statusActionLoading || | |||||
| (statusDialogOpen && (statusDialogKind === 'publish' || statusDialogKind === 'withdraw')); | |||||
| const [currentApplicationDetailData, setCurrentApplicationDetailData] = useState({}); | const [currentApplicationDetailData, setCurrentApplicationDetailData] = useState({}); | ||||
| const [companyName, setCompanyName] = useState({}); | const [companyName, setCompanyName] = useState({}); | ||||
| const [orgDetail, setOrgDetail] = useState({}); | const [orgDetail, setOrgDetail] = useState({}); | ||||
| @@ -59,13 +88,15 @@ const ApplicationDetailCard = ( | |||||
| const [mode, setMode] = useState(""); | const [mode, setMode] = useState(""); | ||||
| const { register, handleSubmit } = useForm() | const { register, handleSubmit } = useForm() | ||||
| // const intl = useIntl(); | |||||
| const intl = useIntl(); | |||||
| const [isWarningPopUp, setIsWarningPopUp] = useState(false); | const [isWarningPopUp, setIsWarningPopUp] = useState(false); | ||||
| const [warningText, setWarningText] = useState(""); | const [warningText, setWarningText] = useState(""); | ||||
| const [remarksPopUp, setRemarksPopUp] = useState(false); | const [remarksPopUp, setRemarksPopUp] = useState(false); | ||||
| const [onDownload, setOnDownload] = 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(() => { | useEffect(() => { | ||||
| //if user data from parent are not null | //if user data from parent are not null | ||||
| @@ -125,12 +156,12 @@ const ApplicationDetailCard = ( | |||||
| fileId: fileDetail?.id, | fileId: fileDetail?.id, | ||||
| skey: fileDetail?.skey, | skey: fileDetail?.skey, | ||||
| filename: fileDetail?.filename, | filename: fileDetail?.filename, | ||||
| onResponse:()=>{ | |||||
| setOnDownload(false) | |||||
| notifyDownloadSuccess() | |||||
| onResponse: () => { | |||||
| setOnDownload(false); | |||||
| }, | }, | ||||
| onError:()=>{ | |||||
| setOnDownload(false) | |||||
| onError: () => { | |||||
| setOnDownload(false); | |||||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||||
| } | } | ||||
| }); | }); | ||||
| setUploadStatus(true) | setUploadStatus(true) | ||||
| @@ -166,10 +197,12 @@ const ApplicationDetailCard = ( | |||||
| }; | }; | ||||
| const withdrawnClick = () => () => { | const withdrawnClick = () => () => { | ||||
| if (publishWithdrawBusy) return; | |||||
| setStatus("withdraw") | setStatus("withdraw") | ||||
| }; | }; | ||||
| const doPublish = () => () => { | const doPublish = () => () => { | ||||
| if (publishWithdrawBusy) return; | |||||
| setStatus("publish") | setStatus("publish") | ||||
| } | } | ||||
| @@ -178,10 +211,13 @@ const ApplicationDetailCard = ( | |||||
| }; | }; | ||||
| const onProofClick = () => { | const onProofClick = () => { | ||||
| if (isProofCheckLoading) return; | |||||
| if (applicationDetailData.data.groupNo) { | if (applicationDetailData.data.groupNo) { | ||||
| setIsProofCheckLoading(true); | |||||
| HttpUtils.get({ | HttpUtils.get({ | ||||
| url: CHECK_CREATE_PROOF + "/" + currentApplicationDetailData.id, | url: CHECK_CREATE_PROOF + "/" + currentApplicationDetailData.id, | ||||
| onSuccess: function (responeData) { | onSuccess: function (responeData) { | ||||
| setIsProofCheckLoading(false); | |||||
| if (responeData.success == true) { | if (responeData.success == true) { | ||||
| window.open("/proof/create/" + currentApplicationDetailData.id, "_blank", "noreferrer"); | window.open("/proof/create/" + currentApplicationDetailData.id, "_blank", "noreferrer"); | ||||
| window.addEventListener("focus", onFocus) | window.addEventListener("focus", onFocus) | ||||
| @@ -190,12 +226,16 @@ const ApplicationDetailCard = ( | |||||
| if (msg === "haveActiveProof") { | if (msg === "haveActiveProof") { | ||||
| msg = "Action Failed: There is already a pending payment and proofreading record for client review." | msg = "Action Failed: There is already a pending payment and proofreading record for client review." | ||||
| } else if (msg === "haveProofed") { | } 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); | setWarningText(msg); | ||||
| setIsWarningPopUp(true); | setIsWarningPopUp(true); | ||||
| } | } | ||||
| } | |||||
| }, | |||||
| onFail: () => setIsProofCheckLoading(false), | |||||
| onError: () => setIsProofCheckLoading(false) | |||||
| }); | }); | ||||
| } else { | } else { | ||||
| setWarningText("Please generate Gazette Code before Create Proof."); | setWarningText("Please generate Gazette Code before Create Proof."); | ||||
| @@ -256,6 +296,7 @@ const ApplicationDetailCard = ( | |||||
| <Button | <Button | ||||
| // size="large" | // size="large" | ||||
| variant="contained" | variant="contained" | ||||
| disabled={isProofCheckLoading} | |||||
| onClick={() => { onProofClick() }} | onClick={() => { onProofClick() }} | ||||
| sx={{ | sx={{ | ||||
| textTransform: 'capitalize', | textTransform: 'capitalize', | ||||
| @@ -324,31 +365,26 @@ const ApplicationDetailCard = ( | |||||
| </> : | </> : | ||||
| (currentApplicationDetailData.status == "confirmed" && currentApplicationDetailData.creditor == 1) ? | (currentApplicationDetailData.status == "confirmed" && currentApplicationDetailData.creditor == 1) ? | ||||
| <> | <> | ||||
| <Button | |||||
| <LoadingButton | |||||
| // size="large" | // size="large" | ||||
| variant="contained" | variant="contained" | ||||
| onClick={doPublish()} | onClick={doPublish()} | ||||
| disabled={setCompleteDisable()} | |||||
| sx={{ | |||||
| textTransform: 'capitalize', | |||||
| alignItems: 'end', | |||||
| backgroundColor: '#52b202' | |||||
| }}> | |||||
| disabled={setCompleteDisable() || publishWithdrawBusy} | |||||
| loading={statusActionLoading && statusDialogKind === 'publish'} | |||||
| sx={publishWithdrawLoadingSx('#52b202', '#489f04')}> | |||||
| <DoneIcon /> | <DoneIcon /> | ||||
| <Typography ml={1} variant="h5">Publish</Typography> | <Typography ml={1} variant="h5">Publish</Typography> | ||||
| </Button> | |||||
| <Button | |||||
| </LoadingButton> | |||||
| <LoadingButton | |||||
| // size="large" | // size="large" | ||||
| variant="contained" | variant="contained" | ||||
| onClick={withdrawnClick()} | onClick={withdrawnClick()} | ||||
| sx={{ | |||||
| textTransform: 'capitalize', | |||||
| alignItems: 'end', | |||||
| backgroundColor: '#ffa733' | |||||
| }}> | |||||
| disabled={publishWithdrawBusy} | |||||
| loading={statusActionLoading && statusDialogKind === 'withdraw'} | |||||
| sx={publishWithdrawLoadingSx('#ffa733', '#e8982e')}> | |||||
| <CloseIcon /> | <CloseIcon /> | ||||
| <Typography ml={1} variant="h5">Withdraw</Typography> | <Typography ml={1} variant="h5">Withdraw</Typography> | ||||
| </Button> | |||||
| </LoadingButton> | |||||
| </> | </> | ||||
| : | : | ||||
| ( | ( | ||||
| @@ -382,18 +418,16 @@ const ApplicationDetailCard = ( | |||||
| <DoneIcon /> | <DoneIcon /> | ||||
| <Typography ml={1} variant="h5">Publish</Typography> | <Typography ml={1} variant="h5">Publish</Typography> | ||||
| </Button> | </Button> | ||||
| <Button | |||||
| <LoadingButton | |||||
| // size="large" | // size="large" | ||||
| variant="contained" | variant="contained" | ||||
| onClick={withdrawnClick()} | onClick={withdrawnClick()} | ||||
| sx={{ | |||||
| textTransform: 'capitalize', | |||||
| alignItems: 'end', | |||||
| backgroundColor: '#ffa733' | |||||
| }}> | |||||
| disabled={publishWithdrawBusy} | |||||
| loading={statusActionLoading && statusDialogKind === 'withdraw'} | |||||
| sx={publishWithdrawLoadingSx('#ffa733', '#e8982e')}> | |||||
| <CloseIcon /> | <CloseIcon /> | ||||
| <Typography ml={1} variant="h5">Withdraw</Typography> | <Typography ml={1} variant="h5">Withdraw</Typography> | ||||
| </Button> | |||||
| </LoadingButton> | |||||
| </> : null | </> : null | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -715,7 +749,11 @@ const ApplicationDetailCard = ( | |||||
| sx={{ | sx={{ | ||||
| textTransform: 'capitalize', | textTransform: 'capitalize', | ||||
| alignItems: 'end', | alignItems: 'end', | ||||
| }}> | |||||
| backgroundColor: '#0C489E', | |||||
| color: '#FFFFFF', | |||||
| '&:hover': { backgroundColor: '#093A7A' }, | |||||
| }} | |||||
| > | |||||
| <DownloadIcon /> | <DownloadIcon /> | ||||
| </Button> | </Button> | ||||
| </Grid> | </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', }}> | <DialogContent style={{ display: 'flex', }}> | ||||
| <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | ||||
| </DialogContent> | </DialogContent> | ||||
| @@ -863,7 +901,7 @@ const ApplicationDetailCard = ( | |||||
| }} | }} | ||||
| > | > | ||||
| <form onSubmit={handleSubmit(onSubmit)}> | <form onSubmit={handleSubmit(onSubmit)}> | ||||
| <DialogTitle><Typography variant="h3">Remarks</Typography></DialogTitle> | |||||
| <DialogTitle component="div"><Typography variant="h3">Remarks</Typography></DialogTitle> | |||||
| <DialogContent style={{ display: 'flex', }}> | <DialogContent style={{ display: 'flex', }}> | ||||
| <Grid container direction="column"> | <Grid container direction="column"> | ||||
| <Grid item sx={{ padding: '16px' }}> | <Grid item sx={{ padding: '16px' }}> | ||||
| @@ -914,7 +952,7 @@ const ApplicationDetailCard = ( | |||||
| filterOptions={(options) => options} | filterOptions={(options) => options} | ||||
| options={ComboData.paymentMeans} | options={ComboData.paymentMeans} | ||||
| value={paymentMeans} | value={paymentMeans} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| inputValue={paymentMeans?.label ? paymentMeans?.label : ""} | inputValue={paymentMeans?.label ? paymentMeans?.label : ""} | ||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| setPaymentMeans(newValue); | setPaymentMeans(newValue); | ||||
| @@ -927,11 +965,9 @@ const ApplicationDetailCard = ( | |||||
| renderInput={(params) => ( | renderInput={(params) => ( | ||||
| <TextField {...params} | <TextField {...params} | ||||
| label="" | label="" | ||||
| InputLabelProps={{ shrink: true }} | |||||
| /> | /> | ||||
| )} | )} | ||||
| InputLabelProps={{ | |||||
| shrink: true, | |||||
| }} | |||||
| disableClearable={true} | disableClearable={true} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| @@ -20,12 +20,14 @@ const LoadingComponent = Loadable(lazy(() => import('../../extra-pages/LoadingCo | |||||
| import * as DateUtils from "utils/DateUtils"; | import * as DateUtils from "utils/DateUtils"; | ||||
| import EditNoteIcon from '@mui/icons-material/EditNote'; | import EditNoteIcon from '@mui/icons-material/EditNote'; | ||||
| import { isGrantedAny } from "auth/utils"; | import { isGrantedAny } from "auth/utils"; | ||||
| import { useIntl } from "react-intl"; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const GazetteDetailCard = ( | const GazetteDetailCard = ( | ||||
| { applicationDetailData, | { applicationDetailData, | ||||
| setStatus | setStatus | ||||
| } | } | ||||
| ) => { | ) => { | ||||
| const intl = useIntl(); | |||||
| const [onReady, setOnReady] = useState(false); | const [onReady, setOnReady] = useState(false); | ||||
| const [issueNum, setIssueNum] = useState(""); | const [issueNum, setIssueNum] = useState(""); | ||||
| const [issueDate, setIssueDate] = useState(""); | const [issueDate, setIssueDate] = useState(""); | ||||
| @@ -51,11 +53,11 @@ const GazetteDetailCard = ( | |||||
| setIssueNum(applicationDetailData.gazetteIssueDetail.volume + "/" + applicationDetailData.gazetteIssueDetail.issueYear | setIssueNum(applicationDetailData.gazetteIssueDetail.volume + "/" + applicationDetailData.gazetteIssueDetail.issueYear | ||||
| + " No. " + applicationDetailData.gazetteIssueDetail.issueNo); | + " No. " + applicationDetailData.gazetteIssueDetail.issueNo); | ||||
| setIssueDate(DateUtils.dateFormat(applicationDetailData.gazetteIssueDetail.issueDate, "D MMM YYYY (ddd)")); | setIssueDate(DateUtils.dateFormat(applicationDetailData.gazetteIssueDetail.issueDate, "D MMM YYYY (ddd)")); | ||||
| setGazetteCode(applicationDetailData.data.groupNo) | |||||
| setGazetteCode(applicationDetailData.data.groupNo ?? '') | |||||
| // console.log(applicationDetailData) | // console.log(applicationDetailData) | ||||
| setSysType(applicationDetailData.userData.sysType) | 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){ | if (applicationDetailData.data.mode != null){ | ||||
| setMode(applicationDetailData.data.mode); | setMode(applicationDetailData.data.mode); | ||||
| } | } | ||||
| @@ -71,7 +73,11 @@ const GazetteDetailCard = ( | |||||
| }, [issueNum]); | }, [issueNum]); | ||||
| const groupDetailClick = () => () => { | 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"); | setStatus("genGazetteCode"); | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -275,6 +281,7 @@ const GazetteDetailCard = ( | |||||
| })} | })} | ||||
| value={careOf} | value={careOf} | ||||
| id='careOf' | id='careOf' | ||||
| inputProps={{ 'aria-label': intl.formatMessage({ id: 'careOf' }) }} | |||||
| sx={{ | sx={{ | ||||
| "& .MuiInputBase-input.Mui-disabled": { | "& .MuiInputBase-input.Mui-disabled": { | ||||
| WebkitTextFillColor: "#000000", | WebkitTextFillColor: "#000000", | ||||
| @@ -303,7 +310,9 @@ const GazetteDetailCard = ( | |||||
| maxHeight: { xs: '90vh', s: '70vh', m: '70vh', lg: '60vh' } | 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', }}> | <DialogContent style={{ display: 'flex', }}> | ||||
| <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | ||||
| </DialogContent> | </DialogContent> | ||||
| @@ -1,6 +1,7 @@ | |||||
| import { | import { | ||||
| useEffect, | useEffect, | ||||
| useState | |||||
| useState, | |||||
| useRef | |||||
| } from "react"; | } from "react"; | ||||
| // material-ui | // material-ui | ||||
| @@ -16,8 +17,9 @@ import { | |||||
| FormLabel, | FormLabel, | ||||
| Autocomplete, | Autocomplete, | ||||
| TextField, | TextField, | ||||
| Grid | |||||
| Grid, | |||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| import LoadingButton from '@mui/lab/LoadingButton'; | |||||
| import * as ComboData from "utils/ComboData"; | import * as ComboData from "utils/ComboData"; | ||||
| import { useFormik, FormikProvider } from 'formik'; | import { useFormik, FormikProvider } from 'formik'; | ||||
| @@ -30,8 +32,15 @@ const StatusChangeDialog = (props) => { | |||||
| const [remarks, setRemarks] = useState(""); | const [remarks, setRemarks] = useState(""); | ||||
| const [helperText, setHelperText] = useState(""); | const [helperText, setHelperText] = useState(""); | ||||
| const [comboInputValue, setComboInputValue] = useState({}); | const [comboInputValue, setComboInputValue] = useState({}); | ||||
| const [positiveSubmitting, setPositiveSubmitting] = useState(false); | |||||
| const positiveOnceRef = useRef(false); | |||||
| const groupTitleComboList = ComboData.groupTitle; | const groupTitleComboList = ComboData.groupTitle; | ||||
| const confirmLoading = Boolean(props.confirmLoading) || positiveSubmitting; | |||||
| const gazetteGroupMissing = | |||||
| props.getStatus === "genGazetteCode" && | |||||
| Object.keys(props.selectedGazetteGroup ?? {}).length === 0; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| setComboInputValue({}); | setComboInputValue({}); | ||||
| if (props.getStatus == "genGazetteCode") { | if (props.getStatus == "genGazetteCode") { | ||||
| @@ -58,22 +67,40 @@ const StatusChangeDialog = (props) => { | |||||
| } | } | ||||
| }, [props.getStatus]); | }, [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 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"); | 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" | id="gazetteGroup" | ||||
| options={groupTitleComboList} | options={groupTitleComboList} | ||||
| filterOptions={(options) => options} | 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} | inputValue={comboInputValue.label} | ||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| if (newValue != null && newValue != {}) { | if (newValue != null && newValue != {}) { | ||||
| @@ -156,7 +188,6 @@ const StatusChangeDialog = (props) => { | |||||
| props.setSelectedGazetteGroup(newValue); | props.setSelectedGazetteGroup(newValue); | ||||
| formik.setFieldValue("", "") | formik.setFieldValue("", "") | ||||
| } else { | } else { | ||||
| gazetteGroup | |||||
| props.setSelectedGazetteGroupInputType(""); | props.setSelectedGazetteGroupInputType(""); | ||||
| } | } | ||||
| }} | }} | ||||
| @@ -177,7 +208,10 @@ const StatusChangeDialog = (props) => { | |||||
| return ( | return ( | ||||
| <Dialog | <Dialog | ||||
| open={props.open} | open={props.open} | ||||
| onClose={props.handleClose} | |||||
| onClose={() => { | |||||
| if (confirmLoading) return; | |||||
| props.handleClose(); | |||||
| }} | |||||
| fullWidth={true} | fullWidth={true} | ||||
| maxWidth={'md'} | maxWidth={'md'} | ||||
| > | > | ||||
| @@ -195,7 +229,7 @@ const StatusChangeDialog = (props) => { | |||||
| <FormikProvider value={formik}> | <FormikProvider value={formik}> | ||||
| <form> | <form> | ||||
| <DialogContent> | <DialogContent> | ||||
| <DialogContentText> | |||||
| <DialogContentText component="div"> | |||||
| {content} | {content} | ||||
| </DialogContentText> | </DialogContentText> | ||||
| </DialogContent> | </DialogContent> | ||||
| @@ -203,18 +237,24 @@ const StatusChangeDialog = (props) => { | |||||
| </FormikProvider> | </FormikProvider> | ||||
| <Stack direction="row" justifyContent="space-around"> | <Stack direction="row" justifyContent="space-around"> | ||||
| <DialogActions> | <DialogActions> | ||||
| <Button variant="contained" onClick={props.handleClose} autoFocus > | |||||
| <Button variant="contained" onClick={props.handleClose} autoFocus disabled={confirmLoading}> | |||||
| <Typography variant="h5"> | <Typography variant="h5"> | ||||
| Cancel | Cancel | ||||
| </Typography> | </Typography> | ||||
| </Button> | </Button> | ||||
| </DialogActions> | </DialogActions> | ||||
| <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} | {prositiveBtnText} | ||||
| </Typography> | </Typography> | ||||
| </Button> | |||||
| </LoadingButton> | |||||
| </DialogActions> | </DialogActions> | ||||
| </Stack> | </Stack> | ||||
| </Dialog> | </Dialog> | ||||
| @@ -70,6 +70,7 @@ const PublicNoticeDetail_GLD = () => { | |||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const [getStatus, setStatus] = useState(""); | const [getStatus, setStatus] = useState(""); | ||||
| const [statusWindowAccepted, setStatusWindowAccepted] = useState(false); | const [statusWindowAccepted, setStatusWindowAccepted] = useState(false); | ||||
| const [statusConfirmLoading, setStatusConfirmLoading] = useState(false); | |||||
| const [selectedGazetteGroup, setSelectedGazetteGroup] = useState({}); | const [selectedGazetteGroup, setSelectedGazetteGroup] = useState({}); | ||||
| const [selectedGazetteGroupInputType, setSelectedGazetteGroupInputType] = useState(""); | const [selectedGazetteGroupInputType, setSelectedGazetteGroupInputType] = useState(""); | ||||
| const [getReason, setReason] = useState({}); | const [getReason, setReason] = useState({}); | ||||
| @@ -182,24 +183,30 @@ const PublicNoticeDetail_GLD = () => { | |||||
| }; | }; | ||||
| useEffect(() => { | 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]); | }, [statusWindowAccepted]); | ||||
| @@ -223,12 +230,23 @@ const PublicNoticeDetail_GLD = () => { | |||||
| .catch(error => { | .catch(error => { | ||||
| console.log(error); | console.log(error); | ||||
| return false; | return false; | ||||
| }) | |||||
| .finally(() => { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| }); | }); | ||||
| } else { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| } | } | ||||
| }; | }; | ||||
| const onNotAcceptClick = (reason) => { | const onNotAcceptClick = (reason) => { | ||||
| if (params.id <= 0) return; | |||||
| if (params.id <= 0) { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| return; | |||||
| } | |||||
| HttpUtils.post({ | HttpUtils.post({ | ||||
| url: `${SET_PUBLIC_NOTICE_STATUS_NOT_ACCEPT}/${params.id}`, | url: `${SET_PUBLIC_NOTICE_STATUS_NOT_ACCEPT}/${params.id}`, | ||||
| params: reason, | params: reason, | ||||
| @@ -238,12 +256,24 @@ const PublicNoticeDetail_GLD = () => { | |||||
| // location.reload(); | // location.reload(); | ||||
| loadApplicationDetail() | loadApplicationDetail() | ||||
| notifySaveSuccess() | notifySaveSuccess() | ||||
| }, | |||||
| onFail: () => { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| }, | |||||
| onError: () => { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| const onPublishClick = () => { | const onPublishClick = () => { | ||||
| if (params.id <= 0) return; | |||||
| if (params.id <= 0) { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| return; | |||||
| } | |||||
| HttpUtils.get({ | HttpUtils.get({ | ||||
| url: `${SET_PUBLIC_NOTICE_STATUS_PUBLISH}/${params.id}`, | url: `${SET_PUBLIC_NOTICE_STATUS_PUBLISH}/${params.id}`, | ||||
| onSuccess: function () { | onSuccess: function () { | ||||
| @@ -251,6 +281,18 @@ const PublicNoticeDetail_GLD = () => { | |||||
| handleClose(); | handleClose(); | ||||
| loadApplicationDetail() | loadApplicationDetail() | ||||
| notifySaveSuccess() | 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 => { | .catch(error => { | ||||
| console.log(error); | console.log(error); | ||||
| return false; | return false; | ||||
| }) | |||||
| .finally(() => { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| }); | }); | ||||
| } else { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -289,7 +338,14 @@ const PublicNoticeDetail_GLD = () => { | |||||
| .catch(error => { | .catch(error => { | ||||
| console.log(error); | console.log(error); | ||||
| return false; | return false; | ||||
| }) | |||||
| .finally(() => { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| }); | }); | ||||
| } else { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -307,7 +363,14 @@ const PublicNoticeDetail_GLD = () => { | |||||
| .catch(error => { | .catch(error => { | ||||
| console.log(error); | console.log(error); | ||||
| return false; | return false; | ||||
| }) | |||||
| .finally(() => { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| }); | }); | ||||
| } else { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -322,6 +385,14 @@ const PublicNoticeDetail_GLD = () => { | |||||
| // location.reload(); | // location.reload(); | ||||
| loadApplicationDetail() | loadApplicationDetail() | ||||
| notifySaveSuccess() | notifySaveSuccess() | ||||
| }, | |||||
| onFail: () => { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| }, | |||||
| onError: () => { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| } | } | ||||
| }); | }); | ||||
| // axios.post(`${SET_PUBLIC_NOTICE_STATUS_RESUBMIT}/${params.id}`) | // axios.post(`${SET_PUBLIC_NOTICE_STATUS_RESUBMIT}/${params.id}`) | ||||
| @@ -338,11 +409,18 @@ const PublicNoticeDetail_GLD = () => { | |||||
| // console.log(error); | // console.log(error); | ||||
| // return false; | // return false; | ||||
| // }); | // }); | ||||
| } else { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| } | } | ||||
| }; | }; | ||||
| const onRevokeClick = () => { | const onRevokeClick = () => { | ||||
| if (params.id <= 0) return; | |||||
| if (params.id <= 0) { | |||||
| setStatusConfirmLoading(false); | |||||
| setStatusWindowAccepted(false); | |||||
| return; | |||||
| } | |||||
| HttpUtils.get({ | HttpUtils.get({ | ||||
| url: `${SET_PUBLIC_NOTICE_STATUS_REVOKE}/${params.id}`, | url: `${SET_PUBLIC_NOTICE_STATUS_REVOKE}/${params.id}`, | ||||
| onSuccess: function () { | onSuccess: function () { | ||||
| @@ -350,6 +428,18 @@ const PublicNoticeDetail_GLD = () => { | |||||
| handleClose(); | handleClose(); | ||||
| loadApplicationDetail() | loadApplicationDetail() | ||||
| notifySaveSuccess() | 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} | issueDate={issueDate} | ||||
| issueNum={issueNum} | issueNum={issueNum} | ||||
| gazetteIssue={gazetteIssue} | gazetteIssue={gazetteIssue} | ||||
| confirmLoading={statusConfirmLoading} | |||||
| //combo value | //combo value | ||||
| selectedGazetteGroup={selectedGazetteGroup} | selectedGazetteGroup={selectedGazetteGroup} | ||||
| setSelectedGazetteGroup={setSelectedGazetteGroup} | setSelectedGazetteGroup={setSelectedGazetteGroup} | ||||
| @@ -425,6 +516,9 @@ const PublicNoticeDetail_GLD = () => { | |||||
| setUpdateApplicationObject={setUpdateApplicationObject} | setUpdateApplicationObject={setUpdateApplicationObject} | ||||
| isEditMode={isEditMode} | isEditMode={isEditMode} | ||||
| setiIsSave={setiIsSave} | setiIsSave={setiIsSave} | ||||
| statusDialogOpen={open} | |||||
| statusDialogKind={getStatus} | |||||
| statusActionLoading={statusConfirmLoading} | |||||
| // isNewRecord={isNewRecord} | // isNewRecord={isNewRecord} | ||||
| /> | /> | ||||
| } | } | ||||
| @@ -17,6 +17,10 @@ export default function SubmittedTab({ appId, setCount }) { | |||||
| const theme = useTheme(); | const theme = useTheme(); | ||||
| const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | ||||
| const renderHeaderWithAria = (params) => ( | |||||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||||
| ); | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| field: 'actions', | field: 'actions', | ||||
| @@ -24,6 +28,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| cellClassName: 'actions', | cellClassName: 'actions', | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return clickableLink('/paymentPage/details/' + params.row.id, params.row.transNo); | return clickableLink('/paymentPage/details/' + params.row.id, params.row.transNo); | ||||
| }, | }, | ||||
| @@ -34,6 +39,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||||
| headerName: 'Trans. Date', | headerName: 'Trans. Date', | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return DateUtils.datetimeStr(params.value); | return DateUtils.datetimeStr(params.value); | ||||
| } | } | ||||
| @@ -44,6 +50,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||||
| headerName: 'Status', | headerName: 'Status', | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return PaymentStatus.getStatus_Eng(params); | return PaymentStatus.getStatus_Eng(params); | ||||
| } | } | ||||
| @@ -53,6 +60,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||||
| field: 'payAmount', | field: 'payAmount', | ||||
| headerName: 'Amount', | headerName: 'Amount', | ||||
| width: 150, | width: 150, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return (params?.value) ? "$ " + FormatUtils.currencyFormat(params?.value) : ""; | 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 = [ | const columns = [ | ||||
| { | { | ||||
| @@ -36,6 +40,7 @@ export default function ProofTab({appId, setCount}) { | |||||
| width: isMdOrLg ? 'auto' : 200, | width: isMdOrLg ? 'auto' : 200, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| cellClassName: 'actions', | cellClassName: 'actions', | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return clickableLink('/proof/reply/' + params.row.id, params.row.refNo); | return clickableLink('/proof/reply/' + params.row.id, params.row.refNo); | ||||
| }, | }, | ||||
| @@ -45,6 +50,7 @@ export default function ProofTab({appId, setCount}) { | |||||
| headerName: 'Status', | headerName: 'Status', | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return ProofStatus.getStatus_Eng(params); | return ProofStatus.getStatus_Eng(params); | ||||
| }, | }, | ||||
| @@ -54,7 +60,7 @@ export default function ProofTab({appId, setCount}) { | |||||
| headerName: 'Proof Issue Date', | headerName: 'Proof Issue Date', | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return DateUtils.datetimeStr(params?.value); | return DateUtils.datetimeStr(params?.value); | ||||
| } | } | ||||
| @@ -64,6 +70,7 @@ export default function ProofTab({appId, setCount}) { | |||||
| headerName: 'Confirmed/Return Date', | headerName: 'Confirmed/Return Date', | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return params?.value?DateUtils.datetimeStr(params?.value):""; | return params?.value?DateUtils.datetimeStr(params?.value):""; | ||||
| } | } | ||||
| @@ -73,15 +80,17 @@ export default function ProofTab({appId, setCount}) { | |||||
| headerName: 'Fee', | headerName: 'Fee', | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return (params?.value)?"$ "+FormatUtils.currencyFormat(params?.value):""; | return (params?.value)?"$ "+FormatUtils.currencyFormat(params?.value):""; | ||||
| } | } | ||||
| }, | }, | ||||
| { | { | ||||
| field: 'actions', | |||||
| type: 'actions', | type: 'actions', | ||||
| headerName: 'Proof Slip', | headerName: 'Proof Slip', | ||||
| width: 100, | width: 100, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| cellClassName: 'actions', | cellClassName: 'actions', | ||||
| getActions: (params) => { | getActions: (params) => { | ||||
| if(params.row.action == null) return[]; | if(params.row.action == null) return[]; | ||||
| @@ -13,6 +13,10 @@ export default function StatusHistoryTab({appId, setCount}) { | |||||
| const theme = useTheme(); | const theme = useTheme(); | ||||
| const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | ||||
| const renderHeaderWithAria = (params) => ( | |||||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||||
| ); | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| id: 'created', | id: 'created', | ||||
| @@ -20,6 +24,7 @@ export default function StatusHistoryTab({appId, setCount}) { | |||||
| headerName: 'Date', | headerName: 'Date', | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return DateUtils.datetimeStr(params?.value); | return DateUtils.datetimeStr(params?.value); | ||||
| } | } | ||||
| @@ -31,6 +36,7 @@ export default function StatusHistoryTab({appId, setCount}) { | |||||
| headerName: 'Changed By', | headerName: 'Changed By', | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| }, | }, | ||||
| { | { | ||||
| id: 'status', | id: 'status', | ||||
| @@ -38,8 +44,9 @@ export default function StatusHistoryTab({appId, setCount}) { | |||||
| headerName: 'Status', | headerName: 'Status', | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return [StatusUtils.getStatusEng(params)] | |||||
| return StatusUtils.getStatusEng(params); | |||||
| }, | }, | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -9,6 +9,7 @@ import { | |||||
| import { TabPanel, TabContext, TabList } from '@mui/lab'; | import { TabPanel, TabContext, TabList } from '@mui/lab'; | ||||
| import {useState, useEffect, lazy} from "react"; | import {useState, useEffect, lazy} from "react"; | ||||
| import { useIntl } from 'react-intl'; | |||||
| import Loadable from 'components/Loadable'; | import Loadable from 'components/Loadable'; | ||||
| const LoadingComponent = Loadable(lazy(() => import('../../../extra-pages/LoadingComponent'))); | const LoadingComponent = Loadable(lazy(() => import('../../../extra-pages/LoadingComponent'))); | ||||
| @@ -20,7 +21,7 @@ const StatusHistoryTab = Loadable(lazy(() => import('./StatusHistoryTab'))); | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const PublicNotice = ({ appId, proofCount, paymentCount, statusHistoryCount, setProofCount, setPaymentCount, setStatusHistoryCount }) => { | const PublicNotice = ({ appId, proofCount, paymentCount, statusHistoryCount, setProofCount, setPaymentCount, setStatusHistoryCount }) => { | ||||
| const intl = useIntl(); | |||||
| const [onReady, setOnReady] = useState(false); | const [onReady, setOnReady] = useState(false); | ||||
| const [selectedTab, setSelectedTab] = useState("1"); | const [selectedTab, setSelectedTab] = useState("1"); | ||||
| @@ -41,10 +42,10 @@ const PublicNotice = ({ appId, proofCount, paymentCount, statusHistoryCount, set | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <TabContext value={selectedTab}> | <TabContext value={selectedTab}> | ||||
| <Box sx={{ borderBottom: 1, borderColor: 'divider', overflowX: 'auto' }}> | <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> | </TabList> | ||||
| </Box> | </Box> | ||||
| <TabPanel value="1" sx={{ p: 0 }}> | <TabPanel value="1" sx={{ p: 0 }}> | ||||
| @@ -222,7 +222,6 @@ const ApplicationDetailCard = ( | |||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| onClick={cancelledClick()} | onClick={cancelledClick()} | ||||
| color="edit" | |||||
| disabled={currentApplicationDetailData.status == "rejected" | disabled={currentApplicationDetailData.status == "rejected" | ||||
| || currentApplicationDetailData.status == "cancelled" | || currentApplicationDetailData.status == "cancelled" | ||||
| || currentApplicationDetailData.status == "withdrawn" | || currentApplicationDetailData.status == "withdrawn" | ||||
| @@ -239,6 +238,11 @@ const ApplicationDetailCard = ( | |||||
| title={intl.formatMessage({ id: 'cancelApp' })} | title={intl.formatMessage({ id: 'cancelApp' })} | ||||
| startIcon={<CloseIcon />} | startIcon={<CloseIcon />} | ||||
| aria-label={intl.formatMessage({ id: 'cancelApp' })} | aria-label={intl.formatMessage({ id: 'cancelApp' })} | ||||
| sx={{ | |||||
| backgroundColor: '#0C489E', | |||||
| color: '#FFFFFF', | |||||
| '&:hover': { backgroundColor: '#093A7A' }, | |||||
| }} | |||||
| > | > | ||||
| <FormattedMessage id="cancelApp" /> | <FormattedMessage id="cancelApp" /> | ||||
| </Button> | </Button> | ||||
| @@ -651,20 +655,24 @@ const ApplicationDetailCard = ( | |||||
| <Grid container direction="row" alignItems="center" justifyContent="flex-start"> | <Grid container direction="row" alignItems="center" justifyContent="flex-start"> | ||||
| <Grid item xs={12} sm={12} md={12} lg={12} sx={{ wordBreak: 'break-word', }}> | <Grid item xs={12} sm={12} md={12} lg={12} sx={{ wordBreak: 'break-word', }}> | ||||
| <Typography | <Typography | ||||
| fullWidth | |||||
| id='fileName' | id='fileName' | ||||
| variant="pnspsFormParagraph" | variant="pnspsFormParagraph" | ||||
| sx={{ width: '100%' }} | |||||
| > | > | ||||
| {fileDetail?.filename} | {fileDetail?.filename} | ||||
| </Typography> | </Typography> | ||||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | <ThemeProvider theme={PNSPS_BUTTON_THEME}> | ||||
| <Button | <Button | ||||
| sx={{ ml: 3 }} | |||||
| sx={{ | |||||
| ml: 3, | |||||
| backgroundColor: '#0C489E', | |||||
| color: '#FFFFFF', | |||||
| '&:hover': { backgroundColor: '#093A7A' }, | |||||
| }} | |||||
| variant="contained" | variant="contained" | ||||
| onClick={onDownloadClick()} | onClick={onDownloadClick()} | ||||
| aria-label={intl.formatMessage({ id: 'download' })} | aria-label={intl.formatMessage({ id: 'download' })} | ||||
| title={intl.formatMessage({ id: 'download' })} | title={intl.formatMessage({ id: 'download' })} | ||||
| color="save" | |||||
| disabled={!fileDetail?.filename||onDownload} | disabled={!fileDetail?.filename||onDownload} | ||||
| startIcon={<DownloadIcon sx={{ alignItems: "center" }} />} | startIcon={<DownloadIcon sx={{ alignItems: "center" }} />} | ||||
| > | > | ||||
| @@ -690,7 +698,9 @@ const ApplicationDetailCard = ( | |||||
| <FormControl variant="outlined" sx={{ width: '100%' }} disabled> | <FormControl variant="outlined" sx={{ width: '100%' }} disabled> | ||||
| <OutlinedInput | <OutlinedInput | ||||
| size="small" | size="small" | ||||
| id="careOf" | |||||
| value={currentApplicationDetailData.careOf} | value={currentApplicationDetailData.careOf} | ||||
| inputProps={{ 'aria-label': intl.formatMessage({ id: 'careOf' }) }} | |||||
| sx={{ | sx={{ | ||||
| "& .MuiInputBase-input.Mui-disabled": { | "& .MuiInputBase-input.Mui-disabled": { | ||||
| WebkitTextFillColor: "#000000", | WebkitTextFillColor: "#000000", | ||||
| @@ -1,10 +1,12 @@ | |||||
| import { | import { | ||||
| useEffect, | useEffect, | ||||
| useState | |||||
| useState, | |||||
| useRef | |||||
| } from "react"; | } from "react"; | ||||
| // material-ui | // material-ui | ||||
| import { | import { | ||||
| Box, | |||||
| Button, | Button, | ||||
| // Link, | // Link, | ||||
| Stack, | Stack, | ||||
| @@ -14,7 +16,7 @@ import { | |||||
| DialogContent, | DialogContent, | ||||
| DialogContentText, | DialogContentText, | ||||
| DialogTitle, | DialogTitle, | ||||
| FormLabel, | |||||
| CircularProgress, | |||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| import { useFormik,FormikProvider } from 'formik'; | import { useFormik,FormikProvider } from 'formik'; | ||||
| import * as yup from 'yup'; | import * as yup from 'yup'; | ||||
| @@ -26,6 +28,7 @@ import {useIntl} from "react-intl"; | |||||
| const StatusChangeDialog = (props) => { | const StatusChangeDialog = (props) => { | ||||
| const [status, setStatus] = useState(""); | const [status, setStatus] = useState(""); | ||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const confirmOnceRef = useRef(false); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| // console.log(Object.keys(!props.selectedGazetteGroup).length) | // console.log(Object.keys(!props.selectedGazetteGroup).length) | ||||
| @@ -33,9 +36,25 @@ const StatusChangeDialog = (props) => { | |||||
| setStatus(intl.formatMessage({id: 'cancel'})) | setStatus(intl.formatMessage({id: 'cancel'})) | ||||
| } | } | ||||
| }, [props.getStatus]); | }, [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 = () => () =>{ | const acceptedHandle = () => () =>{ | ||||
| // console.log(selectedGazetteGroup) | |||||
| if (props.confirmLoading) return; | |||||
| if (confirmOnceRef.current) return; | |||||
| confirmOnceRef.current = true; | |||||
| props.setStatusWindowAccepted(true) | props.setStatusWindowAccepted(true) | ||||
| }; | }; | ||||
| @@ -60,19 +79,20 @@ const StatusChangeDialog = (props) => { | |||||
| fullWidth={true} | fullWidth={true} | ||||
| maxWidth={'xs'} | maxWidth={'xs'} | ||||
| > | > | ||||
| <DialogTitle > | |||||
| <Typography variant="h4"> | |||||
| <DialogTitle component="div"> | |||||
| <Typography variant="h4" component="h2"> | |||||
| {status} {intl.formatMessage({id: 'publicNotice'})} | {status} {intl.formatMessage({id: 'publicNotice'})} | ||||
| </Typography> | </Typography> | ||||
| </DialogTitle> | </DialogTitle> | ||||
| <FormikProvider value={formik}> | <FormikProvider value={formik}> | ||||
| <form> | <form> | ||||
| <DialogContent> | <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> | </DialogContentText> | ||||
| </DialogContent> | </DialogContent> | ||||
| </form> | </form> | ||||
| @@ -85,6 +105,7 @@ const StatusChangeDialog = (props) => { | |||||
| onClick={props.handleClose} | onClick={props.handleClose} | ||||
| autoFocus | autoFocus | ||||
| color="cancel" | color="cancel" | ||||
| disabled={props.confirmLoading} | |||||
| > | > | ||||
| <FormattedMessage id="cancel"/> | <FormattedMessage id="cancel"/> | ||||
| </Button> | </Button> | ||||
| @@ -95,6 +116,8 @@ const StatusChangeDialog = (props) => { | |||||
| color="save" | color="save" | ||||
| onClick={acceptedHandle()} | onClick={acceptedHandle()} | ||||
| autoFocus | autoFocus | ||||
| disabled={props.confirmLoading} | |||||
| startIcon={props.confirmLoading ? <CircularProgress color="inherit" size={20} /> : null} | |||||
| > | > | ||||
| <FormattedMessage id="confirm"/> | <FormattedMessage id="confirm"/> | ||||
| </Button> | </Button> | ||||
| @@ -1,6 +1,7 @@ | |||||
| import React, { | import React, { | ||||
| useEffect, | useEffect, | ||||
| useState, | useState, | ||||
| useRef, | |||||
| lazy | lazy | ||||
| } from "react"; | } from "react"; | ||||
| @@ -32,10 +33,12 @@ import { useNavigate } from "react-router-dom"; | |||||
| import ForwardIcon from '@mui/icons-material/Forward'; | import ForwardIcon from '@mui/icons-material/Forward'; | ||||
| import { notifyActionSuccess } from "utils/CommonFunction"; | import { notifyActionSuccess } from "utils/CommonFunction"; | ||||
| import { FormattedMessage, useIntl } from "react-intl"; | import { FormattedMessage, useIntl } from "react-intl"; | ||||
| import usePageTitle from "components/usePageTitle"; | |||||
| // ==============================|| Body - DEFAULT ||============================== // | // ==============================|| Body - DEFAULT ||============================== // | ||||
| const DashboardDefault = () => { | const DashboardDefault = () => { | ||||
| usePageTitle("myPublicNotice"); | |||||
| const params = useParams(); | const params = useParams(); | ||||
| const [applicationDetailData, setApplicationDetailData] = useState({}); | const [applicationDetailData, setApplicationDetailData] = useState({}); | ||||
| const [appNo, setAapNo] = useState(""); | const [appNo, setAapNo] = useState(""); | ||||
| @@ -47,6 +50,8 @@ const DashboardDefault = () => { | |||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const [getStatus, setStatus] = useState(""); | const [getStatus, setStatus] = useState(""); | ||||
| const [statusWindowAccepted, setStatusWindowAccepted] = useState(false); | const [statusWindowAccepted, setStatusWindowAccepted] = useState(false); | ||||
| const [cancelLoading, setCancelLoading] = useState(false); | |||||
| const cancellingRef = useRef(false); | |||||
| const [proofCount, setProofCount] = useState(0); | const [proofCount, setProofCount] = useState(0); | ||||
| const [paymentCount, setPaymentCount] = useState(0); | const [paymentCount, setPaymentCount] = useState(0); | ||||
| @@ -135,7 +140,10 @@ const DashboardDefault = () => { | |||||
| }, [getStatus]); | }, [getStatus]); | ||||
| const onCancelledClick = () => { | const onCancelledClick = () => { | ||||
| if (cancellingRef.current) return; | |||||
| if (params.id > 0) { | if (params.id > 0) { | ||||
| cancellingRef.current = true; | |||||
| setCancelLoading(true); | |||||
| axios.get(`${SET_PUBLIC_NOTICE_STATUS_CANCELLED}/${params.id}`) | axios.get(`${SET_PUBLIC_NOTICE_STATUS_CANCELLED}/${params.id}`) | ||||
| .then((response) => { | .then((response) => { | ||||
| if (response.status === 204) { | if (response.status === 204) { | ||||
| @@ -149,17 +157,27 @@ const DashboardDefault = () => { | |||||
| .catch(error => { | .catch(error => { | ||||
| console.log(error); | console.log(error); | ||||
| return false; | return false; | ||||
| }) | |||||
| .finally(() => { | |||||
| cancellingRef.current = false; | |||||
| setCancelLoading(false); | |||||
| }); | }); | ||||
| } | } | ||||
| }; | }; | ||||
| return ( | return ( | ||||
| <Grid container sx={{ backgroundColor: '#ffffff' }} direction="column"> | <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}> | <Grid item xs={12}> | ||||
| <div style={BackgroundHead}> | <div style={BackgroundHead}> | ||||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | <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" /> | <FormattedMessage id="myPublicNotice" /> | ||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| @@ -177,7 +195,7 @@ const DashboardDefault = () => { | |||||
| > | > | ||||
| <ForwardIcon style={{ height: 30, width: 50, transform: "rotate(180deg)" }} /> | <ForwardIcon style={{ height: 30, width: 50, transform: "rotate(180deg)" }} /> | ||||
| </Button> | </Button> | ||||
| <Typography ml={3} mt={3} variant="h4">{title}</Typography> | |||||
| <Typography component="h2" ml={3} mt={3} variant="h4">{title}</Typography> | |||||
| </Stack> | </Stack> | ||||
| </Grid> | </Grid> | ||||
| <Grid item width={{ md: "60%", xs: "90%" }}> | <Grid item width={{ md: "60%", xs: "90%" }}> | ||||
| @@ -21,6 +21,10 @@ export default function SubmittedTab({ appId, setCount }) { | |||||
| const { locale } = intl; | const { locale } = intl; | ||||
| const renderHeaderWithAria = (params) => ( | |||||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||||
| ); | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| field: 'actions', | field: 'actions', | ||||
| @@ -28,6 +32,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| cellClassName: 'actions', | cellClassName: 'actions', | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return clickableLink('/paymentPage/details/' + params.row.id, params.row.transNo); | 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'}), | headerName: intl.formatMessage({id: 'payDate'}), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return DateUtils.datetimeStr(params.value); | return DateUtils.datetimeStr(params.value); | ||||
| } | } | ||||
| @@ -48,6 +54,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||||
| headerName: intl.formatMessage({id: 'payStatus'}), | headerName: intl.formatMessage({id: 'payStatus'}), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return locale === 'en' ? PaymentStatus.getStatus_Eng(params):PaymentStatus.getStatus_Cht(params); | return locale === 'en' ? PaymentStatus.getStatus_Eng(params):PaymentStatus.getStatus_Cht(params); | ||||
| } | } | ||||
| @@ -57,6 +64,7 @@ export default function SubmittedTab({ appId, setCount }) { | |||||
| field: 'payAmount', | field: 'payAmount', | ||||
| headerName: intl.formatMessage({id: 'fee'}), | headerName: intl.formatMessage({id: 'fee'}), | ||||
| width: 150, | width: 150, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return (params?.value) ? "$ " + FormatUtils.currencyFormat(params?.value) : ""; | 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 isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | ||||
| const { locale } = intl; | const { locale } = intl; | ||||
| const renderHeaderWithAria = (params) => ( | |||||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||||
| ); | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| field: 'actions', | |||||
| field: 'refNo', | |||||
| headerName: intl.formatMessage({id: 'proofId'}), | headerName: intl.formatMessage({id: 'proofId'}), | ||||
| width: 200, | width: 200, | ||||
| cellClassName: 'actions', | cellClassName: 'actions', | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return clickableLink('/proof/reply/' + params.row.id, params.row.refNo); | return clickableLink('/proof/reply/' + params.row.id, params.row.refNo); | ||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| id: 'actions', | |||||
| field: 'status', | |||||
| headerName: intl.formatMessage({id: 'status'}), | headerName: intl.formatMessage({id: 'status'}), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return locale === 'en' ? ProofStatus.getStatus_Eng(params) : ProofStatus.getStatus_Cht(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'}), | headerName: intl.formatMessage({id: 'proofDate'}), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return DateUtils.datetimeStr(params?.value); | return DateUtils.datetimeStr(params?.value); | ||||
| } | } | ||||
| @@ -56,6 +61,7 @@ export default function ProofTab({appId, setCount}) { | |||||
| headerName: intl.formatMessage({id: 'replyDate'}), | headerName: intl.formatMessage({id: 'replyDate'}), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return params?.value?DateUtils.datetimeStr(params?.value):""; | return params?.value?DateUtils.datetimeStr(params?.value):""; | ||||
| } | } | ||||
| @@ -66,6 +72,7 @@ export default function ProofTab({appId, setCount}) { | |||||
| headerName: intl.formatMessage({id: 'fee'}), | headerName: intl.formatMessage({id: 'fee'}), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return (params?.value)?"$ "+FormatUtils.currencyFormat(params?.value):""; | return (params?.value)?"$ "+FormatUtils.currencyFormat(params?.value):""; | ||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| // material-ui | // material-ui | ||||
| import * as React from 'react'; | import * as React from 'react'; | ||||
| import { useTheme, useMediaQuery } from '@mui/material'; | |||||
| import {FiDataGrid} from "components/FiDataGrid"; | import {FiDataGrid} from "components/FiDataGrid"; | ||||
| import * as DateUtils from "utils/DateUtils" | import * as DateUtils from "utils/DateUtils" | ||||
| import * as StatusUtils from "utils/statusUtils/PublicNoteStatusUtils"; | import * as StatusUtils from "utils/statusUtils/PublicNoteStatusUtils"; | ||||
| @@ -9,7 +10,7 @@ import {GET_PUBLIC_NOTICE_APPLY_DETAIL_STATUS_HISTORY } from "utils/ApiPathConst | |||||
| // ==============================|| EVENT TABLE ||============================== // | // ==============================|| EVENT TABLE ||============================== // | ||||
| export default function StatusHistoryTab({appId, setCount}) { | export default function StatusHistoryTab({appId, setCount}) { | ||||
| const { useState, useEffect } = React; | |||||
| const theme = useTheme(); | const theme = useTheme(); | ||||
| const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | ||||
| @@ -21,6 +22,10 @@ export default function StatusHistoryTab({appId, setCount}) { | |||||
| set_appId(appId); | set_appId(appId); | ||||
| }, []); | }, []); | ||||
| const renderHeaderWithAria = (params) => ( | |||||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||||
| ); | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| id: 'created', | id: 'created', | ||||
| @@ -28,6 +33,7 @@ export default function StatusHistoryTab({appId, setCount}) { | |||||
| headerName: 'Date', | headerName: 'Date', | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return DateUtils.datetimeStr(params?.value); | return DateUtils.datetimeStr(params?.value); | ||||
| } | } | ||||
| @@ -39,6 +45,7 @@ export default function StatusHistoryTab({appId, setCount}) { | |||||
| headerName: 'Changed By', | headerName: 'Changed By', | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| }, | }, | ||||
| { | { | ||||
| id: 'status', | id: 'status', | ||||
| @@ -46,8 +53,9 @@ export default function StatusHistoryTab({appId, setCount}) { | |||||
| headerName: 'Status', | headerName: 'Status', | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | 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}> | <Grid item xs={12}> | ||||
| <TabContext value={selectedTab}> | <TabContext value={selectedTab}> | ||||
| <Box sx={{ borderBottom: 1, borderColor: 'divider' }}> | <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 | <Tab | ||||
| aria-label={intl.formatMessage({ id: 'proofRecord' })} | aria-label={intl.formatMessage({ id: 'proofRecord' })} | ||||
| label={ | label={ | ||||
| @@ -28,6 +28,9 @@ export default function BaseGrid({setCount, url}) { | |||||
| navigate('/publicNotice/'+ params.id); | navigate('/publicNotice/'+ params.id); | ||||
| }; | }; | ||||
| const renderHeaderWithAria = (params) => ( | |||||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||||
| ); | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| @@ -36,8 +39,9 @@ export default function BaseGrid({setCount, url}) { | |||||
| headerName: intl.formatMessage({id: 'applicationId'}), | headerName: intl.formatMessage({id: 'applicationId'}), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | 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'}), | headerName: intl.formatMessage({id: 'submitDate'}), | ||||
| width: isMdOrLg ? 'auto' : 300, | width: isMdOrLg ? 'auto' : 300, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter:(params)=>{ | valueGetter:(params)=>{ | ||||
| return DateUtils.datetimeStr(params?.value); | 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'}), | headerName: isORGLoggedIn()? intl.formatMessage({id: 'gazetteCount2_1'}) : intl.formatMessage({id: 'myRemarks'}), | ||||
| width: isMdOrLg ? 'auto' : 400, | width: isMdOrLg ? 'auto' : 400, | ||||
| flex: isMdOrLg ? 3 : undefined, | flex: isMdOrLg ? 3 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => ( | renderCell: (params) => ( | ||||
| isORGLoggedIn()? | isORGLoggedIn()? | ||||
| isDummyLoggedIn()? | isDummyLoggedIn()? | ||||
| @@ -88,14 +94,16 @@ export default function BaseGrid({setCount, url}) { | |||||
| headerName: intl.formatMessage({id: 'status'}), | headerName: intl.formatMessage({id: 'status'}), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return [getStatusIntl(params,intl)] | |||||
| return getStatusIntl(params, intl); | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| field: 'actions', | field: 'actions', | ||||
| headerName: '', | headerName: '', | ||||
| width: 160, | width: 160, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| cellClassName: 'actions', | cellClassName: 'actions', | ||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return <Button aria-label={intl.formatMessage({id: 'viewDetail'})} onClick={handleDetailClick(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); | navigate('/publicNotice/' + params.id); | ||||
| }; | }; | ||||
| const renderHeaderWithAria = (params) => ( | |||||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||||
| ); | |||||
| const handlePaymentBtn = () => { | const handlePaymentBtn = () => { | ||||
| let appIdList = []; | let appIdList = []; | ||||
| let paymentCheckList = []; | let paymentCheckList = []; | ||||
| @@ -175,6 +179,7 @@ export default function SubmittedTab({ setCount, url }) { | |||||
| headerName: intl.formatMessage({ id: 'applicationId' }), | headerName: intl.formatMessage({ id: 'applicationId' }), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| }, | }, | ||||
| { | { | ||||
| id: 'created', | id: 'created', | ||||
| @@ -182,6 +187,7 @@ export default function SubmittedTab({ setCount, url }) { | |||||
| headerName: intl.formatMessage({ id: 'submitDate' }), | headerName: intl.formatMessage({ id: 'submitDate' }), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return DateUtils.datetimeStr(params.value); | 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' }), | headerName: isORGLoggedIn() ? intl.formatMessage({ id: 'gazetteCount2_1' }) : intl.formatMessage({ id: 'myRemarks' }), | ||||
| width: isMdOrLg ? 'auto' : 400, | width: isMdOrLg ? 'auto' : 400, | ||||
| flex: isMdOrLg ? 3 : undefined, | flex: isMdOrLg ? 3 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => ( | renderCell: (params) => ( | ||||
| isORGLoggedIn() ? | isORGLoggedIn() ? | ||||
| isDummyLoggedIn()? | isDummyLoggedIn()? | ||||
| @@ -222,6 +229,7 @@ export default function SubmittedTab({ setCount, url }) { | |||||
| headerName: intl.formatMessage({ id: 'price' }), | headerName: intl.formatMessage({ id: 'price' }), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return FormatUtils.currencyFormat(params.row.fee) | return FormatUtils.currencyFormat(params.row.fee) | ||||
| }, | }, | ||||
| @@ -230,6 +238,7 @@ export default function SubmittedTab({ setCount, url }) { | |||||
| id: 'paymentMethodAndDeadLine', | id: 'paymentMethodAndDeadLine', | ||||
| field: 'paymentMethodAndDeadLine', | field: 'paymentMethodAndDeadLine', | ||||
| headerName: intl.formatMessage({ id: 'paymentMethodAndDeadLine' }), | headerName: intl.formatMessage({ id: 'paymentMethodAndDeadLine' }), | ||||
| renderHeader: renderHeaderWithAria, | |||||
| width: isMdOrLg ? 'auto' : 250, | width: isMdOrLg ? 'auto' : 250, | ||||
| flex: isMdOrLg ? 2 : undefined, | flex: isMdOrLg ? 2 : undefined, | ||||
| renderCell: (params) => ( | renderCell: (params) => ( | ||||
| @@ -287,8 +296,9 @@ export default function SubmittedTab({ setCount, url }) { | |||||
| headerName: intl.formatMessage({ id: 'status' }), | headerName: intl.formatMessage({ id: 'status' }), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return [StatusUtils.getStatusIntl(params, intl)] | |||||
| return StatusUtils.getStatusIntl(params, intl); | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -296,6 +306,7 @@ export default function SubmittedTab({ setCount, url }) { | |||||
| type: 'actions', | type: 'actions', | ||||
| headerName: '', | headerName: '', | ||||
| width: 150, | width: 150, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| cellClassName: 'actions', | cellClassName: 'actions', | ||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return <Button aria-label={intl.formatMessage({ id: 'viewDetail' })} onClick={handleDetailClick(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) | selectedRowItems.includes(row.id) | ||||
| ); | ); | ||||
| for (var i = 0; i < datas?.length; i++) { | 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; | 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; | return content; | ||||
| } | } | ||||
| @@ -391,6 +406,11 @@ export default function SubmittedTab({ setCount, url }) { | |||||
| id="careOfCombo" | id="careOfCombo" | ||||
| value={selectedCareOf === null ? null : selectedCareOf} | value={selectedCareOf === null ? null : selectedCareOf} | ||||
| options={careOfComboList} | options={careOfComboList} | ||||
| getOptionLabel={(option) => { | |||||
| if (option == null) return ""; | |||||
| if (typeof option === "string") return option; | |||||
| return option.label != null ? String(option.label) : ""; | |||||
| }} | |||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| // console.log(newValue) | // console.log(newValue) | ||||
| setSelectedCareOf(newValue); | setSelectedCareOf(newValue); | ||||
| @@ -400,7 +420,11 @@ export default function SubmittedTab({ setCount, url }) { | |||||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | ||||
| '& .MuiOutlinedInput-root': { height: 40 } | '& .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> | ||||
| </Grid> : null | </Grid> : null | ||||
| @@ -427,7 +451,6 @@ export default function SubmittedTab({ setCount, url }) { | |||||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | <ThemeProvider theme={PNSPS_BUTTON_THEME}> | ||||
| <Button | <Button | ||||
| color="create" | |||||
| variant="contained" | variant="contained" | ||||
| aria-label={intl.formatMessage({ id: 'payOnlineBtn' })} | aria-label={intl.formatMessage({ id: 'payOnlineBtn' })} | ||||
| onClick={() => { handlePaymentBtn() }} | onClick={() => { handlePaymentBtn() }} | ||||
| @@ -138,7 +138,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | <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']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="dateFrom" | id="dateFrom" | ||||
| @@ -166,7 +166,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | <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']}> | <DemoItem components={['DatePicker']}> | ||||
| <DatePicker | <DatePicker | ||||
| id="dateTo" | id="dateTo" | ||||
| @@ -234,7 +234,11 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||||
| } | } | ||||
| value={status} | value={status} | ||||
| // inputValue={status?.labelCht} | // 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) => { | onChange={(event, newValue) => { | ||||
| if(newValue ==null){ | if(newValue ==null){ | ||||
| setStatus(localStorage.getItem('userData').creditor?ComboData.publicNoticeStatic_Creditor[0]:ComboData.publicNoticeStatic[0]); | 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={{ | // InputLabelProps={{ | ||||
| // shrink: true | // shrink: true | ||||
| // }} | // }} | ||||
| @@ -276,7 +284,11 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => | |||||
| } | } | ||||
| value={status} | value={status} | ||||
| // inputValue={status?.labelCht} | // 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) => { | onChange={(event, newValue) => { | ||||
| console.log(newValue) | console.log(newValue) | ||||
| const findAllIndex = newValue.findIndex((ele) => { | const findAllIndex = newValue.findIndex((ele) => { | ||||
| @@ -35,6 +35,10 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| navigate('/publicNotice/' + params.id); | navigate('/publicNotice/' + params.id); | ||||
| }; | }; | ||||
| const renderHeaderWithAria = (params) => ( | |||||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||||
| ); | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| id: 'appNo', | id: 'appNo', | ||||
| @@ -42,8 +46,9 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| headerName: intl.formatMessage({ id: 'applicationId' }), | headerName: intl.formatMessage({ id: 'applicationId' }), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | 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' }), | headerName: intl.formatMessage({ id: 'submitDate' }), | ||||
| width: isMdOrLg ? 'auto' : 160, | width: isMdOrLg ? 'auto' : 160, | ||||
| flex: isMdOrLg ? 1 : undefined, | flex: isMdOrLg ? 1 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return DateUtils.datetimeStr(params?.value); | 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' }), | headerName: isORGLoggedIn() ? intl.formatMessage({ id: 'gazetteCount2_1' }) : intl.formatMessage({ id: 'myRemarks' }), | ||||
| width: isMdOrLg ? 'auto' : 400, | width: isMdOrLg ? 'auto' : 400, | ||||
| flex: isMdOrLg ? 3 : undefined, | flex: isMdOrLg ? 3 : undefined, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => ( | renderCell: (params) => ( | ||||
| isORGLoggedIn() ? | isORGLoggedIn() ? | ||||
| isDummyLoggedIn()? | isDummyLoggedIn()? | ||||
| @@ -117,8 +124,9 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| field: 'status', | field: 'status', | ||||
| headerName: intl.formatMessage({ id: 'status' }), | headerName: intl.formatMessage({ id: 'status' }), | ||||
| width: 200, | width: 200, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return [StatusUtils.getStatusIntl(params, intl)] | |||||
| return StatusUtils.getStatusIntl(params, intl); | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -126,6 +134,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| type: 'actions', | type: 'actions', | ||||
| headerName: '', | headerName: '', | ||||
| width: 150, | width: 150, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| cellClassName: 'actions', | cellClassName: 'actions', | ||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return <Button onClick={handleDetailClick(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 { PNSPS_LONG_BUTTON_THEME } from "../../../themes/buttonConst"; | ||||
| import { ThemeProvider } from "@emotion/react"; | import { ThemeProvider } from "@emotion/react"; | ||||
| import { FormattedMessage, useIntl } from "react-intl"; | import { FormattedMessage, useIntl } from "react-intl"; | ||||
| import usePageTitle from 'components/usePageTitle'; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const PublicNotice = () => { | const PublicNotice = () => { | ||||
| usePageTitle("myPublicNotice"); | |||||
| const [submittedCount, setSubmittedCount] = useState(0); | const [submittedCount, setSubmittedCount] = useState(0); | ||||
| const [pendingPaymentCount, setPendingPaymentCount] = useState(0); | const [pendingPaymentCount, setPendingPaymentCount] = useState(0); | ||||
| const [pendingPublishCount, setPendingPublishCount] = useState(0); | const [pendingPublishCount, setPendingPublishCount] = useState(0); | ||||
| @@ -113,7 +114,7 @@ const PublicNotice = () => { | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <div style={BackgroundHead}> | <div style={BackgroundHead}> | ||||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | <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" /> | <FormattedMessage id="myPublicNotice" /> | ||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| @@ -123,7 +124,11 @@ const PublicNotice = () => { | |||||
| <Stack direction="row" justifyContent="flex-end" alignItems="center"> | <Stack direction="row" justifyContent="flex-end" alignItems="center"> | ||||
| <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | ||||
| <Box sx={{ mr: { md: "47px" } }}> | <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" /> | <FormattedMessage id="applyPublicNotice" /> | ||||
| </Button> | </Button> | ||||
| </Box> | </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 }}> | <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}> | <TabContext value={selectedTab}> | ||||
| <Box sx={{ borderBottom: 1, borderColor: 'divider', overflowX: 'auto', overflowY: 'auto' }}> | <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: '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: 'pendingPublish' })} label={intl.formatMessage({ id: 'pendingPublish' }) + " (" + pendingPublishCount + ")"} value="3" /> | ||||
| <Tab aria-label={intl.formatMessage({ id: 'pendingPayment' })} label={intl.formatMessage({ id: 'pendingPayment' }) + " (" + pendingPaymentCount + ")"} value="4" /> | <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 }}> | <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}> | <TabContext value={selectedTab}> | ||||
| <Box sx={{ borderBottom: 1, borderColor: 'divider' }}> | <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: '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: 'pendingPayment' })} label={intl.formatMessage({ id: 'pendingPayment' }) + " (" + pendingPaymentCount + ")"} value="3" /> | ||||
| <Tab aria-label={intl.formatMessage({ id: 'pendingPublish' })} label={intl.formatMessage({ id: 'pendingPublish' }) + " (" + pendingPublishCount + ")"} value="4" /> | <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 | return groupNo | ||||
| } | } | ||||
| const renderHeaderWithAria = (params) => ( | |||||
| <span aria-label={params.colDef.headerName}>{params.colDef.headerName}</span> | |||||
| ); | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| field: 'actions', | field: 'actions', | ||||
| @@ -60,6 +64,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| sortable: false, | sortable: false, | ||||
| width: 150, | width: 150, | ||||
| cellClassName: 'actions', | cellClassName: 'actions', | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return clickableLink('/application/' + params.id, params.row.appNo); | return clickableLink('/application/' + params.id, params.row.appNo); | ||||
| }, | }, | ||||
| @@ -70,8 +75,9 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| headerName: 'Mode', | headerName: 'Mode', | ||||
| sortable: false, | sortable: false, | ||||
| width: 100, | width: 100, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return [StatusUtils.getModeEng(params)] | |||||
| return StatusUtils.getModeEng(params); | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -80,8 +86,9 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| headerName: 'Status', | headerName: 'Status', | ||||
| sortable: false, | sortable: false, | ||||
| width: 240, | width: 240, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return [StatusUtils.getStatusEng(params)] | |||||
| return StatusUtils.getStatusEng(params); | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -90,8 +97,9 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| headerName: 'With Proof', | headerName: 'With Proof', | ||||
| sortable: false, | sortable: false, | ||||
| width: 120, | width: 120, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | 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, | sortable: false, | ||||
| flex: 1, | flex: 1, | ||||
| minWidth: 200, | minWidth: 200, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return DateUtils.datetimeStr(params?.value); | return DateUtils.datetimeStr(params?.value); | ||||
| } | } | ||||
| @@ -112,6 +121,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| sortable: false, | sortable: false, | ||||
| minWidth: 250, | minWidth: 250, | ||||
| flex: 2, | flex: 2, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| let company = params.row.enCompanyName != null ? params.row.enCompanyName : params.row.chCompanyName; | let company = params.row.enCompanyName != null ? params.row.enCompanyName : params.row.chCompanyName; | ||||
| company = company != null ? company : ""; | company = company != null ? company : ""; | ||||
| @@ -131,6 +141,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| sortable: false, | sortable: false, | ||||
| flex: 1.5, | flex: 1.5, | ||||
| minWidth: 350, | minWidth: 350, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => ( | renderCell: (params) => ( | ||||
| <div> | <div> | ||||
| {genIssueNo(params)} | {genIssueNo(params)} | ||||
| @@ -279,7 +279,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||||
| setSelectedStatus(newValue); | setSelectedStatus(newValue); | ||||
| } | } | ||||
| }} | }} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| sx={{ | sx={{ | ||||
| '& .MuiInputBase-root': { alignItems: 'center' }, | '& .MuiInputBase-root': { alignItems: 'center' }, | ||||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | ||||
| @@ -317,7 +317,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||||
| setSelectedLabelsString(selectedLabelsString); | setSelectedLabelsString(selectedLabelsString); | ||||
| } | } | ||||
| }} | }} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| renderInput={(params) => ( | renderInput={(params) => ( | ||||
| <TextField | <TextField | ||||
| {...params} | {...params} | ||||
| @@ -419,7 +419,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||||
| options={ComboData.groupTitle} | options={ComboData.groupTitle} | ||||
| value={groupSelected} | value={groupSelected} | ||||
| inputValue={(groupSelected?.label) ? groupSelected?.label : ""} | inputValue={(groupSelected?.label) ? groupSelected?.label : ""} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| onChange={(event, newValue) => { | onChange={(event, newValue) => { | ||||
| setGroupSelected(newValue); | setGroupSelected(newValue); | ||||
| }} | }} | ||||
| @@ -470,7 +470,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | ||||
| '& .MuiOutlinedInput-root': { height: 40 } | '& .MuiOutlinedInput-root': { height: 40 } | ||||
| }} | }} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| renderInput={(params) => ( | renderInput={(params) => ( | ||||
| <TextField | <TextField | ||||
| {...params} | {...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 = [ | const columns = [ | ||||
| { | { | ||||
| field: 'actions', | field: 'actions', | ||||
| @@ -122,6 +126,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| sortable: false, | sortable: false, | ||||
| width: 150, | width: 150, | ||||
| cellClassName: 'actions', | cellClassName: 'actions', | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return clickableLink('/application/' + params.id, params.row.appNo); | return clickableLink('/application/' + params.id, params.row.appNo); | ||||
| }, | }, | ||||
| @@ -132,6 +137,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| headerName: 'Customer Name', | headerName: 'Customer Name', | ||||
| flex: 1, | flex: 1, | ||||
| minWidth: 50, | minWidth: 50, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| let company = params.row.enCompanyName != null ? params.row.enCompanyName : params.row.chCompanyName; | let company = params.row.enCompanyName != null ? params.row.enCompanyName : params.row.chCompanyName; | ||||
| company = company != null ? company : ""; | company = company != null ? company : ""; | ||||
| @@ -150,6 +156,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| sortable: false, | sortable: false, | ||||
| flex: 1.5, | flex: 1.5, | ||||
| minWidth: 350, | minWidth: 350, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => ( | renderCell: (params) => ( | ||||
| <div> | <div> | ||||
| {genIssueNo(params)} | {genIssueNo(params)} | ||||
| @@ -165,6 +172,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| sortable: false, | sortable: false, | ||||
| minWidth: 250, | minWidth: 250, | ||||
| flex: 2, | flex: 2, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| let paymentMethod = params.row.paymentMethod!=null?intl.formatMessage({ id: utils.getPaymentMethod(params.row.paymentMethod)}):"" | let paymentMethod = params.row.paymentMethod!=null?intl.formatMessage({ id: utils.getPaymentMethod(params.row.paymentMethod)}):"" | ||||
| return (<> | return (<> | ||||
| @@ -178,6 +186,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| headerName: 'Amount($)', | headerName: 'Amount($)', | ||||
| flex: 1, | flex: 1, | ||||
| minWidth: 100, | minWidth: 100, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| valueGetter: (params) => { | valueGetter: (params) => { | ||||
| return FormatUtils.currencyFormat(params?.value); | return FormatUtils.currencyFormat(params?.value); | ||||
| } | } | ||||
| @@ -188,6 +197,7 @@ export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnRea | |||||
| headerName: 'Remarks', | headerName: 'Remarks', | ||||
| flex: 2, | flex: 2, | ||||
| minWidth: 200, | minWidth: 200, | ||||
| renderHeader: renderHeaderWithAria, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| const handleBlur = (event) => { | const handleBlur = (event) => { | ||||
| const newValue = event.target.value; | const newValue = event.target.value; | ||||
| @@ -266,7 +266,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||||
| setSelectedStatus(newValue); | setSelectedStatus(newValue); | ||||
| } | } | ||||
| }} | }} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| sx={{ | sx={{ | ||||
| '& .MuiInputBase-root': { alignItems: 'center' }, | '& .MuiInputBase-root': { alignItems: 'center' }, | ||||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | ||||
| @@ -304,7 +304,7 @@ const SearchPublicNoticeForm = ({ applySearch, orgComboData, searchCriteria, iss | |||||
| setSelectedLabelsString(selectedLabelsString); | setSelectedLabelsString(selectedLabelsString); | ||||
| } | } | ||||
| }} | }} | ||||
| getOptionLabel={(option) => option.label} | |||||
| getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")} | |||||
| renderInput={(params) => ( | renderInput={(params) => ( | ||||
| <TextField | <TextField | ||||
| {...params} | {...params} | ||||