ui-soxo-bootstrap-core 2.6.39 → 2.6.40-dev.1
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/.github/workflows/npm-publish.yml +40 -33
- package/DEVELOPER_GUIDE.md +38 -9
- package/core/components/index.js +2 -11
- package/core/components/landing-api/landing-api.js +165 -5
- package/core/components/license-management/license-alert.js +97 -0
- package/core/lib/components/global-header/global-header.js +20 -77
- package/core/lib/components/index.js +2 -2
- package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +91 -24
- package/core/lib/modules/generic/generic-list/ExportReactCSV.js +334 -8
- package/core/lib/modules/generic/generic-list/generic-list.scss +34 -0
- package/core/models/core-scripts/core-scripts.js +22 -1
- package/core/models/menus/menus.js +29 -1
- package/core/models/roles/components/role-add/menu-label.js +14 -0
- package/core/models/roles/components/role-add/menu-tree.js +127 -0
- package/core/models/roles/components/role-add/role-add.js +44 -167
- package/core/models/roles/components/role-list/role-list.js +20 -0
- package/core/models/users/components/assign-role/assign-role.js +23 -8
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +202 -5
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +73 -0
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +25 -5
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +1 -0
- package/core/modules/reporting/components/reporting-dashboard/reporting-table.js +519 -0
- package/package.json +1 -1
|
@@ -6,24 +6,62 @@
|
|
|
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 from 'react';
|
|
9
|
+
|
|
10
|
+
import React, { useState } 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';
|
|
13
14
|
// import * as XLSX from 'xlsx-js-style';
|
|
14
15
|
// import * as XLSX from 'xlsx-js-style';
|
|
15
16
|
// Import saveAs from file-saver to trigger file download in browser
|
|
16
17
|
import { saveAs } from 'file-saver';
|
|
17
|
-
import { Button } from 'antd';
|
|
18
|
+
import { Button, Dropdown, Form, Input, Menu, Modal, Radio, message } from 'antd';
|
|
19
|
+
import { DownOutlined, FileExcelFilled, FilePdfFilled, MailFilled, DownloadOutlined } from '@ant-design/icons';
|
|
18
20
|
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();
|
|
19
58
|
|
|
20
|
-
export const ExportReactCSV = ({ csvData, headers, fileName,title }) => {
|
|
21
59
|
/**
|
|
22
60
|
* exportToExcel function
|
|
23
61
|
* Triggered on button click, generates and downloads an Excel file
|
|
24
62
|
*/
|
|
25
63
|
|
|
26
|
-
const exportToExcel = () => {
|
|
64
|
+
const exportToExcel = (downloadFileName) => {
|
|
27
65
|
// if (!csvData?.length) return;
|
|
28
66
|
|
|
29
67
|
let worksheet;
|
|
@@ -75,14 +113,302 @@ export const ExportReactCSV = ({ csvData, headers, fileName,title }) => {
|
|
|
75
113
|
const blob = new Blob([excelBuffer], {
|
|
76
114
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
77
115
|
});
|
|
78
|
-
const flName = fileName || 'Report.xlsx';
|
|
116
|
+
const flName = downloadFileName || fileName || 'Report.xlsx';
|
|
79
117
|
// Trigger download using file-saver
|
|
80
118
|
saveAs(blob, flName);
|
|
81
119
|
};
|
|
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
|
+
|
|
82
408
|
// Render an Ant Design Button that triggers the Excel export
|
|
83
409
|
return (
|
|
84
|
-
<Button type="secondary" size="small" onClick={
|
|
85
|
-
<Trans i18nKey=
|
|
410
|
+
<Button type="secondary" size="small" onClick={handleExportButtonClick} className="export-download-button">
|
|
411
|
+
<Trans i18nKey={buttonText || (isPdf ? 'PDF Download' : 'Download')} /> <DownloadOutlined className="export-download-button__icon" />
|
|
86
412
|
</Button>
|
|
87
413
|
);
|
|
88
414
|
};
|
|
@@ -32,3 +32,37 @@
|
|
|
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,7 +69,6 @@ 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/',
|
|
73
72
|
url: `core-scripts/dashboardquery/${id}`,
|
|
74
73
|
formBody,
|
|
75
74
|
headers: {
|
|
@@ -80,6 +79,14 @@ class CoreScript extends Base {
|
|
|
80
79
|
});
|
|
81
80
|
};
|
|
82
81
|
|
|
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
|
+
|
|
83
90
|
/**
|
|
84
91
|
*Updating user details
|
|
85
92
|
* @returns
|
|
@@ -91,6 +98,20 @@ class CoreScript extends Base {
|
|
|
91
98
|
});
|
|
92
99
|
};
|
|
93
100
|
|
|
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
|
+
};
|
|
94
115
|
getQuery = (formBody) => {
|
|
95
116
|
return ApiUtils.post({
|
|
96
117
|
url: `core-scripts/execute-script-api`,
|
|
@@ -160,6 +160,29 @@ class MenusAPI extends Base {
|
|
|
160
160
|
});
|
|
161
161
|
};
|
|
162
162
|
|
|
163
|
+
getBranches = () => {
|
|
164
|
+
return ApiUtils.get({
|
|
165
|
+
url: 'branches',
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
switchBranch = (formBody, dbPtr) => {
|
|
170
|
+
return ApiUtils.post({
|
|
171
|
+
url: `auth/switch-branch`,
|
|
172
|
+
headers: { db_ptr: dbPtr },
|
|
173
|
+
formBody,
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
getProfile = (token) => {
|
|
178
|
+
return ApiUtils.get({
|
|
179
|
+
url: 'auth/profile',
|
|
180
|
+
headers: {
|
|
181
|
+
Authorization: `Bearer ${token}`,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
163
186
|
/**
|
|
164
187
|
* create menu
|
|
165
188
|
*/
|
|
@@ -185,7 +208,6 @@ class MenusAPI extends Base {
|
|
|
185
208
|
: 'menus/get-menus'; // NURA
|
|
186
209
|
|
|
187
210
|
if (!dbPtr) dbPtr = localStorage.db_ptr;
|
|
188
|
-
|
|
189
211
|
return this.get({
|
|
190
212
|
url,
|
|
191
213
|
config,
|
|
@@ -305,6 +327,12 @@ class MenusAPI extends Base {
|
|
|
305
327
|
// }
|
|
306
328
|
];
|
|
307
329
|
};
|
|
330
|
+
// license summary api call
|
|
331
|
+
getSummary = () => {
|
|
332
|
+
return ApiUtils.get({
|
|
333
|
+
url: 'license/summary',
|
|
334
|
+
});
|
|
335
|
+
};
|
|
308
336
|
}
|
|
309
337
|
|
|
310
338
|
export default MenusAPI;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// ------------------------
|
|
2
|
+
// Menu caption + path label
|
|
3
|
+
// ------------------------
|
|
4
|
+
const MenuLabel = ({ menu }) => {
|
|
5
|
+
const path = menu.path || menu.route;
|
|
6
|
+
return (
|
|
7
|
+
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 8, lineHeight: 1.3 }}>
|
|
8
|
+
<span>{menu.caption}</span>
|
|
9
|
+
{path ? <span style={{ color: '#999', fontSize: 12 }}>{path}</span> : null}
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default MenuLabel;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Collapse, Checkbox, Tag } from 'antd';
|
|
2
|
+
|
|
3
|
+
import MenuLabel from './menu-label';
|
|
4
|
+
|
|
5
|
+
const { Panel } = Collapse;
|
|
6
|
+
|
|
7
|
+
// ------------------------
|
|
8
|
+
// Recursive Nested Menu Component
|
|
9
|
+
// ------------------------
|
|
10
|
+
const MenuTree = ({ menus, selectedMenus, toggleMenu, parentId = null, searchActive = false }) => {
|
|
11
|
+
// Helper: check if parent should be checked
|
|
12
|
+
const isParentChecked = (menu) => {
|
|
13
|
+
if (!menu.sub_menus || menu.sub_menus.length === 0) {
|
|
14
|
+
return selectedMenus.includes(menu.id);
|
|
15
|
+
}
|
|
16
|
+
const allChildIds = menu.sub_menus.map((c) => c.id);
|
|
17
|
+
return allChildIds.every((id) => selectedMenus.includes(id));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Helper: check if parent is indeterminate
|
|
21
|
+
const isParentIndeterminate = (menu) => {
|
|
22
|
+
if (!menu.sub_menus || menu.sub_menus.length === 0) return false;
|
|
23
|
+
const allChildIds = menu.sub_menus.map((c) => c.id);
|
|
24
|
+
const checkedCount = allChildIds.filter((id) => selectedMenus.includes(id)).length;
|
|
25
|
+
return checkedCount > 0 && checkedCount < allChildIds.length;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<>
|
|
30
|
+
{menus.map((menu) => {
|
|
31
|
+
const children = menu.sub_menus || [];
|
|
32
|
+
const parentChecked = isParentChecked(menu);
|
|
33
|
+
const parentIndeterminate = isParentIndeterminate(menu);
|
|
34
|
+
|
|
35
|
+
const onParentChange = (checked) => {
|
|
36
|
+
toggleMenu(menu.id, checked);
|
|
37
|
+
// toggle children recursively
|
|
38
|
+
children.forEach((c) => toggleMenuRecursive(c, checked));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const toggleMenuRecursive = (menu, checked) => {
|
|
42
|
+
toggleMenu(menu.id, checked);
|
|
43
|
+
if (menu.sub_menus && menu.sub_menus.length > 0) {
|
|
44
|
+
menu.sub_menus.forEach((c) => toggleMenuRecursive(c, checked));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (children.length === 0) {
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
style={{
|
|
52
|
+
justifyContent: 'space-between',
|
|
53
|
+
display: 'flex',
|
|
54
|
+
alignItems: 'center',
|
|
55
|
+
border: '1px solid rgba(198, 195, 195, 0.85)',
|
|
56
|
+
// borderRadius: 6,
|
|
57
|
+
padding: '12px 16px',
|
|
58
|
+
marginBottom: 6,
|
|
59
|
+
background: '#fff',
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<div
|
|
63
|
+
key={menu.id}
|
|
64
|
+
style={{
|
|
65
|
+
display: 'flex',
|
|
66
|
+
alignItems: 'center',
|
|
67
|
+
gap: 8,
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
<Checkbox
|
|
71
|
+
checked={selectedMenus.includes(menu.id)}
|
|
72
|
+
onChange={(e) => {
|
|
73
|
+
const checked = e.target.checked;
|
|
74
|
+
|
|
75
|
+
toggleMenu(menu.id, checked);
|
|
76
|
+
|
|
77
|
+
// FORCE parent selection
|
|
78
|
+
if (checked && parentId) {
|
|
79
|
+
toggleMenu(parentId, true);
|
|
80
|
+
}
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
<MenuLabel menu={menu} />
|
|
84
|
+
</div>
|
|
85
|
+
<Tag color={menu.is_visible === true ? 'green' : 'blue'}>{menu.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Collapse
|
|
92
|
+
// remount when search toggles so panels open while searching and collapse when cleared
|
|
93
|
+
key={`${menu.id}-${searchActive}`}
|
|
94
|
+
style={{ marginBottom: 6 }}
|
|
95
|
+
defaultActiveKey={searchActive ? [menu.id] : undefined}
|
|
96
|
+
>
|
|
97
|
+
<Panel
|
|
98
|
+
key={menu.id}
|
|
99
|
+
header={
|
|
100
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
101
|
+
<div
|
|
102
|
+
style={{
|
|
103
|
+
display: 'flex',
|
|
104
|
+
alignItems: 'center',
|
|
105
|
+
gap: 8,
|
|
106
|
+
}}
|
|
107
|
+
onClick={(e) => e.stopPropagation()}
|
|
108
|
+
>
|
|
109
|
+
<Checkbox checked={parentChecked} indeterminate={parentIndeterminate} onChange={(e) => onParentChange(e.target.checked)} />
|
|
110
|
+
<MenuLabel menu={menu} />
|
|
111
|
+
</div>
|
|
112
|
+
<Tag color={menu.is_visible === true ? 'green' : 'blue'}>{menu.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
|
|
113
|
+
</div>
|
|
114
|
+
}
|
|
115
|
+
>
|
|
116
|
+
<div style={{ paddingLeft: 20 }}>
|
|
117
|
+
<MenuTree menus={children} selectedMenus={selectedMenus} toggleMenu={toggleMenu} parentId={menu.id} searchActive={searchActive} />
|
|
118
|
+
</div>
|
|
119
|
+
</Panel>
|
|
120
|
+
</Collapse>
|
|
121
|
+
);
|
|
122
|
+
})}
|
|
123
|
+
</>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export default MenuTree;
|