| @@ -0,0 +1,15 @@ | |||
| { | |||
| // Use IntelliSense to learn about possible attributes. | |||
| // Hover to view descriptions of existing attributes. | |||
| // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | |||
| "version": "0.2.0", | |||
| "configurations": [ | |||
| { | |||
| "type": "chrome", | |||
| "request": "launch", | |||
| "name": "Launch Chrome against localhost", | |||
| "url": "http://localhost:3000", | |||
| "webRoot": "${workspaceFolder}/src" | |||
| } | |||
| ] | |||
| } | |||
| @@ -22,7 +22,7 @@ | |||
| "@testing-library/user-event": "^14.4.3", | |||
| "@types/react-input-mask": "^3.0.2", | |||
| "apexcharts": "^3.35.5", | |||
| "axios": "^1.4.0", | |||
| "axios": "^1.12.2", | |||
| "date-fns": "^3.0.6", | |||
| "dayjs": "^1.11.9", | |||
| "formik": "^2.2.9", | |||
| @@ -94,6 +94,8 @@ | |||
| "@babel/eslint-parser": "^7.21.3", | |||
| "@babel/plugin-proposal-private-property-in-object": "^7.21.11", | |||
| "@mui/x-date-pickers": "^6.18.0", | |||
| "ajv": "^6.12.6", | |||
| "ajv-keywords": "^3.5.2", | |||
| "eslint": "^8.38.0", | |||
| "eslint-config-prettier": "^8.8.0", | |||
| "eslint-config-react-app": "^7.0.1", | |||
| @@ -104,5 +106,14 @@ | |||
| "eslint-plugin-react": "^7.32.2", | |||
| "eslint-plugin-react-hooks": "^4.6.0", | |||
| "prettier": "^2.8.7" | |||
| }, | |||
| "overrides": { | |||
| "schema-utils@3": { | |||
| "ajv": "6.12.6", | |||
| "ajv-keywords": "3.5.2" | |||
| }, | |||
| "@apideck/better-ajv-errors": { | |||
| "ajv": "^8.12.0" | |||
| } | |||
| } | |||
| } | |||
| @@ -2,53 +2,24 @@ | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="utf-8" /> | |||
| <link rel="icon" href="%PUBLIC_URL%/favicon.svg" /> | |||
| <link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png"> | |||
| <link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png"> | |||
| <link rel="mask-icon" href="%PUBLIC_URL%/safari-pinned-tab.svg" color="#5bbad5"> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |||
| <meta name="theme-color" content="#1f1f1f" /> | |||
| <meta name="title" content="PNSPS" /> | |||
| <meta name="theme-color" content="#ffffff" /> | |||
| <meta name="msapplication-TileColor" content="#da532c"> | |||
| <meta name="title" content="PNSPS - Public Notice Submission and Payment System" /> | |||
| <meta | |||
| name="description" | |||
| content="Mantis is a free, super flexible and customizable react redux dashboard template built using MUI React components with open source MIT license." | |||
| content="Public Notice Submission and Payment System - Government Logistics Department" | |||
| /> | |||
| <meta | |||
| name="keywords" | |||
| content="react dashboard, react admin, react redux dashboard, ant design template, saas admin, free react dashboard" | |||
| content="PNSPS, GLD, react redux dashboard, Gazette, Public Notice" | |||
| /> | |||
| <meta name="author" content="CodedThemes" /> | |||
| <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> | |||
| <!-- Open Graph / Facebook --> | |||
| <meta property="og:locale" content="en_US" /> | |||
| <meta property="og:type" content="website" /> | |||
| <meta property="og:url" content="https://mantisdashboard.io/" /> | |||
| <meta property="og:site_name" content="mantisdashboard.io" /> | |||
| <meta property="article:publisher" content="https://www.facebook.com/codedthemes" /> | |||
| <meta property="og:title" content="PNSPS" /> | |||
| <meta | |||
| property="og:description" | |||
| content="Mantis is a free, super flexible and customizable react redux dashboard template built using MUI React components with open source MIT license." | |||
| /> | |||
| <meta property="og:image" content="https://mantisdashboard.io/adv-banner-images/og-social.png" /> | |||
| <!-- Twitter --> | |||
| <meta property="twitter:card" content="summary_large_image" /> | |||
| <meta property="twitter:url" content="https://mantisdashboard.io" /> | |||
| <meta property="twitter:title" content="PNSPS" /> | |||
| <meta | |||
| property="twitter:description" | |||
| content="Mantis is a free, super flexible and customizable react redux dashboard template built using MUI React components with open source MIT license." | |||
| /> | |||
| <meta property="twitter:image" content="https://mantisdashboard.io/adv-banner-images/og-social.png" /> | |||
| <meta name="twitter:creator" content="@codedthemes" /> | |||
| <!-- | |||
| Notice the use of %PUBLIC_URL% in the tags above. | |||
| It will be replaced with the URL of the `public` folder during the build. | |||
| Only files inside the `public` folder can be referenced from the HTML. | |||
| Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will | |||
| work correctly both with client-side routing and a non-root public URL. | |||
| Learn how to configure a non-root public URL by running `npm run build`. | |||
| --> | |||
| <title>PNSPS</title> | |||
| <meta name="author" content="Government Logistics Department" /> | |||
| <link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" /> | |||
| <title>PNSPS - Public Notice Submission and Payment System</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" | |||
| @@ -0,0 +1,54 @@ | |||
| <?xml version="1.0" standalone="no"?> | |||
| <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" | |||
| "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> | |||
| <svg version="1.0" xmlns="http://www.w3.org/2000/svg" | |||
| width="680.000000pt" height="680.000000pt" viewBox="0 0 680.000000 680.000000" | |||
| preserveAspectRatio="xMidYMid meet"> | |||
| <metadata> | |||
| Created by potrace 1.14, written by Peter Selinger 2001-2017 | |||
| </metadata> | |||
| <g transform="translate(0.000000,680.000000) scale(0.100000,-0.100000)" | |||
| fill="#000000" stroke="none"> | |||
| <path d="M2270 4555 l0 -1345 912 2 913 3 0 340 0 340 -195 2 c-107 1 -322 2 | |||
| -476 3 l-282 0 -1 928 c0 510 -1 960 -1 1000 l0 72 -435 0 -435 0 0 -1345z"/> | |||
| <path d="M4627 5044 c-1 -1 -45 -5 -97 -8 -85 -5 -126 -9 -210 -20 -56 -8 | |||
| -159 -28 -215 -43 -33 -8 -68 -17 -78 -19 -47 -9 -204 -72 -307 -124 -170 -85 | |||
| -180 -91 -181 -120 0 -14 -1 -151 -2 -306 l-2 -280 280 0 280 1 0 112 0 112 | |||
| 49 22 c86 37 195 69 286 84 19 3 46 7 60 10 21 4 175 17 205 17 24 1 212 -13 | |||
| 235 -16 14 -2 43 -7 65 -10 48 -7 203 -48 264 -71 76 -28 203 -95 291 -153 99 | |||
| -66 288 -250 345 -337 22 -33 43 -62 46 -65 9 -7 98 -173 100 -185 0 -5 9 -26 | |||
| 19 -45 44 -86 102 -303 117 -435 13 -114 5 -428 -12 -454 -2 -4 -6 -26 -9 -49 | |||
| -9 -79 -78 -288 -132 -401 -113 -237 -256 -417 -449 -561 -64 -47 -103 -70 | |||
| -255 -144 -14 -7 -45 -18 -70 -25 -25 -8 -52 -15 -60 -18 -8 -2 -16 -4 -17 -4 | |||
| -2 0 -16 -4 -33 -9 -87 -25 -340 -34 -472 -17 -32 4 -84 10 -115 13 -63 6 | |||
| -116 13 -148 19 -11 2 -41 6 -67 10 l-47 8 -1 -260 c0 -200 2 -262 12 -270 7 | |||
| -6 36 -16 63 -22 28 -7 57 -14 65 -17 186 -45 213 -49 420 -48 221 0 307 11 | |||
| 517 64 100 25 340 113 353 129 3 3 31 19 63 35 32 16 97 55 145 87 300 198 | |||
| 551 508 697 859 35 86 102 291 110 336 3 20 7 39 9 43 2 3 7 22 10 41 10 60 | |||
| 16 94 20 115 28 130 28 570 1 700 -3 11 -7 36 -10 55 -19 133 -79 336 -137 | |||
| 470 -79 181 -210 395 -312 508 -185 207 -395 366 -631 478 -103 49 -316 126 | |||
| -381 138 -16 2 -40 7 -54 11 -24 5 -89 18 -155 30 -46 8 -155 19 -197 19 -21 | |||
| 1 -38 3 -38 6 0 4 -229 8 -233 4z"/> | |||
| <path d="M1958 5035 c-2 -1 -30 -5 -63 -8 -114 -11 -231 -31 -320 -54 -33 -8 | |||
| -67 -17 -75 -18 -8 -2 -15 -4 -15 -5 0 -2 -14 -5 -53 -14 -12 -3 -27 -8 -32 | |||
| -11 -5 -3 -18 -8 -27 -10 -122 -25 -529 -238 -603 -315 -3 -3 -30 -25 -60 -50 | |||
| -184 -148 -367 -375 -487 -605 -77 -148 -171 -436 -188 -575 -4 -30 -9 -58 | |||
| -11 -61 -17 -29 -26 -431 -11 -564 9 -86 20 -157 31 -205 3 -14 8 -36 11 -50 | |||
| 8 -44 42 -167 49 -179 4 -6 9 -21 11 -33 15 -78 144 -348 212 -445 13 -17 23 | |||
| -36 23 -41 0 -5 9 -17 20 -27 11 -10 20 -22 20 -26 0 -10 0 -10 96 -124 178 | |||
| -213 410 -391 664 -512 181 -86 425 -159 595 -179 22 -2 54 -7 70 -10 17 -3 | |||
| 109 -7 205 -9 200 -5 254 3 420 56 3 1 19 6 35 12 17 6 30 10 30 8 0 -1 45 21 | |||
| 100 49 185 96 342 213 510 384 104 105 185 194 185 202 0 3 9 15 21 27 31 33 | |||
| 126 159 179 237 8 12 20 29 27 38 8 13 11 -62 10 -317 -1 -210 2 -339 8 -347 | |||
| 8 -10 285 -111 355 -129 17 -5 97 -33 120 -42 23 -9 33 -12 55 -15 20 -3 20 5 | |||
| 20 947 l0 950 -910 0 -910 -1 -3 -277 -2 -277 287 -1 c159 0 372 -1 475 -2 | |||
| 204 -1 198 1 146 -58 -13 -15 -36 -44 -53 -65 -133 -169 -383 -441 -491 -534 | |||
| -282 -242 -401 -288 -684 -266 -78 6 -97 8 -167 20 -24 4 -44 7 -45 6 -2 -1 | |||
| -16 3 -31 9 -15 6 -42 13 -60 16 -76 13 -364 140 -397 175 -3 3 -30 23 -60 45 | |||
| -30 22 -60 44 -66 50 -82 75 -126 120 -159 161 -21 27 -42 51 -45 54 -3 3 -27 | |||
| 38 -53 78 -79 118 -176 337 -201 452 -8 35 -25 120 -31 150 -26 135 -26 428 0 | |||
| 550 3 14 8 36 10 50 24 152 133 412 226 544 16 22 37 52 47 65 45 65 157 182 | |||
| 237 250 167 141 481 282 685 307 19 2 46 7 59 10 14 3 57 7 95 10 39 3 76 6 | |||
| 81 8 6 2 10 102 10 272 l-1 269 -62 1 c-34 1 -63 0 -64 -1z"/> | |||
| </g> | |||
| </svg> | |||
| @@ -2,24 +2,23 @@ | |||
| import Routes from 'routes'; | |||
| import ThemeCustomization from 'themes'; | |||
| import ScrollTop from 'components/ScrollTop'; | |||
| import {ToastContainer} from "react-toastify"; | |||
| import { PNSPS_THEME } from './themes/themeConst'; | |||
| import { ThemeProvider } from '@emotion/react'; | |||
| import { ToastContainer } from 'react-toastify'; | |||
| import 'react-toastify/dist/ReactToastify.css'; | |||
| import {PNSPS_THEME} from "./themes/themeConst"; | |||
| import {ThemeProvider} from "@emotion/react"; | |||
| //import {isUserLoggedIn} from 'utils/Utils'; | |||
| //import {DefaultRoute} from 'routes/index' | |||
| // ==============================|| APP - THEME, ROUTER, LOCAL ||============================== // | |||
| const App = () => ( | |||
| <ThemeCustomization> | |||
| <ThemeProvider theme={PNSPS_THEME}> | |||
| <ScrollTop> | |||
| <Routes> | |||
| </Routes> | |||
| </ScrollTop> | |||
| </ThemeProvider> | |||
| <ToastContainer/> | |||
| </ThemeCustomization> | |||
| <ThemeCustomization> | |||
| <ThemeProvider theme={PNSPS_THEME}> | |||
| <ScrollTop> | |||
| <Routes /> | |||
| </ScrollTop> | |||
| </ThemeProvider> | |||
| <ToastContainer /> | |||
| </ThemeCustomization> | |||
| ); | |||
| export default App; | |||
| @@ -1,162 +1,378 @@ | |||
| /* assets/style/navbarStyles.css */ | |||
| /* ===== FIX typo ===== */ | |||
| #nav{ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| background-color: white; | |||
| /* padding: 20px 80px; */ | |||
| box-shadow: 0 5px 15px rgba(0,0 0,0,0.06); | |||
| position:fixed; | |||
| top: 0px; | |||
| width: 100%; | |||
| z-index: 9999; | |||
| border-bottom: 3px solid #0C489E; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| background-color: white; | |||
| box-shadow: 0 5px 15px rgba(0,0,0,0.06); /* <-- fixed */ | |||
| position: fixed; | |||
| top: 0px; | |||
| width: 100%; | |||
| z-index: 9999; | |||
| border-bottom: 3px solid #0C489E; | |||
| min-height: 77px; | |||
| } | |||
| #navbar > div > li { | |||
| height: 77px; /* one single source of truth */ | |||
| display: flex; | |||
| align-items: stretch; | |||
| padding: 0; /* IMPORTANT: stop li padding making different widths */ | |||
| margin: 0; | |||
| } | |||
| #navbar div li{ | |||
| padding: 0; /* override your earlier padding: 0 1vw */ | |||
| } | |||
| /* Make dropdown button identical to anchor */ | |||
| #navbar > div > li > button.navTrigger { | |||
| background: transparent; | |||
| border: 0; | |||
| padding: 0; /* remove default button padding */ | |||
| margin: 0; | |||
| font: inherit; /* inherit font from li */ | |||
| line-height: 1; /* normalize */ | |||
| height: auto; | |||
| display: inline-flex; | |||
| align-items: center; | |||
| cursor: pointer; | |||
| } | |||
| /* Make both anchor and button same vertical box */ | |||
| #navbar > div > li > a, | |||
| #navbar > div > li > button.navTrigger { | |||
| height: 100% !important; /* override the old 72px */ | |||
| padding: 0 24px !important; /* bigger hit area */ | |||
| display: flex; | |||
| align-items: center; | |||
| box-sizing: border-box; | |||
| } | |||
| /* Submenu triggers: same font as app (Noto Sans), bold */ | |||
| #navbar > div > li > button.navTrigger, | |||
| #navbar > div > li > button.navTrigger .MuiTypography-root { | |||
| font-weight: 600 !important; | |||
| font-family: "Noto Sans HK", "Noto Sans SC", sans-serif !important; | |||
| font-size: 1.1rem !important; | |||
| } | |||
| /* Keep links bold */ | |||
| #navbar > div > li > a, | |||
| #navbar > div > li > a .MuiTypography-root { | |||
| font-weight: 600; | |||
| } | |||
| #navbar div{ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| #navbar div li{ | |||
| list-style: none; | |||
| padding: 0 1vw; | |||
| position: relative; | |||
| list-style: none; | |||
| padding: 0 1vw; | |||
| position: relative; | |||
| } | |||
| /* keep your <a> styling */ | |||
| #navbar div li a{ | |||
| text-decoration: none; | |||
| font-size: 1.2rem; | |||
| font-weight: 600; | |||
| /* font-family: 微軟正黑體; */ | |||
| color: black; | |||
| transition: 0.3s ease-in-out; | |||
| text-decoration: none; | |||
| font-size: 1.2rem; | |||
| font-weight: 600; | |||
| color: black; | |||
| transition: 0.3s ease-in-out; | |||
| } | |||
| #navbar div li a span{ | |||
| font-size: 1vw !important; | |||
| /* Dropdown trigger buttons: Noto Sans, bold */ | |||
| #navbar div li button.navTrigger{ | |||
| background: transparent; | |||
| border: 0; | |||
| padding: 0; | |||
| cursor: pointer; | |||
| text-decoration: none; | |||
| font-size: 1.1rem; | |||
| font-weight: 600; | |||
| font-family: "Noto Sans HK", "Noto Sans SC", sans-serif; | |||
| color: black; | |||
| transition: 0.3s ease-in-out; | |||
| display: inline-flex; | |||
| align-items: center; | |||
| gap: 0.25rem; | |||
| } | |||
| #navbar div li a span, | |||
| #navbar div li a svg{ | |||
| font-size: 1vw !important; | |||
| font-size: 1vw !important; | |||
| } | |||
| #navbar div li a:hover{ | |||
| color: #0C489E; | |||
| #navbar div li a:hover, | |||
| #navbar div li button.navTrigger:hover{ | |||
| color: #0C489E; | |||
| } | |||
| #navbar div li a:hover::after, | |||
| #navbar div li a:focus::after{ | |||
| content: ""; | |||
| width: 80%; | |||
| height: 3px; | |||
| background:#0C489E; | |||
| position: absolute; | |||
| bottom: -5px; | |||
| left: 20px; | |||
| } | |||
| #navbar div li ul { | |||
| #navbar div li a:focus-visible::after, | |||
| #navbar div li button.navTrigger:hover::after, | |||
| #navbar div li button.navTrigger:focus-visible::after{ | |||
| content: ""; | |||
| width: 80%; | |||
| height: 3px; | |||
| background:#0C489E; | |||
| position: absolute; | |||
| bottom: -5px; | |||
| left: 20px; | |||
| } | |||
| /* submenu base */ | |||
| #navbar div li ul{ | |||
| background: white; | |||
| visibility: hidden; | |||
| opacity: 0; | |||
| min-width: 18rem; | |||
| position: absolute; | |||
| /* transition: all 0.5s ease; */ | |||
| left: 0; | |||
| display: none; | |||
| padding-left: 0px; | |||
| padding-bottom: 7px; | |||
| /* border: 1px solid #0C489E; */ | |||
| background-clip: padding-box; | |||
| border: 1px solid rgba(0,0,0,.15); | |||
| border-radius: 0.25rem; | |||
| } | |||
| #navbar div li:hover > ul{ | |||
| visibility: visible; | |||
| opacity: 1; | |||
| display: block | |||
| } | |||
| /* #navbar div li:focus-within > ul, | |||
| #navbar div li ul:hover, | |||
| #navbar div li ul:focus { | |||
| /* hover open */ | |||
| #navbar div li:hover > ul{ | |||
| visibility: visible; | |||
| opacity: 1; | |||
| display: block; | |||
| } | |||
| /* keyboard open */ | |||
| #navbar div li:focus-within > ul{ | |||
| visibility: visible; | |||
| opacity: 1; | |||
| display: block | |||
| } */ | |||
| display: block; | |||
| } | |||
| /* Navbar focus ring */ | |||
| #navbar a:focus, | |||
| #navbar button.navTrigger:focus{ | |||
| outline: none; | |||
| } | |||
| #navbar a:focus-visible, | |||
| #navbar button.navTrigger:focus-visible{ | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: 2px; | |||
| border-radius: 10px; | |||
| } | |||
| /* Top-level links (Dashboard, Application, Proof, Logout, etc.) */ | |||
| #navbar a.dashboard, | |||
| #navbar a.application, | |||
| #navbar a.proof, | |||
| #navbar a.myDocumet, | |||
| #navbar a.documentRecord, | |||
| #navbar a.manageOrgUser, | |||
| #navbar a.manageUser, | |||
| #navbar a.systemSetting, | |||
| #navbar a.report, | |||
| #navbar a.payment, | |||
| #navbar a.user, | |||
| #navbar a.logout { | |||
| font-size: 1.1rem !important; | |||
| font-weight: 600 !important; | |||
| } | |||
| #navbar a.dashboard .MuiTypography-root, | |||
| #navbar a.application .MuiTypography-root, | |||
| #navbar a.proof .MuiTypography-root, | |||
| #navbar a.myDocumet .MuiTypography-root, | |||
| #navbar a.documentRecord .MuiTypography-root, | |||
| #navbar a.manageOrgUser .MuiTypography-root, | |||
| #navbar a.manageUser .MuiTypography-root, | |||
| #navbar a.systemSetting .MuiTypography-root, | |||
| #navbar a.report .MuiTypography-root, | |||
| #navbar a.payment .MuiTypography-root, | |||
| #navbar a.user .MuiTypography-root, | |||
| #navbar a.logout .MuiTypography-root { | |||
| font-size: 1.1rem !important; | |||
| font-weight: 600 !important; | |||
| } | |||
| /* Submenu triggers: Noto Sans, 1.1rem, bold */ | |||
| #navbar button.navTrigger.paymentTop, | |||
| #navbar button.navTrigger.client, | |||
| #navbar button.navTrigger.setting, | |||
| #navbar button.navTrigger.paymentRecord, | |||
| #navbar button.navTrigger.userSetting, | |||
| #navbar button.navTrigger.paymentTop .MuiTypography-root, | |||
| #navbar button.navTrigger.client .MuiTypography-root, | |||
| #navbar button.navTrigger.setting .MuiTypography-root, | |||
| #navbar button.navTrigger.paymentRecord .MuiTypography-root, | |||
| #navbar button.navTrigger.userSetting .MuiTypography-root { | |||
| font-size: 1.1rem !important; | |||
| font-weight: 600 !important; | |||
| font-family: "Noto Sans HK", "Noto Sans SC", sans-serif !important; | |||
| } | |||
| /* Make them bigger + bold */ | |||
| #navbar a.login, | |||
| #navbar a.register, | |||
| #navbar a.login .MuiTypography-root, | |||
| #navbar a.register .MuiTypography-root { | |||
| font-size: 1.1rem !important; | |||
| font-weight: 600 !important; | |||
| } | |||
| /* ===== your existing sidebar (unchanged) ===== */ | |||
| #systemTitle{ | |||
| text-decoration: none; | |||
| font-size: 1.3rem; | |||
| font-weight: 600; | |||
| color: #0C489E; | |||
| transition: 0.3s ease-in-out; | |||
| /* font-family: 微軟正黑體; */ | |||
| text-align: center; | |||
| text-decoration: none; | |||
| font-size: 1.1rem; | |||
| font-weight: 600; | |||
| color: #0C489E; | |||
| transition: 0.3s ease-in-out; | |||
| text-align: center; | |||
| } | |||
| #mobileTitle{ | |||
| text-decoration: none; | |||
| font-size: 1.2rem; | |||
| font-weight: 600; | |||
| color: #0C489E; | |||
| transition: 0.3s ease-in-out; | |||
| /* font-family: 微軟正黑體; */ | |||
| text-align: center; | |||
| text-decoration: none; | |||
| font-size: 1.1rem; | |||
| font-weight: 600; | |||
| color: #0C489E; | |||
| transition: 0.3s ease-in-out; | |||
| text-align: center; | |||
| } | |||
| #sidebar{ | |||
| font-size: 1.3rem; | |||
| font-weight: 600; | |||
| /* font-family: 微軟正黑體; */ | |||
| font-size: 1.1rem; | |||
| font-weight: 600; | |||
| } | |||
| #sidebartop{ | |||
| align-items: center; | |||
| justify-content: start; | |||
| padding: 0; | |||
| display: flex; | |||
| width: 100%; | |||
| align-items: center; | |||
| justify-content: start; | |||
| padding: 0; | |||
| display: flex; | |||
| width: 100%; | |||
| } | |||
| #logoutContent{ | |||
| align-items: center; | |||
| justify-content: center; | |||
| padding: 0; | |||
| display: flex; | |||
| width: 100%; | |||
| align-items: center; | |||
| justify-content: center; | |||
| padding: 0; | |||
| display: flex; | |||
| width: 100%; | |||
| } | |||
| #sidebarbottom{ | |||
| align-items: center; | |||
| justify-content: center; | |||
| padding: 0; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| padding: 0; | |||
| display: flex; | |||
| } | |||
| #sidebar li{ | |||
| list-style: none; | |||
| padding: 0 20px; | |||
| position: relative; | |||
| text-align: left; | |||
| list-style: none; | |||
| padding: 0 20px; | |||
| position: relative; | |||
| text-align: left; | |||
| } | |||
| #sidebar li a{ | |||
| text-decoration: none; | |||
| font-size: 1.3rem; | |||
| font-weight: 600; | |||
| /* font-family: 微軟正黑體; */ | |||
| color: black; | |||
| transition: 0.3s ease-in-out; | |||
| text-decoration: none; | |||
| font-size: 1.1rem; | |||
| font-weight: 600; | |||
| color: black; | |||
| transition: 0.3s ease-in-out; | |||
| } | |||
| #sidebar li a:hover{ | |||
| color: #0C489E; | |||
| } | |||
| #sidebar div li ul { | |||
| background: white; | |||
| visibility: hidden; | |||
| opacity: 0; | |||
| min-width: 16rem; | |||
| position: relative; | |||
| /* transition: all 0.5s ease; */ | |||
| left: 0; | |||
| display: none; | |||
| padding-left: 0px; | |||
| /* border: 1px solid #0C489E; */ | |||
| } | |||
| #sidebar div li:hover > ul, | |||
| #sidebar div li:focus-within > ul, | |||
| #sidebar div li ul:hover, | |||
| #sidebar div li ul:focus { | |||
| visibility: visible; | |||
| opacity: 1; | |||
| display: block | |||
| } | |||
| color: #0C489E; | |||
| } | |||
| #sidebar div li ul{ | |||
| background: white; | |||
| visibility: hidden; | |||
| opacity: 0; | |||
| min-width: 16rem; | |||
| position: relative; | |||
| left: 0; | |||
| display: none; | |||
| padding-left: 0px; | |||
| } | |||
| #sidebar div li:hover > ul, | |||
| #sidebar div li:focus-within > ul, | |||
| #sidebar div li ul:hover, | |||
| #sidebar div li ul:focus{ | |||
| visibility: visible; | |||
| opacity: 1; | |||
| display: block; | |||
| } | |||
| /* ===== Mobile Drawer / Sidebar menu styling ===== */ | |||
| #sidebar ul { | |||
| width: 100%; | |||
| padding: 0; | |||
| margin: 0; | |||
| } | |||
| #sidebar li{ | |||
| width: 100%; | |||
| padding: 0; /* remove the old 0 20px */ | |||
| margin: 0; | |||
| } | |||
| /* Make BOTH links and dropdown buttons look the same */ | |||
| #sidebar li > a, | |||
| #sidebar li > button.navTrigger{ | |||
| width: 100%; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; /* text left, arrow right */ | |||
| gap: 12px; | |||
| background: transparent; | |||
| border: 0; /* kill grey border */ | |||
| box-shadow: none; | |||
| outline: none; | |||
| padding: 14px 20px; | |||
| text-align: left; | |||
| font-size: 1.3rem; | |||
| font-weight: 600; | |||
| color: black; | |||
| cursor: pointer; | |||
| } | |||
| /* Hover / focus */ | |||
| #sidebar li > a:hover, | |||
| #sidebar li > button.navTrigger:hover{ | |||
| color: #0C489E; | |||
| } | |||
| #sidebar li > a:focus-visible, | |||
| #sidebar li > button.navTrigger:focus-visible{ | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: 2px; | |||
| border-radius: 10px; | |||
| } | |||
| /* Submenu (indent + full width) */ | |||
| #sidebar li ul{ | |||
| width: 100%; | |||
| padding: 6px 0 6px 0; | |||
| margin: 0; | |||
| display: none; | |||
| visibility: hidden; | |||
| opacity: 0; | |||
| } | |||
| /* Open on focus-within (tap/click focuses button) */ | |||
| #sidebar li:focus-within > ul{ | |||
| display: block; | |||
| visibility: visible; | |||
| opacity: 1; | |||
| } | |||
| #sidebar li ul li > a{ | |||
| padding: 12px 20px 12px 40px; /* indent submenu */ | |||
| font-size: 1.15rem; | |||
| font-weight: 600; | |||
| } | |||
| @@ -11,6 +11,10 @@ body, | |||
| font-family: "Noto Sans HK", "Noto Sans SC"; | |||
| } | |||
| .page-grey { | |||
| filter: grayscale(100%); | |||
| } | |||
| /* Chrome, Safari, Edge, Opera */ | |||
| input::-webkit-outer-spin-button, | |||
| input::-webkit-inner-spin-button { | |||
| @@ -48,26 +52,97 @@ img | |||
| img:hover{-webkit-filter:none;} | |||
| a:link { | |||
| color: #1890ff; | |||
| a:link, | |||
| a:visited, | |||
| a:hover, | |||
| a:active { | |||
| color: #005FCC; /* WCAG AA compliant */ | |||
| background-color: transparent; | |||
| text-decoration: none; | |||
| text-decoration: underline; | |||
| } | |||
| a:visited { | |||
| color: #1890ff; | |||
| background-color: transparent; | |||
| text-decoration: none; | |||
| /* iframe#webpack-dev-server-client-overlay{display:none!important} */ | |||
| /* ===== WCAG 2.4.7 Focus Visible (Keyboard only) ===== */ | |||
| :where( | |||
| a, | |||
| button, | |||
| input, | |||
| select, | |||
| textarea, | |||
| summary, | |||
| [role="button"], | |||
| [role="link"], | |||
| [tabindex]:not([tabindex="-1"]) | |||
| ):focus-visible { | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: 2px; | |||
| border-radius: 4px; | |||
| } | |||
| a:hover { | |||
| color: #1890ff; | |||
| background-color: transparent; | |||
| text-decoration: none; | |||
| /* Suppress mouse focus */ | |||
| :where( | |||
| a, | |||
| button, | |||
| input, | |||
| select, | |||
| textarea, | |||
| summary, | |||
| [role="button"], | |||
| [role="link"], | |||
| [tabindex]:not([tabindex="-1"]) | |||
| ):focus:not(:focus-visible) { | |||
| outline: none; | |||
| } | |||
| a:active { | |||
| color: #1890ff; | |||
| background-color: transparent; | |||
| text-decoration: none; | |||
| /* ===== MUI DataGrid keyboard focus (WCAG 2.4.7 / 2.4.11) ===== */ | |||
| .MuiDataGrid-columnHeader:focus-visible, | |||
| .MuiDataGrid-cell:focus-visible { | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: -2px; | |||
| box-shadow: 0 0 0 3px rgba(12, 72, 158, 0.25); | |||
| } | |||
| /* Contained buttons only */ | |||
| .MuiButton-contained { | |||
| box-shadow: none; | |||
| } | |||
| .MuiButton-contained:hover { | |||
| } | |||
| /* iAM Smart button — keep green border */ | |||
| .MuiButton-outlinedIAmSmart { | |||
| color: #2E7D32; /* green text */ | |||
| border: 1px solid #2E7D32; /* keep your green */ | |||
| } | |||
| .MuiButton-outlinedIAmSmart:hover { | |||
| color: #2E7D32; /* green text */ | |||
| border: 1px solid #2E7D32; | |||
| background-color: rgba(46, 125, 50, 0.08); /* optional */ | |||
| } | |||
| /* ===== Outlined button focus ===== */ | |||
| .MuiButton-outlined:focus-visible { | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: 2px; | |||
| } | |||
| /* Step icon number color (text inside the circle) */ | |||
| .MuiStepIcon-text { | |||
| fill: #1A1A1A; /* dark text, high contrast */ | |||
| } | |||
| /* Placeholder text contrast */ | |||
| .MuiInputBase-input::placeholder { | |||
| color: #6B6B6B; /* passes 4.5:1 on white */ | |||
| opacity: 1; /* IMPORTANT */ | |||
| } | |||
| /* iframe#webpack-dev-server-client-overlay{display:none!important} */ | |||
| /* WCAG-safe error text color */ | |||
| .Mui-error, | |||
| .MuiFormHelperText-root.Mui-error, | |||
| .MuiTypography-errorMessage1 { | |||
| color: #B00020; /* dark red, AA compliant */ | |||
| opacity: 1; /* remove faded appearance */ | |||
| } | |||
| @@ -13,6 +13,12 @@ export const windowCount = 'windowCount' | |||
| import {useNavigate} from "react-router-dom"; | |||
| import {useDispatch} from "react-redux"; | |||
| import { REFRESH_TOKEN } from 'utils/ApiPathConst'; | |||
| import { getMessage } from 'utils/getI18nMessage'; | |||
| // Guard so we only register interceptors once (ThemeRoutes re-renders add duplicate handlers otherwise) | |||
| let axiosInterceptorsSetup = false; | |||
| // In-memory guard so only one 401/logout flow shows the alert (avoids race when multiple 401s or interceptors run) | |||
| let expiredAlertShownInMemory = false; | |||
| // ** Handle User Login | |||
| export const handleLogin = data => { | |||
| @@ -32,6 +38,7 @@ export const handleLogin = data => { | |||
| localStorage.setItem('accessToken', data.accessToken) | |||
| localStorage.setItem('refreshToken', data.refreshToken) | |||
| localStorage.setItem('axiosToken', "Bearer " + data.accessToken) | |||
| localStorage.setItem('searchCriteria',"") | |||
| //localStorage.setItem(config.storageUserRoleKeyName, JSON.stringify(data.role).slice(1).slice(0, -1)) | |||
| localStorage.setItem(refreshIntervalName, "60") | |||
| // for demo only | |||
| @@ -91,8 +98,10 @@ export const handleLogoutFunction = () => { | |||
| localStorage.removeItem('refreshToken') | |||
| localStorage.removeItem('webtoken') | |||
| localStorage.removeItem('transactionid') | |||
| localStorage.removeItem('searchCriteria') | |||
| //localStorage.removeItem(config.storageUserRoleKeyName) | |||
| localStorage.removeItem('expiredAlertShown') | |||
| localStorage.removeItem('expiredAlertShown'); | |||
| expiredAlertShownInMemory = false; | |||
| localStorage.removeItem(refreshIntervalName) | |||
| localStorage.removeItem(windowCount) | |||
| localStorage.removeItem(predictProductionQty) | |||
| @@ -107,7 +116,13 @@ export const SetupAxiosInterceptors = () => { | |||
| const dispatch = useDispatch(); | |||
| //const updateLastRequestTime = useContext(TimerContext); | |||
| let isRefreshToken= false; | |||
| // Avoid stacking interceptors on every ThemeRoutes re-render (would cause multiple alerts on 401) | |||
| if (axiosInterceptorsSetup) { | |||
| return; | |||
| } | |||
| axiosInterceptorsSetup = true; | |||
| axios.interceptors.request.use( | |||
| config => { | |||
| // ** Get token from localStorage | |||
| @@ -154,17 +169,23 @@ export const SetupAxiosInterceptors = () => { | |||
| } | |||
| }) | |||
| .catch((refreshError) => { | |||
| isRefreshToken = false; | |||
| if (!expiredAlertShownInMemory && localStorage.getItem("expiredAlertShown") === null) { | |||
| expiredAlertShownInMemory = true; | |||
| localStorage.setItem("expiredAlertShown", "true"); | |||
| alert(getMessage("autoLogout")); | |||
| } | |||
| dispatch(handleLogoutFunction()); | |||
| navigate('/login'); | |||
| isRefreshToken = false; | |||
| window.location.reload(); | |||
| throw refreshError; | |||
| }); | |||
| } else { | |||
| if (error.response.status === 401) { | |||
| if (localStorage.getItem("expiredAlertShown") === null) { | |||
| localStorage.setItem("expiredAlertShown", true) | |||
| alert("登入驗證已過期,請重新登入。") | |||
| if (error.response && error.response.status === 401) { | |||
| if (!expiredAlertShownInMemory && localStorage.getItem("expiredAlertShown") === null) { | |||
| expiredAlertShownInMemory = true; | |||
| localStorage.setItem("expiredAlertShown", "true"); | |||
| alert(getMessage("autoLogout")); | |||
| } | |||
| } | |||
| @@ -225,7 +246,9 @@ export const handleRefreshTokenFunction = () => { | |||
| }) | |||
| .catch((refreshError) => { | |||
| console.log('Failed to refresh token'); | |||
| console.log(refreshError) | |||
| if (refreshError != undefined){ | |||
| console.log(refreshError) | |||
| } | |||
| // token = null | |||
| isRefresh = false; | |||
| }); | |||
| @@ -14,25 +14,10 @@ export const paymentPath = window.location.href.match("localhost:3000") ? `${hos | |||
| export const delBugMode = true; | |||
| /** | |||
| * Testing: | |||
| * Domain: apigw-isit.staging-eid.gov.hk | |||
| * URL: hk.gov.iamsmart.testapp:// | |||
| * | |||
| * Production | |||
| * Domain: apigw.iamsmart.gov.hk | |||
| * URL: hk.gov.iamsmart:// | |||
| */ | |||
| export const iAmSmartPath = `https://apigw-isit.staging-eid.gov.hk`; | |||
| export const iAmSmartAppPath = `hk.gov.iamsmart.testapp://`; | |||
| export const clientId = "cf61fa7c121e4869966f69c8694b1cd2"; | |||
| export const iAmSmartCallbackPath = () => { | |||
| let hostname = window.location.hostname; | |||
| if (hostname.match("pnspsuat")) { | |||
| hostname = "pnspsuat.gld.gov.hk"; | |||
| } else { | |||
| hostname = "pnspsdev.gld.gov.hk"; | |||
| } | |||
| return hostname; | |||
| }; | |||
| @@ -57,7 +42,10 @@ export const getBowserType = () => { | |||
| if (navigator.userAgent.match(/Android/i)) return "Android_Chrome" | |||
| if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) return "iOS_Chrome" | |||
| return "PC_Browser" | |||
| } else if (navigator.userAgent.indexOf("Safari") != -1) { | |||
| } else if (navigator.userAgent.indexOf('CriOS') != -1) { | |||
| if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) return "iOS_Chrome" | |||
| return "PC_Browser" | |||
| } else if (navigator.userAgent.indexOf("Safari") != -1 && navigator.userAgent.indexOf("Chrome") == -1) { | |||
| if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) return "iOS_Safari" | |||
| return "PC_Browser" | |||
| } else if (navigator.userAgent.indexOf("Firefox") != -1) { | |||
| @@ -89,7 +77,7 @@ export const isGranted = (auth) => { | |||
| const abilities = getUserData() ? getUserData()["abilities"] : null; | |||
| if (abilities == null || abilities.length == 0) return false; | |||
| if (!Array.isArray(auth)) return _checkAuth(abilities, auth); | |||
| if (auth.length > abilities.length) return false; | |||
| let haveAuth = true; | |||
| for (let i = 0; i < auth.length; i++) { | |||
| @@ -138,8 +126,51 @@ export const local = { en: "en-us", zh: "zh-hk", cn: "zh-cn" }; | |||
| export const preferpaymentmethods = ['visa', 'mastercard', 'pps', 'creditcard', 'fps']; | |||
| export const getPaymentMethod = (paymentMethod) => { | |||
| if(paymentMethod == "online") return 'payOnlineMethod'; | |||
| if(paymentMethod == "demandNote") return 'payDnMethod'; | |||
| if(paymentMethod == "office") return 'payNPGOMethod'; | |||
| if (paymentMethod == "online") return 'payOnlineMethod'; | |||
| if (paymentMethod == "demandNote") return 'payDnMethod'; | |||
| if (paymentMethod == "office") return 'payNPGOMethod'; | |||
| return "other"; | |||
| } | |||
| export const getPaymentMethodGLD = (paymentMethod) => { | |||
| if (paymentMethod == "online") return 'Online'; | |||
| if (paymentMethod == "demandNote") return 'Demand Note'; | |||
| if (paymentMethod == "office") return 'NPGO Collection Office'; | |||
| return "other"; | |||
| } | |||
| export const getSearchCriteria = (path) =>{ | |||
| let searchCriteria = "" | |||
| if (localStorage.getItem('searchCriteria')==""){ | |||
| return searchCriteria | |||
| } else if (Object.keys(localStorage.getItem('searchCriteria')).length>0){ | |||
| searchCriteria = JSON.parse(localStorage.getItem("searchCriteria")) | |||
| if (searchCriteria.path === path){ | |||
| return searchCriteria.data | |||
| } else { | |||
| return "" | |||
| } | |||
| } | |||
| } | |||
| export const checkSearchCriteriaPath = (path) =>{ | |||
| if(!path.startsWith("/application") | |||
| || path === "/application/search"){ | |||
| if(!path.startsWith("/user/")){ | |||
| if(!path.startsWith("/publicNotice/")|| path === "/publicNotice"){ | |||
| return true | |||
| } | |||
| } | |||
| } | |||
| } | |||
| export const getPaymentMethodByCode = (code) => { | |||
| if (code === "01,PPSB,PPS") return "PPS"; | |||
| if (code === "02,BCMP,CreditCard" || code === "03,BCMP,CreditCard") return "Credit Card"; | |||
| if (code === "04,BCFP,FPS") return "FPS"; | |||
| return "other"; | |||
| }; | |||
| export function setPageTitle(title) { | |||
| document.title = `${title} - PNSPS | Government Logistics Department`; | |||
| } | |||
| @@ -11,25 +11,40 @@ import Logo from './AdminLogo'; | |||
| import config from 'config'; | |||
| import { activeItem } from 'store/reducers/menu'; | |||
| import { Stack } from '@mui/material'; | |||
| import { | |||
| checkSysEnv | |||
| } from "utils/Utils"; | |||
| import {useIntl} from "react-intl"; | |||
| // ==============================|| MAIN LOGO ||============================== // | |||
| const LogoSection = ({ sx, to }) => { | |||
| const { defaultId } = useSelector((state) => state.menu); | |||
| const dispatch = useDispatch(); | |||
| const intl = useIntl(); | |||
| return ( | |||
| <Stack direction="column" justifyContent="center" alignItems="center" > | |||
| <ButtonBase | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| sx={sx} | |||
| > | |||
| <ButtonBase | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| aria-label={intl.formatMessage({ id: "PNSPS", defaultMessage: "PNSPS" })} | |||
| sx={{ | |||
| ...sx, | |||
| /* ✅ WCAG 2.4.7 focus indicator */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| > | |||
| <Stack direction="column" justifyContent="center" alignItems="center" > | |||
| <Logo /> | |||
| </ButtonBase> | |||
| <span id="systemTitle" >PNSPS</span> | |||
| </Stack> | |||
| <span style={{ color: checkSysEnv()!=''?'red':'#0C489E'}} id="systemTitle">PNSPS</span> | |||
| </Stack> | |||
| </ButtonBase> | |||
| ); | |||
| }; | |||
| @@ -1,19 +1,23 @@ | |||
| import React, { createContext, useState, useEffect } from 'react'; | |||
| import React, { createContext, useState, useEffect, useRef } from 'react'; | |||
| import {useNavigate} from "react-router-dom"; | |||
| import {useIdleTimer} from "react-idle-timer"; | |||
| import { handleLogoutFunction } from 'auth/index'; | |||
| import { useDispatch } from "react-redux"; | |||
| import { useIntl } from "react-intl"; | |||
| import { | |||
| isUserLoggedIn, | |||
| isGLDLoggedIn, | |||
| isGLDLoggedIn, | |||
| isPasswordExpiry | |||
| } from "utils/Utils"; | |||
| const TimerContext = createContext(); | |||
| const AutoLogoutProvider = ({ children }) => { | |||
| const intl = useIntl(); | |||
| const [lastRequestTime, setLastRequestTime] = useState(Date.now()); | |||
| const navigate = useNavigate(); | |||
| const [logoutInterval, setLogoutInterval] = useState(1); | |||
| const idleLogoutTriggeredRef = useRef(false); | |||
| // const [remainingInterval] = useState(5); | |||
| const [state, setState] = useState('Active'); | |||
| const dispatch = useDispatch() | |||
| @@ -81,6 +85,9 @@ const AutoLogoutProvider = ({ children }) => { | |||
| // console.log(logoutInterval) | |||
| const interval = setInterval(async () => { | |||
| const currentTime = Date.now(); | |||
| if (isPasswordExpiry()){ | |||
| navigate('/user/changePassword'); | |||
| } | |||
| // getRemainingTime(); | |||
| if(state !== "Active" && lastActiveTab){ | |||
| const timeElapsed = currentTime - lastRequestTime; | |||
| @@ -89,8 +96,13 @@ const AutoLogoutProvider = ({ children }) => { | |||
| // console.log(remainingInterval * 60); | |||
| // console.log(logoutInterval * 60 * 1000 - timeElapsed) | |||
| if (timeElapsed >= logoutInterval * 60 * 1000) { | |||
| if(isUserLoggedIn()){ | |||
| alert("登入驗證已過期,請重新登入。") | |||
| if (isUserLoggedIn() && !idleLogoutTriggeredRef.current) { | |||
| idleLogoutTriggeredRef.current = true; | |||
| // Skip alert if token-expiry (axios 401) already showed one, to avoid duplicate alerts | |||
| if (localStorage.getItem("expiredAlertShown") === null) { | |||
| localStorage.setItem("expiredAlertShown", "true"); | |||
| alert(intl.formatMessage({ id: "autoLogout" })); | |||
| } | |||
| dispatch(handleLogoutFunction()); | |||
| navigate('/login'); | |||
| window.location.reload(); | |||
| @@ -1,17 +1,20 @@ | |||
| // material-ui | |||
| import {useState, useEffect} from 'react'; | |||
| import { useState, useEffect, useRef } from 'react'; | |||
| import { Box } from "@mui/material"; | |||
| import { | |||
| DataGrid, GridOverlay, | |||
| } from "@mui/x-data-grid"; | |||
| import * as HttpUtils from "utils/HttpUtils"; | |||
| import {FormattedMessage, useIntl} from "react-intl"; | |||
| import {TablePagination, Typography} from '@mui/material'; | |||
| import { FormattedMessage, useIntl } from "react-intl"; | |||
| import { TablePagination, Typography } from '@mui/material'; | |||
| import { getSearchCriteria, checkSearchCriteriaPath } from "auth/utils"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export function FiDataGrid({ rows, columns, sx, autoHeight, | |||
| hideFooterSelectedRowCount, rowModesModel, editMode, | |||
| pageSizeOptions, filterItems, customPageSize, doLoad, ...props }) { | |||
| export function FiDataGrid({ rows, columns, sx, autoHeight = true, | |||
| hideFooterSelectedRowCount, rowModesModel, editMode, | |||
| pageSizeOptions, filterItems, customPageSize, doLoad, applyGridOnReady, applySearch, | |||
| tab, height, maxHeight, pagination = true, ...props }) { | |||
| const intl = useIntl(); | |||
| const [_rows, set_rows] = useState([]); | |||
| const [_doLoad, set_doLoad] = useState({}); | |||
| @@ -20,13 +23,15 @@ export function FiDataGrid({ rows, columns, sx, autoHeight, | |||
| const [_editMode, set_editMode] = useState("row"); | |||
| const [_pageSizeOptions, set_pageSizeOptions] = useState([10]); | |||
| const [_filterItems, set_filterItems] = useState([]); | |||
| const [loading, setLoading] = useState(false); | |||
| const [page, setPage] = useState(0); | |||
| const [pageSize, setPageSize] = useState(10); | |||
| const [_autoHeight, set_autoHeight] = useState(true); | |||
| // const [_autoHeight, set_autoHeight] = useState(true); | |||
| const [myHideFooterSelectedRowCount, setMyHideFooterSelectedRowCount] = useState(true); | |||
| const [_sx, set_sx] = useState({ | |||
| padding: "4 2 4 2", | |||
| lineHeight: "normal", | |||
| '& .MuiDataGrid-cell': { | |||
| borderTop: 1, | |||
| borderBottom: 1, | |||
| @@ -36,18 +41,50 @@ export function FiDataGrid({ rows, columns, sx, autoHeight, | |||
| border: 1, | |||
| borderColor: "#EEE" | |||
| }, | |||
| "& .MuiDataGrid-columnHeaderTitle": { | |||
| whiteSpace: "normal", | |||
| lineHeight: "normal" | |||
| }, | |||
| "& .MuiDataGrid-columnHeader": { | |||
| // Forced to use important since overriding inline styles | |||
| height: "unset !important" | |||
| }, | |||
| }); | |||
| const effectiveAutoHeight = autoHeight && !height && !maxHeight; | |||
| const containerSx = { | |||
| width: '100%', | |||
| ...(height ? { height } : {}), | |||
| ...(maxHeight ? { maxHeight, height: '100%' } : {}), | |||
| overflow: 'hidden', | |||
| }; | |||
| const [rowCount, setRowCount] = useState(0); | |||
| useEffect(() => { | |||
| setPage(0); | |||
| set_doLoad(doLoad); | |||
| useEffect(() => { | |||
| if (doLoad !== undefined && Object.keys(doLoad).length>0 ){ | |||
| if(applySearch!=undefined){ | |||
| if (Object.keys(getSearchCriteria(window.location.pathname)).length>0){ | |||
| const localStorageSearchCriteria = getSearchCriteria(window.location.pathname) | |||
| // console.log(localStorageSearchCriteria) | |||
| if(localStorageSearchCriteria.start!=undefined){ | |||
| // console.log(localStorageSearchCriteria) | |||
| setPage(localStorageSearchCriteria.start/pageSize); | |||
| } | |||
| } | |||
| }else{ | |||
| setPage(0); | |||
| setPageSize(parseInt(event.target.value, 10)); | |||
| } | |||
| set_doLoad(doLoad); | |||
| setLoading(true) | |||
| } | |||
| }, [doLoad]); | |||
| useEffect(()=>{ | |||
| useEffect(() => { | |||
| getDataList(); | |||
| },[_doLoad, page]); | |||
| }, [_doLoad, page]); | |||
| useEffect(() => { | |||
| @@ -70,18 +107,25 @@ export function FiDataGrid({ rows, columns, sx, autoHeight, | |||
| if (pageSizeOptions) { | |||
| set_pageSizeOptions(pageSizeOptions) | |||
| } | |||
| if(autoHeight !== undefined){ | |||
| set_autoHeight(autoHeight) | |||
| } | |||
| if(editMode){ | |||
| // if (autoHeight !== undefined) { | |||
| // set_autoHeight(autoHeight) | |||
| // } | |||
| if (editMode) { | |||
| set_editMode(editMode); | |||
| } | |||
| if(filterItems){ | |||
| if (filterItems) { | |||
| set_filterItems(filterItems); | |||
| } | |||
| if(customPageSize){ | |||
| if (customPageSize) { | |||
| setPageSize(customPageSize); | |||
| } | |||
| // console.log(_doLoad) | |||
| if (_doLoad !== undefined && Object.keys(_doLoad).length==0 ){ | |||
| setLoading(false) | |||
| if (applyGridOnReady !== undefined){ | |||
| applyGridOnReady(false) | |||
| } | |||
| } | |||
| }, [sx, hideFooterSelectedRowCount, rowModesModel, rows, columns, pageSizeOptions, autoHeight, editMode, filterItems, customPageSize]); | |||
| const handleChangePage = (event, newPage) => { | |||
| @@ -95,65 +139,172 @@ export function FiDataGrid({ rows, columns, sx, autoHeight, | |||
| function CustomNoRowsOverlay() { | |||
| return ( | |||
| <GridOverlay> | |||
| <Typography variant="body1"> | |||
| <GridOverlay | |||
| sx={{ | |||
| width: "100%", | |||
| justifyContent: "flex-start", // align overlay to left | |||
| pl: 2, // padding-left to match grid cells | |||
| }} | |||
| > | |||
| <Typography variant="body1" sx={{ textAlign: "left", width: "100%" }}> | |||
| <FormattedMessage id="noRecordFound" /> | |||
| </Typography> | |||
| </GridOverlay> | |||
| ); | |||
| } | |||
| function getDataList() { | |||
| if(_doLoad?.url == null) return; | |||
| if(_doLoad.params == null) _doLoad.params = {}; | |||
| _doLoad.params.start = page*pageSize; | |||
| // console.log(Object.keys(_doLoad.params).length > 0) | |||
| // console.log(Object.keys(_doLoad.params).length > 0) | |||
| if (_doLoad?.url == null){ | |||
| setLoading(false) | |||
| return; | |||
| } | |||
| if (_doLoad.params == undefined) return; | |||
| if (_doLoad.params.searchCriteria !== undefined) return; | |||
| if (_doLoad.params == null) _doLoad.params = {}; | |||
| _doLoad.params.start = page * pageSize; | |||
| _doLoad.params.limit = pageSize; | |||
| if(checkSearchCriteriaPath(window.location.pathname)){ | |||
| if(window.location.pathname === "/publicNotice"){ | |||
| if (tab != undefined && tab ==="application"){ | |||
| localStorage.setItem('searchCriteria', JSON.stringify({path:window.location.pathname,data:_doLoad.params})) | |||
| } | |||
| }else if (window.location.pathname != "/publicNotice"){ | |||
| localStorage.setItem('searchCriteria', JSON.stringify({path:window.location.pathname,data:_doLoad.params})) | |||
| } | |||
| } | |||
| HttpUtils.get({ | |||
| url: _doLoad.url, | |||
| params: _doLoad.params, | |||
| onSuccess: function (responseData) { | |||
| set_rows(responseData?.records); | |||
| setRowCount(responseData?.count); | |||
| if(_doLoad.callback != null){ | |||
| if (_doLoad.callback != null) { | |||
| _doLoad.callback(responseData); | |||
| } | |||
| setLoading(false) | |||
| // console.log(applyGridOnReady) | |||
| if (applyGridOnReady !== undefined){ | |||
| applyGridOnReady(false) | |||
| } | |||
| }, | |||
| onError: function (error){ | |||
| console.log(error) | |||
| setLoading(false) | |||
| if (applyGridOnReady !== undefined){ | |||
| applyGridOnReady(false) | |||
| } | |||
| } | |||
| }); | |||
| } | |||
| 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 ( | |||
| <DataGrid | |||
| {...props} | |||
| rows={_rows} | |||
| columns={_columns} | |||
| paginationMode="server" | |||
| disableColumnMenu | |||
| rowModesModel={_rowModesModel} | |||
| pageSizeOptions={_pageSizeOptions} | |||
| editMode={_editMode} | |||
| autoHeight={_autoHeight} | |||
| hideFooterSelectedRowCount={myHideFooterSelectedRowCount} | |||
| filterModel={{ items: _filterItems }} | |||
| sx={_sx} | |||
| components={{ | |||
| noRowsOverlay: CustomNoRowsOverlay, | |||
| Pagination: () => ( | |||
| <TablePagination | |||
| count={rowCount?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}` | |||
| <Box sx={containerSx} ref={gridRootRef} role="table"> | |||
| <DataGrid | |||
| {...props} | |||
| rows={_rows} | |||
| rowCount={rowCount || 0} | |||
| columns={_columns} | |||
| disableColumnMenu | |||
| shrinkWrap | |||
| rowModesModel={_rowModesModel} | |||
| pageSizeOptions={pagination ? _pageSizeOptions : []} | |||
| editMode={_editMode} | |||
| autoHeight={effectiveAutoHeight} | |||
| hideFooterSelectedRowCount={myHideFooterSelectedRowCount} | |||
| filterModel={{ items: _filterItems }} | |||
| loading={loading} | |||
| paginationMode={pagination ? "server" : undefined} | |||
| sx={{ | |||
| ..._sx, | |||
| '& .MuiDataGrid-virtualScroller': { | |||
| overflowY: height || maxHeight ? 'auto' : 'visible', | |||
| overflowX: height || maxHeight ? 'auto' : 'visible', | |||
| }, | |||
| // 👇 completely hide the footer when pagination is off | |||
| ...(pagination === false && { | |||
| '& .MuiDataGrid-footerContainer': { | |||
| display: 'none', | |||
| }, | |||
| }), | |||
| }} | |||
| components={{ | |||
| NoRowsOverlay: CustomNoRowsOverlay, | |||
| ...(pagination | |||
| ? { | |||
| Pagination: () => ( | |||
| <TablePagination | |||
| count={rowCount || 0} | |||
| page={page} | |||
| rowsPerPage={pageSize} | |||
| rowsPerPageOptions={_pageSizeOptions} | |||
| labelDisplayedRows={() => | |||
| `${(_rows?.length ? page * pageSize + 1 : 0)}-${page * pageSize + (_rows?.length ?? 0)} ${intl.formatMessage({ id: "of" })} ${rowCount}` | |||
| } | |||
| labelRowsPerPage={intl.formatMessage({ id: "rowsPerPage" }) + ":"} | |||
| getItemAriaLabel={(type) => { | |||
| if (type === 'previous') { | |||
| return intl.formatMessage({ id: 'paginationPrev' }); | |||
| } | |||
| if (type === 'next') { | |||
| return intl.formatMessage({ id: 'paginationNext' }); | |||
| } | |||
| return ''; | |||
| }} | |||
| onPageChange={handleChangePage} | |||
| onRowsPerPageChange={handleChangePageSize} | |||
| /> | |||
| ), | |||
| } | |||
| labelRowsPerPage={intl.formatMessage({ id: "rowsPerPage" }) + ":"} | |||
| onPageChange={handleChangePage} | |||
| onRowsPerPageChange={handleChangePageSize} | |||
| /> | |||
| ), | |||
| }} | |||
| /> | |||
| : {}), | |||
| }} | |||
| /> | |||
| </Box> | |||
| ); | |||
| } | |||
| @@ -19,6 +19,7 @@ export default function FileList({ refType, refId, allowDelete, sx, dateHideable | |||
| const theme = useTheme(); | |||
| const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | |||
| const intl = useIntl(); | |||
| const [onDownload, setOnDownload] = React.useState(false); | |||
| React.useEffect(() => { | |||
| loadData(); | |||
| @@ -41,10 +42,17 @@ export default function FileList({ refType, refId, allowDelete, sx, dateHideable | |||
| }; | |||
| const onDownloadClick = (fileId, skey, filename) => () => { | |||
| setOnDownload(true) | |||
| HttpUtils.fileDownload({ | |||
| fileId: fileId, | |||
| skey: skey, | |||
| filename: filename, | |||
| onResponse:()=>{ | |||
| setOnDownload(false) | |||
| }, | |||
| onError:()=>{ | |||
| setOnDownload(false) | |||
| } | |||
| }); | |||
| }; | |||
| @@ -91,6 +99,7 @@ export default function FileList({ refType, refId, allowDelete, sx, dateHideable | |||
| className="textPrimary" | |||
| onClick={onDownloadClick(params.id, params.row.skey, params.row.filename)} | |||
| color="primary" | |||
| disabled={onDownload} | |||
| />] | |||
| }, | |||
| }, | |||
| @@ -139,6 +148,7 @@ export default function FileList({ refType, refId, allowDelete, sx, dateHideable | |||
| className="textPrimary" | |||
| onClick={onDownloadClick(params.id, params.row.skey, params.row.filename)} | |||
| color="primary" | |||
| disabled={onDownload} | |||
| />] | |||
| }, | |||
| }, | |||
| @@ -1,8 +1,8 @@ | |||
| import { useState, useEffect, createContext } from 'react'; | |||
| import { useState, useEffect, createContext, useMemo } from 'react'; | |||
| import { IntlProvider } from 'react-intl'; | |||
| import enMessages from '../translations/en.json'; | |||
| import cnMessages from '../translations/zh-CN.json'; | |||
| import hkMessages from '../translations/zh-HK.json'; | |||
| import enBase from '../translations/en.json'; | |||
| import cnBase from '../translations/zh-CN.json'; | |||
| import hkBase from '../translations/zh-HK.json'; | |||
| import { GET_COMBO, GET_CONTENT } from "utils/ApiPathConst"; | |||
| import { get } from "utils/HttpUtils"; | |||
| @@ -10,72 +10,106 @@ import { get } from "utils/HttpUtils"; | |||
| const LocaleContext = createContext(); | |||
| export const I18nProvider = ({ children }) => { | |||
| const systemMessages = { | |||
| "en": enMessages, | |||
| "zh": hkMessages, | |||
| "zh-HK": hkMessages, | |||
| "zh-CN": cnMessages | |||
| }; | |||
| const [locale, setLocale] = useState('en'); | |||
| // keep base messages immutable | |||
| const [systemMessages, setSystemMessages] = useState({ | |||
| en: { ...enBase }, | |||
| zh: { ...hkBase }, | |||
| 'zh-HK': { ...hkBase }, | |||
| 'zh-CN': { ...cnBase } | |||
| }); | |||
| const [loaded, setLoaded] = useState(false); | |||
| useEffect(() => { | |||
| const saved = localStorage.getItem('locale'); | |||
| if (!saved) localStorage.setItem('locale', 'en'); | |||
| else setLocale(saved); | |||
| }, []); | |||
| const [locale, setLocale] = useState('en'); // Default locale, you can change this as per your requirement | |||
| const [messages, setMessages] = useState(systemMessages[locale]); | |||
| useEffect(() => { | |||
| let alive = true; | |||
| const loadTermsAndConditions = () => { | |||
| // load both endpoints then merge into state | |||
| const p1 = new Promise((resolve) => { | |||
| get({ | |||
| url: GET_CONTENT, | |||
| onSuccess: (responseData) => { | |||
| for (const key in responseData) { | |||
| const value = responseData[key]; | |||
| enMessages[key] = value.en??""; | |||
| cnMessages[key] = value.cn??""; | |||
| hkMessages[key] = value.zh??""; | |||
| } | |||
| } | |||
| url: GET_CONTENT, | |||
| onSuccess: (resp) => resolve(resp || {}), | |||
| onError: () => resolve({}) | |||
| }); | |||
| }); | |||
| const p2 = new Promise((resolve) => { | |||
| get({ | |||
| url: GET_COMBO, | |||
| onSuccess: (responseData) => { | |||
| for (let i = 0; i < responseData.length; i++) { | |||
| let item = responseData[i]; | |||
| enMessages[item.key] = item.en; | |||
| cnMessages[item.key] = item.cn; | |||
| hkMessages[item.key] = item.zh; | |||
| } | |||
| } | |||
| url: GET_COMBO, | |||
| onSuccess: (resp) => resolve(resp || []), | |||
| onError: () => resolve([]) | |||
| }); | |||
| }); | |||
| Promise.all([p1, p2]).then(([contentMap, comboList]) => { | |||
| if (!alive) return; | |||
| setSystemMessages((prev) => { | |||
| // clone prev first (immutably) | |||
| const next = { | |||
| ...prev, | |||
| en: { ...prev.en }, | |||
| 'zh-CN': { ...prev['zh-CN'] }, | |||
| 'zh-HK': { ...prev['zh-HK'] }, | |||
| zh: { ...prev.zh } | |||
| }; | |||
| // merge GET_CONTENT (object) | |||
| for (const key in contentMap) { | |||
| const v = contentMap[key] || {}; | |||
| next.en[key] = v.en ?? ""; | |||
| next['zh-CN'][key] = v.cn ?? ""; | |||
| next['zh-HK'][key] = v.zh ?? ""; | |||
| next.zh[key] = v.zh ?? ""; | |||
| } | |||
| // merge GET_COMBO (array) | |||
| for (const item of comboList) { | |||
| if (!item?.key) continue; | |||
| next.en[item.key] = item.en ?? ""; | |||
| next['zh-CN'][item.key] = item.cn ?? ""; | |||
| next['zh-HK'][item.key] = item.zh ?? ""; | |||
| next.zh[item.key] = item.zh ?? ""; | |||
| } | |||
| return next; | |||
| }); | |||
| } | |||
| useEffect(() => { | |||
| loadTermsAndConditions(); | |||
| if (localStorage.getItem('locale') === null) { | |||
| //no locale case | |||
| localStorage.setItem('locale', 'en'); | |||
| } | |||
| else { | |||
| setLocale(localStorage.getItem('locale')); | |||
| } | |||
| }, []); | |||
| useEffect(() => { | |||
| // Load the messages for the selected locale | |||
| const fetchMessages = async () => { | |||
| setMessages(systemMessages[locale]); | |||
| }; | |||
| fetchMessages(); | |||
| }, [locale]); | |||
| return ( | |||
| <LocaleContext.Provider value={{ locale, setLocale }} > | |||
| <IntlProvider locale={locale} messages={messages}> | |||
| {children} | |||
| </IntlProvider> | |||
| </LocaleContext.Provider> | |||
| ); | |||
| } | |||
| export default LocaleContext; | |||
| setLoaded(true); | |||
| }); | |||
| }; | |||
| loadTermsAndConditions(); | |||
| return () => { | |||
| alive = false; | |||
| }; | |||
| }, []); | |||
| const messages = useMemo(() => { | |||
| return systemMessages[locale] || systemMessages.en; | |||
| }, [systemMessages, locale]); | |||
| return ( | |||
| <LocaleContext.Provider value={{ locale, setLocale }}> | |||
| <IntlProvider | |||
| key={locale} | |||
| locale={locale} | |||
| messages={messages} | |||
| defaultLocale="en" | |||
| > | |||
| {loaded ? children : <div />} | |||
| </IntlProvider> | |||
| </LocaleContext.Provider> | |||
| ); | |||
| }; | |||
| export default LocaleContext; | |||
| @@ -9,18 +9,22 @@ import { useDispatch, useSelector } from 'react-redux'; | |||
| import Logo from './Logo'; | |||
| import config from 'config'; | |||
| import { activeItem } from 'store/reducers/menu'; | |||
| import {useIntl} from "react-intl"; | |||
| // ==============================|| MAIN LOGO ||============================== // | |||
| const LogoSection = ({ sx, to }) => { | |||
| const { defaultId } = useSelector((state) => state.menu); | |||
| const dispatch = useDispatch(); | |||
| const intl = useIntl(); | |||
| return ( | |||
| <ButtonBase | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| aria-label={intl.formatMessage({ id: "PNSPS", defaultMessage: "PNSPS" })} | |||
| sx={sx} | |||
| > | |||
| <Logo /> | |||
| @@ -9,22 +9,35 @@ import { useDispatch, useSelector } from 'react-redux'; | |||
| import Logo from './MobileLogo'; | |||
| import config from 'config'; | |||
| import { activeItem } from 'store/reducers/menu'; | |||
| import {useIntl} from "react-intl"; | |||
| // ==============================|| MAIN LOGO ||============================== // | |||
| const LogoSection = ({ sx, to }) => { | |||
| const intl = useIntl(); | |||
| const { defaultId } = useSelector((state) => state.menu); | |||
| const dispatch = useDispatch(); | |||
| return ( | |||
| <ButtonBase | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| sx={sx} | |||
| > | |||
| <Logo /> | |||
| </ButtonBase> | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| aria-label={intl.formatMessage({ id: "PNSPS" })} | |||
| sx={{ | |||
| ...sx, | |||
| /* WCAG 2.4.7 – visible keyboard focus */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| > | |||
| <Logo /> | |||
| </ButtonBase> | |||
| ); | |||
| }; | |||
| @@ -35,7 +35,9 @@ const RefreshTokenProvider = ({ children }) => { | |||
| }) | |||
| .catch((refreshError) => { | |||
| console.log('Failed to refresh token'); | |||
| console.log(refreshError) | |||
| if (refreshError != undefined){ | |||
| console.log(refreshError) | |||
| } | |||
| token.current = null | |||
| isRefresh.current = false; | |||
| }); | |||
| @@ -0,0 +1,36 @@ | |||
| import { useState, useEffect, createContext } from 'react'; | |||
| import { get } from "utils/HttpUtils" | |||
| import {GET_SYS_SETTING} from "utils/ApiPathConst" | |||
| const SysContext = createContext(); | |||
| const SysSettingProvider = ({ children }) => { | |||
| const [sysSetting, setSysSetting] = useState({}); | |||
| useEffect(() => { | |||
| loadSysSetting(); | |||
| }, []); | |||
| const loadSysSetting = () => { | |||
| get({ | |||
| url: GET_SYS_SETTING, | |||
| onSuccess: (responseData) => { | |||
| // console.log(responseData) | |||
| setSysSetting(responseData); | |||
| localStorage.setItem('sysEnv', responseData.sysEnv) | |||
| localStorage.setItem('paymentSuspension', responseData.suspensionMode) | |||
| } | |||
| }); | |||
| } | |||
| return ( | |||
| <SysContext.Provider value={{ sysSetting, setSysSetting }} > | |||
| {children} | |||
| </SysContext.Provider> | |||
| ); | |||
| } | |||
| export {SysContext, SysSettingProvider}; | |||
| @@ -2,6 +2,7 @@ | |||
| import { useMediaQuery, Container, Link, Typography, Stack } from '@mui/material'; | |||
| import bhkLogo from 'assets/images/BHK_logo_rgb_zh-hk.png'; | |||
| import {FormattedMessage} from "react-intl"; | |||
| import {useIntl} from "react-intl"; | |||
| import { | |||
| isGLDLoggedIn, | |||
| } from "utils/Utils"; | |||
| @@ -9,9 +10,14 @@ import { | |||
| const AuthFooter = () => { | |||
| const matchDownSM = useMediaQuery((theme) => theme.breakpoints.down('sm')); | |||
| const intl = useIntl(); | |||
| const bhkAlt = intl.formatMessage({ id: "bhkLogoAlt" }); | |||
| const wcagAlt = intl.formatMessage({ id: "wcagAaAlt" }); | |||
| return ( | |||
| <Container maxWidth= "xl" sx={{minHeight: '5vh'}}> | |||
| <Container maxWidth="xl" sx={{ minHeight: '5vh' }}> | |||
| <Stack | |||
| direction={matchDownSM ? 'column' : 'row'} | |||
| justifyContent={matchDownSM ? 'center' : 'flex-start'} | |||
| @@ -19,14 +25,18 @@ const AuthFooter = () => { | |||
| textAlign={matchDownSM ? 'center' : 'inherit'} | |||
| alignItems="center" | |||
| > | |||
| <Typography variant="subtitle2" color="secondary" component="span"> | |||
| <Typography | |||
| variant="subtitle2" | |||
| component="span" | |||
| sx={{ color: '#4A4A4A' }} | |||
| > | |||
| 2024 © <FormattedMessage id="HKGLD" /> | |||
| </Typography> | |||
| <Typography | |||
| variant="subtitle2" | |||
| color="secondary" | |||
| component={Link} | |||
| // href="https://material-ui.com/store/contributors/codedthemes/" | |||
| href="/importantNotice" | |||
| target="_blank" | |||
| underline="hover" | |||
| > | |||
| @@ -36,8 +46,7 @@ const AuthFooter = () => { | |||
| variant="subtitle2" | |||
| color="secondary" | |||
| component={Link} | |||
| href="https://www.gld.gov.hk/zh-hk/privacy-policy/" | |||
| //href="/testMailPage" | |||
| href="/privacyPolicy" | |||
| target="_blank" | |||
| underline="hover" | |||
| > | |||
| @@ -45,22 +54,29 @@ const AuthFooter = () => { | |||
| </Typography> | |||
| </Stack> | |||
| <Stack direction={matchDownSM ? 'column' : 'row'} spacing={matchDownSM ? 1 : 3} textAlign={matchDownSM ? 'center' : 'inherit'} justifyContent={matchDownSM?"center":"flex-end"}> | |||
| {!isGLDLoggedIn()? | |||
| <a href="https://www.w3.org/WAI/WCAG2AA-Conformance" | |||
| title="Explanation of WCAG 2 Level AA conformance"> | |||
| <img height="32" width="88" | |||
| src="https://www.w3.org/WAI/wcag2AA" | |||
| alt="Level AA conformance, | |||
| W3C WAI Web Content Accessibility Guidelines 2.0"/> | |||
| </a>:null | |||
| } | |||
| <a href="https://www.brandhk.gov.hk/zh-hk" | |||
| title="Brand Hong Kong"> | |||
| <img src={bhkLogo} alt="logo" height="32" width="88" | |||
| {!isGLDLoggedIn()? ( | |||
| <a | |||
| href="https://www.w3.org/WAI/WCAG2AA-Conformance" | |||
| title="Explanation of WCAG 2 Level AA conformance" | |||
| > | |||
| <img | |||
| height="32" | |||
| width="88" | |||
| src="https://www.w3.org/WAI/wcag2AA" | |||
| alt={wcagAlt} | |||
| /> | |||
| </a> | |||
| </Stack> | |||
| ) : null} | |||
| <a href="https://www.brandhk.gov.hk/zh-hk" title="Brand Hong Kong"> | |||
| <img | |||
| src={bhkLogo} | |||
| alt={bhkAlt} | |||
| height="32" | |||
| width="88" | |||
| /> | |||
| </a> | |||
| </Stack> | |||
| </Container> | |||
| ); | |||
| }; | |||
| @@ -1,5 +1,6 @@ | |||
| // material-ui | |||
| import {useState, useEffect} from 'react'; | |||
| import { useIntl } from 'react-intl'; | |||
| import iAmSmartICon from 'assets/images/icons/icon_iAmSmart.png'; | |||
| import { | |||
| Button, | |||
| @@ -9,7 +10,7 @@ import { | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export function IAmSmartButton({ label, onClickFun, fullWidth }) { | |||
| const intl = useIntl(); | |||
| const [_label, set_label] = useState(""); | |||
| useEffect(()=>{ | |||
| @@ -23,7 +24,7 @@ export function IAmSmartButton({ label, onClickFun, fullWidth }) { | |||
| } | |||
| return ( | |||
| <Button onClick={()=>doOnClick()} sx={{textTransform: 'none'}} color="iAmSmart" fullWidth={fullWidth} size="large" variant="outlined" startIcon={<img src={iAmSmartICon} alt="iAM Smart" width="30" />}> | |||
| <Button onClick={()=>doOnClick()} sx={{textTransform: 'none'}} color="iAmSmart" fullWidth={fullWidth} size="large" variant="outlined" startIcon={<img src={iAmSmartICon} alt={intl.formatMessage({ id: 'iAmSmartAlt' })} width="30" />}> | |||
| <Typography variant="h5"> | |||
| {_label} | |||
| </Typography> | |||
| @@ -0,0 +1,42 @@ | |||
| import { useEffect } from "react"; | |||
| import { useIntl } from "react-intl"; | |||
| export default function usePageTitle(messageIdOrText) { | |||
| const intl = useIntl(); | |||
| useEffect(() => { | |||
| let pageTitle; | |||
| let systemName; | |||
| let gldName; | |||
| // If string looks like an intl id, try translate | |||
| try { | |||
| pageTitle = intl.formatMessage({ id: messageIdOrText }); | |||
| systemName = intl.formatMessage({ id: "PNSPS_fullname" }); | |||
| gldName = intl.formatMessage({ id: "HKGLD" }); | |||
| } catch { | |||
| pageTitle = messageIdOrText; | |||
| } | |||
| const fullTitle = `${pageTitle} - ${systemName} | ${gldName}`; | |||
| // Update document title (tab title) | |||
| document.title = fullTitle; | |||
| // Update <meta name="title"> and <meta name="description"> so they're language-dependent too | |||
| const metaTitle = document.querySelector('meta[name="title"]'); | |||
| if (metaTitle) { | |||
| metaTitle.setAttribute("content", fullTitle); | |||
| } | |||
| const metaDescription = document.querySelector('meta[name="description"]'); | |||
| if (metaDescription) { | |||
| metaDescription.setAttribute("content", fullTitle); | |||
| } | |||
| // Keep <html lang="..."> in sync with current locale | |||
| if (document.documentElement) { | |||
| document.documentElement.lang = intl.locale || "en"; | |||
| } | |||
| }, [messageIdOrText, intl]); | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| import { StrictMode } from 'react'; | |||
| import { StrictMode, useEffect, useContext } from 'react'; | |||
| import { createRoot } from 'react-dom/client'; | |||
| import { BrowserRouter } from 'react-router-dom'; | |||
| import "./assets/style/styles.css" | |||
| @@ -16,9 +16,34 @@ import 'assets/third-party/apex-chart.css'; | |||
| import App from './App'; | |||
| import { store } from 'store'; | |||
| import reportWebVitals from './reportWebVitals'; | |||
| import {I18nProvider} from "components/I18nProvider"; | |||
| import {AutoLogoutProvider} from "components/AutoLogoutProvider"; | |||
| import {RefreshTokenProvider} from "components/RefreshTokenProvider"; | |||
| import { I18nProvider } from "components/I18nProvider"; | |||
| import { AutoLogoutProvider } from "components/AutoLogoutProvider"; | |||
| import { RefreshTokenProvider } from "components/RefreshTokenProvider"; | |||
| import { SysSettingProvider, SysContext } from 'components/SysSettingProvider'; | |||
| import { useLocation } from 'react-router-dom'; | |||
| function GreyWrapper({ children }) { | |||
| const location = useLocation(); | |||
| const { sysSetting } = useContext(SysContext); | |||
| useEffect(() => { | |||
| const isLoginPage = location.pathname === '/login'; | |||
| const enableGrey = sysSetting?.greyLogin === true; | |||
| if (isLoginPage && enableGrey) { | |||
| document.body.classList.add('page-grey'); | |||
| } else { | |||
| document.body.classList.remove('page-grey'); | |||
| } | |||
| return () => { | |||
| document.body.classList.remove('page-grey'); | |||
| }; | |||
| }, [location.pathname, sysSetting?.greyLogin]); | |||
| return children; | |||
| } | |||
| // ==============================|| MAIN - REACT DOM RENDER ||============================== // | |||
| @@ -26,18 +51,23 @@ const container = document.getElementById('root'); | |||
| const root = createRoot(container); // createRoot(container!) if you use TypeScript | |||
| //const NotAuthorized = lazy(() => import('../views/NotAuthorized')) | |||
| //const Error = lazy(() => import('../views/Error')) | |||
| root.render( | |||
| <StrictMode> | |||
| <ReduxProvider store={store}> | |||
| <SysSettingProvider> | |||
| <I18nProvider> | |||
| <BrowserRouter basename="/"> | |||
| <RefreshTokenProvider> | |||
| <AutoLogoutProvider> | |||
| <GreyWrapper> | |||
| <App /> | |||
| </GreyWrapper> | |||
| </AutoLogoutProvider> | |||
| </RefreshTokenProvider> | |||
| </BrowserRouter> | |||
| </I18nProvider> | |||
| </SysSettingProvider> | |||
| </ReduxProvider> | |||
| </StrictMode> | |||
| ); | |||
| @@ -4,6 +4,7 @@ import { useMemo } from 'react'; | |||
| // material-ui | |||
| import { useTheme } from '@mui/material/styles'; | |||
| import { Box, Drawer, useMediaQuery } from '@mui/material'; | |||
| import { useIntl } from 'react-intl'; | |||
| // project import | |||
| import DrawerHeader from './DrawerHeader'; | |||
| @@ -15,6 +16,7 @@ import { drawerWidth } from 'config'; | |||
| const MainDrawer = ({ open, handleDrawerToggle, window }) => { | |||
| const theme = useTheme(); | |||
| const intl = useIntl(); | |||
| const matchDownMD = useMediaQuery(theme.breakpoints.down('lg')); | |||
| // responsive drawer container | |||
| @@ -25,7 +27,7 @@ const MainDrawer = ({ open, handleDrawerToggle, window }) => { | |||
| const drawerHeader = useMemo(() => <DrawerHeader open={open} />, [open]); | |||
| return ( | |||
| <Box component="nav" sx={{ flexShrink: { md: 0 }, zIndex: 1300 }} aria-label="mailbox folders"> | |||
| <Box component="nav" sx={{ flexShrink: { md: 0 }, zIndex: 1300 }} aria-label={intl.formatMessage({ id: 'ariaMailboxFolders' })}> | |||
| {!matchDownMD ? ( | |||
| <MiniDrawerStyled variant="permanent" open={open}> | |||
| {drawerHeader} | |||
| @@ -16,7 +16,7 @@ import { | |||
| import Transitions from 'components/@extended/Transitions'; | |||
| import LanguageIcon from '@mui/icons-material/Language'; | |||
| import {FormattedMessage} from "react-intl"; | |||
| import {FormattedMessage, useIntl} from "react-intl"; | |||
| import * as React from "react"; | |||
| import LocaleContext from "components/I18nProvider"; | |||
| @@ -27,6 +27,8 @@ const LocaleSelector = () => { | |||
| const matchesXs = useMediaQuery(theme.breakpoints.down('md')); | |||
| const { setLocale } = useContext(LocaleContext); | |||
| const intl = useIntl(); | |||
| const anchorRef = useRef(null); | |||
| const [open, setOpen] = useState(false); | |||
| const handleToggle = () => { | |||
| @@ -47,16 +49,26 @@ const LocaleSelector = () => { | |||
| return ( | |||
| <Box sx={{ flexShrink: 0, ml: 0.75 }}> | |||
| <IconButton | |||
| disableRipple | |||
| color="secondary" | |||
| sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor }} | |||
| aria-label="open profile" | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'profile-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| disableRipple | |||
| color="secondary" | |||
| sx={{ | |||
| color: 'text.primary', | |||
| bgcolor: open ? iconBackColorOpen : iconBackColor, | |||
| /* ✅ WCAG 2.4.7 focus indicator */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| aria-label={intl.formatMessage({id: 'openLanguage'})} | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'profile-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| > | |||
| <LanguageIcon /> | |||
| <LanguageIcon /> | |||
| </IconButton> | |||
| <Popper | |||
| placement={matchesXs ? 'bottom' : 'bottom-end'} | |||
| @@ -96,8 +108,9 @@ const LocaleSelector = () => { | |||
| <ListItem disablePadding> | |||
| <ListItemButton | |||
| onClick={() => { | |||
| setLocale("en") | |||
| setLocale("en"); | |||
| localStorage.setItem('locale','en'); | |||
| setOpen(false); | |||
| }} | |||
| > | |||
| <ListItemText | |||
| @@ -108,8 +121,9 @@ const LocaleSelector = () => { | |||
| <ListItem disablePadding> | |||
| <ListItemButton | |||
| onClick={() => { | |||
| setLocale("zh-HK") | |||
| setLocale("zh-HK"); | |||
| localStorage.setItem('locale','zh-HK'); | |||
| setOpen(false); | |||
| }} | |||
| > | |||
| <ListItemText | |||
| @@ -120,8 +134,9 @@ const LocaleSelector = () => { | |||
| <ListItem disablePadding> | |||
| <ListItemButton | |||
| onClick={() => { | |||
| setLocale("zh-CN") | |||
| setLocale("zh-CN"); | |||
| localStorage.setItem('locale','zh-CN'); | |||
| setOpen(false); | |||
| }} | |||
| > | |||
| <ListItemText | |||
| @@ -45,19 +45,26 @@ const MobileSection = () => { | |||
| <> | |||
| <Box sx={{ flexShrink: 0, ml: 0.75 }}> | |||
| <IconButton | |||
| component="span" | |||
| disableRipple | |||
| sx={{ | |||
| bgcolor: open ? 'grey.300' : 'grey.100' | |||
| }} | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'menu-list-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| color="inherit" | |||
| > | |||
| <MoreOutlined /> | |||
| </IconButton> | |||
| component="span" | |||
| disableRipple | |||
| sx={{ | |||
| bgcolor: open ? 'grey.300' : 'grey.100', | |||
| /* WCAG 2.4.7 – visible keyboard focus */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'menu-list-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| color="inherit" | |||
| > | |||
| <MoreOutlined /> | |||
| </IconButton> | |||
| </Box> | |||
| <Popper | |||
| placement="bottom-end" | |||
| @@ -71,17 +71,27 @@ const Notification = () => { | |||
| <IconButton | |||
| disableRipple | |||
| color="secondary" | |||
| sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor }} | |||
| aria-label="open profile" | |||
| sx={{ | |||
| color: 'text.primary', | |||
| bgcolor: open ? iconBackColorOpen : iconBackColor, | |||
| /* ✅ WCAG 2.4.7 focus indicator */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| aria-label={intl.formatMessage({id: 'openLanguage'})} | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'profile-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| > | |||
| <Badge badgeContent={4} color="primary"> | |||
| <BellOutlined /> | |||
| </Badge> | |||
| </IconButton> | |||
| <Badge badgeContent={4} color="primary"> | |||
| <BellOutlined /> | |||
| </Badge> | |||
| </IconButton> | |||
| <Popper | |||
| placement={matchesXs ? 'bottom' : 'bottom-end'} | |||
| open={open} | |||
| @@ -33,6 +33,7 @@ import { LogoutOutlined, | |||
| import { handleLogoutFunction } from 'auth/index'; | |||
| import {useNavigate} from "react-router-dom"; | |||
| import {useDispatch} from "react-redux"; | |||
| import { useIntl } from 'react-intl'; | |||
| import AccountCircleIcon from '@mui/icons-material/AccountCircle'; | |||
| // tab panel wrapper | |||
| @@ -61,6 +62,7 @@ TabPanel.propTypes = { | |||
| const Profile = () => { | |||
| const theme = useTheme(); | |||
| const intl = useIntl(); | |||
| const navigate = useNavigate() | |||
| const dispatch = useDispatch() | |||
| @@ -101,7 +103,7 @@ const Profile = () => { | |||
| borderRadius: 1, | |||
| '&:hover': { bgcolor: 'secondary.lighter' } | |||
| }} | |||
| aria-label="open profile" | |||
| aria-label={intl.formatMessage({id: 'openLanguage'})} | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'profile-grow' : undefined} | |||
| aria-haspopup="true" | |||
| @@ -170,7 +172,7 @@ const Profile = () => { | |||
| {/* {open && ( | |||
| <> | |||
| <Box sx={{ borderBottom: 1, borderColor: 'divider' }}> | |||
| <Tabs variant="fullWidth" value={value} onChange={handleChange} aria-label="profile tabs"> | |||
| <Tabs variant="fullWidth" value={value} onChange={handleChange} aria-label={intl.formatMessage({ id: 'ariaProfileTabs' })}> | |||
| <Tab | |||
| sx={{ | |||
| display: 'flex', | |||
| @@ -1,6 +1,7 @@ | |||
| import { useEffect, useState } from 'react'; | |||
| import { Outlet } from 'react-router-dom'; | |||
| import { useDispatch, useSelector } from 'react-redux'; | |||
| import { useLocation } from 'react-router-dom'; | |||
| // material-ui | |||
| import { useTheme } from '@mui/material/styles'; | |||
| @@ -31,7 +32,8 @@ const MainLayout = () => { | |||
| const theme = useTheme(); | |||
| const matchDownLG = useMediaQuery(theme.breakpoints.down('lg')); | |||
| const dispatch = useDispatch(); | |||
| const location = useLocation(); | |||
| const hideNavbarRoutes = ['/databaseHealthCheck'] | |||
| const { drawerOpen } = useSelector((state) => state.menu); | |||
| // drawer toggler | |||
| @@ -55,18 +57,26 @@ const MainLayout = () => { | |||
| }, [drawerOpen]); | |||
| return ( | |||
| <Box sx={{backgroundColor:'#ffffff', display: 'flex', width: '100%', flexDirection: "column", paddingTop: { xs: "5px", sm: "25px", md: "43px" }}}> | |||
| <Header/> | |||
| {/* <Drawer open={open} handleDrawerToggle={handleDrawerToggle} /> */} | |||
| <Box style={{ width: '100%', flexGrow: 1 } } sx={{ paddingTop: "38px" }}> | |||
| {/* <Toolbar /> */} | |||
| {/* <Breadcrumbs navigation={navigation} title /> */} | |||
| <Outlet /> | |||
| </Box> | |||
| <Box sx={{borderTop: "3px solid #0C489E"}}> | |||
| <Footer/> | |||
| </Box> | |||
| </Box> | |||
| <> | |||
| {!hideNavbarRoutes.includes(location.pathname) && ( | |||
| <Box sx={{backgroundColor:'#ffffff', display: 'flex', width: '100%', flexDirection: "column", paddingTop: { xs: "5px", sm: "25px", md: "43px" }}}> | |||
| <Header/> | |||
| {/* <Drawer open={open} handleDrawerToggle={handleDrawerToggle} /> */} | |||
| <Box style={{ width: '100%', flexGrow: 1 }} sx={{ paddingTop: "36px" }}> | |||
| {/* <Toolbar /> */} | |||
| {/* <Breadcrumbs navigation={navigation} title /> */} | |||
| <Outlet /> | |||
| </Box> | |||
| <Box sx={{borderTop: "3px solid #0C489E"}}> | |||
| <Footer/> | |||
| </Box> | |||
| </Box> | |||
| )} | |||
| {hideNavbarRoutes.includes(location.pathname) && ( | |||
| <Outlet /> | |||
| )} | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -10,7 +10,7 @@ import { clickableLink } from 'utils/CommonFunction'; | |||
| import {GET_ANNOUNCE_LIST} from "utils/ApiPathConst"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function SearchPublicNoticeTable({ searchCriteria }) { | |||
| export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnReady, applySearch}) { | |||
| const navigate = useNavigate() | |||
| @@ -73,10 +73,16 @@ export default function SearchPublicNoticeTable({ searchCriteria }) { | |||
| customPageSize={10} | |||
| getRowHeight={() => 'auto'} | |||
| onRowDoubleClick={handleRowDoubleClick} | |||
| doLoad={{ | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch = {applySearch} | |||
| // doLoad={{ | |||
| // url: GET_ANNOUNCE_LIST, | |||
| // params: _searchCriteria, | |||
| // }} | |||
| doLoad={React.useMemo(() => ({ | |||
| url: GET_ANNOUNCE_LIST, | |||
| params: _searchCriteria, | |||
| }} | |||
| }), [_searchCriteria])} | |||
| /> | |||
| </div> | |||
| ); | |||
| @@ -17,8 +17,9 @@ import dayjs from "dayjs"; | |||
| import {DemoItem} from "@mui/x-date-pickers/internals/demo"; | |||
| import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; | |||
| import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const SearchPublicNoticeForm = ({ applySearch, searchCriteria}) => { | |||
| const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady}) => { | |||
| const navigate = useNavigate() | |||
| const [minDate, setMinDate] = React.useState(searchCriteria.dateFrom); | |||
| @@ -50,6 +51,8 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria}) => { | |||
| key: data.key, | |||
| dateFrom: sentDateFrom, | |||
| dateTo: sentDateTo, | |||
| start:0, | |||
| limit:10 | |||
| }; | |||
| applySearch(temp); | |||
| }; | |||
| @@ -58,7 +61,8 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria}) => { | |||
| function resetForm() { | |||
| setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | |||
| setMaxDate(DateUtils.dateValue(new Date())) | |||
| reset(); | |||
| reset({key:""}); | |||
| localStorage.setItem('searchCriteria',"") | |||
| } | |||
| @@ -180,6 +184,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria}) => { | |||
| <Button | |||
| variant="contained" | |||
| type="submit" | |||
| disabled={onGridReady} | |||
| > | |||
| Submit | |||
| </Button> | |||
| @@ -7,6 +7,7 @@ import { | |||
| import MainCard from "components/MainCard"; | |||
| import * as React from "react"; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import { getSearchCriteria } from "auth/utils"; | |||
| import Loadable from 'components/Loadable'; | |||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | |||
| @@ -28,20 +29,37 @@ const BackgroundHead = { | |||
| const UserSearchPage_Individual = () => { | |||
| const [searchCriteria, setSearchCriteria] = React.useState({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate() - 90)), | |||
| }); | |||
| const [searchCriteria, setSearchCriteria] = React.useState({}); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| const [onGridReady, setGridOnReady] = React.useState(false); | |||
| React.useEffect(() => { | |||
| if (Object.keys(getSearchCriteria(window.location.pathname)).length>0){ | |||
| setSearchCriteria(getSearchCriteria(window.location.pathname)) | |||
| }else{ | |||
| localStorage.setItem('searchCriteria',"") | |||
| setSearchCriteria({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }) | |||
| } | |||
| }, []); | |||
| React.useEffect(() => { | |||
| setOnReady(true); | |||
| }, [searchCriteria]); | |||
| function applySearch(input) { | |||
| setGridOnReady(true) | |||
| setSearchCriteria(input); | |||
| localStorage.setItem('searchCriteria', JSON.stringify({path:window.location.pathname,data:input})) | |||
| } | |||
| function applyGridOnReady(input) { | |||
| setGridOnReady(input); | |||
| } | |||
| return ( | |||
| !onReady ? | |||
| <Grid container sx={{ minHeight: '95vh', mb: 3 }} direction="column" justifyContent="center" alignItems="center"> | |||
| @@ -63,6 +81,7 @@ const UserSearchPage_Individual = () => { | |||
| <SearchForm | |||
| applySearch={applySearch} | |||
| searchCriteria={searchCriteria} | |||
| onGridReady={onGridReady} | |||
| /> | |||
| </Grid> | |||
| {/*row 2*/} | |||
| @@ -73,7 +92,9 @@ const UserSearchPage_Individual = () => { | |||
| sx={{ backgroundColor: '#fff' }} | |||
| > | |||
| <EventTable | |||
| searchCriteria={searchCriteria} | |||
| searchCriteria={searchCriteria} | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| /> | |||
| </MainCard> | |||
| </Grid> | |||
| @@ -6,7 +6,7 @@ import { FormattedMessage, useIntl } from "react-intl"; | |||
| import {GET_ANNOUNCE_LIST} from "utils/ApiPathConst"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function SearchPublicNoticeTable({ searchCriteria }) { | |||
| export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnReady,applySearch }) { | |||
| const intl = useIntl(); | |||
| const { locale } = intl; | |||
| @@ -57,10 +57,16 @@ export default function SearchPublicNoticeTable({ searchCriteria }) { | |||
| columns={columns} | |||
| customPageSize={10} | |||
| getRowHeight={() => 'auto'} | |||
| doLoad={{ | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| // doLoad={{ | |||
| // url: GET_ANNOUNCE_LIST, | |||
| // params: _searchCriteria | |||
| // }} | |||
| doLoad={React.useMemo(() => ({ | |||
| url: GET_ANNOUNCE_LIST, | |||
| params: _searchCriteria | |||
| }} | |||
| params: _searchCriteria, | |||
| }), [_searchCriteria])} | |||
| /> | |||
| </div> | |||
| ); | |||
| @@ -19,7 +19,7 @@ import {DemoItem} from "@mui/x-date-pickers/internals/demo"; | |||
| import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; | |||
| import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => { | |||
| // const navigate = useNavigate() | |||
| const [minDate, setMinDate] = React.useState(searchCriteria.dateFrom); | |||
| @@ -39,6 +39,22 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| setToDateValue(maxDate); | |||
| }, [maxDate]); | |||
| const _sx = { | |||
| padding: "4 2 4 2", | |||
| boxShadow: 1, | |||
| border: 1, | |||
| borderColor: '#DDD', | |||
| '& .MuiDataGrid-cell': { | |||
| borderTop: 1, | |||
| borderBottom: 1, | |||
| borderColor: "#EEE" | |||
| }, | |||
| '& .MuiDataGrid-footerContainer': { | |||
| border: 1, | |||
| borderColor: "#EEE" | |||
| } | |||
| } | |||
| const marginBottom = 2.5; | |||
| const { reset, register, handleSubmit } = useForm() | |||
| const onSubmit = (data) => { | |||
| @@ -52,6 +68,8 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| key: data.key, | |||
| dateFrom: sentDateFrom, | |||
| dateTo: sentDateTo, | |||
| start:0, | |||
| limit:10 | |||
| }; | |||
| applySearch(temp); | |||
| }; | |||
| @@ -60,7 +78,8 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| function resetForm() { | |||
| setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | |||
| setMaxDate(DateUtils.dateValue(new Date())) | |||
| reset(); | |||
| reset({key:""}); | |||
| localStorage.setItem('searchCriteria',"") | |||
| } | |||
| @@ -68,7 +87,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| <MainCard xs={12} md={12} lg={12} | |||
| border={false} | |||
| content={false} | |||
| sx={{ backgroundColor: '#fff' }} | |||
| sx={_sx} | |||
| > | |||
| <form onSubmit={handleSubmit(onSubmit)}> | |||
| @@ -97,7 +116,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| <Grid item xs={12} s={6} md={6} lg={4} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <Grid container> | |||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateFrom" | |||
| @@ -129,7 +148,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| </Grid> | |||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateTo" | |||
| @@ -176,7 +195,9 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| <Grid item sx={{ ml: 3 }}> | |||
| <Button | |||
| variant="contained" | |||
| color="cancel" | |||
| onClick={resetForm} | |||
| aria-label={intl.formatMessage({ id: 'reset' })} | |||
| > | |||
| <FormattedMessage id="reset"></FormattedMessage> | |||
| </Button> | |||
| @@ -186,6 +207,8 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| <Button | |||
| variant="contained" | |||
| type="submit" | |||
| disabled={onGridReady} | |||
| aria-label={intl.formatMessage({id: 'submit'})} | |||
| > | |||
| <FormattedMessage id="submit"></FormattedMessage> | |||
| </Button> | |||
| @@ -14,6 +14,8 @@ const SearchForm = Loadable(React.lazy(() => import('./SearchForm'))); | |||
| const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); | |||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | |||
| import { FormattedMessage } from "react-intl"; | |||
| import { getSearchCriteria } from "auth/utils"; | |||
| import usePageTitle from "components/usePageTitle"; | |||
| const BackgroundHead = { | |||
| backgroundImage: `url(${titleBackgroundImg})`, | |||
| @@ -28,20 +30,37 @@ const BackgroundHead = { | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const UserSearchPage_Individual = () => { | |||
| const [searchCriteria, setSearchCriteria] = React.useState({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate() - 90)), | |||
| }); | |||
| usePageTitle("announcement"); | |||
| const [searchCriteria, setSearchCriteria] = React.useState({}); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| const [onGridReady, setGridOnReady] = React.useState(false); | |||
| React.useEffect(() => { | |||
| if (Object.keys(getSearchCriteria(window.location.pathname)).length>0){ | |||
| setSearchCriteria(getSearchCriteria(window.location.pathname)) | |||
| }else{ | |||
| localStorage.setItem('searchCriteria',"") | |||
| setSearchCriteria({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }) | |||
| } | |||
| }, []); | |||
| React.useEffect(() => { | |||
| setOnReady(true); | |||
| if(Object.keys(searchCriteria).length>0){ | |||
| setOnReady(true); | |||
| } | |||
| }, [searchCriteria]); | |||
| function applySearch(input) { | |||
| setSearchCriteria(input); | |||
| localStorage.setItem('searchCriteria', JSON.stringify({path:window.location.pathname,data:input})) | |||
| } | |||
| function applyGridOnReady(input) { | |||
| setGridOnReady(input); | |||
| } | |||
| return ( | |||
| @@ -56,7 +75,7 @@ const UserSearchPage_Individual = () => { | |||
| <Grid item xs={12}> | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{ "textShadow": "0px 0px 25px #0C489E" }}><FormattedMessage id="announcement" /></Typography> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{ "textShadow": "0px 0px 25px #0C489E" }}><FormattedMessage id="announcement" /></Typography> | |||
| </Stack> | |||
| </div> | |||
| </Grid> | |||
| @@ -65,6 +84,7 @@ const UserSearchPage_Individual = () => { | |||
| <SearchForm | |||
| applySearch={applySearch} | |||
| searchCriteria={searchCriteria} | |||
| onGridReady={onGridReady} | |||
| /> | |||
| </Grid> | |||
| {/*row 2*/} | |||
| @@ -76,6 +96,8 @@ const UserSearchPage_Individual = () => { | |||
| > | |||
| <EventTable | |||
| searchCriteria={searchCriteria} | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| /> | |||
| </MainCard> | |||
| </Grid> | |||
| @@ -21,16 +21,18 @@ import {ThemeProvider} from "@emotion/react"; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import * as UrlUtils from "utils/ApiPathConst"; | |||
| import * as HttpUtils from "utils/HttpUtils"; | |||
| import Loadable from 'components/Loadable'; | |||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | |||
| // import Loadable from 'components/Loadable'; | |||
| // const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | |||
| import {DatePicker} from "@mui/x-date-pickers/DatePicker"; | |||
| import dayjs from "dayjs"; | |||
| import {DemoItem} from "@mui/x-date-pickers/internals/demo"; | |||
| import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; | |||
| import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; | |||
| import { isGranted } from "auth/utils"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const AuditLogSearchForm = ({ applySearch, searchCriteria}) => { | |||
| const AuditLogSearchForm = ({ applySearch, searchCriteria, onGridReady}) => { | |||
| // const navigate = useNavigate(); | |||
| const [minDate, setMinDate] = React.useState(searchCriteria.modifiedFrom); | |||
| @@ -64,6 +66,8 @@ const AuditLogSearchForm = ({ applySearch, searchCriteria}) => { | |||
| username: data.userName, | |||
| modifiedTo: sentDateTo, | |||
| modifiedFrom: sentDateFrom, | |||
| start:0, | |||
| limit:10 | |||
| }; | |||
| applySearch(temp); | |||
| }; | |||
| @@ -77,6 +81,7 @@ const AuditLogSearchForm = ({ applySearch, searchCriteria}) => { | |||
| setOnDownload(true) | |||
| HttpUtils.fileDownload({ | |||
| url: UrlUtils.AUDIT_LOG_EXPORT, | |||
| params: searchCriteria, | |||
| onResponse:()=>{ | |||
| setOnDownload(false) | |||
| }, | |||
| @@ -185,18 +190,17 @@ const AuditLogSearchForm = ({ applySearch, searchCriteria}) => { | |||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||
| <Grid item xs={12} md={12}> | |||
| <Grid container maxWidth justifyContent="flex-end"> | |||
| <Grid item sx={{ ml: 3, mr: 3, mb: 3,}}> | |||
| {onDownload? | |||
| <LoadingComponent disableText={true} alignItems="flex-start"/> | |||
| : | |||
| {isGranted("MAINTAIN_SETTING") ? | |||
| <Grid item sx={{ ml: 3, mr: 3, mb: 3,}}> | |||
| <Button | |||
| variant="contained" | |||
| onClick={exportExcel} | |||
| disabled={onDownload} | |||
| > | |||
| Export | |||
| </Button> | |||
| } | |||
| </Grid> | |||
| </Grid> : null | |||
| } | |||
| <Grid item sx={{ ml: 3, mr: 3, mb: 3,}}> | |||
| <Button | |||
| variant="contained" | |||
| @@ -210,6 +214,7 @@ const AuditLogSearchForm = ({ applySearch, searchCriteria}) => { | |||
| <Button | |||
| variant="contained" | |||
| type="submit" | |||
| disabled={onGridReady} | |||
| > | |||
| Search | |||
| </Button> | |||
| @@ -12,7 +12,7 @@ import { | |||
| } from '@mui/material'; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function AuditLogTable({searchCriteria}) { | |||
| export default function AuditLogTable({searchCriteria, applyGridOnReady,applySearch}) { | |||
| const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria); | |||
| useEffect(() => { | |||
| @@ -87,10 +87,16 @@ export default function AuditLogTable({searchCriteria}) { | |||
| columns={columns} | |||
| customPageSize={10} | |||
| getRowHeight={() => 'auto'} | |||
| doLoad={{ | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| // doLoad={{ | |||
| // url: GET_AUDIT_LOG_LIST, | |||
| // params: _searchCriteria | |||
| // }} | |||
| doLoad={React.useMemo(() => ({ | |||
| url: GET_AUDIT_LOG_LIST, | |||
| params: _searchCriteria | |||
| }} | |||
| params: _searchCriteria, | |||
| }), [_searchCriteria])} | |||
| /> | |||
| </div> | |||
| ); | |||
| @@ -8,6 +8,7 @@ import { | |||
| import MainCard from "components/MainCard"; | |||
| import { useEffect, useState } from "react"; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import * as React from "react"; | |||
| import Loadable from 'components/Loadable'; | |||
| import { lazy } from 'react'; | |||
| @@ -33,15 +34,21 @@ const AuditLogPage = () => { | |||
| modifiedFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }); | |||
| const [onReady, setOnReady] = useState(false); | |||
| const [onGridReady, setGridOnReady] = React.useState(false); | |||
| useEffect(() => { | |||
| setOnReady(true); | |||
| }, [searchCriteria]); | |||
| function applySearch(input) { | |||
| setGridOnReady(true) | |||
| setSearchCriteria(input); | |||
| } | |||
| function applyGridOnReady(input) { | |||
| setGridOnReady(input); | |||
| } | |||
| return ( | |||
| !onReady ? | |||
| <Grid container sx={{ minHeight: '87vh', mb: 3 }} direction="column" justifyContent="center" alignItems="center"> | |||
| @@ -64,7 +71,7 @@ const AuditLogPage = () => { | |||
| <SearchForm | |||
| applySearch={applySearch} | |||
| searchCriteria={searchCriteria} | |||
| onGridReady={onGridReady} | |||
| /> | |||
| </Grid> | |||
| {/*row 2*/} | |||
| @@ -75,6 +82,8 @@ const AuditLogPage = () => { | |||
| > | |||
| <EventTable | |||
| searchCriteria={searchCriteria} | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| /> | |||
| </MainCard> | |||
| </Grid> | |||
| @@ -13,7 +13,7 @@ import * as DateUtils from "utils/DateUtils"; | |||
| import * as UrlUtils from "utils/ApiPathConst"; | |||
| import * as HttpUtils from "utils/HttpUtils"; | |||
| import { useNavigate } from "react-router-dom"; | |||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||
| import { notifyActionError } from 'utils/CommonFunction'; | |||
| import { PNSPS_BUTTON_THEME } from "../../../themes/buttonConst"; | |||
| import { ThemeProvider } from "@emotion/react"; | |||
| import { useIntl } from "react-intl"; | |||
| @@ -118,8 +118,11 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData, _paymentCount, _p | |||
| params: { | |||
| "dnIdList": dnIdList | |||
| }, | |||
| onSuccess: function () { | |||
| notifyDownloadSuccess(); | |||
| onResponse: function () { | |||
| // 200: browser handles save; no success toast | |||
| }, | |||
| onError: function () { | |||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||
| } | |||
| }); | |||
| } | |||
| @@ -136,6 +139,8 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData, _paymentCount, _p | |||
| } | |||
| const temp = { | |||
| issueId: issueSelected.id, | |||
| start:0, | |||
| limit:10 | |||
| }; | |||
| applySearch(temp); | |||
| }; | |||
| @@ -171,6 +176,11 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData, _paymentCount, _p | |||
| setIssueSelected(newValue); | |||
| } | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label="Gazette Issue" | |||
| @@ -179,6 +189,10 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData, _paymentCount, _p | |||
| }} | |||
| /> | |||
| )} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| <Grid item > | |||
| @@ -15,11 +15,12 @@ import * as StatusUtils from "utils/statusUtils/PublicNoteStatusUtils"; | |||
| import * as HttpUtils from "utils/HttpUtils"; | |||
| import DownloadIcon from '@mui/icons-material/Download'; | |||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||
| import { notifyActionError } from 'utils/CommonFunction'; | |||
| import { useIntl } from 'react-intl'; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const ApplicationDetailCard = ({ data }) => { | |||
| const intl = useIntl(); | |||
| const [appDetail, setAppDetails] = React.useState({}); | |||
| React.useEffect(() => { | |||
| @@ -33,8 +34,11 @@ const ApplicationDetailCard = ({ data }) => { | |||
| fileId: appDetail.appFileId, | |||
| skey: appDetail.appSkey, | |||
| filename: appDetail.appFilename, | |||
| onResponse: function () {}, | |||
| onError: function () { | |||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||
| } | |||
| }); | |||
| notifyDownloadSuccess(); | |||
| }; | |||
| return ( | |||
| @@ -143,7 +147,7 @@ const ApplicationDetailCard = ({ data }) => { | |||
| </Grid> | |||
| <Grid container direction="row" justifyContent="space-between" | |||
| alignItems="center"> | |||
| <Grid item xs={12} md={6} lg={6} mt={1}> | |||
| <Grid item xs={12} md={6} lg={6} mt={1} mb={2}> | |||
| <Grid container alignItems={"center"}> | |||
| <Grid item xs={12} md={12} lg={12}> | |||
| <Grid container direction="row"> | |||
| @@ -14,12 +14,13 @@ import Loadable from 'components/Loadable'; | |||
| const MainCard = Loadable(React.lazy(() => import('components/MainCard'))); | |||
| import DownloadIcon from '@mui/icons-material/Download'; | |||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||
| import { notifyActionError } from 'utils/CommonFunction'; | |||
| import { useIntl } from 'react-intl'; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const DnDetailCard = ({ data }) => { | |||
| const intl = useIntl(); | |||
| const [dnData, setDnData] = React.useState({}); | |||
| React.useEffect(() => { | |||
| @@ -33,8 +34,9 @@ const DnDetailCard = ({ data }) => { | |||
| fileId: dnData.fileId, | |||
| skey: dnData.skey, | |||
| filename: dnData.filename, | |||
| onResponse: function () { | |||
| notifyDownloadSuccess(); | |||
| onResponse: function () {}, | |||
| onError: function () { | |||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||
| } | |||
| }); | |||
| }; | |||
| @@ -77,9 +77,9 @@ export default function SearchPublicNoticeTable({ searchCriteria,}) { | |||
| // let user = params.row.enCompanyName != null ? params.row.enCompanyName : params.row.chCompanyName; | |||
| let user = params.row.contactPerson; | |||
| // user = user != null ? user : ""; | |||
| if (params.row.sysType != null && params.row.sysType == "dummy"){ | |||
| user = "Dummy - PD" | |||
| } | |||
| // if (params.row.sysType != null && params.row.sysType == "dummy"){ | |||
| // user = "Dummy - PD" | |||
| // } | |||
| return <div> | |||
| {user} | |||
| </div>; | |||
| @@ -95,7 +95,7 @@ export default function SearchPublicNoticeTable({ searchCriteria,}) { | |||
| let company = params.row.enCompanyName != null ? params.row.enCompanyName : params.row.chCompanyName; | |||
| company = company != null ? company : ""; | |||
| if (params.row.sysType != null && params.row.sysType == "dummy"){ | |||
| company = params.row.contactPerson | |||
| company = params.row.custName | |||
| } | |||
| return <div> | |||
| {company} | |||
| @@ -109,7 +109,13 @@ export default function SearchPublicNoticeTable({ searchCriteria,}) { | |||
| flex: 2, | |||
| minWidth: 200, | |||
| renderCell: (params) => { | |||
| return <>{(params?.value)}</>; | |||
| let careOf = params.row.careOf | |||
| // if (params.row.sysType != null && params.row.sysType == "dummy"){ | |||
| // careOf = '' | |||
| // } | |||
| return <div> | |||
| {careOf} | |||
| </div>; | |||
| } | |||
| }, | |||
| { | |||
| @@ -120,13 +126,13 @@ export default function SearchPublicNoticeTable({ searchCriteria,}) { | |||
| minWidth: 100, | |||
| valueGetter: (params) => { | |||
| let length = params.row.length | |||
| let colCount = params.row.colCount | |||
| // let colCount = params.row.colCount | |||
| let noOfPages = params.row.noOfPages | |||
| let dimension = 0 | |||
| if (noOfPages != null){ | |||
| dimension = length*colCount*noOfPages | |||
| dimension = length*noOfPages | |||
| }else{ | |||
| dimension = length*colCount | |||
| dimension = length | |||
| } | |||
| return dimension; | |||
| } | |||
| @@ -134,7 +140,7 @@ export default function SearchPublicNoticeTable({ searchCriteria,}) { | |||
| { | |||
| id: 'fee', | |||
| field: 'fee', | |||
| headerName: 'Amount(HK$)', | |||
| headerName: 'Amount($)', | |||
| flex: 1, | |||
| minWidth: 100, | |||
| valueGetter: (params) => { | |||
| @@ -137,6 +137,8 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData }) => { | |||
| } | |||
| const temp = { | |||
| issueId: issueSelected.id, | |||
| start:0, | |||
| limit:10 | |||
| }; | |||
| applySearch(temp); | |||
| }; | |||
| @@ -172,6 +174,11 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData }) => { | |||
| setIssueSelected(newValue); | |||
| } | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label="Gazette Issue" | |||
| @@ -180,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> | |||
| @@ -13,37 +13,42 @@ import * as FormatUtils from "utils/FormatUtils"; | |||
| import * as StatusUtils from "utils/statusUtils/DnStatus"; | |||
| import { useNavigate } from "react-router-dom"; | |||
| import { FiDataGrid } from "components/FiDataGrid"; | |||
| import { notifyDownloadSuccess } from 'utils/CommonFunction'; | |||
| import { notifyActionError } from 'utils/CommonFunction'; | |||
| import { | |||
| DEMAND_NOTE_EXPORT, | |||
| DEMAND_NOTE_SEND, | |||
| DEMAND_NOTE_ATTACH, | |||
| DEMAND_NOTE_MARK_PAID, | |||
| DEMAND_NOTE_LIST_ALL | |||
| DEMAND_NOTE_LIST_ALL, | |||
| DEMAND_NOTE_REVOKE_PAID | |||
| } from "utils/ApiPathConst"; | |||
| import * as HttpUtils from "utils/HttpUtils"; | |||
| import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | |||
| import { ThemeProvider } from "@emotion/react"; | |||
| import { isGrantedAny } from "auth/utils"; | |||
| import { useIntl } from "react-intl"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function SearchDemandNote({ searchCriteria, applySearch }) { | |||
| export default function SearchDemandNote({ applySearch, searchCriteria, applyGridOnReady }) { | |||
| const intl = useIntl(); | |||
| const [isConfirmPopUp, setConfirmPopUp] = useState(false); | |||
| const [isRevokePopUp, setRevokePopUp] = useState(false); | |||
| const [isSendPopUp, setSendPopUp] = useState(false); | |||
| const [isErrorPopUp, setIsErrorPopUp] = useState(false); | |||
| const [selectonWarning, setSelectonWarning] = useState(false); | |||
| const [wait, setWait] = useState(false); | |||
| const [reload, setReload] = useState(new Date()); | |||
| const [rows, setRows] = useState([]); | |||
| const [_searchCriteria, set_searchCriteria] = useState(searchCriteria); | |||
| const [_searchCriteria, set_searchCriteria] = useState({}); | |||
| const [selectedRowItems, setSelectedRowItems] = useState([]); | |||
| const navigate = useNavigate() | |||
| const [onDownload, setOnDownload] = useState(false); | |||
| useEffect(() => { | |||
| set_searchCriteria(searchCriteria); | |||
| }, [searchCriteria]); | |||
| const handleDnClick = (params) => () => { | |||
| navigate('/paymentPage/demandNote/details/' + params.id); | |||
| }; | |||
| @@ -76,17 +81,26 @@ export default function SearchDemandNote({ searchCriteria, applySearch }) { | |||
| params: { | |||
| dnIdList: idList | |||
| }, | |||
| onSuccess: function () { | |||
| notifyDownloadSuccess(); | |||
| onResponse: function () {}, | |||
| onError: function () { | |||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||
| } | |||
| }); | |||
| } | |||
| const onDownloadClick = (params) => () => { | |||
| setOnDownload(true) | |||
| HttpUtils.fileDownload({ | |||
| fileId: params.row.fileId, | |||
| skey: params.row.skey, | |||
| filename: params.row.filename, | |||
| onResponse: () => { | |||
| setOnDownload(false); | |||
| }, | |||
| onError: () => { | |||
| setOnDownload(false); | |||
| notifyActionError(intl.formatMessage({ id: 'downloadFailed' })); | |||
| } | |||
| }); | |||
| }; | |||
| @@ -109,7 +123,7 @@ export default function SearchDemandNote({ searchCriteria, applySearch }) { | |||
| dnIdList: idList | |||
| }, | |||
| onSuccess: () => { | |||
| if (reloadFun) reloadFun(); | |||
| setReload(new Date()); | |||
| } | |||
| }); | |||
| @@ -133,13 +147,36 @@ export default function SearchDemandNote({ searchCriteria, applySearch }) { | |||
| }, | |||
| files: [file], | |||
| onSuccess() { | |||
| setWait(false); | |||
| if (reloadFun) reloadFun(); | |||
| setReload(new Date()); | |||
| }, | |||
| }); | |||
| document.getElementById("uploadFileBtn").value = ""; | |||
| } | |||
| const revokePaid = () => { | |||
| setRevokePopUp(false); | |||
| let idList = []; | |||
| const datas = rows?.filter((row) => | |||
| selectedRowItems.includes(row.id) | |||
| ); | |||
| if (datas?.length < 1) { | |||
| setSelectonWarning(true); | |||
| return; | |||
| } | |||
| for (var i = 0; i < datas?.length; i++) { | |||
| idList.push(datas[i].id); | |||
| } | |||
| HttpUtils.post({ | |||
| url: DEMAND_NOTE_REVOKE_PAID, | |||
| params: { | |||
| dnIdList: idList | |||
| }, | |||
| onSuccess: () => { | |||
| setReload(new Date()); | |||
| } | |||
| }); | |||
| } | |||
| const markPaid = () => { | |||
| setConfirmPopUp(false); | |||
| let idList = []; | |||
| @@ -159,7 +196,7 @@ export default function SearchDemandNote({ searchCriteria, applySearch }) { | |||
| dnIdList: idList | |||
| }, | |||
| onSuccess: () => { | |||
| if (reloadFun) reloadFun(); | |||
| setReload(new Date()); | |||
| } | |||
| }); | |||
| } | |||
| @@ -240,9 +277,11 @@ export default function SearchDemandNote({ searchCriteria, applySearch }) { | |||
| width: 300, | |||
| renderCell: (params) => { | |||
| return (<table> | |||
| <tr><td>Issue:</td><td>{DateUtils.dateStr(params?.row.issueDate)}</td></tr> | |||
| <tr><td>Due:</td><td>{params?.value ? DateUtils.dateStr(params?.value) : "--"}</td></tr> | |||
| <tr><td>Sent:</td><td>{params.row.sentDate ? <> {DateUtils.datetimeStr(params.row.sentDate)} - {params.row.sentBy} </> : <> To be sent</>}</td></tr> | |||
| <tbody> | |||
| <tr><td>Issue:</td><td>{DateUtils.dateStr(params?.row.issueDate)}</td></tr> | |||
| <tr><td>Due:</td><td>{params?.value ? DateUtils.dateStr(params?.value) : "--"}</td></tr> | |||
| <tr><td>Sent:</td><td>{params.row.sentDate ? DateUtils.datetimeStr(params.row.sentDate) +" - "+ params.row.sentBy : "To be sent"}</td></tr> | |||
| </tbody> | |||
| </table>); | |||
| } | |||
| }, | |||
| @@ -254,7 +293,7 @@ export default function SearchDemandNote({ searchCriteria, applySearch }) { | |||
| ), | |||
| width: 280, | |||
| renderCell: (params) => { | |||
| return <Button onClick={onDownloadClick(params)}><u>{params.row.filename}</u></Button>; | |||
| return <Button disabled={onDownload} onClick={onDownloadClick(params)}><u>{params.row.filename}</u></Button>; | |||
| }, | |||
| }, | |||
| { | |||
| @@ -262,7 +301,7 @@ export default function SearchDemandNote({ searchCriteria, applySearch }) { | |||
| headerName: 'Status', | |||
| width: 175, | |||
| renderCell: (params) => { | |||
| return [StatusUtils.getStatus_Eng(params)] | |||
| return StatusUtils.getStatus_Eng(params) | |||
| }, | |||
| }, | |||
| ]; | |||
| @@ -300,6 +339,7 @@ export default function SearchDemandNote({ searchCriteria, applySearch }) { | |||
| </Button> | |||
| </label> | |||
| </Grid> | |||
| <Grid item sx={{ ml: 3, mr: 3, mb: 3, mt: 3 }}> | |||
| <Button | |||
| variant="contained" | |||
| @@ -334,13 +374,23 @@ export default function SearchDemandNote({ searchCriteria, applySearch }) { | |||
| Mark as Paid | |||
| </Button> | |||
| </Grid> | |||
| <Grid item sx={{ ml: 3, mr: 3, mb: 3, mt: 3 }}> | |||
| <Button | |||
| variant="contained" | |||
| onClick={() => setRevokePopUp(true)} | |||
| > | |||
| Revoke payment | |||
| </Button> | |||
| </Grid> | |||
| </ThemeProvider> | |||
| </Grid> | |||
| : <></> | |||
| } | |||
| <Box sx={{ backgroundColor: "#fff", ml: 2 }} width="98%"> | |||
| <FiDataGrid | |||
| checkboxSelection = {isGrantedAny(["MAINTAIN_DEMANDNOTE"])} | |||
| checkboxSelection={isGrantedAny(["MAINTAIN_DEMANDNOTE"])} | |||
| disableRowSelectionOnClick | |||
| onRowSelectionModelChange={(newSelection) => { | |||
| setSelectedRowItems(newSelection); | |||
| @@ -349,13 +399,15 @@ export default function SearchDemandNote({ searchCriteria, applySearch }) { | |||
| customPageSize={100} | |||
| getRowHeight={() => 'auto'} | |||
| onRowDoubleClick={handleRowDoubleClick} | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| doLoad={useMemo(() => ({ | |||
| url: DEMAND_NOTE_LIST_ALL, | |||
| params: _searchCriteria, | |||
| callback: function (responseData) { | |||
| setRows(responseData?.records); | |||
| } | |||
| }), [_searchCriteria])} | |||
| }), [_searchCriteria, reload])} | |||
| /> | |||
| </Box> | |||
| <div> | |||
| @@ -422,6 +474,28 @@ export default function SearchDemandNote({ searchCriteria, applySearch }) { | |||
| </DialogActions> | |||
| </Dialog> | |||
| </div> | |||
| <div> | |||
| <Dialog | |||
| open={isRevokePopUp} | |||
| onClose={() => setRevokePopUp(false)} | |||
| PaperProps={{ | |||
| sx: { | |||
| minWidth: '40vw', | |||
| maxWidth: { xs: '90vw', s: '90vw', m: '70vw', lg: '70vw' }, | |||
| maxHeight: { xs: '90vh', s: '70vh', m: '70vh', lg: '60vh' } | |||
| } | |||
| }} | |||
| > | |||
| <DialogTitle><Typography variant="h3">Confirm</Typography></DialogTitle> | |||
| <DialogContent style={{ display: 'flex', }}> | |||
| <Typography variant="h4" style={{ padding: '16px' }}>Are you sure to revoke DN as To Be Paid?</Typography> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setRevokePopUp(false)}><Typography variant="h5">Cancel</Typography></Button> | |||
| <Button onClick={() => revokePaid()}><Typography variant="h5">Confirm</Typography></Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| </div> | |||
| <div> | |||
| <Dialog | |||
| open={isSendPopUp} | |||
| @@ -21,7 +21,7 @@ import {DemoItem} from "@mui/x-date-pickers/internals/demo"; | |||
| import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; | |||
| import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issueComboData | |||
| const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issueComboData, onGridReady | |||
| }) => { | |||
| const [type, setType] = React.useState([]); | |||
| @@ -44,6 +44,28 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| const intl = useIntl(); | |||
| const { locale } = intl; | |||
| React.useEffect(() => { | |||
| if(searchCriteria.status!=undefined){ | |||
| if(searchCriteria.status === ""){ | |||
| ComboData.denmandNoteStatus[0] | |||
| }else{ | |||
| setSelectedStatus(ComboData.denmandNoteStatus.find(item => item.type === searchCriteria.status)) | |||
| } | |||
| if(searchCriteria.dueDateFrom != ""){ | |||
| setMinDueDate(DateUtils.dateValue(searchCriteria.dueDateFrom)) | |||
| }else{ | |||
| setMinDueDate(null) | |||
| } | |||
| if(searchCriteria.dueDateTo != ""){ | |||
| setMaxDueDate(DateUtils.dateValue(searchCriteria.dueDateTo)) | |||
| }else{ | |||
| setMaxDueDate(null); | |||
| } | |||
| }else{ | |||
| setSelectedStatus(ComboData.denmandNoteStatus[0]) | |||
| } | |||
| }, [searchCriteria]); | |||
| React.useEffect(() => { | |||
| setFromDateValue(minDate); | |||
| }, [minDate]); | |||
| @@ -93,6 +115,8 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| dueDateFrom: sentDueDateFrom, | |||
| dueDateTo: sentDueDateTo, | |||
| status: (data?.status === '' || data?.status?.includes("all")) ? "" : data.status, | |||
| start:0, | |||
| limit:10 | |||
| }; | |||
| applySearch(temp); | |||
| }; | |||
| @@ -101,12 +125,18 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| React.useEffect(() => { | |||
| if (orgComboData && orgComboData.length > 0) { | |||
| setOrgCombo(orgComboData); | |||
| if(searchCriteria.orgId!=undefined){ | |||
| setOrgSelected(orgComboData.find(item => item.key === searchCriteria.orgId)) | |||
| } | |||
| } | |||
| }, [orgComboData]); | |||
| React.useEffect(() => { | |||
| if (issueComboData && issueComboData.length > 0) { | |||
| setIssueCombo(issueComboData); | |||
| if(searchCriteria.issueId!=undefined){ | |||
| setIssueSelected(issueComboData.find(item => item.id === searchCriteria.issueId)) | |||
| } | |||
| } | |||
| }, [issueComboData]); | |||
| @@ -117,9 +147,13 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| setSelectedStatus(ComboData.denmandNoteStatus[0]); | |||
| setMinDueDate(null); | |||
| setMaxDueDate(null); | |||
| setMinDate(searchCriteria.dateFrom); | |||
| setMaxDate(searchCriteria.dateTo); | |||
| reset(); | |||
| setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | |||
| setMaxDate(DateUtils.dateValue(new Date())) | |||
| reset({ | |||
| appNo:"", | |||
| dnNo:"", | |||
| }); | |||
| localStorage.setItem('searchCriteria',"") | |||
| } | |||
| function getIssueLabel(data) { | |||
| @@ -177,6 +211,11 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| onChange={(event, newValue) => { | |||
| setIssueSelected(newValue); | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label="Gazette Issue No." | |||
| @@ -185,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> | |||
| @@ -209,10 +252,12 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| disablePortal | |||
| id="orgId" | |||
| options={orgCombo} | |||
| groupBy={(option) => option.groupType} | |||
| size="small" | |||
| value={orgSelected} | |||
| getOptionLabel={(option) => option.name? option.name : ""} | |||
| inputValue={orgSelected ? orgSelected.name : ""} | |||
| inputValue={orgSelected ? orgSelected.name!=undefined?orgSelected.name:"" : ""} | |||
| onChange={(event, newValue) => { | |||
| if (newValue !== null) { | |||
| setOrgSelected(newValue); | |||
| @@ -220,6 +265,11 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| setOrgSelected({}); | |||
| } | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label="Organisation" | |||
| @@ -228,12 +278,23 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| }} | |||
| /> | |||
| )} | |||
| renderGroup={(params) => ( | |||
| <Grid item key={params.key}> | |||
| <Typography fontSize={20} fontStyle="italic" p={1}> | |||
| {params.group} | |||
| </Typography> | |||
| {params.children} | |||
| </Grid> | |||
| )} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| : <></> | |||
| } | |||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <TextField | |||
| fullWidth | |||
| @@ -377,6 +438,11 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| setSelectedStatus(newValue); | |||
| } | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| getOptionLabel={(option) => option.label} | |||
| renderInput={(params) => ( | |||
| <TextField | |||
| @@ -387,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> | |||
| @@ -411,6 +481,7 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue | |||
| <Button | |||
| variant="contained" | |||
| type="submit" | |||
| disabled={onGridReady} | |||
| > | |||
| Submit | |||
| </Button> | |||
| @@ -10,6 +10,7 @@ import * as React from "react"; | |||
| import * as UrlUtils from "utils/ApiPathConst"; | |||
| import * as HttpUtils from "utils/HttpUtils"; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import { getSearchCriteria } from "auth/utils"; | |||
| import Loadable from 'components/Loadable'; | |||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | |||
| @@ -35,16 +36,26 @@ const UserSearchPage_Individual = () => { | |||
| const [orgCombo, setOrgCombo] = React.useState([]); | |||
| const [issueCombo, setIssueCombo] = React.useState([]); | |||
| const [searchCriteria, setSearchCriteria] = React.useState({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate() - 14)), | |||
| // dateTo: DateUtils.dateValue(new Date()), | |||
| // dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate() - 14)), | |||
| // dueDateTo: DateUtils.dateValue(new Date()), | |||
| // dueDateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate() - 14)), | |||
| }); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| const [onGridReady, setGridOnReady] = React.useState(false); | |||
| React.useEffect(() => { | |||
| getOrgCombo(); | |||
| getIssueCombo(); | |||
| if (Object.keys(getSearchCriteria(window.location.pathname)).length>0){ | |||
| setSearchCriteria(getSearchCriteria(window.location.pathname)) | |||
| }else{ | |||
| localStorage.setItem('searchCriteria',"") | |||
| setSearchCriteria({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }) | |||
| } | |||
| }, []); | |||
| React.useEffect(() => { | |||
| @@ -72,9 +83,14 @@ const UserSearchPage_Individual = () => { | |||
| }); | |||
| } | |||
| function applySearch(input) { | |||
| setGridOnReady(true) | |||
| setSearchCriteria(input); | |||
| localStorage.setItem('searchCriteria', JSON.stringify({path:window.location.pathname,data:input})) | |||
| } | |||
| function applyGridOnReady(input) { | |||
| setGridOnReady(input); | |||
| } | |||
| return ( | |||
| @@ -102,7 +118,7 @@ const UserSearchPage_Individual = () => { | |||
| orgComboData={orgCombo} | |||
| issueComboData={issueCombo} | |||
| searchCriteria={searchCriteria} | |||
| onGridReady={onGridReady} | |||
| /> | |||
| </Grid> | |||
| {/*row 2*/} | |||
| @@ -115,6 +131,7 @@ const UserSearchPage_Individual = () => { | |||
| <EventTable | |||
| applySearch={applySearch} | |||
| searchCriteria={searchCriteria} | |||
| applyGridOnReady={applyGridOnReady} | |||
| /> | |||
| </MainCard> | |||
| </Grid> | |||
| @@ -15,11 +15,12 @@ import {useIntl} from "react-intl"; | |||
| import {DEMAND_NOTE_LIST} from "utils/ApiPathConst"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function SearchDemandNote({ searchCriteria }) { | |||
| export default function SearchDemandNote({ searchCriteria, applyGridOnReady,applySearch }) { | |||
| const intl = useIntl(); | |||
| const theme = useTheme(); | |||
| const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | |||
| const { locale } = intl; | |||
| const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria); | |||
| @@ -52,7 +53,7 @@ export default function SearchDemandNote({ searchCriteria }) { | |||
| { | |||
| id: 'issueDate', | |||
| field: 'issueDate', | |||
| headerName: intl.formatMessage({id: 'receiptDate'}), | |||
| headerName: intl.formatMessage({id: 'sendDate'}), | |||
| width: isMdOrLg ? 'auto' : 175, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| valueGetter: (params) => { | |||
| @@ -65,12 +66,13 @@ export default function SearchDemandNote({ searchCriteria }) { | |||
| width: isMdOrLg ? 'auto' : 175, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| renderCell: (params) => { | |||
| return [StatusUtils.getStatus_Cht(params)] | |||
| return [StatusUtils.getStatus_i18n(params, locale) ] | |||
| }, | |||
| }, | |||
| { | |||
| field: 'sentDate', | |||
| headerName: intl.formatMessage({id: 'sendDate'}), | |||
| headerName: intl.formatMessage({id: 'sendDateTime'}), | |||
| width: isMdOrLg ? 'auto' : 200, | |||
| flex: isMdOrLg ? 1 : undefined, | |||
| valueGetter: (params) => { | |||
| @@ -95,10 +97,16 @@ export default function SearchDemandNote({ searchCriteria }) { | |||
| columns={columns} | |||
| customPageSize={10} | |||
| getRowHeight={() => 'auto'} | |||
| doLoad={{ | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| // doLoad={{ | |||
| // url: DEMAND_NOTE_LIST, | |||
| // params: _searchCriteria, | |||
| // }} | |||
| doLoad={React.useMemo(() => ({ | |||
| url: DEMAND_NOTE_LIST, | |||
| params: _searchCriteria, | |||
| }} | |||
| }), [_searchCriteria])} | |||
| /> | |||
| </Box> | |||
| </div> | |||
| @@ -22,7 +22,7 @@ import {DemoItem} from "@mui/x-date-pickers/internals/demo"; | |||
| import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; | |||
| import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData | |||
| const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData, onGridReady | |||
| }) => { | |||
| const intl = useIntl(); | |||
| @@ -38,6 +38,18 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData | |||
| const [fromDateValue, setFromDateValue] = React.useState("dd / mm / yyyy"); | |||
| const [toDateValue, setToDateValue] = React.useState("dd / mm / yyyy"); | |||
| React.useEffect(() => { | |||
| if(searchCriteria.status!=undefined){ | |||
| if(searchCriteria.status === ""){ | |||
| ComboData.denmandNoteStatus_Public[0] | |||
| }else{ | |||
| setSelectedStatus(ComboData.denmandNoteStatus_Public.find(item => item.type === searchCriteria.status)) | |||
| } | |||
| }else{ | |||
| setSelectedStatus(ComboData.denmandNoteStatus_Public[0]) | |||
| } | |||
| }, [searchCriteria]); | |||
| React.useEffect(() => { | |||
| setFromDateValue(minDate); | |||
| }, [minDate]); | |||
| @@ -76,17 +88,23 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData | |||
| React.useEffect(() => { | |||
| if (issueComboData && issueComboData.length > 0) { | |||
| setIssueCombo(issueComboData); | |||
| if(searchCriteria.issueId!=undefined){ | |||
| setIssueSelected(issueComboData.find(item => item.id === searchCriteria.issueId)) | |||
| } | |||
| } | |||
| }, [issueComboData]); | |||
| function resetForm() { | |||
| setType([]); | |||
| // setStatus({ key: 0, label: 'All', type: 'all' }); | |||
| setSelectedStatus(ComboData.denmandNoteStatus_Public[0]); | |||
| // setOrgSelected({}); | |||
| setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | |||
| setMaxDate(DateUtils.dateValue(new Date())) | |||
| setIssueSelected({}); | |||
| reset(); | |||
| reset({ | |||
| appNo:"", | |||
| dnNo:"", | |||
| }); | |||
| } | |||
| function getIssueLabel(data) { | |||
| @@ -149,6 +167,11 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData | |||
| setIssueSelected(newValue); | |||
| } | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label={intl.formatMessage({ id: 'gazetteCount' })} | |||
| @@ -157,6 +180,10 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData | |||
| }} | |||
| /> | |||
| )} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| @@ -187,7 +214,7 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData | |||
| </Grid> | |||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateFrom" | |||
| @@ -214,7 +241,7 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData | |||
| </Grid> | |||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateTo" | |||
| @@ -246,6 +273,7 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData | |||
| {...register("status")} | |||
| id="status" | |||
| size="small" | |||
| disableClearable | |||
| options={ComboData.denmandNoteStatus_Public} | |||
| getOptionLabel={(option) => option?.i18nLabel ? intl.formatMessage({ id: option.i18nLabel }) : ""} | |||
| inputValue={selectedStatus?.i18nLabel ? intl.formatMessage({ id: selectedStatus.i18nLabel }) : ""} | |||
| @@ -255,6 +283,11 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData | |||
| setSelectedStatus(newValue); | |||
| } | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField | |||
| {...params} | |||
| @@ -264,6 +297,10 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| @@ -288,6 +325,7 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData | |||
| <Button | |||
| variant="contained" | |||
| type="submit" | |||
| disabled={onGridReady} | |||
| > | |||
| <FormattedMessage id="submit" /> | |||
| </Button> | |||
| @@ -10,6 +10,7 @@ import * as React from "react"; | |||
| import {GET_ORG_COMBO, GET_ISSUE_COMBO} from "utils/ApiPathConst"; | |||
| import * as HttpUtils from "utils/HttpUtils"; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import { getSearchCriteria } from "auth/utils"; | |||
| import Loadable from 'components/Loadable'; | |||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | |||
| @@ -17,6 +18,7 @@ const SearchForm = Loadable(React.lazy(() => import('./SearchForm'))); | |||
| const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); | |||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | |||
| import {FormattedMessage} from "react-intl"; | |||
| import usePageTitle from "components/usePageTitle"; | |||
| const BackgroundHead = { | |||
| backgroundImage: `url(${titleBackgroundImg})`, | |||
| @@ -31,18 +33,25 @@ const BackgroundHead = { | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const SearchPage_DemandNote_Pub = () => { | |||
| usePageTitle("paymentInfoRecord"); | |||
| const [orgCombo, setOrgCombo] = React.useState([]); | |||
| const [issueCombo, setIssueCombo] = React.useState([]); | |||
| const [searchCriteria, setSearchCriteria] = React.useState({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate() - 14)), | |||
| }); | |||
| const [searchCriteria, setSearchCriteria] = React.useState({}); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| const [onGridReady, setGridOnReady] = React.useState(false); | |||
| React.useEffect(() => { | |||
| getOrgCombo(); | |||
| getIssueCombo(); | |||
| if (Object.keys(getSearchCriteria(window.location.pathname)).length>0){ | |||
| setSearchCriteria(getSearchCriteria(window.location.pathname)) | |||
| }else{ | |||
| localStorage.setItem('searchCriteria',"") | |||
| setSearchCriteria({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }) | |||
| } | |||
| }, []); | |||
| React.useEffect(() => { | |||
| @@ -72,7 +81,13 @@ const SearchPage_DemandNote_Pub = () => { | |||
| function applySearch(input) { | |||
| setGridOnReady(true) | |||
| setSearchCriteria(input); | |||
| localStorage.setItem('searchCriteria', JSON.stringify({path:window.location.pathname,data:input})) | |||
| } | |||
| function applyGridOnReady(input) { | |||
| setGridOnReady(input); | |||
| } | |||
| return ( | |||
| @@ -87,7 +102,7 @@ const SearchPage_DemandNote_Pub = () => { | |||
| <Grid item xs={12}> | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <FormattedMessage id="paymentInfoRecord" /> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -100,6 +115,7 @@ const SearchPage_DemandNote_Pub = () => { | |||
| orgComboData={orgCombo} | |||
| issueComboData={issueCombo} | |||
| searchCriteria={searchCriteria} | |||
| onGridReady={onGridReady} | |||
| /> | |||
| </Grid> | |||
| {/*row 2*/} | |||
| @@ -110,7 +126,9 @@ const SearchPage_DemandNote_Pub = () => { | |||
| sx={{ backgroundColor: '#fff' }} | |||
| > | |||
| <EventTable | |||
| searchCriteria={searchCriteria} | |||
| searchCriteria={searchCriteria} | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| /> | |||
| </MainCard> | |||
| </Grid> | |||
| @@ -50,7 +50,7 @@ const Index = () => { | |||
| axios.get(`${UrlUtils.GET_EMAIL}/${params.id}`) | |||
| .then((response) => { | |||
| if (response.status === 200) { | |||
| console.log(response) | |||
| // console.log(response) | |||
| setRecord(response.data.data) | |||
| } | |||
| }) | |||
| @@ -95,14 +95,14 @@ const Index = () => { | |||
| console.log(error); | |||
| return false; | |||
| }); | |||
| console.log(data) | |||
| // console.log(data) | |||
| } | |||
| const handleDelete = () => { | |||
| axios.delete(`${UrlUtils.DELETE_EMAIL}/${params.id}`, | |||
| ) | |||
| .then((response) => { | |||
| console.log(response) | |||
| // console.log(response) | |||
| if (response.status === 204) { | |||
| // location.reload(); | |||
| navigate('/setting/emailTemplate'); | |||
| @@ -97,10 +97,14 @@ export default function EmailTemplateTable({ responseData }) { | |||
| customPageSize={10} | |||
| onRowDoubleClick={handleRowDoubleClick} | |||
| getRowHeight={() => 'auto'} | |||
| doLoad={{ | |||
| url:GET_EMAIL_LIST, | |||
| params: _responseData | |||
| }} | |||
| // doLoad={{ | |||
| // url:GET_EMAIL_LIST, | |||
| // params: _responseData | |||
| // }} | |||
| doLoad={React.useMemo(() => ({ | |||
| url: GET_EMAIL_LIST, | |||
| params: _responseData, | |||
| }), [_responseData])} | |||
| /> | |||
| </div> | |||
| ); | |||
| @@ -4,10 +4,11 @@ import * as FormatUtils from "utils/FormatUtils" | |||
| import {GFIMIS_LIST} from "utils/ApiPathConst"; | |||
| import { useNavigate } from "react-router-dom"; | |||
| import { FiDataGrid } from "components/FiDataGrid"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function SearchTable({ searchCriteria }) { | |||
| const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria); | |||
| export default function SearchTable({ previewSearchCriteria, onPreviewGridOnReady,selectedIds = [], previewToken }) { | |||
| // const [_searchCriteria, set_searchCriteria] = React.useState(previewSearchCriteria); | |||
| const navigate = useNavigate() | |||
| // const [rows, setRows] = React.useState([]); | |||
| @@ -27,9 +28,21 @@ export default function SearchTable({ searchCriteria }) { | |||
| } | |||
| } | |||
| React.useEffect(() => { | |||
| set_searchCriteria(searchCriteria); | |||
| }, [searchCriteria]); | |||
| const doLoad = React.useMemo(() => { | |||
| if (!selectedIds?.length) return undefined; | |||
| return { | |||
| url: GFIMIS_LIST, | |||
| params: { | |||
| ...previewSearchCriteria, | |||
| paymentId: selectedIds.join(',') | |||
| } | |||
| }; | |||
| }, [previewSearchCriteria, selectedIds, previewToken]); | |||
| // React.useEffect(() => { | |||
| // set_searchCriteria(previewSearchCriteria); | |||
| // }, [previewSearchCriteria]); | |||
| const handleEditClick = (params) => () => { | |||
| navigate('/paymentPage/details/' + params.row.id); | |||
| @@ -39,7 +52,7 @@ export default function SearchTable({ searchCriteria }) { | |||
| { | |||
| id: 'paymentMethod', | |||
| field: 'paymentMethod', | |||
| headerName: 'Payment Means', | |||
| headerName: 'GFMIS Payment Method', | |||
| flex: 4, | |||
| renderCell: (params) => { | |||
| let paymentMethod = params.row.paymentMethod; | |||
| @@ -60,20 +73,15 @@ export default function SearchTable({ searchCriteria }) { | |||
| return ( | |||
| <div style={{ width: '100%' }}> | |||
| <FiDataGrid | |||
| key={previewToken} | |||
| sx={_sx} | |||
| rowHeight={80} | |||
| columns={columns} | |||
| customPageSize={10} | |||
| onRowDoubleClick={handleEditClick} | |||
| doLoad={React.useMemo(() => ({ | |||
| url: GFIMIS_LIST, | |||
| params: _searchCriteria, | |||
| callback: function () { | |||
| // setRows(responseData?.records); | |||
| } | |||
| }), [_searchCriteria])} | |||
| applyGridOnReady={onPreviewGridOnReady} | |||
| doLoad={doLoad} | |||
| /> | |||
| </div> | |||
| ); | |||
| @@ -2,8 +2,8 @@ | |||
| import { | |||
| Button, | |||
| Grid, | |||
| // TextField, | |||
| // Autocomplete, | |||
| TextField, | |||
| Autocomplete, | |||
| Typography | |||
| } from '@mui/material'; | |||
| import MainCard from "components/MainCard"; | |||
| @@ -14,6 +14,7 @@ import {PNSPS_BUTTON_THEME} from "../../themes/buttonConst"; | |||
| import {ThemeProvider} from "@emotion/react"; | |||
| // import * as ComboData from "utils/ComboData"; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import * as ComboData from "utils/ComboData"; | |||
| import {DatePicker} from "@mui/x-date-pickers/DatePicker"; | |||
| import dayjs from "dayjs"; | |||
| @@ -21,56 +22,82 @@ import {DemoItem} from "@mui/x-date-pickers/internals/demo"; | |||
| import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; | |||
| import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const SearchPublicNoticeForm = ({ applySearch, generateXML, searchCriteria }) => { | |||
| const SearchPublicNoticeForm = ({ applySearch, generateXML, searchCriteria, onGridReady, selectedIds = [] }) => { | |||
| // const [minDate, setMinDate] = React.useState(searchCriteria.dateFrom); | |||
| const [minDate, setMinDate] = React.useState(searchCriteria.dateFrom); | |||
| const [maxDate] = React.useState(searchCriteria.dateFrom); | |||
| // const [status, setStatus] = React.useState(ComboData.paymentStatus[0]); | |||
| const [maxDate, setMaxDate] = React.useState(searchCriteria.dateTo); | |||
| const [fromDateValue, setFromDateValue] = React.useState("dd / mm / yyyy"); | |||
| const [toDateValue, setToDateValue] = React.useState("dd / mm / yyyy"); | |||
| const [payMethod, setPayMethod] = React.useState(ComboData.payMethod[0]); | |||
| // const [status, setStatus] = React.useState(ComboData.paymentStatus[0]); | |||
| const marginBottom = 2.5; | |||
| const { | |||
| // register, | |||
| const { | |||
| register, | |||
| handleSubmit, | |||
| } = useForm() | |||
| React.useEffect(() => { | |||
| setFromDateValue(minDate); | |||
| }, [minDate]); | |||
| React.useEffect(() => { | |||
| setToDateValue(maxDate); | |||
| }, [maxDate]); | |||
| const toPayMethodArray = (opt) => { | |||
| if (!opt || opt.type === 'all') return []; | |||
| return Array.isArray(opt.type) ? opt.type : [opt.type]; | |||
| }; | |||
| const onSubmit = () => { | |||
| let sentDateFrom = ""; | |||
| let sentDateTo = ""; | |||
| if (fromDateValue != "dd / mm / yyyy") { | |||
| sentDateFrom = DateUtils.dateValue(fromDateValue) | |||
| } | |||
| if (toDateValue != "dd / mm / yyyy") { | |||
| sentDateTo = DateUtils.dateValue(toDateValue) | |||
| } | |||
| const temp = { | |||
| // code: data.code, | |||
| // transNo: data.transNo, | |||
| dateFrom: sentDateFrom, | |||
| // dateTo: data.dateTo, | |||
| dateTo: sentDateTo, | |||
| // paymentId: selectedIds.join(','), | |||
| payMethod : toPayMethodArray(payMethod), | |||
| // status : (status?.type && status?.type != 'all') ? status?.type : "", | |||
| }; | |||
| applySearch(temp); | |||
| }; | |||
| const generateHandler = () => { | |||
| if (!selectedIds || selectedIds.length === 0) { | |||
| alert('No payment is selected.'); | |||
| return; | |||
| } | |||
| let sentDateFrom = ""; | |||
| let sentDateTo = ""; | |||
| if (fromDateValue != "dd / mm / yyyy") { | |||
| sentDateFrom = DateUtils.dateValue(fromDateValue) | |||
| if (fromDateValue !== "dd / mm / yyyy") { | |||
| sentDateFrom = DateUtils.dateValue(fromDateValue); | |||
| } | |||
| if (toDateValue !== "dd / mm / yyyy") { | |||
| sentDateTo = DateUtils.dateValue(toDateValue); | |||
| } | |||
| // const dateTo = getValues("dateTo") | |||
| const temp = { | |||
| // code: data.code, | |||
| // transNo: data.transNo, | |||
| dateFrom: sentDateFrom, | |||
| dateTo: "", | |||
| // status : (status?.type && status?.type != 'all') ? status?.type : "", | |||
| dateTo: sentDateTo, | |||
| }; | |||
| generateXML(temp); | |||
| } | |||
| }; | |||
| return ( | |||
| @@ -90,8 +117,7 @@ const SearchPublicNoticeForm = ({ applySearch, generateXML, searchCriteria }) => | |||
| {/*row 2*/} | |||
| <Grid container display="flex" alignItems={"center"}> | |||
| <Grid item xs={9} s={6} md={4} lg={4} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <Grid item xs={9} s={6} md={3} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| @@ -104,7 +130,7 @@ const SearchPublicNoticeForm = ({ applySearch, generateXML, searchCriteria }) => | |||
| // }, | |||
| }} | |||
| format="DD/MM/YYYY" | |||
| label="Credit Date" | |||
| label="From Date" | |||
| value={minDate === null ? null : dayjs(minDate)} | |||
| maxDate={maxDate === null ? null : dayjs(maxDate)} | |||
| onChange={(newValue) => { | |||
| @@ -118,22 +144,65 @@ const SearchPublicNoticeForm = ({ applySearch, generateXML, searchCriteria }) => | |||
| </LocalizationProvider> | |||
| </Grid> | |||
| <Grid item xs={9} s={6} md={4} lg={4} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| {/* <TextField | |||
| fullWidth | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| {...register("dateTo")} | |||
| InputProps={{ inputProps: { min: minDate } }} | |||
| onChange={(newValue) => { | |||
| setMaxDate(DateUtils.dateValue(newValue)); | |||
| }} | |||
| id="dateTo" | |||
| type="date" | |||
| label="To" | |||
| defaultValue={searchCriteria.dateTo} | |||
| /> */} | |||
| <Grid item xs={9} s={6} md={3} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateTo" | |||
| // onError={(newError) => setReceiptFromError(newError)} | |||
| slotProps={{ | |||
| field: { readOnly: true, }, | |||
| // textField: { | |||
| // helperText: receiptFromErrorMessage, | |||
| // }, | |||
| }} | |||
| format="DD/MM/YYYY" | |||
| label="To Date" | |||
| value={maxDate === null ? null : dayjs(maxDate)} | |||
| minDate={minDate === null ? null : dayjs(minDate)} | |||
| onChange={(newValue) => { | |||
| // console.log(newValue) | |||
| if(newValue!=null){ | |||
| setMaxDate(newValue); | |||
| } | |||
| }} | |||
| /> | |||
| </DemoItem > | |||
| </LocalizationProvider> | |||
| </Grid> | |||
| <Grid item xs={9} s={6} md={3} lg={3} sx={{ ml: 3, mr: 3, mb: marginBottom }}> | |||
| <Autocomplete | |||
| {...register("payMethod")} | |||
| disablePortal={false} | |||
| size="small" | |||
| id="payMethod" | |||
| filterOptions={(options) => options} | |||
| options={ComboData.payMethod} | |||
| value={payMethod} | |||
| getOptionLabel={(option) => option.label} | |||
| inputValue={payMethod?.label ? payMethod?.label : ""} | |||
| onChange={(event, newValue) => { | |||
| if(newValue==null){ | |||
| setPayMethod(ComboData.payMethod[0]); | |||
| }else{ | |||
| setPayMethod(newValue); | |||
| } | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label="Payment Method" | |||
| /> | |||
| )} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| /> | |||
| </Grid> | |||
| {/* <Grid item xs={9} s={6} md={4} lg={3}> | |||
| @@ -146,8 +215,9 @@ const SearchPublicNoticeForm = ({ applySearch, generateXML, searchCriteria }) => | |||
| <Button | |||
| variant="contained" | |||
| type="submit" | |||
| disabled={onGridReady} | |||
| > | |||
| Preview | |||
| Search | |||
| </Button> | |||
| </Grid> | |||
| @@ -155,9 +225,11 @@ const SearchPublicNoticeForm = ({ applySearch, generateXML, searchCriteria }) => | |||
| <Button | |||
| variant="contained" | |||
| onClick={generateHandler} | |||
| > | |||
| disabled={!selectedIds || selectedIds.length === 0} | |||
| > | |||
| Generate | |||
| </Button> | |||
| </Grid> | |||
| </ThemeProvider> | |||
| </Grid> | |||
| @@ -0,0 +1,156 @@ | |||
| // material-ui | |||
| import * as React from 'react'; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import {PAYMENT_GFMIS_LIST} from "utils/ApiPathConst"; | |||
| // import * as HttpUtils from "utils/HttpUtils"; | |||
| import * as FormatUtils from "utils/FormatUtils" | |||
| // import { useNavigate } from "react-router-dom"; | |||
| import { FiDataGrid } from "components/FiDataGrid"; | |||
| import { clickableLink } from 'utils/CommonFunction'; | |||
| import { getPaymentMethodByCode} from "auth/utils"; | |||
| import { | |||
| // Checkbox, | |||
| // Dialog, DialogTitle, DialogContent, DialogActions, | |||
| // Button,Typography | |||
| // MenuItem | |||
| } from '@mui/material'; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function SearchPaymentTable({ searchCriteria, applyGridOnReady, applySearch, selectedIds = [], onSelectionChange,}) { | |||
| const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria); | |||
| // const navigate = useNavigate() | |||
| const _sx = { | |||
| padding: "4 2 4 2", | |||
| boxShadow: 1, | |||
| border: 1, | |||
| borderColor: '#DDD', | |||
| '& .MuiDataGrid-cell': { | |||
| borderTop: 1, | |||
| borderBottom: 1, | |||
| borderColor: "#EEE" | |||
| }, | |||
| '& .MuiDataGrid-footerContainer': { | |||
| border: 1, | |||
| borderColor: "#EEE" | |||
| } | |||
| } | |||
| React.useEffect(() => { | |||
| set_searchCriteria(searchCriteria); | |||
| }, [searchCriteria]); | |||
| const selectedIdsRef = React.useRef(selectedIds); | |||
| React.useEffect(() => { | |||
| selectedIdsRef.current = selectedIds; | |||
| }, [selectedIds]); | |||
| const columns = [ | |||
| { | |||
| id: 'appNos', | |||
| field: 'appNos', | |||
| headerName: 'Application No.', | |||
| flex: 1, | |||
| minWidth: 150, | |||
| renderCell: (params) => { | |||
| let appNo = params.row.appNos; | |||
| return <div style={{ marginTop: 2, marginBottom: 2 }}>{appNo}</div> | |||
| }, | |||
| }, | |||
| { | |||
| field: 'actions', | |||
| headerName: 'Transaction No.', | |||
| flex: 1, | |||
| minWidth: 200, | |||
| cellClassName: 'actions', | |||
| renderCell: (params) => { | |||
| return clickableLink('/paymentPage/details/' + params.row.id, params.row.transNo); | |||
| }, | |||
| }, | |||
| { | |||
| id: 'transDateTime', | |||
| field: 'transDateTime', | |||
| headerName: 'Transaction Date', | |||
| flex: 1, | |||
| minWidth: 150, | |||
| // sorting/filtering uses this value | |||
| valueGetter: (params) => DateUtils.toDate(params?.value), | |||
| // display uses this (params.value is the *Date* returned above) | |||
| valueFormatter: (params) => { | |||
| const d = params.value; // Date or Invalid Date | |||
| return d instanceof Date && !isNaN(d.getTime()) | |||
| ? DateUtils.dateStr(d) | |||
| : ""; | |||
| }, | |||
| // make sorting 100% deterministic | |||
| sortComparator: (v1, v2) => { | |||
| const t1 = v1 instanceof Date && !isNaN(v1.getTime()) ? v1.getTime() : -Infinity; | |||
| const t2 = v2 instanceof Date && !isNaN(v2.getTime()) ? v2.getTime() : -Infinity; | |||
| return t1 - t2; | |||
| } | |||
| }, | |||
| { | |||
| field: 'payMethod', | |||
| headerName: 'Payment Method', | |||
| flex: 1, | |||
| width: 150, | |||
| renderCell: (params) => { | |||
| return getPaymentMethodByCode(params?.value); | |||
| } | |||
| }, | |||
| { | |||
| id: 'payAmount', | |||
| field: 'payAmount', | |||
| headerName: 'Amount ($)', | |||
| width: 150, | |||
| valueGetter: (params) => { | |||
| return (params?.value) ? "$ " + FormatUtils.currencyFormat(params?.value) : ""; | |||
| } | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div style={{ width: '100%' }}> | |||
| <FiDataGrid | |||
| sx={_sx} | |||
| rowHeight={60} | |||
| columns={columns} | |||
| height={500} | |||
| autoHeight={false} | |||
| pagination={false} | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| checkboxSelection | |||
| disableRowSelectionOnClick | |||
| rowSelectionModel={selectedIds} | |||
| onRowSelectionModelChange={(ids) => onSelectionChange?.(ids)} | |||
| doLoad={React.useMemo(() => ({ | |||
| url: PAYMENT_GFMIS_LIST, | |||
| params: _searchCriteria, | |||
| callback: (responseData) => { | |||
| const newIds = (responseData?.records ?? []).map(r => r.id); | |||
| const currentSelected = selectedIdsRef.current; | |||
| if (newIds.length === 0) { | |||
| onSelectionChange?.([]); | |||
| return; | |||
| } | |||
| if (!currentSelected || currentSelected.length === 0) { | |||
| onSelectionChange?.(newIds); | |||
| } else { | |||
| const stillValid = currentSelected.filter(id => newIds.includes(id)); | |||
| onSelectionChange?.(stillValid); | |||
| } | |||
| } | |||
| }), [_searchCriteria])} | |||
| /> | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -2,7 +2,9 @@ | |||
| import { | |||
| Grid, | |||
| Typography, | |||
| Stack | |||
| Stack, | |||
| Button, | |||
| Dialog, DialogTitle, DialogContent, DialogActions, | |||
| } from '@mui/material'; | |||
| import MainCard from "components/MainCard"; | |||
| import {GEN_GFMIS_XML} from "utils/ApiPathConst"; | |||
| @@ -10,10 +12,17 @@ import * as React from "react"; | |||
| import * as HttpUtils from "utils/HttpUtils"; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import {DatePicker} from "@mui/x-date-pickers/DatePicker"; | |||
| import dayjs from "dayjs"; | |||
| import {DemoItem} from "@mui/x-date-pickers/internals/demo"; | |||
| import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; | |||
| import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; | |||
| import Loadable from 'components/Loadable'; | |||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | |||
| const SearchForm = Loadable(React.lazy(() => import('./SearchForm'))); | |||
| const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); | |||
| const TransactionTable = Loadable(React.lazy(() => import('./TransactionDataGrid'))); | |||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | |||
| const BackgroundHead = { | |||
| @@ -30,27 +39,78 @@ const BackgroundHead = { | |||
| const Index = () => { | |||
| const [isTxLoading, setIsTxLoading] = React.useState(false); | |||
| const [autoPreviewPending, setAutoPreviewPending] = React.useState(false); | |||
| const [searchCriteria, setSearchCriteria] = React.useState({ | |||
| dateFrom: DateUtils.dateValue(new Date()), | |||
| // dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }); | |||
| const [previewSearchCriteria, setPreviewSearchCriteria] = React.useState({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| const [onGridReady, setGridOnReady] = React.useState(false); | |||
| const [isPreviewLoading, setIsPreviewLoading] = React.useState(false); | |||
| const [isPopUp, setIsPopUp] = React.useState(false); | |||
| const [downloadInput, setDownloadInput] = React.useState(); | |||
| const [selectedIds, setSelectedIds] = React.useState([]); | |||
| const [inputDate, setInputDate] = React.useState(searchCriteria.dateTo); | |||
| const [inputDateValue, setInputDateValue] = React.useState("dd / mm / yyyy"); | |||
| const [previewToken, setPreviewToken] = React.useState(0); | |||
| React.useEffect(() => { | |||
| setInputDateValue(inputDate); | |||
| }, [inputDate]); | |||
| React.useEffect(() => { | |||
| setOnReady(true); | |||
| }, [searchCriteria]); | |||
| React.useEffect(() => { | |||
| if (!autoPreviewPending) return; | |||
| // wait for tx grid load complete, and for auto-selection to happen | |||
| if (isTxLoading) return; | |||
| if (!selectedIds || selectedIds.length === 0) return; | |||
| // trigger preview exactly once | |||
| const withToken = { ...searchCriteria, __ts: Date.now() }; | |||
| setPreviewSearchCriteria(withToken); | |||
| setPreviewToken(t => t + 1); | |||
| function downloadXML(input) { | |||
| // console.log(input) | |||
| setAutoPreviewPending(false); | |||
| }, [autoPreviewPending, isTxLoading, selectedIds, searchCriteria]); | |||
| React.useEffect(() => { | |||
| if (selectedIds.length === 0) { | |||
| setPreviewSearchCriteria({}); | |||
| setPreviewToken(t => t + 1); // forces preview grid remount -> clears rows | |||
| } | |||
| }, [selectedIds]); | |||
| function downloadXML() { | |||
| console.log(selectedIds.join(',')) | |||
| setIsPopUp(false) | |||
| let sentDateFrom = ""; | |||
| if (inputDateValue != "dd / mm / yyyy") { | |||
| sentDateFrom = DateUtils.dateValue(inputDateValue) | |||
| } | |||
| HttpUtils.get({ | |||
| url: GEN_GFMIS_XML + "/today", | |||
| params:{ | |||
| // dateTo: input.dateTo, | |||
| dateFrom: input.dateFrom, | |||
| dateTo: downloadInput.dateTo, | |||
| dateFrom: downloadInput.dateFrom, | |||
| inputDate: sentDateFrom, | |||
| paymentId: selectedIds.join(',') | |||
| }, | |||
| onSuccess: (responseData) => { | |||
| console.log(responseData) | |||
| // console.log(responseData) | |||
| const parser = new DOMParser(); | |||
| const xmlDoc = parser.parseFromString(responseData, 'application/xml'); | |||
| // Get the DCBHeader element | |||
| @@ -75,7 +135,7 @@ const Index = () => { | |||
| const updatedXmlString = new XMLSerializer().serializeToString(xmlDoc); | |||
| const filename = xmlDoc.querySelector('FileHeader').getAttribute('H_Filename'); | |||
| console.log(updatedXmlString) | |||
| // console.log(updatedXmlString) | |||
| const blob = new Blob([updatedXmlString], { type: 'application/xml' }); | |||
| // Create a download link | |||
| const link = document.createElement('a'); | |||
| @@ -97,11 +157,42 @@ const Index = () => { | |||
| function applySearch(input) { | |||
| setAutoPreviewPending(true); // NEW: ask for auto-preview after grid loads | |||
| setGridOnReady(true); | |||
| setSelectedIds([]); | |||
| setPreviewSearchCriteria({}); | |||
| setSearchCriteria(input); | |||
| setInputDate(input.dateFrom); | |||
| } | |||
| function previewSearch() { | |||
| if (selectedIds.length === 0) return; | |||
| setIsPopUp(false); | |||
| setIsPreviewLoading(true); | |||
| const withToken = { ...searchCriteria, __ts: Date.now() }; | |||
| setPreviewSearchCriteria(withToken); | |||
| setPreviewToken(t => t + 1); | |||
| } | |||
| function onPreviewGridOnReady(isLoading) { | |||
| // FiDataGrid calls this with true/false | |||
| setIsPreviewLoading(isLoading); | |||
| } | |||
| function applyGridOnReady(isLoading) { | |||
| setGridOnReady(isLoading); // keep existing behavior for disabling Search | |||
| setIsTxLoading(isLoading); // NEW: remember tx grid loading | |||
| } | |||
| function generateXML(input) { | |||
| downloadXML(input); | |||
| setDownloadInput(input); | |||
| setIsPopUp(true) | |||
| } | |||
| return ( | |||
| @@ -121,10 +212,13 @@ const Index = () => { | |||
| {/*row 1*/} | |||
| <Grid item xs={12} md={12} lg={12} sx={{mb:-1}}> | |||
| <SearchForm | |||
| applySearch={applySearch} | |||
| generateXML={generateXML} | |||
| searchCriteria={searchCriteria} | |||
| applySearch={applySearch} | |||
| generateXML={generateXML} | |||
| searchCriteria={searchCriteria} | |||
| onGridReady={onGridReady} | |||
| selectedIds={selectedIds} | |||
| /> | |||
| </Grid> | |||
| {/*row 2*/} | |||
| <Grid item xs={12} md={12} lg={12}> | |||
| @@ -133,11 +227,83 @@ const Index = () => { | |||
| content={false} | |||
| sx={{width: "-webkit-fill-available"}} | |||
| > | |||
| <EventTable | |||
| <TransactionTable | |||
| searchCriteria={searchCriteria} | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| selectedIds={selectedIds} | |||
| onSelectionChange={setSelectedIds} | |||
| /> | |||
| </MainCard> | |||
| </Grid> | |||
| <Grid item xs={12} md={12} lg={12} sx={{ml:2}}> | |||
| <Button | |||
| variant="contained" | |||
| onClick={previewSearch} | |||
| disabled={isPreviewLoading || selectedIds.length === 0} | |||
| > | |||
| Preview | |||
| </Button> | |||
| </Grid> | |||
| <Grid item xs={12} md={12} lg={12}> | |||
| <MainCard elevation={0} | |||
| border={false} | |||
| content={false} | |||
| sx={{width: "-webkit-fill-available"}} | |||
| > | |||
| <EventTable | |||
| previewSearchCriteria={previewSearchCriteria} | |||
| onPreviewGridOnReady={onPreviewGridOnReady} | |||
| selectedIds={selectedIds} | |||
| previewToken={previewToken} | |||
| /> | |||
| </MainCard> | |||
| </Grid> | |||
| <Dialog | |||
| open={isPopUp} | |||
| onClose={() => setIsPopUp(false)} | |||
| PaperProps={{ | |||
| sx: { | |||
| minWidth: '40vw', | |||
| maxWidth: { xs: '90vw', s: '90vw', m: '70vw', lg: '70vw' }, | |||
| maxHeight: { xs: '90vh', s: '70vh', m: '70vh', lg: '60vh' } | |||
| } | |||
| }} | |||
| > | |||
| <DialogTitle> Bank Statement Collection Date </DialogTitle> | |||
| <DialogContent style={{ display: 'flex', }}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateFrom" | |||
| // onError={(newError) => setReceiptFromError(newError)} | |||
| slotProps={{ | |||
| field: { readOnly: true, }, | |||
| // textField: { | |||
| // helperText: receiptFromErrorMessage, | |||
| // }, | |||
| }} | |||
| format="DD/MM/YYYY" | |||
| // label="Credit Date" | |||
| value={inputDate === null ? null : dayjs(inputDate)} | |||
| minDate={searchCriteria.dateFrom === null ? null : dayjs(searchCriteria.dateFrom)} | |||
| onChange={(newValue) => { | |||
| // console.log(newValue) | |||
| if(newValue!=null){ | |||
| setInputDate(newValue); | |||
| } | |||
| }} | |||
| /> | |||
| </DemoItem > | |||
| </LocalizationProvider> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setIsPopUp(false)}><Typography variant="h5">Cancel</Typography></Button> | |||
| <Button onClick={() => downloadXML()}><Typography variant="h5">Confirm</Typography></Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| </Grid> | |||
| ); | |||
| }; | |||
| @@ -7,7 +7,7 @@ import {GET_ISSUE} from "utils/ApiPathConst"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function GazetteIssueTable({ searchCriteria }) { | |||
| export default function GazetteIssueTable({ searchCriteria, applyGridOnReady }) { | |||
| const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria); | |||
| React.useEffect(() => { | |||
| @@ -100,10 +100,15 @@ export default function GazetteIssueTable({ searchCriteria }) { | |||
| customPageSize={10} | |||
| // onRowDoubleClick={handleRowDoubleClick} | |||
| getRowHeight={() => 'auto'} | |||
| doLoad={{ | |||
| applyGridOnReady={applyGridOnReady} | |||
| // doLoad={{ | |||
| // url: GET_ISSUE, | |||
| // params: _searchCriteria, | |||
| // }} | |||
| doLoad={React.useMemo(() => ({ | |||
| url: GET_ISSUE, | |||
| params: _searchCriteria, | |||
| }} | |||
| }), [_searchCriteria])} | |||
| /> | |||
| </div> | |||
| ); | |||
| @@ -27,9 +27,9 @@ const SearchGazetteIssueForm = ({ applyExport, comboData, waitDownload}) => { | |||
| handleSubmit } = useForm() | |||
| const onSubmit = () => { | |||
| console.log(selectedYear) | |||
| // console.log(selectedYear) | |||
| if (selectedYear !=null && Object.keys(selectedYear).length>0){ | |||
| console.log("okkkkkkkkkkkkkkkk") | |||
| // console.log("okkkkkkkkkkkkkkkk") | |||
| const temp = { | |||
| year: selectedYear.label, | |||
| }; | |||
| @@ -81,9 +81,9 @@ const SearchGazetteIssueForm = ({ applyExport, comboData, waitDownload}) => { | |||
| setSelectedYear(newValue); | |||
| }} | |||
| sx={{ | |||
| "& .MuiInputBase-root": { height: "41px" }, | |||
| "#year-combo": { padding: "0px 0px 0px 0px" }, | |||
| "& .MuiAutocomplete-endAdornment": { top: "auto" }, | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => <TextField {...params} placeholder={""}/>} | |||
| /> | |||
| @@ -16,7 +16,7 @@ import {ThemeProvider} from "@emotion/react"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const SearchGazetteIssueForm = ({ applySearch, comboData}) => { | |||
| const SearchGazetteIssueForm = ({ applySearch, comboData, onGridReady}) => { | |||
| const [selectedYear, setSelectedYear] = React.useState([]); | |||
| // const [defaultYear, setDefaultYear] = React.useState(searchCriteria.year); | |||
| const [comboList, setComboList] = React.useState([]); | |||
| @@ -79,9 +79,9 @@ const SearchGazetteIssueForm = ({ applySearch, comboData}) => { | |||
| setSelectedYear(newValue); | |||
| }} | |||
| sx={{ | |||
| "& .MuiInputBase-root": { height: "41px" }, | |||
| "#year-combo": { padding: "0px 0px 0px 0px" }, | |||
| "& .MuiAutocomplete-endAdornment": { top: "auto" }, | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => <TextField {...params} placeholder={""}/>} | |||
| /> | |||
| @@ -114,6 +114,7 @@ const SearchGazetteIssueForm = ({ applySearch, comboData}) => { | |||
| <Button | |||
| variant="contained" | |||
| type="submit" | |||
| disabled={onGridReady} | |||
| > | |||
| Search | |||
| </Button> | |||
| @@ -1,4 +1,3 @@ | |||
| // material-ui | |||
| import { | |||
| Grid, | |||
| Typography, | |||
| @@ -16,7 +15,7 @@ import MainCard from 'components/MainCard'; | |||
| const ExportForm = Loadable(React.lazy(() => import('./ExportForm'))); | |||
| const SearchForm = Loadable(React.lazy(() => import('./SearchForm'))); | |||
| const GazetteIssueTable = Loadable(React.lazy(() => import('./DataGrid'))) | |||
| const GazetteIssueTable = Loadable(React.lazy(() => import('./DataGrid'))); | |||
| const BackgroundHead = { | |||
| backgroundImage: `url(${titleBackgroundImg})`, | |||
| @@ -26,42 +25,39 @@ const BackgroundHead = { | |||
| backgroundRepeat: 'no-repeat', | |||
| backgroundColor: '#0C489E', | |||
| backgroundPosition: 'right' | |||
| } | |||
| import {PNSPS_LONG_BUTTON_THEME} from "themes/buttonConst"; | |||
| import {ThemeProvider} from "@emotion/react"; | |||
| }; | |||
| import { PNSPS_LONG_BUTTON_THEME } from "themes/buttonConst"; | |||
| import { ThemeProvider } from "@emotion/react"; | |||
| import { dateStr_Year } from "utils/DateUtils"; | |||
| import { notifySaveSuccess } from 'utils/CommonFunction'; | |||
| import { isGrantedAny } from "auth/utils"; | |||
| import { useIntl } from 'react-intl'; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const Index = () => { | |||
| const intl = useIntl(); | |||
| const [comboData, setComboData] = React.useState([]); | |||
| const [holidayComboData, setHolidayComboData] = React.useState([]); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| const [onGridReady, setGridOnReady] = React.useState(false); | |||
| const [onSearchReady, setOnSearchReady] = React.useState(false); | |||
| const [onExportReady, setOnExportReady] = React.useState(false); | |||
| const [searchCriteria, setSearchCriteria] = React.useState({ | |||
| year: dateStr_Year(new Date()), | |||
| // dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }); | |||
| const [exportCriteria, setExportCriteria] = React.useState({ | |||
| // year: dateStr_Year(new Date()), | |||
| // dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }); | |||
| const [exportCriteria, setExportCriteria] = React.useState({}); | |||
| const [attachments, setAttachments] = React.useState([]); | |||
| const [waitImport, setWaitImport] = React.useState(false); | |||
| const [waitDownload, setWaitDownload] = React.useState(false); | |||
| const [isWarningPopUp, setIsWarningPopUp] = React.useState(false); | |||
| const [warningText, setWarningText] = React.useState(""); | |||
| const fileInputRef = React.useRef(null); | |||
| React.useEffect(() => { | |||
| // console.log(searchCriteria) | |||
| setOnSearchReady(false) | |||
| setOnSearchReady(false); | |||
| loadCombo(); | |||
| }, [searchCriteria]); | |||
| function loadCombo() { | |||
| HttpUtils.get({ | |||
| @@ -69,9 +65,8 @@ const Index = () => { | |||
| onSuccess: (responseData) => { | |||
| let combo = responseData; | |||
| setComboData(combo); | |||
| setOnSearchReady(true) | |||
| loadHolidayCombo(true) | |||
| // setOnReady(true); | |||
| setOnSearchReady(true); | |||
| loadHolidayCombo(true); | |||
| } | |||
| }); | |||
| } | |||
| @@ -80,25 +75,29 @@ const Index = () => { | |||
| HttpUtils.get({ | |||
| url: UrlUtils.GET_HOLIDAY_COMBO, | |||
| onSuccess: (responseData) => { | |||
| let combo = responseData | |||
| setHolidayComboData(combo) | |||
| setOnExportReady(true) | |||
| setOnReady(true) | |||
| let combo = responseData; | |||
| setHolidayComboData(combo); | |||
| setOnExportReady(true); | |||
| setOnReady(true); | |||
| } | |||
| }); | |||
| } | |||
| function applySearch(input) { | |||
| setGridOnReady(true); | |||
| setSearchCriteria(input); | |||
| } | |||
| function applyExport(input) { | |||
| setExportCriteria(input); | |||
| } | |||
| function applyGridOnReady(input) { | |||
| setGridOnReady(input); | |||
| } | |||
| React.useEffect(() => { | |||
| if (Object.keys(exportCriteria).length > 0) { | |||
| // console.log(exportCriteria) | |||
| if (Object.keys(exportCriteria).length > 0) { | |||
| doExport(); | |||
| } | |||
| }, [exportCriteria]); | |||
| @@ -112,51 +111,47 @@ const Index = () => { | |||
| const readFile = (event) => { | |||
| let file = event.target.files[0]; | |||
| if (file) { | |||
| if (!file.name.toLowerCase().substr(file.name.length - 5).includes(".xlsx")) { | |||
| if (!file.name.toLowerCase().endsWith(".xlsx")) { | |||
| setWarningText("Please upload a valid file (File format: .xlsx)."); | |||
| setIsWarningPopUp(true); | |||
| document.getElementById("uploadFileBtn").value = ""; | |||
| event.target.value = ""; | |||
| return; | |||
| } | |||
| file['id'] = attachments.length; | |||
| setAttachments([ | |||
| ...attachments, | |||
| file | |||
| ]); | |||
| document.getElementById("uploadFileBtn").value = ""; | |||
| setAttachments([...attachments, file]); | |||
| event.target.value = ""; | |||
| } | |||
| } | |||
| }; | |||
| const doExport=()=>{ | |||
| setWaitDownload(true) | |||
| const doExport = () => { | |||
| setWaitDownload(true); | |||
| HttpUtils.fileDownload({ | |||
| url: UrlUtils.GET_ISSUE_LIST, | |||
| params: exportCriteria, | |||
| onResponse: () => { | |||
| setTimeout(()=> setWaitDownload(false), 500) | |||
| setTimeout(() => setWaitDownload(false), 500); | |||
| } | |||
| }); | |||
| } | |||
| }; | |||
| const importHoliday = () => { | |||
| setWaitImport(true); | |||
| if (!attachments || attachments.length <= 0) { | |||
| setWarningText("Please upload file."); | |||
| setSaving(false); | |||
| setWaitImport(false); | |||
| return; | |||
| } | |||
| HttpUtils.postWithFiles({ | |||
| url: UrlUtils.POST_ISSUE_FILE, | |||
| files: attachments, | |||
| onSuccess: () => { | |||
| notifySaveSuccess() | |||
| notifySaveSuccess(); | |||
| setWaitImport(false); | |||
| setAttachments([]); | |||
| loadCombo(); | |||
| loadForm(); | |||
| } | |||
| }); | |||
| } | |||
| }; | |||
| return ( | |||
| !onReady ? | |||
| @@ -167,7 +162,7 @@ const Index = () => { | |||
| </Grid> | |||
| : | |||
| ( | |||
| <Grid container sx={{minHeight: '87vh', backgroundColor: 'backgroundColor.default'}} direction="column" justifyContent="flex-start" alignItems="center" > | |||
| <Grid container sx={{ minHeight: '87vh', backgroundColor: 'backgroundColor.default' }} direction="column" justifyContent="flex-start" alignItems="center"> | |||
| <Grid item xs={12} width="100%"> | |||
| <div style={BackgroundHead} width="100%"> | |||
| <Stack direction="row" height='70px'> | |||
| @@ -175,90 +170,107 @@ const Index = () => { | |||
| </Stack> | |||
| </div> | |||
| </Grid> | |||
| {!onExportReady? | |||
| <LoadingComponent />: | |||
| {!onExportReady ? | |||
| <LoadingComponent /> : | |||
| <Grid item xs={12} md={12} lg={12} width="100%"> | |||
| <ExportForm | |||
| <ExportForm | |||
| applyExport={applyExport} | |||
| comboData={holidayComboData} | |||
| waitDownload={waitDownload} | |||
| /> | |||
| </Grid> | |||
| } | |||
| <Grid item xs={12} md={12} lg={6} width="100%"> | |||
| <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={2} sx={{ml:2,mt:1}} > | |||
| <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | |||
| <input | |||
| id="uploadFileBtn" | |||
| name="file" | |||
| type="file" | |||
| accept=".xlsx" | |||
| style={{ display: 'none' }} | |||
| disabled={waitImport} | |||
| onChange={(event) => { | |||
| readFile(event) | |||
| }} | |||
| /> | |||
| <label htmlFor="uploadFileBtn"> | |||
| {isGrantedAny(["MAINTAIN_GAZETTE_ISSUE"]) && | |||
| <Grid item xs={12} md={12} lg={6} width="100%"> | |||
| <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={2} sx={{ ml: 2, mt: 1 }}> | |||
| <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | |||
| <Button | |||
| component="span" | |||
| variant="contained" | |||
| size="large" | |||
| disabled={waitImport} | |||
| type="button" | |||
| onClick={() => { | |||
| if (fileInputRef.current) { | |||
| fileInputRef.current.click(); | |||
| } | |||
| }} | |||
| onKeyDown={(event) => { | |||
| if (event.key === 'Enter' || event.key === ' ') { | |||
| event.preventDefault(); | |||
| if (fileInputRef.current) { | |||
| fileInputRef.current.click(); | |||
| } | |||
| } | |||
| }} | |||
| > | |||
| <Typography variant="h5">Upload Files</Typography> | |||
| </Button> | |||
| </label> | |||
| </ThemeProvider> | |||
| </Stack> | |||
| </Grid> | |||
| {/*row 1*/} | |||
| <input | |||
| id="uploadFileBtn" | |||
| name="file" | |||
| type="file" | |||
| accept=".xlsx" | |||
| hidden | |||
| disabled={waitImport} | |||
| onChange={readFile} | |||
| aria-label={intl.formatMessage({ id: 'ariaUploadExcelFile' })} | |||
| ref={fileInputRef} | |||
| /> | |||
| </ThemeProvider> | |||
| </Stack> | |||
| </Grid> | |||
| } | |||
| {/* Row 1 */} | |||
| <Grid item xs={12} md={12} lg={12} width="100%"> | |||
| <SearchForm | |||
| <SearchForm | |||
| applySearch={applySearch} | |||
| comboData={comboData} | |||
| onGridReady={onGridReady} | |||
| /> | |||
| </Grid> | |||
| {/*row 2*/} | |||
| {!onSearchReady? | |||
| <LoadingComponent/>: | |||
| {/* Row 2 */} | |||
| {!onSearchReady ? | |||
| <LoadingComponent /> : | |||
| <Grid item xs={12} md={12} lg={12} width="100%"> | |||
| <MainCard elevation={0} | |||
| border={false} | |||
| content={false} | |||
| > | |||
| <MainCard elevation={0} border={false} content={false}> | |||
| <GazetteIssueTable | |||
| searchCriteria={searchCriteria} | |||
| searchCriteria={searchCriteria} | |||
| applyGridOnReady={applyGridOnReady} | |||
| /> | |||
| </MainCard> | |||
| </Grid> | |||
| } | |||
| <div> | |||
| <Dialog | |||
| open={isWarningPopUp} | |||
| onClose={() => setIsWarningPopUp(false)} | |||
| PaperProps={{ | |||
| sx: { | |||
| minWidth: '40vw', | |||
| maxWidth: { xs: '90vw', s: '90vw', m: '70vw', lg: '70vw' }, | |||
| maxHeight: { xs: '90vh', s: '70vh', m: '70vh', lg: '60vh' } | |||
| } | |||
| }} | |||
| > | |||
| <DialogTitle><Typography variant="h3">Warning</Typography></DialogTitle> | |||
| <DialogContent style={{ display: 'flex', }}> | |||
| <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setIsWarningPopUp(false)}><Typography variant="h5">OK</Typography></Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| </div> | |||
| </Grid > | |||
| <Dialog | |||
| open={isWarningPopUp} | |||
| onClose={() => setIsWarningPopUp(false)} | |||
| PaperProps={{ | |||
| sx: { | |||
| minWidth: '40vw', | |||
| maxWidth: { xs: '90vw', s: '90vw', m: '70vw', lg: '70vw' }, | |||
| maxHeight: { xs: '90vh', s: '70vh', m: '70vh', lg: '60vh' } | |||
| } | |||
| }} | |||
| > | |||
| <DialogTitle> | |||
| <Typography variant="h3">Warning</Typography> | |||
| </DialogTitle> | |||
| <DialogContent style={{ display: 'flex' }}> | |||
| <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setIsWarningPopUp(false)}> | |||
| <Typography variant="h5">OK</Typography> | |||
| </Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| </Grid> | |||
| ) | |||
| ); | |||
| }; | |||
| export default Index; | |||
| export default Index; | |||
| @@ -9,13 +9,13 @@ import { dateStr } from "utils/DateUtils"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function HolidayTable({ recordList }) { | |||
| export default function HolidayTable({ recordList, applyGridOnReady }) { | |||
| const [rows, setRows] = React.useState(recordList); | |||
| // const navigate = useNavigate() | |||
| useEffect(() => { | |||
| console.log(recordList) | |||
| // console.log(recordList) | |||
| setRows(recordList.records); | |||
| }, [recordList]); | |||
| @@ -48,8 +48,13 @@ export default function HolidayTable({ recordList }) { | |||
| rows={rows} | |||
| columns={columns} | |||
| customPageSize={20} | |||
| applyGridOnReady={applyGridOnReady} | |||
| // onRowDoubleClick={handleRowDoubleClick} | |||
| getRowHeight={() => 'auto'} | |||
| // doLoad={React.useMemo(() => ({ | |||
| // url: LIST_PROOF, | |||
| // params: _searchCriteria, | |||
| // }), [_searchCriteria])} | |||
| /> | |||
| </div> | |||
| ); | |||
| @@ -16,7 +16,7 @@ import {ThemeProvider} from "@emotion/react"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const SearchHolidayForm = ({ applySearch, comboData}) => { | |||
| const SearchHolidayForm = ({ applySearch, comboData, onGridReady}) => { | |||
| const [selectedYear, setSelectedYear] = React.useState([]); | |||
| // const [defaultYear, setDefaultYear] = React.useState(searchCriteria.year); | |||
| const [comboList, setComboList] = React.useState([]); | |||
| @@ -79,9 +79,9 @@ const SearchHolidayForm = ({ applySearch, comboData}) => { | |||
| setSelectedYear(newValue); | |||
| }} | |||
| sx={{ | |||
| "& .MuiInputBase-root": { height: "41px" }, | |||
| "#year-combo": { padding: "0px 0px 0px 0px" }, | |||
| "& .MuiAutocomplete-endAdornment": { top: "auto" }, | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => <TextField {...params} placeholder={""}/>} | |||
| /> | |||
| @@ -114,6 +114,7 @@ const SearchHolidayForm = ({ applySearch, comboData}) => { | |||
| <Button | |||
| variant="contained" | |||
| type="submit" | |||
| disabled={onGridReady} | |||
| > | |||
| Search | |||
| </Button> | |||
| @@ -1,4 +1,3 @@ | |||
| // material-ui | |||
| import { | |||
| Grid, | |||
| Typography, | |||
| @@ -12,9 +11,8 @@ import * as HttpUtils from "utils/HttpUtils"; | |||
| import Loadable from 'components/Loadable'; | |||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | |||
| const HolidayTable = Loadable(React.lazy(() => import('pages/Holiday/DataGrid'))) | |||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | |||
| // import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; | |||
| const HolidayTable = Loadable(React.lazy(() => import('pages/Holiday/DataGrid'))); | |||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png'; | |||
| import MainCard from 'components/MainCard'; | |||
| const SearchForm = Loadable(React.lazy(() => import('./SearchForm'))); | |||
| @@ -26,59 +24,50 @@ const BackgroundHead = { | |||
| backgroundRepeat: 'no-repeat', | |||
| backgroundColor: '#0C489E', | |||
| backgroundPosition: 'right' | |||
| } | |||
| // import { useNavigate } from "react-router"; | |||
| import {PNSPS_LONG_BUTTON_THEME} from "themes/buttonConst"; | |||
| import {ThemeProvider} from "@emotion/react"; | |||
| }; | |||
| import { PNSPS_LONG_BUTTON_THEME } from "themes/buttonConst"; | |||
| import { ThemeProvider } from "@emotion/react"; | |||
| import { dateStr_Year } from "utils/DateUtils"; | |||
| import { notifySaveSuccess } from 'utils/CommonFunction'; | |||
| import { isGrantedAny } from "auth/utils"; | |||
| import { useIntl } from 'react-intl'; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const Index = () => { | |||
| const intl = useIntl(); | |||
| const [record, setRecord] = React.useState([]); | |||
| const [comboData, setComboData] = React.useState([]); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| const [onSearchReady, setOnSearchReady] = React.useState(false); | |||
| // const navigate = useNavigate() | |||
| const [onGridReady, setGridOnReady] = React.useState(false); | |||
| const [searchCriteria, setSearchCriteria] = React.useState({ | |||
| year: dateStr_Year(new Date()), | |||
| // dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }); | |||
| const [attachments, setAttachments] = React.useState([]); | |||
| const [waitImport, setWaitImport] = React.useState(false); | |||
| const [waitDownload, setWaitDownload] = React.useState(false); | |||
| const [isWarningPopUp, setIsWarningPopUp] = React.useState(false); | |||
| const [warningText, setWarningText] = React.useState(""); | |||
| // React.useLayoutEffect(() => { | |||
| // loadForm(); | |||
| // }, []); | |||
| // React.useLayoutEffect(() => { | |||
| // if (comboData) { | |||
| // setOnReady(true); | |||
| // } | |||
| // }, [comboData]); | |||
| const fileInputRef = React.useRef(null); | |||
| React.useEffect(() => { | |||
| // console.log(searchCriteria) | |||
| setOnSearchReady(false) | |||
| setOnSearchReady(false); | |||
| loadForm(); | |||
| }, [searchCriteria]); | |||
| function loadForm() { | |||
| HttpUtils.get({ | |||
| url: UrlUtils.GET_HOLIDAY, | |||
| params: searchCriteria, | |||
| onSuccess: (responseData) => { | |||
| // console.log(responseData) | |||
| setRecord(responseData); | |||
| if (comboData.length == 0) { | |||
| if (comboData.length === 0) { | |||
| loadCombo(); | |||
| }else{ | |||
| setOnSearchReady(true) | |||
| } else { | |||
| setOnSearchReady(true); | |||
| } | |||
| } | |||
| }); | |||
| @@ -91,15 +80,20 @@ const Index = () => { | |||
| let combo = responseData; | |||
| setComboData(combo); | |||
| setOnReady(true); | |||
| setOnSearchReady(true) | |||
| setOnSearchReady(true); | |||
| } | |||
| }); | |||
| } | |||
| function applySearch(input) { | |||
| setGridOnReady(true); | |||
| setSearchCriteria(input); | |||
| } | |||
| function applyGridOnReady(input) { | |||
| setGridOnReady(input); | |||
| } | |||
| React.useEffect(() => { | |||
| if (attachments.length > 0) { | |||
| importHoliday(); | |||
| @@ -107,52 +101,51 @@ const Index = () => { | |||
| }, [attachments]); | |||
| const readFile = (event) => { | |||
| let file = event.target.files[0]; | |||
| let file = event.target.files && event.target.files[0]; | |||
| if (file) { | |||
| if (!file.name.toLowerCase().substr(file.name.length - 5).includes(".xlsx")) { | |||
| if (!file.name.toLowerCase().endsWith(".xlsx")) { | |||
| setWarningText("Please upload a valid file (File format: .xlsx)."); | |||
| setIsWarningPopUp(true); | |||
| document.getElementById("uploadFileBtn").value = ""; | |||
| // clear the input value | |||
| event.target.value = ""; | |||
| return; | |||
| } | |||
| file['id'] = attachments.length; | |||
| setAttachments([ | |||
| ...attachments, | |||
| file | |||
| ]); | |||
| document.getElementById("uploadFileBtn").value = ""; | |||
| setAttachments(prev => [...prev, file]); | |||
| // clear the input value | |||
| event.target.value = ""; | |||
| } | |||
| } | |||
| }; | |||
| const doExport=()=>{ | |||
| setWaitDownload(true) | |||
| const doExport = () => { | |||
| setWaitDownload(true); | |||
| HttpUtils.fileDownload({ | |||
| url: UrlUtils.GET_HOLIDAY_TEMPLATE, | |||
| onResponse: () => { | |||
| setTimeout(()=> setWaitDownload(false), 500) | |||
| setTimeout(() => setWaitDownload(false), 500); | |||
| } | |||
| }); | |||
| } | |||
| }; | |||
| const importHoliday = () => { | |||
| setWaitImport(true); | |||
| setOnSearchReady(false); | |||
| if (!attachments || attachments.length <= 0) { | |||
| setWarningText("Please upload file."); | |||
| setSaving(false); | |||
| setWaitImport(false); | |||
| return; | |||
| } | |||
| HttpUtils.postWithFiles({ | |||
| url: UrlUtils.POST_HOLIDAY, | |||
| files: attachments, | |||
| onSuccess: () => { | |||
| notifySaveSuccess() | |||
| notifySaveSuccess(); | |||
| setWaitImport(false); | |||
| setAttachments([]); | |||
| loadForm(); | |||
| } | |||
| }); | |||
| } | |||
| }; | |||
| return ( | |||
| !onReady ? | |||
| @@ -163,7 +156,7 @@ const Index = () => { | |||
| </Grid> | |||
| : | |||
| ( | |||
| <Grid container sx={{minHeight: '87vh', backgroundColor: 'backgroundColor.default'}} direction="column" justifyContent="flex-start" alignItems="center" > | |||
| <Grid container sx={{ minHeight: '87vh', backgroundColor: 'backgroundColor.default' }} direction="column" justifyContent="flex-start" alignItems="center" > | |||
| <Grid item xs={12} width="100%"> | |||
| <div style={BackgroundHead} width="100%"> | |||
| <Stack direction="row" height='70px'> | |||
| @@ -171,97 +164,106 @@ const Index = () => { | |||
| </Stack> | |||
| </div> | |||
| </Grid> | |||
| <Grid item xs={12} md={12} lg={6} width="100%"> | |||
| <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}> | |||
| <label htmlFor="downloadFileBtn"> | |||
| <Button | |||
| component="span" | |||
| variant="contained" | |||
| size="large" | |||
| disabled={waitDownload} | |||
| onClick={doExport} | |||
| > | |||
| <Typography variant="h5">Export</Typography> | |||
| </Button> | |||
| </label> | |||
| <Button | |||
| variant="contained" | |||
| size="large" | |||
| disabled={waitDownload} | |||
| onClick={doExport} | |||
| aria-label={intl.formatMessage({ id: 'ariaExportHolidayTemplate' })} | |||
| > | |||
| <Typography variant="h5">Export</Typography> | |||
| </Button> | |||
| </ThemeProvider> | |||
| <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | |||
| <input | |||
| id="uploadFileBtn" | |||
| name="file" | |||
| type="file" | |||
| accept=".xlsx" | |||
| style={{ display: 'none' }} | |||
| disabled={waitImport} | |||
| onChange={(event) => { | |||
| readFile(event) | |||
| }} | |||
| /> | |||
| <label htmlFor="uploadFileBtn"> | |||
| {isGrantedAny(["MAINTAIN_GAZETTE_ISSUE"]) ? | |||
| <ThemeProvider theme={PNSPS_LONG_BUTTON_THEME}> | |||
| <Button | |||
| component="span" | |||
| variant="contained" | |||
| size="large" | |||
| disabled={waitImport} | |||
| type="button" | |||
| onClick={() => { | |||
| if (fileInputRef.current) { | |||
| fileInputRef.current.click(); | |||
| } | |||
| }} | |||
| onKeyDown={(event) => { | |||
| if (event.key === 'Enter' || event.key === ' ') { | |||
| event.preventDefault(); | |||
| if (fileInputRef.current) { | |||
| fileInputRef.current.click(); | |||
| } | |||
| } | |||
| }} | |||
| > | |||
| <Typography variant="h5">Upload Files</Typography> | |||
| </Button> | |||
| </label> | |||
| </ThemeProvider> | |||
| <input | |||
| id="uploadFileBtn" | |||
| name="file" | |||
| type="file" | |||
| accept=".xlsx" | |||
| hidden | |||
| disabled={waitImport} | |||
| onChange={readFile} | |||
| aria-label={intl.formatMessage({ id: 'ariaUploadExcelFile' })} | |||
| ref={fileInputRef} | |||
| /> | |||
| </ThemeProvider> | |||
| : null | |||
| } | |||
| </Stack> | |||
| </Grid> | |||
| {/*row 1*/} | |||
| {/* row 1 */} | |||
| <Grid item xs={12} md={12} lg={12} width="100%"> | |||
| <SearchForm | |||
| <SearchForm | |||
| applySearch={applySearch} | |||
| // generateXML={generateXML} | |||
| searchCriteria={searchCriteria} | |||
| comboData={comboData} | |||
| onGridReady={onGridReady} | |||
| /> | |||
| </Grid> | |||
| {/*row 2*/} | |||
| {!onSearchReady? | |||
| <LoadingComponent/>: | |||
| {/* row 2 */} | |||
| {!onSearchReady ? | |||
| <LoadingComponent /> : | |||
| <Grid item xs={12} md={12} lg={12} width="100%"> | |||
| <MainCard elevation={0} | |||
| border={false} | |||
| content={false} | |||
| > | |||
| <MainCard elevation={0} border={false} content={false}> | |||
| <HolidayTable | |||
| recordList={record} | |||
| applyGridOnReady={applyGridOnReady} | |||
| /> | |||
| </MainCard> | |||
| </Grid> | |||
| } | |||
| <div> | |||
| <Dialog | |||
| open={isWarningPopUp} | |||
| onClose={() => setIsWarningPopUp(false)} | |||
| PaperProps={{ | |||
| sx: { | |||
| minWidth: '40vw', | |||
| maxWidth: { xs: '90vw', s: '90vw', m: '70vw', lg: '70vw' }, | |||
| maxHeight: { xs: '90vh', s: '70vh', m: '70vh', lg: '60vh' } | |||
| } | |||
| }} | |||
| > | |||
| <DialogTitle><Typography variant="h3">Warning</Typography></DialogTitle> | |||
| <DialogContent style={{ display: 'flex', }}> | |||
| <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setIsWarningPopUp(false)}><Typography variant="h5">OK</Typography></Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| </div> | |||
| <Dialog | |||
| open={isWarningPopUp} | |||
| onClose={() => setIsWarningPopUp(false)} | |||
| PaperProps={{ | |||
| sx: { | |||
| minWidth: '40vw', | |||
| maxWidth: { xs: '90vw', s: '90vw', m: '70vw', lg: '70vw' }, | |||
| maxHeight: { xs: '90vh', s: '70vh', m: '70vh', lg: '60vh' } | |||
| } | |||
| }} | |||
| > | |||
| <DialogTitle><Typography variant="h3">Warning</Typography></DialogTitle> | |||
| <DialogContent style={{ display: 'flex' }}> | |||
| <Typography variant="h4" style={{ padding: '16px' }}>{warningText}</Typography> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setIsWarningPopUp(false)}><Typography variant="h5">OK</Typography></Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| </Grid > | |||
| ) | |||
| ); | |||
| }; | |||
| export default Index; | |||
| export default Index; | |||
| @@ -0,0 +1,187 @@ | |||
| // import { useState } from 'react'; | |||
| // material-ui | |||
| import { | |||
| Grid, | |||
| Typography, | |||
| Stack, | |||
| Paper, | |||
| Box, | |||
| CircularProgress, | |||
| Button | |||
| } from '@mui/material'; | |||
| import * as React from "react"; | |||
| import { GET_JVM_INFO, GET_NOTIFICATION_QUEUE_STATUS } from "utils/ApiPathConst"; | |||
| import axios from "axios"; | |||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | |||
| const JVMDefault = () => { | |||
| const [jvmInfo, setJvmInfo] = React.useState(null); | |||
| const [loading, setLoading] = React.useState(true); | |||
| const [error, setError] = React.useState(null); | |||
| const [queueStatus, setQueueStatus] = React.useState(null); | |||
| const [queueLoading, setQueueLoading] = React.useState(false); | |||
| const [queueError, setQueueError] = React.useState(null); | |||
| const fetchJvmInfo = () => { | |||
| setLoading(true); | |||
| setError(null); | |||
| axios.get(`${GET_JVM_INFO}`) | |||
| .then((response) => { | |||
| if (response.status === 200) { | |||
| console.log(response) | |||
| setJvmInfo(response.data); | |||
| } | |||
| }) | |||
| .catch(error => { | |||
| setError(error); | |||
| setLoading(false); | |||
| }); | |||
| }; | |||
| const fetchNotificationQueueStatus = () => { | |||
| setQueueLoading(true); | |||
| setQueueError(null); | |||
| setQueueStatus(null); | |||
| axios.get(`${GET_NOTIFICATION_QUEUE_STATUS}`) | |||
| .then((response) => { | |||
| if (response.status === 200) { | |||
| setQueueStatus(response.data); | |||
| } | |||
| }) | |||
| .catch(err => { | |||
| setQueueError(err); | |||
| }) | |||
| .finally(() => { | |||
| setQueueLoading(false); | |||
| }); | |||
| }; | |||
| React.useEffect(() => { | |||
| localStorage.setItem('searchCriteria', ""); | |||
| setLoading(false); | |||
| }, []); | |||
| React.useEffect(() => { | |||
| if(jvmInfo != null) { | |||
| if (Object.keys(jvmInfo).length > 0 && jvmInfo !== undefined) { | |||
| setLoading(false); | |||
| } | |||
| } | |||
| }, [jvmInfo]); | |||
| const BackgroundHead = { | |||
| backgroundImage: `url(${titleBackgroundImg})`, | |||
| width: '100%', | |||
| height: '100%', | |||
| backgroundSize: 'contain', | |||
| backgroundRepeat: 'no-repeat', | |||
| backgroundColor: '#0C489E', | |||
| backgroundPosition: 'right' | |||
| }; | |||
| return ( | |||
| <Grid container sx={{ minHeight: '87vh', backgroundColor: "backgroundColor.default" }} direction="column"> | |||
| <Grid item xs={12}> | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="space-between" alignItems="center"> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{ "textShadow": "0px 0px 25px #0C489E" }}> | |||
| System Background Status | |||
| </Typography> | |||
| </Stack> | |||
| </div> | |||
| </Grid> | |||
| <Grid item xs={12} ml={15} mb={2} mt={2}> | |||
| <Stack direction="row" spacing={2}> | |||
| <Button | |||
| size="large" | |||
| variant="contained" | |||
| type="submit" | |||
| sx={{ | |||
| textTransform: 'capitalize', | |||
| alignItems: 'end' | |||
| }} | |||
| onClick={fetchJvmInfo} | |||
| disabled={loading} | |||
| > | |||
| <Typography variant="h5">JVM Info</Typography> | |||
| </Button> | |||
| <Button | |||
| size="large" | |||
| variant="contained" | |||
| type="button" | |||
| sx={{ | |||
| textTransform: 'capitalize', | |||
| alignItems: 'end' | |||
| }} | |||
| onClick={fetchNotificationQueueStatus} | |||
| disabled={queueLoading} | |||
| > | |||
| <Typography variant="h5">Notification Queue Status</Typography> | |||
| </Button> | |||
| </Stack> | |||
| </Grid> | |||
| <Grid item xs={12} ml={15} mb={2} mt={2}> | |||
| <Paper elevation={3} sx={{ p: 2, bgcolor: 'background.paper' }}> | |||
| {loading ? ( | |||
| <Box display="flex" justifyContent="center" alignItems="center" minHeight={200}> | |||
| <CircularProgress /> | |||
| </Box> | |||
| ) : error ? ( | |||
| <Typography color="error">Error: {error.message}</Typography> | |||
| ) : jvmInfo ? ( | |||
| <Box | |||
| component="pre" | |||
| sx={{ | |||
| p: 2, | |||
| borderRadius: 1, | |||
| bgcolor: 'grey.100', | |||
| overflow: 'auto', | |||
| maxHeight: 400, | |||
| fontSize: '0.875rem', | |||
| lineHeight: 1.6 | |||
| }} | |||
| > | |||
| {JSON.stringify(jvmInfo, null, 2)} | |||
| </Box> | |||
| ) : ( | |||
| <Typography>No data available</Typography> | |||
| )} | |||
| </Paper> | |||
| </Grid> | |||
| <Grid item xs={12} ml={15} mb={2} mt={2}> | |||
| <Paper elevation={3} sx={{ p: 2, bgcolor: 'background.paper' }}> | |||
| <Typography variant="h6" gutterBottom>Notification Queue Status</Typography> | |||
| {queueLoading ? ( | |||
| <Box display="flex" justifyContent="center" alignItems="center" minHeight={120}> | |||
| <CircularProgress /> | |||
| </Box> | |||
| ) : queueError ? ( | |||
| <Typography color="error">Error: {queueError.message}</Typography> | |||
| ) : queueStatus ? ( | |||
| <Box | |||
| component="pre" | |||
| sx={{ | |||
| p: 2, | |||
| borderRadius: 1, | |||
| bgcolor: 'grey.100', | |||
| overflow: 'auto', | |||
| maxHeight: 300, | |||
| fontSize: '0.875rem', | |||
| lineHeight: 1.6 | |||
| }} | |||
| > | |||
| {JSON.stringify(queueStatus, null, 2)} | |||
| </Box> | |||
| ) : ( | |||
| <Typography color="text.secondary">Click "Notification Queue Status" to load data.</Typography> | |||
| )} | |||
| </Paper> | |||
| </Grid> | |||
| </Grid> | |||
| ); | |||
| }; | |||
| export default JVMDefault; | |||
| @@ -17,6 +17,8 @@ const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/Loa | |||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | |||
| import { FormattedMessage } from "react-intl"; | |||
| import usePageTitle from 'components/usePageTitle'; | |||
| const BackgroundHead = { | |||
| backgroundImage: `url(${titleBackgroundImg})`, | |||
| width: '100%', | |||
| @@ -30,6 +32,8 @@ const BackgroundHead = { | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const Index = () => { | |||
| usePageTitle("msgDetails"); | |||
| const params = useParams(); | |||
| const navigate = useNavigate() | |||
| @@ -72,7 +76,7 @@ const Index = () => { | |||
| <Grid item xs={12} width="100%"> | |||
| <div style={BackgroundHead} width="100%"> | |||
| <Stack direction="row" height='70px'> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{ pt: 2 }}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{ pt: 2 }}> | |||
| <FormattedMessage id="msgDetails" /> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -83,7 +87,7 @@ const Index = () => { | |||
| <Grid container justifyContent="flex-start" alignItems="center" > | |||
| <center> | |||
| <Grid item xs={12} md={12} sx={{p:2}} > | |||
| <Typography variant="h3" sx={{ textAlign: "left", borderBottom: "1px solid black" }}> | |||
| <Typography component="h2" variant="h3" sx={{ textAlign: "left", borderBottom: "1px solid black" }}> | |||
| {record?.subject} | |||
| </Typography> | |||
| <Typography sx={{p:1}} align="justify">{DateUtils.datetimeStr(record?.sentDate)}</Typography> | |||
| @@ -91,7 +95,7 @@ const Index = () => { | |||
| <div dangerouslySetInnerHTML={{__html: record?.content}}></div> | |||
| </Typography> | |||
| <Typography variant="h4" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | |||
| <Typography component="h3" variant="h4" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | |||
| <Button | |||
| component="span" | |||
| variant="contained" | |||
| @@ -5,13 +5,21 @@ import { useNavigate } from "react-router-dom"; | |||
| import { FiDataGrid } from "components/FiDataGrid"; | |||
| import {useIntl} from "react-intl"; | |||
| import { clickableLink } from 'utils/CommonFunction'; | |||
| import {GET_MSG_LIST} from "utils/ApiPathConst"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function MsgTable({ recordList }) { | |||
| const [rows, setRows] = React.useState(recordList); | |||
| export default function MsgTable({ searchCriteria, applyGridOnReady, applySearch}) { | |||
| const navigate = useNavigate() | |||
| const intl = useIntl(); | |||
| const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria); | |||
| React.useEffect(() => { | |||
| set_searchCriteria(searchCriteria); | |||
| }, [searchCriteria]); | |||
| const _sx = { | |||
| padding: "4 2 4 2", | |||
| boxShadow: 1, | |||
| @@ -25,48 +33,56 @@ export default function MsgTable({ recordList }) { | |||
| '& .MuiDataGrid-footerContainer': { | |||
| border: 1, | |||
| borderColor: "#EEE" | |||
| } | |||
| }, | |||
| "& .MuiDataGrid-columnHeaderTitle": { | |||
| whiteSpace: "normal", | |||
| lineHeight: "normal" | |||
| }, | |||
| "& .MuiDataGrid-columnHeader": { | |||
| // Forced to use important since overriding inline styles | |||
| height: "unset !important" | |||
| }, | |||
| } | |||
| React.useEffect(() => { | |||
| setRows(recordList); | |||
| }, [recordList]); | |||
| const handleEditClick = (params) => () => { | |||
| navigate('/msg/details/' + params.row.id); | |||
| }; | |||
| const columns = [ | |||
| { | |||
| id: 'sentDate', | |||
| field: 'sentDate', | |||
| headerName: intl.formatMessage({id: 'date'}), | |||
| width: 160, | |||
| renderCell: (params) => { | |||
| return DateUtils.datetimeStr(params.row.sentDate); | |||
| width: 170, | |||
| valueGetter: (params) => { | |||
| return DateUtils.datetimeStr(params?.value); | |||
| }, | |||
| }, | |||
| { | |||
| field: 'actions', | |||
| headerName: intl.formatMessage({id: 'payId'}), | |||
| field: 'subject', | |||
| headerName: intl.formatMessage({id: 'subject'}), | |||
| flex: 1 , | |||
| cellClassName: 'actions', | |||
| cellClassName: 'subject', | |||
| renderCell: (params) => { | |||
| return clickableLink('/msg/details/' + params.row.id, params.row.subject); | |||
| }, | |||
| }, | |||
| ]; | |||
| function handleEditClick(params) { | |||
| navigate('/msg/details/' + params.row.id); | |||
| } | |||
| return ( | |||
| <div style={{ minHeight: 400, width: '100%' }}> | |||
| <div style={{ width: '100%', overflowX: 'auto'}}> | |||
| <FiDataGrid | |||
| sx={_sx} | |||
| rowHeight={80} | |||
| rows={rows} | |||
| columns={columns} | |||
| customPageSize={20} | |||
| customPageSize={10} | |||
| getRowHeight={() => 'auto'} | |||
| onRowDoubleClick={handleEditClick} | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| doLoad={React.useMemo(() => ({ | |||
| url: GET_MSG_LIST, | |||
| params: _searchCriteria, | |||
| }), [_searchCriteria])} | |||
| /> | |||
| </div> | |||
| ); | |||
| @@ -21,7 +21,7 @@ import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const SearchForm = ({ applySearch, searchCriteria }) => { | |||
| const SearchForm = ({ applySearch, searchCriteria, onGridReady }) => { | |||
| const intl = useIntl(); | |||
| const [minDate, setMinDate] = React.useState(searchCriteria.dateFrom); | |||
| const [maxDate, setMaxDate] = React.useState(searchCriteria.dateTo); | |||
| @@ -64,9 +64,11 @@ const SearchForm = ({ applySearch, searchCriteria }) => { | |||
| sentDateTo = DateUtils.dateValue(toDateValue) | |||
| } | |||
| const temp = { | |||
| keywork: data.keywork, | |||
| keyword: data.keyword, | |||
| dateFrom: sentDateFrom, | |||
| dateTo: sentDateTo, | |||
| start:0, | |||
| limit:10 | |||
| }; | |||
| applySearch(temp); | |||
| }; | |||
| @@ -75,6 +77,8 @@ const SearchForm = ({ applySearch, searchCriteria }) => { | |||
| setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | |||
| setMaxDate(DateUtils.dateValue(new Date())) | |||
| reset(); | |||
| localStorage.setItem('searchCriteria',"") | |||
| } | |||
| @@ -115,7 +119,7 @@ const SearchForm = ({ applySearch, searchCriteria }) => { | |||
| <Grid item xs={12} s={6} md={6} lg={4} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <Grid container> | |||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateFrom" | |||
| @@ -147,7 +151,7 @@ const SearchForm = ({ applySearch, searchCriteria }) => { | |||
| </Grid> | |||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateTo" | |||
| @@ -196,6 +200,7 @@ const SearchForm = ({ applySearch, searchCriteria }) => { | |||
| <Button | |||
| variant="contained" | |||
| type="submit" | |||
| disabled={onGridReady} | |||
| aria-label={intl.formatMessage({id: 'submit'})} | |||
| > | |||
| <FormattedMessage id="submit"/> | |||
| @@ -5,9 +5,9 @@ import { | |||
| Stack | |||
| } from '@mui/material'; | |||
| import MainCard from "components/MainCard"; | |||
| import * as UrlUtils from "utils/ApiPathConst"; | |||
| // import * as UrlUtils from "utils/ApiPathConst"; | |||
| import * as React from "react"; | |||
| import * as HttpUtils from "utils/HttpUtils"; | |||
| // import * as HttpUtils from "utils/HttpUtils"; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import Loadable from 'components/Loadable'; | |||
| @@ -16,7 +16,8 @@ const SearchForm = Loadable(React.lazy(() => import('./SearchForm'))); | |||
| const EventTable = Loadable(React.lazy(() => import('./DataGrid'))); | |||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | |||
| import {FormattedMessage} from "react-intl"; | |||
| import { getSearchCriteria } from "auth/utils"; | |||
| import usePageTitle from "components/usePageTitle"; | |||
| const BackgroundHead = { | |||
| backgroundImage: `url(${titleBackgroundImg})`, | |||
| width: '100%', | |||
| @@ -30,46 +31,64 @@ const BackgroundHead = { | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const Index = () => { | |||
| usePageTitle("systemMessage"); | |||
| const [record,setRecord] = React.useState([]); | |||
| const [searchCriteria, setSearchCriteria] = React.useState({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }); | |||
| const [searchCriteria, setSearchCriteria] = React.useState({}); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| const [onGridReady, setGridOnReady] = React.useState(false); | |||
| React.useEffect(() => { | |||
| setOnReady(true); | |||
| }, [record]); | |||
| if (Object.keys(getSearchCriteria(window.location.pathname)).length>0){ | |||
| setSearchCriteria(getSearchCriteria(window.location.pathname)) | |||
| }else{ | |||
| localStorage.setItem('searchCriteria',"") | |||
| setSearchCriteria({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }) | |||
| } | |||
| }, []); | |||
| React.useEffect(() => { | |||
| loadGrid(); | |||
| if(Object.keys(searchCriteria).length>0){ | |||
| setOnReady(true); | |||
| } | |||
| }, [searchCriteria]); | |||
| function loadGrid(){ | |||
| HttpUtils.get({ | |||
| url: UrlUtils.GET_MSG_LIST, | |||
| params: searchCriteria, | |||
| onSuccess: function(responseData){ | |||
| setRecord(responseData); | |||
| } | |||
| }); | |||
| } | |||
| // function loadGrid(){ | |||
| // HttpUtils.get({ | |||
| // url: UrlUtils.GET_MSG_LIST, | |||
| // params: searchCriteria, | |||
| // onSuccess: function(responseData){ | |||
| // setRecord(responseData); | |||
| // } | |||
| // }); | |||
| // } | |||
| function applySearch(input) { | |||
| setGridOnReady(true) | |||
| setSearchCriteria(input); | |||
| localStorage.setItem('searchCriteria', JSON.stringify({path:window.location.pathname,data:input})) | |||
| } | |||
| function applyGridOnReady(input) { | |||
| setGridOnReady(input); | |||
| } | |||
| return ( | |||
| !onReady ? | |||
| <LoadingComponent/> | |||
| <Grid container sx={{ minHeight: '95vh', mb: 3 }} direction="column" justifyContent="center" alignItems="center"> | |||
| <Grid item> | |||
| <LoadingComponent /> | |||
| </Grid> | |||
| </Grid> | |||
| : | |||
| <Grid container sx={{minHeight: '85vh',backgroundColor:'#ffffff'}} direction="column"> | |||
| <Grid container sx={{ minHeight: '95vh',backgroundColor: 'backgroundColor.default' }} direction="column"> | |||
| <Grid item xs={12}> | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <FormattedMessage id="systemMessage"/> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -78,8 +97,9 @@ const Index = () => { | |||
| {/*row 1*/} | |||
| <Grid item xs={12} md={12} lg={12}> | |||
| <SearchForm | |||
| applySearch={applySearch} | |||
| searchCriteria={searchCriteria} | |||
| applySearch={applySearch} | |||
| searchCriteria={searchCriteria} | |||
| onGridReady={onGridReady} | |||
| /> | |||
| </Grid> | |||
| {/*row 2*/} | |||
| @@ -90,7 +110,10 @@ const Index = () => { | |||
| sx={{width: "-webkit-fill-available"}} | |||
| > | |||
| <EventTable | |||
| recordList={record} | |||
| // recordList={record} | |||
| searchCriteria={searchCriteria} | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| /> | |||
| </MainCard> | |||
| </Grid> | |||
| @@ -14,6 +14,7 @@ import { useEffect, useState, lazy } from "react"; | |||
| import * as DateUtils from 'utils/DateUtils'; | |||
| import * as HttpUtils from 'utils/HttpUtils'; | |||
| import * as UrlUtils from "utils/ApiPathConst"; | |||
| import {checkMarkAsCreditClient} from 'utils/Utils'; | |||
| import * as FieldUtils from "utils/FieldUtils"; | |||
| import * as ComboData from "utils/ComboData"; | |||
| const LoadingComponent = Loadable(lazy(() => import('../../extra-pages/LoadingComponent'))); | |||
| @@ -24,18 +25,20 @@ import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | |||
| import { ThemeProvider } from "@emotion/react"; | |||
| import { isGrantedAny } from "auth/utils"; | |||
| import {DatePicker} from "@mui/x-date-pickers/DatePicker"; | |||
| import { DatePicker } from "@mui/x-date-pickers/DatePicker"; | |||
| import dayjs from "dayjs"; | |||
| import {DemoItem} from "@mui/x-date-pickers/internals/demo"; | |||
| import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; | |||
| import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; | |||
| import { DemoItem } from "@mui/x-date-pickers/internals/demo"; | |||
| import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; | |||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| const intl = useIntl(); | |||
| const [creditorConfirmPopUp, setCreditorConfirmPopUp] = React.useState(false); | |||
| const [nonCreditorConfirmPopUp, setNonCreditorConfirmPopUp] = React.useState(false); | |||
| const [afterSendPopUp, setAfterSendPopUp] = React.useState(false); | |||
| const [currentUserData, setCurrentUserData] = useState({}); | |||
| const [overduePublicNotice, setOverduePublicNotice] = useState(0); | |||
| const [editMode, setEditMode] = useState(false); | |||
| const [createMode, setCreateMode] = useState(false); | |||
| const [onReady, setOnReady] = useState(false); | |||
| @@ -44,7 +47,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| const [fromDate, setFromDate] = React.useState(null); | |||
| const [currentFromDate, setCurrentFromDate] = React.useState(null); | |||
| const [fromDateValue, setFromDateValue] = React.useState(null); | |||
| const {register, handleSubmit, reset} = useForm() | |||
| const { register, handleSubmit, reset } = useForm() | |||
| React.useEffect(() => { | |||
| setFromDateValue(fromDate); | |||
| @@ -54,14 +57,14 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| //if state data are ready and assign to different field | |||
| // console.log(currentApplicationDetailData) | |||
| if (Object.keys(currentUserData).length > 0) { | |||
| console.log(currentUserData) | |||
| if(DateUtils.dateValue(currentUserData.brExpiryDate)>DateUtils.dateValue(minDate)){ | |||
| // console.log(currentUserData) | |||
| if (DateUtils.dateValue(currentUserData.brExpiryDate) > DateUtils.dateValue(minDate)) { | |||
| setFromDate(currentUserData.brExpiryDate); | |||
| }else{ | |||
| } else { | |||
| setCurrentFromDate(currentUserData.brExpiryDate); | |||
| // setErrorMsg("Please select a date after today.") | |||
| } | |||
| setOnReady(true); | |||
| } | |||
| }, [currentUserData]); | |||
| @@ -115,7 +118,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| let sentDateFrom = ""; | |||
| if (fromDateValue == null) { | |||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInBusinessRegCertValidityDate' })) | |||
| }else{ | |||
| } else { | |||
| sentDateFrom = DateUtils.dateValue(fromDateValue) | |||
| HttpUtils.post({ | |||
| url: UrlUtils.POST_ORG_SAVE_PATH, | |||
| @@ -123,7 +126,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| id: id > 0 ? id : null, | |||
| enCompanyName: values.enCompanyName, | |||
| chCompanyName: values.chCompanyName, | |||
| orgShortName: values.orgShortName==="N/A"?"":values.orgShortName, | |||
| orgShortName: values.orgShortName === "N/A" ? "" : values.orgShortName, | |||
| brNo: values.brNo, | |||
| // brExpiryDate: values.brExpiryDate, | |||
| brExpiryDate: sentDateFrom, | |||
| @@ -188,9 +191,9 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| const onSubmit = (data) => { | |||
| let sentOrgShortName = ""; | |||
| if(data.orgShortName!=null && data.orgShortName!="" && data.orgShortName!="N/A"){ | |||
| sentOrgShortName = data.orgShortName | |||
| if (sentOrgShortName.length <=24){ | |||
| if (data.orgShortName != null && data.orgShortName != "" && data.orgShortName != "N/A") { | |||
| sentOrgShortName = data.orgShortName | |||
| if (sentOrgShortName.length <= 24) { | |||
| const temp = { | |||
| orgShortName: sentOrgShortName, | |||
| }; | |||
| @@ -230,6 +233,17 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| }); | |||
| } | |||
| const sendDn_Overdue = () => { | |||
| setNonCreditorConfirmPopUp(false); | |||
| HttpUtils.get({ | |||
| url: UrlUtils.GET_SEND_OVERDUE_CREDITOR_LIST + "/" + id, | |||
| onSuccess: (responseData) => { | |||
| setOverduePublicNotice(responseData.overduePublicNotice); | |||
| setAfterSendPopUp(true); | |||
| } | |||
| }); | |||
| } | |||
| return ( | |||
| <MainCard elevation={0} | |||
| border={false} | |||
| @@ -299,29 +313,47 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| { | |||
| currentUserData.creditor ? | |||
| <Grid item sx={{ ml: 3, mr: 3 }}> | |||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||
| <Button | |||
| variant="contained" | |||
| color="error" | |||
| onClick={() => setNonCreditorConfirmPopUp(true)} | |||
| > | |||
| Mark as Non-Credit Client | |||
| </Button> | |||
| </ThemeProvider> | |||
| </Grid> | |||
| !checkMarkAsCreditClient()? | |||
| <Grid item sx={{ ml: 3, mr: 3 }}> | |||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||
| <Button | |||
| variant="contained" | |||
| color="error" | |||
| onClick={() => setNonCreditorConfirmPopUp(true)} | |||
| > | |||
| Mark as Non-Credit Client | |||
| </Button> | |||
| </ThemeProvider> | |||
| </Grid>:null | |||
| : | |||
| <Grid item sx={{ ml: 3, mr: 3 }}> | |||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||
| <Button | |||
| variant="contained" | |||
| color="orange" | |||
| onClick={() => setCreditorConfirmPopUp(true)} | |||
| > | |||
| Mark as Credit Client | |||
| </Button> | |||
| </ThemeProvider> | |||
| </Grid> | |||
| <> | |||
| {!checkMarkAsCreditClient()? | |||
| <Grid item sx={{ ml: 3, mr: 3 }}> | |||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||
| <Button | |||
| variant="contained" | |||
| color="orange" | |||
| onClick={() => setCreditorConfirmPopUp(true)} | |||
| > | |||
| Mark as Credit Client | |||
| </Button> | |||
| </ThemeProvider> | |||
| </Grid>:null | |||
| } | |||
| { isGrantedAny("MAINTAIN_DEMANDNOTE")? | |||
| <Grid item sx={{ ml: 3, mr: 3 }}> | |||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||
| <Button | |||
| variant="contained" | |||
| color="primary" | |||
| onClick={() => sendDn_Overdue(true)} | |||
| > | |||
| Generate O/S DN List | |||
| </Button> | |||
| </ThemeProvider> | |||
| </Grid> : null | |||
| } | |||
| </> | |||
| } | |||
| </> | |||
| } | |||
| @@ -357,18 +389,20 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={4} > | |||
| <FormControlLabel | |||
| control={<Checkbox checked={formik.values.creditor} />} | |||
| label="is Credit Client" | |||
| name="creditor" | |||
| onChange={() => { | |||
| formik.setFieldValue("creditor", !formik.values.creditor); | |||
| }} | |||
| disabled={true} | |||
| //disabled={!editMode && !createMode} | |||
| /> | |||
| </Grid> | |||
| {!checkMarkAsCreditClient()? | |||
| <Grid item xs={12} lg={4} > | |||
| <FormControlLabel | |||
| control={<Checkbox checked={formik.values.creditor} />} | |||
| label="is Credit Client" | |||
| name="creditor" | |||
| onChange={() => { | |||
| formik.setFieldValue("creditor", !formik.values.creditor); | |||
| }} | |||
| disabled={true} | |||
| //disabled={!editMode && !createMode} | |||
| /> | |||
| </Grid>:null | |||
| } | |||
| <Grid item xs={12} lg={4} ></Grid> | |||
| @@ -396,18 +430,18 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| <Typography variant="pnspsFormParagraphBold">{FieldUtils.notNullFieldLabel("Expiry Date:")}</Typography> | |||
| </Grid> | |||
| <Grid item xs={12} md={6} lg={6}> | |||
| {(!editMode && !createMode)? | |||
| {(!editMode && !createMode) ? | |||
| <TextField | |||
| fullWidth | |||
| id="currentExDate" | |||
| // error={(fromDate===null)} | |||
| // type="date" | |||
| name="currentExDate" | |||
| value={fromDate!=null?DateUtils.dateStr(fromDate):DateUtils.dateStr(currentFromDate)} | |||
| value={fromDate != null ? DateUtils.dateStr(fromDate) : DateUtils.dateStr(currentFromDate)} | |||
| disabled={true} | |||
| />: | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <DemoItem components={['DatePicker']}> | |||
| /> : | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="brExpiryDate" | |||
| name="brExpiryDate" | |||
| @@ -426,9 +460,9 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| onChange={(newValue) => { | |||
| // console.log(newValue) | |||
| // setErrorMsg("") | |||
| if(DateUtils.dateValue(newValue)>DateUtils.dateValue(new Date())){ | |||
| if (DateUtils.dateValue(newValue) > DateUtils.dateValue(new Date())) { | |||
| setFromDate(newValue); | |||
| }else{ | |||
| } else { | |||
| // setErrorMsg("Please select a date after today.") | |||
| } | |||
| }} | |||
| @@ -438,19 +472,20 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| } | |||
| </Grid> | |||
| { | |||
| fromDate==null? | |||
| (!editMode && !createMode)? | |||
| <FormHelperText error id="helper-text-date"> | |||
| Please select a date after today. | |||
| </FormHelperText> | |||
| : | |||
| fromDate == null ? | |||
| (!editMode && !createMode) ? | |||
| // <FormHelperText error id="helper-text-date"> | |||
| // Please select a date after today. | |||
| // </FormHelperText> | |||
| null | |||
| : | |||
| <FormHelperText error id="helper-text-date"> | |||
| {intl.formatMessage({ id: 'pleaseFillInBusinessRegCertValidityDate' })} | |||
| </FormHelperText> | |||
| : | |||
| : | |||
| null | |||
| } | |||
| } | |||
| </Grid> | |||
| </Grid> | |||
| @@ -488,27 +523,6 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={4} > | |||
| {FieldUtils.getComboField({ | |||
| label: FieldUtils.notNullFieldLabel("Country:"), | |||
| valueName: "country", | |||
| disabled: (!editMode && !createMode), | |||
| dataList: ComboData.country, | |||
| getOptionLabel: (option) => option.type ? intl.formatMessage({ id: option.type }) : "", | |||
| form: formik | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={4} > | |||
| {FieldUtils.getComboField({ | |||
| label: FieldUtils.notNullFieldLabel("District:"), | |||
| valueName: "district", | |||
| disabled: (!editMode && !createMode), | |||
| dataList: ComboData.district, | |||
| getOptionLabel: (option) => option.type ? intl.formatMessage({ id: option.type }) : "", | |||
| form: formik | |||
| })} | |||
| </Grid> | |||
| { | |||
| currentUserData.creditor ? | |||
| <Grid item xs={12} lg={4} > | |||
| @@ -533,6 +547,28 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={12} > | |||
| {FieldUtils.getProfileComboField({ | |||
| label: "", | |||
| valueName: "district", | |||
| disabled: (!editMode && !createMode), | |||
| dataList: ComboData.district, | |||
| getOptionLabel: (option) => option.type ? intl.formatMessage({ id: option.type }) : "", | |||
| form: formik | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={12} > | |||
| {FieldUtils.getProfileComboField({ | |||
| label: "", | |||
| valueName: "country", | |||
| disabled: true, | |||
| dataList: ComboData.country, | |||
| getOptionLabel: (option) => option.type ? intl.formatMessage({ id: option.type }) : "", | |||
| form: formik | |||
| })} | |||
| </Grid> | |||
| <Grid item lg={12} ></Grid> | |||
| </Grid> | |||
| @@ -566,7 +602,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| {...register("orgShortName")} | |||
| id='orgShortName' | |||
| label="Organisation Short Name" | |||
| defaultValue={currentUserData.orgShortName!="N/A"?currentUserData.orgShortName:""} | |||
| defaultValue={currentUserData.orgShortName != "N/A" ? currentUserData.orgShortName : ""} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| @@ -605,6 +641,27 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| </DialogActions> | |||
| </Dialog> | |||
| </div> | |||
| <div> | |||
| <Dialog | |||
| open={afterSendPopUp} | |||
| onClose={() => setAfterSendPopUp(false)} | |||
| PaperProps={{ | |||
| sx: { | |||
| minWidth: '40vw', | |||
| maxWidth: { xs: '90vw', s: '90vw', m: '70vw', lg: '70vw' }, | |||
| maxHeight: { xs: '90vh', s: '70vh', m: '70vh', lg: '60vh' } | |||
| } | |||
| }} | |||
| > | |||
| <DialogTitle><Typography variant="h3">Info</Typography></DialogTitle> | |||
| <DialogContent style={{ display: 'flex', }}> | |||
| <Typography variant="h4" style={{ padding: '16px' }}>Overdue Public Notice count: {overduePublicNotice}</Typography> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setAfterSendPopUp(false)}><Typography variant="h5">OK</Typography></Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| </div> | |||
| </MainCard> | |||
| ); | |||
| }; | |||
| @@ -1,6 +1,6 @@ | |||
| // material-ui | |||
| import { | |||
| Grid, Button, | |||
| Grid, Button, | |||
| // Checkbox, FormControlLabel, | |||
| Typography, | |||
| Dialog, DialogTitle, DialogContent, DialogActions, | |||
| @@ -20,9 +20,9 @@ const LoadingComponent = Loadable(lazy(() => import('../../extra-pages/LoadingCo | |||
| import Loadable from 'components/Loadable'; | |||
| import { lazy } from 'react'; | |||
| import { notifySaveSuccess } from 'utils/CommonFunction'; | |||
| import {FormattedMessage, useIntl} from "react-intl"; | |||
| import {PNSPS_BUTTON_THEME} from "themes/buttonConst"; | |||
| import {ThemeProvider} from "@emotion/react"; | |||
| import { FormattedMessage, useIntl } from "react-intl"; | |||
| import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | |||
| import { ThemeProvider } from "@emotion/react"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| @@ -54,19 +54,19 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| enableReinitialize: true, | |||
| initialValues: currentUserData, | |||
| validationSchema: yup.object().shape({ | |||
| addressLine1: yup.string().max(40).required(displayErrorMsg(intl.formatMessage({id: 'validateAddressLine1'}))), | |||
| addressLine2: yup.string().max(40, displayErrorMsg(intl.formatMessage({id: 'noMoreThen40Words'}))), | |||
| addressLine3: yup.string().max(40, displayErrorMsg(intl.formatMessage({id: 'noMoreThen40Words'}))), | |||
| tel_countryCode: yup.string().min(3, displayErrorMsg(intl.formatMessage({id: 'requireDialingCode'}))), | |||
| phoneNumber: yup.string().min(8, displayErrorMsg(intl.formatMessage({id: 'requiredValidNumber'}))).required(displayErrorMsg(intl.formatMessage({id: 'requireContactNumber'}))), | |||
| addressLine1: yup.string().max(40).required(displayErrorMsg(intl.formatMessage({ id: 'validateAddressLine1' }))), | |||
| addressLine2: yup.string().max(40, displayErrorMsg(intl.formatMessage({ id: 'noMoreThen40Words' }))), | |||
| addressLine3: yup.string().max(40, displayErrorMsg(intl.formatMessage({ id: 'noMoreThen40Words' }))), | |||
| tel_countryCode: yup.string().min(3, displayErrorMsg(intl.formatMessage({ id: 'requireDialingCode' }))), | |||
| phoneNumber: yup.string().min(8, displayErrorMsg(intl.formatMessage({ id: 'requiredValidNumber' }))).required(displayErrorMsg(intl.formatMessage({ id: 'requireContactNumber' }))), | |||
| faxNumber: yup.string().min(8, displayErrorMsg(intl.formatMessage({ id: 'require8Number' }))).nullable(), | |||
| }), | |||
| onSubmit: values => { | |||
| if (values.country==null){ | |||
| setErrorMsg(intl.formatMessage({id: 'pleaseFillInCountry'})) | |||
| if (values.country == null) { | |||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInCountry' })) | |||
| } else { | |||
| if (values.country.type =="hongKong" && values.district == null){ | |||
| setErrorMsg(intl.formatMessage({id: 'pleaseFillInDistrict'})) | |||
| if (values.country.type == "hongKong" && values.district == null) { | |||
| setErrorMsg(intl.formatMessage({ id: 'pleaseFillInDistrict' })) | |||
| } else { | |||
| HttpUtils.post({ | |||
| url: UrlUtils.POST_PUB_ORG_SAVE_PATH, | |||
| @@ -100,9 +100,9 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| } | |||
| }); | |||
| useEffect(()=>{ | |||
| useEffect(() => { | |||
| setEditModeFun(editMode); | |||
| },[editMode]); | |||
| }, [editMode]); | |||
| useEffect(() => { | |||
| if (Object.keys(userData).length > 0) { | |||
| @@ -188,7 +188,7 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| onClick={onEditClick} | |||
| color="success" | |||
| > | |||
| < FormattedMessage id="edit" /> | |||
| < FormattedMessage id="edit" /> | |||
| </Button> | |||
| </ThemeProvider> | |||
| </Grid> | |||
| @@ -202,11 +202,11 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| <LoadingComponent /> | |||
| : | |||
| <Grid container spacing={1}> | |||
| <Grid item xs={12}> | |||
| {/* <Grid item xs={12}> | |||
| <Typography variant="h4" sx={{ mb: 2, mr: 3, borderBottom: "1px solid black" }}> | |||
| <FormattedMessage id="organizationDetails" /> | |||
| </Typography> | |||
| </Grid> | |||
| </Grid> */} | |||
| <Grid item xs={12}> | |||
| <FormHelperText error id="helper-text-address1-signup"> | |||
| <Typography variant="errorMessage1"> | |||
| @@ -216,27 +216,27 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| </Grid> | |||
| <Grid item xs={12} lg={4} > | |||
| {FieldUtils.getTextField({ | |||
| label: intl.formatMessage({id: 'brNo'}) + ":", | |||
| label: intl.formatMessage({ id: 'brNo' }) + ":", | |||
| valueName: "brNo", | |||
| disabled: true, | |||
| form: formik | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={4} > | |||
| {/* {FieldUtils.getTextField({ | |||
| label: intl.formatMessage({id: 'creditorAccount'}) + ":", | |||
| valueName: "creditor", | |||
| disabled: true, | |||
| form: formik | |||
| })} */} | |||
| {FieldUtils.getTextField({ | |||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: 'expiryDate' }) + ":"), | |||
| valueName: "brExpiryDate", | |||
| disabled: true, | |||
| form: formik | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={4} ></Grid> | |||
| <Grid item xs={12} lg={4} > | |||
| {FieldUtils.getTextField({ | |||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({id: 'nameEng'}) + ":"), | |||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: 'nameEng' }) + ":"), | |||
| valueName: "enCompanyName", | |||
| disabled: true, | |||
| form: formik | |||
| @@ -245,7 +245,7 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| <Grid item xs={12} lg={4} > | |||
| {FieldUtils.getTextField({ | |||
| label: intl.formatMessage({id: 'nameChi'}) + ":", | |||
| label: intl.formatMessage({ id: 'nameChi' }) + ":", | |||
| valueName: "chCompanyName", | |||
| disabled: true, | |||
| form: formik | |||
| @@ -253,17 +253,12 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| </Grid> | |||
| <Grid item xs={12} lg={4} > | |||
| {FieldUtils.getTextField({ | |||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({id: 'expiryDate'}) + ":"), | |||
| valueName: "brExpiryDate", | |||
| disabled: true, | |||
| form: formik | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={4} > | |||
| {FieldUtils.getTextField({ | |||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({id: 'contactPerson'}) + ":"), | |||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: 'contactPerson' }) + ":"), | |||
| valueName: "contactPerson", | |||
| disabled: (!editMode && !createMode), | |||
| form: formik | |||
| @@ -272,7 +267,7 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| <Grid item xs={12} lg={4} > | |||
| {FieldUtils.getPhoneField({ | |||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({id: 'userContactNumber'}) + ":"), | |||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: 'userContactNumber' }) + ":"), | |||
| valueName: { | |||
| code: "tel_countryCode", | |||
| num: "phoneNumber" | |||
| @@ -284,7 +279,7 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| <Grid item xs={12} lg={4} > | |||
| {FieldUtils.getPhoneField({ | |||
| label: intl.formatMessage({id: 'contactFaxNumber'}) + ":", | |||
| label: intl.formatMessage({ id: 'contactFaxNumber' }) + ":", | |||
| valueName: { | |||
| code: "fax_countryCode", | |||
| num: "faxNumber" | |||
| @@ -294,34 +289,35 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={4} > | |||
| {FieldUtils.getComboField({ | |||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({id: 'country'}) + ":"), | |||
| valueName: "country", | |||
| <Grid item xs={12} lg={12} > | |||
| {FieldUtils.getAddressField({ | |||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: 'formAddress' }) + ":"), | |||
| valueName: ["addressLine1", "addressLine2", "addressLine3"], | |||
| disabled: (!editMode && !createMode), | |||
| dataList: ComboData.country, | |||
| getOptionLabel: (option) => option.type? intl.formatMessage({ id: option.type }) : "", | |||
| form: formik | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={4} > | |||
| {FieldUtils.getComboField({ | |||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({id: 'district'}) + ":"), | |||
| <Grid item xs={12} lg={12} > | |||
| {FieldUtils.getProfileComboField({ | |||
| // label: FieldUtils.notNullFieldLabel(""), | |||
| label: "", | |||
| valueName: "district", | |||
| disabled: (!editMode && !createMode), | |||
| dataList: ComboData.district, | |||
| getOptionLabel: (option) => option.type? intl.formatMessage({ id: option.type }) : "", | |||
| getOptionLabel: (option) => option.type ? intl.formatMessage({ id: option.type }) : "", | |||
| form: formik | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={12} > | |||
| {FieldUtils.getAddressField({ | |||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({id: 'formAddress'}) + ":"), | |||
| valueName: ["addressLine1", "addressLine2", "addressLine3"], | |||
| disabled: (!editMode && !createMode), | |||
| {FieldUtils.getProfileComboField({ | |||
| // label: FieldUtils.notNullFieldLabel(""), | |||
| label: "", | |||
| valueName: "country", | |||
| disabled: true, | |||
| dataList: ComboData.country, | |||
| getOptionLabel: (option) => option.type ? intl.formatMessage({ id: option.type }) : "", | |||
| form: formik | |||
| })} | |||
| </Grid> | |||
| @@ -348,7 +344,7 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| <Typography variant="h4" style={{ padding: '16px' }}>Are you sure mark as Credit Client?</Typography> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setCreditorConfirmPopUp(false)}><Typography variant="h5">Cancel</Typography></Button> | |||
| <Button onClick={() => setCreditorConfirmPopUp(false)}><Typography variant="h5">Cancel</Typography></Button> | |||
| <Button onClick={() => markAsCreditor()}><Typography variant="h5">Confirm</Typography></Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| @@ -370,7 +366,7 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||
| <Typography variant="h4" style={{ padding: '16px' }}>Are you sure mark as Non-Credit Client?</Typography> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setNonCreditorConfirmPopUp(false)}><Typography variant="h5">Cancel</Typography></Button> | |||
| <Button onClick={() => setNonCreditorConfirmPopUp(false)}><Typography variant="h5">Cancel</Typography></Button> | |||
| <Button onClick={() => markAsNonCreditor()}><Typography variant="h5">Confirm</Typography></Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| @@ -22,6 +22,7 @@ import { | |||
| isORGLoggedIn, | |||
| isPrimaryLoggedIn | |||
| } from "utils/Utils"; | |||
| import usePageTitle from "components/usePageTitle"; | |||
| const BackgroundHead = { | |||
| backgroundImage: `url(${titleBackgroundImg})`, | |||
| @@ -42,6 +43,9 @@ import { | |||
| const OrganizationDetailPage = () => { | |||
| // Localized document title/meta for organisation details (GLD) | |||
| usePageTitle("organizationProfile"); | |||
| const params = useParams(); | |||
| const [formData, setFormData] = React.useState({}) | |||
| const [list, setList] = React.useState([]) | |||
| @@ -136,11 +140,11 @@ const OrganizationDetailPage = () => { | |||
| <div style={BackgroundHead}> | |||
| <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center"> | |||
| {isGLDLoggedIn()? | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| Maintain Organisation | |||
| </Typography> | |||
| : | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{display: { xs: 'none', sm: 'none', md: 'block' }}}> | |||
| <FormattedMessage id="organizationProfile" /> | |||
| </Typography> | |||
| } | |||
| @@ -233,7 +233,7 @@ const OrganizationCard_loadFromUser = ({ userData, userId }) => { | |||
| <Typography variant="pnspsFormParagraphBold">{FieldUtils.notNullFieldLabel("Expiry Date:")}</Typography> | |||
| </Grid> | |||
| <Grid item xs={12} md={6} lg={6}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="brExpiryDate" | |||
| @@ -310,19 +310,17 @@ const OrganizationCard_loadFromUser = ({ userData, userId }) => { | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={4}> | |||
| {FieldUtils.getComboField({ | |||
| label: FieldUtils.notNullFieldLabel("Country:"), | |||
| valueName: "country", | |||
| dataList: ComboData.country, | |||
| getOptionLabel: (option) => option.type ? intl.formatMessage({ id: option.type }) : "", | |||
| <Grid item xs={12}> | |||
| {FieldUtils.getAddressField({ | |||
| label: FieldUtils.notNullFieldLabel("Address:"), | |||
| valueName: ["addressLine1", "addressLine2", "addressLine3"], | |||
| form: formik | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12} lg={4}> | |||
| <Grid item xs={12} lg={12}> | |||
| {FieldUtils.getComboField({ | |||
| label: FieldUtils.notNullFieldLabel("District:"), | |||
| label: "", | |||
| valueName: "district", | |||
| dataList: ComboData.district, | |||
| getOptionLabel: (option) => option.type ? intl.formatMessage({ id: option.type }) : "", | |||
| @@ -330,13 +328,17 @@ const OrganizationCard_loadFromUser = ({ userData, userId }) => { | |||
| })} | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| {FieldUtils.getAddressField({ | |||
| label: FieldUtils.notNullFieldLabel("Address:"), | |||
| valueName: ["addressLine1", "addressLine2", "addressLine3"], | |||
| <Grid item xs={12} lg={12}> | |||
| {FieldUtils.getComboField({ | |||
| label: "", | |||
| valueName: "country", | |||
| disabled: true, | |||
| dataList: ComboData.country, | |||
| getOptionLabel: (option) => option.type ? intl.formatMessage({ id: option.type }) : "", | |||
| form: formik | |||
| })} | |||
| </Grid> | |||
| </Grid> | |||
| } | |||
| </form> | |||
| @@ -25,10 +25,14 @@ const BackgroundHead = { | |||
| backgroundColor: '#0C489E', | |||
| backgroundPosition: 'right' | |||
| } | |||
| import usePageTitle from "components/usePageTitle"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const OrganizationDetailPage_FromUser = () => { | |||
| // Localized document title/meta for organisation details (from user) | |||
| usePageTitle("organizationProfile"); | |||
| const params = useParams(); | |||
| const [formData, setFormData] = useState({}) | |||
| const [isLoading, setLoding] = useState(true); | |||
| @@ -8,7 +8,7 @@ import { | |||
| import MainCard from "components/MainCard"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { useState } from "react"; | |||
| import { useState,useEffect } from "react"; | |||
| import * as React from "react"; | |||
| import * as UrlUtils from "utils/ApiPathConst"; | |||
| @@ -19,11 +19,21 @@ import {ThemeProvider} from "@emotion/react"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const OrganizationSearchForm = ({ applySearch }) => { | |||
| const OrganizationSearchForm = ({ applySearch, onGridReady, searchCriteria }) => { | |||
| const [type, setType] = useState([]); | |||
| const [creditorSelected, setCreditorSelected] = React.useState(ComboData.CreditorStatus[0]); | |||
| const { reset, register, handleSubmit } = useForm() | |||
| const [onDownload, setOnDownload] = React.useState(false); | |||
| useEffect(() => { | |||
| if(searchCriteria.creditor!=undefined){ | |||
| setCreditorSelected(ComboData.CreditorStatus.find(item => item.type === searchCriteria.creditor.toString())) | |||
| }else{ | |||
| setCreditorSelected(ComboData.CreditorStatus[0]); | |||
| } | |||
| }, [searchCriteria]); | |||
| const onSubmit = (data) => { | |||
| let typeArray = []; | |||
| @@ -48,12 +58,23 @@ const OrganizationSearchForm = ({ applySearch }) => { | |||
| function resetForm() { | |||
| setType([]); | |||
| setCreditorSelected(ComboData.CreditorStatus[0]); | |||
| reset(); | |||
| reset({ | |||
| brNo: "", | |||
| enCompanyName: "", | |||
| chCompanyName: "", | |||
| }); | |||
| } | |||
| const doExport=()=>{ | |||
| setOnDownload(true) | |||
| HttpUtils.fileDownload({ | |||
| url: UrlUtils.GET_ORG_EXPORT | |||
| url: UrlUtils.GET_ORG_EXPORT, | |||
| onResponse:()=>{ | |||
| setOnDownload(false) | |||
| }, | |||
| onError:()=>{ | |||
| setOnDownload(false) | |||
| } | |||
| }); | |||
| } | |||
| @@ -80,6 +101,7 @@ const OrganizationSearchForm = ({ applySearch }) => { | |||
| {...register("brNo")} | |||
| id='brNo' | |||
| label="BR No." | |||
| defaultValue={searchCriteria.brNo} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| @@ -92,6 +114,7 @@ const OrganizationSearchForm = ({ applySearch }) => { | |||
| {...register("enCompanyName")} | |||
| id="enCompanyName" | |||
| label="Name (English)" | |||
| defaultValue={searchCriteria.enCompanyName} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| @@ -104,6 +127,7 @@ const OrganizationSearchForm = ({ applySearch }) => { | |||
| {...register("chCompanyName")} | |||
| id="chCompanyName" | |||
| label="Name (Chinese)" | |||
| defaultValue={searchCriteria.chCompanyName} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| @@ -125,6 +149,11 @@ const OrganizationSearchForm = ({ applySearch }) => { | |||
| } | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| getOptionLabel={(option) => option.label} | |||
| renderInput={(params) => ( | |||
| <TextField | |||
| @@ -148,6 +177,7 @@ const OrganizationSearchForm = ({ applySearch }) => { | |||
| <Button | |||
| variant="contained" | |||
| onClick={doExport} | |||
| disabled={onDownload} | |||
| > | |||
| Export | |||
| </Button> | |||
| @@ -167,6 +197,7 @@ const OrganizationSearchForm = ({ applySearch }) => { | |||
| <Button | |||
| variant="contained" | |||
| type="submit" | |||
| disabled={onGridReady} | |||
| > | |||
| Submit | |||
| </Button> | |||
| @@ -11,7 +11,7 @@ import { clickableLink} from 'utils/CommonFunction'; | |||
| import {GET_ORG_PATH} from "utils/ApiPathConst"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function OrganizationTable({ searchCriteria }) { | |||
| export default function OrganizationTable({ searchCriteria, applyGridOnReady, applySearch}) { | |||
| const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria); | |||
| const navigate = useNavigate() | |||
| @@ -111,12 +111,18 @@ export default function OrganizationTable({ searchCriteria }) { | |||
| <div style={{ height: "fit-content", width: '100%' }}> | |||
| <FiDataGrid | |||
| columns={columns} | |||
| customPageSize={5} | |||
| customPageSize={10} | |||
| onRowDoubleClick={handleRowDoubleClick} | |||
| doLoad={{ | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| // doLoad={{ | |||
| // url: GET_ORG_PATH, | |||
| // params: _searchCriteria, | |||
| // }} | |||
| doLoad={React.useMemo(() => ({ | |||
| url: GET_ORG_PATH, | |||
| params: _searchCriteria, | |||
| }} | |||
| }), [_searchCriteria])} | |||
| /> | |||
| </div> | |||
| ); | |||
| @@ -5,7 +5,8 @@ import { | |||
| import MainCard from "components/MainCard"; | |||
| import { useEffect, useState } from "react"; | |||
| import * as React from "react"; | |||
| import { getSearchCriteria } from "auth/utils"; | |||
| import usePageTitle from "components/usePageTitle"; | |||
| // import LoadingComponent from "../extra-pages/LoadingComponent"; | |||
| // import SearchForm from "./OrganizationSearchForm"; | |||
| @@ -29,16 +30,34 @@ const BackgroundHead = { | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const OrganizationSearchPage = () => { | |||
| // Localized document title/meta for organisation search | |||
| usePageTitle("organizationProfile"); | |||
| const [searchCriteria, setSearchCriteria] = useState({}); | |||
| const [onReady, setOnReady] = useState(false); | |||
| const [onGridReady, setGridOnReady] = React.useState(false); | |||
| useEffect(() => { | |||
| if (Object.keys(getSearchCriteria(window.location.pathname)).length>0){ | |||
| setSearchCriteria(getSearchCriteria(window.location.pathname)) | |||
| }else{ | |||
| localStorage.setItem('searchCriteria',"") | |||
| setSearchCriteria({}) | |||
| } | |||
| }, []); | |||
| useEffect(() => { | |||
| setOnReady(true); | |||
| }, [searchCriteria]); | |||
| function applySearch(input) { | |||
| setGridOnReady(true) | |||
| setSearchCriteria(input); | |||
| localStorage.setItem('searchCriteria', JSON.stringify({path:window.location.pathname,data:input})) | |||
| } | |||
| function applyGridOnReady(input) { | |||
| setGridOnReady(input); | |||
| } | |||
| return ( | |||
| @@ -59,7 +78,11 @@ const OrganizationSearchPage = () => { | |||
| </Grid> | |||
| {/*row 1*/} | |||
| <Grid item xs={12} md={12} lg={12} sx={{ mb: -1 }}> | |||
| <SearchForm applySearch={applySearch} /> | |||
| <SearchForm | |||
| applySearch={applySearch} | |||
| onGridReady={onGridReady} | |||
| searchCriteria={searchCriteria} | |||
| /> | |||
| </Grid> | |||
| {/*row 2*/} | |||
| <Grid item xs={12} md={12} lg={12}> | |||
| @@ -69,6 +92,8 @@ const OrganizationSearchPage = () => { | |||
| > | |||
| <EventTable | |||
| searchCriteria={searchCriteria} | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| /> | |||
| </MainCard> | |||
| </Grid> | |||
| @@ -43,7 +43,7 @@ export default function SearchPublicNoticeTable({ recordList }) { | |||
| flex: 1, | |||
| renderCell: (params) => { | |||
| let appNo = params.row.appNo; | |||
| console.log(params.row) | |||
| // console.log(params.row) | |||
| return <div style={{ margin: 4, textAlign:"left"}}>Gazette Supplement No. 6 <br/> | |||
| {isORGLoggedIn()&¶ms.row.careOf!=null&¶ms.row.careOf!=""?<>{params.row.careOf}<br /></>:null} | |||
| App No: {appNo}<br/> | |||
| @@ -9,15 +9,19 @@ import { | |||
| import * as React from "react"; | |||
| import * as FormatUtils from "utils/FormatUtils"; | |||
| import * as PaymentStatus from "utils/statusUtils/PaymentStatus"; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import Loadable from 'components/Loadable'; | |||
| const MainCard = Loadable(React.lazy(() => import('components/MainCard'))); | |||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | |||
| import DownloadIcon from '@mui/icons-material/Download'; | |||
| import {useIntl} from "react-intl"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const PaymentDetails = ({ formData,doPrint,onDownload }) => { | |||
| const intl = useIntl(); | |||
| const [data, setData] = React.useState({}); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| // const { locale } = intl; | |||
| React.useEffect(() => { | |||
| if (formData != null && formData != undefined && Object.keys(formData).length > 0) { | |||
| @@ -89,7 +93,7 @@ const PaymentDetails = ({ formData,doPrint,onDownload }) => { | |||
| </Grid> | |||
| <Grid item xs={6} md={5} sx={{ml:5, textAlign: "left" }}> | |||
| <FormLabel sx={{ color: "#000000" }}> | |||
| {data.transDateStr + " (DD/MM/YYYY)"} | |||
| {DateUtils.dateFormat(data.transDateStr, intl.formatMessage({id: "dateStrFormat"})) +" ("+intl.formatMessage({id: "dateStrFormat"})+")"} | |||
| </FormLabel> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -131,7 +135,7 @@ const PaymentDetails = ({ formData,doPrint,onDownload }) => { | |||
| </Grid> | |||
| <Grid item xs={6} md={5} sx={{ml:5, textAlign: "left" }}> | |||
| <FormLabel sx={{ color: "#000000" }}> | |||
| {"HK$ " + FormatUtils.currencyFormat(data.payload?.amount)} | |||
| {"$ " + FormatUtils.currencyFormat(data.payload?.amount)} | |||
| </FormLabel> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -161,14 +165,10 @@ const PaymentDetails = ({ formData,doPrint,onDownload }) => { | |||
| </FormLabel> | |||
| </Grid> | |||
| <Grid xs={6} md={5} sx={{ml:5,textAlign: "left" }}> | |||
| {onDownload? | |||
| <LoadingComponent disableText={true} alignItems="flex-start"/> | |||
| : | |||
| <Button className="printHidden" variant="contained" sx={{ mt:2 }} onClick={doPrint}> | |||
| <DownloadIcon/> | |||
| <Typography sx={{fontSize: "16px"}}>Download</Typography> | |||
| </Button> | |||
| } | |||
| <Button className="printHidden" variant="contained" disabled={onDownload} sx={{ mt:2 }} onClick={doPrint}> | |||
| <DownloadIcon/> | |||
| <Typography sx={{fontSize: "16px"}}>Download</Typography> | |||
| </Button> | |||
| </Grid> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -77,7 +77,7 @@ const Index = () => { | |||
| if (!responseData.data?.id) { | |||
| navigate("/paymentPage/search"); | |||
| } | |||
| responseData.data["transDateStr"] = DateUtils.dateFormat(responseData.data.transDateTime, "DD/MM/YYYY"); | |||
| responseData.data["transDateStr"] = responseData.data.transDateTime; | |||
| responseData.data["transTimeStr"] = DateUtils.dateFormat(responseData.data.transDateTime, "HH:mm:ss"); | |||
| setItemList(responseData.paymentItemList) | |||
| setRecord(responseData.data); | |||
| @@ -81,7 +81,7 @@ export default function SearchPublicNoticeTable({ recordList }) { | |||
| { | |||
| id: 'fee', | |||
| field: 'fee', | |||
| headerName: intl.formatMessage({id: 'currencyAmount'}) + ' ($)', | |||
| headerName: intl.formatMessage({id: 'currencyAmount'}), | |||
| width: 200, | |||
| valueGetter: (params) => { | |||
| return (params?.value) ? "$ " + FormatUtils.currencyFormat(params?.value) : ""; | |||
| @@ -9,6 +9,7 @@ import { | |||
| import * as React from "react"; | |||
| import * as FormatUtils from "utils/FormatUtils"; | |||
| import * as PaymentStatus from "utils/statusUtils/PaymentStatus"; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import Loadable from 'components/Loadable'; | |||
| const MainCard = Loadable(React.lazy(() => import('components/MainCard'))); | |||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | |||
| @@ -24,30 +25,56 @@ const PaymentDetails = ({ formData,doPrint,onDownload }) => { | |||
| React.useEffect(() => { | |||
| if (formData != null && formData != undefined && Object.keys(formData).length > 0) { | |||
| console.log(formData) | |||
| // console.log(formData) | |||
| setData(formData); | |||
| } | |||
| }, [formData]); | |||
| React.useEffect(() => { | |||
| if (data != null && data != undefined && Object.keys(data).length > 0) { | |||
| console.log(data) | |||
| // console.log(data) | |||
| setOnReady(data != {}); | |||
| } | |||
| }, [data]); | |||
| const getPaymentMethod=()=>{ | |||
| let paymentmethod = "" | |||
| // console.log(locale) | |||
| if (data?.payload!=null) { | |||
| paymentmethod = data.payload?.paymentdetail.paymentmethod; | |||
| if("01" == paymentmethod) return "PPS"; | |||
| if("02" == paymentmethod || "03" == paymentmethod) return "Credit Card"; | |||
| if("04" == paymentmethod) return "FPS"; | |||
| if (locale == "zh-HK"){ | |||
| if("01" == paymentmethod) return "繳費靈"; | |||
| if("02" == paymentmethod || "03" == paymentmethod) return "信用卡"; | |||
| if("04" == paymentmethod) return "轉數快"; | |||
| } | |||
| else if (locale == "zh-CN"){ | |||
| if("01" == paymentmethod) return "缴费灵"; | |||
| if("02" == paymentmethod || "03" == paymentmethod) return "信用卡"; | |||
| if("04" == paymentmethod) return "转数快"; | |||
| } | |||
| else { | |||
| if("01" == paymentmethod) return "PPS"; | |||
| if("02" == paymentmethod || "03" == paymentmethod) return "Credit Card"; | |||
| if("04" == paymentmethod) return "FPS"; | |||
| } | |||
| } else { | |||
| paymentmethod = data.payMethod; | |||
| if("01,PPSB,PPS" == paymentmethod) return "PPS"; | |||
| if("02,BCMP,CreditCard" == paymentmethod || "03,BCMP,CreditCard" == paymentmethod) return "Credit Card"; | |||
| if("04,BCFP,FPS" == paymentmethod) return "FPS"; | |||
| if (locale == "zh-HK"){ | |||
| if("01,PPSB,PPS" == paymentmethod) return "繳費靈"; | |||
| if("02,BCMP,CreditCard" == paymentmethod || "03,BCMP,CreditCard" == paymentmethod) return "信用卡"; | |||
| if("04,BCFP,FPS" == paymentmethod) return "轉數快"; | |||
| } | |||
| else if (locale == "zh-CN"){ | |||
| if("01,PPSB,PPS" == paymentmethod) return "缴费灵"; | |||
| if("02,BCMP,CreditCard" == paymentmethod || "03,BCMP,CreditCard" == paymentmethod) return "信用卡"; | |||
| if("04,BCFP,FPS" == paymentmethod) return "转数快"; | |||
| } | |||
| else { | |||
| if("01,PPSB,PPS" == paymentmethod) return "PPS"; | |||
| if("02,BCMP,CreditCard" == paymentmethod || "03,BCMP,CreditCard" == paymentmethod) return "Credit Card"; | |||
| if("04,BCFP,FPS" == paymentmethod) return "FPS"; | |||
| } | |||
| } | |||
| return paymentmethod; | |||
| } | |||
| @@ -109,7 +136,7 @@ const PaymentDetails = ({ formData,doPrint,onDownload }) => { | |||
| </Grid> | |||
| <Grid item xs={6} md={6} sx={{textAlign: "left" }}> | |||
| <FormLabel sx={{ fontSize: "16px", color: "#000000" }}> | |||
| {data.transDateStr + " (DD/MM/YYYY)"} | |||
| {DateUtils.dateFormat(data.transDateStr, intl.formatMessage({id: "dateStrFormat"})) +" ("+intl.formatMessage({id: "dateStrFormat"})+")"} | |||
| </FormLabel> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -151,7 +178,7 @@ const PaymentDetails = ({ formData,doPrint,onDownload }) => { | |||
| </Grid> | |||
| <Grid item xs={6} md={6} sx={{textAlign: "left" }}> | |||
| <FormLabel sx={{ fontSize: "16px", color: "#000000" }}> | |||
| {"HK$ " + FormatUtils.currencyFormat(data.payload?.amount?data.payload?.amount:data.payAmount)} | |||
| {"$ " + FormatUtils.currencyFormat(data.payload?.amount?data.payload?.amount:data.payAmount)} | |||
| </FormLabel> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -181,16 +208,12 @@ const PaymentDetails = ({ formData,doPrint,onDownload }) => { | |||
| </FormLabel> | |||
| </Grid> | |||
| <Grid item xs={6} md={5} sx={{textAlign: "left" }}> | |||
| {onDownload? | |||
| <LoadingComponent disableText={true} alignItems="flex-start"/> | |||
| : | |||
| <Button className="printHidden" variant="contained" sx={{ mt:2 }} onClick={doPrint}> | |||
| <DownloadIcon/> | |||
| <Typography sx={{fontSize: "16px"}}> | |||
| <FormattedMessage id="download"/> | |||
| </Typography> | |||
| </Button> | |||
| } | |||
| <Button className="printHidden" variant="contained" disabled={onDownload} sx={{ mt:2 }} onClick={doPrint}> | |||
| <DownloadIcon/> | |||
| <Typography sx={{fontSize: "16px"}}> | |||
| <FormattedMessage id="download"/> | |||
| </Typography> | |||
| </Button> | |||
| </Grid> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -20,6 +20,7 @@ const DataGrid = Loadable(React.lazy(() => import('./DataGrid'))); | |||
| import ForwardIcon from '@mui/icons-material/Forward'; | |||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | |||
| import {FormattedMessage,useIntl} from "react-intl"; | |||
| import usePageTitle from "components/usePageTitle"; | |||
| const BackgroundHead = { | |||
| backgroundImage: `url(${titleBackgroundImg})`, | |||
| width: '100%', | |||
| @@ -33,6 +34,8 @@ const BackgroundHead = { | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const Index = () => { | |||
| usePageTitle("payDetail"); | |||
| const params = useParams(); | |||
| const navigate = useNavigate() | |||
| const intl = useIntl(); | |||
| @@ -59,9 +62,9 @@ const Index = () => { | |||
| React.useEffect(() => { | |||
| if (Object.keys(transactionData).length > 0) { | |||
| console.log(transactionData) | |||
| console.log(itemList) | |||
| console.log(record) | |||
| // console.log(transactionData) | |||
| // console.log(itemList) | |||
| // console.log(record) | |||
| setOnReady(true); | |||
| } | |||
| }, [transactionData]); | |||
| @@ -102,21 +105,21 @@ const Index = () => { | |||
| "paymentId": params.id | |||
| }, | |||
| onSuccess: function(responseData2){ | |||
| responseData2.data["transDateStr"] = DateUtils.dateFormat(responseData2.data.transDateTime, "DD/MM/YYYY"); | |||
| responseData2.data["transDateStr"] = responseData2.data.transDateTime; | |||
| responseData2.data["transTimeStr"] = DateUtils.dateFormat(responseData2.data.transDateTime, "HH:mm:ss"); | |||
| setResponeData(responseData2.transactionData) | |||
| setItemList(responseData2.paymentItemList) | |||
| setRecord(responseData2.data); | |||
| }, | |||
| onError: function(){ | |||
| responseData.data["transDateStr"] = DateUtils.dateFormat(responseData.data.transDateTime, "DD/MM/YYYY"); | |||
| responseData.data["transDateStr"] = responseData.data.transDateTime; | |||
| responseData.data["transTimeStr"] = DateUtils.dateFormat(responseData.data.transDateTime, "HH:mm:ss"); | |||
| setResponeData(responseData) | |||
| } | |||
| }); | |||
| }else{ | |||
| responseData.data["transDateStr"] = DateUtils.dateFormat(responseData.data.transDateTime, "DD/MM/YYYY"); | |||
| responseData.data["transDateStr"] = responseData.data.transDateTime; | |||
| responseData.data["transTimeStr"] = DateUtils.dateFormat(responseData.data.transDateTime, "HH:mm:ss"); | |||
| setResponeData(responseData) | |||
| setItemList(responseData.paymentItemList) | |||
| @@ -144,7 +147,7 @@ const Index = () => { | |||
| <Grid className="printHidden" item xs={12} width="100%"> | |||
| <div style={BackgroundHead} width="100%"> | |||
| <Stack direction="row" height='70px'> | |||
| <Typography ml={15} color='#FFF' variant="h4" sx={{ pt: 2 }}> | |||
| <Typography component="h1" ml={15} color='#FFF' variant="h4" sx={{ pt: 2 }}> | |||
| <FormattedMessage id="payDetail"/> | |||
| </Typography> | |||
| </Stack> | |||
| @@ -104,7 +104,7 @@ const AckPage = () => { | |||
| onSuccess: function(responseData){ | |||
| localStorage.removeItem("webtoken"); | |||
| localStorage.removeItem("transactionid"); | |||
| responseData.data["transDateStr"] = DateUtils.dateFormat(responseData.data.transDateTime, "DD/MM/YYYY"); | |||
| responseData.data["transDateStr"] = responseData.data.transDateTime; | |||
| responseData.data["transTimeStr"] = DateUtils.dateFormat(responseData.data.transDateTime, "HH:mm:ss"); | |||
| setResponeDataData(responseData.transactionData) | |||
| setItemList(responseData.paymentItemList) | |||
| @@ -202,7 +202,8 @@ const AckPage = () => { | |||
| {/*row 1*/} | |||
| <Grid item xs={12} md={12} spacing={2} sx={{ textAlign: "center" }}> | |||
| <Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | |||
| 您的申請和付款已收到 | |||
| {/* 您的申請和付款已收到 */} | |||
| <FormattedMessage id="MSG.paymentMsg"/> | |||
| </Typography> | |||
| <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 }}> | |||
| @@ -253,21 +254,22 @@ const AckPage = () => { | |||
| <center> | |||
| <Grid item xs={12} md={8} > | |||
| <Typography variant="h5" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | |||
| 付款取消訊息: | |||
| {/* 付款取消訊息: */} | |||
| <FormattedMessage id="MSG.paymentCancelMsg1"/> | |||
| <br /><br /> | |||
| 您的付款已被取消。我們收到了您的付款請求,但由於某些原因,付款無法完成。請注意以下事項: | |||
| <FormattedMessage id="MSG.paymentCancelMsg2"/> | |||
| <br /><br /> | |||
| 如果您主動取消了支付,請確認並確保取消是您的意願。 | |||
| <FormattedMessage id="MSG.paymentCancelMsg3"/> | |||
| <br /> | |||
| 如果付款被取消是由於系統問題或其他原因,請您嘗試以下解決方法: | |||
| <FormattedMessage id="MSG.paymentCancelMsg4"/> | |||
| <br /><br /> | |||
| 檢查您的支付帳戶是否有任何異常或限制。 | |||
| <FormattedMessage id="MSG.paymentCancelMsg5"/> | |||
| <br /> | |||
| 確保您的付款資訊準確無誤。 | |||
| <FormattedMessage id="MSG.paymentCancelMsg6"/> | |||
| <br /> | |||
| 檢查您的網路連線是否正常。 | |||
| <FormattedMessage id="MSG.paymentCancelMsg7"/> | |||
| <br /><br /> | |||
| 如果您需要進一步的協助或有任何疑問,請隨時與我們聯繫,我們將盡快解決您的付款問題。謝謝! | |||
| <FormattedMessage id="MSG.paymentCancelMsg8"/> | |||
| </Typography> | |||
| </Grid> | |||
| </center> | |||
| @@ -296,29 +298,39 @@ const AckPage = () => { | |||
| <Grid container justifyContent="flex-start" alignItems="center" > | |||
| <center> | |||
| <Grid item xs={12} md={8} > | |||
| <Typography variant="h5" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | |||
| 付款失敗訊息: | |||
| <FormattedMessage id="MSG.paymentFailMsg1"/> | |||
| <br /><br /> | |||
| 親愛的用戶,很遺憾地告訴您,您的付款操作未成功。我們在處理您的付款時遇到了問題。請您仔細檢查以下事項: | |||
| <FormattedMessage id="MSG.paymentFailMsg2"/> | |||
| <br /><br /> | |||
| 您的支付帳戶餘額是否足夠。 | |||
| <br /> | |||
| 您提供的付款資訊是否準確無誤。 | |||
| <br /> | |||
| 請檢查您的網路連線是否正常。 | |||
| <ul> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg3"/> | |||
| </li> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg4"/> | |||
| </li> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg5"/> | |||
| </li> | |||
| </ul> | |||
| <br /><br /> | |||
| 如果您已確認以上問題無誤,但付款失敗,請您嘗試以下解決方法: | |||
| <FormattedMessage id="MSG.paymentFailMsg6"/> | |||
| <br /><br /> | |||
| 嘗試使用其他付款方式進行付款。 | |||
| <br /> | |||
| 檢查您的支付帳戶是否有異常或限制。 | |||
| <br /> | |||
| 聯絡我們的客服人員尋求協助。 | |||
| <ul> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg7"/> | |||
| </li> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg8"/> | |||
| </li> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg9"/> | |||
| </li> | |||
| </ul> | |||
| <br /><br /> | |||
| 如果您需要進一步的協助或有任何疑問,請隨時與我們聯繫。非常抱歉給您帶來不便,我們將盡快解決您的付款問題。謝謝! | |||
| <FormattedMessage id="MSG.paymentFailMsg10"/> | |||
| </Typography> | |||
| </Grid> | |||
| </center> | |||
| </Grid> | |||
| @@ -10,6 +10,9 @@ import * as HttpUtils from "utils/HttpUtils"; | |||
| import * as UrlUtils from "utils/ApiPathConst"; | |||
| import { useNavigate } from "react-router-dom"; | |||
| import FpsIcon from "assets/images/icons/fps.svg"; | |||
| import expiredQrcodeEN from "assets/images/icons/expiredQrcodeEN.png"; | |||
| import expiredQrcodeZH from "assets/images/icons/expiredQrcodeZH.png"; | |||
| import expiredQrcodeCN from "assets/images/icons/expiredQrcodeCN.png"; | |||
| import { useLocation } from 'react-router-dom'; | |||
| // import {paymentPath} from "auth/utils"; | |||
| import {currencyFormat} from "utils/FormatUtils"; | |||
| @@ -19,7 +22,7 @@ import Loadable from 'components/Loadable'; | |||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | |||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | |||
| import {FormattedMessage} from "react-intl"; | |||
| import {FormattedMessage, useIntl} from "react-intl"; | |||
| const BackgroundHead = { | |||
| backgroundImage: `url(${titleBackgroundImg})`, | |||
| width: '100%', | |||
| @@ -30,15 +33,21 @@ const BackgroundHead = { | |||
| backgroundPosition: 'right' | |||
| } | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const Index = () => { | |||
| const navigate = useNavigate() | |||
| const location = useLocation(); | |||
| const intl = useIntl(); | |||
| const { locale } = intl; | |||
| const [locationData, setLocationData] = React.useState({}); | |||
| const [paymentData, setPaymentData] = React.useState({}); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| const [qrCodeTimeout, setqrCodeTimeout] = React.useState(false); | |||
| const [paymentStatusCode, setPaymentStatusCode] = React.useState(""); | |||
| const [expiredQrcode, setExpiredQrcode] = React.useState(expiredQrcodeEN); | |||
| const [responeData, setResponeDataData] = React.useState({}); | |||
| const [fpsTransctionData, setFpsTransctionData] = React.useState({}); | |||
| @@ -49,6 +58,7 @@ const Index = () => { | |||
| const [fpsqrcodeurlPrd, setFpsqrcodeurlPrd] = React.useState(""); | |||
| const [fpsqrcodeurlFps, setFpsqrcodeurlFps] = React.useState(""); | |||
| const [browserType, setBrowserType] = React.useState(""); | |||
| const [sysEnv, setSysEnv] = React.useState(""); | |||
| const mobileBrowser = "Mobile"; | |||
| const desktopBrowser = "Desktop"; | |||
| @@ -69,12 +79,14 @@ const Index = () => { | |||
| if(Object.keys(location.state).length > 0){ | |||
| // console.log (location.state) | |||
| setLocationData(location.state) | |||
| setBrowserType(desktopBrowser) | |||
| if (/Android|webOS|iPhone|iPad|iPod|Opera Mini/i.test(navigator.userAgent)) { | |||
| console.log('Mobile web browser'); | |||
| // console.log('Mobile web browser'); | |||
| setBrowserType(mobileBrowser) | |||
| // setFpsqrcodeurl(openPASGUrl) | |||
| } else { | |||
| console.log('Desktop web browser'); | |||
| // console.log('Desktop web browser'); | |||
| setBrowserType(desktopBrowser) | |||
| } | |||
| } | |||
| @@ -88,6 +100,17 @@ const Index = () => { | |||
| } | |||
| }, [locationData]); | |||
| React.useEffect(() => { | |||
| // console.log (locationData) | |||
| if (locale === 'zh-HK'){ | |||
| setExpiredQrcode(expiredQrcodeZH) | |||
| } else if (locale === 'en'){ | |||
| setExpiredQrcode(expiredQrcodeEN) | |||
| } else { | |||
| setExpiredQrcode(expiredQrcodeCN) | |||
| } | |||
| }, [locale]); | |||
| React.useEffect(() => { | |||
| // console.log (paymentData) | |||
| if (Object.keys(paymentData).length > 0){ | |||
| @@ -159,6 +182,7 @@ const Index = () => { | |||
| */ | |||
| setResponeDataData(responseData) | |||
| const timeoutdatetime = responseData.fpsmerchanttimeoutdatetime | |||
| setSysEnv(responseData.sysEnv) | |||
| const searchString = "[UTC]"; | |||
| let convertedDateString = ""; | |||
| if ( timeoutdatetime.toString().includes(searchString) ){ | |||
| @@ -175,12 +199,18 @@ const Index = () => { | |||
| console.log(fpsqrcodeurl) | |||
| console.log(fpsqrcodeurlwithFps) | |||
| const openPASGUrl = pasgPath + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurl) + '&callback=' | |||
| + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?TRANSACTION_ID='+transactionid+"&WEB_TOKEN="+webtoken+"&PAYMENT_ID="+localStorage.getItem("paymentId")); | |||
| const openPASGUrlPrd = pasgPathPrd + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurl) + '&callback=' | |||
| + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?TRANSACTION_ID='+transactionid+"&WEB_TOKEN="+webtoken+"&PAYMENT_ID="+localStorage.getItem("paymentId")); | |||
| const openPASGUrl = pasgPath + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback=' | |||
| + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?&PAYMENT_ID='+localStorage.getItem("paymentId")); | |||
| const openPASGUrlPrd = pasgPathPrd + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback=' | |||
| + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?&PAYMENT_ID='+localStorage.getItem("paymentId")); | |||
| const openPASGUrlPrdFps = pasgPath + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback=' | |||
| + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?TRANSACTION_ID='+transactionid+"&WEB_TOKEN="+webtoken+"&PAYMENT_ID="+localStorage.getItem("paymentId")); | |||
| + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?&PAYMENT_ID='+localStorage.getItem("paymentId")); | |||
| // const openPASGUrl = pasgPath + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback=' | |||
| // + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?TRANSACTION_ID='+transactionid+"&WEB_TOKEN="+webtoken+"&PAYMENT_ID="+localStorage.getItem("paymentId")); | |||
| // const openPASGUrlPrd = pasgPathPrd + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback=' | |||
| // + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?TRANSACTION_ID='+transactionid+"&WEB_TOKEN="+webtoken+"&PAYMENT_ID="+localStorage.getItem("paymentId")); | |||
| // const openPASGUrlPrdFps = pasgPath + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback=' | |||
| // + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?TRANSACTION_ID='+transactionid+"&WEB_TOKEN="+webtoken+"&PAYMENT_ID="+localStorage.getItem("paymentId")); | |||
| setFpsqrcodeurl(openPASGUrl) | |||
| setFpsqrcodeurlPrd(openPASGUrlPrd) | |||
| setFpsqrcodeurlFps(openPASGUrlPrdFps) | |||
| @@ -188,7 +218,7 @@ const Index = () => { | |||
| }); | |||
| //testing | |||
| // const timeoutdatetime = "2024-05-06T11:10:30Z[UTC]" | |||
| // const timeoutdatetime = "2024-11-18T07:04:35Z[UTC]" | |||
| // const convertedDateString = timeoutdatetime.replace("[UTC]", ""); | |||
| // setFpsmerchanttimeoutdatetime(convertedDateString) | |||
| // setPaymentId("C202310268000681") | |||
| @@ -196,7 +226,7 @@ const Index = () => { | |||
| // { | |||
| // "paymentid": "C202310268000681", | |||
| // "paymentstatus": "INPR", | |||
| // "fpsmerchanttimeoutdatetime": "2024-05-06T11:10:30Z[UTC]", | |||
| // "fpsmerchanttimeoutdatetime": "2024-11-18T07:04:35Z[UTC]", | |||
| // "fpsqrcodeimgbase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAuyklEQVR4Xu3dfcy/d1nff2IAAAAASUVORK5CYII=", | |||
| // "fpsqrcodeurl": "http://127.0.0.1:8080/api/payment/wallet/fps/enquiryfpspayload/vm.JKDDlTOavR3ASviSwUnS1Lw4-" | |||
| // } | |||
| @@ -231,6 +261,7 @@ const Index = () => { | |||
| }, | |||
| onSuccess: function(responseData){ | |||
| const paymentstatuscode = responseData.paymentdetail.result.paymentstatuscode; | |||
| setPaymentStatusCode(paymentstatuscode) | |||
| if (paymentstatuscode != "" && paymentstatuscode != "INPR" ){ | |||
| if (paymentstatuscode === 'APPR') { | |||
| // const timestamp = Date.now(); | |||
| @@ -249,7 +280,8 @@ const Index = () => { | |||
| } | |||
| }, | |||
| onError: function(){ | |||
| cancelPayment() | |||
| alert("ERROR") | |||
| // cancelPayment() | |||
| // clearInterval(currentTimer.current); | |||
| } | |||
| }); | |||
| @@ -260,35 +292,43 @@ const Index = () => { | |||
| const timeOutDate = new Date(fpsmerchanttimeoutdatetime); | |||
| const currentTime = new Date; | |||
| const timedowncount = Math.round((timeOutDate.getTime() - currentTime.getTime()) / 1000); | |||
| setTimeDownCount(timedowncount); | |||
| // console.log(time) | |||
| // console.log(timeOutDate) | |||
| // console.log(currentTime) | |||
| // console.log(timeOutDate.getTime()-currentTime.getTime()) | |||
| getPaymentStatus(); | |||
| if (timeOutDate.getTime()<currentTime.getTime()){ | |||
| // console.log("stop"); | |||
| clearInterval(currentTimer.current); | |||
| cancelPayment() | |||
| if (browserType === desktopBrowser){ | |||
| getPaymentStatus(); | |||
| if (timeOutDate.getTime()<currentTime.getTime()){ | |||
| // console.log("stop"); | |||
| clearInterval(currentTimer.current); | |||
| setqrCodeTimeout(true) | |||
| setTimeDownCount(0); | |||
| // cancelPayment() | |||
| }else{ | |||
| setTimeDownCount(timedowncount); | |||
| } | |||
| } | |||
| },[time]) | |||
| const cancelPayment = ()=>{ | |||
| if (Object.keys(paymentData).length>0){ | |||
| HttpUtils.post({ | |||
| url: UrlUtils.CANCEL_PAYMENT_URL, | |||
| params:{ | |||
| "transactionid": paymentData.transactionid, | |||
| "webtoken": paymentData.webtoken, | |||
| "paymentid": fpsTransctionData.paymentid | |||
| }, | |||
| onSuccess: function(){ | |||
| // navigate('/paymentPage/fps/ackpage'); | |||
| let page = '/paymentPage/fps/ackpage'; | |||
| let stateParams = { state: { transactionid: paymentData.transactionid} } | |||
| navigate(page, stateParams); | |||
| } | |||
| }); | |||
| getPaymentStatus() | |||
| if (paymentStatusCode === "INPR"){ | |||
| HttpUtils.post({ | |||
| url: UrlUtils.CANCEL_PAYMENT_URL, | |||
| params:{ | |||
| "transactionid": paymentData.transactionid, | |||
| "webtoken": paymentData.webtoken, | |||
| "paymentid": fpsTransctionData.paymentid | |||
| }, | |||
| onSuccess: function(){ | |||
| navigate('/paymentPage/fps/ackpage'); | |||
| let page = '/paymentPage/fps/ackpage'; | |||
| let stateParams = { state: { transactionid: paymentData.transactionid} } | |||
| navigate(page, stateParams); | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| } | |||
| @@ -330,6 +370,8 @@ const Index = () => { | |||
| <Grid item xs={12} md={12} > | |||
| <Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | |||
| <FormattedMessage id="payAlert"/> | |||
| <br /><br /> | |||
| <img src={FpsIcon} width="80" height="80" alt="FPS"></img> | |||
| <br /> | |||
| <FormattedMessage id="payTotalDeatail"/> | |||
| @@ -338,50 +380,69 @@ const Index = () => { | |||
| </Typography> | |||
| {browserType==mobileBrowser? | |||
| <Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | |||
| <Button | |||
| component="span" | |||
| variant="contained" | |||
| size="large" | |||
| color="primary" | |||
| onClick={()=>{ | |||
| mobliePayment(); | |||
| }} | |||
| sx={{ m: 4 }} | |||
| >請選擇支付程式付款-Testing</Button> | |||
| <Button | |||
| component="span" | |||
| variant="contained" | |||
| size="large" | |||
| color="primary" | |||
| onClick={()=>{ | |||
| mobliePaymentPrd(); | |||
| }} | |||
| sx={{ m: 4 }} | |||
| >請選擇支付程式付款-PRD</Button> | |||
| <Button | |||
| component="span" | |||
| variant="contained" | |||
| size="large" | |||
| color="primary" | |||
| onClick={()=>{ | |||
| mobliePaymentFps(); | |||
| }} | |||
| sx={{ m: 4 }} | |||
| >請選擇支付程式付款-fps prefix</Button> | |||
| { | |||
| sysEnv=="prod"? | |||
| <Button | |||
| component="span" | |||
| variant="contained" | |||
| size="large" | |||
| color="primary" | |||
| onClick={()=>{ | |||
| mobliePaymentPrd(); | |||
| }} | |||
| sx={{ m: 4 }} | |||
| >請選擇支付程式付款</Button> | |||
| : | |||
| <> | |||
| <Button | |||
| component="span" | |||
| variant="contained" | |||
| size="large" | |||
| color="primary" | |||
| onClick={()=>{ | |||
| mobliePayment(); | |||
| }} | |||
| sx={{ m: 4 }} | |||
| >請選擇支付程式付款-Testing</Button> | |||
| <Button | |||
| component="span" | |||
| variant="contained" | |||
| size="large" | |||
| color="primary" | |||
| onClick={()=>{ | |||
| mobliePaymentFps(); | |||
| }} | |||
| sx={{ m: 4 }} | |||
| >請選擇支付程式付款-fps prefix</Button> | |||
| </> | |||
| } | |||
| </Typography> | |||
| : | |||
| <Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | |||
| 請掃描以下二維碼 | |||
| <FormattedMessage id="fpsQrcodeTitle1"/> | |||
| <br /> | |||
| <img src={fpsTransctionData.fpsqrcodeimgbase64} alt="QR Code"/> | |||
| { | |||
| !qrCodeTimeout? | |||
| <img src={fpsTransctionData.fpsqrcodeimgbase64} alt="QR Code"/> | |||
| :<img src={expiredQrcode} alt="Expired QR Code"/> | |||
| } | |||
| <br /> | |||
| {"["+paymentId+"]"} | |||
| <br/> | |||
| 二維碼有效期限3分鐘 | |||
| <br /> | |||
| 請在規定時間內完成付款流程 | |||
| <br /> | |||
| {"剩餘時間: "+timeDownCount+ "秒"} | |||
| { | |||
| timeDownCount<=0? | |||
| <FormattedMessage id="fpsQrcodeExpired"/>: | |||
| <> | |||
| <FormattedMessage id="fpsQrcodeTitle2"/> | |||
| <br /> | |||
| <FormattedMessage id="fpsQrcodeTitle3"/> | |||
| <br /> | |||
| <FormattedMessage id="fpsQrcodeTitle4"/> | |||
| {timeDownCount} | |||
| <FormattedMessage id="fpsQrcodeTitle5"/> | |||
| </> | |||
| } | |||
| </Typography> | |||
| } | |||
| <Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | |||
| @@ -312,7 +312,7 @@ const Index = () => { | |||
| <br/> | |||
| 二維碼有效期限3分鐘 | |||
| <br /> | |||
| 請在規定時間內完成付款流程 | |||
| 請在限規定時間內完成付款流程 | |||
| <br /> | |||
| {"剩餘時間:"+timeDownCount} | |||
| </Typography> | |||
| @@ -76,22 +76,22 @@ const Fpscallback = () => { | |||
| const loadForm = () => { | |||
| const params = new URLSearchParams(window.location.search); | |||
| let transactionid = params.get("TRANSACTION_ID") | |||
| let webtoken = params.get("WEB_TOKEN") | |||
| // let transactionid = params.get("TRANSACTION_ID") | |||
| // let webtoken = params.get("WEB_TOKEN") | |||
| let paymentId = params.get("PAYMENT_ID") | |||
| paymentId = paymentId.split('?is_successful')[0]; | |||
| console.log(transactionid) | |||
| console.log(webtoken) | |||
| // console.log(transactionid) | |||
| // console.log(webtoken) | |||
| console.log(paymentId) | |||
| HttpUtils.post({ | |||
| url: UrlUtils.PAYMENT_CALLBACK_STATUS_API, | |||
| params:{ | |||
| "apprefid": transactionid, | |||
| "webtoken": webtoken, | |||
| // "apprefid": transactionid, | |||
| // "webtoken": webtoken, | |||
| "paymentId": paymentId, | |||
| "transactionid":Number(transactionid) | |||
| // "transactionid":Number(transactionid) | |||
| }, | |||
| onSuccess: function(responseData){ | |||
| setResponeDataData(responseData) | |||
| @@ -99,7 +99,7 @@ const Fpscallback = () => { | |||
| localStorage.removeItem("webtoken"); | |||
| localStorage.removeItem("transactionid"); | |||
| } | |||
| responseData.data["transDateStr"] = DateUtils.dateFormat(responseData.data.transDateTime, "DD/MM/YYYY"); | |||
| responseData.data["transDateStr"] = responseData.data.transDateTime; | |||
| responseData.data["transTimeStr"] = DateUtils.dateFormat(responseData.data.transDateTime, "HH:mm:ss"); | |||
| setResponeDataData(responseData.transactionData) | |||
| setItemList(responseData.paymentItemList) | |||
| @@ -191,7 +191,7 @@ const Fpscallback = () => { | |||
| {/*row 1*/} | |||
| <Grid item xs={12} md={12} spacing={2} sx={{ textAlign: "center" }}> | |||
| <Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | |||
| 您的申請和付款已收到 | |||
| <FormattedMessage id="MSG.paymentMsg"/> | |||
| </Typography> | |||
| <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 }}> | |||
| @@ -242,21 +242,21 @@ const Fpscallback = () => { | |||
| <center> | |||
| <Grid item xs={12} md={8} > | |||
| <Typography variant="h5" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | |||
| 付款取消訊息: | |||
| <br /><br /> | |||
| 您的付款已被取消。我們收到了您的付款請求,但由於某些原因,付款無法完成。請注意以下事項: | |||
| <br /><br /> | |||
| 如果您主動取消了支付,請確認並確保取消是您的意願。 | |||
| <br /> | |||
| 如果付款被取消是由於系統問題或其他原因,請您嘗試以下解決方法: | |||
| <br /><br /> | |||
| 檢查您的支付帳戶是否有任何異常或限制。 | |||
| <br /> | |||
| 確保您的付款資訊準確無誤。 | |||
| <br /> | |||
| 檢查您的網路連線是否正常。 | |||
| <br /><br /> | |||
| 如果您需要進一步的協助或有任何疑問,請隨時與我們聯繫,我們將盡快解決您的付款問題。謝謝! | |||
| <FormattedMessage id="MSG.paymentCancelMsg1"/> | |||
| <br /><br /> | |||
| <FormattedMessage id="MSG.paymentCancelMsg2"/> | |||
| <br /><br /> | |||
| <FormattedMessage id="MSG.paymentCancelMsg3"/> | |||
| <br /> | |||
| <FormattedMessage id="MSG.paymentCancelMsg4"/> | |||
| <br /><br /> | |||
| <FormattedMessage id="MSG.paymentCancelMsg5"/> | |||
| <br /> | |||
| <FormattedMessage id="MSG.paymentCancelMsg6"/> | |||
| <br /> | |||
| <FormattedMessage id="MSG.paymentCancelMsg7"/> | |||
| <br /><br /> | |||
| <FormattedMessage id="MSG.paymentCancelMsg8"/> | |||
| </Typography> | |||
| </Grid> | |||
| </center> | |||
| @@ -285,29 +285,39 @@ const Fpscallback = () => { | |||
| <Grid container justifyContent="flex-start" alignItems="center" > | |||
| <center> | |||
| <Grid item xs={12} md={8} > | |||
| <Typography variant="h5" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | |||
| 付款失敗訊息: | |||
| <FormattedMessage id="MSG.paymentFailMsg1"/> | |||
| <br /><br /> | |||
| 親愛的用戶,很遺憾地告訴您,您的付款操作未成功。我們在處理您的付款時遇到了問題。請您仔細檢查以下事項: | |||
| <FormattedMessage id="MSG.paymentFailMsg2"/> | |||
| <br /><br /> | |||
| 您的支付帳戶餘額是否足夠。 | |||
| <br /> | |||
| 您提供的付款資訊是否準確無誤。 | |||
| <br /> | |||
| 請檢查您的網路連線是否正常。 | |||
| <ul> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg3"/> | |||
| </li> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg4"/> | |||
| </li> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg5"/> | |||
| </li> | |||
| </ul> | |||
| <br /><br /> | |||
| 如果您已確認以上問題無誤,但付款失敗,請您嘗試以下解決方法: | |||
| <FormattedMessage id="MSG.paymentFailMsg6"/> | |||
| <br /><br /> | |||
| 嘗試使用其他付款方式進行付款。 | |||
| <br /> | |||
| 檢查您的支付帳戶是否有異常或限制。 | |||
| <br /> | |||
| 聯絡我們的客服人員尋求協助。 | |||
| <ul> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg7"/> | |||
| </li> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg8"/> | |||
| </li> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg9"/> | |||
| </li> | |||
| </ul> | |||
| <br /><br /> | |||
| 如果您需要進一步的協助或有任何疑問,請隨時與我們聯繫。非常抱歉給您帶來不便,我們將盡快解決您的付款問題。謝謝! | |||
| <FormattedMessage id="MSG.paymentFailMsg10"/> | |||
| </Typography> | |||
| </Grid> | |||
| </center> | |||
| </Grid> | |||
| @@ -64,7 +64,7 @@ const MultiPaymentWindow = (props) => { | |||
| // console.log(props.transactionData) | |||
| if(Object.keys(props.transactionData).length > 0){ | |||
| setLoadtTransactionData(props.transactionData) | |||
| console.log(props.browserType) | |||
| // console.log(props.browserType) | |||
| } | |||
| }, [props.transactionData]); | |||
| @@ -292,7 +292,7 @@ const MultiPaymentWindow = (props) => { | |||
| <DialogContent> | |||
| <DialogContentText> | |||
| <FormLabel sx={{ fontSize: "20px", color: "#000000", textAlign: "left", ml:1}}> | |||
| <FormattedMessage id="paymentProcessLimited"/>。 | |||
| <FormattedMessage id="paymentProcessLimited"/> | |||
| </FormLabel> | |||
| <Grid item xs={12} md={12} sx={{ pt: 2 }} style={{ height: '100%' }} width="100%"> | |||
| <Box xs={12} md={12} sx={{ p: 4, border: '3px solid #eee', borderRadius: '10px' }} > | |||
| @@ -304,7 +304,7 @@ const MultiPaymentWindow = (props) => { | |||
| </Typography> | |||
| {/* <Typography variant="h5" sx={{ textAlign: "left" }}> | |||
| 支付金額: HK$ {FormatUtils.currencyFormat(props.totalAmount)} | |||
| 付款金額: HK$ {FormatUtils.currencyFormat(props.totalAmount)} | |||
| </Typography> */} | |||
| {!props.onReady ? | |||
| <LoadingComponent /> | |||
| @@ -373,7 +373,7 @@ const MultiPaymentWindow = (props) => { | |||
| </Grid>: | |||
| <Grid container direction="row" justifyContent="center" alignItems="center"> | |||
| <FormLabel sx={{ fontSize: "20px", color: "#000000", textAlign: "center"}}> | |||
| <FormattedMessage id="paymentMethodNotAvailable"/>。 | |||
| <FormattedMessage id="paymentMethodNotAvailable"/> | |||
| </FormLabel> | |||
| </Grid> | |||
| } | |||
| @@ -381,12 +381,12 @@ const MultiPaymentWindow = (props) => { | |||
| <Grid item xs={12} md={12}> | |||
| <Grid container > | |||
| <Grid item> | |||
| <Typography variant="pnspsFormParagraphBold" sx={{ color: "#000000", textAlign: "left" }}> | |||
| <FormattedMessage id="payTotal"/>(HK$): | |||
| <Typography variant="h5" sx={{ color: "#000000", textAlign: "left" }}> | |||
| <FormattedMessage id="payTotal"/> ($): | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item> | |||
| <Typography variant="pnspsFormParagraphBold" sx={{color: "#000000", textAlign: "left" }}> | |||
| <Typography variant="h5" sx={{color: "#000000", textAlign: "left" }}> | |||
| {" HK$ " + FormatUtils.currencyFormat(props.totalAmount)} | |||
| </Typography> | |||
| </Grid> | |||
| @@ -123,7 +123,7 @@ const Index = () => { | |||
| localStorage.removeItem("webtoken"); | |||
| localStorage.removeItem("transactionid"); | |||
| } | |||
| responseData.data["transDateStr"] = DateUtils.dateFormat(responseData.data.transDateTime, "DD/MM/YYYY"); | |||
| responseData.data["transDateStr"] = responseData.data.transDateTime; | |||
| responseData.data["transTimeStr"] = DateUtils.dateFormat(responseData.data.transDateTime, "HH:mm:ss"); | |||
| setResponeDataData(responseData.transactionData) | |||
| setItemList(responseData.paymentItemList) | |||
| @@ -294,29 +294,39 @@ const Index = () => { | |||
| <Grid container justifyContent="flex-start" alignItems="center" > | |||
| <center> | |||
| <Grid item xs={12} md={8} > | |||
| <Typography variant="h5" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | |||
| <FormattedMessage id="MSG.paymentFailMsg1"/> | |||
| <br /><br /> | |||
| <FormattedMessage id="MSG.paymentFailMsg2"/> | |||
| <br /><br /> | |||
| <FormattedMessage id="MSG.paymentFailMsg3"/> | |||
| <br /> | |||
| <FormattedMessage id="MSG.paymentFailMsg4"/> | |||
| <br /> | |||
| <FormattedMessage id="MSG.paymentFailMsg5"/> | |||
| <ul> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg3"/> | |||
| </li> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg4"/> | |||
| </li> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg5"/> | |||
| </li> | |||
| </ul> | |||
| <br /><br /> | |||
| <FormattedMessage id="MSG.paymentFailMsg6"/> | |||
| <br /><br /> | |||
| <FormattedMessage id="MSG.paymentFailMsg7"/> | |||
| <br /> | |||
| <FormattedMessage id="MSG.paymentFailMsg8"/> | |||
| <br /> | |||
| <FormattedMessage id="MSG.paymentFailMsg9"/> | |||
| <ul> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg7"/> | |||
| </li> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg8"/> | |||
| </li> | |||
| <li> | |||
| <FormattedMessage id="MSG.paymentFailMsg9"/> | |||
| </li> | |||
| </ul> | |||
| <br /><br /> | |||
| <FormattedMessage id="MSG.paymentFailMsg10"/> | |||
| </Typography> | |||
| </Typography> | |||
| </Grid> | |||
| </center> | |||
| </Grid> | |||
| @@ -1,17 +1,35 @@ | |||
| // material-ui | |||
| import * as React from 'react'; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import {PAYMENT_LIST} from "utils/ApiPathConst"; | |||
| import {PAYMENT_LIST, PAYMENT_BIB} from "utils/ApiPathConst"; | |||
| import * as HttpUtils from "utils/HttpUtils"; | |||
| import * as FormatUtils from "utils/FormatUtils" | |||
| import * as PaymentStatus from "utils/statusUtils/PaymentStatus" | |||
| import { useNavigate } from "react-router-dom"; | |||
| import { FiDataGrid } from "components/FiDataGrid"; | |||
| import { clickableLink } from 'utils/CommonFunction'; | |||
| import { getPaymentMethodByCode} from "auth/utils"; | |||
| import { | |||
| Checkbox, | |||
| Dialog, DialogTitle, DialogContent, DialogActions, | |||
| Button,Typography | |||
| // MenuItem | |||
| } from '@mui/material'; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function SearchPaymentTable({ searchCriteria }) { | |||
| export default function SearchPaymentTable({ searchCriteria, applyGridOnReady, applySearch}) { | |||
| const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria); | |||
| const navigate = useNavigate() | |||
| const [isPopUp, setIsPopUp] = React.useState(false); | |||
| const [bibId, setBibId] = React.useState(); | |||
| const [bib, setBib] = React.useState(); | |||
| const [appNo, setAppNo] = React.useState(); | |||
| const [refreshTrigger, setRefreshTrigger] = React.useState(0); | |||
| const forceRefresh = () => { | |||
| setRefreshTrigger(prev => prev + 1); | |||
| }; | |||
| const _sx = { | |||
| padding: "4 2 4 2", | |||
| @@ -37,6 +55,24 @@ export default function SearchPaymentTable({ searchCriteria }) { | |||
| navigate('/paymentPage/details/' + params.row.id); | |||
| }; | |||
| const doBIB = () => { | |||
| setIsPopUp(false); | |||
| // console.log(refreshTrigger) | |||
| HttpUtils.post({ | |||
| url: PAYMENT_BIB + "/" + bibId, | |||
| onSuccess: function () { | |||
| forceRefresh() | |||
| } | |||
| }); | |||
| } | |||
| const popUPBib = (id, bibFlag, appNo) => { | |||
| setBibId(id) | |||
| setBib(bibFlag) | |||
| setAppNo(appNo) | |||
| setIsPopUp(true); | |||
| } | |||
| const columns = [ | |||
| { | |||
| id: 'appNos', | |||
| @@ -59,14 +95,46 @@ export default function SearchPaymentTable({ searchCriteria }) { | |||
| return clickableLink('/paymentPage/details/' + params.row.id, params.row.transNo); | |||
| }, | |||
| }, | |||
| { | |||
| field: 'payMethod', | |||
| headerName: 'Payment Method', | |||
| flex: 1, | |||
| width: 150, | |||
| renderCell: (params) => { | |||
| return getPaymentMethodByCode(params?.value) | |||
| } | |||
| }, | |||
| { | |||
| id: 'transDateTime', | |||
| field: 'transDateTime', | |||
| headerName: 'Transaction Date', | |||
| flex: 1, | |||
| minWidth: 150, | |||
| valueGetter: (params) => { | |||
| return DateUtils.dateStr(params?.value); | |||
| // sorting/filtering uses this value | |||
| valueGetter: (params) => DateUtils.toDate(params?.value), | |||
| // display uses this (params.value is the *Date* returned above) | |||
| valueFormatter: (params) => { | |||
| const d = params.value; // Date or Invalid Date | |||
| return d instanceof Date && !isNaN(d.getTime()) | |||
| ? DateUtils.dateStr(d) | |||
| : ""; | |||
| }, | |||
| // make sorting 100% deterministic | |||
| sortComparator: (v1, v2) => { | |||
| const t1 = v1 instanceof Date && !isNaN(v1.getTime()) ? v1.getTime() : -Infinity; | |||
| const t2 = v2 instanceof Date && !isNaN(v2.getTime()) ? v2.getTime() : -Infinity; | |||
| return t1 - t2; | |||
| } | |||
| }, | |||
| { | |||
| field: 'bib', | |||
| headerName: 'BIB', | |||
| width: 150, | |||
| renderCell: (params) => { | |||
| return <Checkbox checked={params.row.bib} onChange={() => {popUPBib(params.row.id, params.row.bib, params.row.appNos)}}/>; | |||
| } | |||
| }, | |||
| { | |||
| @@ -96,11 +164,39 @@ export default function SearchPaymentTable({ searchCriteria }) { | |||
| columns={columns} | |||
| customPageSize={10} | |||
| onRowDoubleClick={handleEditClick} | |||
| doLoad={{ | |||
| url:PAYMENT_LIST, | |||
| params:_searchCriteria, | |||
| }} | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| // doLoad={{ | |||
| // url:PAYMENT_LIST, | |||
| // params:_searchCriteria, | |||
| // }} | |||
| doLoad={React.useMemo(() => ({ | |||
| url: PAYMENT_LIST, | |||
| params: _searchCriteria, | |||
| }), [_searchCriteria, refreshTrigger])} | |||
| /> | |||
| <div> | |||
| <Dialog | |||
| open={isPopUp} | |||
| onClose={() => setIsPopUp(false)} | |||
| PaperProps={{ | |||
| sx: { | |||
| minWidth: '40vw', | |||
| maxWidth: { xs: '90vw', s: '90vw', m: '70vw', lg: '70vw' }, | |||
| maxHeight: { xs: '90vh', s: '70vh', m: '70vh', lg: '60vh' } | |||
| } | |||
| }} | |||
| > | |||
| <DialogTitle>Bank-in-bank</DialogTitle> | |||
| <DialogContent style={{ display: 'flex', }}> | |||
| <Typography variant="h5" style={{ padding: '16px' }}>{bib?"Cancel Bank-in-bank?":"Set "+appNo+" as Bank-in-bank?"}</Typography> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button onClick={() => setIsPopUp(false)}><Typography variant="h5">Cancel</Typography></Button> | |||
| <Button onClick={() => doBIB()}><Typography variant="h5">Confirm</Typography></Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -19,11 +19,12 @@ import {DemoItem} from "@mui/x-date-pickers/internals/demo"; | |||
| import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; | |||
| import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => { | |||
| const [minDate, setMinDate] = React.useState(searchCriteria.dateFrom); | |||
| const [maxDate, setMaxDate] = React.useState(searchCriteria.dateTo); | |||
| const [status, setStatus] = React.useState(ComboData.paymentStatus[0]); | |||
| const [payMethod, setPayMethod] = React.useState(ComboData.payMethod[0]); | |||
| const { reset, register, handleSubmit } = useForm() | |||
| const marginBottom = 2.5; | |||
| @@ -31,6 +32,38 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| const [fromDateValue, setFromDateValue] = React.useState("dd / mm / yyyy"); | |||
| const [toDateValue, setToDateValue] = React.useState("dd / mm / yyyy"); | |||
| React.useEffect(() => { | |||
| if(searchCriteria.status!=undefined){ | |||
| if(searchCriteria.status === ""){ | |||
| ComboData.paymentStatus[0] | |||
| }else{ | |||
| setStatus(ComboData.paymentStatus.find(item => item.type === searchCriteria.status)) | |||
| } | |||
| }else{ | |||
| setStatus(ComboData.paymentStatus[0]) | |||
| } | |||
| }, [searchCriteria]); | |||
| React.useEffect(() => { | |||
| const defaultPayMethod = ComboData.payMethod[0]; | |||
| const value = searchCriteria?.payMethod; // may be [], null, undefined, or array of strings | |||
| if (!value || value.length === 0) { | |||
| setPayMethod(defaultPayMethod); | |||
| return; | |||
| } | |||
| // Find the matching entry whose type array matches value contents | |||
| const found = ComboData.payMethod.find(item => | |||
| Array.isArray(item.type) && | |||
| item.type.length === value.length && | |||
| item.type.every((v, i) => v === value[i]) // strict positional match | |||
| ); | |||
| setPayMethod(found ?? defaultPayMethod); | |||
| }, [searchCriteria?.payMethod]); | |||
| React.useEffect(() => { | |||
| setFromDateValue(minDate); | |||
| }, [minDate]); | |||
| @@ -39,6 +72,13 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| setToDateValue(maxDate); | |||
| }, [maxDate]); | |||
| // add near the top inside the component (after useState for payMethod) | |||
| const toPayMethodArray = (opt) => { | |||
| if (!opt || opt.type === 'all') return []; | |||
| return Array.isArray(opt.type) ? opt.type : [opt.type]; | |||
| }; | |||
| const onSubmit = (data) => { | |||
| let sentDateFrom = ""; | |||
| let sentDateTo = ""; | |||
| @@ -54,6 +94,9 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| dateFrom: sentDateFrom, | |||
| dateTo: sentDateTo, | |||
| status : (status?.type && status?.type != 'all') ? status?.type : "", | |||
| payMethod : toPayMethodArray(payMethod), | |||
| start:0, | |||
| limit:10 | |||
| }; | |||
| applySearch(temp); | |||
| }; | |||
| @@ -62,7 +105,11 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| setStatus(ComboData.paymentStatus[0]); | |||
| setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | |||
| setMaxDate(DateUtils.dateValue(new Date())) | |||
| reset(); | |||
| reset({ | |||
| code:"", | |||
| transNo:"" | |||
| }); | |||
| localStorage.setItem('searchCriteria',"") | |||
| } | |||
| @@ -184,6 +231,11 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| setStatus(newValue); | |||
| } | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label="Status" | |||
| @@ -194,6 +246,40 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| }} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: marginBottom }}> | |||
| <Autocomplete | |||
| {...register("payMethod")} | |||
| disablePortal={false} | |||
| size="small" | |||
| id="payMethod" | |||
| filterOptions={(options) => options} | |||
| options={ComboData.payMethod} | |||
| value={payMethod} | |||
| getOptionLabel={(option) => option.label} | |||
| inputValue={payMethod?.label ? payMethod?.label : ""} | |||
| onChange={(event, newValue) => { | |||
| if(newValue==null){ | |||
| setPayMethod(ComboData.payMethod[0]); | |||
| }else{ | |||
| setPayMethod(newValue); | |||
| } | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label="Payment Method" | |||
| /> | |||
| )} | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -215,6 +301,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| <Button | |||
| variant="contained" | |||
| type="submit" | |||
| disabled={onGridReady} | |||
| > | |||
| Submit | |||
| </Button> | |||
| @@ -7,6 +7,7 @@ import { | |||
| import MainCard from "components/MainCard"; | |||
| import * as React from "react"; | |||
| import * as DateUtils from "utils/DateUtils"; | |||
| import { getSearchCriteria } from "auth/utils"; | |||
| import Loadable from 'components/Loadable'; | |||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | |||
| @@ -28,18 +29,34 @@ const BackgroundHead = { | |||
| const Index = () => { | |||
| const [searchCriteria, setSearchCriteria] = React.useState({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }); | |||
| const [searchCriteria, setSearchCriteria] = React.useState({}); | |||
| const [onReady, setOnReady] = React.useState(false); | |||
| const [onGridReady, setGridOnReady] = React.useState(false); | |||
| React.useEffect(() => { | |||
| if (Object.keys(getSearchCriteria(window.location.pathname)).length>0){ | |||
| setSearchCriteria(getSearchCriteria(window.location.pathname)) | |||
| }else{ | |||
| localStorage.setItem('searchCriteria',"") | |||
| setSearchCriteria({ | |||
| dateTo: DateUtils.dateValue(new Date()), | |||
| dateFrom: DateUtils.dateValue(new Date().setDate(new Date().getDate()-14)), | |||
| }) | |||
| } | |||
| }, []); | |||
| React.useEffect(() => { | |||
| setOnReady(true); | |||
| }, [searchCriteria]); | |||
| function applySearch(input) { | |||
| setGridOnReady(true) | |||
| setSearchCriteria(input); | |||
| localStorage.setItem('searchCriteria', JSON.stringify({path:window.location.pathname,data:input})) | |||
| } | |||
| function applyGridOnReady(input) { | |||
| setGridOnReady(input); | |||
| } | |||
| return ( | |||
| @@ -63,8 +80,9 @@ const Index = () => { | |||
| {/*row 1*/} | |||
| <Grid item xs={12} md={12} lg={12} sx={{mb:-1}}> | |||
| <SearchForm | |||
| applySearch={applySearch} | |||
| searchCriteria={searchCriteria} | |||
| applySearch={applySearch} | |||
| searchCriteria={searchCriteria} | |||
| onGridReady={onGridReady} | |||
| /> | |||
| </Grid> | |||
| {/*row 2*/} | |||
| @@ -76,6 +94,8 @@ const Index = () => { | |||
| > | |||
| <EventTable | |||
| searchCriteria={searchCriteria} | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| /> | |||
| </MainCard> | |||
| </Grid> | |||
| @@ -14,7 +14,7 @@ import { clickableLink } from 'utils/CommonFunction'; | |||
| import {PAYMENT_LIST} from "utils/ApiPathConst"; | |||
| // ==============================|| EVENT TABLE ||============================== // | |||
| export default function SearchPublicNoticeTable({ searchCriteria }) { | |||
| export default function SearchPublicNoticeTable({ searchCriteria, applyGridOnReady, applySearch }) { | |||
| const navigate = useNavigate() | |||
| const theme = useTheme(); | |||
| const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); | |||
| @@ -94,7 +94,7 @@ export default function SearchPublicNoticeTable({ searchCriteria }) { | |||
| { | |||
| id: 'payAmount', | |||
| field: 'payAmount', | |||
| headerName: intl.formatMessage({id: 'currencyAmount'}) + ' ($)', | |||
| headerName: intl.formatMessage({id: 'currencyAmount'}), | |||
| width: 150, | |||
| valueGetter: (params) => { | |||
| return (params?.value) ? "$ " + FormatUtils.currencyFormat(params?.value) : ""; | |||
| @@ -110,10 +110,16 @@ export default function SearchPublicNoticeTable({ searchCriteria }) { | |||
| columns={columns} | |||
| customPageSize={10} | |||
| onRowDoubleClick={handleEditDoubleClick} | |||
| doLoad={{ | |||
| applyGridOnReady={applyGridOnReady} | |||
| applySearch={applySearch} | |||
| // doLoad={{ | |||
| // url: PAYMENT_LIST, | |||
| // params: _searchCriteria, | |||
| // }} | |||
| doLoad={React.useMemo(() => ({ | |||
| url: PAYMENT_LIST, | |||
| params: _searchCriteria, | |||
| }} | |||
| }), [_searchCriteria])} | |||
| /> | |||
| </div> | |||
| ); | |||
| @@ -21,11 +21,11 @@ import {DemoItem} from "@mui/x-date-pickers/internals/demo"; | |||
| import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; | |||
| import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; | |||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
| const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => { | |||
| const intl = useIntl(); | |||
| const [minDate, setMinDate] = React.useState(searchCriteria.dateFrom); | |||
| const [maxDate, setMaxDate] = React.useState(searchCriteria.dateTo); | |||
| const [status, setStatus] = React.useState(ComboData.paymentStatus[0]); | |||
| const [status, setStatus] = React.useState(searchCriteria.status!=undefined?ComboData.paymentStatus.find(item => item.type === searchCriteria.status):ComboData.paymentStatus[0]); | |||
| const [fromDateValue, setFromDateValue] = React.useState("dd / mm / yyyy"); | |||
| const [toDateValue, setToDateValue] = React.useState("dd / mm / yyyy"); | |||
| @@ -70,6 +70,8 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| dateFrom: sentDateFrom, | |||
| dateTo: sentDateTo, | |||
| status : (status?.type && status?.type != 'all') ? status?.type : "", | |||
| start:0, | |||
| limit:10 | |||
| }; | |||
| applySearch(temp); | |||
| }; | |||
| @@ -78,7 +80,11 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| setStatus(ComboData.paymentStatus[0]); | |||
| setMinDate(DateUtils.dateValue(new Date().setDate(new Date().getDate()-14))) | |||
| setMaxDate(DateUtils.dateValue(new Date())) | |||
| reset(); | |||
| reset({ | |||
| code:"", | |||
| transNo:"" | |||
| }); | |||
| localStorage.setItem('searchCriteria',"") | |||
| } | |||
| @@ -118,7 +124,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| <Grid item xs={12} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <Grid container spacing={1}> | |||
| <Grid item xs={6}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateFrom" | |||
| @@ -146,7 +152,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||
| <DemoItem components={['DatePicker']}> | |||
| <DatePicker | |||
| id="dateTo" | |||
| @@ -181,6 +187,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| disablePortal={false} | |||
| id="status" | |||
| size="small" | |||
| disableClearable | |||
| filterOptions={(options) => options} | |||
| options={ComboData.paymentStatus} | |||
| value={status} | |||
| @@ -193,6 +200,11 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| setStatus(ComboData.paymentStatus[0]); | |||
| } | |||
| }} | |||
| sx={{ | |||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||
| '& .MuiOutlinedInput-root': { height: 40 } | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField {...params} | |||
| label={intl.formatMessage({id: 'status'})} | |||
| @@ -201,6 +213,10 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| InputLabelProps={{ | |||
| shrink: true | |||
| }} | |||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||
| /> | |||
| </Grid> | |||
| @@ -239,6 +255,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria }) => { | |||
| variant="contained" | |||
| type="submit" | |||
| aria-label={intl.formatMessage({id: 'submit'})} | |||
| disabled={onGridReady} | |||
| > | |||
| <FormattedMessage id="submit"/> | |||
| </Button> | |||