瀏覽代碼

CR026 Item 2 upload BR

CR026
Jason Chuang 1 天之前
父節點
當前提交
0294c7c8ad
共有 19 個文件被更改,包括 1021 次插入41 次删除
  1. +130
    -0
      src/components/BrStatusDialog.js
  2. +140
    -9
      src/pages/Organization/DetailPage/OrganizationCard.js
  3. +21
    -4
      src/pages/Organization/DetailPage/OrganizationPubCard.js
  4. +6
    -0
      src/pages/Organization/DetailPage/index.js
  5. +30
    -0
      src/pages/Organization/SearchPage/OrganizationTable.js
  6. +388
    -0
      src/pages/Organization/SubmitBrPage/index.js
  7. +15
    -3
      src/pages/PublicNotice/ApplyForm/index.js
  8. +5
    -3
      src/pages/PublicNotice/ListPanel/PendingPaymentTab.js
  9. +28
    -0
      src/pages/PublicNotice/ListPanel/index.js
  10. +64
    -4
      src/pages/Setting/SystemSetting/Table.js
  11. +37
    -1
      src/pages/dashboard/Public/index.js
  12. +5
    -0
      src/routes/PublicUserRoutes.js
  13. +15
    -0
      src/themes/colorConst.js
  14. +25
    -5
      src/translations/en.json
  15. +24
    -4
      src/translations/zh-CN.json
  16. +24
    -4
      src/translations/zh-HK.json
  17. +3
    -0
      src/utils/ApiPathConst.js
  18. +5
    -4
      src/utils/FieldUtils.js
  19. +56
    -0
      src/utils/orgBrUtils.js

+ 130
- 0
src/components/BrStatusDialog.js 查看文件

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

+ 140
- 9
src/pages/Organization/DetailPage/OrganizationCard.js 查看文件

@@ -22,8 +22,10 @@ import Loadable from 'components/Loadable';
import { notifySaveSuccess } from 'utils/CommonFunction';
import { useIntl } from "react-intl";
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 { isGrantedAny } from "auth/utils";
import FileList from "components/FileList";

import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import dayjs from "dayjs";
@@ -36,6 +38,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => {
const [creditorConfirmPopUp, setCreditorConfirmPopUp] = React.useState(false);
const [nonCreditorConfirmPopUp, setNonCreditorConfirmPopUp] = React.useState(false);
const [afterSendPopUp, setAfterSendPopUp] = React.useState(false);
const [confirmBrPopUp, setConfirmBrPopUp] = React.useState(false);

const [currentUserData, setCurrentUserData] = useState({});
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 (
<MainCard elevation={0}
border={false}
@@ -316,6 +330,19 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => {
</Button>
</ThemeProvider>
</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 ?
@@ -324,7 +351,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => {
<ThemeProvider theme={PNSPS_BUTTON_THEME}>
<Button
variant="contained"
color="error"
color="success"
onClick={() => setNonCreditorConfirmPopUp(true)}
>
Mark as Non-Credit Client
@@ -338,7 +365,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => {
<ThemeProvider theme={PNSPS_BUTTON_THEME}>
<Button
variant="contained"
color="orange"
color="success"
onClick={() => setCreditorConfirmPopUp(true)}
>
Mark as Credit Client
@@ -351,7 +378,7 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => {
<ThemeProvider theme={PNSPS_BUTTON_THEME}>
<Button
variant="contained"
color="primary"
color="success"
onClick={() => sendDn_Overdue(true)}
>
Generate O&#47;S DN List
@@ -440,10 +467,16 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => {
<TextField
fullWidth
id="currentExDate"
// error={(fromDate===null)}
// type="date"
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}
/> :
<LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}>
@@ -480,9 +513,6 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => {
{
fromDate == null ?
(!editMode && !createMode) ?
// <FormHelperText error id="helper-text-date">
// Please select a date after today.
// </FormHelperText>
null
:
<FormHelperText error id="helper-text-date">
@@ -575,6 +605,81 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => {
})}
</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>
@@ -668,6 +773,32 @@ const OrganizationCard = ({ userData, loadDataFun, id, setEditModeFun }) => {
</DialogActions>
</Dialog>
</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>
);
};


+ 21
- 4
src/pages/Organization/DetailPage/OrganizationPubCard.js 查看文件

@@ -23,12 +23,14 @@ import { notifySaveSuccess } from 'utils/CommonFunction';
import { FormattedMessage, useIntl } from "react-intl";
import { PNSPS_BUTTON_THEME } from "themes/buttonConst";
import { ThemeProvider } from "@emotion/react";
import { useNavigate } from "react-router-dom";

// ==============================|| DASHBOARD - DEFAULT ||============================== //


const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => {
const intl = useIntl();
const navigate = useNavigate();
const [creditorConfirmPopUp, setCreditorConfirmPopUp] = React.useState(false);
const [nonCreditorConfirmPopUp, setNonCreditorConfirmPopUp] = React.useState(false);

@@ -200,6 +202,19 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => {
</Button>
</ThemeProvider>
</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>
@@ -235,13 +250,15 @@ const OrganizationPubCard = ({ userData, loadDataFun, id, setEditModeFun }) => {
label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: 'expiryDate' }) + ":"),
valueName: "brExpiryDate",
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 item xs={12} lg={4} ></Grid>

<Grid item xs={12} lg={4} >
{FieldUtils.getTextField({
label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: 'nameEng' }) + ":"),


+ 6
- 0
src/pages/Organization/DetailPage/index.js 查看文件

@@ -90,6 +90,12 @@ const OrganizationDetailPage = () => {
response.data["brExpiryDate"] = response.data.brExpiryDate ? DateUtils.dateValue(response.data.brExpiryDate) : "";
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)
setList(response.historyList)
}


+ 30
- 0
src/pages/Organization/SearchPage/OrganizationTable.js 查看文件

@@ -9,11 +9,13 @@ import { useNavigate } from "react-router-dom";
import * as DateUtils from "utils/DateUtils";
import { clickableLink} from 'utils/CommonFunction';
import {GET_ORG_PATH} from "utils/ApiPathConst";
import { useIntl } from "react-intl";
// ==============================|| EVENT TABLE ||============================== //

export default function OrganizationTable({ searchCriteria, applyGridOnReady, applySearch}) {
const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria);
const navigate = useNavigate()
const intl = useIntl();

React.useEffect(() => {
set_searchCriteria(searchCriteria);
@@ -91,6 +93,34 @@ export default function OrganizationTable({ searchCriteria, applyGridOnReady, ap
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',
field: 'creditor',


+ 388
- 0
src/pages/Organization/SubmitBrPage/index.js 查看文件

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

+ 15
- 3
src/pages/PublicNotice/ApplyForm/index.js 查看文件

@@ -5,6 +5,7 @@ import * as UrlUtils from "utils/ApiPathConst";
import * as FormatUtils from "utils/FormatUtils";
import * as DateUtils from "utils/DateUtils";
import { useIntl } from "react-intl";
import { useNavigate } from "react-router-dom";

import {
Grid,
@@ -24,6 +25,7 @@ import {
// checkIsOnlyOnlinePayment
// isCreditorLoggedIn
} from "utils/Utils";
import { fetchOrgBrData, applyBlockVariant } from "utils/orgBrUtils";
// ==============================|| DASHBOARD - DEFAULT ||============================== //

const ApplyForm = () => {
@@ -35,12 +37,23 @@ const ApplyForm = () => {
const [selections, setSelection] = React.useState([]);
const [isLoading, setLoding] = React.useState(true);
const [orgBrReady, setOrgBrReady] = React.useState(false);

const intl = useIntl();
const navigate = useNavigate();
const { locale } = intl;

React.useEffect(() => {
loadUserData();
fetchOrgBrData({
onSuccess: (org) => {
if (applyBlockVariant(org)) {
navigate("/dashboard", { replace: true });
return;
}
setOrgBrReady(true);
}
});
}, []);

const loadUserData = () => {
@@ -112,11 +125,10 @@ const ApplyForm = () => {


React.useEffect(() => {
if (userData !== null){
if (userData !== null && orgBrReady){
setLoding(false);
// console.log(isOnlyOnlinePayment)
}
}, [userData]);
}, [userData, orgBrReady]);

return (
isLoading ?


+ 5
- 3
src/pages/PublicNotice/ListPanel/PendingPaymentTab.js 查看文件

@@ -398,9 +398,11 @@ export default function SubmittedTab({ setCount, url }) {
<>
<div style={{ minHeight: 400, width: '100%', padding: 4 }}>
{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 item xs={8} md={2}>
<Autocomplete


+ 28
- 0
src/pages/PublicNotice/ListPanel/index.js 查看文件

@@ -32,6 +32,8 @@ import { PNSPS_LONG_BUTTON_THEME } from "../../../themes/buttonConst";
import { ThemeProvider } from "@emotion/react";
import { FormattedMessage, useIntl } from "react-intl";
import usePageTitle from 'components/usePageTitle';
import { fetchOrgBrData, applyBlockVariant, applyClickVariant } from "utils/orgBrUtils";
import BrStatusDialog from "components/BrStatusDialog";
// ==============================|| DASHBOARD - DEFAULT ||============================== //

const PublicNotice = () => {
@@ -44,6 +46,8 @@ const PublicNotice = () => {
const [selectedTab, setSelectedTab] = useState("1");
const navigate = useNavigate();
const intl = useIntl();
const [orgBrData, setOrgBrData] = useState(null);
const [brDialog, setBrDialog] = useState({ open: false, variant: null, afterClose: null });

const _sx = {
padding: "4 2 4 2",
@@ -76,6 +80,9 @@ const PublicNotice = () => {

useEffect(() => {
loadData();
fetchOrgBrData({
onSuccess: (org) => setOrgBrData(org)
});
}, []);

const loadData = () => {
@@ -99,6 +106,15 @@ const PublicNotice = () => {
}

const onBtnClick = () => {
const variant = applyClickVariant(orgBrData);
if (variant) {
setBrDialog({
open: true,
variant,
afterClose: applyBlockVariant(orgBrData) ? "stay" : "apply",
});
return;
}
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>
);
};


+ 64
- 4
src/pages/Setting/SystemSetting/Table.js 查看文件

@@ -1,22 +1,51 @@
// material-ui
import {
// Box,
Autocomplete,
TextField,
Typography
} from '@mui/material';
import MainCard from "components/MainCard";
import * as React from "react";
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 * 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 ||============================== //
const Table = ({onRowClick, searchCriteria, refreshTrigger}) => {
const intl = useIntl();
const [_searchCriteria, set_searchCriteria] = React.useState(searchCriteria);
const [nameOptions, setNameOptions] = React.useState([]);
const [selectedName, setSelectedName] = React.useState("");

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 = [
{
@@ -61,6 +90,37 @@ const Table = ({onRowClick, searchCriteria, refreshTrigger}) => {
System Params
</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" }, }}>
{/* <Box width= '100%' sx={{ backgroundColor: "#fff", ml: 2 }} height='100%'> */}
<FiDataGrid


+ 37
- 1
src/pages/dashboard/Public/index.js 查看文件

@@ -20,6 +20,8 @@ import * as HttpUtils from "utils/HttpUtils";
import * as UrlUtils from "utils/ApiPathConst";
import * as DateUtils from "utils/DateUtils";
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 Notice = Loadable(React.lazy(() => import('./Notice')));
@@ -56,10 +58,21 @@ const DashboardDefault = () => {
const [itemList, setItemList] = React.useState([]);
const [listData, setListData] = React.useState([]);
const [isPopUp, setIsPopUp] = React.useState(false);
const [orgBrData, setOrgBrData] = React.useState(null);
const [brDialog, setBrDialog] = React.useState({ open: false, variant: null });
React.useEffect(() => {
loadMessageData()
loadNoticeData()
fetchOrgBrData({
onSuccess: (org) => {
setOrgBrData(org);
const variant = loginPopupVariant(org);
if (variant) {
setBrDialog({ open: true, variant, afterClose: "stay" });
}
}
});
localStorage.setItem('searchCriteria',"")
}, []);
@@ -199,7 +212,18 @@ const DashboardDefault = () => {
<Grid item xs={12} lg={5} sx={{ pt: 2 }} style={{ height: '100%' }}>
<Button
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' })}
sx={{
width: "100%",
@@ -306,6 +330,18 @@ const DashboardDefault = () => {
</DialogActions>
</Dialog>
</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>
);
};


+ 5
- 0
src/routes/PublicUserRoutes.js 查看文件

@@ -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_Organization = Loadable(lazy(() => import('pages/User/DetailsPage_Organization')));
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_Search = Loadable(lazy(() => import('pages/Message/Search')));
const AnnouncementSearch = Loadable(lazy(() => import('pages/Announcement/Search_Public')));
@@ -126,6 +127,10 @@ const PublicDashboard = {
path: '/orgUser',
element: <UserMaintainPage_Organization />
},
{
path: '/org/submit-br',
element: <SubmitBrPage />
},
{
path: '/org',
element: <OrganizationDetailPage />


+ 15
- 0
src/themes/colorConst.js 查看文件

@@ -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 = {
backgroundColor: CONTAINED_PRIMARY_BLUE,
color: '#FFFFFF',


+ 25
- 5
src/translations/en.json 查看文件

@@ -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.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.expiredApp": "Public Notice application has expired",

@@ -286,10 +286,10 @@
"sameAsBusinessRegistrationCert": "Same as Business Registration Certificate",
"businessRegCert": "Business Registration Certificate",
"businessRegCertNumber": "Hong Kong Business Reg Cert Number",
"businessRegCertAndDoc":"Business Registration Certificate and other documents",
"businessRegCertAndDoc":"Business Registration Certificate",
"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.",
"pleaseUploadIdDocSubTitle": "Such as: Hong Kong ID card; passport; Mainland China ID card; professional practice certificate, etc.",
"uploadIdDoc": "Upload identity document",
@@ -321,7 +321,7 @@
"pleaseFillInBusinessRegCertNumber": "Please fill in Business Registration Certificate Number",
"pleaseFillInValidBusinessRegCertNumber": "Please fill in valid Business Registration Certificate Number",
"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",
"addressLine1": "First line of address",
"addressLine2": "Second line of address",
@@ -606,6 +606,24 @@
"nameEng": "Name (Eng)",
"nameChi": "Name (Chi)",
"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",
"confirmTo": "Confirm to ",

@@ -648,6 +666,8 @@
"connectionError": "Connection error. Please try again.",
"downloadFailed": "Download failed. Please try again.",

"systemSettingName": "Name",

"muiClear": "Clear",
"muiClose": "Close",
"muiOpen": "Open",


+ 24
- 4
src/translations/zh-CN.json 查看文件

@@ -102,7 +102,7 @@
"MSG.registerIAmSmart": "你可点击「智方便」按钮,系统会自动输入个人资料,或自行输入个人资料,以即时启动 公共启事提交及缴费系统 帐户。<br/>如欲使用「智方便」提供个人资料,请先下载「智方便」流动应用程式并登记成为「智方便」用户。",
"MSG.registerPersonal": "需上载身份证明文件数码档案以进行网上申请。 <br/>如:香港身份证; 护照; 中国内地身份证; 专业执业证书等",
"MSG.registerOrg": "需上载以下任何一份证明文件以进行网上申请。 <br/>如:商业登记证;专业执业证书",
"MSG.registerOrg": "需上载商业登记证以进行网上申请。",
"MSG.paymentMsg": "你的申请和付款已收到",
"MSG.expiredApp": "公共启事申请已过期",

@@ -329,10 +329,10 @@
"sameAsBusinessRegistrationCert": "与商业登记证相同",
"businessRegCert": "商业登记证",
"businessRegCertNumber": "香港商业登记证号码",
"businessRegCertAndDoc":"商业登记证及其他文件",
"businessRegCertAndDoc":"商业登记证",
"businessRegCertExpiryDate": "商业登记证有效期届满日期",
"pleaseUploadDoc": "请上传你的 有效商业登记证及其他文件 的数码档案,以验证你的身份。",
"uploadFile": "上传商业登记证及其他文件",
"pleaseUploadDoc": "请上传你的 有效商业登记证 的数码档案,以验证你的身份。",
"uploadFile": "上传商业登记证",
"pleaseUploadIdDoc": "请上传你的 有效身份证明文件 的数码档案,以验证你的身份。",
"pleaseUploadIdDocSubTitle": "如: 香港身份证; 护照; 中国内地身份证; 专业执业证书等",
"uploadIdDoc": "上传身份证明文件",
@@ -602,6 +602,24 @@
"nameEng": "名称 (英文)",
"nameChi": "名称 (中文)",
"expiryDate": "屆滿日期",
"brStatusValid": "有效",
"brStatusInvalid": "无效",
"submitBrCertificate": "提交商业登记证 (BR)",
"goToSubmitBr": "去提交证书",
"brPopupTitleExpiring": "温馨提示",
"brPopupTitleExpired": "系统提示",
"brPopupTitlePendingVerify": "系统提示",
"brSubmitSuccessNotExpired": "感谢您提交商业登记证,请等待官方审批通过,您仍可以在旧有商业登记证有效期限届满前提交新的公共启事申请。",
"brSubmitSuccessExpired": "感谢您提交商业登记证,请等待官方审批通过后,再提交新的公共启事申请。",
"selectCertificateFile": "选择证书档案",
"newBrExpiryDate": "新商业登记证届满日期",
"newBr": "新商业登记证",
"newBrInformation": "新商业登记证资料",
"confirmNewBr": "确认新商业登记证",
"confirmNewBrMessage": "确定以新的商业登记证资料覆盖现有机构资料?",
"brExpiredMsg": "贵司的商业登记证已过期。请上传有效的商业登记证。",
"brPendingVerifyMsg": "贵司已上传最新的商业登记证,请等待官方审批。",
"uploadedFiles": "已上传档案",
"create": "创建",
"confirmTo": "确定",

@@ -644,6 +662,8 @@
"connectionError": "连接错误,请稍后再试。",
"downloadFailed": "下载失败,请稍后再试。",

"systemSettingName": "名称",

"muiClear": "清除",
"muiClose": "关闭",
"muiOpen": "打开",


+ 24
- 4
src/translations/zh-HK.json 查看文件

@@ -102,7 +102,7 @@

"MSG.registerIAmSmart": "你可點擊「智方便」按鈕,系統會自動輸入個人資料,或自行輸入個人資料,以即時啟動 公共啟事提交及繳費系統 帳戶。<br/>如欲使用「智方便」提供個人資料,請先下載「智方便」流動應用程式並登記成為「智方便」用戶。",
"MSG.registerPersonal": "需上載身份證明文件數碼檔案以進行網上申請。<br/>如:香港身份證; 護照; 中國內地身份證; 專業執業証書等",
"MSG.registerOrg": "需上載以下任何一份證明文件以進行網上申請。<br/>如:商業登記證;專業執業證書",
"MSG.registerOrg": "需上載商業登記證以進行網上申請。",
"MSG.paymentMsg": "你的申請和付款已收到",
"MSG.expiredApp": "公共啟事申請已過期",

@@ -324,13 +324,13 @@
"sameAsBusinessRegistrationCert": "與商業登記證相同",
"businessRegCert": "商業登記證",
"businessRegCertNumber": "香港商業登記證號碼",
"businessRegCertAndDoc":"商業登記證及其他文件",
"businessRegCertAndDoc":"商業登記證",
"businessRegCertExpiryDate": "商業登記證有效期屆滿日期",
"pleaseUploadDoc": "請上傳你的 有效商業登記證及其他文件 的數碼檔案,以驗證你的身份。",
"pleaseUploadDoc": "請上傳你的 有效商業登記證 的數碼檔案,以驗證你的身份。",
"pleaseUploadIdDoc": "請上傳你的 有效身份證明文件 的數碼檔案,以驗證你的身份。",
"pleaseUploadIdDocSubTitle": "如: 香港身份證; 護照; 中國內地身份證; 專業執業証書等",
"uploadIdDoc": "上傳身份證明文件",
"uploadFile": "上傳商業登記證及其他文件",
"uploadFile": "上傳商業登記證",
"fileName": "檔案名稱",
"forOrgUser": "機構/公司用戶",
"forIndUser": "個人用戶",
@@ -603,6 +603,24 @@
"nameEng": "名稱 (英文)",
"nameChi": "名稱 (中文)",
"expiryDate": "屆滿日期",
"brStatusValid": "有效",
"brStatusInvalid": "無效",
"submitBrCertificate": "提交商業登記證 (BR)",
"goToSubmitBr": "去提交證書",
"brPopupTitleExpiring": "溫馨提示",
"brPopupTitleExpired": "系統提示",
"brPopupTitlePendingVerify": "系統提示",
"brSubmitSuccessNotExpired": "感謝您提交商業登記證,請等待官方審批通過,您仍可以在舊有商業登記證有效期限屆滿前提交新的公共啟事申請。",
"brSubmitSuccessExpired": "感謝您提交商業登記證,請等待官方審批通過後,再提交新的公共啟事申請。",
"selectCertificateFile": "選擇證書檔案",
"newBrExpiryDate": "新商業登記證屆滿日期",
"newBr": "新商業登記證",
"newBrInformation": "新商業登記證資料",
"confirmNewBr": "確認新商業登記證",
"confirmNewBrMessage": "確定以新的商業登記證資料覆寫現有機構資料?",
"brExpiredMsg": "貴司的商業登記證已過期。請上傳有效的商業登記證。",
"brPendingVerifyMsg": "貴司已上傳最新的商業登記證,請等待官方審批。",
"uploadedFiles": "已上載檔案",
"create": "創建",
"confirmTo": "確定",

@@ -645,6 +663,8 @@
"connectionError": "連線錯誤,請稍後再試。",
"downloadFailed": "下載失敗,請稍後再試。",

"systemSettingName": "名稱",

"muiClear": "清除",
"muiClose": "關閉",
"muiOpen": "開啟",


+ 3
- 0
src/utils/ApiPathConst.js 查看文件

@@ -9,6 +9,7 @@ export const LOGOUT = "/logout"
export const CHANGE_PASSWORD_PATH = "/user/change-password"

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 UPDATE_PAYMENT_SUSPENSION_MODE = apiPath+'/settings/update-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
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_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_NON_CREDITOR = apiPath+'/org/pub/mark-as-non-creditor';



+ 5
- 4
src/utils/FieldUtils.js 查看文件

@@ -26,7 +26,7 @@ export const getDateField = ({ label, valueName, form, disabled }) => {
</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}}>
<Grid item xs={12} sm={12} md={12} lg={12}>
<Grid container spacing={1}>
@@ -40,7 +40,8 @@ export const getTextField = ({ label, valueName, form, disabled, autoFocus }) =>
valueName: valueName,
form: form,
disabled: disabled,
autoFocus:autoFocus
autoFocus:autoFocus,
displayValue: displayValue
})}
</Grid>
</Grid>
@@ -233,7 +234,7 @@ export const getProfileComboField = ({ label, dataList, valueName, form, disable
</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]);

return <TextField
@@ -248,7 +249,7 @@ export const initField = ({ type, valueName, form, disabled, autoFocus, multilin
error={err}
helperText={form.errors[valueName] ? form.errors[valueName] : ''}
onChange={handleChange ? handleChange : form.handleChange}
value={form.values[valueName]}
value={displayValue !== undefined ? displayValue : form.values[valueName]}
disabled={disabled}
autoFocus={autoFocus}
sx={{


+ 56
- 0
src/utils/orgBrUtils.js 查看文件

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

Loading…
取消
儲存