@visns-studio/visns-components 5.14.9 → 5.14.11
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/README.md +51 -7
- package/package.json +2 -1
- package/src/components/columns/ColumnRenderers.jsx +16 -6
- package/src/components/generic/GenericReport.jsx +1311 -158
- package/src/components/generic/GroupedReportRenderer.jsx +91 -0
- package/src/components/generic/SectionGroupedReport.jsx +388 -0
- package/src/components/generic/groupedReport.css +307 -0
- package/src/components/generic/shared/formatters.js +152 -23
- package/src/components/generic/shared/groupingUtils.js +118 -0
- package/src/components/styles/GenericReport.module.scss +532 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/* Grouped Report Base Styles */
|
|
2
|
+
.grouped-report {
|
|
3
|
+
--group-header-bg-primary: #2c5282;
|
|
4
|
+
--group-header-bg-secondary: #4a5568;
|
|
5
|
+
--group-header-bg-success: #38a169;
|
|
6
|
+
--group-header-bg-warning: #d69e2e;
|
|
7
|
+
--group-header-bg-danger: #e53e3e;
|
|
8
|
+
--group-header-color: white;
|
|
9
|
+
--empty-row-bg-light: #f8f9fa;
|
|
10
|
+
--empty-row-bg-transparent: transparent;
|
|
11
|
+
--border-color: #dee2e6;
|
|
12
|
+
--border-color-light: #e2e8f0;
|
|
13
|
+
--text-muted: #6c757d;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.grouped-report * {
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Report Header */
|
|
21
|
+
.grouped-report-header {
|
|
22
|
+
display: flex;
|
|
23
|
+
justify-content: space-between;
|
|
24
|
+
align-items: center;
|
|
25
|
+
margin-bottom: 1.5rem;
|
|
26
|
+
padding: 1rem;
|
|
27
|
+
background-color: #f8f9fa;
|
|
28
|
+
border-radius: 0.375rem;
|
|
29
|
+
border: 1px solid var(--border-color);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.grouped-report-summary {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: 1rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.summary-text {
|
|
39
|
+
font-weight: 500;
|
|
40
|
+
color: var(--text-muted);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.grouped-report-actions {
|
|
44
|
+
display: flex;
|
|
45
|
+
gap: 0.5rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.btn-export {
|
|
49
|
+
background-color: #0d6efd;
|
|
50
|
+
color: white;
|
|
51
|
+
border: none;
|
|
52
|
+
padding: 0.5rem 1rem;
|
|
53
|
+
border-radius: 0.375rem;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
font-weight: 500;
|
|
56
|
+
transition: background-color 0.15s ease-in-out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.btn-export:hover {
|
|
60
|
+
background-color: #0b5ed7;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Section-based Grouping */
|
|
64
|
+
.section-grouped-report {
|
|
65
|
+
width: 100%;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.grouped-report--sections .group-section {
|
|
69
|
+
margin-bottom: 2rem;
|
|
70
|
+
page-break-inside: avoid;
|
|
71
|
+
border: 1px solid var(--border-color);
|
|
72
|
+
border-radius: 0.5rem;
|
|
73
|
+
overflow: hidden;
|
|
74
|
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Group Headers */
|
|
78
|
+
.group-header {
|
|
79
|
+
padding: 0.75rem 1rem;
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
display: flex;
|
|
82
|
+
justify-content: space-between;
|
|
83
|
+
align-items: center;
|
|
84
|
+
color: var(--group-header-color);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.group-header-content {
|
|
88
|
+
display: flex;
|
|
89
|
+
justify-content: space-between;
|
|
90
|
+
align-items: center;
|
|
91
|
+
width: 100%;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.group-title {
|
|
95
|
+
font-size: 1rem;
|
|
96
|
+
margin: 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.group-stats {
|
|
100
|
+
font-size: 0.875rem;
|
|
101
|
+
font-weight: 400;
|
|
102
|
+
opacity: 0.9;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Group Header Styles */
|
|
106
|
+
.group-section--primary .group-header {
|
|
107
|
+
background-color: var(--group-header-bg-primary);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.group-section--secondary .group-header {
|
|
111
|
+
background-color: var(--group-header-bg-secondary);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.group-section--success .group-header {
|
|
115
|
+
background-color: var(--group-header-bg-success);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.group-section--warning .group-header {
|
|
119
|
+
background-color: var(--group-header-bg-warning);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.group-section--danger .group-header {
|
|
123
|
+
background-color: var(--group-header-bg-danger);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Group Content */
|
|
127
|
+
.group-content {
|
|
128
|
+
background-color: white;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.group-table,
|
|
132
|
+
.grouped-table {
|
|
133
|
+
width: 100%;
|
|
134
|
+
border-collapse: collapse;
|
|
135
|
+
margin: 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.group-table thead th,
|
|
139
|
+
.grouped-table thead th {
|
|
140
|
+
background-color: #f8f9fa;
|
|
141
|
+
color: #495057;
|
|
142
|
+
font-weight: 600;
|
|
143
|
+
padding: 0.75rem;
|
|
144
|
+
text-align: left;
|
|
145
|
+
border-bottom: 2px solid var(--border-color);
|
|
146
|
+
border-right: 1px solid var(--border-color-light);
|
|
147
|
+
font-size: 0.875rem;
|
|
148
|
+
text-transform: uppercase;
|
|
149
|
+
letter-spacing: 0.05em;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.group-table thead th:last-child,
|
|
153
|
+
.grouped-table thead th:last-child {
|
|
154
|
+
border-right: none;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.group-table thead th.required-column,
|
|
158
|
+
.grouped-table thead th.required-column {
|
|
159
|
+
background-color: #e3f2fd;
|
|
160
|
+
font-weight: 700;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.group-table tbody td,
|
|
164
|
+
.grouped-table tbody td {
|
|
165
|
+
padding: 0.75rem;
|
|
166
|
+
border-bottom: 1px solid var(--border-color-light);
|
|
167
|
+
border-right: 1px solid var(--border-color-light);
|
|
168
|
+
vertical-align: top;
|
|
169
|
+
font-size: 0.875rem;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.group-table tbody td:last-child,
|
|
173
|
+
.grouped-table tbody td:last-child {
|
|
174
|
+
border-right: none;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.group-table tbody td.required-column,
|
|
178
|
+
.grouped-table tbody td.required-column {
|
|
179
|
+
font-weight: 500;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Data Rows */
|
|
183
|
+
.data-row {
|
|
184
|
+
background-color: white;
|
|
185
|
+
transition: background-color 0.15s ease-in-out;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.data-row:hover {
|
|
189
|
+
background-color: #f8f9fa;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.data-row:nth-child(even) {
|
|
193
|
+
background-color: #fbfcfd;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.data-row:nth-child(even):hover {
|
|
197
|
+
background-color: #f1f3f4;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Empty Rows */
|
|
201
|
+
.empty-row td {
|
|
202
|
+
height: 2.5rem;
|
|
203
|
+
min-height: 2.5rem;
|
|
204
|
+
border-bottom: 1px solid var(--border-color-light);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.empty-row--light {
|
|
208
|
+
background-color: var(--empty-row-bg-light);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.empty-row--transparent {
|
|
212
|
+
background-color: var(--empty-row-bg-transparent);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.empty-row--striped:nth-child(even) {
|
|
216
|
+
background-color: #f8f9fa;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Empty State */
|
|
220
|
+
.grouped-report-empty {
|
|
221
|
+
text-align: center;
|
|
222
|
+
padding: 3rem;
|
|
223
|
+
color: var(--text-muted);
|
|
224
|
+
background-color: #f8f9fa;
|
|
225
|
+
border: 1px solid var(--border-color);
|
|
226
|
+
border-radius: 0.5rem;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.grouped-report-error {
|
|
230
|
+
text-align: center;
|
|
231
|
+
padding: 2rem;
|
|
232
|
+
color: #dc3545;
|
|
233
|
+
background-color: #f8d7da;
|
|
234
|
+
border: 1px solid #f5c6cb;
|
|
235
|
+
border-radius: 0.5rem;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Responsive Design */
|
|
239
|
+
@media (max-width: 768px) {
|
|
240
|
+
.grouped-report-header {
|
|
241
|
+
flex-direction: column;
|
|
242
|
+
gap: 1rem;
|
|
243
|
+
align-items: stretch;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.group-table,
|
|
247
|
+
.grouped-table {
|
|
248
|
+
font-size: 0.75rem;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.group-table thead th,
|
|
252
|
+
.group-table tbody td,
|
|
253
|
+
.grouped-table thead th,
|
|
254
|
+
.grouped-table tbody td {
|
|
255
|
+
padding: 0.5rem;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.group-header {
|
|
259
|
+
padding: 0.5rem;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.group-title {
|
|
263
|
+
font-size: 0.875rem;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.group-stats {
|
|
267
|
+
font-size: 0.75rem;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* Print Styles */
|
|
272
|
+
@media print {
|
|
273
|
+
.grouped-report-actions {
|
|
274
|
+
display: none;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.group-section {
|
|
278
|
+
page-break-inside: avoid;
|
|
279
|
+
margin-bottom: 1rem;
|
|
280
|
+
border: 1px solid #000;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.group-header {
|
|
284
|
+
background-color: #000 !important;
|
|
285
|
+
color: white !important;
|
|
286
|
+
-webkit-print-color-adjust: exact;
|
|
287
|
+
print-color-adjust: exact;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.group-table,
|
|
291
|
+
.grouped-table {
|
|
292
|
+
border-collapse: collapse;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.group-table thead th,
|
|
296
|
+
.group-table tbody td,
|
|
297
|
+
.grouped-table thead th,
|
|
298
|
+
.grouped-table tbody td {
|
|
299
|
+
border: 1px solid #000;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.empty-row--light {
|
|
303
|
+
background-color: #f0f0f0 !important;
|
|
304
|
+
-webkit-print-color-adjust: exact;
|
|
305
|
+
print-color-adjust: exact;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -2,6 +2,132 @@ import parse from 'html-react-parser';
|
|
|
2
2
|
import moment from 'moment';
|
|
3
3
|
import numeral from 'numeral';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Smart boolean/binary value interpreter based on column name and context
|
|
7
|
+
* @param {string|number} value - The value (0/1, true/false, etc.)
|
|
8
|
+
* @param {Object} column - Column configuration with id/name
|
|
9
|
+
* @returns {string} Human-friendly representation
|
|
10
|
+
*/
|
|
11
|
+
const interpretBooleanValue = (value, column) => {
|
|
12
|
+
const columnName = (column.id || column.name || '').toLowerCase();
|
|
13
|
+
const isTrue = value === 1 || value === true || value === 'true' || value === 'yes' || value === 'Yes';
|
|
14
|
+
const isFalse = value === 0 || value === false || value === 'false' || value === 'no' || value === 'No';
|
|
15
|
+
|
|
16
|
+
if (!isTrue && !isFalse) return value; // Return original if not clearly boolean
|
|
17
|
+
|
|
18
|
+
// Contextual interpretations based on column name patterns
|
|
19
|
+
const interpretations = {
|
|
20
|
+
// Status/State patterns
|
|
21
|
+
'active': { true: 'Active', false: 'Inactive' },
|
|
22
|
+
'enabled': { true: 'Enabled', false: 'Disabled' },
|
|
23
|
+
'status': { true: 'Active', false: 'Inactive' },
|
|
24
|
+
'visible': { true: 'Visible', false: 'Hidden' },
|
|
25
|
+
'published': { true: 'Published', false: 'Draft' },
|
|
26
|
+
'public': { true: 'Public', false: 'Private' },
|
|
27
|
+
'live': { true: 'Live', false: 'Offline' },
|
|
28
|
+
|
|
29
|
+
// Completion/Progress patterns
|
|
30
|
+
'completed': { true: 'Completed', false: 'Pending' },
|
|
31
|
+
'finished': { true: 'Finished', false: 'In Progress' },
|
|
32
|
+
'done': { true: 'Done', false: 'To Do' },
|
|
33
|
+
'complete': { true: 'Complete', false: 'Incomplete' },
|
|
34
|
+
'resolved': { true: 'Resolved', false: 'Open' },
|
|
35
|
+
'closed': { true: 'Closed', false: 'Open' },
|
|
36
|
+
|
|
37
|
+
// Approval/Verification patterns
|
|
38
|
+
'approved': { true: 'Approved', false: 'Pending' },
|
|
39
|
+
'verified': { true: 'Verified', false: 'Unverified' },
|
|
40
|
+
'confirmed': { true: 'Confirmed', false: 'Unconfirmed' },
|
|
41
|
+
'accepted': { true: 'Accepted', false: 'Rejected' },
|
|
42
|
+
'valid': { true: 'Valid', false: 'Invalid' },
|
|
43
|
+
|
|
44
|
+
// Access/Permission patterns
|
|
45
|
+
'allowed': { true: 'Allowed', false: 'Denied' },
|
|
46
|
+
'permitted': { true: 'Permitted', false: 'Restricted' },
|
|
47
|
+
'authorized': { true: 'Authorized', false: 'Unauthorized' },
|
|
48
|
+
'locked': { true: 'Locked', false: 'Unlocked' },
|
|
49
|
+
|
|
50
|
+
// Notification/Communication patterns
|
|
51
|
+
'sent': { true: 'Sent', false: 'Not Sent' },
|
|
52
|
+
'delivered': { true: 'Delivered', false: 'Pending' },
|
|
53
|
+
'read': { true: 'Read', false: 'Unread' },
|
|
54
|
+
'notified': { true: 'Notified', false: 'Not Notified' },
|
|
55
|
+
|
|
56
|
+
// Business/Financial patterns
|
|
57
|
+
'paid': { true: 'Paid', false: 'Unpaid' },
|
|
58
|
+
'invoiced': { true: 'Invoiced', false: 'Not Invoiced' },
|
|
59
|
+
'billed': { true: 'Billed', false: 'Unbilled' },
|
|
60
|
+
'taxable': { true: 'Taxable', false: 'Tax Free' },
|
|
61
|
+
|
|
62
|
+
// Quality/Condition patterns
|
|
63
|
+
'available': { true: 'Available', false: 'Unavailable' },
|
|
64
|
+
'ready': { true: 'Ready', false: 'Not Ready' },
|
|
65
|
+
'urgent': { true: 'Urgent', false: 'Normal' },
|
|
66
|
+
'priority': { true: 'High Priority', false: 'Normal Priority' },
|
|
67
|
+
'critical': { true: 'Critical', false: 'Normal' },
|
|
68
|
+
|
|
69
|
+
// Agreement/Contract patterns
|
|
70
|
+
'signed': { true: 'Signed', false: 'Unsigned' },
|
|
71
|
+
'executed': { true: 'Executed', false: 'Draft' },
|
|
72
|
+
'expired': { true: 'Expired', false: 'Current' },
|
|
73
|
+
|
|
74
|
+
// Project/Work patterns
|
|
75
|
+
'assigned': { true: 'Assigned', false: 'Unassigned' },
|
|
76
|
+
'allocated': { true: 'Allocated', false: 'Available' },
|
|
77
|
+
'booked': { true: 'Booked', false: 'Available' },
|
|
78
|
+
'scheduled': { true: 'Scheduled', false: 'Unscheduled' },
|
|
79
|
+
'stage': { true: 'Completed', false: 'Pending' },
|
|
80
|
+
'final': { true: 'Completed', false: 'Incomplete' },
|
|
81
|
+
'finished': { true: 'Finished', false: 'In Progress' },
|
|
82
|
+
'done': { true: 'Done', false: 'To Do' },
|
|
83
|
+
|
|
84
|
+
// System/Technical patterns
|
|
85
|
+
'online': { true: 'Online', false: 'Offline' },
|
|
86
|
+
'connected': { true: 'Connected', false: 'Disconnected' },
|
|
87
|
+
'synced': { true: 'Synced', false: 'Not Synced' },
|
|
88
|
+
'backup': { true: 'Backed Up', false: 'Not Backed Up' },
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Try to find a match based on column name
|
|
92
|
+
for (const [pattern, values] of Object.entries(interpretations)) {
|
|
93
|
+
if (columnName.includes(pattern)) {
|
|
94
|
+
return isTrue ? values.true : values.false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Special handling for specific common patterns
|
|
99
|
+
if (columnName.includes('is_') || columnName.startsWith('has_') || columnName.endsWith('_flag')) {
|
|
100
|
+
// Extract the meaningful part
|
|
101
|
+
const meaningfulPart = columnName
|
|
102
|
+
.replace(/^(is_|has_)/, '')
|
|
103
|
+
.replace(/_flag$/, '')
|
|
104
|
+
.replace(/_/g, ' ')
|
|
105
|
+
.replace(/\b\w/g, l => l.toUpperCase()); // Title case
|
|
106
|
+
|
|
107
|
+
return isTrue ? meaningfulPart : `Not ${meaningfulPart}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Default fallback - still better than raw 0/1
|
|
111
|
+
return isTrue ? 'Yes' : 'No';
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Auto-detect appropriate date format based on value content
|
|
116
|
+
* @param {*} value - The date value to analyze
|
|
117
|
+
* @param {string} fallbackFormat - Default format if detection fails
|
|
118
|
+
* @returns {string} Appropriate moment.js format string
|
|
119
|
+
*/
|
|
120
|
+
export const autoDetectDateFormat = (value, fallbackFormat = 'DD-MM-YYYY') => {
|
|
121
|
+
if (!value || !moment(value).isValid()) {
|
|
122
|
+
return fallbackFormat;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const momentValue = moment(value);
|
|
126
|
+
const hasTime = momentValue.format('HH:mm:ss') !== '00:00:00';
|
|
127
|
+
|
|
128
|
+
return hasTime ? 'DD-MM-YYYY HH:mm' : 'DD-MM-YYYY';
|
|
129
|
+
};
|
|
130
|
+
|
|
5
131
|
/**
|
|
6
132
|
* Format quarter columns with status, contacts, and reasons in tooltips
|
|
7
133
|
* @param {*} value - The date value
|
|
@@ -29,7 +155,7 @@ const formatQuarterWithStatus = (value, column, row) => {
|
|
|
29
155
|
// If status is 'sent' and we have a date
|
|
30
156
|
if (status === 'sent' && value) {
|
|
31
157
|
const formattedDate = moment(value).isValid()
|
|
32
|
-
? moment(value).format('DD
|
|
158
|
+
? moment(value).format('DD-MM-YYYY')
|
|
33
159
|
: value;
|
|
34
160
|
|
|
35
161
|
const contactCount = Array.isArray(contacts) ? contacts.length : 0;
|
|
@@ -71,7 +197,7 @@ const formatQuarterWithStatus = (value, column, row) => {
|
|
|
71
197
|
// If we have a date but no clear status (legacy data)
|
|
72
198
|
if (value) {
|
|
73
199
|
const formattedDate = moment(value).isValid()
|
|
74
|
-
? moment(value).format('DD
|
|
200
|
+
? moment(value).format('DD-MM-YYYY')
|
|
75
201
|
: value;
|
|
76
202
|
|
|
77
203
|
const contactCount = Array.isArray(contacts) ? contacts.length : 0;
|
|
@@ -125,11 +251,11 @@ export const formatCellContent = (value, column, row = null) => {
|
|
|
125
251
|
switch (valueType) {
|
|
126
252
|
case 'date':
|
|
127
253
|
return moment(value).isValid()
|
|
128
|
-
? moment(value).format(column.format || 'DD
|
|
254
|
+
? moment(value).format(column.format || autoDetectDateFormat(value, 'DD-MM-YYYY'))
|
|
129
255
|
: value;
|
|
130
256
|
case 'datetime':
|
|
131
257
|
return moment(value).isValid()
|
|
132
|
-
? moment(value).format(column.format || 'DD
|
|
258
|
+
? moment(value).format(column.format || autoDetectDateFormat(value, 'DD-MM-YYYY HH:mm'))
|
|
133
259
|
: value;
|
|
134
260
|
case 'currency':
|
|
135
261
|
return numeral(value).format(column.format || '$0,0.00');
|
|
@@ -139,25 +265,7 @@ export const formatCellContent = (value, column, row = null) => {
|
|
|
139
265
|
? numeral(value).format(column.format || '0,0')
|
|
140
266
|
: value;
|
|
141
267
|
case 'boolean':
|
|
142
|
-
|
|
143
|
-
value === 1 ||
|
|
144
|
-
value === true ||
|
|
145
|
-
value === 'true' ||
|
|
146
|
-
value === 'yes' ||
|
|
147
|
-
value === 'Yes'
|
|
148
|
-
) {
|
|
149
|
-
return 'Yes';
|
|
150
|
-
}
|
|
151
|
-
if (
|
|
152
|
-
value === 0 ||
|
|
153
|
-
value === false ||
|
|
154
|
-
value === 'false' ||
|
|
155
|
-
value === 'no' ||
|
|
156
|
-
value === 'No'
|
|
157
|
-
) {
|
|
158
|
-
return 'No';
|
|
159
|
-
}
|
|
160
|
-
return value;
|
|
268
|
+
return interpretBooleanValue(value, column);
|
|
161
269
|
case 'year':
|
|
162
270
|
// Special handling for year values
|
|
163
271
|
return value.toString();
|
|
@@ -174,6 +282,27 @@ export const formatCellContent = (value, column, row = null) => {
|
|
|
174
282
|
// Parse rich text content
|
|
175
283
|
return parse(String(value));
|
|
176
284
|
default:
|
|
285
|
+
// Check if this might be a boolean value based on its content
|
|
286
|
+
if ((value === 0 || value === 1) && column.id) {
|
|
287
|
+
// Only apply smart boolean interpretation if the column name suggests it's boolean
|
|
288
|
+
const columnName = (column.id || column.name || '').toLowerCase();
|
|
289
|
+
const booleanIndicators = [
|
|
290
|
+
'is_', 'has_', 'can_', 'should_', 'will_', 'did_',
|
|
291
|
+
'active', 'enabled', 'completed', 'confirmed', 'approved', 'verified',
|
|
292
|
+
'sent', 'delivered', 'paid', 'signed', 'expired', 'urgent', 'critical',
|
|
293
|
+
'public', 'visible', 'available', 'ready', 'locked', 'online',
|
|
294
|
+
'_flag', '_status', '_state'
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
const seemsBooleanish = booleanIndicators.some(indicator =>
|
|
298
|
+
columnName.includes(indicator)
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
if (seemsBooleanish) {
|
|
302
|
+
return interpretBooleanValue(value, column);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
177
306
|
// For text values, check if it contains HTML tags
|
|
178
307
|
if (
|
|
179
308
|
typeof value === 'string' &&
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export const getNestedValue = (obj, path) => {
|
|
2
|
+
if (typeof path === 'string') {
|
|
3
|
+
path = path.split('.');
|
|
4
|
+
}
|
|
5
|
+
return path.reduce((current, key) => current?.[key], obj) || '';
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const formatCellValue = (value, column) => {
|
|
9
|
+
if (value === null || value === undefined || value === '') {
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
switch (column.type) {
|
|
14
|
+
case 'date':
|
|
15
|
+
return new Date(value).toLocaleDateString();
|
|
16
|
+
case 'datetime':
|
|
17
|
+
return new Date(value).toLocaleString();
|
|
18
|
+
case 'decimal':
|
|
19
|
+
return parseFloat(value).toFixed(2);
|
|
20
|
+
case 'integer':
|
|
21
|
+
return parseInt(value).toLocaleString();
|
|
22
|
+
case 'currency':
|
|
23
|
+
return new Intl.NumberFormat('en-AU', {
|
|
24
|
+
style: 'currency',
|
|
25
|
+
currency: 'AUD'
|
|
26
|
+
}).format(value);
|
|
27
|
+
default:
|
|
28
|
+
return value.toString();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const sortGroups = (groups, sortDirection = 'asc', customOrder = null) => {
|
|
33
|
+
if (customOrder) {
|
|
34
|
+
return groups.sort((a, b) => {
|
|
35
|
+
const aIndex = customOrder.indexOf(a.groupName);
|
|
36
|
+
const bIndex = customOrder.indexOf(b.groupName);
|
|
37
|
+
|
|
38
|
+
// If both found in custom order, sort by position
|
|
39
|
+
if (aIndex !== -1 && bIndex !== -1) {
|
|
40
|
+
return aIndex - bIndex;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// If only one found, prioritize it
|
|
44
|
+
if (aIndex !== -1) return -1;
|
|
45
|
+
if (bIndex !== -1) return 1;
|
|
46
|
+
|
|
47
|
+
// If neither found, sort alphabetically
|
|
48
|
+
return sortDirection === 'asc'
|
|
49
|
+
? a.groupName.localeCompare(b.groupName)
|
|
50
|
+
: b.groupName.localeCompare(a.groupName);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return groups.sort((a, b) => {
|
|
55
|
+
return sortDirection === 'asc'
|
|
56
|
+
? a.groupName.localeCompare(b.groupName)
|
|
57
|
+
: b.groupName.localeCompare(a.groupName);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const processGroupData = (data, groupingConfig) => {
|
|
62
|
+
if (!data || !Array.isArray(data) || !groupingConfig?.enabled) {
|
|
63
|
+
return {
|
|
64
|
+
grouped: false,
|
|
65
|
+
data: data || []
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { groupByField, rowsPerGroup = 10, showEmptyRows = true } = groupingConfig;
|
|
70
|
+
|
|
71
|
+
// Group data by the specified field
|
|
72
|
+
const grouped = data.reduce((acc, row) => {
|
|
73
|
+
const groupValue = getNestedValue(row, groupByField) || 'Unassigned';
|
|
74
|
+
if (!acc[groupValue]) {
|
|
75
|
+
acc[groupValue] = [];
|
|
76
|
+
}
|
|
77
|
+
acc[groupValue].push(row);
|
|
78
|
+
return acc;
|
|
79
|
+
}, {});
|
|
80
|
+
|
|
81
|
+
// Process each group
|
|
82
|
+
const processedGroups = Object.keys(grouped).map(groupName => {
|
|
83
|
+
const rows = grouped[groupName];
|
|
84
|
+
const emptyRows = [];
|
|
85
|
+
|
|
86
|
+
if (showEmptyRows && rows.length < rowsPerGroup) {
|
|
87
|
+
const emptyRowsCount = rowsPerGroup - rows.length;
|
|
88
|
+
for (let i = 0; i < emptyRowsCount; i++) {
|
|
89
|
+
emptyRows.push({ __empty_row__: true, id: `empty_${groupName}_${i}` });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
groupName,
|
|
95
|
+
groupDisplayName: groupName,
|
|
96
|
+
rows,
|
|
97
|
+
emptyRows,
|
|
98
|
+
totalRows: rows.length,
|
|
99
|
+
maxRows: rowsPerGroup,
|
|
100
|
+
hasData: rows.length > 0
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Sort groups
|
|
105
|
+
const sortedGroups = sortGroups(
|
|
106
|
+
processedGroups,
|
|
107
|
+
groupingConfig.sortDirection,
|
|
108
|
+
groupingConfig.customGroupOrder
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
grouped: true,
|
|
113
|
+
groupingConfig,
|
|
114
|
+
groups: sortedGroups,
|
|
115
|
+
totalGroups: sortedGroups.length,
|
|
116
|
+
totalRecords: data.length
|
|
117
|
+
};
|
|
118
|
+
};
|