Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 

390 wiersze
21 KiB

  1. import { Grid, Typography, Stack, Box, Button, FormHelperText, CircularProgress } from '@mui/material';
  2. import * as React from "react";
  3. import { useFormik } from 'formik';
  4. import * as yup from 'yup';
  5. import * as HttpUtils from "utils/HttpUtils";
  6. import * as UrlUtils from "utils/ApiPathConst";
  7. import * as DateUtils from "utils/DateUtils";
  8. import * as FieldUtils from "utils/FieldUtils";
  9. import * as ComboData from "utils/ComboData";
  10. import { getObjectByType } from "utils/CommonFunction";
  11. import { isORGLoggedIn, isPrimaryLoggedIn } from "utils/Utils";
  12. import Loadable from "components/Loadable";
  13. import { lazy } from "react";
  14. import MainCard from "components/MainCard";
  15. import ForwardIcon from "@mui/icons-material/Forward";
  16. import { useNavigate } from "react-router-dom";
  17. import titleBackgroundImg from "assets/images/dashboard/gazette-bar.png";
  18. import { FormattedMessage, useIntl } from "react-intl";
  19. import usePageTitle from "components/usePageTitle";
  20. import { PNSPS_BUTTON_THEME } from "themes/buttonConst";
  21. import { PRIMARY_CONTAINED_BUTTON_SX } from "themes/colorConst";
  22. import { ThemeProvider } from "@emotion/react";
  23. import { DatePicker } from "@mui/x-date-pickers/DatePicker";
  24. import dayjs from "dayjs";
  25. import { DemoItem } from "@mui/x-date-pickers/internals/demo";
  26. import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
  27. import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
  28. import { Dialog, DialogTitle, DialogContent, DialogActions } from "@mui/material";
  29. import BrStatusDialog from "components/BrStatusDialog";
  30. const LoadingComponent = Loadable(lazy(() => import("pages/extra-pages/LoadingComponent")));
  31. const UploadFileTable = Loadable(lazy(() => import("pages/Proof/Reply_Public/UploadFileTable")));
  32. const BackgroundHead = {
  33. backgroundImage: `url(${titleBackgroundImg})`,
  34. width: "100%",
  35. height: "100%",
  36. backgroundSize: "contain",
  37. backgroundRepeat: "no-repeat",
  38. backgroundColor: "#0C489E",
  39. backgroundPosition: "right"
  40. };
  41. const SubmitBrPage = () => {
  42. usePageTitle("submitBrCertificate");
  43. const intl = useIntl();
  44. const navigate = useNavigate();
  45. const [formData, setFormData] = React.useState({});
  46. const [isLoading, setLoading] = React.useState(true);
  47. const [errorMsg, setErrorMsg] = React.useState("");
  48. const [attachments, setAttachments] = React.useState([]);
  49. const attachmentsRef = React.useRef([]);
  50. attachmentsRef.current = attachments;
  51. const [warningText, setWarningText] = React.useState("");
  52. const [isWarningPopUp, setIsWarningPopUp] = React.useState(false);
  53. const [showSubmittedDialog, setShowSubmittedDialog] = React.useState(false);
  54. const fileInputRef = React.useRef(null);
  55. const minDate = React.useMemo(() => new Date().setDate(new Date().getDate() + 1), []);
  56. React.useEffect(() => {
  57. if (!isORGLoggedIn()) {
  58. navigate("/dashboard");
  59. return;
  60. }
  61. HttpUtils.get({
  62. url: UrlUtils.GET_PUB_ORG_PATH,
  63. onSuccess: (response) => {
  64. const data = response.data || {};
  65. if (!data.canSubmitBr) {
  66. navigate(isPrimaryLoggedIn() ? "/org" : "/dashboard");
  67. return;
  68. }
  69. data.country = getObjectByType(ComboData.country, "type", data.addressTemp?.country);
  70. data.district = getObjectByType(ComboData.district, "type", data.addressTemp?.district);
  71. data.addressLine1 = data.addressTemp?.addressLine1;
  72. data.addressLine2 = data.addressTemp?.addressLine2;
  73. data.addressLine3 = data.addressTemp?.addressLine3;
  74. setFormData(data);
  75. setLoading(false);
  76. },
  77. onFail: () => {
  78. navigate("/dashboard");
  79. },
  80. onError: () => {
  81. navigate("/dashboard");
  82. }
  83. });
  84. }, [navigate]);
  85. const displayErrorMsg = (msg) => <Typography variant="errorMessage1">{msg}</Typography>;
  86. const getFileExtension = (fileName) => {
  87. const name = (fileName || "").toLowerCase();
  88. const dot = name.lastIndexOf(".");
  89. if (dot <= 0 || dot === name.length - 1) {
  90. return "";
  91. }
  92. return name.substring(dot + 1);
  93. };
  94. const isAcceptedFile = (fileName) => ["pdf", "jpg", "jpeg", "png"].includes(getFileExtension(fileName));
  95. const initialValues = React.useMemo(() => ({
  96. ...formData,
  97. chCompanyName: formData.chCompanyName ?? "",
  98. brExpiryDate: null,
  99. certificateFile: null,
  100. }), [formData]);
  101. const formik = useFormik({
  102. enableReinitialize: true,
  103. initialValues,
  104. validationSchema: yup.object().shape({
  105. enCompanyName: yup.string().trim().max(150).required(displayErrorMsg(intl.formatMessage({ id: "userRequireEnglishName" }))),
  106. chCompanyName: yup.string().max(150).nullable(),
  107. addressLine1: yup.string().trim().max(40).required(displayErrorMsg(intl.formatMessage({ id: "validateAddressLine1" }))),
  108. addressLine2: yup.string().max(40, displayErrorMsg(intl.formatMessage({ id: "noMoreThen40Words" }))),
  109. addressLine3: yup.string().max(40, displayErrorMsg(intl.formatMessage({ id: "noMoreThen40Words" }))),
  110. brExpiryDate: yup.mixed().required(displayErrorMsg(intl.formatMessage({ id: "pleaseFillInBusinessRegCertValidityDate" }))),
  111. certificateFile: yup.mixed().required(displayErrorMsg(intl.formatMessage({ id: "requireValidFileWithProofReplyFormat" }))),
  112. country: yup.mixed().required(displayErrorMsg(intl.formatMessage({ id: "pleaseFillInCountry" }))),
  113. district: yup.mixed().nullable().test(
  114. "hk-district",
  115. displayErrorMsg(intl.formatMessage({ id: "pleaseFillInDistrict" })),
  116. function (value) {
  117. const country = this.parent.country;
  118. if (country && country.type === "hongKong") {
  119. return value != null;
  120. }
  121. return true;
  122. }
  123. ),
  124. }),
  125. onSubmit: (values, { setSubmitting }) => {
  126. setErrorMsg("");
  127. const files = attachmentsRef.current;
  128. if (!values.brExpiryDate || !files || files.length === 0 || values.country == null
  129. || (values.country.type === "hongKong" && values.district == null)) {
  130. setSubmitting(false);
  131. return;
  132. }
  133. return new Promise((resolve, reject) => {
  134. HttpUtils.postWithFiles({
  135. url: UrlUtils.POST_PUB_ORG_SUBMIT_BR,
  136. params: {
  137. enCompanyName: values.enCompanyName,
  138. chCompanyName: values.chCompanyName?.trim() || "",
  139. brExpiryDate: DateUtils.dateValue(values.brExpiryDate),
  140. address: {
  141. country: values.country.type,
  142. district: values.district?.type,
  143. addressLine1: values.addressLine1,
  144. addressLine2: values.addressLine2,
  145. addressLine3: values.addressLine3,
  146. },
  147. },
  148. files: files,
  149. onSuccess: (response) => {
  150. if (response?.msg) {
  151. setErrorMsg(intl.formatMessage({ id: response.msg, defaultMessage: response.msg }));
  152. reject();
  153. return;
  154. }
  155. setShowSubmittedDialog(true);
  156. resolve();
  157. },
  158. onFail: () => reject(),
  159. onError: () => reject(),
  160. });
  161. });
  162. }
  163. });
  164. const setCertificateFiles = (files) => {
  165. const next = files || [];
  166. setAttachments(next);
  167. formik.setFieldValue("certificateFile", next[0] || null, true);
  168. };
  169. const handleSelectCertificate = (event) => {
  170. const file = event.target.files[0];
  171. if (!file) {
  172. return;
  173. }
  174. if (!isAcceptedFile(file.name)) {
  175. setWarningText(intl.formatMessage({ id: "requireValidFileWithProofReplyFormat" }));
  176. setIsWarningPopUp(true);
  177. event.target.value = "";
  178. return;
  179. }
  180. if (file.size >= (10 * 1024 * 1034)) {
  181. setWarningText(intl.formatMessage({ id: "fileSizeWarning" }));
  182. setIsWarningPopUp(true);
  183. event.target.value = "";
  184. return;
  185. }
  186. file.id = 0;
  187. setCertificateFiles([file]);
  188. event.target.value = "";
  189. };
  190. return (
  191. isLoading ?
  192. <Grid container sx={{ minHeight: "87vh", mb: 3 }} direction="column" justifyContent="center" alignItems="center">
  193. <Grid item><LoadingComponent /></Grid>
  194. </Grid>
  195. :
  196. <Grid container direction="column" sx={{ minHeight: "87vh", backgroundColor: "#ffffff" }}>
  197. <Grid item xs={12}>
  198. <div style={BackgroundHead}>
  199. <Stack direction="row" height="70px" justifyContent="flex-start" alignItems="center">
  200. <Typography component="h1" ml={15} color="#FFF" variant="h4" sx={{ display: { xs: "none", md: "block" } }}>
  201. <FormattedMessage id="submitBrCertificate" />
  202. </Typography>
  203. </Stack>
  204. </div>
  205. </Grid>
  206. <Grid item xs={12}>
  207. <Button aria-label={intl.formatMessage({ id: "back" })} title="Back" sx={{ ml: 3.5, mt: 2 }} style={{ border: "2px solid" }} variant="outlined" onClick={() => navigate(-1)}>
  208. <ForwardIcon style={{ height: 30, width: 50, transform: "rotate(180deg)" }} />
  209. </Button>
  210. </Grid>
  211. <Grid item xs={12}>
  212. <Box sx={{ p: 1, borderRadius: "10px" }}>
  213. <MainCard elevation={0} border={false} content={false}>
  214. <form onSubmit={formik.handleSubmit} noValidate style={{ padding: 24 }}>
  215. <Grid container spacing={1}>
  216. <Grid item xs={12}>
  217. <FormHelperText error>
  218. <Typography variant="errorMessage1">{errorMsg}</Typography>
  219. </FormHelperText>
  220. </Grid>
  221. <Grid item xs={12} lg={4}>
  222. {FieldUtils.getTextField({
  223. label: intl.formatMessage({ id: "brNo" }) + ":",
  224. valueName: "brNo",
  225. disabled: true,
  226. form: formik
  227. })}
  228. </Grid>
  229. <Grid item xs={12} lg={4}>
  230. <Grid container alignItems={"center"}>
  231. <Grid item xs={12} md={3} lg={3} sx={{ display: "flex", alignItems: "center" }}>
  232. <Typography variant="pnspsFormParagraphBold">{FieldUtils.notNullFieldLabel(intl.formatMessage({ id: "newBrExpiryDate" }) + ":")}</Typography>
  233. </Grid>
  234. <Grid item xs={12} md={6} lg={6}>
  235. <LocalizationProvider dateAdapter={AdapterDayjs} localeText={DateUtils.getPickerLocaleText(intl.locale)}>
  236. <DemoItem components={["DatePicker"]}>
  237. <DatePicker
  238. id="brExpiryDate"
  239. name="brExpiryDate"
  240. slotProps={{
  241. field: { readOnly: true },
  242. textField: {
  243. error: Boolean(formik.errors.brExpiryDate && (formik.touched.brExpiryDate || formik.submitCount > 0)),
  244. helperText: (formik.touched.brExpiryDate || formik.submitCount > 0) ? formik.errors.brExpiryDate : "",
  245. },
  246. }}
  247. format="DD/MM/YYYY"
  248. value={formik.values.brExpiryDate == null ? null : dayjs(formik.values.brExpiryDate)}
  249. minDate={minDate == null ? null : dayjs(minDate)}
  250. onChange={(newValue) => {
  251. if (DateUtils.dateValue(newValue) > DateUtils.dateValue(new Date())) {
  252. formik.setFieldValue("brExpiryDate", newValue, true);
  253. formik.setFieldTouched("brExpiryDate", true, false);
  254. }
  255. }}
  256. />
  257. </DemoItem>
  258. </LocalizationProvider>
  259. </Grid>
  260. </Grid>
  261. </Grid>
  262. <Grid item xs={12} lg={4}></Grid>
  263. <Grid item xs={12} lg={4}>
  264. {FieldUtils.getTextField({
  265. label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: "nameEng" }) + ":"),
  266. valueName: "enCompanyName",
  267. form: formik
  268. })}
  269. </Grid>
  270. <Grid item xs={12} lg={4}>
  271. {FieldUtils.getTextField({
  272. label: intl.formatMessage({ id: "nameChi" }) + ":",
  273. valueName: "chCompanyName",
  274. form: formik
  275. })}
  276. </Grid>
  277. <Grid item xs={12} lg={4}></Grid>
  278. <Grid item xs={12} lg={12}>
  279. {FieldUtils.getAddressField({
  280. label: FieldUtils.notNullFieldLabel(intl.formatMessage({ id: "formAddress" }) + ":"),
  281. valueName: ["addressLine1", "addressLine2", "addressLine3"],
  282. form: formik
  283. })}
  284. </Grid>
  285. <Grid item xs={12} lg={12}>
  286. {FieldUtils.getProfileComboField({
  287. label: "",
  288. valueName: "district",
  289. dataList: ComboData.district,
  290. getOptionLabel: (option) => option.type ? intl.formatMessage({ id: option.type }) : "",
  291. form: formik
  292. })}
  293. </Grid>
  294. <Grid item xs={12} lg={12}>
  295. {FieldUtils.getProfileComboField({
  296. label: "",
  297. valueName: "country",
  298. disabled: true,
  299. dataList: ComboData.country,
  300. getOptionLabel: (option) => option.type ? intl.formatMessage({ id: option.type }) : "",
  301. form: formik
  302. })}
  303. </Grid>
  304. <Grid item xs={12}>
  305. <Typography variant="subtitle1" sx={{ color: "primary.primary" }}>
  306. <FormattedMessage id="acceptProofReplyFileFormats" />
  307. </Typography>
  308. </Grid>
  309. <Grid item xs={12}>
  310. <input
  311. ref={fileInputRef}
  312. id="uploadFileBtn"
  313. type="file"
  314. accept=".pdf,.jpg,.jpeg,.png,image/png,image/jpeg,application/pdf"
  315. hidden
  316. onChange={handleSelectCertificate}
  317. />
  318. <ThemeProvider theme={PNSPS_BUTTON_THEME}>
  319. <Button
  320. variant="contained"
  321. type="button"
  322. sx={PRIMARY_CONTAINED_BUTTON_SX}
  323. onClick={() => fileInputRef.current && fileInputRef.current.click()}
  324. >
  325. <FormattedMessage id="selectCertificateFile" />
  326. </Button>
  327. </ThemeProvider>
  328. {(formik.touched.certificateFile || formik.submitCount > 0) && formik.errors.certificateFile ?
  329. <FormHelperText error>{formik.errors.certificateFile}</FormHelperText>
  330. : null}
  331. </Grid>
  332. {attachments.length > 0 ?
  333. <Grid item xs={12} md={8}>
  334. <UploadFileTable recordList={attachments} setRecordList={setCertificateFiles} />
  335. </Grid>
  336. : null}
  337. <Grid item xs={12} sx={{ mt: 2 }}>
  338. <ThemeProvider theme={PNSPS_BUTTON_THEME}>
  339. <Button
  340. variant="contained"
  341. type="submit"
  342. color="success"
  343. disabled={formik.isSubmitting || showSubmittedDialog}
  344. startIcon={formik.isSubmitting ? <CircularProgress color="inherit" size={18} /> : null}
  345. >
  346. <FormattedMessage id="submit" />
  347. </Button>
  348. </ThemeProvider>
  349. </Grid>
  350. </Grid>
  351. </form>
  352. </MainCard>
  353. </Box>
  354. </Grid>
  355. <Dialog open={isWarningPopUp} onClose={() => setIsWarningPopUp(false)}>
  356. <DialogTitle><FormattedMessage id="attention" /></DialogTitle>
  357. <DialogContent><Typography>{warningText}</Typography></DialogContent>
  358. <DialogActions>
  359. <Button onClick={() => setIsWarningPopUp(false)}><FormattedMessage id="close" /></Button>
  360. </DialogActions>
  361. </Dialog>
  362. <BrStatusDialog
  363. open={showSubmittedDialog}
  364. variant={formData.brStatus === "Invalid" || formData.applyBlockState === "expired"
  365. ? "submitExpired"
  366. : "submitNotExpired"}
  367. expiryDate={formData.brExpiryDate}
  368. onClose={() => {
  369. setShowSubmittedDialog(false);
  370. navigate(isPrimaryLoggedIn() ? "/org" : "/dashboard");
  371. }}
  372. />
  373. </Grid>
  374. );
  375. };
  376. export default SubmitBrPage;