| @@ -0,0 +1,130 @@ | |||||
| import { | |||||
| Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography | |||||
| } from '@mui/material'; | |||||
| import { FormattedMessage, useIntl } from "react-intl"; | |||||
| import { useNavigate } from "react-router-dom"; | |||||
| import SafeHtml from "components/SafeHtml"; | |||||
| import * as DateUtils from "utils/DateUtils"; | |||||
| import { GREY_CONTAINED_BUTTON_SX, HEADER_BACKGROUND_COLOR, PRIMARY_CONTAINED_BUTTON_SX } from "themes/colorConst"; | |||||
| const TITLE_IDS = { | |||||
| expiring: "brPopupTitleExpiring", | |||||
| expired: "brPopupTitleExpired", | |||||
| pending: "brPopupTitlePendingVerify", | |||||
| submitNotExpired: "brPopupTitlePendingVerify", | |||||
| submitExpired: "brPopupTitlePendingVerify", | |||||
| }; | |||||
| const MESSAGE_IDS = { | |||||
| expiring: "brPopupExpiring", | |||||
| expired: "brPopupExpired", | |||||
| pending: "brPopupPendingVerify", | |||||
| submitNotExpired: "brSubmitSuccessNotExpired", | |||||
| submitExpired: "brSubmitSuccessExpired", | |||||
| }; | |||||
| const TITLE_PREFIX = /^(?:<strong>)?\s*(Reminder|Action Required(?:\s*:[^<]*)?|Notice|【\s*溫馨提示\s*】|【\s*温馨提示\s*】|系統提示(?:[::][^<]*)?|系统提示(?:[::][^<]*)?)\s*(?:<\/strong>)?\s*[::]?\s*/i; | |||||
| function stripTags(html) { | |||||
| return (html || "") | |||||
| .replace(/<[^>]+>/g, " ") | |||||
| .replace(/ /gi, " ") | |||||
| .replace(/\s+/g, " ") | |||||
| .trim(); | |||||
| } | |||||
| function splitTitleFromHtml(html, fallbackTitle) { | |||||
| const raw = (html || "").trim(); | |||||
| if (!raw) { | |||||
| return { title: fallbackTitle, bodyHtml: "" }; | |||||
| } | |||||
| const inner = raw | |||||
| .replace(/^<p[^>]*>/i, "") | |||||
| .replace(/<\/p>\s*$/i, "") | |||||
| .trim(); | |||||
| const brMatch = inner.match(/<br\s*\/?>/i); | |||||
| if (brMatch && brMatch.index >= 0) { | |||||
| const title = stripTags(inner.slice(0, brMatch.index)).replace(/[::]\s*$/, "").trim(); | |||||
| const body = inner.slice(brMatch.index + brMatch[0].length).trim(); | |||||
| return { | |||||
| title: title || fallbackTitle, | |||||
| bodyHtml: body ? `<p>${body}</p>` : "" | |||||
| }; | |||||
| } | |||||
| const prefix = inner.match(TITLE_PREFIX); | |||||
| if (prefix) { | |||||
| const title = stripTags(prefix[1]).replace(/[::]\s*$/, "").trim(); | |||||
| const rest = inner.slice(prefix[0].length).trim(); | |||||
| return { | |||||
| title: title || fallbackTitle, | |||||
| bodyHtml: rest ? `<p>${rest}</p>` : raw | |||||
| }; | |||||
| } | |||||
| return { title: fallbackTitle, bodyHtml: raw.startsWith("<") ? raw : `<p>${raw}</p>` }; | |||||
| } | |||||
| export default function BrStatusDialog({ open, variant, expiryDate, onClose }) { | |||||
| const intl = useIntl(); | |||||
| const navigate = useNavigate(); | |||||
| const messageId = MESSAGE_IDS[variant] || MESSAGE_IDS.expiring; | |||||
| const titleId = TITLE_IDS[variant] || TITLE_IDS.expiring; | |||||
| const formattedDate = expiryDate ? DateUtils.dateStr(expiryDate) : ""; | |||||
| const html = (intl.formatMessage({ id: messageId, defaultMessage: "" }) || "") | |||||
| .replaceAll("[BR_EXPIRY_DATE]", formattedDate); | |||||
| const fallbackTitle = intl.formatMessage({ id: titleId, defaultMessage: "" }); | |||||
| const { title, bodyHtml } = splitTitleFromHtml(html, fallbackTitle); | |||||
| const showGo = variant === "expiring" || variant === "expired"; | |||||
| const goToSubmit = () => { | |||||
| if (onClose) { | |||||
| onClose(); | |||||
| } | |||||
| navigate("/org/submit-br"); | |||||
| }; | |||||
| return ( | |||||
| <Dialog | |||||
| open={Boolean(open && variant)} | |||||
| onClose={onClose} | |||||
| maxWidth={false} | |||||
| PaperProps={{ | |||||
| sx: { | |||||
| width: { xs: "90vw", md: "40vw" }, | |||||
| maxWidth: { xs: "90vw", md: "40vw" }, | |||||
| maxHeight: { xs: "90vh", md: "70vh" }, | |||||
| overflow: "hidden" | |||||
| } | |||||
| }} | |||||
| > | |||||
| <DialogTitle | |||||
| sx={{ | |||||
| bgcolor: HEADER_BACKGROUND_COLOR, | |||||
| color: "#FFFFFF", | |||||
| py: 1, | |||||
| px: 3 | |||||
| }} | |||||
| > | |||||
| <Typography component="h2" variant="h6" sx={{ color: "#FFFFFF", fontWeight: 600 }}> | |||||
| {title} | |||||
| </Typography> | |||||
| </DialogTitle> | |||||
| <DialogContent sx={{ px: 3, pt: 2, pb: 2, "&.MuiDialogContent-root": { paddingTop: 2 }, "& p:first-of-type": { mt: 0 } }}> | |||||
| <SafeHtml html={bodyHtml} /> | |||||
| </DialogContent> | |||||
| <DialogActions> | |||||
| {showGo ? | |||||
| <Button variant="contained" onClick={goToSubmit} sx={PRIMARY_CONTAINED_BUTTON_SX}> | |||||
| <Typography variant="h5"><FormattedMessage id="goToSubmitBr" /></Typography> | |||||
| </Button> | |||||
| : null} | |||||
| <Button variant="contained" onClick={onClose} sx={GREY_CONTAINED_BUTTON_SX}> | |||||
| <Typography variant="h5"><FormattedMessage id="close" /></Typography> | |||||
| </Button> | |||||
| </DialogActions> | |||||
| </Dialog> | |||||
| ); | |||||
| } | |||||
| @@ -22,8 +22,10 @@ import Loadable from 'components/Loadable'; | |||||
| import { notifySaveSuccess } from 'utils/CommonFunction'; | import { notifySaveSuccess } from 'utils/CommonFunction'; | ||||
| import { useIntl } from "react-intl"; | import { useIntl } from "react-intl"; | ||||
| import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | ||||
| import { GREY_CONTAINED_BUTTON_SX, PRIMARY_CONTAINED_BUTTON_SX } from "themes/colorConst"; | |||||
| import { ThemeProvider } from "@emotion/react"; | import { ThemeProvider } from "@emotion/react"; | ||||
| import { isGrantedAny } from "auth/utils"; | import { isGrantedAny } from "auth/utils"; | ||||
| import FileList from "components/FileList"; | |||||
| import { DatePicker } from "@mui/x-date-pickers/DatePicker"; | import { DatePicker } from "@mui/x-date-pickers/DatePicker"; | ||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| @@ -36,6 +38,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| const [creditorConfirmPopUp, setCreditorConfirmPopUp] = React.useState(false); | const [creditorConfirmPopUp, setCreditorConfirmPopUp] = React.useState(false); | ||||
| const [nonCreditorConfirmPopUp, setNonCreditorConfirmPopUp] = React.useState(false); | const [nonCreditorConfirmPopUp, setNonCreditorConfirmPopUp] = React.useState(false); | ||||
| const [afterSendPopUp, setAfterSendPopUp] = React.useState(false); | const [afterSendPopUp, setAfterSendPopUp] = React.useState(false); | ||||
| const [confirmBrPopUp, setConfirmBrPopUp] = React.useState(false); | |||||
| const [currentUserData, setCurrentUserData] = useState({}); | const [currentUserData, setCurrentUserData] = useState({}); | ||||
| const [overduePublicNotice, setOverduePublicNotice] = useState(0); | const [overduePublicNotice, setOverduePublicNotice] = useState(0); | ||||
| @@ -246,6 +249,17 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| }); | }); | ||||
| } | } | ||||
| const confirmNewBr = () => { | |||||
| setConfirmBrPopUp(false); | |||||
| HttpUtils.post({ | |||||
| url: UrlUtils.POST_ORG_CONFIRM_BR + "/" + id + "/confirm-br", | |||||
| onSuccess: () => { | |||||
| notifySaveSuccess(); | |||||
| loadDataFun(); | |||||
| } | |||||
| }); | |||||
| } | |||||
| return ( | return ( | ||||
| <MainCard elevation={0} | <MainCard elevation={0} | ||||
| border={false} | border={false} | ||||
| @@ -316,6 +330,19 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| </Button> | </Button> | ||||
| </ThemeProvider> | </ThemeProvider> | ||||
| </Grid> | </Grid> | ||||
| {currentUserData.newBrSubmitted ? | |||||
| <Grid item sx={{ ml: 3, mr: 3 }}> | |||||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||||
| <Button | |||||
| variant="contained" | |||||
| color="success" | |||||
| onClick={() => setConfirmBrPopUp(true)} | |||||
| > | |||||
| {intl.formatMessage({ id: "confirmNewBr" })} | |||||
| </Button> | |||||
| </ThemeProvider> | |||||
| </Grid> | |||||
| : null} | |||||
| { | { | ||||
| currentUserData.creditor ? | currentUserData.creditor ? | ||||
| @@ -324,7 +351,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | <ThemeProvider theme={PNSPS_BUTTON_THEME}> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| color="error" | |||||
| color="success" | |||||
| onClick={() => setNonCreditorConfirmPopUp(true)} | onClick={() => setNonCreditorConfirmPopUp(true)} | ||||
| > | > | ||||
| Mark as Non-Credit Client | Mark as Non-Credit Client | ||||
| @@ -338,7 +365,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | <ThemeProvider theme={PNSPS_BUTTON_THEME}> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| color="orange" | |||||
| color="success" | |||||
| onClick={() => setCreditorConfirmPopUp(true)} | onClick={() => setCreditorConfirmPopUp(true)} | ||||
| > | > | ||||
| Mark as Credit Client | Mark as Credit Client | ||||
| @@ -351,7 +378,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | <ThemeProvider theme={PNSPS_BUTTON_THEME}> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| color="primary" | |||||
| color="success" | |||||
| onClick={() => sendDn_Overdue(true)} | onClick={() => sendDn_Overdue(true)} | ||||
| > | > | ||||
| Generate O/S DN List | Generate O/S DN List | ||||
| @@ -440,10 +467,16 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| <TextField | <TextField | ||||
| fullWidth | fullWidth | ||||
| id="currentExDate" | id="currentExDate" | ||||
| // error={(fromDate===null)} | |||||
| // type="date" | |||||
| name="currentExDate" | name="currentExDate" | ||||
| value={fromDate != null ? DateUtils.dateStr(fromDate) : DateUtils.dateStr(currentFromDate)} | |||||
| value={(() => { | |||||
| const dateStr = fromDate != null ? DateUtils.dateStr(fromDate) : DateUtils.dateStr(currentFromDate); | |||||
| const statusLabel = currentUserData.brStatus === "Invalid" | |||||
| ? intl.formatMessage({ id: "brStatusInvalid" }) | |||||
| : currentUserData.brStatus === "Valid" | |||||
| ? intl.formatMessage({ id: "brStatusValid" }) | |||||
| : ""; | |||||
| return statusLabel ? `${dateStr} (${statusLabel})` : dateStr; | |||||
| })()} | |||||
| disabled={true} | disabled={true} | ||||
| /> : | /> : | ||||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | ||||
| @@ -480,9 +513,6 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| { | { | ||||
| fromDate == null ? | fromDate == null ? | ||||
| (!editMode && !createMode) ? | (!editMode && !createMode) ? | ||||
| // <FormHelperText error id="helper-text-date"> | |||||
| // Please select a date after today. | |||||
| // </FormHelperText> | |||||
| null | null | ||||
| : | : | ||||
| <FormHelperText error id="helper-text-date"> | <FormHelperText error id="helper-text-date"> | ||||
| @@ -575,6 +605,81 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| })} | })} | ||||
| </Grid> | </Grid> | ||||
| {currentUserData.newBrSubmitted ? | |||||
| <> | |||||
| <Grid item xs={12}> | |||||
| <Typography variant="h4" sx={{ mb: 2, mr: 3, mt: 2, borderBottom: "1px solid black" }}> | |||||
| {intl.formatMessage({ id: "newBrInformation" })} | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid item xs={12} lg={4}> | |||||
| {FieldUtils.getTextField({ | |||||
| label: FieldUtils.notNullFieldLabel("Name (Eng):"), | |||||
| valueName: "enCompanyNameTemp", | |||||
| disabled: true, | |||||
| form: formik | |||||
| })} | |||||
| </Grid> | |||||
| <Grid item xs={12} lg={4}> | |||||
| {FieldUtils.getTextField({ | |||||
| label: "Name (Ch):", | |||||
| valueName: "chCompanyNameTemp", | |||||
| disabled: true, | |||||
| form: formik | |||||
| })} | |||||
| </Grid> | |||||
| <Grid item xs={12} lg={4}> | |||||
| {FieldUtils.getTextField({ | |||||
| label: FieldUtils.notNullFieldLabel("Expiry Date:"), | |||||
| valueName: "brExpiryDateTemp", | |||||
| disabled: true, | |||||
| form: formik | |||||
| })} | |||||
| </Grid> | |||||
| <Grid item xs={12} lg={12}> | |||||
| {FieldUtils.getAddressField({ | |||||
| label: FieldUtils.notNullFieldLabel("Address:"), | |||||
| valueName: ["newAddressLine1", "newAddressLine2", "newAddressLine3"], | |||||
| disabled: true, | |||||
| form: formik | |||||
| })} | |||||
| </Grid> | |||||
| <Grid item xs={12} lg={12}> | |||||
| {FieldUtils.getProfileComboField({ | |||||
| label: "", | |||||
| valueName: "newDistrict", | |||||
| disabled: true, | |||||
| 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: "newCountry", | |||||
| disabled: true, | |||||
| dataList: ComboData.country, | |||||
| getOptionLabel: (option) => option.type ? intl.formatMessage({ id: option.type }) : "", | |||||
| form: formik | |||||
| })} | |||||
| </Grid> | |||||
| </> | |||||
| : null} | |||||
| {id > 0 ? | |||||
| <Grid item xs={12} sx={{ mt: 2 }}> | |||||
| <Typography variant="h4" sx={{ mb: 2, mr: 3, borderBottom: "1px solid black" }}> | |||||
| {intl.formatMessage({ id: "uploadedFiles" })} | |||||
| </Typography> | |||||
| <FileList | |||||
| key={`${id}-${currentUserData.newBrSubmitted}-${currentUserData.brExpiryDateTemp || ""}`} | |||||
| refType={"orgBrFile"} | |||||
| refId={id} | |||||
| /> | |||||
| </Grid> | |||||
| : null} | |||||
| <Grid item lg={12} ></Grid> | <Grid item lg={12} ></Grid> | ||||
| </Grid> | </Grid> | ||||
| @@ -668,6 +773,32 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| </DialogActions> | </DialogActions> | ||||
| </Dialog> | </Dialog> | ||||
| </div> | </div> | ||||
| <div> | |||||
| <Dialog | |||||
| open={confirmBrPopUp} | |||||
| onClose={() => setConfirmBrPopUp(false)} | |||||
| PaperProps={{ | |||||
| sx: { | |||||
| minWidth: '40vw', | |||||
| maxWidth: { xs: '90vw', md: '70vw', lg: '70vw' }, | |||||
| maxHeight: { xs: '90vh', md: '70vh', lg: '60vh' } | |||||
| } | |||||
| }} | |||||
| > | |||||
| <DialogTitle><Typography variant="h3">{intl.formatMessage({ id: "confirmNewBr" })}</Typography></DialogTitle> | |||||
| <DialogContent style={{ display: 'flex' }}> | |||||
| <Typography variant="h4" style={{ padding: '16px' }}>{intl.formatMessage({ id: "confirmNewBrMessage" })}</Typography> | |||||
| </DialogContent> | |||||
| <DialogActions> | |||||
| <Button variant="contained" onClick={() => setConfirmBrPopUp(false)} sx={GREY_CONTAINED_BUTTON_SX}> | |||||
| <Typography variant="h5">{intl.formatMessage({ id: "close" })}</Typography> | |||||
| </Button> | |||||
| <Button variant="contained" onClick={() => confirmNewBr()} sx={PRIMARY_CONTAINED_BUTTON_SX}> | |||||
| <Typography variant="h5">{intl.formatMessage({ id: "confirmNewBr" })}</Typography> | |||||
| </Button> | |||||
| </DialogActions> | |||||
| </Dialog> | |||||
| </div> | |||||
| </MainCard> | </MainCard> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -23,12 +23,14 @@ import { notifySaveSuccess } from 'utils/CommonFunction'; | |||||
| import { FormattedMessage, useIntl } from "react-intl"; | import { FormattedMessage, useIntl } from "react-intl"; | ||||
| import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | ||||
| import { ThemeProvider } from "@emotion/react"; | import { ThemeProvider } from "@emotion/react"; | ||||
| import { useNavigate } from "react-router-dom"; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | ||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const navigate = useNavigate(); | |||||
| const [creditorConfirmPopUp, setCreditorConfirmPopUp] = React.useState(false); | const [creditorConfirmPopUp, setCreditorConfirmPopUp] = React.useState(false); | ||||
| const [nonCreditorConfirmPopUp, setNonCreditorConfirmPopUp] = React.useState(false); | const [nonCreditorConfirmPopUp, setNonCreditorConfirmPopUp] = React.useState(false); | ||||
| @@ -200,6 +202,19 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| </Button> | </Button> | ||||
| </ThemeProvider> | </ThemeProvider> | ||||
| </Grid> | </Grid> | ||||
| {currentUserData.canSubmitBr ? | |||||
| <Grid item sx={{ ml: 3, mr: 3 }}> | |||||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||||
| <Button | |||||
| variant="contained" | |||||
| onClick={() => navigate("/org/submit-br")} | |||||
| color="success" | |||||
| > | |||||
| <FormattedMessage id="submitBrCertificate" /> | |||||
| </Button> | |||||
| </ThemeProvider> | |||||
| </Grid> | |||||
| : null} | |||||
| </> | </> | ||||
| } | } | ||||
| </Grid> | </Grid> | ||||
| @@ -235,13 +250,15 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => { | |||||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: 'expiryDate' }) + ":"), | label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: 'expiryDate' }) + ":"), | ||||
| valueName: "brExpiryDate", | valueName: "brExpiryDate", | ||||
| disabled: true, | disabled: true, | ||||
| form: formik | |||||
| form: formik, | |||||
| displayValue: (!editMode && !createMode && currentUserData.brStatus) | |||||
| ? `${formik.values.brExpiryDate || ""} (${currentUserData.brStatus === "Invalid" | |||||
| ? intl.formatMessage({ id: "brStatusInvalid" }) | |||||
| : intl.formatMessage({ id: "brStatusValid" })})`.trim() | |||||
| : undefined | |||||
| })} | })} | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12} lg={4} ></Grid> | |||||
| <Grid item xs={12} lg={4} > | <Grid item xs={12} lg={4} > | ||||
| {FieldUtils.getTextField({ | {FieldUtils.getTextField({ | ||||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: 'nameEng' }) + ":"), | label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: 'nameEng' }) + ":"), | ||||
| @@ -90,6 +90,12 @@ const OrganizationDetailPage = () => { | |||||
| response.data["brExpiryDate"] = response.data.brExpiryDate ? DateUtils.dateValue(response.data.brExpiryDate) : ""; | response.data["brExpiryDate"] = response.data.brExpiryDate ? DateUtils.dateValue(response.data.brExpiryDate) : ""; | ||||
| response.data["orgShortName"] = response.data.orgShortName ? response.data.orgShortName : "N/A" ; | response.data["orgShortName"] = response.data.orgShortName ? response.data.orgShortName : "N/A" ; | ||||
| response.data["newAddressLine1"] = response.data.newBrAddress?.addressLine1; | |||||
| response.data["newAddressLine2"] = response.data.newBrAddress?.addressLine2; | |||||
| response.data["newAddressLine3"] = response.data.newBrAddress?.addressLine3; | |||||
| response.data["newDistrict"] = getObjectByType(ComboData.district, "type", response.data.newBrAddress?.district); | |||||
| response.data["newCountry"] = getObjectByType(ComboData.country, "type", response.data.newBrAddress?.country); | |||||
| response.data["brExpiryDateTemp"] = response.data.brExpiryDateTemp ? DateUtils.dateStr(response.data.brExpiryDateTemp) : ""; | |||||
| setFormData(response.data) | setFormData(response.data) | ||||
| setList(response.historyList) | setList(response.historyList) | ||||
| } | } | ||||
| @@ -9,11 +9,13 @@ import { useNavigate } from "react-router-dom"; | |||||
| import * as DateUtils from "utils/DateUtils"; | import * as DateUtils from "utils/DateUtils"; | ||||
| import { clickableLink} from 'utils/CommonFunction'; | import { clickableLink} from 'utils/CommonFunction'; | ||||
| import {GET_ORG_PATH} from "utils/ApiPathConst"; | import {GET_ORG_PATH} from "utils/ApiPathConst"; | ||||
| import { useIntl } from "react-intl"; | |||||
| // ==============================|| EVENT TABLE ||============================== // | // ==============================|| EVENT TABLE ||============================== // | ||||
| export default function OrganizationTable({ searchCriteria, applyGridOnReady, applySearch}) { | export default function OrganizationTable({ searchCriteria, applyGridOnReady, applySearch}) { | ||||
| const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria); | const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria); | ||||
| const navigate = useNavigate() | const navigate = useNavigate() | ||||
| const intl = useIntl(); | |||||
| React.useEffect(() => { | React.useEffect(() => { | ||||
| set_searchCriteria(searchCriteria); | set_searchCriteria(searchCriteria); | ||||
| @@ -91,6 +93,34 @@ export default function OrganizationTable({ searchCriteria, applyGridOnReady, ap | |||||
| return DateUtils.dateValue(params?.value); | return DateUtils.dateValue(params?.value); | ||||
| } | } | ||||
| }, | }, | ||||
| { | |||||
| id: 'brStatus', | |||||
| field: 'brStatus', | |||||
| headerName: 'BR Status', | |||||
| flex: 1, | |||||
| minWidth: 110, | |||||
| valueGetter: (params) => { | |||||
| const status = params?.row?.brStatus; | |||||
| if (status === 'Invalid') { | |||||
| return intl.formatMessage({ id: 'brStatusInvalid' }); | |||||
| } | |||||
| if (status === 'Valid') { | |||||
| return intl.formatMessage({ id: 'brStatusValid' }); | |||||
| } | |||||
| return status || ''; | |||||
| } | |||||
| }, | |||||
| { | |||||
| id: 'newBrSubmitted', | |||||
| field: 'newBrSubmitted', | |||||
| headerName: intl.formatMessage({ id: 'newBr' }), | |||||
| width: 120, | |||||
| minWidth: 120, | |||||
| valueGetter: (params) => { | |||||
| const value = params?.value; | |||||
| return value === true || value === 1 || value === '1' ? intl.formatMessage({ id: 'newBr' }) : ''; | |||||
| } | |||||
| }, | |||||
| { | { | ||||
| id: 'creditor', | id: 'creditor', | ||||
| field: 'creditor', | field: 'creditor', | ||||
| @@ -0,0 +1,388 @@ | |||||
| import { Grid, Typography, Stack, Box, Button, FormHelperText, CircularProgress } from '@mui/material'; | |||||
| import * as React from "react"; | |||||
| import { useFormik } from 'formik'; | |||||
| import * as yup from 'yup'; | |||||
| import * as HttpUtils from "utils/HttpUtils"; | |||||
| import * as UrlUtils from "utils/ApiPathConst"; | |||||
| import * as DateUtils from "utils/DateUtils"; | |||||
| import * as FieldUtils from "utils/FieldUtils"; | |||||
| import * as ComboData from "utils/ComboData"; | |||||
| import { getObjectByType } from "utils/CommonFunction"; | |||||
| import { isORGLoggedIn, isPrimaryLoggedIn } from "utils/Utils"; | |||||
| import Loadable from "components/Loadable"; | |||||
| import { lazy } from "react"; | |||||
| import MainCard from "components/MainCard"; | |||||
| import ForwardIcon from "@mui/icons-material/Forward"; | |||||
| import { useNavigate } from "react-router-dom"; | |||||
| import titleBackgroundImg from "assets/images/dashboard/gazette-bar.png"; | |||||
| import { FormattedMessage, useIntl } from "react-intl"; | |||||
| import usePageTitle from "components/usePageTitle"; | |||||
| import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | |||||
| import { PRIMARY_CONTAINED_BUTTON_SX } from "themes/colorConst"; | |||||
| import { ThemeProvider } from "@emotion/react"; | |||||
| 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 { Dialog, DialogTitle, DialogContent, DialogActions } from "@mui/material"; | |||||
| import BrStatusDialog from "components/BrStatusDialog"; | |||||
| const LoadingComponent = Loadable(lazy(() => import("pages/extra-pages/LoadingComponent"))); | |||||
| const UploadFileTable = Loadable(lazy(() => import("pages/Proof/Reply_Public/UploadFileTable"))); | |||||
| const BackgroundHead = { | |||||
| backgroundImage: `url(${titleBackgroundImg})`, | |||||
| width: "100%", | |||||
| height: "100%", | |||||
| backgroundSize: "contain", | |||||
| backgroundRepeat: "no-repeat", | |||||
| backgroundColor: "#0C489E", | |||||
| backgroundPosition: "right" | |||||
| }; | |||||
| const SubmitBrPage = () => { | |||||
| usePageTitle("submitBrCertificate"); | |||||
| const intl = useIntl(); | |||||
| const navigate = useNavigate(); | |||||
| const [formData, setFormData] = React.useState({}); | |||||
| const [isLoading, setLoading] = React.useState(true); | |||||
| const [errorMsg, setErrorMsg] = React.useState(""); | |||||
| const [attachments, setAttachments] = React.useState([]); | |||||
| const attachmentsRef = React.useRef([]); | |||||
| attachmentsRef.current = attachments; | |||||
| const [warningText, setWarningText] = React.useState(""); | |||||
| const [isWarningPopUp, setIsWarningPopUp] = React.useState(false); | |||||
| const [showSubmittedDialog, setShowSubmittedDialog] = React.useState(false); | |||||
| const fileInputRef = React.useRef(null); | |||||
| const minDate = React.useMemo(() => new Date().setDate(new Date().getDate() + 1), []); | |||||
| React.useEffect(() => { | |||||
| if (!isORGLoggedIn()) { | |||||
| navigate("/dashboard"); | |||||
| return; | |||||
| } | |||||
| HttpUtils.get({ | |||||
| url: UrlUtils.GET_PUB_ORG_PATH, | |||||
| onSuccess: (response) => { | |||||
| const data = response.data || {}; | |||||
| if (!data.canSubmitBr) { | |||||
| navigate(isPrimaryLoggedIn() ? "/org" : "/dashboard"); | |||||
| return; | |||||
| } | |||||
| data.country = getObjectByType(ComboData.country, "type", data.addressTemp?.country); | |||||
| data.district = getObjectByType(ComboData.district, "type", data.addressTemp?.district); | |||||
| data.addressLine1 = data.addressTemp?.addressLine1; | |||||
| data.addressLine2 = data.addressTemp?.addressLine2; | |||||
| data.addressLine3 = data.addressTemp?.addressLine3; | |||||
| setFormData(data); | |||||
| setLoading(false); | |||||
| }, | |||||
| onFail: () => { | |||||
| navigate("/dashboard"); | |||||
| }, | |||||
| onError: () => { | |||||
| navigate("/dashboard"); | |||||
| } | |||||
| }); | |||||
| }, [navigate]); | |||||
| const displayErrorMsg = (msg) => <Typography variant="errorMessage1">{msg}</Typography>; | |||||
| const getFileExtension = (fileName) => { | |||||
| const name = (fileName || "").toLowerCase(); | |||||
| const dot = name.lastIndexOf("."); | |||||
| if (dot <= 0 || dot === name.length - 1) { | |||||
| return ""; | |||||
| } | |||||
| return name.substring(dot + 1); | |||||
| }; | |||||
| const isAcceptedFile = (fileName) => ["pdf", "jpg", "jpeg", "png"].includes(getFileExtension(fileName)); | |||||
| const initialValues = React.useMemo(() => ({ | |||||
| ...formData, | |||||
| brExpiryDate: null, | |||||
| certificateFile: null, | |||||
| }), [formData]); | |||||
| const formik = useFormik({ | |||||
| enableReinitialize: true, | |||||
| initialValues, | |||||
| validationSchema: yup.object().shape({ | |||||
| enCompanyName: yup.string().trim().max(150).required(displayErrorMsg(intl.formatMessage({ id: "userRequireEnglishName" }))), | |||||
| chCompanyName: yup.string().max(150).nullable(), | |||||
| addressLine1: yup.string().trim().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" }))), | |||||
| brExpiryDate: yup.mixed().required(displayErrorMsg(intl.formatMessage({ id: "pleaseFillInBusinessRegCertValidityDate" }))), | |||||
| certificateFile: yup.mixed().required(displayErrorMsg(intl.formatMessage({ id: "requireValidFileWithProofReplyFormat" }))), | |||||
| country: yup.mixed().required(displayErrorMsg(intl.formatMessage({ id: "pleaseFillInCountry" }))), | |||||
| district: yup.mixed().nullable().test( | |||||
| "hk-district", | |||||
| displayErrorMsg(intl.formatMessage({ id: "pleaseFillInDistrict" })), | |||||
| function (value) { | |||||
| const country = this.parent.country; | |||||
| if (country && country.type === "hongKong") { | |||||
| return value != null; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| ), | |||||
| }), | |||||
| onSubmit: (values, { setSubmitting }) => { | |||||
| setErrorMsg(""); | |||||
| const files = attachmentsRef.current; | |||||
| if (!values.brExpiryDate || !files || files.length === 0 || values.country == null | |||||
| || (values.country.type === "hongKong" && values.district == null)) { | |||||
| setSubmitting(false); | |||||
| return; | |||||
| } | |||||
| return new Promise((resolve, reject) => { | |||||
| HttpUtils.postWithFiles({ | |||||
| url: UrlUtils.POST_PUB_ORG_SUBMIT_BR, | |||||
| params: { | |||||
| enCompanyName: values.enCompanyName, | |||||
| chCompanyName: values.chCompanyName, | |||||
| brExpiryDate: DateUtils.dateValue(values.brExpiryDate), | |||||
| address: { | |||||
| country: values.country.type, | |||||
| district: values.district?.type, | |||||
| addressLine1: values.addressLine1, | |||||
| addressLine2: values.addressLine2, | |||||
| addressLine3: values.addressLine3, | |||||
| }, | |||||
| }, | |||||
| files: files, | |||||
| onSuccess: (response) => { | |||||
| if (response?.msg) { | |||||
| setErrorMsg(intl.formatMessage({ id: response.msg, defaultMessage: response.msg })); | |||||
| reject(); | |||||
| return; | |||||
| } | |||||
| setShowSubmittedDialog(true); | |||||
| resolve(); | |||||
| }, | |||||
| onFail: () => reject(), | |||||
| onError: () => reject(), | |||||
| }); | |||||
| }); | |||||
| } | |||||
| }); | |||||
| const setCertificateFiles = (files) => { | |||||
| const next = files || []; | |||||
| setAttachments(next); | |||||
| formik.setFieldValue("certificateFile", next[0] || null, true); | |||||
| }; | |||||
| const handleSelectCertificate = (event) => { | |||||
| const file = event.target.files[0]; | |||||
| if (!file) { | |||||
| return; | |||||
| } | |||||
| if (!isAcceptedFile(file.name)) { | |||||
| setWarningText(intl.formatMessage({ id: "requireValidFileWithProofReplyFormat" })); | |||||
| setIsWarningPopUp(true); | |||||
| event.target.value = ""; | |||||
| return; | |||||
| } | |||||
| if (file.size >= (10 * 1024 * 1034)) { | |||||
| setWarningText(intl.formatMessage({ id: "fileSizeWarning" })); | |||||
| setIsWarningPopUp(true); | |||||
| event.target.value = ""; | |||||
| return; | |||||
| } | |||||
| file.id = 0; | |||||
| setCertificateFiles([file]); | |||||
| event.target.value = ""; | |||||
| }; | |||||
| return ( | |||||
| isLoading ? | |||||
| <Grid container sx={{ minHeight: "87vh", mb: 3 }} direction="column" justifyContent="center" alignItems="center"> | |||||
| <Grid item><LoadingComponent /></Grid> | |||||
| </Grid> | |||||
| : | |||||
| <Grid container direction="column" sx={{ minHeight: "87vh", backgroundColor: "#ffffff" }}> | |||||
| <Grid item xs={12}> | |||||
| <div style={BackgroundHead}> | |||||
| <Stack direction="row" height="70px" justifyContent="flex-start" alignItems="center"> | |||||
| <Typography component="h1" ml={15} color="#FFF" variant="h4" sx={{ display: { xs: "none", md: "block" } }}> | |||||
| <FormattedMessage id="submitBrCertificate" /> | |||||
| </Typography> | |||||
| </Stack> | |||||
| </div> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <Button aria-label={intl.formatMessage({ id: "back" })} title="Back" sx={{ ml: 3.5, mt: 2 }} style={{ border: "2px solid" }} variant="outlined" onClick={() => navigate(-1)}> | |||||
| <ForwardIcon style={{ height: 30, width: 50, transform: "rotate(180deg)" }} /> | |||||
| </Button> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <Box sx={{ p: 1, borderRadius: "10px" }}> | |||||
| <MainCard elevation={0} border={false} content={false}> | |||||
| <form onSubmit={formik.handleSubmit} noValidate style={{ padding: 24 }}> | |||||
| <Grid container spacing={1}> | |||||
| <Grid item xs={12}> | |||||
| <FormHelperText error> | |||||
| <Typography variant="errorMessage1">{errorMsg}</Typography> | |||||
| </FormHelperText> | |||||
| </Grid> | |||||
| <Grid item xs={12} lg={4}> | |||||
| {FieldUtils.getTextField({ | |||||
| label: intl.formatMessage({ id: "brNo" }) + ":", | |||||
| valueName: "brNo", | |||||
| disabled: true, | |||||
| form: formik | |||||
| })} | |||||
| </Grid> | |||||
| <Grid item xs={12} lg={4}> | |||||
| <Grid container alignItems={"center"}> | |||||
| <Grid item xs={12} md={3} lg={3} sx={{ display: "flex", alignItems: "center" }}> | |||||
| <Typography variant="pnspsFormParagraphBold">{FieldUtils.notNullFieldLabel(intl.formatMessage({ id: "newBrExpiryDate" }) + ":")}</Typography> | |||||
| </Grid> | |||||
| <Grid item xs={12} md={6} lg={6}> | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}> | |||||
| <DemoItem components={["DatePicker"]}> | |||||
| <DatePicker | |||||
| id="brExpiryDate" | |||||
| name="brExpiryDate" | |||||
| slotProps={{ | |||||
| field: { readOnly: true }, | |||||
| textField: { | |||||
| error: Boolean(formik.errors.brExpiryDate && (formik.touched.brExpiryDate || formik.submitCount > 0)), | |||||
| helperText: (formik.touched.brExpiryDate || formik.submitCount > 0) ? formik.errors.brExpiryDate : "", | |||||
| }, | |||||
| }} | |||||
| format="DD/MM/YYYY" | |||||
| value={formik.values.brExpiryDate == null ? null : dayjs(formik.values.brExpiryDate)} | |||||
| minDate={minDate == null ? null : dayjs(minDate)} | |||||
| onChange={(newValue) => { | |||||
| if (DateUtils.dateValue(newValue) > DateUtils.dateValue(new Date())) { | |||||
| formik.setFieldValue("brExpiryDate", newValue, true); | |||||
| formik.setFieldTouched("brExpiryDate", true, false); | |||||
| } | |||||
| }} | |||||
| /> | |||||
| </DemoItem> | |||||
| </LocalizationProvider> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <Grid item xs={12} lg={4}></Grid> | |||||
| <Grid item xs={12} lg={4}> | |||||
| {FieldUtils.getTextField({ | |||||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: "nameEng" }) + ":"), | |||||
| valueName: "enCompanyName", | |||||
| form: formik | |||||
| })} | |||||
| </Grid> | |||||
| <Grid item xs={12} lg={4}> | |||||
| {FieldUtils.getTextField({ | |||||
| label: intl.formatMessage({ id: "nameChi" }) + ":", | |||||
| valueName: "chCompanyName", | |||||
| form: formik | |||||
| })} | |||||
| </Grid> | |||||
| <Grid item xs={12} lg={4}></Grid> | |||||
| <Grid item xs={12} lg={12}> | |||||
| {FieldUtils.getAddressField({ | |||||
| label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: "formAddress" }) + ":"), | |||||
| valueName: ["addressLine1", "addressLine2", "addressLine3"], | |||||
| form: formik | |||||
| })} | |||||
| </Grid> | |||||
| <Grid item xs={12} lg={12}> | |||||
| {FieldUtils.getProfileComboField({ | |||||
| label: "", | |||||
| valueName: "district", | |||||
| 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 xs={12}> | |||||
| <Typography variant="subtitle1" sx={{ color: "primary.primary" }}> | |||||
| <FormattedMessage id="acceptProofReplyFileFormats" /> | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <input | |||||
| ref={fileInputRef} | |||||
| id="uploadFileBtn" | |||||
| type="file" | |||||
| accept=".pdf,.jpg,.jpeg,.png,image/png,image/jpeg,application/pdf" | |||||
| hidden | |||||
| onChange={handleSelectCertificate} | |||||
| /> | |||||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||||
| <Button | |||||
| variant="contained" | |||||
| type="button" | |||||
| sx={PRIMARY_CONTAINED_BUTTON_SX} | |||||
| onClick={() => fileInputRef.current && fileInputRef.current.click()} | |||||
| > | |||||
| <FormattedMessage id="selectCertificateFile" /> | |||||
| </Button> | |||||
| </ThemeProvider> | |||||
| {(formik.touched.certificateFile || formik.submitCount > 0) && formik.errors.certificateFile ? | |||||
| <FormHelperText error>{formik.errors.certificateFile}</FormHelperText> | |||||
| : null} | |||||
| </Grid> | |||||
| {attachments.length > 0 ? | |||||
| <Grid item xs={12} md={8}> | |||||
| <UploadFileTable recordList={attachments} setRecordList={setCertificateFiles} /> | |||||
| </Grid> | |||||
| : null} | |||||
| <Grid item xs={12} sx={{ mt: 2 }}> | |||||
| <ThemeProvider theme={PNSPS_BUTTON_THEME}> | |||||
| <Button | |||||
| variant="contained" | |||||
| type="submit" | |||||
| color="success" | |||||
| disabled={formik.isSubmitting || showSubmittedDialog} | |||||
| startIcon={formik.isSubmitting ? <CircularProgress color="inherit" size={18} /> : null} | |||||
| > | |||||
| <FormattedMessage id="submit" /> | |||||
| </Button> | |||||
| </ThemeProvider> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </form> | |||||
| </MainCard> | |||||
| </Box> | |||||
| </Grid> | |||||
| <Dialog open={isWarningPopUp} onClose={() => setIsWarningPopUp(false)}> | |||||
| <DialogTitle><FormattedMessage id="attention" /></DialogTitle> | |||||
| <DialogContent><Typography>{warningText}</Typography></DialogContent> | |||||
| <DialogActions> | |||||
| <Button onClick={() => setIsWarningPopUp(false)}><FormattedMessage id="close" /></Button> | |||||
| </DialogActions> | |||||
| </Dialog> | |||||
| <BrStatusDialog | |||||
| open={showSubmittedDialog} | |||||
| variant={formData.brStatus === "Invalid" || formData.applyBlockState === "expired" | |||||
| ? "submitExpired" | |||||
| : "submitNotExpired"} | |||||
| expiryDate={formData.brExpiryDate} | |||||
| onClose={() => { | |||||
| setShowSubmittedDialog(false); | |||||
| navigate(isPrimaryLoggedIn() ? "/org" : "/dashboard"); | |||||
| }} | |||||
| /> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default SubmitBrPage; | |||||
| @@ -5,6 +5,7 @@ import * as UrlUtils from "utils/ApiPathConst"; | |||||
| import * as FormatUtils from "utils/FormatUtils"; | import * as FormatUtils from "utils/FormatUtils"; | ||||
| import * as DateUtils from "utils/DateUtils"; | import * as DateUtils from "utils/DateUtils"; | ||||
| import { useIntl } from "react-intl"; | import { useIntl } from "react-intl"; | ||||
| import { useNavigate } from "react-router-dom"; | |||||
| import { | import { | ||||
| Grid, | Grid, | ||||
| @@ -24,6 +25,7 @@ import { | |||||
| // checkIsOnlyOnlinePayment | // checkIsOnlyOnlinePayment | ||||
| // isCreditorLoggedIn | // isCreditorLoggedIn | ||||
| } from "utils/Utils"; | } from "utils/Utils"; | ||||
| import { fetchOrgBrData, applyBlockVariant } from "utils/orgBrUtils"; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const ApplyForm = () => { | const ApplyForm = () => { | ||||
| @@ -35,12 +37,23 @@ const ApplyForm = () => { | |||||
| const [selections, setSelection] = React.useState([]); | const [selections, setSelection] = React.useState([]); | ||||
| const [isLoading, setLoding] = React.useState(true); | const [isLoading, setLoding] = React.useState(true); | ||||
| const [orgBrReady, setOrgBrReady] = React.useState(false); | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const navigate = useNavigate(); | |||||
| const { locale } = intl; | const { locale } = intl; | ||||
| React.useEffect(() => { | React.useEffect(() => { | ||||
| loadUserData(); | loadUserData(); | ||||
| fetchOrgBrData({ | |||||
| onSuccess: (org) => { | |||||
| if (applyBlockVariant(org)) { | |||||
| navigate("/dashboard", { replace: true }); | |||||
| return; | |||||
| } | |||||
| setOrgBrReady(true); | |||||
| } | |||||
| }); | |||||
| }, []); | }, []); | ||||
| const loadUserData = () => { | const loadUserData = () => { | ||||
| @@ -112,11 +125,10 @@ const ApplyForm = () => { | |||||
| React.useEffect(() => { | React.useEffect(() => { | ||||
| if (userData !== null){ | |||||
| if (userData !== null && orgBrReady){ | |||||
| setLoding(false); | setLoding(false); | ||||
| // console.log(isOnlyOnlinePayment) | |||||
| } | } | ||||
| }, [userData]); | |||||
| }, [userData, orgBrReady]); | |||||
| return ( | return ( | ||||
| isLoading ? | isLoading ? | ||||
| @@ -398,9 +398,11 @@ export default function SubmittedTab({ setCount, url }) { | |||||
| <> | <> | ||||
| <div style={{ minHeight: 400, width: '100%', padding: 4 }}> | <div style={{ minHeight: 400, width: '100%', padding: 4 }}> | ||||
| {isORGLoggedIn() ? | {isORGLoggedIn() ? | ||||
| <Grid container direction="row" justifyContent="flex-start" alignItems="center" > | |||||
| <Grid item xs={3} md={1}> | |||||
| <Typography variant="h5"><FormattedMessage id="careOf" />:</Typography> | |||||
| <Grid container direction="row" justifyContent="flex-start" alignItems="center" columnSpacing={1} sx={{ mb: 1 }}> | |||||
| <Grid item> | |||||
| <Typography variant="h5" sx={{ whiteSpace: 'nowrap' }}> | |||||
| <FormattedMessage id="careOf" />: | |||||
| </Typography> | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={8} md={2}> | <Grid item xs={8} md={2}> | ||||
| <Autocomplete | <Autocomplete | ||||
| @@ -32,6 +32,8 @@ import { PNSPS_LONG_BUTTON_THEME } from "../../../themes/buttonConst"; | |||||
| import { ThemeProvider } from "@emotion/react"; | import { ThemeProvider } from "@emotion/react"; | ||||
| import { FormattedMessage, useIntl } from "react-intl"; | import { FormattedMessage, useIntl } from "react-intl"; | ||||
| import usePageTitle from 'components/usePageTitle'; | import usePageTitle from 'components/usePageTitle'; | ||||
| import { fetchOrgBrData, applyBlockVariant, applyClickVariant } from "utils/orgBrUtils"; | |||||
| import BrStatusDialog from "components/BrStatusDialog"; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const PublicNotice = () => { | const PublicNotice = () => { | ||||
| @@ -44,6 +46,8 @@ const PublicNotice = () => { | |||||
| const [selectedTab, setSelectedTab] = useState("1"); | const [selectedTab, setSelectedTab] = useState("1"); | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const [orgBrData, setOrgBrData] = useState(null); | |||||
| const [brDialog, setBrDialog] = useState({ open: false, variant: null, afterClose: null }); | |||||
| const _sx = { | const _sx = { | ||||
| padding: "4 2 4 2", | padding: "4 2 4 2", | ||||
| @@ -76,6 +80,9 @@ const PublicNotice = () => { | |||||
| useEffect(() => { | useEffect(() => { | ||||
| loadData(); | loadData(); | ||||
| fetchOrgBrData({ | |||||
| onSuccess: (org) => setOrgBrData(org) | |||||
| }); | |||||
| }, []); | }, []); | ||||
| const loadData = () => { | const loadData = () => { | ||||
| @@ -99,6 +106,15 @@ const PublicNotice = () => { | |||||
| } | } | ||||
| const onBtnClick = () => { | const onBtnClick = () => { | ||||
| const variant = applyClickVariant(orgBrData); | |||||
| if (variant) { | |||||
| setBrDialog({ | |||||
| open: true, | |||||
| variant, | |||||
| afterClose: applyBlockVariant(orgBrData) ? "stay" : "apply", | |||||
| }); | |||||
| return; | |||||
| } | |||||
| navigate('/publicNotice/apply') | navigate('/publicNotice/apply') | ||||
| } | } | ||||
| @@ -209,6 +225,18 @@ const PublicNotice = () => { | |||||
| ) | ) | ||||
| } | } | ||||
| <BrStatusDialog | |||||
| open={brDialog.open} | |||||
| variant={brDialog.variant} | |||||
| expiryDate={orgBrData?.brExpiryDate} | |||||
| onClose={() => { | |||||
| const afterClose = brDialog.afterClose; | |||||
| setBrDialog({ open: false, variant: null, afterClose: null }); | |||||
| if (afterClose === "apply") { | |||||
| navigate("/publicNotice/apply"); | |||||
| } | |||||
| }} | |||||
| /> | |||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,22 +1,51 @@ | |||||
| // material-ui | // material-ui | ||||
| import { | import { | ||||
| // Box, | |||||
| Autocomplete, | |||||
| TextField, | |||||
| Typography | Typography | ||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| import MainCard from "components/MainCard"; | import MainCard from "components/MainCard"; | ||||
| import * as React from "react"; | import * as React from "react"; | ||||
| import { FiDataGrid } from "components/FiDataGrid"; | import { FiDataGrid } from "components/FiDataGrid"; | ||||
| import { GET_SYS_PARAMS } from "utils/ApiPathConst"; | |||||
| import { GET_SYS_PARAMS, GET_SYS_PARAM_NAMES } from "utils/ApiPathConst"; | |||||
| import SafeHtml from 'components/SafeHtml'; | import SafeHtml from 'components/SafeHtml'; | ||||
| import * as HttpUtils from "utils/HttpUtils"; | |||||
| import { useIntl } from "react-intl"; | |||||
| const LANG_SUFFIX = /\.(en|zh|cn)$/i; | |||||
| function toSearchKey(name) { | |||||
| return String(name || "").replace(LANG_SUFFIX, ""); | |||||
| } | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const Table = ({onRowClick, searchCriteria, refreshTrigger}) => { | const Table = ({onRowClick, searchCriteria, refreshTrigger}) => { | ||||
| const intl = useIntl(); | |||||
| const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria); | const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria); | ||||
| const [nameOptions, setNameOptions] = React.useState([]); | |||||
| const [selectedName, setSelectedName] = React.useState(""); | |||||
| React.useEffect(() => { | React.useEffect(() => { | ||||
| set_searchCriteria(searchCriteria); | |||||
| }, [searchCriteria]); | |||||
| HttpUtils.get({ | |||||
| url: GET_SYS_PARAM_NAMES, | |||||
| onSuccess: (responseData) => { | |||||
| const keys = (Array.isArray(responseData) ? responseData : []) | |||||
| .map((item) => toSearchKey(item?.label ?? item)) | |||||
| .filter(Boolean); | |||||
| setNameOptions([...new Set(keys)].sort((a, b) => a.localeCompare(b))); | |||||
| } | |||||
| }); | |||||
| }, []); | |||||
| React.useEffect(() => { | |||||
| const next = { ...searchCriteria }; | |||||
| if (selectedName) { | |||||
| next.name = selectedName; | |||||
| } else { | |||||
| delete next.name; | |||||
| } | |||||
| set_searchCriteria(next); | |||||
| }, [searchCriteria, selectedName]); | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| @@ -61,6 +90,37 @@ const Table = ({onRowClick, searchCriteria, refreshTrigger}) => { | |||||
| System Params | System Params | ||||
| </Typography> | </Typography> | ||||
| <Autocomplete | |||||
| disablePortal | |||||
| options={nameOptions} | |||||
| value={selectedName || null} | |||||
| onChange={(event, newValue) => { | |||||
| setSelectedName(newValue || ""); | |||||
| }} | |||||
| getOptionLabel={(option) => (option != null ? String(option) : "")} | |||||
| isOptionEqualToValue={(option, value) => String(option) === String(value)} | |||||
| sx={{ | |||||
| mt: 2, | |||||
| ml: 3, | |||||
| mr: 3, | |||||
| maxWidth: 480, | |||||
| '& .MuiInputBase-root': { alignItems: 'center' }, | |||||
| '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, | |||||
| '& .MuiOutlinedInput-root': { height: 40 } | |||||
| }} | |||||
| renderInput={(params) => ( | |||||
| <TextField | |||||
| {...params} | |||||
| label={intl.formatMessage({ id: "systemSettingName" })} | |||||
| InputLabelProps={{ shrink: true }} | |||||
| /> | |||||
| )} | |||||
| clearText={intl.formatMessage({ id: "muiClear" })} | |||||
| closeText={intl.formatMessage({ id: "muiClose" })} | |||||
| openText={intl.formatMessage({ id: "muiOpen" })} | |||||
| noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} | |||||
| /> | |||||
| <div style={{ width: { xs: '92vw', sm: '96.5vw', md: "auto" }, }}> | <div style={{ width: { xs: '92vw', sm: '96.5vw', md: "auto" }, }}> | ||||
| {/* <Box width= '100%' sx={{ backgroundColor: "#fff", ml: 2 }} height='100%'> */} | {/* <Box width= '100%' sx={{ backgroundColor: "#fff", ml: 2 }} height='100%'> */} | ||||
| <FiDataGrid | <FiDataGrid | ||||
| @@ -20,6 +20,8 @@ import * as HttpUtils from "utils/HttpUtils"; | |||||
| import * as UrlUtils from "utils/ApiPathConst"; | import * as UrlUtils from "utils/ApiPathConst"; | ||||
| import * as DateUtils from "utils/DateUtils"; | import * as DateUtils from "utils/DateUtils"; | ||||
| import { checkPaymentSuspension } from "utils/Utils"; | import { checkPaymentSuspension } from "utils/Utils"; | ||||
| import { fetchOrgBrData, applyBlockVariant, applyClickVariant, loginPopupVariant } from "utils/orgBrUtils"; | |||||
| import BrStatusDialog from "components/BrStatusDialog"; | |||||
| const Message = Loadable(React.lazy(() => import('./Message'))); | const Message = Loadable(React.lazy(() => import('./Message'))); | ||||
| const Notice = Loadable(React.lazy(() => import('./Notice'))); | const Notice = Loadable(React.lazy(() => import('./Notice'))); | ||||
| @@ -56,10 +58,21 @@ const DashboardDefault = () => { | |||||
| const [itemList, setItemList] = React.useState([]); | const [itemList, setItemList] = React.useState([]); | ||||
| const [listData, setListData] = React.useState([]); | const [listData, setListData] = React.useState([]); | ||||
| const [isPopUp, setIsPopUp] = React.useState(false); | const [isPopUp, setIsPopUp] = React.useState(false); | ||||
| const [orgBrData, setOrgBrData] = React.useState(null); | |||||
| const [brDialog, setBrDialog] = React.useState({ open: false, variant: null }); | |||||
| React.useEffect(() => { | React.useEffect(() => { | ||||
| loadMessageData() | loadMessageData() | ||||
| loadNoticeData() | loadNoticeData() | ||||
| fetchOrgBrData({ | |||||
| onSuccess: (org) => { | |||||
| setOrgBrData(org); | |||||
| const variant = loginPopupVariant(org); | |||||
| if (variant) { | |||||
| setBrDialog({ open: true, variant, afterClose: "stay" }); | |||||
| } | |||||
| } | |||||
| }); | |||||
| localStorage.setItem('searchCriteria',"") | localStorage.setItem('searchCriteria',"") | ||||
| }, []); | }, []); | ||||
| @@ -199,7 +212,18 @@ const DashboardDefault = () => { | |||||
| <Grid item xs={12} lg={5} sx={{ pt: 2 }} style={{ height: '100%' }}> | <Grid item xs={12} lg={5} sx={{ pt: 2 }} style={{ height: '100%' }}> | ||||
| <Button | <Button | ||||
| xs={12} | xs={12} | ||||
| onClick={() => { navigate("/publicNotice/apply"); }} | |||||
| onClick={() => { | |||||
| const variant = applyClickVariant(orgBrData); | |||||
| if (variant) { | |||||
| setBrDialog({ | |||||
| open: true, | |||||
| variant, | |||||
| afterClose: applyBlockVariant(orgBrData) ? "stay" : "apply", | |||||
| }); | |||||
| return; | |||||
| } | |||||
| navigate("/publicNotice/apply"); | |||||
| }} | |||||
| aria-label={intl.formatMessage({ id: 'submitApplication' })} | aria-label={intl.formatMessage({ id: 'submitApplication' })} | ||||
| sx={{ | sx={{ | ||||
| width: "100%", | width: "100%", | ||||
| @@ -306,6 +330,18 @@ const DashboardDefault = () => { | |||||
| </DialogActions> | </DialogActions> | ||||
| </Dialog> | </Dialog> | ||||
| </div> | </div> | ||||
| <BrStatusDialog | |||||
| open={brDialog.open} | |||||
| variant={brDialog.variant} | |||||
| expiryDate={orgBrData?.brExpiryDate} | |||||
| onClose={() => { | |||||
| const afterClose = brDialog.afterClose; | |||||
| setBrDialog({ open: false, variant: null, afterClose: null }); | |||||
| if (afterClose === "apply") { | |||||
| navigate("/publicNotice/apply"); | |||||
| } | |||||
| }} | |||||
| /> | |||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -27,6 +27,7 @@ const DemandNote_Public = Loadable(lazy(() => import('pages/DemandNote/Search_Pu | |||||
| const UserMaintainPage_Individual = Loadable(lazy(() => import('pages/User/DetailsPage_Individual'))); | const UserMaintainPage_Individual = Loadable(lazy(() => import('pages/User/DetailsPage_Individual'))); | ||||
| const UserMaintainPage_Organization = Loadable(lazy(() => import('pages/User/DetailsPage_Organization'))); | const UserMaintainPage_Organization = Loadable(lazy(() => import('pages/User/DetailsPage_Organization'))); | ||||
| const OrganizationDetailPage = Loadable(lazy(() => import('pages/Organization/DetailPage'))); | const OrganizationDetailPage = Loadable(lazy(() => import('pages/Organization/DetailPage'))); | ||||
| const SubmitBrPage = Loadable(lazy(() => import('pages/Organization/SubmitBrPage'))); | |||||
| const Msg_Details = Loadable(lazy(() => import('pages/Message/Details'))); | const Msg_Details = Loadable(lazy(() => import('pages/Message/Details'))); | ||||
| const Msg_Search = Loadable(lazy(() => import('pages/Message/Search'))); | const Msg_Search = Loadable(lazy(() => import('pages/Message/Search'))); | ||||
| const AnnouncementSearch = Loadable(lazy(() => import('pages/Announcement/Search_Public'))); | const AnnouncementSearch = Loadable(lazy(() => import('pages/Announcement/Search_Public'))); | ||||
| @@ -126,6 +127,10 @@ const PublicDashboard = { | |||||
| path: '/orgUser', | path: '/orgUser', | ||||
| element: <UserMaintainPage_Organization /> | element: <UserMaintainPage_Organization /> | ||||
| }, | }, | ||||
| { | |||||
| path: '/org/submit-br', | |||||
| element: <SubmitBrPage /> | |||||
| }, | |||||
| { | { | ||||
| path: '/org', | path: '/org', | ||||
| element: <OrganizationDetailPage /> | element: <OrganizationDetailPage /> | ||||
| @@ -69,6 +69,21 @@ export const ERROR_CONTAINED_BUTTON_SX = { | |||||
| }, | }, | ||||
| }; | }; | ||||
| /** WCAG 2.2 AA contained grey — matches PNSPS containedCancel (#616161 + white, ~5.7:1). */ | |||||
| export const CONTAINED_NEUTRAL_GREY = '#616161'; | |||||
| export const GREY_CONTAINED_BUTTON_SX = { | |||||
| backgroundColor: CONTAINED_NEUTRAL_GREY, | |||||
| color: '#FFFFFF', | |||||
| '&:hover': { | |||||
| backgroundColor: '#545454', | |||||
| }, | |||||
| '&:focus-visible': { | |||||
| outline: '2px solid #616161', | |||||
| outlineOffset: '2px', | |||||
| }, | |||||
| }; | |||||
| export const PRIMARY_CONTAINED_BUTTON_SX = { | export const PRIMARY_CONTAINED_BUTTON_SX = { | ||||
| backgroundColor: CONTAINED_PRIMARY_BLUE, | backgroundColor: CONTAINED_PRIMARY_BLUE, | ||||
| color: '#FFFFFF', | color: '#FFFFFF', | ||||
| @@ -61,7 +61,7 @@ | |||||
| "MSG.registerIAmSmart": "You may click the \"iAM Smart\" button to fill the personal information automatically or enter the information manually to activate the PNSPS account now.<br/>If you want to use \"iAM Smart\" to fill the personal information, please download the \"iAM Smart\" mobile app and register as an \"iAM Smart\" user first.", | "MSG.registerIAmSmart": "You may click the \"iAM Smart\" button to fill the personal information automatically or enter the information manually to activate the PNSPS account now.<br/>If you want to use \"iAM Smart\" to fill the personal information, please download the \"iAM Smart\" mobile app and register as an \"iAM Smart\" user first.", | ||||
| "MSG.registerPersonal": "To complete the online application, you need to upload digital copies of identification documents.<br/>e.g. Hong Kong Identity Card, Passport, Mainland China Identity Card, Professional Practicing Certificate, etc.", | "MSG.registerPersonal": "To complete the online application, you need to upload digital copies of identification documents.<br/>e.g. Hong Kong Identity Card, Passport, Mainland China Identity Card, Professional Practicing Certificate, etc.", | ||||
| "MSG.registerOrg": "You need to upload the proof documents for the online application.<br/>e.g. Business Registration Certificate, Professional Practicing Certificate, etc.", | |||||
| "MSG.registerOrg": "You need to upload the Business Registration Certificate for the online application.", | |||||
| "MSG.paymentMsg": "Your application and payment have been received", | "MSG.paymentMsg": "Your application and payment have been received", | ||||
| "MSG.expiredApp": "Public Notice application has expired", | "MSG.expiredApp": "Public Notice application has expired", | ||||
| @@ -286,10 +286,10 @@ | |||||
| "sameAsBusinessRegistrationCert": "Same as Business Registration Certificate", | "sameAsBusinessRegistrationCert": "Same as Business Registration Certificate", | ||||
| "businessRegCert": "Business Registration Certificate", | "businessRegCert": "Business Registration Certificate", | ||||
| "businessRegCertNumber": "Hong Kong Business Reg Cert Number", | "businessRegCertNumber": "Hong Kong Business Reg Cert Number", | ||||
| "businessRegCertAndDoc":"Business Registration Certificate and other documents", | |||||
| "businessRegCertAndDoc":"Business Registration Certificate", | |||||
| "businessRegCertExpiryDate": "Business registration certificate expiry date", | "businessRegCertExpiryDate": "Business registration certificate expiry date", | ||||
| "pleaseUploadDoc": "Please upload a digital file of your valid business registration certificate and other documents to verify your identity.", | |||||
| "uploadFile": "Upload business registration certificate and other documents", | |||||
| "pleaseUploadDoc": "Please upload a digital file of your valid business registration certificate to verify your identity.", | |||||
| "uploadFile": "Upload business registration certificate", | |||||
| "pleaseUploadIdDoc": "Please upload a digital file of your valid identity document to verify your identity.", | "pleaseUploadIdDoc": "Please upload a digital file of your valid identity document to verify your identity.", | ||||
| "pleaseUploadIdDocSubTitle": "Such as: Hong Kong ID card; passport; Mainland China ID card; professional practice certificate, etc.", | "pleaseUploadIdDocSubTitle": "Such as: Hong Kong ID card; passport; Mainland China ID card; professional practice certificate, etc.", | ||||
| "uploadIdDoc": "Upload identity document", | "uploadIdDoc": "Upload identity document", | ||||
| @@ -321,7 +321,7 @@ | |||||
| "pleaseFillInBusinessRegCertNumber": "Please fill in Business Registration Certificate Number", | "pleaseFillInBusinessRegCertNumber": "Please fill in Business Registration Certificate Number", | ||||
| "pleaseFillInValidBusinessRegCertNumber": "Please fill in valid Business Registration Certificate Number", | "pleaseFillInValidBusinessRegCertNumber": "Please fill in valid Business Registration Certificate Number", | ||||
| "businessRegCertValidityDate": "Business Reg Cert validity date", | "businessRegCertValidityDate": "Business Reg Cert validity date", | ||||
| "pleaseFillInBusinessRegCertValidityDate": "Please fill in Business Reg Cert validity date", | |||||
| "pleaseFillInBusinessRegCertValidityDate": "Please fill in BR certificate validity date", | |||||
| "formAddress": "Address", | "formAddress": "Address", | ||||
| "addressLine1": "First line of address", | "addressLine1": "First line of address", | ||||
| "addressLine2": "Second line of address", | "addressLine2": "Second line of address", | ||||
| @@ -606,6 +606,24 @@ | |||||
| "nameEng": "Name (Eng)", | "nameEng": "Name (Eng)", | ||||
| "nameChi": "Name (Chi)", | "nameChi": "Name (Chi)", | ||||
| "expiryDate": "Expiry Date", | "expiryDate": "Expiry Date", | ||||
| "brStatusValid": "Valid", | |||||
| "brStatusInvalid": "Invalid", | |||||
| "submitBrCertificate": "Submit Business Registration Certificate (BR)", | |||||
| "goToSubmitBr": "Go to submit BR", | |||||
| "brPopupTitleExpiring": "Reminder", | |||||
| "brPopupTitleExpired": "Action Required", | |||||
| "brPopupTitlePendingVerify": "Notice", | |||||
| "brSubmitSuccessNotExpired": "Thank you for your BR's submission. Please wait for official approval, you may submit new public notice application before the expiry date.", | |||||
| "brSubmitSuccessExpired": "Thank you for your BR's submission. Please wait for official approval before making any new application submissions.", | |||||
| "selectCertificateFile": "Select Certificate File", | |||||
| "newBrExpiryDate": "New BR Expiry Date", | |||||
| "newBr": "New BR", | |||||
| "newBrInformation": "New BR Information", | |||||
| "confirmNewBr": "Confirm New BR", | |||||
| "confirmNewBrMessage": "Confirm the new BR information and overwrite the existing organisation details?", | |||||
| "brExpiredMsg": "Your company’s business registration has expired. Please upload a valid BR certificate.", | |||||
| "brPendingVerifyMsg": "Your company has already submitted the BR certificate for verification.", | |||||
| "uploadedFiles": "Uploaded Files", | |||||
| "create": "Create", | "create": "Create", | ||||
| "confirmTo": "Confirm to ", | "confirmTo": "Confirm to ", | ||||
| @@ -648,6 +666,8 @@ | |||||
| "connectionError": "Connection error. Please try again.", | "connectionError": "Connection error. Please try again.", | ||||
| "downloadFailed": "Download failed. Please try again.", | "downloadFailed": "Download failed. Please try again.", | ||||
| "systemSettingName": "Name", | |||||
| "muiClear": "Clear", | "muiClear": "Clear", | ||||
| "muiClose": "Close", | "muiClose": "Close", | ||||
| "muiOpen": "Open", | "muiOpen": "Open", | ||||
| @@ -102,7 +102,7 @@ | |||||
| "MSG.registerIAmSmart": "你可点击「智方便」按钮,系统会自动输入个人资料,或自行输入个人资料,以即时启动 公共启事提交及缴费系统 帐户。<br/>如欲使用「智方便」提供个人资料,请先下载「智方便」流动应用程式并登记成为「智方便」用户。", | "MSG.registerIAmSmart": "你可点击「智方便」按钮,系统会自动输入个人资料,或自行输入个人资料,以即时启动 公共启事提交及缴费系统 帐户。<br/>如欲使用「智方便」提供个人资料,请先下载「智方便」流动应用程式并登记成为「智方便」用户。", | ||||
| "MSG.registerPersonal": "需上载身份证明文件数码档案以进行网上申请。 <br/>如:香港身份证; 护照; 中国内地身份证; 专业执业证书等", | "MSG.registerPersonal": "需上载身份证明文件数码档案以进行网上申请。 <br/>如:香港身份证; 护照; 中国内地身份证; 专业执业证书等", | ||||
| "MSG.registerOrg": "需上载以下任何一份证明文件以进行网上申请。 <br/>如:商业登记证;专业执业证书", | |||||
| "MSG.registerOrg": "需上载商业登记证以进行网上申请。", | |||||
| "MSG.paymentMsg": "你的申请和付款已收到", | "MSG.paymentMsg": "你的申请和付款已收到", | ||||
| "MSG.expiredApp": "公共启事申请已过期", | "MSG.expiredApp": "公共启事申请已过期", | ||||
| @@ -329,10 +329,10 @@ | |||||
| "sameAsBusinessRegistrationCert": "与商业登记证相同", | "sameAsBusinessRegistrationCert": "与商业登记证相同", | ||||
| "businessRegCert": "商业登记证", | "businessRegCert": "商业登记证", | ||||
| "businessRegCertNumber": "香港商业登记证号码", | "businessRegCertNumber": "香港商业登记证号码", | ||||
| "businessRegCertAndDoc":"商业登记证及其他文件", | |||||
| "businessRegCertAndDoc":"商业登记证", | |||||
| "businessRegCertExpiryDate": "商业登记证有效期届满日期", | "businessRegCertExpiryDate": "商业登记证有效期届满日期", | ||||
| "pleaseUploadDoc": "请上传你的 有效商业登记证及其他文件 的数码档案,以验证你的身份。", | |||||
| "uploadFile": "上传商业登记证及其他文件", | |||||
| "pleaseUploadDoc": "请上传你的 有效商业登记证 的数码档案,以验证你的身份。", | |||||
| "uploadFile": "上传商业登记证", | |||||
| "pleaseUploadIdDoc": "请上传你的 有效身份证明文件 的数码档案,以验证你的身份。", | "pleaseUploadIdDoc": "请上传你的 有效身份证明文件 的数码档案,以验证你的身份。", | ||||
| "pleaseUploadIdDocSubTitle": "如: 香港身份证; 护照; 中国内地身份证; 专业执业证书等", | "pleaseUploadIdDocSubTitle": "如: 香港身份证; 护照; 中国内地身份证; 专业执业证书等", | ||||
| "uploadIdDoc": "上传身份证明文件", | "uploadIdDoc": "上传身份证明文件", | ||||
| @@ -602,6 +602,24 @@ | |||||
| "nameEng": "名称 (英文)", | "nameEng": "名称 (英文)", | ||||
| "nameChi": "名称 (中文)", | "nameChi": "名称 (中文)", | ||||
| "expiryDate": "屆滿日期", | "expiryDate": "屆滿日期", | ||||
| "brStatusValid": "有效", | |||||
| "brStatusInvalid": "无效", | |||||
| "submitBrCertificate": "提交商业登记证 (BR)", | |||||
| "goToSubmitBr": "去提交证书", | |||||
| "brPopupTitleExpiring": "温馨提示", | |||||
| "brPopupTitleExpired": "系统提示", | |||||
| "brPopupTitlePendingVerify": "系统提示", | |||||
| "brSubmitSuccessNotExpired": "感谢您提交商业登记证,请等待官方审批通过,您仍可以在旧有商业登记证有效期限届满前提交新的公共启事申请。", | |||||
| "brSubmitSuccessExpired": "感谢您提交商业登记证,请等待官方审批通过后,再提交新的公共启事申请。", | |||||
| "selectCertificateFile": "选择证书档案", | |||||
| "newBrExpiryDate": "新商业登记证届满日期", | |||||
| "newBr": "新商业登记证", | |||||
| "newBrInformation": "新商业登记证资料", | |||||
| "confirmNewBr": "确认新商业登记证", | |||||
| "confirmNewBrMessage": "确定以新的商业登记证资料覆盖现有机构资料?", | |||||
| "brExpiredMsg": "贵司的商业登记证已过期。请上传有效的商业登记证。", | |||||
| "brPendingVerifyMsg": "贵司已上传最新的商业登记证,请等待官方审批。", | |||||
| "uploadedFiles": "已上传档案", | |||||
| "create": "创建", | "create": "创建", | ||||
| "confirmTo": "确定", | "confirmTo": "确定", | ||||
| @@ -644,6 +662,8 @@ | |||||
| "connectionError": "连接错误,请稍后再试。", | "connectionError": "连接错误,请稍后再试。", | ||||
| "downloadFailed": "下载失败,请稍后再试。", | "downloadFailed": "下载失败,请稍后再试。", | ||||
| "systemSettingName": "名称", | |||||
| "muiClear": "清除", | "muiClear": "清除", | ||||
| "muiClose": "关闭", | "muiClose": "关闭", | ||||
| "muiOpen": "打开", | "muiOpen": "打开", | ||||
| @@ -102,7 +102,7 @@ | |||||
| "MSG.registerIAmSmart": "你可點擊「智方便」按鈕,系統會自動輸入個人資料,或自行輸入個人資料,以即時啟動 公共啟事提交及繳費系統 帳戶。<br/>如欲使用「智方便」提供個人資料,請先下載「智方便」流動應用程式並登記成為「智方便」用戶。", | "MSG.registerIAmSmart": "你可點擊「智方便」按鈕,系統會自動輸入個人資料,或自行輸入個人資料,以即時啟動 公共啟事提交及繳費系統 帳戶。<br/>如欲使用「智方便」提供個人資料,請先下載「智方便」流動應用程式並登記成為「智方便」用戶。", | ||||
| "MSG.registerPersonal": "需上載身份證明文件數碼檔案以進行網上申請。<br/>如:香港身份證; 護照; 中國內地身份證; 專業執業証書等", | "MSG.registerPersonal": "需上載身份證明文件數碼檔案以進行網上申請。<br/>如:香港身份證; 護照; 中國內地身份證; 專業執業証書等", | ||||
| "MSG.registerOrg": "需上載以下任何一份證明文件以進行網上申請。<br/>如:商業登記證;專業執業證書", | |||||
| "MSG.registerOrg": "需上載商業登記證以進行網上申請。", | |||||
| "MSG.paymentMsg": "你的申請和付款已收到", | "MSG.paymentMsg": "你的申請和付款已收到", | ||||
| "MSG.expiredApp": "公共啟事申請已過期", | "MSG.expiredApp": "公共啟事申請已過期", | ||||
| @@ -324,13 +324,13 @@ | |||||
| "sameAsBusinessRegistrationCert": "與商業登記證相同", | "sameAsBusinessRegistrationCert": "與商業登記證相同", | ||||
| "businessRegCert": "商業登記證", | "businessRegCert": "商業登記證", | ||||
| "businessRegCertNumber": "香港商業登記證號碼", | "businessRegCertNumber": "香港商業登記證號碼", | ||||
| "businessRegCertAndDoc":"商業登記證及其他文件", | |||||
| "businessRegCertAndDoc":"商業登記證", | |||||
| "businessRegCertExpiryDate": "商業登記證有效期屆滿日期", | "businessRegCertExpiryDate": "商業登記證有效期屆滿日期", | ||||
| "pleaseUploadDoc": "請上傳你的 有效商業登記證及其他文件 的數碼檔案,以驗證你的身份。", | |||||
| "pleaseUploadDoc": "請上傳你的 有效商業登記證 的數碼檔案,以驗證你的身份。", | |||||
| "pleaseUploadIdDoc": "請上傳你的 有效身份證明文件 的數碼檔案,以驗證你的身份。", | "pleaseUploadIdDoc": "請上傳你的 有效身份證明文件 的數碼檔案,以驗證你的身份。", | ||||
| "pleaseUploadIdDocSubTitle": "如: 香港身份證; 護照; 中國內地身份證; 專業執業証書等", | "pleaseUploadIdDocSubTitle": "如: 香港身份證; 護照; 中國內地身份證; 專業執業証書等", | ||||
| "uploadIdDoc": "上傳身份證明文件", | "uploadIdDoc": "上傳身份證明文件", | ||||
| "uploadFile": "上傳商業登記證及其他文件", | |||||
| "uploadFile": "上傳商業登記證", | |||||
| "fileName": "檔案名稱", | "fileName": "檔案名稱", | ||||
| "forOrgUser": "機構/公司用戶", | "forOrgUser": "機構/公司用戶", | ||||
| "forIndUser": "個人用戶", | "forIndUser": "個人用戶", | ||||
| @@ -603,6 +603,24 @@ | |||||
| "nameEng": "名稱 (英文)", | "nameEng": "名稱 (英文)", | ||||
| "nameChi": "名稱 (中文)", | "nameChi": "名稱 (中文)", | ||||
| "expiryDate": "屆滿日期", | "expiryDate": "屆滿日期", | ||||
| "brStatusValid": "有效", | |||||
| "brStatusInvalid": "無效", | |||||
| "submitBrCertificate": "提交商業登記證 (BR)", | |||||
| "goToSubmitBr": "去提交證書", | |||||
| "brPopupTitleExpiring": "溫馨提示", | |||||
| "brPopupTitleExpired": "系統提示", | |||||
| "brPopupTitlePendingVerify": "系統提示", | |||||
| "brSubmitSuccessNotExpired": "感謝您提交商業登記證,請等待官方審批通過,您仍可以在舊有商業登記證有效期限屆滿前提交新的公共啟事申請。", | |||||
| "brSubmitSuccessExpired": "感謝您提交商業登記證,請等待官方審批通過後,再提交新的公共啟事申請。", | |||||
| "selectCertificateFile": "選擇證書檔案", | |||||
| "newBrExpiryDate": "新商業登記證屆滿日期", | |||||
| "newBr": "新商業登記證", | |||||
| "newBrInformation": "新商業登記證資料", | |||||
| "confirmNewBr": "確認新商業登記證", | |||||
| "confirmNewBrMessage": "確定以新的商業登記證資料覆寫現有機構資料?", | |||||
| "brExpiredMsg": "貴司的商業登記證已過期。請上傳有效的商業登記證。", | |||||
| "brPendingVerifyMsg": "貴司已上傳最新的商業登記證,請等待官方審批。", | |||||
| "uploadedFiles": "已上載檔案", | |||||
| "create": "創建", | "create": "創建", | ||||
| "confirmTo": "確定", | "confirmTo": "確定", | ||||
| @@ -645,6 +663,8 @@ | |||||
| "connectionError": "連線錯誤,請稍後再試。", | "connectionError": "連線錯誤,請稍後再試。", | ||||
| "downloadFailed": "下載失敗,請稍後再試。", | "downloadFailed": "下載失敗,請稍後再試。", | ||||
| "systemSettingName": "名稱", | |||||
| "muiClear": "清除", | "muiClear": "清除", | ||||
| "muiClose": "關閉", | "muiClose": "關閉", | ||||
| "muiOpen": "開啟", | "muiOpen": "開啟", | ||||
| @@ -9,6 +9,7 @@ export const LOGOUT = "/logout" | |||||
| export const CHANGE_PASSWORD_PATH = "/user/change-password" | export const CHANGE_PASSWORD_PATH = "/user/change-password" | ||||
| export const GET_SYS_PARAMS = apiPath+'/settings'; | export const GET_SYS_PARAMS = apiPath+'/settings'; | ||||
| export const GET_SYS_PARAM_NAMES = apiPath+'/settings/combo-name'; | |||||
| export const PRIVACY_POLICY_PATH = apiPath+'/privacyPolicy'; | export const PRIVACY_POLICY_PATH = apiPath+'/privacyPolicy'; | ||||
| export const UPDATE_PAYMENT_SUSPENSION_MODE = apiPath+'/settings/update-payment-suspension'; | export const UPDATE_PAYMENT_SUSPENSION_MODE = apiPath+'/settings/update-payment-suspension'; | ||||
| export const GET_PAYMENT_SUSPENSION_MODE = apiPath+'/settings/get-payment-suspension'; | export const GET_PAYMENT_SUSPENSION_MODE = apiPath+'/settings/get-payment-suspension'; | ||||
| @@ -59,6 +60,8 @@ export const GET_SEND_OVERDUE_CREDITOR_LIST = apiPath+'/org/sendDn_OverdueCredit | |||||
| //public | //public | ||||
| export const GET_PUB_ORG_PATH = apiPath+'/org/pub'; | export const GET_PUB_ORG_PATH = apiPath+'/org/pub'; | ||||
| export const POST_PUB_ORG_SAVE_PATH = apiPath+'/org/pub/save'; | export const POST_PUB_ORG_SAVE_PATH = apiPath+'/org/pub/save'; | ||||
| export const POST_PUB_ORG_SUBMIT_BR = apiPath+'/org/pub/submit-br'; | |||||
| export const POST_ORG_CONFIRM_BR = apiPath+'/org'; | |||||
| export const GET_PUB_ORG_MARK_AS_CREDITOR = apiPath+'/org/pub/mark-as-creditor'; | export const GET_PUB_ORG_MARK_AS_CREDITOR = apiPath+'/org/pub/mark-as-creditor'; | ||||
| export const GET_PUB_ORG_MARK_AS_NON_CREDITOR = apiPath+'/org/pub/mark-as-non-creditor'; | export const GET_PUB_ORG_MARK_AS_NON_CREDITOR = apiPath+'/org/pub/mark-as-non-creditor'; | ||||
| @@ -26,7 +26,7 @@ export const getDateField = ({ label, valueName, form, disabled }) => { | |||||
| </Grid>; | </Grid>; | ||||
| } | } | ||||
| export const getTextField = ({ label, valueName, form, disabled, autoFocus }) => { | |||||
| export const getTextField = ({ label, valueName, form, disabled, autoFocus, displayValue }) => { | |||||
| return <Grid container alignItems={"center"} sx={{mb:2}}> | return <Grid container alignItems={"center"} sx={{mb:2}}> | ||||
| <Grid item xs={12} sm={12} md={12} lg={12}> | <Grid item xs={12} sm={12} md={12} lg={12}> | ||||
| <Grid container spacing={1}> | <Grid container spacing={1}> | ||||
| @@ -40,7 +40,8 @@ export const getTextField = ({ label, valueName, form, disabled, autoFocus }) => | |||||
| valueName: valueName, | valueName: valueName, | ||||
| form: form, | form: form, | ||||
| disabled: disabled, | disabled: disabled, | ||||
| autoFocus:autoFocus | |||||
| autoFocus:autoFocus, | |||||
| displayValue: displayValue | |||||
| })} | })} | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| @@ -233,7 +234,7 @@ export const getProfileComboField = ({ label, dataList, valueName, form, disable | |||||
| </Grid>; | </Grid>; | ||||
| } | } | ||||
| export const initField = ({ type, valueName, form, disabled, autoFocus, multiline, handleChange, placeholder, inputProps, InputProps, width, ...props }) => { | |||||
| export const initField = ({ type, valueName, form, disabled, autoFocus, multiline, handleChange, placeholder, inputProps, InputProps, width, displayValue, ...props }) => { | |||||
| let err = Boolean(form.errors[valueName]); | let err = Boolean(form.errors[valueName]); | ||||
| return <TextField | return <TextField | ||||
| @@ -248,7 +249,7 @@ export const initField = ({ type, valueName, form, disabled, autoFocus, multilin | |||||
| error={err} | error={err} | ||||
| helperText={form.errors[valueName] ? form.errors[valueName] : ''} | helperText={form.errors[valueName] ? form.errors[valueName] : ''} | ||||
| onChange={handleChange ? handleChange : form.handleChange} | onChange={handleChange ? handleChange : form.handleChange} | ||||
| value={form.values[valueName]} | |||||
| value={displayValue !== undefined ? displayValue : form.values[valueName]} | |||||
| disabled={disabled} | disabled={disabled} | ||||
| autoFocus={autoFocus} | autoFocus={autoFocus} | ||||
| sx={{ | sx={{ | ||||
| @@ -0,0 +1,56 @@ | |||||
| import * as HttpUtils from "utils/HttpUtils"; | |||||
| import { GET_PUB_ORG_PATH } from "utils/ApiPathConst"; | |||||
| import { isORGLoggedIn } from "utils/Utils"; | |||||
| export const fetchOrgBrData = ({ onSuccess }) => { | |||||
| if (!isORGLoggedIn()) { | |||||
| onSuccess(null); | |||||
| return; | |||||
| } | |||||
| HttpUtils.get({ | |||||
| url: GET_PUB_ORG_PATH, | |||||
| onSuccess: (response) => onSuccess(response?.data || null), | |||||
| onFail: () => onSuccess(null), | |||||
| onError: () => onSuccess(null), | |||||
| }); | |||||
| }; | |||||
| export const applyBlockVariant = (org) => { | |||||
| if (!org) { | |||||
| return null; | |||||
| } | |||||
| if (org.applyBlockState === "expired") { | |||||
| return "expired"; | |||||
| } | |||||
| if (org.applyBlockState === "pending") { | |||||
| return "pending"; | |||||
| } | |||||
| return null; | |||||
| }; | |||||
| export const applyClickVariant = (org) => { | |||||
| if (!org) { | |||||
| return null; | |||||
| } | |||||
| const blocked = applyBlockVariant(org); | |||||
| if (blocked) { | |||||
| return blocked; | |||||
| } | |||||
| if (org.showExpiringPopup) { | |||||
| return "expiring"; | |||||
| } | |||||
| return null; | |||||
| }; | |||||
| export const loginPopupVariant = (org) => { | |||||
| if (!org) { | |||||
| return null; | |||||
| } | |||||
| if (org.applyBlockState === "expired") { | |||||
| return "expired"; | |||||
| } | |||||
| if (org.showExpiringPopup) { | |||||
| return "expiring"; | |||||
| } | |||||
| return null; | |||||
| }; | |||||