ui-soxo-bootstrap-core 2.6.40-dev.16 → 2.6.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEVELOPER_GUIDE.md +9 -38
- package/core/components/index.js +11 -2
- package/core/components/landing-api/landing-api.js +5 -165
- package/core/lib/components/global-header/global-header.js +77 -20
- package/core/lib/components/index.js +2 -2
- package/core/lib/components/sidemenu/sidemenu.js +147 -66
- package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +24 -91
- package/core/lib/models/menus/components/menu-add/menu-add.js +2 -2
- package/core/lib/modules/generic/generic-list/ExportReactCSV.js +8 -334
- package/core/lib/modules/generic/generic-list/generic-list.scss +0 -34
- package/core/models/core-scripts/core-scripts.js +1 -22
- package/core/models/menus/components/menu-add/menu-add.js +4 -31
- package/core/models/menus/components/menu-lists/menu-lists.js +9 -76
- package/core/models/menus/menus.js +1 -29
- package/core/models/roles/components/role-add/role-add.js +169 -54
- package/core/models/roles/components/role-list/role-list.js +0 -20
- package/core/models/roles/roles.js +0 -3
- package/core/models/users/components/assign-role/assign-role.js +8 -23
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +5 -202
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +0 -73
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +35 -56
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +0 -1
- package/package.json +1 -1
- package/core/components/license-management/license-alert.js +0 -97
- package/core/models/roles/components/role-add/menu-label.js +0 -14
- package/core/models/roles/components/role-add/menu-tree.js +0 -127
- package/core/modules/reporting/components/reporting-dashboard/reporting-table.js +0 -519
|
@@ -6,62 +6,24 @@
|
|
|
6
6
|
* @param {Array<{ label: string, key: string }>} [headers] - Optional column headers and field mappings.
|
|
7
7
|
* @param {string} [fileName='Report.xlsx'] - Optional name for the downloaded Excel file.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
|
-
import React
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
11
|
// Import XLSX library to handle Excel file creation
|
|
12
12
|
import * as XLSX from 'xlsx';
|
|
13
|
-
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
|
14
13
|
// import * as XLSX from 'xlsx-js-style';
|
|
15
14
|
// import * as XLSX from 'xlsx-js-style';
|
|
16
15
|
// Import saveAs from file-saver to trigger file download in browser
|
|
17
16
|
import { saveAs } from 'file-saver';
|
|
18
|
-
import { Button
|
|
19
|
-
import { DownOutlined, FileExcelFilled, FilePdfFilled, MailFilled, DownloadOutlined } from '@ant-design/icons';
|
|
17
|
+
import { Button } from 'antd';
|
|
20
18
|
import { Trans } from 'react-i18next';
|
|
21
|
-
import { CoreScripts } from '../../../../models';
|
|
22
|
-
import './generic-list.scss';
|
|
23
|
-
|
|
24
|
-
const normalizeScriptId = (value) => {
|
|
25
|
-
if (value === undefined || value === null || value === '') return value;
|
|
26
|
-
|
|
27
|
-
const numberValue = Number(value);
|
|
28
|
-
return Number.isNaN(numberValue) ? value : numberValue;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const getRecipients = (value) => {
|
|
32
|
-
if (Array.isArray(value)) return value.filter(Boolean);
|
|
33
|
-
|
|
34
|
-
return String(value || '')
|
|
35
|
-
.split(/[\n,;]+/)
|
|
36
|
-
.map((email) => email.trim())
|
|
37
|
-
.filter(Boolean);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
export const ExportReactCSV = ({
|
|
42
|
-
csvData,
|
|
43
|
-
headers,
|
|
44
|
-
fileName,
|
|
45
|
-
pdfFileName,
|
|
46
|
-
title,
|
|
47
|
-
format = 'excel',
|
|
48
|
-
buttonText,
|
|
49
|
-
dropdown = false,
|
|
50
|
-
onSendReportToMail,
|
|
51
|
-
scriptId,
|
|
52
|
-
inputParameters,
|
|
53
|
-
}) => {
|
|
54
|
-
const [mailModalVisible, setMailModalVisible] = useState(false);
|
|
55
|
-
const [sendingMail, setSendingMail] = useState(false);
|
|
56
|
-
const [selectedFormat, setSelectedFormat] = useState('excel');
|
|
57
|
-
const [mailForm] = Form.useForm();
|
|
58
19
|
|
|
20
|
+
export const ExportReactCSV = ({ csvData, headers, fileName,title }) => {
|
|
59
21
|
/**
|
|
60
22
|
* exportToExcel function
|
|
61
23
|
* Triggered on button click, generates and downloads an Excel file
|
|
62
24
|
*/
|
|
63
25
|
|
|
64
|
-
const exportToExcel = (
|
|
26
|
+
const exportToExcel = () => {
|
|
65
27
|
// if (!csvData?.length) return;
|
|
66
28
|
|
|
67
29
|
let worksheet;
|
|
@@ -113,302 +75,14 @@ export const ExportReactCSV = ({
|
|
|
113
75
|
const blob = new Blob([excelBuffer], {
|
|
114
76
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
115
77
|
});
|
|
116
|
-
const flName =
|
|
78
|
+
const flName = fileName || 'Report.xlsx';
|
|
117
79
|
// Trigger download using file-saver
|
|
118
80
|
saveAs(blob, flName);
|
|
119
81
|
};
|
|
120
|
-
|
|
121
|
-
const exportToPdf = async (downloadFileName) => {
|
|
122
|
-
const pdfDoc = await PDFDocument.create();
|
|
123
|
-
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
|
124
|
-
const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
125
|
-
const pageSize = [842, 595];
|
|
126
|
-
const margin = 30;
|
|
127
|
-
const rowHeight = 20;
|
|
128
|
-
const titleHeight = 38;
|
|
129
|
-
const fontSize = 8;
|
|
130
|
-
const headerFontSize = 8;
|
|
131
|
-
const headersToUse = headers?.length
|
|
132
|
-
? headers
|
|
133
|
-
: Object.keys(csvData?.[0] || {}).map((key) => ({ label: key, key }));
|
|
134
|
-
const availableWidth = pageSize[0] - margin * 2;
|
|
135
|
-
const columnWidth = headersToUse.length ? availableWidth / headersToUse.length : availableWidth;
|
|
136
|
-
let page;
|
|
137
|
-
let y;
|
|
138
|
-
|
|
139
|
-
const sanitizeValue = (value) => {
|
|
140
|
-
if (value === undefined || value === null) return '';
|
|
141
|
-
if (typeof value === 'object') return JSON.stringify(value);
|
|
142
|
-
return String(value);
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const fitText = (text, width, textFont, size) => {
|
|
146
|
-
const sanitizedText = sanitizeValue(text);
|
|
147
|
-
if (textFont.widthOfTextAtSize(sanitizedText, size) <= width) return sanitizedText;
|
|
148
|
-
|
|
149
|
-
let fittedText = sanitizedText;
|
|
150
|
-
while (fittedText.length && textFont.widthOfTextAtSize(`${fittedText}...`, size) > width) {
|
|
151
|
-
fittedText = fittedText.slice(0, -1);
|
|
152
|
-
}
|
|
153
|
-
return fittedText ? `${fittedText}...` : '';
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const addPage = () => {
|
|
157
|
-
page = pdfDoc.addPage(pageSize);
|
|
158
|
-
y = pageSize[1] - margin;
|
|
159
|
-
|
|
160
|
-
page.drawText(title || 'Report', {
|
|
161
|
-
x: margin,
|
|
162
|
-
y,
|
|
163
|
-
size: 14,
|
|
164
|
-
font: boldFont,
|
|
165
|
-
color: rgb(0.1, 0.1, 0.1),
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
y -= titleHeight;
|
|
169
|
-
|
|
170
|
-
headersToUse.forEach((header, index) => {
|
|
171
|
-
const x = margin + index * columnWidth;
|
|
172
|
-
page.drawRectangle({
|
|
173
|
-
x,
|
|
174
|
-
y: y - 5,
|
|
175
|
-
width: columnWidth,
|
|
176
|
-
height: rowHeight,
|
|
177
|
-
color: rgb(0.92, 0.94, 0.96),
|
|
178
|
-
borderColor: rgb(0.72, 0.75, 0.78),
|
|
179
|
-
borderWidth: 0.5,
|
|
180
|
-
});
|
|
181
|
-
page.drawText(fitText(header.label, columnWidth - 8, boldFont, headerFontSize), {
|
|
182
|
-
x: x + 4,
|
|
183
|
-
y: y + 1,
|
|
184
|
-
size: headerFontSize,
|
|
185
|
-
font: boldFont,
|
|
186
|
-
color: rgb(0.1, 0.1, 0.1),
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
y -= rowHeight;
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
addPage();
|
|
194
|
-
|
|
195
|
-
(csvData || []).forEach((row) => {
|
|
196
|
-
if (y < margin + rowHeight) addPage();
|
|
197
|
-
|
|
198
|
-
headersToUse.forEach((header, index) => {
|
|
199
|
-
const x = margin + index * columnWidth;
|
|
200
|
-
page.drawRectangle({
|
|
201
|
-
x,
|
|
202
|
-
y: y - 5,
|
|
203
|
-
width: columnWidth,
|
|
204
|
-
height: rowHeight,
|
|
205
|
-
color: row.isSummaryRow ? rgb(0.97, 0.97, 0.97) : rgb(1, 1, 1),
|
|
206
|
-
borderColor: rgb(0.82, 0.84, 0.86),
|
|
207
|
-
borderWidth: 0.5,
|
|
208
|
-
});
|
|
209
|
-
page.drawText(fitText(row[header.key], columnWidth - 8, row.isSummaryRow ? boldFont : font, fontSize), {
|
|
210
|
-
x: x + 4,
|
|
211
|
-
y: y + 1,
|
|
212
|
-
size: fontSize,
|
|
213
|
-
font: row.isSummaryRow ? boldFont : font,
|
|
214
|
-
color: rgb(0.12, 0.12, 0.12),
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
y -= rowHeight;
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
const pdfBytes = await pdfDoc.save();
|
|
222
|
-
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
|
|
223
|
-
const flName = downloadFileName || pdfFileName || fileName || 'Report.pdf';
|
|
224
|
-
saveAs(blob, flName);
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const handleDownloadMenuClick = ({ key }) => {
|
|
228
|
-
if (key === 'excel') {
|
|
229
|
-
exportToExcel(fileName);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (key === 'pdf') {
|
|
234
|
-
exportToPdf(pdfFileName);
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (key === 'mail') {
|
|
239
|
-
setMailModalVisible(true);
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
const handleMailModalClose = () => {
|
|
244
|
-
setMailModalVisible(false);
|
|
245
|
-
mailForm.resetFields();
|
|
246
|
-
setSelectedFormat('excel');
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
const handleFormatToggle = (value) => {
|
|
250
|
-
const nextValue = selectedFormat === value ? null : value;
|
|
251
|
-
setSelectedFormat(nextValue);
|
|
252
|
-
mailForm.setFieldsValue({ format: nextValue });
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
// const handleSendReportToMail = async (values) => {
|
|
256
|
-
// setSendingMail(true);
|
|
257
|
-
|
|
258
|
-
// try {
|
|
259
|
-
// const mailFormat = values.format || selectedFormat || 'excel';
|
|
260
|
-
// const isBoth = mailFormat === 'both';
|
|
261
|
-
// const payload = {
|
|
262
|
-
// script_id: normalizeScriptId(scriptId),
|
|
263
|
-
// input_parameters: inputParameters || {},
|
|
264
|
-
// is_pdf: mailFormat === 'pdf' || isBoth,
|
|
265
|
-
// is_excel: mailFormat === 'excel' || isBoth,
|
|
266
|
-
// is_both: isBoth,
|
|
267
|
-
// recipients: getRecipients(values.email),
|
|
268
|
-
// };
|
|
269
|
-
// const data = onSendReportToMail
|
|
270
|
-
// ? await onSendReportToMail(payload)
|
|
271
|
-
// : await CoreScripts.sentToReportMail(payload);
|
|
272
|
-
|
|
273
|
-
// if (data?.success === false) throw data;
|
|
274
|
-
|
|
275
|
-
// message.success(data?.message || 'Report mail sent successfully');
|
|
276
|
-
// handleMailModalClose();
|
|
277
|
-
// } catch (error) {
|
|
278
|
-
// message.error(error?.message || 'Unable to send report mail');
|
|
279
|
-
// } finally {
|
|
280
|
-
// setSendingMail(false);
|
|
281
|
-
// }
|
|
282
|
-
// };
|
|
283
|
-
|
|
284
|
-
const downloadMenu = (
|
|
285
|
-
<Menu className="export-download-menu" onClick={handleDownloadMenuClick}>
|
|
286
|
-
<Menu.Item
|
|
287
|
-
key="excel"
|
|
288
|
-
icon={<FileExcelFilled className="export-download-menu__icon export-download-menu__icon--excel" />}
|
|
289
|
-
>
|
|
290
|
-
Download Excel Sheet
|
|
291
|
-
</Menu.Item>
|
|
292
|
-
<Menu.Item
|
|
293
|
-
key="pdf"
|
|
294
|
-
icon={<FilePdfFilled className="export-download-menu__icon export-download-menu__icon--pdf" />}
|
|
295
|
-
>
|
|
296
|
-
Download PDF
|
|
297
|
-
</Menu.Item>
|
|
298
|
-
{/* <Menu.Item
|
|
299
|
-
key="mail"
|
|
300
|
-
icon={<MailFilled className="export-download-menu__icon export-download-menu__icon--mail" />}
|
|
301
|
-
>
|
|
302
|
-
Send report to mail
|
|
303
|
-
</Menu.Item> */}
|
|
304
|
-
</Menu>
|
|
305
|
-
);
|
|
306
|
-
|
|
307
|
-
const isPdf = format === 'pdf';
|
|
308
|
-
const handleExportButtonClick = () => {
|
|
309
|
-
if (isPdf) {
|
|
310
|
-
exportToPdf();
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
exportToExcel();
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
if (dropdown) {
|
|
318
|
-
return (
|
|
319
|
-
<>
|
|
320
|
-
<Dropdown overlay={downloadMenu} trigger={['click']} placement="bottomRight">
|
|
321
|
-
<Button type="primary" size="small" className="export-download-button">
|
|
322
|
-
<Trans i18nKey={buttonText || 'Download'} /> <DownloadOutlined className="export-download-button__icon" />
|
|
323
|
-
</Button>
|
|
324
|
-
</Dropdown>
|
|
325
|
-
{/*
|
|
326
|
-
<Modal
|
|
327
|
-
title="Send report to mail"
|
|
328
|
-
open={mailModalVisible}
|
|
329
|
-
onCancel={handleMailModalClose}
|
|
330
|
-
footer={null}
|
|
331
|
-
destroyOnClose
|
|
332
|
-
width={460}
|
|
333
|
-
>
|
|
334
|
-
<Form
|
|
335
|
-
form={mailForm}
|
|
336
|
-
layout="vertical"
|
|
337
|
-
initialValues={{ format: 'excel' }}
|
|
338
|
-
onFinish={handleSendReportToMail}
|
|
339
|
-
>
|
|
340
|
-
<Form.Item
|
|
341
|
-
name="format"
|
|
342
|
-
style={{ marginBottom: 16 }}
|
|
343
|
-
rules={[
|
|
344
|
-
{
|
|
345
|
-
validator: () =>
|
|
346
|
-
selectedFormat
|
|
347
|
-
? Promise.resolve()
|
|
348
|
-
: Promise.reject(new Error('Please select a format')),
|
|
349
|
-
},
|
|
350
|
-
]}
|
|
351
|
-
>
|
|
352
|
-
<div
|
|
353
|
-
className="export-mail-format-options"
|
|
354
|
-
style={{ display: 'flex', alignItems: 'center', columnGap: 24 }}
|
|
355
|
-
>
|
|
356
|
-
<Radio
|
|
357
|
-
checked={selectedFormat === 'excel'}
|
|
358
|
-
style={{ marginRight: 0 }}
|
|
359
|
-
onClick={() => handleFormatToggle('excel')}
|
|
360
|
-
>
|
|
361
|
-
Excel
|
|
362
|
-
</Radio>
|
|
363
|
-
<Radio
|
|
364
|
-
checked={selectedFormat === 'pdf'}
|
|
365
|
-
style={{ marginRight: 0 }}
|
|
366
|
-
onClick={() => handleFormatToggle('pdf')}
|
|
367
|
-
>
|
|
368
|
-
PDF
|
|
369
|
-
</Radio>
|
|
370
|
-
<Radio
|
|
371
|
-
checked={selectedFormat === 'both'}
|
|
372
|
-
style={{ marginRight: 0 }}
|
|
373
|
-
onClick={() => handleFormatToggle('both')}
|
|
374
|
-
>
|
|
375
|
-
Both
|
|
376
|
-
</Radio>
|
|
377
|
-
</div>
|
|
378
|
-
</Form.Item>
|
|
379
|
-
|
|
380
|
-
<Form.Item
|
|
381
|
-
name="email"
|
|
382
|
-
label="EMAIL ID"
|
|
383
|
-
rules={[
|
|
384
|
-
{ required: true, message: 'Please enter email id' },
|
|
385
|
-
{ type: 'email', message: 'Please enter a valid email id' },
|
|
386
|
-
]}
|
|
387
|
-
>
|
|
388
|
-
<Input placeholder="Enter Mail Id" />
|
|
389
|
-
</Form.Item>
|
|
390
|
-
|
|
391
|
-
<Form.Item
|
|
392
|
-
name="remarks"
|
|
393
|
-
label="REMARKS"
|
|
394
|
-
rules={[{ required: true, message: 'Please enter remarks' }]}
|
|
395
|
-
>
|
|
396
|
-
<Input.TextArea placeholder="Enter Remarks" rows={4} />
|
|
397
|
-
</Form.Item>
|
|
398
|
-
|
|
399
|
-
<Button type="primary" htmlType="submit" loading={sendingMail}>
|
|
400
|
-
Send
|
|
401
|
-
</Button>
|
|
402
|
-
</Form>
|
|
403
|
-
</Modal> */}
|
|
404
|
-
</>
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
82
|
// Render an Ant Design Button that triggers the Excel export
|
|
409
83
|
return (
|
|
410
|
-
<Button type="secondary" size="small" onClick={
|
|
411
|
-
<Trans i18nKey=
|
|
84
|
+
<Button type="secondary" size="small" onClick={exportToExcel}>
|
|
85
|
+
<Trans i18nKey="Download" />
|
|
412
86
|
</Button>
|
|
413
87
|
);
|
|
414
88
|
};
|
|
@@ -32,37 +32,3 @@
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
.export-download-button {
|
|
37
|
-
&.ant-btn-primary {
|
|
38
|
-
background: #2f80ed;
|
|
39
|
-
border-color: #2f80ed;
|
|
40
|
-
color: #fff;
|
|
41
|
-
|
|
42
|
-
&:hover,
|
|
43
|
-
&:focus {
|
|
44
|
-
background: #1c7ed6;
|
|
45
|
-
border-color: #1c7ed6;
|
|
46
|
-
color: #fff;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.export-download-menu {
|
|
52
|
-
&__icon {
|
|
53
|
-
font-size: 16px;
|
|
54
|
-
vertical-align: -0.15em;
|
|
55
|
-
|
|
56
|
-
&--excel {
|
|
57
|
-
color: #18a058;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
&--pdf {
|
|
61
|
-
color: #e03131;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
&--mail {
|
|
65
|
-
color: #1c7ed6;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
@@ -69,6 +69,7 @@ class CoreScript extends Base {
|
|
|
69
69
|
// Settings db pointer
|
|
70
70
|
if (!dbPtr) dbPtr = localStorage.db_ptr;
|
|
71
71
|
return ApiUtils.post({
|
|
72
|
+
// baseUrl: 'http://localhost:8002/dev/',
|
|
72
73
|
url: `core-scripts/dashboardquery/${id}`,
|
|
73
74
|
formBody,
|
|
74
75
|
headers: {
|
|
@@ -79,14 +80,6 @@ class CoreScript extends Base {
|
|
|
79
80
|
});
|
|
80
81
|
};
|
|
81
82
|
|
|
82
|
-
// sentToReportMail = (formBody) => {
|
|
83
|
-
// return ApiUtils.post({
|
|
84
|
-
// baseUrl: 'http://localhost:8002/dev/',
|
|
85
|
-
// url: `core-scripts/send-to-report-mail`,
|
|
86
|
-
// formBody,
|
|
87
|
-
// });
|
|
88
|
-
// };
|
|
89
|
-
|
|
90
83
|
/**
|
|
91
84
|
*Updating user details
|
|
92
85
|
* @returns
|
|
@@ -98,20 +91,6 @@ class CoreScript extends Base {
|
|
|
98
91
|
});
|
|
99
92
|
};
|
|
100
93
|
|
|
101
|
-
getCorescript = (formBody,dbPtr) => {
|
|
102
|
-
|
|
103
|
-
if (!dbPtr) dbPtr = localStorage.db_ptr;
|
|
104
|
-
return ApiUtils.post({
|
|
105
|
-
// baseUrl: 'http://localhost:8002/dev/',
|
|
106
|
-
url: `core-scripts/get-core-script`,
|
|
107
|
-
headers: {
|
|
108
|
-
'Content-Type': 'application/json',
|
|
109
|
-
Authorization: 'Bearer ' + localStorage.access_token,
|
|
110
|
-
db_ptr: dbPtr,
|
|
111
|
-
},
|
|
112
|
-
formBody,
|
|
113
|
-
});
|
|
114
|
-
};
|
|
115
94
|
getQuery = (formBody) => {
|
|
116
95
|
return ApiUtils.post({
|
|
117
96
|
url: `core-scripts/execute-script-api`,
|
|
@@ -116,13 +116,6 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
116
116
|
values.step = formContent.step || step;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
// Cleared selects come back as undefined; send null so the backend clears them
|
|
120
|
-
['model_id', 'page_id', 'header_id'].forEach((field) => {
|
|
121
|
-
if (values[field] === undefined) {
|
|
122
|
-
values[field] = null;
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
119
|
if (values.attributes && typeof values === 'object') {
|
|
127
120
|
values = {
|
|
128
121
|
...values,
|
|
@@ -190,7 +183,7 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
190
183
|
{/* Model */}
|
|
191
184
|
<Form.Item label="Models" name={'model_id'}>
|
|
192
185
|
{/* <ReferenceSelect value={model.id} label={model.name} model={Models} /> */}
|
|
193
|
-
<Select showSearch style={{ width: '100%' }} placeholder="Select a Page" optionFilterProp="label"
|
|
186
|
+
<Select showSearch style={{ width: '100%' }} placeholder="Select a Page" optionFilterProp="label">
|
|
194
187
|
{models.map((model, key) => (
|
|
195
188
|
<Option key={key} label={model.name} value={model.id}>
|
|
196
189
|
{model.name}
|
|
@@ -205,7 +198,7 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
205
198
|
<Form.Item label="Pages" name={'page_id'}>
|
|
206
199
|
{/* <ReferenceSelect value={pages.id} label={pages.name} model={Pages} /> */}
|
|
207
200
|
|
|
208
|
-
<Select showSearch optionFilterProp="children" style={{ width: '100%' }}
|
|
201
|
+
<Select showSearch optionFilterProp="children" style={{ width: '100%' }}>
|
|
209
202
|
{pages.map((model, key) => (
|
|
210
203
|
<Option key={key} value={model.id}>
|
|
211
204
|
{model.name}
|
|
@@ -236,33 +229,13 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
236
229
|
{/* Pages Ends */}
|
|
237
230
|
|
|
238
231
|
{/* Path */}
|
|
239
|
-
<Form.Item
|
|
240
|
-
name="path"
|
|
241
|
-
label="Path"
|
|
242
|
-
rules={[
|
|
243
|
-
{ required: true, message: 'Path is required' },
|
|
244
|
-
{
|
|
245
|
-
pattern: /^\//,
|
|
246
|
-
message: 'Path must start with /',
|
|
247
|
-
},
|
|
248
|
-
]}
|
|
249
|
-
>
|
|
232
|
+
<Form.Item name="path" label="Path" rules={[{ required: true, message: 'Path is required' }]}>
|
|
250
233
|
<Input placeholder="Enter path" />
|
|
251
234
|
</Form.Item>
|
|
252
235
|
{/* Path Ends */}
|
|
253
236
|
|
|
254
237
|
{/* Route */}
|
|
255
|
-
<Form.Item
|
|
256
|
-
name="route"
|
|
257
|
-
label="Route"
|
|
258
|
-
rules={[
|
|
259
|
-
{ required: false, message: 'Route is required' },
|
|
260
|
-
{
|
|
261
|
-
pattern: /^\//,
|
|
262
|
-
message: 'Route must start with /',
|
|
263
|
-
},
|
|
264
|
-
]}
|
|
265
|
-
>
|
|
238
|
+
<Form.Item name="route" label="Route" rules={[{ required: true, message: 'Route is required' }]}>
|
|
266
239
|
<Input placeholder="Enter route" />
|
|
267
240
|
</Form.Item>
|
|
268
241
|
{/* Route Ends */}
|
|
@@ -30,9 +30,6 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
30
30
|
const [query, setQuery] = useState('');
|
|
31
31
|
const [dragMode, setDragMode] = useState(false);
|
|
32
32
|
const [orderChanged, setOrderChanged] = useState(false);
|
|
33
|
-
// keys of currently-expanded top-level panels. Seeded from the search results
|
|
34
|
-
// when the query changes, but the user can freely toggle panels afterwards.
|
|
35
|
-
const [openKeys, setOpenKeys] = useState([]);
|
|
36
33
|
|
|
37
34
|
const [nextId, setNextId] = useState(10000);
|
|
38
35
|
|
|
@@ -184,16 +181,6 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
184
181
|
return;
|
|
185
182
|
}
|
|
186
183
|
|
|
187
|
-
// Prevent moving a menu into one of its own descendants
|
|
188
|
-
// (e.g. A > B, C — A cannot be dropped under B or C).
|
|
189
|
-
if (targetParentId) {
|
|
190
|
-
const draggedNode = findMenuById(records, draggedItem.id);
|
|
191
|
-
if (draggedNode && findMenuById(draggedNode.sub_menus || [], targetParentId)) {
|
|
192
|
-
message.warning('Cannot move a menu into its own sub menu');
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
184
|
// Remove item from original location
|
|
198
185
|
const { newItems: itemsAfterRemove, foundItem } = findAndRemoveItem(records, draggedItem.id);
|
|
199
186
|
|
|
@@ -207,7 +194,7 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
207
194
|
|
|
208
195
|
setRecords(finalItems);
|
|
209
196
|
setOrderChanged(true);
|
|
210
|
-
message.success(`Moved "${foundItem.
|
|
197
|
+
message.success(`Moved "${foundItem.name}" to level ${targetLevel}`);
|
|
211
198
|
},
|
|
212
199
|
[records]
|
|
213
200
|
);
|
|
@@ -216,42 +203,9 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
216
203
|
model.delete(rec).then(loadMenus);
|
|
217
204
|
};
|
|
218
205
|
|
|
219
|
-
|
|
220
|
-
// caption matches OR any descendant matches. When the menu itself matches we
|
|
221
|
-
// keep its full subtree; when only a descendant matches we keep just the
|
|
222
|
-
// pruned path so the matching item stays reachable.
|
|
223
|
-
const filterMenus = (menus, q) => {
|
|
224
|
-
if (!q) return menus || [];
|
|
225
|
-
const lower = q.toLowerCase();
|
|
226
|
-
|
|
227
|
-
return (menus || []).reduce((acc, menu) => {
|
|
228
|
-
const selfMatch = (menu.caption || '').toLowerCase().includes(lower);
|
|
229
|
-
if (selfMatch) {
|
|
230
|
-
acc.push({ ...menu });
|
|
231
|
-
return acc;
|
|
232
|
-
}
|
|
233
|
-
const subFiltered = filterMenus(menu.sub_menus || [], q);
|
|
234
|
-
if (subFiltered.length) {
|
|
235
|
-
acc.push({ ...menu, sub_menus: subFiltered });
|
|
236
|
-
}
|
|
237
|
-
return acc;
|
|
238
|
-
}, []);
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// Ids of items (at one level) that have sub_menus — used to auto-expand
|
|
242
|
-
// every branch leading to a search match.
|
|
243
|
-
const expandableKeys = (items) => (items || []).filter((m) => m.sub_menus && m.sub_menus.length > 0).map((m) => String(m.id));
|
|
244
|
-
|
|
245
|
-
const searchActive = !!query;
|
|
246
|
-
const visibleItems = filterMenus(records, query);
|
|
206
|
+
const filtered = records.filter((r) => r.caption?.toUpperCase().includes(query.toUpperCase()));
|
|
247
207
|
|
|
248
|
-
|
|
249
|
-
// branch on the path to a match) so the matched sub-menu is revealed. After
|
|
250
|
-
// that the user can collapse/expand freely via the Collapse onChange below.
|
|
251
|
-
useEffect(() => {
|
|
252
|
-
setOpenKeys(query ? expandableKeys(filterMenus(records, query)) : []);
|
|
253
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
254
|
-
}, [query]);
|
|
208
|
+
const visibleItems = filtered;
|
|
255
209
|
|
|
256
210
|
const onSearch = (event) => {
|
|
257
211
|
setQuery(event.target.value);
|
|
@@ -351,7 +305,7 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
351
305
|
<Card className="generic-list">
|
|
352
306
|
<div style={{ marginBottom: 16 }}>
|
|
353
307
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
|
354
|
-
<Search placeholder="Search
|
|
308
|
+
<Search placeholder="Enter Search Value" allowClear style={{ width: 300, marginBottom: '0px' }} onChange={onSearch} />
|
|
355
309
|
|
|
356
310
|
<Space size="small">
|
|
357
311
|
<Button onClick={getRecords} size={'small'} type="default">
|
|
@@ -407,11 +361,7 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
407
361
|
{/* {!view ? ( */}
|
|
408
362
|
<Card>
|
|
409
363
|
<DndProvider backend={HTML5Backend}>
|
|
410
|
-
<Collapse
|
|
411
|
-
accordion={!searchActive}
|
|
412
|
-
activeKey={openKeys}
|
|
413
|
-
onChange={(keys) => setOpenKeys(Array.isArray(keys) ? keys : keys ? [keys] : [])}
|
|
414
|
-
>
|
|
364
|
+
<Collapse accordion>
|
|
415
365
|
{visibleItems && visibleItems.length > 0 ? (
|
|
416
366
|
visibleItems.map((item, index) => (
|
|
417
367
|
<Panel
|
|
@@ -439,8 +389,6 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
439
389
|
items={item.sub_menus || []}
|
|
440
390
|
model={model}
|
|
441
391
|
dragMode={dragMode}
|
|
442
|
-
searchActive={searchActive}
|
|
443
|
-
query={query}
|
|
444
392
|
setSelectedRecord={setSelectedRecord}
|
|
445
393
|
setDrawerTitle={setDrawerTitle}
|
|
446
394
|
setDrawerVisible={setDrawerVisible}
|
|
@@ -552,8 +500,6 @@ function NestedMenu({
|
|
|
552
500
|
items,
|
|
553
501
|
model,
|
|
554
502
|
dragMode,
|
|
555
|
-
searchActive,
|
|
556
|
-
query,
|
|
557
503
|
setSelectedRecord,
|
|
558
504
|
setDrawerTitle,
|
|
559
505
|
setDrawerVisible,
|
|
@@ -562,22 +508,15 @@ function NestedMenu({
|
|
|
562
508
|
onCrossLevelMove,
|
|
563
509
|
onChange,
|
|
564
510
|
}) {
|
|
511
|
+
// do not render Collapse
|
|
512
|
+
if (!items || items.length === 0) return null;
|
|
513
|
+
|
|
565
514
|
const [localItems, setLocalItems] = useState(items);
|
|
566
|
-
// keys of currently-expanded sub-panels. Seeded from the search results when
|
|
567
|
-
// the query changes, but the user can freely toggle panels afterwards.
|
|
568
|
-
const [openKeys, setOpenKeys] = useState([]);
|
|
569
515
|
|
|
570
516
|
useEffect(() => {
|
|
571
517
|
setLocalItems(items);
|
|
572
518
|
}, [items]);
|
|
573
519
|
|
|
574
|
-
// Seed the open sub-panels from the search path when the query changes; keep
|
|
575
|
-
// the user's toggles otherwise so a search-opened panel can be closed.
|
|
576
|
-
useEffect(() => {
|
|
577
|
-
setOpenKeys(searchActive ? (items || []).filter((c) => c.sub_menus && c.sub_menus.length > 0).map((c) => String(c.id)) : []);
|
|
578
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
579
|
-
}, [query, searchActive]);
|
|
580
|
-
|
|
581
520
|
const moveSubMenu = useCallback(
|
|
582
521
|
(from, to) => {
|
|
583
522
|
if (!dragMode || from === to) return;
|
|
@@ -597,11 +536,7 @@ function NestedMenu({
|
|
|
597
536
|
}
|
|
598
537
|
|
|
599
538
|
return (
|
|
600
|
-
<Collapse
|
|
601
|
-
accordion={!searchActive}
|
|
602
|
-
activeKey={openKeys}
|
|
603
|
-
onChange={(keys) => setOpenKeys(Array.isArray(keys) ? keys : keys ? [keys] : [])}
|
|
604
|
-
>
|
|
539
|
+
<Collapse accordion>
|
|
605
540
|
{localItems.map((child, index) => (
|
|
606
541
|
<Panel
|
|
607
542
|
key={child.id}
|
|
@@ -628,8 +563,6 @@ function NestedMenu({
|
|
|
628
563
|
step={step + 1}
|
|
629
564
|
items={child.sub_menus || []}
|
|
630
565
|
dragMode={dragMode}
|
|
631
|
-
searchActive={searchActive}
|
|
632
|
-
query={query}
|
|
633
566
|
setSelectedRecord={setSelectedRecord}
|
|
634
567
|
setDrawerTitle={setDrawerTitle}
|
|
635
568
|
setDrawerVisible={setDrawerVisible}
|