diff --git a/src/pages/GFMIS/index.js b/src/pages/GFMIS/index.js index 3bca046..2dc8b02 100644 --- a/src/pages/GFMIS/index.js +++ b/src/pages/GFMIS/index.js @@ -4,6 +4,7 @@ import { Typography, Stack, Button, + CircularProgress, Dialog, DialogTitle, DialogContent, DialogActions, } from '@mui/material'; import MainCard from "components/MainCard"; @@ -55,6 +56,8 @@ const Index = () => { const [isPreviewLoading, setIsPreviewLoading] = React.useState(false); const [isPopUp, setIsPopUp] = React.useState(false); + const [isXmlDialogSubmitting, setIsXmlDialogSubmitting] = React.useState(false); + const xmlDownloadInFlightRef = React.useRef(false); const [downloadInput, setDownloadInput] = React.useState(); const [selectedIds, setSelectedIds] = React.useState([]); @@ -66,6 +69,13 @@ const Index = () => { setInputDateValue(inputDate); }, [inputDate]); + React.useEffect(() => { + if (!isPopUp) { + xmlDownloadInFlightRef.current = false; + setIsXmlDialogSubmitting(false); + } + }, [isPopUp]); + React.useEffect(() => { setOnReady(true); }, [searchCriteria]); @@ -94,66 +104,61 @@ const Index = () => { function downloadXML() { - console.log(selectedIds.join(',')) - setIsPopUp(false) + if (xmlDownloadInFlightRef.current) return; + xmlDownloadInFlightRef.current = true; + console.log(selectedIds.join(',')); + setIsXmlDialogSubmitting(true); let sentDateFrom = ""; - + if (inputDateValue != "dd / mm / yyyy") { - sentDateFrom = DateUtils.dateValue(inputDateValue) + sentDateFrom = DateUtils.dateValue(inputDateValue); } HttpUtils.get({ url: GEN_GFMIS_XML + "/today", - params:{ + params: { dateTo: downloadInput.dateTo, dateFrom: downloadInput.dateFrom, inputDate: sentDateFrom, paymentId: selectedIds.join(',') }, onSuccess: (responseData) => { - // console.log(responseData) const parser = new DOMParser(); const xmlDoc = parser.parseFromString(responseData, 'application/xml'); - // Get the DCBHeader element const dcbHeader = xmlDoc.querySelector("DCBHeader"); - - // Get the Receipt and Allocation elements + const receiptElement = dcbHeader.querySelector("Receipt"); const allocationElement = dcbHeader.querySelector("Allocation"); const paymentMethodElements = Array.from(dcbHeader.querySelectorAll("PaymentMethod")); - // Remove existing elements from DCBHeader dcbHeader.innerHTML = ""; - + dcbHeader.appendChild(receiptElement); dcbHeader.appendChild(allocationElement); - + if (paymentMethodElements) { paymentMethodElements.forEach((paymentMethodElement) => { - dcbHeader.appendChild(paymentMethodElement); - }); + dcbHeader.appendChild(paymentMethodElement); + }); } - + const updatedXmlString = new XMLSerializer().serializeToString(xmlDoc); const filename = xmlDoc.querySelector('FileHeader').getAttribute('H_Filename'); - // console.log(updatedXmlString) const blob = new Blob([updatedXmlString], { type: 'application/xml' }); - // Create a download link const link = document.createElement('a'); link.href = URL.createObjectURL(blob); - link.download = filename+'.xml'; - - // Append the link to the document body + link.download = filename + '.xml'; + document.body.appendChild(link); - - // Programmatically click the link to trigger the download link.click(); - - // Clean up the link document.body.removeChild(link); + setIsPopUp(false); + }, + onFinally: () => { + xmlDownloadInFlightRef.current = false; + setIsXmlDialogSubmitting(false); } - }); - // open(UrlUtils.GEN_GFMIS_XML + "/today?online=true") - } + }); + } function applySearch(input) { @@ -263,7 +268,11 @@ const Index = () => { setIsPopUp(false)} + onClose={() => { + if (isXmlDialogSubmitting) return; + setIsPopUp(false); + }} + disableEscapeKeyDown={isXmlDialogSubmitting} PaperProps={{ sx: { minWidth: '40vw', @@ -300,8 +309,22 @@ const Index = () => { - - + + diff --git a/src/pages/Proof/Reply_GLD/ApplicationDetails.js b/src/pages/Proof/Reply_GLD/ApplicationDetails.js index c241b75..04325b1 100644 --- a/src/pages/Proof/Reply_GLD/ApplicationDetails.js +++ b/src/pages/Proof/Reply_GLD/ApplicationDetails.js @@ -8,12 +8,13 @@ import { Button, Stack, Dialog, DialogTitle, DialogContent, DialogActions, + CircularProgress, } from '@mui/material'; import { useFormik } from 'formik'; import { useIntl } from 'react-intl'; import {isGranted} from "auth/utils"; -import {useState,useEffect,lazy} from "react"; +import {useState, useEffect, useRef, lazy} from "react"; import * as HttpUtils from "utils/HttpUtils" import * as UrlUtils from "utils/ApiPathConst" import * as DateUtils from "utils/DateUtils" @@ -36,6 +37,8 @@ const ApplicationDetailCard = ({ const [data, setData] = useState({}); const [cancelPopUp, setCancelPopUp] = useState(false); + const [cancelLoading, setCancelLoading] = useState(false); + const cancellingRef = useRef(false); const [onDownload, setOnDownload] = useState(false); const [alertMsg, setAlertMsg] = useState(''); const [showAlert, setShowAlert] = useState(false); @@ -98,17 +101,30 @@ const ApplicationDetailCard = ({ } const confirmCancel = () => { - setCancelPopUp(false); + if (cancellingRef.current) return; + cancellingRef.current = true; + setCancelLoading(true); HttpUtils.get({ url: UrlUtils.CANCEL_PROOF + "/" + params.id, onSuccess: function (responseData) { + cancellingRef.current = false; + setCancelLoading(false); if (responseData && responseData.success === false) { + setCancelPopUp(false); const msg = responseData.msg ? intl.formatMessage({ id: responseData.msg }) : intl.formatMessage({ id: 'proofAlreadyCancelled' }); setAlertMsg(msg); setShowAlert(true); } else { window.location.reload(false); } + }, + onFail: () => { + cancellingRef.current = false; + setCancelLoading(false); + }, + onError: () => { + cancellingRef.current = false; + setCancelLoading(false); } }); } @@ -348,15 +364,24 @@ const ApplicationDetailCard = ({
setCancelPopUp(false)} + onClose={() => { + if (cancelLoading) return; + setCancelPopUp(false); + }} > Confirm Are you sure you want to cancel this proof? - - + + setShowAlert(false)}> diff --git a/src/pages/PublicNotice/Details_GLD/ApplicationDetailCard.js b/src/pages/PublicNotice/Details_GLD/ApplicationDetailCard.js index ec48a37..1012d51 100644 --- a/src/pages/PublicNotice/Details_GLD/ApplicationDetailCard.js +++ b/src/pages/PublicNotice/Details_GLD/ApplicationDetailCard.js @@ -9,6 +9,7 @@ import { Stack, Dialog, DialogTitle, DialogContent, DialogActions, InputAdornment, Autocomplete } from '@mui/material'; +import LoadingButton from '@mui/lab/LoadingButton'; import { isGranted, delBugMode, getPaymentMethodGLD} from "auth/utils"; const MainCard = Loadable(lazy(() => import('components/MainCard'))); import { useForm } from "react-hook-form"; @@ -37,14 +38,42 @@ import { notifyActionError } from 'utils/CommonFunction'; import { isGrantedAny } from "auth/utils"; import { useIntl } from "react-intl"; +/** Contained buttons with custom bg must restyle disabled/loading or they stay green/orange. */ +const publishWithdrawLoadingSx = (mainBg, hoverBg) => (theme) => ({ + textTransform: 'capitalize', + alignItems: 'end', + backgroundColor: mainBg, + color: '#fff', + '&:hover:not(.Mui-disabled):not(.MuiLoadingButton-loading)': { + backgroundColor: hoverBg, + }, + '&.Mui-disabled, &.MuiLoadingButton-loading': { + backgroundColor: theme.palette.action.disabledBackground, + color: theme.palette.action.disabled, + }, + '&.Mui-disabled .MuiSvgIcon-root, &.MuiLoadingButton-loading .MuiSvgIcon-root': { + color: theme.palette.action.disabled, + }, + '&.Mui-disabled .MuiTypography-root, &.MuiLoadingButton-loading .MuiTypography-root': { + color: `${theme.palette.action.disabled} !important`, + }, +}); + // ==============================|| DASHBOARD - DEFAULT ||============================== // const ApplicationDetailCard = ( { applicationDetailData, setStatus, - setUploadStatus + setUploadStatus, + statusDialogOpen = false, + statusDialogKind = "", + statusActionLoading = false, } ) => { + const publishWithdrawBusy = + statusActionLoading || + (statusDialogOpen && (statusDialogKind === 'publish' || statusDialogKind === 'withdraw')); + const [currentApplicationDetailData, setCurrentApplicationDetailData] = useState({}); const [companyName, setCompanyName] = useState({}); const [orgDetail, setOrgDetail] = useState({}); @@ -168,10 +197,12 @@ const ApplicationDetailCard = ( }; const withdrawnClick = () => () => { + if (publishWithdrawBusy) return; setStatus("withdraw") }; const doPublish = () => () => { + if (publishWithdrawBusy) return; setStatus("publish") } @@ -334,31 +365,26 @@ const ApplicationDetailCard = ( : (currentApplicationDetailData.status == "confirmed" && currentApplicationDetailData.creditor == 1) ? <> - - + : ( @@ -392,18 +418,16 @@ const ApplicationDetailCard = ( Publish - + : null ) } diff --git a/src/pages/PublicNotice/Details_GLD/StatusChangeDialog.js b/src/pages/PublicNotice/Details_GLD/StatusChangeDialog.js index f32d8ed..bdd5488 100644 --- a/src/pages/PublicNotice/Details_GLD/StatusChangeDialog.js +++ b/src/pages/PublicNotice/Details_GLD/StatusChangeDialog.js @@ -1,6 +1,7 @@ import { useEffect, - useState + useState, + useRef } from "react"; // material-ui @@ -16,8 +17,9 @@ import { FormLabel, Autocomplete, TextField, - Grid + Grid, } from '@mui/material'; +import LoadingButton from '@mui/lab/LoadingButton'; import * as ComboData from "utils/ComboData"; import { useFormik, FormikProvider } from 'formik'; @@ -30,8 +32,15 @@ const StatusChangeDialog = (props) => { const [remarks, setRemarks] = useState(""); const [helperText, setHelperText] = useState(""); const [comboInputValue, setComboInputValue] = useState({}); + const [positiveSubmitting, setPositiveSubmitting] = useState(false); + const positiveOnceRef = useRef(false); const groupTitleComboList = ComboData.groupTitle; + const confirmLoading = Boolean(props.confirmLoading) || positiveSubmitting; + const gazetteGroupMissing = + props.getStatus === "genGazetteCode" && + Object.keys(props.selectedGazetteGroup ?? {}).length === 0; + useEffect(() => { setComboInputValue({}); if (props.getStatus == "genGazetteCode") { @@ -58,22 +67,40 @@ const StatusChangeDialog = (props) => { } }, [props.getStatus]); + useEffect(() => { + if (!props.open) { + positiveOnceRef.current = false; + setPositiveSubmitting(false); + } + }, [props.open]); + + const wasConfirmLoadingRef = useRef(false); + useEffect(() => { + if (wasConfirmLoadingRef.current && !props.confirmLoading) { + positiveOnceRef.current = false; + setPositiveSubmitting(false); + } + wasConfirmLoadingRef.current = Boolean(props.confirmLoading); + }, [props.confirmLoading]); + const acceptedHandle = () => () => { - const getStatus = props.getStatus.status; - if (getStatus == "notAccepted") { - if (!remarks || remarks == "") + if (confirmLoading) return; + if (positiveOnceRef.current) return; + + const statusKey = props.getStatus; + + if (statusKey === "notAccepted" || statusKey === "resubmit") { + if (!remarks || remarks === "") { setHelperText("Please enter reason"); - } - if (!helperText) { - props.setReason({ "reason": remarks }); - if (remarks != null && remarks != "") { - // console.log(remarks) - // props.setStatusWindowAccepted(true); + return; } + setHelperText(""); + props.setReason({ "reason": remarks }); } - if (getStatus != "notAccepted") { - props.setStatusWindowAccepted(true); - } + + positiveOnceRef.current = true; + setPositiveSubmitting(true); + props.setStatusWindowAccepted(true); }; @@ -181,7 +208,10 @@ const StatusChangeDialog = (props) => { return ( { + if (confirmLoading) return; + props.handleClose(); + }} fullWidth={true} maxWidth={'md'} > @@ -207,18 +237,24 @@ const StatusChangeDialog = (props) => { - - + diff --git a/src/pages/PublicNotice/Details_GLD/index.js b/src/pages/PublicNotice/Details_GLD/index.js index 2eba8ec..394803c 100644 --- a/src/pages/PublicNotice/Details_GLD/index.js +++ b/src/pages/PublicNotice/Details_GLD/index.js @@ -70,6 +70,7 @@ const PublicNoticeDetail_GLD = () => { const [open, setOpen] = useState(false); const [getStatus, setStatus] = useState(""); const [statusWindowAccepted, setStatusWindowAccepted] = useState(false); + const [statusConfirmLoading, setStatusConfirmLoading] = useState(false); const [selectedGazetteGroup, setSelectedGazetteGroup] = useState({}); const [selectedGazetteGroupInputType, setSelectedGazetteGroupInputType] = useState(""); const [getReason, setReason] = useState({}); @@ -182,24 +183,30 @@ const PublicNoticeDetail_GLD = () => { }; useEffect(() => { - if (statusWindowAccepted) { - if (getStatus == "genGazetteCode") { - onAcceptedClick() - } else if (getStatus == "complete") { - onComplatedClick() - } else if (getStatus == "withdraw") { - onWithdrawnClick() - } else if (getStatus == "notAccepted") { - onNotAcceptClick(getReason); - } else if (getStatus == "resubmit") { - onReSubmitClick(getReason); - } else if (getStatus == "publish") { - onPublishClick(); - } else if (getStatus == "revoke") { - onRevokeClick(); - } else if(getStatus == "paid"){ - onPaidClick(); - } + if (!statusWindowAccepted) { + setStatusConfirmLoading(false); + return; + } + setStatusConfirmLoading(true); + if (getStatus == "genGazetteCode") { + onAcceptedClick() + } else if (getStatus == "complete") { + onComplatedClick() + } else if (getStatus == "withdraw") { + onWithdrawnClick() + } else if (getStatus == "notAccepted") { + onNotAcceptClick(getReason); + } else if (getStatus == "resubmit") { + onReSubmitClick(getReason); + } else if (getStatus == "publish") { + onPublishClick(); + } else if (getStatus == "revoke") { + onRevokeClick(); + } else if(getStatus == "paid"){ + onPaidClick(); + } else { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); } }, [statusWindowAccepted]); @@ -223,12 +230,23 @@ const PublicNoticeDetail_GLD = () => { .catch(error => { console.log(error); return false; + }) + .finally(() => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); }); + } else { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); } }; const onNotAcceptClick = (reason) => { - if (params.id <= 0) return; + if (params.id <= 0) { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); + return; + } HttpUtils.post({ url: `${SET_PUBLIC_NOTICE_STATUS_NOT_ACCEPT}/${params.id}`, params: reason, @@ -238,12 +256,24 @@ const PublicNoticeDetail_GLD = () => { // location.reload(); loadApplicationDetail() notifySaveSuccess() + }, + onFail: () => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); + }, + onError: () => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); } }); } const onPublishClick = () => { - if (params.id <= 0) return; + if (params.id <= 0) { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); + return; + } HttpUtils.get({ url: `${SET_PUBLIC_NOTICE_STATUS_PUBLISH}/${params.id}`, onSuccess: function () { @@ -251,6 +281,18 @@ const PublicNoticeDetail_GLD = () => { handleClose(); loadApplicationDetail() notifySaveSuccess() + }, + onFail: () => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); + }, + onError: () => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); + }, + onFinally: () => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); } }); } @@ -270,7 +312,14 @@ const PublicNoticeDetail_GLD = () => { .catch(error => { console.log(error); return false; + }) + .finally(() => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); }); + } else { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); } }; @@ -289,7 +338,14 @@ const PublicNoticeDetail_GLD = () => { .catch(error => { console.log(error); return false; + }) + .finally(() => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); }); + } else { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); } }; @@ -307,7 +363,14 @@ const PublicNoticeDetail_GLD = () => { .catch(error => { console.log(error); return false; + }) + .finally(() => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); }); + } else { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); } }; @@ -322,6 +385,14 @@ const PublicNoticeDetail_GLD = () => { // location.reload(); loadApplicationDetail() notifySaveSuccess() + }, + onFail: () => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); + }, + onError: () => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); } }); // axios.post(`${SET_PUBLIC_NOTICE_STATUS_RESUBMIT}/${params.id}`) @@ -338,11 +409,18 @@ const PublicNoticeDetail_GLD = () => { // console.log(error); // return false; // }); + } else { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); } }; const onRevokeClick = () => { - if (params.id <= 0) return; + if (params.id <= 0) { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); + return; + } HttpUtils.get({ url: `${SET_PUBLIC_NOTICE_STATUS_REVOKE}/${params.id}`, onSuccess: function () { @@ -350,6 +428,18 @@ const PublicNoticeDetail_GLD = () => { handleClose(); loadApplicationDetail() notifySaveSuccess() + }, + onFail: () => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); + }, + onError: () => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); + }, + onFinally: () => { + setStatusConfirmLoading(false); + setStatusWindowAccepted(false); } }); } @@ -383,6 +473,7 @@ const PublicNoticeDetail_GLD = () => { issueDate={issueDate} issueNum={issueNum} gazetteIssue={gazetteIssue} + confirmLoading={statusConfirmLoading} //combo value selectedGazetteGroup={selectedGazetteGroup} setSelectedGazetteGroup={setSelectedGazetteGroup} @@ -425,6 +516,9 @@ const PublicNoticeDetail_GLD = () => { setUpdateApplicationObject={setUpdateApplicationObject} isEditMode={isEditMode} setiIsSave={setiIsSave} + statusDialogOpen={open} + statusDialogKind={getStatus} + statusActionLoading={statusConfirmLoading} // isNewRecord={isNewRecord} /> } diff --git a/src/pages/PublicNotice/Details_Public/StatusChangeDialog.js b/src/pages/PublicNotice/Details_Public/StatusChangeDialog.js index 82f7b7b..d3c5ffa 100644 --- a/src/pages/PublicNotice/Details_Public/StatusChangeDialog.js +++ b/src/pages/PublicNotice/Details_Public/StatusChangeDialog.js @@ -1,6 +1,7 @@ import { useEffect, - useState + useState, + useRef } from "react"; // material-ui @@ -15,6 +16,7 @@ import { DialogContent, DialogContentText, DialogTitle, + CircularProgress, } from '@mui/material'; import { useFormik,FormikProvider } from 'formik'; import * as yup from 'yup'; @@ -26,6 +28,7 @@ import {useIntl} from "react-intl"; const StatusChangeDialog = (props) => { const [status, setStatus] = useState(""); const intl = useIntl(); + const confirmOnceRef = useRef(false); useEffect(() => { // console.log(Object.keys(!props.selectedGazetteGroup).length) @@ -33,9 +36,25 @@ const StatusChangeDialog = (props) => { setStatus(intl.formatMessage({id: 'cancel'})) } }, [props.getStatus]); + + useEffect(() => { + if (!props.open) { + confirmOnceRef.current = false; + } + }, [props.open]); + + const wasConfirmLoadingRef = useRef(false); + useEffect(() => { + if (wasConfirmLoadingRef.current && !props.confirmLoading) { + confirmOnceRef.current = false; + } + wasConfirmLoadingRef.current = props.confirmLoading; + }, [props.confirmLoading]); const acceptedHandle = () => () =>{ - // console.log(selectedGazetteGroup) + if (props.confirmLoading) return; + if (confirmOnceRef.current) return; + confirmOnceRef.current = true; props.setStatusWindowAccepted(true) }; @@ -86,6 +105,7 @@ const StatusChangeDialog = (props) => { onClick={props.handleClose} autoFocus color="cancel" + disabled={props.confirmLoading} > @@ -96,6 +116,8 @@ const StatusChangeDialog = (props) => { color="save" onClick={acceptedHandle()} autoFocus + disabled={props.confirmLoading} + startIcon={props.confirmLoading ? : null} > diff --git a/src/pages/PublicNotice/Details_Public/index.js b/src/pages/PublicNotice/Details_Public/index.js index 4ed3305..a5bb0ec 100644 --- a/src/pages/PublicNotice/Details_Public/index.js +++ b/src/pages/PublicNotice/Details_Public/index.js @@ -1,6 +1,7 @@ import React, { useEffect, useState, + useRef, lazy } from "react"; @@ -49,6 +50,8 @@ const DashboardDefault = () => { const [open, setOpen] = useState(false); const [getStatus, setStatus] = useState(""); const [statusWindowAccepted, setStatusWindowAccepted] = useState(false); + const [cancelLoading, setCancelLoading] = useState(false); + const cancellingRef = useRef(false); const [proofCount, setProofCount] = useState(0); const [paymentCount, setPaymentCount] = useState(0); @@ -137,7 +140,10 @@ const DashboardDefault = () => { }, [getStatus]); const onCancelledClick = () => { + if (cancellingRef.current) return; if (params.id > 0) { + cancellingRef.current = true; + setCancelLoading(true); axios.get(`${SET_PUBLIC_NOTICE_STATUS_CANCELLED}/${params.id}`) .then((response) => { if (response.status === 204) { @@ -151,13 +157,23 @@ const DashboardDefault = () => { .catch(error => { console.log(error); return false; + }) + .finally(() => { + cancellingRef.current = false; + setCancelLoading(false); }); } }; return ( - +
diff --git a/src/utils/HttpUtils.js b/src/utils/HttpUtils.js index 9b89f3f..b8cd605 100644 --- a/src/utils/HttpUtils.js +++ b/src/utils/HttpUtils.js @@ -2,7 +2,7 @@ import axios from "axios"; import { FILE_UP_POST, FILE_DOWN_GET } from "../utils/ApiPathConst"; import qs from 'qs'; -export const get = ({ url, params, onSuccess, onFail, onError }) => { +export const get = ({ url, params, onSuccess, onFail, onError, onFinally }) => { axios.get(url, { params, paramsSerializer: (p) => qs.stringify(p, { arrayFormat: 'repeat' }) // <-- FIX @@ -10,6 +10,10 @@ export const get = ({ url, params, onSuccess, onFail, onError }) => { (response) => { onResponse(response, onSuccess, onFail); } ).catch((error) => { return handleError(error, onError); + }).finally(() => { + if (typeof onFinally === 'function') { + onFinally(); + } }); };