ui-soxo-bootstrap-core 2.6.18 → 2.6.20

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.
@@ -6,79 +6,53 @@
6
6
  * Ensure to follow a minimal standard
7
7
  */
8
8
  import React, { useState, useEffect, useRef } from 'react';
9
-
10
9
  import PhoneInput from 'react-phone-input-2';
11
-
12
10
  import 'react-phone-input-2/lib/style.css';
13
-
14
- import PropTypes from "prop-types";
15
-
11
+ import PropTypes from 'prop-types';
16
12
  import './phone-input.scss';
17
13
 
18
-
19
- export default function CountryPhoneInput({ value, onChange, disabled, disableCountryCode, required, defaultCountryCode, onBlur, onKeyDown }) {
20
-
21
- let inputRef = useRef();
22
-
23
- const [phone, setPhone] = useState();
24
-
14
+ export default function CountryPhoneInput({
15
+ value,
16
+ onChange,
17
+ disabled,
18
+ disableCountryCode,
19
+ required,
20
+ defaultCountryCode,
21
+ onBlur,
22
+ className,
23
+ onKeyDown,
24
+ }) {
25
+ const inputRef = useRef();
26
+ const hasInitializedRef = useRef(false); // IMPORTANT
27
+
28
+ const [phone, setPhone] = useState('');
29
+ /**
30
+ * run ONLY ONCE when initial value has dial code
31
+ */
25
32
  useEffect(() => {
26
- if (value && value.code && value.code.dialCode !== null) {
27
- // To prepopulate phone number ,concat dialCode and phone number
33
+ if (!hasInitializedRef.current && value?.code?.dialCode && value?.value) {
28
34
  setPhone(value.code.dialCode + value.value);
29
-
30
- // In case of update , to identify the country code that is getting selected
31
- // We are setting a timeout
32
- setTimeout(() => {
33
- // Using the reference , we get the internal state of the library component
34
- let selectedCountry = null;
35
-
36
- let selectedCode = null;
37
-
38
- if (
39
- inputRef.current &&
40
- inputRef.current.state &&
41
- inputRef.current.state.selectedCountry
42
- ) {
43
- selectedCountry = inputRef.current.state.selectedCountry;
44
-
45
- // Once we get the selected country ,we format it to the form that is received for normal onChange
46
- selectedCode = {
47
- countryCode: selectedCountry.iso2,
48
- dialCode: selectedCountry.dialCode,
49
- name: selectedCountry.name,
50
- };
51
- }
52
-
53
- // Below being the format expected , we trigger the onChange to manually update the form
54
- let updatedValue = {
55
- value: value.code.dialCode + value.value,
56
- code: selectedCode,
57
- };
58
-
59
- onChange(updatedValue);
60
- }, 0);
35
+ hasInitializedRef.current = true; // 👈 mark as done
61
36
  }
62
- }, []);
37
+ }, [value]);
63
38
 
64
39
  /**
65
- * To get value in parent component
66
- * @param {*} value
40
+ * handle user input
67
41
  */
68
- function handleInput(value, code) {
69
- if (code.dialCode) {
70
- value = value.substring(code.dialCode.length);
71
- }
72
- // To get country code with phone number in parent component
73
- value = {
74
- value,
42
+ function handleInput(inputValue, code) {
43
+ if (!code?.dialCode) return;
44
+
45
+ const phoneNumber = inputValue.slice(code.dialCode.length);
46
+
47
+ // remove autofill class when user edits field (if you use it)
48
+ onChange({
49
+ value: phoneNumber,
75
50
  code,
76
- };
77
- onChange(value);
51
+ });
78
52
  }
79
53
 
80
54
  return (
81
- <div className="phone-input">
55
+ <div className={`phone-input ${className || ''}`}>
82
56
  <PhoneInput
83
57
  ref={inputRef}
84
58
  country={defaultCountryCode || 'in'}
@@ -104,4 +78,4 @@ CountryPhoneInput.propTypes = {
104
78
  disableCountryCode: PropTypes.bool,
105
79
  /** A boolean indicating whether the input is required or not. This prop is optional. */
106
80
  required: PropTypes.bool,
107
- };
81
+ };
@@ -12,6 +12,20 @@
12
12
  // }
13
13
  }
14
14
 
15
+ .autofilled-field {
16
+ .react-tel-input {
17
+ .form-control {
18
+ border-color: #fa8c16 !important; // AntD warning orange
19
+ background-color: #fff7e6;
20
+ box-shadow: none;
21
+ }
22
+
23
+ .flag-dropdown {
24
+ border-color: #fa8c16 !important;
25
+ background-color: #fff7e6;
26
+ }
27
+ }
28
+ }
15
29
  // /* For tablets and smaller screens (max-width: 1024px) */
16
30
  // @media (max-width: 1024px) {
17
31
  // .phone-input {
@@ -10,6 +10,8 @@
10
10
  import React from 'react';
11
11
  // Import XLSX library to handle Excel file creation
12
12
  import * as XLSX from 'xlsx';
13
+ // import * as XLSX from 'xlsx-js-style';
14
+ // import * as XLSX from 'xlsx-js-style';
13
15
  // Import saveAs from file-saver to trigger file download in browser
14
16
  import { saveAs } from 'file-saver';
15
17
  import { Button } from 'antd';
@@ -30,7 +32,27 @@ export const ExportReactCSV = ({ csvData, headers, fileName,title }) => {
30
32
  // Extract label names for the header row
31
33
  const headerLabels = headers.map((h) => h.label);
32
34
  // Map data rows according to the headers' key order
33
- const rows = csvData.map((row) => headers.map((h) => row[h.key]));
35
+ const rows = csvData.map((row) =>
36
+ headers.map((h, hIndex) => {
37
+ let value = row[h.key];
38
+ // If it's a summary row, return a cell object with bold styling
39
+ if (row.isSummaryRow) {
40
+ // Remove the index value (typically the first column) for the summary row
41
+ if (hIndex === 0) value = "";
42
+
43
+ return {
44
+ v: value,
45
+ s: {
46
+ font: { bold: true },
47
+ // "Thick" visual indicator via border
48
+ // Visual indicator via border
49
+ border: { bottom: { style: "thick", color: { rgb: "000000" } } }
50
+ }
51
+ };
52
+ }
53
+ return value;
54
+ })
55
+ );
34
56
  // Create a worksheet from an array of arrays (header + rows)
35
57
  const data = [[title || "Report"], [], headerLabels, ...rows];
36
58
  // worksheet = XLSX.utils.aoa_to_sheet([headerLabels, ...rows]);
@@ -44,7 +66,11 @@ export const ExportReactCSV = ({ csvData, headers, fileName,title }) => {
44
66
  // Append the worksheet to the workbook with the name 'Sheet1'
45
67
  XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
46
68
  // Write workbook to a buffer as an array
47
- const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
69
+ const excelBuffer = XLSX.write(workbook, {
70
+ bookType: 'xlsx',
71
+ type: 'array',
72
+ cellStyles: true, // Required to export styles
73
+ });
48
74
  // Create a Blob object from the buffer with correct MIME type
49
75
  const blob = new Blob([excelBuffer], {
50
76
  type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
@@ -30,7 +30,7 @@ import { getAccessToken, getRefreshToken } from '../../utils/http/auth.helper';
30
30
 
31
31
  import { Location } from '../../utils';
32
32
 
33
- import { checkLicenseStatus, formatMobile, checkExpiryStatus, safeJSON } from '../../utils/common/common.utils';
33
+ import { checkLicenseStatus, formatMobile, safeJSON } from '../../utils/common/common.utils';
34
34
 
35
35
  import { MailOutlined, MessageOutlined, WhatsAppOutlined } from '@ant-design/icons';
36
36
 
@@ -50,8 +50,6 @@ const tailLayout = {
50
50
 
51
51
  const LICENSE_EXPIRY = '2026-12-12';
52
52
 
53
- //password valdity expire
54
- const PASSWORD_VALIDITY_DAYS = 90;
55
53
 
56
54
  const headers = {
57
55
  db_ptr: 'nuraho',
@@ -115,47 +113,32 @@ function LoginPhone({ history, appSettings }) {
115
113
  *
116
114
  * - Parses `last_password_change` from user.other_details.
117
115
  * - Calculates expiry using PASSWORD_VALIDITY_DAYS.
118
- * - Uses `checkExpiryStatus()` to determine status.
119
116
  * - Shows Ant Design warning message if expired or within warning period.
120
117
  * - Warning message includes navigation to `/change-password`.
121
118
  *
122
119
  * Requires:
123
120
  * - PASSWORD_VALIDITY_DAYS constant
124
- * - checkExpiryStatus utility
125
121
  * - React Router history
126
122
  * - antd message component
127
123
  */
128
- const handlePasswordExpiryCheck = (user) => {
129
- const otherDetails = user?.other_details ? JSON.parse(user.other_details) : null;
130
-
131
- const lastPasswordChange = otherDetails?.last_password_change;
132
-
133
- if (lastPasswordChange) {
134
- const onlyDate = new Date(lastPasswordChange).toISOString().split('T')[0];
135
- const passwordExpiryDate = new Date(onlyDate);
136
- passwordExpiryDate.setDate(passwordExpiryDate.getDate() + PASSWORD_VALIDITY_DAYS);
137
-
138
- const passwordStatus = checkExpiryStatus({
139
- expiryDate: passwordExpiryDate,
140
- warningDays: 2,
141
- expiredMessage: 'Your password has expired. Please reset it.',
142
- warningMessage: (d) => (
143
- <span>
144
- Your password will expire in {d} day(s).{' '}
145
- <a
146
- onClick={() => {
147
- history.push('/change-password');
148
- }}
149
- >
150
- Click here to update.
151
- </a>
152
- </span>
153
- ),
154
- });
124
+ const handlePasswordExpiryCheck = (expiryDays) => {
125
+ if (expiryDays == null) return;
155
126
 
156
- if (passwordStatus.message) {
157
- message.warning(passwordStatus.message);
158
- }
127
+ if (expiryDays <= 0) {
128
+ message.error('Your password has expired. Please reset it.');
129
+
130
+ setExpiredPassword(true);
131
+ setShowResetpassword(true);
132
+
133
+ return;
134
+ }
135
+
136
+ if (expiryDays <= 2) {
137
+ message.warning(
138
+ <span>
139
+ Your password will expire in {expiryDays} day(s). <a onClick={() => history.push('/change-password')}>Click here to update.</a>
140
+ </span>
141
+ );
159
142
  }
160
143
  };
161
144
 
@@ -196,7 +179,7 @@ function LoginPhone({ history, appSettings }) {
196
179
  if (insider_token) localStorage.insider_token = insider_token;
197
180
 
198
181
  if (result.success) {
199
- handlePasswordExpiryCheck(user);
182
+ handlePasswordExpiryCheck(result.expiry_in_days);
200
183
 
201
184
  //two_factor_authentication variable is present then proceed Two factor authentication
202
185
  if (result.data && result.data.two_factor_authentication) {
@@ -323,6 +306,8 @@ function LoginPhone({ history, appSettings }) {
323
306
  *
324
307
  */
325
308
  const sendAuthenticationOtp = () => {
309
+ if (loading) return;
310
+
326
311
  // Reset previous error
327
312
  setModeError(false);
328
313
 
@@ -421,7 +406,7 @@ function LoginPhone({ history, appSettings }) {
421
406
  // set user info into local storage
422
407
  localStorage.setItem('userInfo', JSON.stringify(userInfo));
423
408
 
424
- handlePasswordExpiryCheck(result.user);
409
+ handlePasswordExpiryCheck(result.expiry_in_days);
425
410
 
426
411
  setTimeout(() => history.push('/'), 500);
427
412
  } else {
@@ -462,6 +447,8 @@ function LoginPhone({ history, appSettings }) {
462
447
  * Otp resend Logic
463
448
  */
464
449
  const handleResendOTP = () => {
450
+ if (loading) return;
451
+
465
452
  setOtpValue('');
466
453
  setModeError(false);
467
454
  setOtpError(false);
@@ -731,7 +718,7 @@ function LoginPhone({ history, appSettings }) {
731
718
 
732
719
  {!otpVerification ? (
733
720
  <div className="otp-container">
734
- <Button type="primary" onClick={sendAuthenticationOtp} style={{ marginTop: '6px' }}>
721
+ <Button loading={loading} type="primary" onClick={sendAuthenticationOtp} style={{ marginTop: '6px' }}>
735
722
  Send OTP
736
723
  </Button>
737
724
  </div>
@@ -777,7 +764,7 @@ function LoginPhone({ history, appSettings }) {
777
764
  </div>
778
765
  </div>
779
766
  {!otpSuccess && (
780
- <Button type="primary" disabled={otpExpired} onClick={verifyOtp} style={{ marginTop: 16 }}>
767
+ <Button loading={loading} type="primary" disabled={otpExpired} onClick={verifyOtp} style={{ marginTop: 16 }}>
781
768
  Verify OTP
782
769
  </Button>
783
770
  )}
@@ -1,4 +1,4 @@
1
- import { PostData, GetData, PutData, PatchData, DeleteData } from './../http/http.utils';
1
+ import { PostData, GetData, PutData, PatchData, DeleteData,UploadData } from './../http/http.utils';
2
2
 
3
3
  let headers = {};
4
4
 
@@ -124,33 +124,29 @@ export default class ApiUtils {
124
124
  });
125
125
  };
126
126
 
127
- static upload = ({ url, data }) => {
128
- return fetch(process.env.REACT_APP_endpoint + url, {
129
- // Your POST endpoint
130
- method: 'POST',
131
- headers: {
132
- // 'App-Type': 313,
133
- // 'App-Version': '1.0.1',
134
- Authorization: 'Bearer ' + localStorage.access_token,
135
- // type:'multipart/formData'
136
- },
137
- // credentials: 'include',
138
- body: data,
139
- }).then(
140
- (result) => {
141
- return result.json();
142
- },
143
- (error) => {
144
- console.log(error);
145
- return error;
146
- },
147
- (progress) => {
148
- console.log(progress);
149
- return progress;
150
- }
151
- );
127
+ // upload document
128
+ static upload = async ({ url, data, headers: customHeaders = {}, ...props }) => {
129
+ const dbPtr = localStorage.getItem('db_ptr');
130
+
131
+ // Ensure settings object exists to avoid undefined errors in ApiCall
132
+ const settings = {
133
+ headers: {},
134
+ getToken: () => localStorage.getItem('access_token')
135
+ };
136
+
137
+ return UploadData({
138
+ url,
139
+ formBody: data,
140
+ // Your FormData goes here
141
+ isUpload: true, // Indicate that this is an upload request
142
+ settings,
143
+ headers: {
144
+ ...(dbPtr ? { db_ptr: dbPtr } : {}),
145
+ ...customHeaders,
146
+ },
147
+ ...props,
148
+ });
152
149
  };
153
-
154
150
  /**
155
151
  * Get Auth Status
156
152
  *
@@ -123,41 +123,6 @@ export const checkLicenseStatus = (expiryDate) => {
123
123
  return { valid: true, daysLeft, message: null, level: null };
124
124
  };
125
125
 
126
- /**
127
- * Checks password expiry status.
128
- *
129
- * @param {string|Date} expiryDate
130
- * @param {number} warningDays
131
- * @param {string} expiredMessage
132
- * @param {(daysLeft: number) => string} warningMessage
133
- *
134
- * @returns {{ valid: boolean, daysLeft: number, message: string|null, level: "error"|"warning"|null }}
135
- */
136
-
137
- export const checkExpiryStatus = ({ expiryDate, warningDays, expiredMessage, warningMessage }) => {
138
- const expiry = new Date(expiryDate);
139
-
140
- if (isNaN(expiry)) {
141
- return { valid: false, daysLeft: 0, message: 'Invalid date', level: 'error' };
142
- }
143
-
144
- expiry.setHours(0, 0, 0, 0);
145
- const today = new Date();
146
- today.setHours(0, 0, 0, 0);
147
-
148
- const msDiff = expiry.getTime() - today.getTime();
149
- const daysLeft = Math.ceil(msDiff / (1000 * 60 * 60 * 24));
150
-
151
- if (daysLeft < 0) {
152
- return { valid: false, daysLeft, message: expiredMessage, level: 'error' };
153
- }
154
-
155
- if (daysLeft <= warningDays) {
156
- return { valid: true, daysLeft, message: warningMessage(daysLeft), level: 'warning' };
157
- }
158
-
159
- return { valid: true, daysLeft, message: null, level: null };
160
- };
161
126
 
162
127
  /**
163
128
  * Masks a mobile number by hiding all but the last `visibleDigits`.
@@ -31,7 +31,8 @@ export function getExportData(records, exportDataColumns) {
31
31
  const response = records.map((row, columnIndex) => {
32
32
 
33
33
  let entry = {
34
- 'Sl No': columnIndex + 1
34
+ 'Sl No': row.isSummaryRow ? '' : columnIndex + 1,
35
+ isSummaryRow: row.isSummaryRow
35
36
  };
36
37
 
37
38
  filteredColumns.forEach((column, indexValue) => {
@@ -70,24 +70,37 @@ export async function DeleteData({ url, ...props }) {
70
70
  *
71
71
  * @param {*} param
72
72
  */
73
-
74
73
  export async function ApiCall({ url, formBody, method, settings, ...props }) {
75
74
  const token = props.token || (await settings.getToken());
76
75
  const path = window.location.pathname;
77
76
  const baseUrl = props.baseUrl || process.env.REACT_APP_endpoint;
78
77
 
78
+ const isFormData = formBody instanceof FormData;
79
+
79
80
  const payload = {
80
81
  method,
81
82
  headers: {
82
83
  ...settings.headers,
83
- ...headers,
84
84
  ...(props.headers || {}),
85
85
  Authorization: `Bearer ${token}`,
86
- 'Content-Type': 'application/json',
87
86
  },
88
- body: formBody ? JSON.stringify(formBody) : null,
89
87
  };
90
88
 
89
+ // ✅ Upload + normal handling
90
+ if (method !== 'GET' && formBody) {
91
+ payload.body = formBody;
92
+
93
+ if (!isFormData && !(props.isUpload)) {
94
+ payload.body = JSON.stringify(formBody);
95
+ payload.headers['Content-Type'] = 'application/json';
96
+ }
97
+
98
+ // ❗ VERY IMPORTANT
99
+ if (isFormData || (props.isUpload)) {
100
+ delete payload.headers['Content-Type'];
101
+ }
102
+ }
103
+
91
104
  // 🟡 Handles both normal and retried responses
92
105
  const handleResponse = async (res) => {
93
106
  if (props.responseType === 'blob') {
@@ -156,3 +169,18 @@ export async function ApiCall({ url, formBody, method, settings, ...props }) {
156
169
  throw err;
157
170
  }
158
171
  }
172
+
173
+ /**
174
+ * Method for uploading documents
175
+ * @public
176
+ */
177
+ export async function UploadData({ url, formBody, ...props }) {
178
+ return ApiCall({
179
+ url,
180
+ formBody,
181
+ isUpload: true,
182
+ method: 'POST',
183
+ returnResponse: true,
184
+ ...props,
185
+ });
186
+ }
@@ -25,7 +25,7 @@ function getExportDefinition(entry, record) {
25
25
  * @param {Array} root0.columns
26
26
  * @param {Array} root0.patients
27
27
  * @param {boolean} root0.isFixedIndex
28
- * @param {Object} root0.CustomComponents
28
+ * @param {Object.<string, React.ComponentType<any>>} root0.CustomComponents
29
29
  * @param {Function} root0.refresh
30
30
  * @param {Object} [root0.otherDetails={}] - Optional details from the report configuration.
31
31
  * @param {boolean} [root0.otherDetails.isFilterEnabled] - Fallback to enable filtering on all columns.
@@ -298,7 +298,6 @@ export default function ReportingDashboard({
298
298
  }
299
299
  // Update patients
300
300
  setPatients(resultDetails || []);
301
- console.log(parsedColumns);
302
301
 
303
302
  // Check if columns are not yet defined
304
303
  if (parsedColumns.length === 0 && resultDetails.length > 0) {
@@ -601,6 +600,7 @@ function GuestList({
601
600
  const [single, setSingle] = useState({});
602
601
  const otherDetails = config.other_details1 ? JSON.parse(config.other_details1) : {};
603
602
 
603
+ // const otherDetails = config.other_details1 ? JSON.parse(config.other_details1) : {};
604
604
  // const [view, setView] = useState(isMobile ? true : false); //Need to check this condition
605
605
  const cols = buildDisplayColumns({
606
606
  columns,
@@ -608,7 +608,7 @@ function GuestList({
608
608
  isFixedIndex,
609
609
  CustomComponents,
610
610
  refresh,
611
- otherDetails
611
+ otherDetails,
612
612
  });
613
613
 
614
614
  /**
@@ -654,8 +654,39 @@ function GuestList({
654
654
  }
655
655
  return col;
656
656
  });
657
+ const summaryCols = columns.filter((col) => col.enable_summary);
658
+ let dataToExport = [...patients];
659
+
660
+ if (summaryCols.length > 0) {
661
+ // Build one synthetic row for CSV export that mirrors the table layout:
662
+ // numeric summary cells are populated from `calculateSummaryValues`, while
663
+ // non-summary columns stay blank unless a configured caption should be shown.
664
+ const summaryValues = calculateSummaryValues(summaryCols, patients);
665
+ const summaryRow = { isSummaryRow: true };
666
+
667
+ cols.forEach((col, index) => {
668
+ // Start each export column empty so the appended row keeps the same shape
669
+ // as the data rows and does not leak index/helper values into the export.
670
+ const colKey = col.field || col.key || col.dataIndex;
671
+ if (colKey && !summaryRow[colKey]) {
672
+ summaryRow[colKey] = '';
673
+ }
657
674
 
658
- let exportDatas = getExportData(patients, exportCols);
675
+ if (summaryValues[col.field] !== undefined) {
676
+ // Fill columns that have an aggregate configured (sum, count, avg, etc.).
677
+ summaryRow[col.field] = summaryValues[col.field];
678
+ } else {
679
+ // If this column is marked as the caption target for a summary column,
680
+ // place the configured label (for example "Total") into that cell.
681
+ const captionConfig = columns.find((c) => col.field && c.caption_field === col.field);
682
+ if (captionConfig) {
683
+ summaryRow[col.field] = captionConfig.summary_caption || '';
684
+ }
685
+ }
686
+ });
687
+ dataToExport.push(summaryRow);
688
+ }
689
+ let exportDatas = getExportData(dataToExport, exportCols);
659
690
 
660
691
  if (exportDatas.exportDataColumns.length && exportDatas.exportDataHeaders.length) {
661
692
  setExportData({ exportDatas });
@@ -740,6 +771,55 @@ function GuestList({
740
771
  const handleCloseEdit = () => {
741
772
  setShowEdit(false);
742
773
  };
774
+ /**
775
+ * Calculates aggregate values for the configured summary columns.
776
+ *
777
+ * Each summary definition contributes one value keyed by its `field`. Missing
778
+ * row values are treated as `0` for numeric operations so the table summary and
779
+ * export summary row can be built from the same result object.
780
+ *
781
+ * Supported functions:
782
+ * `sum` - totals all numeric values in the field.
783
+ * `count` - returns the number of rows in the current dataset.
784
+ * `avg` - returns the arithmetic mean of the field values.
785
+ * `min` - returns the smallest numeric value in the field.
786
+ * `max` - returns the largest numeric value in the field.
787
+ *
788
+ * @param {Array<Object>} summaryCols - Column configs with `field` and `function`.
789
+ * @param {Array<Object>} pageData - Rows currently being summarized.
790
+ * @returns {Object} Aggregate values keyed by field name.
791
+ */
792
+ function calculateSummaryValues(summaryCols, pageData) {
793
+ const summaryValues = {};
794
+
795
+ summaryCols.forEach((col) => {
796
+ const field = col.field;
797
+
798
+ if (col.function === 'sum') {
799
+ summaryValues[field] = pageData.reduce((total, row) => total + Number(row[field] || 0), 0);
800
+ }
801
+
802
+ if (col.function === 'count') {
803
+ summaryValues[field] = pageData.length;
804
+ }
805
+
806
+ if (col.function === 'avg') {
807
+ const total = pageData.reduce((sum, row) => sum + Number(row[field] || 0), 0);
808
+ summaryValues[field] = pageData.length ? total / pageData.length : 0;
809
+ }
810
+ if (col.function === 'min') {
811
+ const values = pageData.map((row) => Number(row[field] || 0));
812
+ summaryValues[field] = values.length ? Math.min(...values) : 0;
813
+ }
814
+
815
+ if (col.function === 'max') {
816
+ const values = pageData.map((row) => Number(row[field] || 0));
817
+ summaryValues[field] = values.length ? Math.max(...values) : 0;
818
+ }
819
+ });
820
+
821
+ return summaryValues;
822
+ }
743
823
  return (
744
824
  <>
745
825
  <div className="table-header">
@@ -790,6 +870,7 @@ function GuestList({
790
870
  {exportData.exportDatas && (
791
871
  <ExportReactCSV
792
872
  title={config.caption}
873
+ fileName={`${(config.caption || 'Report').trim().replace(/\s+/g, '_')}_${moment().format('YYYY-MM-DD-HH-mm-ss-SSS')}.xlsx`}
793
874
  headers={exportData.exportDatas.exportDataHeaders}
794
875
  csvData={exportData.exportDatas.exportDataColumns}
795
876
  />
@@ -814,47 +895,37 @@ function GuestList({
814
895
  columns={cols}
815
896
  sticky
816
897
  pagination={false}
817
- // title={config.caption}
818
898
  summary={(pageData) => {
819
- // Variable to save the summary data
820
- let summary = {};
821
-
822
- let summaryColumns = [
823
- { field: 'opb_amt', title: 'Amount' },
824
- { field: 'opb_netamt', title: 'Net Amount' },
825
- ];
826
-
827
- let tableColumns = cols;
828
-
829
- // Creating a copy of columns to append the summary configuration that is needed to set
830
- tableColumns.forEach((record, index) => {
831
- summaryColumns.forEach((inner) => {
832
- if (record.field === inner.field) {
833
- tableColumns[index].summary = inner;
834
- }
835
- });
836
- });
837
-
838
- // Initialize
839
- summaryColumns.map((item) => {
840
- return (summary[item.field] = 0);
841
- });
842
-
843
- // Find the total
844
- summaryColumns.map((item) => {
845
- pageData.forEach((entry) => {
846
- return (summary[item.field] = summary[item.field] + entry[item.field]);
847
- });
848
- });
899
+ const summaryCols = columns.filter((col) => col.enable_summary);
900
+ if (!summaryCols.length) return null;
901
+ /** calculate summary*/
902
+
903
+ const summaryValues = calculateSummaryValues(summaryCols, pageData);
904
+
849
905
 
850
906
  return (
851
- <>
852
- <Table.Summary.Row>
853
- {tableColumns.map((column, key) => {
854
- return <Table.Summary.Cell key={key}>{column.summary ? <>{summary[column.summary.field]}</> : null}</Table.Summary.Cell>;
855
- })}
856
- </Table.Summary.Row>
857
- </>
907
+ <Table.Summary.Row className="report-summary-row">
908
+ {cols.map((col, index) => {
909
+ if (summaryValues[col.field] !== undefined) {
910
+ return (
911
+ <Table.Summary.Cell key={index}>
912
+ <strong style={{ fontWeight: 900 }}>{summaryValues[col.field]}</strong>
913
+ </Table.Summary.Cell>
914
+ );
915
+ }
916
+
917
+ const captionConfig = columns.find((c) => col.field && c.caption_field === col.field);
918
+ if (captionConfig) {
919
+ return (
920
+ <Table.Summary.Cell key={index}>
921
+ <strong style={{ fontWeight: 900 }}>{captionConfig.summary_caption || ''}</strong>
922
+ </Table.Summary.Cell>
923
+ );
924
+ }
925
+
926
+ return <Table.Summary.Cell key={index} />;
927
+ })}
928
+ </Table.Summary.Row>
858
929
  );
859
930
  }}
860
931
  />
@@ -5,6 +5,13 @@
5
5
  justify-content: space-between;
6
6
  }
7
7
 
8
+ .report-summary-row {
9
+ position: sticky;
10
+ bottom: 0;
11
+ background: #fff;
12
+ z-index: 10;
13
+ }
14
+
8
15
  .table-header {
9
16
  display: flex;
10
17
  justify-content: space-between;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.6.18",
3
+ "version": "2.6.20",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"