ui-soxo-bootstrap-core 2.6.30 → 2.6.32-dev.0
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/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 +16 -76
- package/core/lib/components/index.js +2 -2
- package/core/lib/components/sidemenu/sidemenu.js +13 -8
- package/core/lib/models/forms/components/form-creator/form-creator.js +55 -37
- package/core/models/core-scripts/core-scripts.js +14 -1
- package/core/models/menus/menus.js +29 -1
- package/core/modules/reporting/components/reporting-dashboard/adavance-search/advance-search.js +18 -4
- 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 +161 -531
- package/core/modules/reporting/components/reporting-dashboard/reporting-table.js +519 -0
- package/package.json +1 -1
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Table, Skeleton, Input, Modal, message, Pagination } from 'antd';
|
|
3
|
+
import { QrcodeOutlined } from '@ant-design/icons';
|
|
4
|
+
import { ExportReactCSV, getExportData, Card, TableComponent, QrScanner } from './../../../../lib/';
|
|
5
|
+
import moment from 'moment-timezone';
|
|
6
|
+
import { CoreScripts } from './../../../../models/';
|
|
7
|
+
import Button from '../../../../lib/elements/basic/button/button';
|
|
8
|
+
import buildDisplayColumns from './display-columns/build-display-columns';
|
|
9
|
+
import { getRedirectLink } from './display-columns/display-cell-renderer';
|
|
10
|
+
import * as ReportingDashboardComp from '../index';
|
|
11
|
+
|
|
12
|
+
const { Search } = Input;
|
|
13
|
+
const genericComponents = require('./../../../../lib');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} ReportingTablePagination
|
|
17
|
+
* @property {number} [current] Current page number.
|
|
18
|
+
* @property {number} [pageSize] Number of rows per page.
|
|
19
|
+
* @property {number} [total] Total number of records available.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} ReportingTableRequestPayload
|
|
24
|
+
* @property {Object} [body] Request body sent when loading reporting data.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {Object} ReportingTableProps
|
|
29
|
+
* @property {Array<Object>} [patients] Preloaded table rows supplied by a parent component.
|
|
30
|
+
* @property {Array<Object>} [columns] Preconfigured display column definitions.
|
|
31
|
+
* @property {boolean} [loading] External loading state used when parent owns data fetching.
|
|
32
|
+
* @property {Object.<string, React.ComponentType<any>>} [CustomComponents] Custom renderers/actions available to display columns.
|
|
33
|
+
* @property {Function} [refresh] Refresh callback passed down to nested action components.
|
|
34
|
+
* @property {boolean} [isFixedIndex] Enables fixed index rendering in generated columns.
|
|
35
|
+
* @property {string} [barcodeFilterKey] Record field compared against scanned QR/barcode values.
|
|
36
|
+
* @property {boolean} [showScanner] Controls whether the QR scanner button is shown.
|
|
37
|
+
* @property {Object} [config] Reporting configuration returned by the core script.
|
|
38
|
+
* @property {ReportingTablePagination} [pagination] External pagination state.
|
|
39
|
+
* @property {(pager: ReportingTablePagination) => void} [handlePagination] Parent-owned pagination handler.
|
|
40
|
+
* @property {string} [attributes] JSON string containing extra button/config attributes.
|
|
41
|
+
* @property {Function} [fetchReportData] Parent refresh callback used in controlled mode.
|
|
42
|
+
* @property {string|number} [reportId] Report id used to load configuration and rows.
|
|
43
|
+
* @property {string|number} [requestId] Alternative report request id used for independent loading.
|
|
44
|
+
* @property {ReportingTableRequestPayload} [requestPayload] Optional request payload override for listing calls.
|
|
45
|
+
* @property {(pager: ReportingTablePagination) => void} [onPaginationChange] Callback fired after independent pagination updates.
|
|
46
|
+
* @property {string} [mode] Report mode used when fetching by mode/submode.
|
|
47
|
+
* @property {string} [submode] Report submode used when fetching by mode/submode.
|
|
48
|
+
* @property {Object} [replacements] Dynamic replacements used by some report APIs and action links.
|
|
49
|
+
* @property {boolean} [isNuradesk] Switches to Nuradesk-specific report loading behavior.
|
|
50
|
+
* @property {string} [dbPtr] Optional branch pointer override for report API calls.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* ReportingTable renders report rows with search, summary, export, QR scan, and
|
|
55
|
+
* pagination support.
|
|
56
|
+
*
|
|
57
|
+
* The component supports two data modes:
|
|
58
|
+
* - controlled mode: parent supplies rows/columns/loading/pagination
|
|
59
|
+
* - independent mode: the component fetches configuration and listing data
|
|
60
|
+
* using `reportId`, `requestId`, or mode/submode inputs
|
|
61
|
+
*
|
|
62
|
+
* @param {ReportingTableProps} props
|
|
63
|
+
* @returns {React.ReactNode}
|
|
64
|
+
*/
|
|
65
|
+
export default function ReportingTable({
|
|
66
|
+
patients: propsPatients,
|
|
67
|
+
columns: propsColumns,
|
|
68
|
+
loading: propsLoading,
|
|
69
|
+
CustomComponents,
|
|
70
|
+
refresh,
|
|
71
|
+
isFixedIndex,
|
|
72
|
+
barcodeFilterKey,
|
|
73
|
+
showScanner,
|
|
74
|
+
config: propsConfig,
|
|
75
|
+
pagination: propsPagination,
|
|
76
|
+
handlePagination: propsHandlePagination,
|
|
77
|
+
attributes,
|
|
78
|
+
fetchReportData,
|
|
79
|
+
reportId,
|
|
80
|
+
requestId,
|
|
81
|
+
requestPayload,
|
|
82
|
+
// dbPtr: propsDbPtr,
|
|
83
|
+
onPaginationChange,
|
|
84
|
+
mode,
|
|
85
|
+
submode,
|
|
86
|
+
replacements,
|
|
87
|
+
isNuradesk,
|
|
88
|
+
dbPtr
|
|
89
|
+
}) {
|
|
90
|
+
const [internalPatients, setInternalPatients] = useState([]);
|
|
91
|
+
const [internalColumns, setInternalColumns] = useState([]);
|
|
92
|
+
const [internalLoading, setInternalLoading] = useState(false);
|
|
93
|
+
const [internalConfig, setInternalConfig] = useState({});
|
|
94
|
+
const [internalPagination, setInternalPagination] = useState({ current: 1, pageSize: 20, total: 0 });
|
|
95
|
+
|
|
96
|
+
// Independent mode is enabled when enough identifiers are present for the
|
|
97
|
+
// table to load its own schema and data instead of relying on parent props.
|
|
98
|
+
const shouldFetchData = !!(requestId || reportId || replacements.submode || replacements.mode);
|
|
99
|
+
const requestPayloadKey = JSON.stringify(requestPayload || {});
|
|
100
|
+
|
|
101
|
+
const propValues = (attributes && JSON.parse(attributes)) || {};
|
|
102
|
+
const { buttonAttributes = [] } = propValues;
|
|
103
|
+
|
|
104
|
+
const [query, setQuery] = useState('');
|
|
105
|
+
const [exportData, setExportData] = useState({});
|
|
106
|
+
const [isScannerVisible, setScannerVisible] = useState(false);
|
|
107
|
+
const [visible, setVisible] = useState(false);
|
|
108
|
+
const [ActiveComponent, setActiveComponent] = useState(null);
|
|
109
|
+
const [single, setSingle] = useState({});
|
|
110
|
+
|
|
111
|
+
// The table can operate in either controlled or self-fetching mode. These
|
|
112
|
+
// selectors keep the render path mode-agnostic after initial setup.
|
|
113
|
+
const patients = shouldFetchData ? internalPatients : propsPatients;
|
|
114
|
+
const columns = propsColumns?.length ? propsColumns : internalColumns;
|
|
115
|
+
const loading = shouldFetchData ? internalLoading : propsLoading;
|
|
116
|
+
const config = Object.keys(propsConfig || {}).length ? propsConfig : internalConfig;
|
|
117
|
+
const pagination = shouldFetchData ? internalPagination : propsPagination;
|
|
118
|
+
const otherDetails = config?.other_details1 ? JSON.parse(config.other_details1) : {};
|
|
119
|
+
const cols = buildDisplayColumns({
|
|
120
|
+
columns,
|
|
121
|
+
patients,
|
|
122
|
+
isFixedIndex,
|
|
123
|
+
CustomComponents: { ...CustomComponents, ...genericComponents, ...ReportingDashboardComp },
|
|
124
|
+
refresh,
|
|
125
|
+
otherDetails,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Updates the local text filter used for in-memory search across row values.
|
|
130
|
+
*
|
|
131
|
+
* @param {React.ChangeEvent<HTMLInputElement>} event
|
|
132
|
+
*/
|
|
133
|
+
function onSearch(event) {
|
|
134
|
+
setQuery(event.target.value);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Loads reporting configuration and paginated data when the component is
|
|
139
|
+
* operating in independent mode.
|
|
140
|
+
*
|
|
141
|
+
* Flow:
|
|
142
|
+
* 1. Resolve the active report key and db pointer.
|
|
143
|
+
* 2. Fetch report configuration to build display columns/caption metadata.
|
|
144
|
+
* 3. Fetch listing data using the current page and page size.
|
|
145
|
+
* 4. Sync local pagination and notify the parent if needed.
|
|
146
|
+
*
|
|
147
|
+
* @param {ReportingTablePagination} [pager]
|
|
148
|
+
* @returns {Promise<void>}
|
|
149
|
+
*/
|
|
150
|
+
const loadIndependentData = async (pager) => {
|
|
151
|
+
setInternalLoading(true);
|
|
152
|
+
const dbPointer = dbPtr || localStorage.db_ptr;
|
|
153
|
+
|
|
154
|
+
const currentPager = pager || internalPagination;
|
|
155
|
+
let reportKey = requestId || reportId || replacements?.mode;
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
if (!reportKey) return;
|
|
159
|
+
|
|
160
|
+
// Load the report definition first so display columns and captions stay in
|
|
161
|
+
// sync with the server-side report being requested.
|
|
162
|
+
let formBody = {
|
|
163
|
+
reportId: reportId,
|
|
164
|
+
};
|
|
165
|
+
if (replacements?.mode && replacements?.submode) {
|
|
166
|
+
formBody = {
|
|
167
|
+
mode: replacements.mode,
|
|
168
|
+
submode: replacements.submode,
|
|
169
|
+
// replacements: { ...replacements },
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let data = await CoreScripts.getCorescript({ ...formBody },dbPointer);
|
|
174
|
+
if (data?.result?.display_columns) {
|
|
175
|
+
setInternalColumns(JSON.parse(data.result.display_columns));
|
|
176
|
+
}
|
|
177
|
+
setInternalConfig(data);
|
|
178
|
+
// if (isNuradesk) {
|
|
179
|
+
// setInternalPatients(data.result || []);
|
|
180
|
+
// setInternalColumns(Object.keys(data.result[0]).map((key) => ({ title: key, field: key })));
|
|
181
|
+
// }
|
|
182
|
+
// }
|
|
183
|
+
|
|
184
|
+
// Then request the actual row data for the active page.
|
|
185
|
+
const baseBody = requestPayload?.body || {};
|
|
186
|
+
let body = {
|
|
187
|
+
body: {
|
|
188
|
+
...baseBody,
|
|
189
|
+
page: currentPager.current,
|
|
190
|
+
limit: currentPager.pageSize,
|
|
191
|
+
...(requestPayload?.body ? {} : { mode, submode }),
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
// if (!isNuradesk) {
|
|
195
|
+
if (isNuradesk) {
|
|
196
|
+
reportKey = data.result.id;
|
|
197
|
+
body = {
|
|
198
|
+
body: {
|
|
199
|
+
...replacements,
|
|
200
|
+
page: currentPager.current,
|
|
201
|
+
limit: currentPager.pageSize,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const result = await CoreScripts.getReportingLisitng(reportKey, body, dbPtr);
|
|
206
|
+
const apiData = Array.isArray(result) ? result : Array.isArray(result?.result) ? result.result : [];
|
|
207
|
+
const resultDetails = apiData[0] || [];
|
|
208
|
+
|
|
209
|
+
setInternalPatients(resultDetails || []);
|
|
210
|
+
// Fall back to inferred columns when the report definition does not
|
|
211
|
+
// provide an explicit `display_columns` config.
|
|
212
|
+
if (!data.result.display_columns && resultDetails.length > 0) {
|
|
213
|
+
setInternalColumns(Object.keys(resultDetails[0]).map((key) => ({ title: key, field: key })));
|
|
214
|
+
}
|
|
215
|
+
// }
|
|
216
|
+
|
|
217
|
+
const nextPagination = {
|
|
218
|
+
current: currentPager.current,
|
|
219
|
+
pageSize: currentPager.pageSize,
|
|
220
|
+
total: resultDetails?.[0]?.TotalCount ?? internalPagination.total,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
setInternalPagination((prev) => ({
|
|
224
|
+
...prev,
|
|
225
|
+
...nextPagination,
|
|
226
|
+
}));
|
|
227
|
+
|
|
228
|
+
if (onPaginationChange) {
|
|
229
|
+
onPaginationChange(nextPagination);
|
|
230
|
+
}
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error('Error loading independent table data', error);
|
|
233
|
+
} finally {
|
|
234
|
+
setInternalLoading(false);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
if (propsPagination?.current || propsPagination?.pageSize || propsPagination?.total === 0) {
|
|
240
|
+
setInternalPagination((prev) => ({
|
|
241
|
+
...prev,
|
|
242
|
+
current: propsPagination?.current || prev.current,
|
|
243
|
+
pageSize: propsPagination?.pageSize || prev.pageSize,
|
|
244
|
+
total: typeof propsPagination?.total === 'number' ? propsPagination.total : prev.total,
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
}, [propsPagination?.current, propsPagination?.pageSize, propsPagination?.total]);
|
|
248
|
+
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
if (shouldFetchData) {
|
|
251
|
+
const nextPager = {
|
|
252
|
+
current: propsPagination?.current || 1,
|
|
253
|
+
pageSize: propsPagination?.pageSize || internalPagination.pageSize,
|
|
254
|
+
};
|
|
255
|
+
loadIndependentData(nextPager);
|
|
256
|
+
}
|
|
257
|
+
}, [requestId, reportId, mode, submode, requestPayloadKey]);
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Routes pagination changes to either the internal loader or the parent
|
|
261
|
+
* handler, depending on the current data mode.
|
|
262
|
+
*
|
|
263
|
+
* @param {ReportingTablePagination} pager
|
|
264
|
+
*/
|
|
265
|
+
const handlePaginationInternal = (pager) => {
|
|
266
|
+
if (shouldFetchData) {
|
|
267
|
+
loadIndependentData(pager);
|
|
268
|
+
} else if (propsHandlePagination) {
|
|
269
|
+
propsHandlePagination(pager);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
if (patients) {
|
|
275
|
+
// Export uses the visible display column structure and appends the same
|
|
276
|
+
// summary row that appears in the table when summary columns are enabled.
|
|
277
|
+
const exportCols = cols.map((col) => {
|
|
278
|
+
if (col.title && typeof col.title === 'object' && col.title.props) {
|
|
279
|
+
return { ...col, title: col.title.props.title };
|
|
280
|
+
}
|
|
281
|
+
return col;
|
|
282
|
+
});
|
|
283
|
+
const summaryCols = columns.filter((col) => col.enable_summary);
|
|
284
|
+
let dataToExport = [...patients];
|
|
285
|
+
|
|
286
|
+
if (summaryCols.length > 0) {
|
|
287
|
+
const summaryValues = calculateSummaryValues(summaryCols, patients);
|
|
288
|
+
const summaryRow = { isSummaryRow: true };
|
|
289
|
+
|
|
290
|
+
cols.forEach((col) => {
|
|
291
|
+
const colKey = col.field || col.key || col.dataIndex;
|
|
292
|
+
if (colKey && !summaryRow[colKey]) {
|
|
293
|
+
summaryRow[colKey] = '';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (summaryValues[col.field] !== undefined) {
|
|
297
|
+
summaryRow[col.field] = summaryValues[col.field];
|
|
298
|
+
} else {
|
|
299
|
+
const captionConfig = columns.find((c) => col.field && c.caption_field === col.field);
|
|
300
|
+
if (captionConfig) {
|
|
301
|
+
summaryRow[col.field] = captionConfig.summary_caption || '';
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
dataToExport.push(summaryRow);
|
|
306
|
+
}
|
|
307
|
+
let exportDatas = getExportData(dataToExport, exportCols);
|
|
308
|
+
|
|
309
|
+
if (exportDatas.exportDataColumns.length && exportDatas.exportDataHeaders.length) {
|
|
310
|
+
setExportData({ exportDatas });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}, [patients, columns]);
|
|
314
|
+
|
|
315
|
+
let filtered = patients;
|
|
316
|
+
if (patients && query) {
|
|
317
|
+
filtered = patients.filter((record) => {
|
|
318
|
+
let keys = Object.keys(record);
|
|
319
|
+
let flag = false;
|
|
320
|
+
keys.forEach((key) => {
|
|
321
|
+
let ele = record[key];
|
|
322
|
+
if (ele && typeof ele === 'string' && ele.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
|
|
323
|
+
flag = true;
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
return flag;
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Handles successful QR scans by looking up a matching record and navigating
|
|
332
|
+
* through the first configured action column.
|
|
333
|
+
*
|
|
334
|
+
* @param {string} code
|
|
335
|
+
*/
|
|
336
|
+
const handleScanSuccess = (code) => {
|
|
337
|
+
const matched = filtered.filter((patient) => patient[barcodeFilterKey] === code);
|
|
338
|
+
if (matched.length) {
|
|
339
|
+
const patient = matched[0];
|
|
340
|
+
message.success(`Match found for ${code}, redirecting...`);
|
|
341
|
+
const actionColumn = columns.find((col) => col.field === 'action') || columns.find((col) => col.type === 'action');
|
|
342
|
+
if (actionColumn) {
|
|
343
|
+
const redirectLink = getRedirectLink(actionColumn, patient,CustomComponents);
|
|
344
|
+
window.location.href = redirectLink;
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
Modal.warning({
|
|
348
|
+
title: 'No matching records.',
|
|
349
|
+
content: `No match for scanned code: ${code}`,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Opens a reporting dashboard action component declared in `buttonAttributes`.
|
|
356
|
+
*
|
|
357
|
+
* @param {Object} button
|
|
358
|
+
*/
|
|
359
|
+
const handleOpenEdit = (button) => {
|
|
360
|
+
const componentName = button.component;
|
|
361
|
+
const ComponentToRender = ReportingDashboardComp[componentName];
|
|
362
|
+
if (!ComponentToRender) {
|
|
363
|
+
console.error(`Component ${componentName} not found!`);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
setSingle({});
|
|
367
|
+
setActiveComponent(() => ComponentToRender);
|
|
368
|
+
setVisible(true);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Computes summary values for the current page based on per-column summary
|
|
373
|
+
* configuration.
|
|
374
|
+
*
|
|
375
|
+
* Supported functions:
|
|
376
|
+
* - `sum`
|
|
377
|
+
* - `count`
|
|
378
|
+
* - `avg`
|
|
379
|
+
* - `min`
|
|
380
|
+
* - `max`
|
|
381
|
+
*
|
|
382
|
+
* @param {Array<Object>} summaryCols
|
|
383
|
+
* @param {Array<Object>} pageData
|
|
384
|
+
* @returns {Object.<string, number>}
|
|
385
|
+
*/
|
|
386
|
+
function calculateSummaryValues(summaryCols, pageData) {
|
|
387
|
+
const summaryValues = {};
|
|
388
|
+
summaryCols.forEach((col) => {
|
|
389
|
+
const field = col.field;
|
|
390
|
+
if (col.function === 'sum') {
|
|
391
|
+
summaryValues[field] = pageData.reduce((total, row) => total + Number(row[field] || 0), 0);
|
|
392
|
+
}
|
|
393
|
+
if (col.function === 'count') {
|
|
394
|
+
summaryValues[field] = pageData.length;
|
|
395
|
+
}
|
|
396
|
+
if (col.function === 'avg') {
|
|
397
|
+
const total = pageData.reduce((sum, row) => sum + Number(row[field] || 0), 0);
|
|
398
|
+
summaryValues[field] = pageData.length ? total / pageData.length : 0;
|
|
399
|
+
}
|
|
400
|
+
if (col.function === 'min') {
|
|
401
|
+
const values = pageData.map((row) => Number(row[field] || 0));
|
|
402
|
+
summaryValues[field] = values.length ? Math.min(...values) : 0;
|
|
403
|
+
}
|
|
404
|
+
if (col.function === 'max') {
|
|
405
|
+
const values = pageData.map((row) => Number(row[field] || 0));
|
|
406
|
+
summaryValues[field] = values.length ? Math.max(...values) : 0;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
return summaryValues;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return (
|
|
413
|
+
<>
|
|
414
|
+
<div
|
|
415
|
+
className="table-header"
|
|
416
|
+
style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, paddingTop: 10, paddingBottom: 10 }}
|
|
417
|
+
>
|
|
418
|
+
<div className="table-right" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 8, flexWrap: 'wrap' }}>
|
|
419
|
+
<div className="table-search" style={{ minWidth: 240, width: 280, maxWidth: '100%', flex: '0 0 auto' }}>
|
|
420
|
+
<Search placeholder="Enter Search Value" allowClear onChange={onSearch} />
|
|
421
|
+
</div>
|
|
422
|
+
{showScanner && (
|
|
423
|
+
<Button size="small" type="primary" icon={<QrcodeOutlined />} onClick={() => setScannerVisible(true)}>
|
|
424
|
+
Scan QR
|
|
425
|
+
</Button>
|
|
426
|
+
)}
|
|
427
|
+
{Array.isArray(buttonAttributes) &&
|
|
428
|
+
buttonAttributes.map((btn, index) => (
|
|
429
|
+
<Button key={index} size="small" type="primary" style={{ marginLeft: 8 }} onClick={() => handleOpenEdit(btn)}>
|
|
430
|
+
{btn.title}
|
|
431
|
+
</Button>
|
|
432
|
+
))}
|
|
433
|
+
<Modal open={visible} onCancel={() => setVisible(false)} footer={null} destroyOnClose width={950} style={{ top: 10 }}>
|
|
434
|
+
{ActiveComponent && (
|
|
435
|
+
<ActiveComponent
|
|
436
|
+
formContent={single}
|
|
437
|
+
callback={() => {
|
|
438
|
+
setVisible(false);
|
|
439
|
+
refresh && refresh();
|
|
440
|
+
shouldFetchData ? loadIndependentData() : fetchReportData && fetchReportData();
|
|
441
|
+
}}
|
|
442
|
+
/>
|
|
443
|
+
)}
|
|
444
|
+
</Modal>
|
|
445
|
+
<Modal open={isScannerVisible} title="Scan QR Code" footer={null} onCancel={() => setScannerVisible(false)} destroyOnClose>
|
|
446
|
+
<QrScanner onScanSuccess={handleScanSuccess} onClose={() => setScannerVisible(false)} />
|
|
447
|
+
</Modal>
|
|
448
|
+
<div>
|
|
449
|
+
{exportData.exportDatas && !isNuradesk && (
|
|
450
|
+
<ExportReactCSV
|
|
451
|
+
title={config.caption}
|
|
452
|
+
fileName={`${(config.caption || 'Report').trim().replace(/\s+/g, '_')}_${moment().format('YYYY-MM-DD-HH-mm-ss-SSS')}.xlsx`}
|
|
453
|
+
headers={exportData.exportDatas.exportDataHeaders}
|
|
454
|
+
csvData={exportData.exportDatas.exportDataColumns}
|
|
455
|
+
/>
|
|
456
|
+
)}
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
460
|
+
<div>
|
|
461
|
+
<Card>
|
|
462
|
+
{loading ? (
|
|
463
|
+
<Skeleton active paragraph={{ rows: 6 }} />
|
|
464
|
+
) : (
|
|
465
|
+
<TableComponent
|
|
466
|
+
size="small"
|
|
467
|
+
// Change x to '100%' or true to make it responsive to the container width
|
|
468
|
+
// Your vertical logic (adjust '10' based on how many rows fit on your screen)
|
|
469
|
+
scroll={{ x: 'max-content', y: (filtered?.length || 0) > 11 ? 400 : undefined }}
|
|
470
|
+
rowKey={(record) => record.OpNo}
|
|
471
|
+
dataSource={filtered}
|
|
472
|
+
columns={cols}
|
|
473
|
+
sticky
|
|
474
|
+
pagination={false}
|
|
475
|
+
summary={(pageData) => {
|
|
476
|
+
const summaryCols = columns.filter((col) => col.enable_summary);
|
|
477
|
+
if (!summaryCols.length) return null;
|
|
478
|
+
const summaryValues = calculateSummaryValues(summaryCols, pageData);
|
|
479
|
+
return (
|
|
480
|
+
<Table.Summary.Row className="report-summary-row">
|
|
481
|
+
{cols.map((col, index) => {
|
|
482
|
+
if (summaryValues[col.field] !== undefined) {
|
|
483
|
+
return (
|
|
484
|
+
<Table.Summary.Cell key={index}>
|
|
485
|
+
<strong style={{ fontWeight: 900 }}>{summaryValues[col.field]}</strong>
|
|
486
|
+
</Table.Summary.Cell>
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
const captionConfig = columns.find((c) => col.field && c.caption_field === col.field);
|
|
490
|
+
if (captionConfig) {
|
|
491
|
+
return (
|
|
492
|
+
<Table.Summary.Cell key={index}>
|
|
493
|
+
<strong style={{ fontWeight: 900 }}>{captionConfig.summary_caption || ''}</strong>
|
|
494
|
+
</Table.Summary.Cell>
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
return <Table.Summary.Cell key={index} />;
|
|
498
|
+
})}
|
|
499
|
+
</Table.Summary.Row>
|
|
500
|
+
);
|
|
501
|
+
}}
|
|
502
|
+
/>
|
|
503
|
+
)}
|
|
504
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}>
|
|
505
|
+
<Pagination
|
|
506
|
+
showSizeChanger
|
|
507
|
+
current={pagination?.current}
|
|
508
|
+
pageSize={pagination?.pageSize}
|
|
509
|
+
total={pagination?.total}
|
|
510
|
+
pageSizeOptions={[20, 30, 50, 100]}
|
|
511
|
+
onChange={(page, pageSize) => handlePaginationInternal({ current: page, pageSize })}
|
|
512
|
+
/>
|
|
513
|
+
</div>
|
|
514
|
+
<p className="size-hint">{patients ? patients.length : 0} records.</p>
|
|
515
|
+
</Card>
|
|
516
|
+
</div>
|
|
517
|
+
</>
|
|
518
|
+
);
|
|
519
|
+
}
|