Bläddra i källkod

no message

master
[email protected] 19 timmar sedan
förälder
incheckning
0fe6f4a685
8 ändrade filer med 180 tillägg och 74 borttagningar
  1. +11
    -0
      src/menu-items/client.js
  2. +2
    -13
      src/menu-items/setting.js
  3. +1
    -1
      src/pages/formSigPage/FormSigPageSearchForm.js
  4. +2
    -13
      src/pages/formSigPage/FormSigPageTable.js
  5. +55
    -4
      src/pages/lionerUserSearchPage/UserSearchForm.js
  6. +96
    -31
      src/pages/pdf/PdfSearchPage/PdfTable.js
  7. +13
    -0
      src/routes/ClientRoutes.js
  8. +0
    -12
      src/routes/SettingRoutes.js

+ 11
- 0
src/menu-items/client.js Visa fil

@@ -1,6 +1,7 @@
// assets
import BoyIcon from '@mui/icons-material/Boy';
import AssignmentIcon from '@mui/icons-material/Assignment';
import PictureAsPdfOutlinedIcon from '@mui/icons-material/PictureAsPdfOutlined';

// icons

@@ -12,6 +13,7 @@ const icons = {
)
},
AssignmentIcon,
FormSigPageIcon: PictureAsPdfOutlinedIcon,
};

// ==============================|| MENU ITEMS - DASHBOARD ||============================== //
@@ -40,6 +42,15 @@ const client = {
breadcrumbs: false,
ability:['VIEW','TEMPLATE']
},
{
id: 'formSigPage',
title: 'Form Sig Page',
type: 'item',
url: '/formSigPage',
icon: icons.FormSigPageIcon,
breadcrumbs: false,
ability:['VIEW','TEMPLATE']
},
]
};



+ 2
- 13
src/menu-items/setting.js Visa fil

@@ -13,8 +13,7 @@ import {
MenuUnfoldOutlined,
FileSearchOutlined,
MailOutlined,
ApartmentOutlined,
FilePdfOutlined
ApartmentOutlined
} from '@ant-design/icons';

// icons
@@ -32,8 +31,7 @@ const icons = {
MenuUnfoldOutlined,
FileSearchOutlined,
MailOutlined,
ApartmentOutlined,
FilePdfOutlined
ApartmentOutlined
};

// ==============================|| MENU ITEMS - EXTRA PAGES ||============================== //
@@ -143,15 +141,6 @@ const setting = {
breadcrumbs: false,
ability:['VIEW','USER']
},
{
id: 'formSigPage',
title: 'Form Sig Page',
type: 'item',
url: '/formSigPage',
icon: icons.FilePdfOutlined,
breadcrumbs: false,
ability:['MANAGE','SYSTEM_CONFIGURATION']
},
// {
// id: 'passwordPolicy',
// title: 'Password Policy',


+ 1
- 1
src/pages/formSigPage/FormSigPageSearchForm.js Visa fil

@@ -13,7 +13,7 @@ import { LIONER_BUTTON_THEME } from "../../themes/colorConst";
import { CARD_MAX_WIDTH } from "../../themes/themeConst";

const FORM_CODES = ['IDA', 'FNA', 'HSBCFIN', 'HSBCA31', 'MLB03S', 'MLFNA_EN', 'MLFNA_CHI', 'SLFNA_EN', 'SLFNA_CHI', 'SLAPP', 'SLGII'];
const SIG_TYPES = ['upload1', 'upload2'];
const SIG_TYPES = ['upload1', 'upload2', 'upload3'];

const FormSigPageSearchForm = ({ applySearch }) => {
const { reset, register, handleSubmit } = useForm();


+ 2
- 13
src/pages/formSigPage/FormSigPageTable.js Visa fil

@@ -37,8 +37,7 @@ import { LIONER_BUTTON_THEME } from "../../themes/colorConst";
import { ThemeProvider } from "@emotion/react";

const FORM_CODES = ['IDA', 'FNA', 'HSBCFIN', 'HSBCA31', 'MLB03S', 'MLFNA_EN', 'MLFNA_CHI', 'SLFNA_EN', 'SLFNA_CHI', 'SLAPP', 'SLGII'];
const SIG_TYPES = ['upload1', 'upload2'];
const ACTIONS = ['REPLACE', 'SKIP_AND_APPEND'];
const SIG_TYPES = ['upload1', 'upload2', 'upload3'];

let newRowId = -1;

@@ -86,7 +85,6 @@ function EditToolbar({ setRows, setRowModesModel }) {
sigType: 'upload1',
pageFrom: 1,
pageTo: 1,
action: 'REPLACE',
isNew: true,
},
...oldRows,
@@ -178,13 +176,12 @@ export default function FormSigPageTable({ recordList }) {
sigType: newRow.sigType,
pageFrom: Number(newRow.pageFrom),
pageTo: Number(newRow.pageTo),
action: newRow.action,
};
if (!isCreate) payload.id = Number(newRow.id);

return new Promise((resolve, reject) => {
if (!payload.formCode || !payload.startDate || !payload.sigType ||
payload.pageFrom == null || payload.pageTo == null || !payload.action) {
payload.pageFrom == null || payload.pageTo == null) {
reject(new Error('All fields are required'));
return;
}
@@ -280,14 +277,6 @@ export default function FormSigPageTable({ recordList }) {
},
{ field: 'pageFrom', headerName: 'Page From', type: 'number', width: 95, editable: true },
{ field: 'pageTo', headerName: 'Page To', type: 'number', width: 95, editable: true },
{
field: 'action',
headerName: 'Action',
width: 140,
editable: true,
type: 'singleSelect',
valueOptions: ACTIONS,
},
];

return (


+ 55
- 4
src/pages/lionerUserSearchPage/UserSearchForm.js Visa fil

@@ -1,13 +1,14 @@
// material-ui
import {
Button,
Grid,
Grid,
TextField,
Typography,
Dialog,
DialogTitle,
DialogContent,
DialogActions
DialogActions,
InputLabel,
} from '@mui/material';
import MainCard from "../../components/MainCard";
import {useForm} from "react-hook-form";
@@ -21,6 +22,7 @@ import {ThemeProvider} from "@emotion/react";
import {LIONER_BUTTON_THEME} from "../../themes/colorConst";
import {USER_GROUP_COMBO} from "../../utils/ComboConst";
import {CARD_MAX_WIDTH} from "../../themes/themeConst";
import {GET_CONSULTANT_COMBO_LIST} from "../../utils/ApiPathConst";

// ==============================|| USER SEARCH FORM ||============================== //

@@ -33,9 +35,22 @@ const UserSearchForm = ({applySearch}) => {
// States for Report Dialog
const [openReportDialog, setOpenReportDialog] = useState(false);
const [reportDates, setReportDates] = useState({ fromDate: '', toDate: '' });
const [consultantOptions, setConsultantOptions] = useState([]);
const [selectedConsultants, setSelectedConsultants] = useState([]);

const { reset, register, handleSubmit } = useForm();

useEffect(() => {
axios
.get(`${apiPath}${GET_CONSULTANT_COMBO_LIST}`)
.then((res) => {
if (res.data?.records) {
setConsultantOptions(res.data.records);
}
})
.catch(() => {});
}, []);

const onSubmit = (data) => {
const temp = {
username: data.userName,
@@ -53,11 +68,26 @@ const UserSearchForm = ({applySearch}) => {
const handleCloseReport = () => {
setOpenReportDialog(false);
setReportDates({ fromDate: '', toDate: '' });
setSelectedConsultants([]);
};

const handleExport = () => {
const params = {
fromDate: reportDates.fromDate,
toDate: reportDates.toDate,
};
if (selectedConsultants.length > 0) {
params.consultantIds = selectedConsultants.map((c) => c.id);
}
axios.get(`${apiPath}/client/excel-client-consultant-report`, {
params: reportDates,
params,
paramsSerializer: (p) => {
const search = new URLSearchParams();
if (p.fromDate) search.append('fromDate', p.fromDate);
if (p.toDate) search.append('toDate', p.toDate);
(p.consultantIds || []).forEach((id) => search.append('consultantIds', String(id)));
return search.toString();
},
responseType: 'blob',
})
.then((response) => {
@@ -153,7 +183,7 @@ const UserSearchForm = ({applySearch}) => {
</form>

{/* Report Criteria Dialog */}
<Dialog open={openReportDialog} onClose={handleCloseReport} fullWidth maxWidth="xs">
<Dialog open={openReportDialog} onClose={handleCloseReport} fullWidth maxWidth="sm">
<DialogTitle>Export Client Consultant Report</DialogTitle>
<DialogContent>
<Grid container spacing={2} sx={{ mt: 0.5 }}>
@@ -177,6 +207,27 @@ const UserSearchForm = ({applySearch}) => {
onChange={(e) => setReportDates({ ...reportDates, toDate: e.target.value })}
/>
</Grid>
<Grid item xs={12}>
<InputLabel htmlFor="report-consultant-combo">Consultant (optional)</InputLabel>
<Autocomplete
multiple
disableCloseOnSelect
id="report-consultant-combo"
size="small"
options={consultantOptions}
value={selectedConsultants}
onChange={(event, newValue) => setSelectedConsultants(newValue)}
getOptionLabel={(option) => option?.name ?? ''}
isOptionEqualToValue={(a, b) => a?.id === b?.id}
renderInput={(params) => (
<TextField
{...params}
placeholder="All consultants if empty"
fullWidth
/>
)}
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions sx={{ p: 3 }}>


+ 96
- 31
src/pages/pdf/PdfSearchPage/PdfTable.js Visa fil

@@ -39,7 +39,22 @@ import {ThemeProvider} from "@emotion/react";
const initialUploadState = {
id: null,
templateName: null, // This will be the formCode
refType: null, // To differentiate between upload1 and upload2 if needed
refType: null, // upload1 | upload2 | upload3
}

const REF_TYPE_TO_FILE_FIELD = {
upload1: "upload1FileId",
upload2: "upload2FileId",
upload3: "upload3FileId",
};

/** Backend adds sigTypes from form_sig_page for this row's formCode + created date. */
function normalizeSigTypes(row) {
const st = row?.sigTypes;
if (Array.isArray(st) && st.length > 0) {
return st;
}
return ["upload1"];
}

export default function PdfTable({recordList}) {
@@ -73,13 +88,19 @@ export default function PdfTable({recordList}) {
navigate(`/pdf/maintain/${id}`);
};

/** Safely get YYYY-MM-DD from row.created; returns null if missing or invalid. */
/**
* YYYY-MM-DD in local calendar (matches server JVM date for filled_form.created better than UTC ISO).
* Used only as fallback when the API cannot resolve filledFormId.
*/
const getAsOfDateString = (row) => {
const raw = row?.created;
if (raw == null || raw === "") return null;
const d = new Date(raw);
if (Number.isNaN(d.getTime())) return null;
return d.toISOString().slice(0, 10);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${y}-${m}-${day}`;
};

/**
@@ -97,6 +118,7 @@ export default function PdfTable({recordList}) {
setIsDialogOpen(true);
const asOfDate = getAsOfDateString(row);
const params = new URLSearchParams({ formCode });
params.set("filledFormId", String(id));
if (asOfDate) params.set("asOfDate", asOfDate);
axios.get(`${apiPath}${GET_FORM_SIG_PAGE_CONFIG}?${params}`)
.then((res) => {
@@ -122,6 +144,7 @@ export default function PdfTable({recordList}) {
setIsDialogOpen(true);
const asOfDate = getAsOfDateString(row);
const params = new URLSearchParams({ formCode });
params.set("filledFormId", String(id));
if (asOfDate) params.set("asOfDate", asOfDate);
axios.get(`${apiPath}${GET_FORM_SIG_PAGE_CONFIG}?${params}`)
.then((res) => {
@@ -132,6 +155,28 @@ export default function PdfTable({recordList}) {
.catch(() => setCurrentUploadLabel("Upload 2"));
};

const handleUpload3Click = (id, templateName, formCode, row) => () => {
setCurrentUploadRow({
id: id,
templateName: templateName,
formCode: formCode,
refType: "upload3"
});
setCurrentUploadLabel(null);
setIsDialogOpen(true);
const asOfDate = getAsOfDateString(row);
const params = new URLSearchParams({ formCode });
params.set("filledFormId", String(id));
if (asOfDate) params.set("asOfDate", asOfDate);
axios.get(`${apiPath}${GET_FORM_SIG_PAGE_CONFIG}?${params}`)
.then((res) => {
const configs = res.data?.configs || [];
const upload3 = configs.find((c) => c.sigType === "upload3");
setCurrentUploadLabel(upload3?.label || "Upload 3");
})
.catch(() => setCurrentUploadLabel("Upload 3"));
};

/**
* Handles the standard download (fillable file, endpoint: /download-ff/{id})
*/
@@ -241,6 +286,7 @@ export default function PdfTable({recordList}) {
const getUploadDialogTitle = (formCode, refType) => {
if (refType === "upload1") return "Upload Signature";
if (refType === "upload2") return "Upload 2";
if (refType === "upload3") return "Upload 3";
return "Upload File";
};
@@ -289,7 +335,7 @@ export default function PdfTable({recordList}) {
prevRows.map(row => {
if (row.id === id) {
// Update the relevant file ID field based on refType
const updateField = refType === 'upload1' ? 'upload1FileId' : 'upload2FileId';
const updateField = REF_TYPE_TO_FILE_FIELD[refType] || "upload1FileId";
return {
...row,
[updateField]: uploadedFileId // Set the file ID to trigger the icon
@@ -367,46 +413,47 @@ export default function PdfTable({recordList}) {
Upload Sig.
</div>
),
width: 100,
width: 150,
cellClassName: 'actions',
getActions: ({ id, row }) => {

// Check upload status
const isUploaded1 = !!row.upload1FileId;
const isUploaded2 = !!row.upload2FileId;
const isUploaded2 = !!row.upload2FileId;
const isUploaded3 = !!row.upload3FileId;
const templateName = row.templateName;
const formCode = row.formCode;

// FIX 5: Use conditional colors for Upload 1
const upload1Icon = isUploaded1
? <CheckCircleIcon sx={{fontSize: 25, color: 'success.main'}} /> // Green tick if uploaded
: <UploadFileIcon sx={{fontSize: 25, color: 'warning.main'}} />; // Warning color if not uploaded
const sigTypes = normalizeSigTypes(row);
const showUpload1 = sigTypes.includes("upload1");
const showUpload2 = sigTypes.includes("upload2");
const showUpload3 = sigTypes.includes("upload3");

const upload1Label = isUploaded1 ? "Update Signature" : "Upload Signature";
const actions = [];

// Define the actions
const actions = [
<ThemeProvider key="UploadSign1" theme={LIONER_BUTTON_THEME}>
<GridActionsCellItem
icon={upload1Icon} // Use the dynamic icon
label={upload1Label} // Use the dynamic label
className="textPrimary"
onClick={handleUploadClick(id, templateName, formCode, row)}
color="upload"
/>
</ThemeProvider>
];
if (showUpload1) {
const upload1Icon = isUploaded1
? <CheckCircleIcon sx={{fontSize: 25, color: 'success.main'}} />
: <UploadFileIcon sx={{fontSize: 25, color: 'warning.main'}} />;
const upload1Label = isUploaded1 ? "Update Signature" : "Upload Signature";
actions.push(
<ThemeProvider key="UploadSign1" theme={LIONER_BUTTON_THEME}>
<GridActionsCellItem
icon={upload1Icon}
label={upload1Label}
className="textPrimary"
onClick={handleUploadClick(id, templateName, formCode, row)}
color="upload"
/>
</ThemeProvider>
);
}

// Conditional rendering logic for Upload 2
if (row.formCode === "MLB03S" || row.formCode === "SLGII" || row.formCode === "SLAPP") {
// FIX 5: Use conditional colors for Upload 2
if (showUpload2) {
const upload2Icon = isUploaded2
? <CheckCircleIcon sx={{fontSize: 25, color: 'success.main'}} /> // Green tick if uploaded
: <UploadFileIcon sx={{fontSize: 25, color: 'warning.main'}} />; // Warning color if not uploaded

? <CheckCircleIcon sx={{fontSize: 25, color: 'success.main'}} />
: <UploadFileIcon sx={{fontSize: 25, color: 'warning.main'}} />;
const upload2Label = isUploaded2 ? "Update 2" : "Upload 2";
actions.push(
<ThemeProvider key="UploadSign2" theme={LIONER_BUTTON_THEME}>
<GridActionsCellItem
@@ -420,6 +467,24 @@ export default function PdfTable({recordList}) {
);
}

if (showUpload3) {
const upload3Icon = isUploaded3
? <CheckCircleIcon sx={{fontSize: 25, color: 'success.main'}} />
: <UploadFileIcon sx={{fontSize: 25, color: 'warning.main'}} />;
const upload3Label = isUploaded3 ? "Update 3" : "Upload 3";
actions.push(
<ThemeProvider key="UploadSign3" theme={LIONER_BUTTON_THEME}>
<GridActionsCellItem
icon={upload3Icon}
label={upload3Label}
className="textPrimary"
onClick={handleUpload3Click(id, templateName, formCode, row)}
color="upload"
/>
</ThemeProvider>
);
}

return actions;
},
},


+ 13
- 0
src/routes/ClientRoutes.js Visa fil

@@ -16,6 +16,7 @@ const PdfFormUpAndDown = Loadable(lazy(() => import('pages/pdf/PdfFormUpAndDown'
const PdfViewer = Loadable(lazy(() => import('pages/pdf/PdfViewer')));
const PdfSearchPage = Loadable(lazy(() => import('pages/pdf/PdfSearchPage')));
const TemplateSearchPage = Loadable(lazy(() => import('pages/pdf/TemplateSearchPage')));
const FormSigPageSearchPanel = Loadable(lazy(() => import('pages/formSigPage')));

// ==============================|| AUTH ROUTING ||============================== //

@@ -88,6 +89,18 @@ const ClientRoutes =() => {
</Require2FA>
),
},
{
path: '/formSigPage',
element: (
<Require2FA>
{handleRouteAbility(
ability.can('VIEW', 'TEMPLATE'),
<FormSigPageSearchPanel />,
<Navigate to="/" />
)}
</Require2FA>
),
},
{
path: '/pdf/form-up-down/:id',
element: (


+ 0
- 12
src/routes/SettingRoutes.js Visa fil

@@ -29,8 +29,6 @@ const EmailConfigPage = Loadable(lazy(() => import('pages/lionerEmailConfig')));
const GenerateReminderPage = Loadable(lazy(() => import('pages/lionerManualButtonPage')));
const ClientDepartmentPage = Loadable(lazy(() => import('pages/lionerClientDepartmentPage')));
const ProfilePage = Loadable(lazy(() => import('pages/profile/profile')));
const FormSigPageSearchPanel = Loadable(lazy(() => import('pages/formSigPage')));

// ==============================|| AUTH ROUTING ||============================== //

const SettingRoutes = () => {
@@ -261,16 +259,6 @@ const SettingRoutes = () => {
)
),
},
{
path: 'formSigPage',
element: (
handleRouteAbility(
ability.can('MANAGE', 'SYSTEM_CONFIGURATION'),
<FormSigPageSearchPanel />,
<Navigate to="/" />
)
),
},
{
path: 'logout',
element: <LogoutPage />


Laddar…
Avbryt
Spara