Compare commits

...

421 Commits

Author SHA1 Message Date
  Jason Chuang 0ff322d87a avoid double click to forgot page 3 days ago
  Jason Chuang d6ec014a26 add back toastify message box with 30 sec 3 days ago
  Jason Chuang cbe3357883 incident update 3 days ago
  Jason Chuang 71339bd540 update to avoid displaying too much alert "Login verification has expired, please log in again." 1 week ago
  Jason Chuang 7673697565 apply html title and description to match the page contents 1 week ago
  Jason Chuang c9b48778ed button add keyboard control 1 week ago
  Jason Chuang 3e4f5227b6 button add keyboard control 1 week ago
  Jason Chuang ea5529d24b remove toastify and change to dialog box 1 week ago
  Jason Chuang 42a8501fd2 fix for concurrent create proof an check create 1 week ago
  Jason Chuang 1f16b40fe9 aria-label for careOf 2 weeks ago
  Jason Chuang b648db4023 added back missing i18n 2 weeks ago
  Jason Chuang 20accadbe1 add back aria-label with i18n 2 weeks ago
  Jason Chuang 23918f7cf1 update all aria-label 2 weeks ago
  Jason Chuang 572762b31c add i18n 2 weeks ago
  Jason Chuang 1a85f8f146 update navbar style 2 weeks ago
  Jason Chuang 5aa7d51974 minor adjust login page layout 2 weeks ago
  Jason Chuang d5dc361b87 avoid displaying too much alert "Login verification has expired, please log in again." 2 weeks ago
  Jason Chuang dbb1462474 avoid double click to submit in application form 2 weeks ago
  Jason Chuang 6de845a965 CR020 - remove importantNotice and landingMessage and privacyPolicy to settings 2 weeks ago
  Jason Chuang f6c68b0327 CR020 - remove importantNotice and landingMessage and privacyPolicy to settings 2 weeks ago
  Jason Chuang 1e8e9402d1 fix multiple reply proof and reset username and password 2 weeks ago
  Jason Chuang 933bfe2752 update reset button in FormWizard 3 weeks ago
  Jason Chuang 9a0e515d64 update style. 3 weeks ago
  Jason Chuang 4978621d6b double the message display time 3 weeks ago
  Jason Chuang a865e4e3f3 WCAG 2.0 A 1.3.1 Info and Relationships 3 weeks ago
  Jason Chuang 8bc293c817 Merge branch 'web_access_fix' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into web_access_fix 3 weeks ago
  Jason Chuang bc87f5df36 Async Queue for Fallback Excel with Email 3 weeks ago
  Alex Cheung e7d1c3402e update WCAG to 4.1.2 3 weeks ago
  Alex Cheung 617904b649 WCAG 1.3.1 and add back 1.1.1 3 weeks ago
  Jason Chuang 1a74f172d9 WCAG 2.0, A 4.1.2 Name, Role, Value 3 weeks ago
  Jason Chuang a412582bbc remove border 3 weeks ago
  Jason Chuang 686ca8aeeb WCAG 2.0 AA 1.4.3 Contrast (Minimum) 3 weeks ago
  Jason Chuang 3e7280485f add back footer info 4 weeks ago
  Jason Chuang 27b5128b23 WCAG 2.0, AA 2.4.7 Focus Visible 4 weeks ago
  Jason Chuang bfecc333d6 WCAG 2.1, AA 1.4.11 Non-Text Contrast 4 weeks ago
  Alex Cheung f3f75f97c6 add audio for captcha 1 month ago
  Alex Cheung e8690f0baf update 1.1.1 and test audio in window 1 month ago
  Jason Chuang 125cbf505a apply sorting 1 month ago
  Jason Chuang 3d2674a433 label typo 1 month ago
  Jason Chuang a687fb6dc1 summary report 1000 days 1 month ago
  Jason Chuang dda218165a summary report 1000 days 1 month ago
  Jason Chuang 3ef1096f25 registration form - fix district check and email check 1 month ago
  Jason Chuang 11c5a3be3d update to GFMIS XML form 1 month ago
  Jason Chuang dc9e0c5bd0 GFMIS update 1 month ago
  Jason Chuang 6d29b27b4d update submit date to payment date 1 month ago
  Jason Chuang 65b0382330 fix date sorting in proof search 1 month ago
  Jason Chuang 90d151dfa4 summary report, add 90 days validation check 1 month ago
  Jason Chuang dc39322e9b fix date sorting in proof search 1 month ago
  Jason Chuang e79c1c634d Merge branch 'CR013B2' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into CR013B2 1 month ago
  Jason Chuang 0449cc1d0e redirect aboutUs to external site 1 month ago
  Alex Cheung 5a4cf53aa9 outstanding fix update 1 month ago
  Jason Chuang 99b4a072aa update status color 1 month ago
  Jason Chuang 5186caae6a update full impl date 1 month ago
  Jason Chuang a7e9f0a81e user guide redirect old page to userGuidePub 1 month ago
  Jason Chuang ee44296897 remove old user guide 1 month ago
  Jason Chuang 720a634de8 link user guide to new page 1 month ago
  Jason Chuang 2b4fb8e7ba hardcode date comment 1 month ago
  Jason Chuang 1a74e40f80 user guide use userguide-fi/ 1 month ago
  Jason Chuang 8288cd9c45 update message 1 month ago
  Jason Chuang 0c153693d8 update message 2 months ago
  Jason Chuang 5d476a6195 add /userguide-fi and /userGuidePub1 2 months ago
  Jason Chuang a879fd9320 update full impl landing msg 1 month ago
  Jason Chuang e18fe48bf7 update payment deadline display format in apply application 1 month ago
  Jason Chuang 6ec022890d update 2 months ago
  Alex Cheung 7dbaff2ea6 update popup window setting before login 2 months ago
  Jason Chuang fc0ec304bb update message 2 months ago
  Jason Chuang 45476b5ba1 update manual 2 months ago
  Jason Chuang 364813da43 update 2 months ago
  Jason Chuang 40b8c0f09d update 2 months ago
  Jason Chuang 4ff3bef0ad update 2 months ago
  Jason Chuang 53022fec3f removed ok and change to close, various update 2 months ago
  Jason Chuang d54aa49332 update user menu link 2 months ago
  Jason Chuang 7455f11b8c Merge branch 'CR013B2' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into CR013B2 2 months ago
  Jason Chuang a694bb1580 i18n update 2 months ago
  Alex Cheung a554516765 update suspension text in i18n, add new guide page, add new suspension text 2 months ago
  Alex Cheung 4171a9fdcf Merge branch 'New_Enhancement' into CR013B2 2 months ago
  Alex Cheung 065188ae99 update check type 2 months ago
  Jason Chuang 2cd999802d update layout 2 months ago
  Alex Cheung 18e8b3e326 update org combo update 3 months ago
  Jason Chuang 457cad73fd bug fix to expiry date for payment sus mode 2 months ago
  Jason Chuang 7e61c2adee update message 2 months ago
  Jason Chuang 0b2c08c7a5 update message 2 months ago
  Jason Chuang 9f0e080138 update message 2 months ago
  Alex Cheung 9fc1ac49ed update org combo update 3 months ago
  Jason Chuang 6f2dc9de27 update login and about us text 3 months ago
  Jason Chuang 34959d72a1 update labl 3 months ago
  Jason Chuang 1030c8abe2 update layout 3 months ago
  Jason Chuang a15aad4892 update privacy 3 months ago
  Jason Chuang 2ca380f2dc payment suspension cannot change payment means 3 months ago
  Jason Chuang c0c045c69d update typo from Suspention to Suspension 3 months ago
  Jason Chuang b3b21475b3 update privacy 3 months ago
  Jason Chuang ef8862dd46 update label 3 months ago
  Jason Chuang d54e3aceb5 update 3 months ago
  Jason Chuang 685c7b9317 Merge remote-tracking branch 'origin/New_Enhancement' into CR013B2 3 months ago
  Jason Chuang d17e8632aa update privacy page 3 months ago
  Jason Chuang cdfd46481d make grey login as setting 3 months ago
  Jason Chuang 44370fac3a update label 3 months ago
  Jason Chuang c9708dfcc8 full impl date 3 months ago
  Jason Chuang 8b06c4317f fix transDateTime 3 months ago
  Jason Chuang 078c6203eb update label 3 months ago
  Jason Chuang faddaf176f update 3 months ago
  Jason Chuang fd2ffd2ef0 grey login 3 months ago
  Jason Chuang e3c434c9cd disabled banner 3 months ago
  Alex Cheung 10ee4d9633 update hidden checkbox and text in online only 3 months ago
  Alex Cheung ae78d2ea7a update payment date checking 4 months ago
  Alex Cheung e26e45395f update combo layout 4 months ago
  Jason Chuang 1c1db00bce update lego banner, disappear after 7 Dec 4 months ago
  Alex Cheung 81bfb54021 update index popup 4 months ago
  Jason Chuang bdfa2a2a5e Merge branch 'CR013B2' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into CR013B2 4 months ago
  Jason Chuang 48c8eb9c57 update label 4 months ago
  Alex Cheung f8b699158e update filename 4 months ago
  Alex Cheung 10f578a6bc Merge branch 'CR013B2' of http://svn.2fi-solutions.com:8300/alex/PNSPS-frontend-MaterialUI into CR013B2 4 months ago
  Alex Cheung b1dae29d80 update combo 4 months ago
  Jason Chuang 21c49c9e0a update 4 months ago
  Jason Chuang d8ae0eebb6 update label 4 months ago
  Jason Chuang 9db5cb6f6a update UI 4 months ago
  Alex Cheung 2106d561f3 add report pages 4 months ago
  Jason Chuang 02374dc8f5 update 4 months ago
  Alex Cheung e208f02e78 update expiry date 4 months ago
  Alex Cheung b584928741 update combo layout and field update 4 months ago
  Jason Chuang 9085495fa4 package.json 4 months ago
  Jason Chuang a1cd8c52d6 update message 5 months ago
  Jason Chuang 770461aece update message 5 months ago
  Jason Chuang 49cca90282 update message 5 months ago
  Jason Chuang 84eb7909a8 update message 5 months ago
  Jason Chuang 66f77ffc3e update message 5 months ago
  Jason Chuang c44f21d1fe update message 5 months ago
  Jason Chuang b14fe00338 submission issue display update 5 months ago
  Jason Chuang ab24530b5b application submission no issue check 5 months ago
  Jason Chuang 6848203aac Merge branch 'CR013B2' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into CR013B2 5 months ago
  Jason Chuang 2d57f3ed55 update 5 months ago
  Alex Cheung 1f3099d30b update checking 5 months ago
  Alex Cheung 0647a0278a update checking 5 months ago
  Alex Cheung 95ab87723f update check public date 5 months ago
  Alex Cheung 1d4324f211 add check current date for online payment 5 months ago
  Alex Cheung 5866a393ea add issue check for target date 5 months ago
  Jason Chuang f31826beb2 update 5 months ago
  Alex Cheung 63b85fe5e5 update xml gird 6 months ago
  Alex Cheung 269f4f05d6 update checkbox and target date for online payment 6 months ago
  Alex Cheung bad15bf9f5 add select all checkbox 6 months ago
  Alex Cheung 77c862a41d update comment 6 months ago
  Jason Chuang cabafad91c fix typo 6 months ago
  Jason Chuang b4284d8eee update msg 6 months ago
  Alex Cheung 1e721095be update import offline and xml 6 months ago
  Alex Cheung d2b5f0407c update report in gld page 6 months ago
  Alex Cheung b1c5f43aba update offline mode 6 months ago
  CCHooo 3663276cb1 add login public check, message check, demand note no 6 months ago
  Jason Chuang dd9a84d7af fix typo 6 months ago
  Alex Cheung 1af1b1ff78 add suspension mode, add allow login, update gfmis 6 months ago
  Alex Cheung 2fb6afbe14 Merge branch 'CR013B1' into CR013B2 6 months ago
  Alex Cheung 612ea34da6 Merge branch 'New_Enhancement' into CR013B1 6 months ago
  Alex Cheung ada48d3ae3 fix paging 6 months ago
  Alex Cheung 93c3fc3d85 update header 6 months ago
  Alex Cheung d2eafa69b8 fix case 1-5 6 months ago
  Alex Cheung f8fa390d85 Merge branch 'New_Enhancement' into CR013B1 6 months ago
  Alex Cheung 9e03c0472e add DemandNoteNo, add payment means for application 6 months ago
  Alex Cheung a37e78e8e4 update set bib add search payment 7 months ago
  Jason Chuang 62c3a6bbfd FPS enable web-to-app 7 months ago
  Alex Cheung 3f2229880b Merge branch 'CR013B1' into CR013B2 7 months ago
  Alex Cheung fd9a595178 remove user guide add bib 7 months ago
  Alex Cheung a12cb4da7d update check page 7 months ago
  Alex Cheung b9f2959e44 update db check page /databaseHealthCheck 7 months ago
  Alex Cheung 59988b5b67 update recon download file btn 8 months ago
  Alex Cheung d42078b8fe update download btn disable 8 months ago
  Alex Cheung 6f7b42e36c add get jvm info 8 months ago
  Alex Cheung eb89e8a0be update jvm page 8 months ago
  Alex Cheung 065acd93f6 FIx FPS cancel 9 months ago
  Alex Cheung fe87557cc9 fix public notes save search criteria 9 months ago
  Jason Chuang 3b7de3788e fix audit log export 9 months ago
  Alex Cheung 664d9d49a7 update SearchCriteria save 9 months ago
  Alex Cheung 1d72ac64c2 update save page number for data gird 9 months ago
  Alex Cheung c152df6ba9 update massage 9 months ago
  Alex Cheung 5a7999bb90 update message 9 months ago
  Alex Cheung 3cdf8d0c90 update org register remove '()' checking 9 months ago
  Alex Cheung 3d2ecf8186 update org user faxNo and org faxNo and dashboard clear search 9 months ago
  Alex Cheung b7374bbfb7 fix clear combo 10 months ago
  Alex Cheung c8f1f6093a update gld view searching and gld user search page bug fix 10 months ago
  Alex Cheung 5c11ea708d update application and bug fix 10 months ago
  Alex Cheung 78a5d3917e update public search criteria save 10 months ago
  Alex Cheung 8307b7a052 update proof searchCriteria save 10 months ago
  Alex Cheung 32c3b256da update check page 7 months ago
  Jason Chuang d59bff36dd date hardcode 7 months ago
  Alex Cheung fbd0a00b8b update db check page /databaseHealthCheck 7 months ago
  Jason Chuang 63d1af9565 Merge branch 'CR013B1' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into CR013B1 8 months ago
  Jason Chuang a73453306b update 8 months ago
  Alex Cheung cf09c0215d update proof payment method check 8 months ago
  Alex Cheung 345d0970e4 add sample date 8 months ago
  Alex Cheung d94a603775 update application payment with issueDate show 8 months ago
  Alex Cheung 520de2f87c update recon download file btn 8 months ago
  Alex Cheung 8a873414c6 update download btn disable 8 months ago
  Alex Cheung 0347064423 add get jvm info 8 months ago
  Alex Cheung 3fdf6311bb update jvm page 8 months ago
  Alex Cheung 7777e520c4 FIx FPS cancel 9 months ago
  Alex Cheung f5e671ca7f fix public notes save search criteria 9 months ago
  Jason Chuang 985118ae10 fix audit log export 9 months ago
  Alex Cheung ad333c8621 update SearchCriteria save 9 months ago
  Alex Cheung ab6377db8c update save page number for data gird 9 months ago
  Alex Cheung afb6d486f7 add checking for creditor btn 9 months ago
  Alex Cheung afd6fe0242 update massage 9 months ago
  Alex Cheung 3cf55a0191 update message 9 months ago
  Alex Cheung 673229bbe5 update org register remove '()' checking 9 months ago
  Alex Cheung 5f22c8c649 update keep only online payment after 2026-01-28 9 months ago
  Alex Cheung 3cc816b55c update org user faxNo and org faxNo and dashboard clear search 9 months ago
  Alex Cheung f85eb71007 fix clear combo 10 months ago
  Alex Cheung 923c8577f3 update gld view searching and gld user search page bug fix 10 months ago
  Alex Cheung c2fe95a1a6 update application and bug fix 10 months ago
  Alex Cheung 70e6943b4a update public search criteria save 10 months ago
  Alex Cheung 868e84838d update proof searchCriteria save 10 months ago
  Alex Cheung ef23ec8af9 layout fix 1 year ago
  Alex Cheung 75f4c51fff update home page message and about us 1 year ago
  Alex Cheung d849afb6fc update about us 1 year ago
  Alex Cheung b436ffa1d0 update application reset btn 1 year ago
  Alex Cheung fe84a6f046 add back FPS auto cancel 1 year ago
  Alex Cheung 6a07b6cc71 add careOf and org combo 1 year ago
  Jason Chuang 257526d37c emport phone no 1 year ago
  Alex Cheung 18a990b716 Merge branch 'New_Enhancement' into CR003 1 year ago
  Alex Cheung ebcccfc705 update proof combo 1 year ago
  Alex Cheung d66410f7ac fix table field for dummy user 1 year ago
  Alex Cheung d748c23f51 update fps web to app for testing 1 year ago
  Alex Cheung 68914b6406 updated contactPerson with empty string when user is dummy user 1 year ago
  Alex Cheung 71541441a5 update fps expired 1 year ago
  Alex Cheung a591027d29 update fps 1 year ago
  Alex Cheung 324548afbf update gen gdn 1 year ago
  Alex Cheung 50a22d0d69 handle dummy user client display 1 year ago
  Alex Cheung 48a88815c1 add search combo for gld views 1 year ago
  Alex Cheung ded2428881 combo update 1 year ago
  Alex Cheung 6015900f57 update audit log auth 1 year ago
  Alex Cheung 77efaee1da add application remark auth 1 year ago
  Alex Cheung c16e0af999 update group 1 year ago
  Alex Cheung 35daecbf33 add auth 1 year ago
  Alex Cheung 874aeeb2cc fix grid loading 1 year ago
  Alex Cheung 9d537b917c remove build 1 year ago
  Alex Cheung 4952fcab16 search button update 1 year ago
  Alex Cheung efa7c3933b fix datagrid error 1 year ago
  Alex Cheung 9d4ecf4390 fix datagird without search button 1 year ago
  Alex Cheung 20902d5305 fix disable button with error 1 year ago
  Alex Cheung 5b5589566a update search button 1 year ago
  Alex Cheung 9c5bd9ac95 fix double search api for data table 1 year ago
  Alex Cheung 50ddd841b4 add loading to datagird 1 year ago
  Alex Cheung 63738d8b6a add loading for iAmsmart loading data 1 year ago
  Anna Ho 44325ecc10 CriOS as iOS_Chrome 1 year ago
  Alex Cheung 3ef7e45af1 update FPS test 1 year ago
  Jason Chuang 46aa7177fe Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Jason Chuang 6b9998fccc FPS 1 year ago
  Alex Cheung 3b0753f753 update user guide 1 year ago
  Jason Chuang 505554a408 update user guide label 1 year ago
  Jason Chuang 0e92a27d81 update label 1 year ago
  Jason Chuang 70b87c12db update user guide pub link 1 year ago
  Anna Ho 3adb028a51 add 1 year ago
  Jason Chuang 8b0caa2231 Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Jason Chuang f971b01920 update about us 1 year ago
  Anna Ho ddad685252 cn use zh user guide file 1 year ago
  Anna Ho e91b67420c change path 1 year ago
  Anna Ho 6d4e1cd767 add header 1 year ago
  Jason Chuang 5548862d84 Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Jason Chuang c98e2c0a83 fps and about us 1 year ago
  Anna Ho f9e43b23e7 user-guide-pub 1 year ago
  Alex Cheung 4d74c29810 date change 1 year ago
  Alex Cheung 53ac90879b fix pending payment deadline 1 year ago
  Alex Cheung c1c38cdb53 update wordings 1 year ago
  Alex Cheung dd3c15fe40 Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Alex Cheung c74c956d76 disable console.log 1 year ago
  Anna Ho 561610a51d change folder name 1 year ago
  Alex Cheung b6d805afa5 Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Alex Cheung 78e85c246f update uat title color change 1 year ago
  Anna Ho 47da9555d4 AboutUs, ImportantNotice, PrivacyPolicy 1 year ago
  Jason Chuang 6b81ba3544 enable FPS web to app 1 year ago
  Alex Cheung 33f312ce75 update fps disable check mobile 1 year ago
  Alex Cheung 40ef9d6fe3 update district 1 year ago
  Anna Ho 0460eae108 about us color 1 year ago
  Anna Ho cddce62251 About Us UI fix 1 year ago
  Anna Ho 1f07220334 about us 1 year ago
  Alex Cheung bd313899b5 update wordings and userprofile 1 year ago
  Alex Cheung 8091aaeb27 Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Alex Cheung 1bdbd45897 update idNo check 1 year ago
  Jason Chuang 3dfc109d63 iam smart label update 1 year ago
  Alex Cheung b1e5a946c4 fix display detail 1 year ago
  Alex Cheung b284fd5ba2 wording update 1 year ago
  Anna Ho 524272274d update 1 year ago
  Alex Cheung c0b27730d8 fix date bug 1 year ago
  Alex Cheung 0b46126b8c add admin header display 1 year ago
  Alex Cheung 1ceebfdb87 update wordings 1 year ago
  Anna Ho 3761c1184e iamsmart and fix dn sendDate bug 1 year ago
  Anna Ho ceed5c5cec change header to "Deadline for online manuscript revision (Pay online via PNSPS)" 1 year ago
  Anna Ho bd9655c4d2 fix bug 1 year ago
  Anna Ho 6880ed72ba iAM Smart set brokerPage=true 1 year ago
  Alex Cheung 0ee39a7fde update ui layout 1 year ago
  Alex Cheung 96b7bc6ef5 update i18n 1 year ago
  Alex Cheung 4afb60358b update i18n 1 year ago
  Alex Cheung e8a24e7403 update comment from Matt 1 year ago
  Alex Cheung ed089e64d1 fix i18n bug 1 year ago
  Alex Cheung 724e79b2fe Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Alex Cheung 505af12820 update i18n 1 year ago
  Anna Ho 783bf7ccc8 update UI 1 year ago
  Anna Ho 602c4b420b fix bug 1 year ago
  Anna Ho c0811a72df fix i18n 1 year ago
  Anna Ho b04f433b4d update i18n 1 year ago
  Anna Ho 0cee61df7d remove "pnspsdev.gld.gov.hk" 1 year ago
  Anna Ho 875458a76f update UI 1 year ago
  Anna Ho a322555297 update UI 1 year ago
  Anna Ho ffd3f166fa update i18n 1 year ago
  Anna Ho 26b3f9597b update string 1 year ago
  Jason Chuang f378e4f16d Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Jason Chuang 858d9d727e update message 1 year ago
  Alex Cheung f02a69cb4d Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Alex Cheung 6e97e779d8 update payment status 1 year ago
  Anna Ho 428abdc670 on iamsmart fail page 1 year ago
  Alex Cheung 0cb399846f update payment status display 1 year ago
  Alex Cheung 5219813c26 fix gns routes 1 year ago
  Jason Chuang 2de38844d0 update access right for GDN 1 year ago
  Anna Ho f952d2a7fc fix url 1 year ago
  Anna Ho c2dabf0d9f update i18n 1 year ago
  Anna Ho cd93eccdc8 update user guide ui 1 year ago
  Anna Ho 09eb8afdd3 add userGuide 1 year ago
  Anna Ho aa370d75ed add Important Notice 1 year ago
  Anna Ho 6c3aca90bf update check ID 1 year ago
  Alex Cheung 07a6f593b6 fix user profile 1 year ago
  Anna Ho c91f032a2d Login Routes re 1 year ago
  Alex Cheung ab3adaad33 Merge branch 'test_HKID' into New_Enhancement 1 year ago
  Alex Cheung f1af83023b fix cnid cannot save 1 year ago
  Anna Ho 1a632f2cad add SYS.ui.manage.allowRegistration 1 year ago
  Alex Cheung a640af8c79 update change password page add text 1 year ago
  Alex Cheung f8d82db96d update user HKid and checkdigi 1 year ago
  Alex Cheung 51e3209acc Merge branch 'New_Enhancement' into test_HKID 1 year ago
  Anna Ho dedcbf29bd move iAM Smart api path to properties 1 year ago
  Alex Cheung dda5735faf fix login with wrong username 1 year ago
  Alex Cheung cdbf342265 update index icon 1 year ago
  Jason Chuang a683d36674 update about us 1 year ago
  Anna Ho ef73acfeb3 fix create proof bug 1 year ago
  Anna Ho a3217e28c3 update forgot username 1 year ago
  Anna Ho 89dcb5cad8 update reply proof UI 1 year ago
  Jason Chuang 524be841e9 Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Jason Chuang bfe26b8160 update label 1 year ago
  Alex Cheung 4506564ee0 update fps prod url 1 year ago
  Anna Ho 6d3f5550a4 add about us 1 year ago
  Alex Cheung fd8b5427bc update gdn 1 year ago
  Anna Ho 7a5d5e6ede address5 change update phone 1 year ago
  Anna Ho ea4d92337c check proof column should > 0 1 year ago
  Anna Ho ca0dca8c10 update words 1 year ago
  Alex Cheung 09bd2f5098 Merge branch 'New_Enhancement' into SRAA_Test 1 year ago
  Alex Cheung 8ec89dec0f sraa update 1 year ago
  Jason Chuang fe5d91e148 update locale 1 year ago
  Anna Ho 4f95510418 word 1 year ago
  Anna Ho 3c4e51ca35 update word 1 year ago
  Alex Cheung a5d16b54a6 update Axios version for Vulnerable JS Library 1 year ago
  Anna Ho 415c86d1b9 update word 1 year ago
  Jason Chuang e70b0cbf05 Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Jason Chuang 5982711ad6 update label 1 year ago
  Anna Ho b727c5cb8f privacyPolicy 1 year ago
  Jason Chuang 1fcfcbe8ec update label 1 year ago
  Anna Ho 1db138f63e user group fix bug 1 year ago
  Anna Ho f43c78838d fix i am smart bug 1 year ago
  Alex Cheung 36ebdfce6f update fps mobile btn 1 year ago
  Jason Chuang bcf88deadd Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Jason Chuang fa974d4856 update label 1 year ago
  Terence 51dce719c3 update route case sensitive 1 year ago
  Anna Ho 4dd1b26a02 update iAM Smart button 1 year ago
  Alex Cheung bf113662ee Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Alex Cheung 414e5da675 update check pasword special character and update org btn auth and application Id i18n 1 year ago
  Anna Ho 6bfed621e7 update iAM Smart button text 1 year ago
  Alex Cheung cd687d6cb9 update hidle payment table for credit user 1 year ago
  Anna Ho 63400671a7 GLD proof search statue 1 year ago
  Jason Chuang cb28ce731c Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Jason Chuang b4eb579409 update label 1 year ago
  Anna Ho b700fff6e2 iAM Smart & status i18n 1 year ago
  Anna Ho bdf58c0b2d "iAM Smart" update Login Success UI 1 year ago
  Anna Ho f6fbec0c45 "iAM Smart" from update 1 year ago
  Anna Ho 13bf252cdb "iAM Smart" update 1 year ago
  Alex Cheung a88c79deb8 update emailConfirm handle 1 year ago
  Alex Cheung 066ba60193 update Amendment zip 1 year ago
  Anna Ho 53e51f84c3 update gen O/S dn List 1 year ago
  Anna Ho 7b8b15398d status 1 year ago
  Anna Ho 6d76833cd4 iAm Smart fix typo 1 year ago
  Jason Chuang d36afa6ee9 Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Jason Chuang 2999987498 update i18n 1 year ago
  Alex Cheung a91c53694e update check not accept btn checking and ui update 1 year ago
  Anna Ho 3aa8d99095 Proof Records Starus 1 year ago
  Anna Ho af54518f20 update status i18n 1 year ago
  Alex Cheung f120ab9edb update dummy user edit apply public notice fax and phone number 1 year ago
  Anna Ho 8fdc98276d fix bug 1 year ago
  Anna Ho 0c0b97456f GET_SEND_OVERDUE_CREDITOR_LIST 1 year ago
  Alex Cheung 99a229cf0c update setting 1 year ago
  Jason Chuang 646421d897 update label 1 year ago
  Jason Chuang a7b5878ecf update label 1 year ago
  Alex Cheung ad18ada2dc Merge branch 'master' into New_Enhancement 1 year ago
  Jason Chuang 1eed4d7f3d Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Jason Chuang 55f53d384c update label 1 year ago
  Alex Cheung b8b843aae2 fix preferLocale and add orgPaymentRecord for org check online payment record 1 year ago
  Jason Chuang 922abf73ac Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Jason Chuang 9234d74a74 label and layout update 1 year ago
  Alex Cheung 95e1b8c921 update 1 year ago
  Alex Cheung 13ec88f514 update fps second 1 year ago
  Alex Cheung e5614037b8 fix autoFocus 1 year ago
  Alex Cheung 51462e8283 fix eng name and cht name 1 year ago
  Alex Cheung 6ae8ba07a6 hidle error message without edit mode 1 year ago
  Alex Cheung 9e55135d38 update check captcha 1 year ago
  Alex Cheung 016ea6235d update check captcha 1 year ago
  Alex Cheung 39b150a58c update layout 1 year ago
  Alex Cheung e3cdffa2f5 update proof and payment page text 1 year ago
  Alex Cheung d7ae3c0ee1 update check name 1 year ago
  Anna Ho 4dca798a3a disable 15:00 Scheduled, add send mail Button 1 year ago
  Anna Ho 81639691e5 fix bug - reload after mark "published" 1 year ago
  Anna Ho ff0cbe1c05 Merge branch 'master' into New_Enhancement 1 year ago
  Anna Ho f13b166d63 Merge branch 'master' into New_Enhancement 1 year ago
  Alex Cheung cbc8eb2c90 update dashboard loading 1 year ago
  Alex Cheung 37a6b1f9e9 update 1 year ago
  Alex Cheung e9f2fdb852 update 1 year ago
  Alex Cheung b2a727ca61 add change password page 1 year ago
  Alex Cheung 55f9a1d78f Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Alex Cheung bd40598a1d update password log 1 year ago
  Anna Ho da6bcd39fb fix bug 1 year ago
  Anna Ho 7af45e4e8d Merge branch 'master' into New_Enhancement 1 year ago
  Anna Ho c2e1d3a305 dn revoke paid 1 year ago
  Alex Cheung 630344cfc4 Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Alex Cheung a78cdad254 add password remark 1 year ago
  Anna Ho 131b31f850 email and paid button at application 1 year ago
  Alex Cheung cb04b13088 Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Alex Cheung b61dc7d04a update captcha 1 year ago
  Anna Ho a0d8c5f0db Merge branch 'master' into New_Enhancement 1 year ago
  Alex Cheung b7a919f44e Merge branch 'New_Enhancement' of https://git.2fi-solutions.com/alex/PNSPS-frontend-MaterialUI into New_Enhancement 1 year ago
  Alex Cheung ec5aa563e4 update gldRemarks for gld user 1 year ago
100 changed files with 15784 additions and 20564 deletions
Split View
  1. +15
    -0
      .vscode/launch.json
  2. +11359
    -18747
      package-lock.json
  3. +12
    -1
      package.json
  4. BIN
      public/apple-touch-icon.png
  5. BIN
      public/favicon-16x16.png
  6. BIN
      public/favicon-32x32.png
  7. +11
    -40
      public/index.html
  8. +54
    -0
      public/safari-pinned-tab.svg
  9. +11
    -12
      src/App.js
  10. BIN
      src/assets/images/2025_lgce.jpg
  11. BIN
      src/assets/images/icons/expiredQrcodeCN.png
  12. BIN
      src/assets/images/icons/expiredQrcodeEN.png
  13. BIN
      src/assets/images/icons/expiredQrcodeZH.png
  14. +330
    -114
      src/assets/style/navbarStyles.css
  15. +92
    -17
      src/assets/style/styles.css
  16. +31
    -8
      src/auth/index.js
  17. +51
    -20
      src/auth/utils.js
  18. +26
    -11
      src/components/AdminLogo/index.js
  19. +16
    -4
      src/components/AutoLogoutProvider.js
  20. +207
    -56
      src/components/FiDataGrid.js
  21. +10
    -0
      src/components/FileList.js
  22. +98
    -64
      src/components/I18nProvider.js
  23. +4
    -0
      src/components/Logo/index.js
  24. +21
    -8
      src/components/MobileLogo/index.js
  25. +3
    -1
      src/components/RefreshTokenProvider.js
  26. +36
    -0
      src/components/SysSettingProvider.js
  27. +35
    -19
      src/components/cards/AuthFooter.js
  28. +3
    -2
      src/components/iAmSmartButton.js
  29. +42
    -0
      src/components/usePageTitle.js
  30. +34
    -4
      src/index.js
  31. +3
    -1
      src/layout/MainLayout/Drawer/index.js
  32. +28
    -13
      src/layout/MainLayout/Header/HeaderContent/LocaleSelector.js
  33. +20
    -13
      src/layout/MainLayout/Header/HeaderContent/MobileSection.js
  34. +16
    -6
      src/layout/MainLayout/Header/HeaderContent/Notification.js
  35. +4
    -2
      src/layout/MainLayout/Header/HeaderContent/Profile/index.js
  36. +838
    -540
      src/layout/MainLayout/Header/index.js
  37. +23
    -13
      src/layout/MainLayout/index.js
  38. +9
    -3
      src/pages/Announcement/Search/DataGrid.js
  39. +7
    -2
      src/pages/Announcement/Search/SearchForm.js
  40. +26
    -5
      src/pages/Announcement/Search/index.js
  41. +10
    -4
      src/pages/Announcement/Search_Public/DataGrid.js
  42. +28
    -5
      src/pages/Announcement/Search_Public/SearchForm.js
  43. +29
    -7
      src/pages/Announcement/Search_Public/index.js
  44. +14
    -9
      src/pages/AuditLog/AuditLogSearchForm.js
  45. +10
    -4
      src/pages/AuditLog/AuditLogTable.js
  46. +10
    -1
      src/pages/AuditLog/index.js
  47. +17
    -3
      src/pages/DemandNote/Create/SearchForm.js
  48. +8
    -4
      src/pages/DemandNote/Details/ApplicationDetailCard.js
  49. +6
    -4
      src/pages/DemandNote/Details/DnDetailCard.js
  50. +15
    -9
      src/pages/DemandNote/Export/DataGrid.js
  51. +11
    -0
      src/pages/DemandNote/Export/SearchForm.js
  52. +93
    -19
      src/pages/DemandNote/Search/DataGrid.js
  53. +77
    -6
      src/pages/DemandNote/Search/SearchForm.js
  54. +21
    -4
      src/pages/DemandNote/Search/index.js
  55. +14
    -6
      src/pages/DemandNote/Search_Public/DataGrid.js
  56. +43
    -5
      src/pages/DemandNote/Search_Public/SearchForm.js
  57. +25
    -7
      src/pages/DemandNote/Search_Public/index.js
  58. +3
    -3
      src/pages/EmailTemplate/Detail_GLD/index.js
  59. +8
    -4
      src/pages/EmailTemplate/Search_GLD/DataGrid.js
  60. +22
    -14
      src/pages/GFMIS/DataGrid.js
  61. +109
    -37
      src/pages/GFMIS/SearchForm.js
  62. +156
    -0
      src/pages/GFMIS/TransactionDataGrid.js
  63. +180
    -14
      src/pages/GFMIS/index.js
  64. +8
    -3
      src/pages/GazetteIssue/DataGrid.js
  65. +5
    -5
      src/pages/GazetteIssue/ExportForm.js
  66. +5
    -4
      src/pages/GazetteIssue/SearchForm.js
  67. +112
    -100
      src/pages/GazetteIssue/index.js
  68. +7
    -2
      src/pages/Holiday/DataGrid.js
  69. +5
    -4
      src/pages/Holiday/SearchForm.js
  70. +111
    -109
      src/pages/Holiday/index.js
  71. +187
    -0
      src/pages/JVM/index.js
  72. +7
    -3
      src/pages/Message/Details/index.js
  73. +38
    -22
      src/pages/Message/Search/DataGrid.js
  74. +9
    -4
      src/pages/Message/Search/SearchForm.js
  75. +49
    -26
      src/pages/Message/Search/index.js
  76. +143
    -86
      src/pages/Organization/DetailPage/OrganizationCard.js
  77. +51
    -55
      src/pages/Organization/DetailPage/OrganizationPubCard.js
  78. +6
    -2
      src/pages/Organization/DetailPage/index.js
  79. +15
    -13
      src/pages/Organization/DetailPage_FromUser/OrganizationCard_loadFromUser.js
  80. +4
    -0
      src/pages/Organization/DetailPage_FromUser/index.js
  81. +35
    -4
      src/pages/Organization/SearchPage/OrganizationSearchForm.js
  82. +10
    -4
      src/pages/Organization/SearchPage/OrganizationTable.js
  83. +27
    -2
      src/pages/Organization/SearchPage/index.js
  84. +1
    -1
      src/pages/Payment/Details_GLD/DataGrid.js
  85. +10
    -10
      src/pages/Payment/Details_GLD/PaymentDetails.js
  86. +1
    -1
      src/pages/Payment/Details_GLD/index.js
  87. +1
    -1
      src/pages/Payment/Details_Public/DataGrid.js
  88. +43
    -20
      src/pages/Payment/Details_Public/PaymentDetails.js
  89. +10
    -7
      src/pages/Payment/Details_Public/index.js
  90. +38
    -26
      src/pages/Payment/FPS/AckPage.js
  91. +129
    -68
      src/pages/Payment/FPS/FPS.js
  92. +1
    -1
      src/pages/Payment/FPS/FPSTest.js
  93. +50
    -40
      src/pages/Payment/FPS/fpscallback.js
  94. +7
    -7
      src/pages/Payment/MultiPaymentWindow.js
  95. +24
    -14
      src/pages/Payment/PaymentCallback.js
  96. +104
    -8
      src/pages/Payment/Search_GLD/DataGrid.js
  97. +89
    -2
      src/pages/Payment/Search_GLD/SearchForm.js
  98. +26
    -6
      src/pages/Payment/Search_GLD/index.js
  99. +10
    -4
      src/pages/Payment/Search_Public/DataGrid.js
  100. +22
    -5
      src/pages/Payment/Search_Public/SearchForm.js

+ 15
- 0
.vscode/launch.json View File

@@ -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"
}
]
}

+ 11359
- 18747
package-lock.json
File diff suppressed because it is too large
View File


+ 12
- 1
package.json View File

@@ -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"
}
}
}

BIN
public/apple-touch-icon.png View File

Before After
Width: 180  |  Height: 180  |  Size: 6.4 KiB

BIN
public/favicon-16x16.png View File

Before After
Width: 16  |  Height: 16  |  Size: 1.1 KiB

BIN
public/favicon-32x32.png View File

Before After
Width: 32  |  Height: 32  |  Size: 1.7 KiB

+ 11
- 40
public/index.html View File

@@ -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"


+ 54
- 0
public/safari-pinned-tab.svg View File

@@ -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>

+ 11
- 12
src/App.js View File

@@ -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;

BIN
src/assets/images/2025_lgce.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 631 KiB

BIN
src/assets/images/icons/expiredQrcodeCN.png View File

Before After
Width: 300  |  Height: 300  |  Size: 6.8 KiB

BIN
src/assets/images/icons/expiredQrcodeEN.png View File

Before After
Width: 300  |  Height: 300  |  Size: 8.8 KiB

BIN
src/assets/images/icons/expiredQrcodeZH.png View File

Before After
Width: 300  |  Height: 300  |  Size: 6.9 KiB

+ 330
- 114
src/assets/style/navbarStyles.css View File

@@ -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;
}

+ 92
- 17
src/assets/style/styles.css View File

@@ -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 */
}

+ 31
- 8
src/auth/index.js View File

@@ -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;
});


+ 51
- 20
src/auth/utils.js View File

@@ -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`;
}

+ 26
- 11
src/components/AdminLogo/index.js View File

@@ -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>

);
};


+ 16
- 4
src/components/AutoLogoutProvider.js View File

@@ -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();


+ 207
- 56
src/components/FiDataGrid.js View File

@@ -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>
);
}

+ 10
- 0
src/components/FileList.js View File

@@ -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}
/>]
},
},


+ 98
- 64
src/components/I18nProvider.js View File

@@ -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;

+ 4
- 0
src/components/Logo/index.js View File

@@ -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 />


+ 21
- 8
src/components/MobileLogo/index.js View File

@@ -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>
);
};



+ 3
- 1
src/components/RefreshTokenProvider.js View File

@@ -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;
});


+ 36
- 0
src/components/SysSettingProvider.js View File

@@ -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};

+ 35
- 19
src/components/cards/AuthFooter.js View File

@@ -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 &copy; <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>
);
};


+ 3
- 2
src/components/iAmSmartButton.js View File

@@ -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>


+ 42
- 0
src/components/usePageTitle.js View File

@@ -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]);
}

+ 34
- 4
src/index.js View File

@@ -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>
);


+ 3
- 1
src/layout/MainLayout/Drawer/index.js View File

@@ -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}


+ 28
- 13
src/layout/MainLayout/Header/HeaderContent/LocaleSelector.js View File

@@ -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


+ 20
- 13
src/layout/MainLayout/Header/HeaderContent/MobileSection.js View File

@@ -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"


+ 16
- 6
src/layout/MainLayout/Header/HeaderContent/Notification.js View File

@@ -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}


+ 4
- 2
src/layout/MainLayout/Header/HeaderContent/Profile/index.js View File

@@ -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',


+ 838
- 540
src/layout/MainLayout/Header/index.js
File diff suppressed because it is too large
View File


+ 23
- 13
src/layout/MainLayout/index.js View File

@@ -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 />
)}
</>
);
};



+ 9
- 3
src/pages/Announcement/Search/DataGrid.js View File

@@ -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>
);


+ 7
- 2
src/pages/Announcement/Search/SearchForm.js View File

@@ -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>


+ 26
- 5
src/pages/Announcement/Search/index.js View File

@@ -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>


+ 10
- 4
src/pages/Announcement/Search_Public/DataGrid.js View File

@@ -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>
);


+ 28
- 5
src/pages/Announcement/Search_Public/SearchForm.js View File

@@ -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>


+ 29
- 7
src/pages/Announcement/Search_Public/index.js View File

@@ -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>


+ 14
- 9
src/pages/AuditLog/AuditLogSearchForm.js View File

@@ -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>


+ 10
- 4
src/pages/AuditLog/AuditLogTable.js View File

@@ -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>
);


+ 10
- 1
src/pages/AuditLog/index.js View File

@@ -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>


+ 17
- 3
src/pages/DemandNote/Create/SearchForm.js View File

@@ -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 >


+ 8
- 4
src/pages/DemandNote/Details/ApplicationDetailCard.js View File

@@ -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">


+ 6
- 4
src/pages/DemandNote/Details/DnDetailCard.js View File

@@ -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' }));
}
});
};


+ 15
- 9
src/pages/DemandNote/Export/DataGrid.js View File

@@ -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) => {


+ 11
- 0
src/pages/DemandNote/Export/SearchForm.js View File

@@ -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>


+ 93
- 19
src/pages/DemandNote/Search/DataGrid.js View File

@@ -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}


+ 77
- 6
src/pages/DemandNote/Search/SearchForm.js View File

@@ -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>


+ 21
- 4
src/pages/DemandNote/Search/index.js View File

@@ -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>


+ 14
- 6
src/pages/DemandNote/Search_Public/DataGrid.js View File

@@ -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>


+ 43
- 5
src/pages/DemandNote/Search_Public/SearchForm.js View File

@@ -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>


+ 25
- 7
src/pages/DemandNote/Search_Public/index.js View File

@@ -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>


+ 3
- 3
src/pages/EmailTemplate/Detail_GLD/index.js View File

@@ -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');


+ 8
- 4
src/pages/EmailTemplate/Search_GLD/DataGrid.js View File

@@ -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>
);

+ 22
- 14
src/pages/GFMIS/DataGrid.js View File

@@ -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>
);


+ 109
- 37
src/pages/GFMIS/SearchForm.js View File

@@ -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>


+ 156
- 0
src/pages/GFMIS/TransactionDataGrid.js View File

@@ -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>
);
}

+ 180
- 14
src/pages/GFMIS/index.js View File

@@ -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>
);
};


+ 8
- 3
src/pages/GazetteIssue/DataGrid.js View File

@@ -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>
);

+ 5
- 5
src/pages/GazetteIssue/ExportForm.js View File

@@ -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={""}/>}
/>


+ 5
- 4
src/pages/GazetteIssue/SearchForm.js View File

@@ -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>


+ 112
- 100
src/pages/GazetteIssue/index.js View File

@@ -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;

+ 7
- 2
src/pages/Holiday/DataGrid.js View File

@@ -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>
);

+ 5
- 4
src/pages/Holiday/SearchForm.js View File

@@ -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>


+ 111
- 109
src/pages/Holiday/index.js View File

@@ -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;

+ 187
- 0
src/pages/JVM/index.js View File

@@ -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 &quot;Notification Queue Status&quot; to load data.</Typography>
)}
</Paper>
</Grid>
</Grid>
);
};

export default JVMDefault;

+ 7
- 3
src/pages/Message/Details/index.js View File

@@ -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"


+ 38
- 22
src/pages/Message/Search/DataGrid.js View File

@@ -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>
);


+ 9
- 4
src/pages/Message/Search/SearchForm.js View File

@@ -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"/>


+ 49
- 26
src/pages/Message/Search/index.js View File

@@ -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>


+ 143
- 86
src/pages/Organization/DetailPage/OrganizationCard.js View File

@@ -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&#47;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>
);
};


+ 51
- 55
src/pages/Organization/DetailPage/OrganizationPubCard.js View File

@@ -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>


+ 6
- 2
src/pages/Organization/DetailPage/index.js View File

@@ -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>
}


+ 15
- 13
src/pages/Organization/DetailPage_FromUser/OrganizationCard_loadFromUser.js View File

@@ -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>


+ 4
- 0
src/pages/Organization/DetailPage_FromUser/index.js View File

@@ -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);


+ 35
- 4
src/pages/Organization/SearchPage/OrganizationSearchForm.js View File

@@ -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>


+ 10
- 4
src/pages/Organization/SearchPage/OrganizationTable.js View File

@@ -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>
);


+ 27
- 2
src/pages/Organization/SearchPage/index.js View File

@@ -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>


+ 1
- 1
src/pages/Payment/Details_GLD/DataGrid.js View File

@@ -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()&&params.row.careOf!=null&&params.row.careOf!=""?<>{params.row.careOf}<br /></>:null}
App No: {appNo}<br/>


+ 10
- 10
src/pages/Payment/Details_GLD/PaymentDetails.js View File

@@ -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>


+ 1
- 1
src/pages/Payment/Details_GLD/index.js View File

@@ -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);


+ 1
- 1
src/pages/Payment/Details_Public/DataGrid.js View File

@@ -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) : "";


+ 43
- 20
src/pages/Payment/Details_Public/PaymentDetails.js View File

@@ -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>


+ 10
- 7
src/pages/Payment/Details_Public/index.js View File

@@ -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>


+ 38
- 26
src/pages/Payment/FPS/AckPage.js View File

@@ -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>


+ 129
- 68
src/pages/Payment/FPS/FPS.js View File

@@ -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"/>&nbsp;
{timeDownCount}&nbsp;
<FormattedMessage id="fpsQrcodeTitle5"/>
</>
}
</Typography>
}
<Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}>


+ 1
- 1
src/pages/Payment/FPS/FPSTest.js View File

@@ -312,7 +312,7 @@ const Index = () => {
<br/>
二維碼有效期限3分鐘
<br />
請在規定時間內完成付款流程
請在規定時間內完成付款流程
<br />
{"剩餘時間:"+timeDownCount}
</Typography>


+ 50
- 40
src/pages/Payment/FPS/fpscallback.js View File

@@ -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>


+ 7
- 7
src/pages/Payment/MultiPaymentWindow.js View File

@@ -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$):&nbsp;
<Typography variant="h5" sx={{ color: "#000000", textAlign: "left" }}>
<FormattedMessage id="payTotal"/>&nbsp;($):&nbsp;
</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>


+ 24
- 14
src/pages/Payment/PaymentCallback.js View File

@@ -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>


+ 104
- 8
src/pages/Payment/Search_GLD/DataGrid.js View File

@@ -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>
);
}

+ 89
- 2
src/pages/Payment/Search_GLD/SearchForm.js View File

@@ -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>


+ 26
- 6
src/pages/Payment/Search_GLD/index.js View File

@@ -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>


+ 10
- 4
src/pages/Payment/Search_Public/DataGrid.js View File

@@ -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>
);


+ 22
- 5
src/pages/Payment/Search_Public/SearchForm.js View File

@@ -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>


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save