@visns-studio/visns-components 5.1.22 → 5.1.24
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/package.json
CHANGED
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
78
78
|
},
|
|
79
79
|
"name": "@visns-studio/visns-components",
|
|
80
|
-
"version": "5.1.
|
|
80
|
+
"version": "5.1.24",
|
|
81
81
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
82
82
|
"main": "src/index.js",
|
|
83
83
|
"files": [
|
|
@@ -15,7 +15,6 @@ import styles from './styles/GenericEditableTable.module.scss';
|
|
|
15
15
|
// Updated ColourPicker with an expanded pastel palette and a refined Akar icon
|
|
16
16
|
const ColourPicker = ({ value, columnId, keyCounter, onChange }) => {
|
|
17
17
|
const [colorPickerShow, setColorPickerShow] = useState(false);
|
|
18
|
-
const colorPickerRef = useRef(null);
|
|
19
18
|
const buttonRef = useRef(null);
|
|
20
19
|
const popoverRef = useRef(null);
|
|
21
20
|
|
|
@@ -54,20 +53,23 @@ const ColourPicker = ({ value, columnId, keyCounter, onChange }) => {
|
|
|
54
53
|
? buttonRef.current.getBoundingClientRect()
|
|
55
54
|
: {};
|
|
56
55
|
|
|
56
|
+
const handleClear = () => {
|
|
57
|
+
// Passing an empty string to represent "no colour selected"
|
|
58
|
+
onChange('', columnId, keyCounter);
|
|
59
|
+
setColorPickerShow(false);
|
|
60
|
+
};
|
|
61
|
+
|
|
57
62
|
return (
|
|
58
63
|
<div className={styles.cpicker}>
|
|
59
|
-
<div className={styles.cpicker__btn}
|
|
64
|
+
<div className={styles.cpicker__btn}>
|
|
60
65
|
<button
|
|
61
66
|
ref={buttonRef}
|
|
62
|
-
style={
|
|
63
|
-
value && value !== 'null' ? { background: value } : {}
|
|
64
|
-
}
|
|
67
|
+
style={value ? { background: value } : {}}
|
|
65
68
|
onClick={(e) => {
|
|
66
69
|
e.preventDefault();
|
|
67
70
|
setColorPickerShow(!colorPickerShow);
|
|
68
71
|
}}
|
|
69
72
|
>
|
|
70
|
-
{/* Using the Akar Water icon with increased size and some margin */}
|
|
71
73
|
<Water size={20} style={{ color: 'black' }} />
|
|
72
74
|
</button>
|
|
73
75
|
|
|
@@ -87,14 +89,29 @@ const ColourPicker = ({ value, columnId, keyCounter, onChange }) => {
|
|
|
87
89
|
className={styles.colour_cover}
|
|
88
90
|
onClick={() => setColorPickerShow(false)}
|
|
89
91
|
/>
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
<div className={styles.pickerContainer}>
|
|
93
|
+
<CompactPicker
|
|
94
|
+
color={value || ''}
|
|
95
|
+
colors={pastelColors}
|
|
96
|
+
onChangeComplete={(color) => {
|
|
97
|
+
onChange(
|
|
98
|
+
color.hex,
|
|
99
|
+
columnId,
|
|
100
|
+
keyCounter
|
|
101
|
+
);
|
|
102
|
+
setColorPickerShow(false);
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
{/* Extra clear button added below the CompactPicker */}
|
|
106
|
+
<div className={styles.clearContainer}>
|
|
107
|
+
<button
|
|
108
|
+
onClick={handleClear}
|
|
109
|
+
className={styles.clearButton}
|
|
110
|
+
>
|
|
111
|
+
Clear
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
98
115
|
</div>,
|
|
99
116
|
document.body
|
|
100
117
|
)}
|
|
@@ -116,6 +133,9 @@ const GenericEditableTable = ({
|
|
|
116
133
|
|
|
117
134
|
const [localData, setLocalData] = useState([]);
|
|
118
135
|
|
|
136
|
+
// Determine if a total column exists and its index.
|
|
137
|
+
const totalColumnIndex = columns.findIndex((col) => col.type === 'total');
|
|
138
|
+
|
|
119
139
|
// Helper to sort by sort_order without mutating the original array
|
|
120
140
|
const sortBySortOrder = (dataArray) =>
|
|
121
141
|
[...dataArray].sort((a, b) => a.sort_order - b.sort_order);
|
|
@@ -128,6 +148,46 @@ const GenericEditableTable = ({
|
|
|
128
148
|
: undefined;
|
|
129
149
|
};
|
|
130
150
|
|
|
151
|
+
// New helper to calculate total using the updated keys structure
|
|
152
|
+
const calculateTotal = (entry, keys) => {
|
|
153
|
+
let total = 0;
|
|
154
|
+
if (Array.isArray(keys)) {
|
|
155
|
+
// Legacy format: simply sum up the values.
|
|
156
|
+
total = keys.reduce(
|
|
157
|
+
(sum, key) => sum + (parseFloat(entry[key]) || 0),
|
|
158
|
+
0
|
|
159
|
+
);
|
|
160
|
+
} else if (
|
|
161
|
+
keys &&
|
|
162
|
+
typeof keys === 'object' &&
|
|
163
|
+
keys.price &&
|
|
164
|
+
keys.unit
|
|
165
|
+
) {
|
|
166
|
+
const priceKeys = keys.price;
|
|
167
|
+
const unitKeys = keys.unit;
|
|
168
|
+
// If there's one unit key, use its value for all price keys.
|
|
169
|
+
if (unitKeys.length === 1) {
|
|
170
|
+
const unitValue = parseFloat(entry[unitKeys[0]]) || 0;
|
|
171
|
+
total = priceKeys.reduce(
|
|
172
|
+
(sum, key) =>
|
|
173
|
+
sum + (parseFloat(entry[key]) || 0) * unitValue,
|
|
174
|
+
0
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
// Otherwise, if both arrays have the same length, multiply pair‑wise.
|
|
178
|
+
else if (priceKeys.length === unitKeys.length) {
|
|
179
|
+
total = priceKeys.reduce(
|
|
180
|
+
(sum, key, index) =>
|
|
181
|
+
sum +
|
|
182
|
+
(parseFloat(entry[key]) || 0) *
|
|
183
|
+
(parseFloat(entry[unitKeys[index]]) || 0),
|
|
184
|
+
0
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return total;
|
|
189
|
+
};
|
|
190
|
+
|
|
131
191
|
// On load, enrich the data and sort by sort_order
|
|
132
192
|
useEffect(() => {
|
|
133
193
|
const enrichedData = data[rows.key]?.map((entry, index) => ({
|
|
@@ -441,10 +501,7 @@ const GenericEditableTable = ({
|
|
|
441
501
|
);
|
|
442
502
|
}
|
|
443
503
|
case 'total': {
|
|
444
|
-
const subtotal = column.keys
|
|
445
|
-
(sum, key) => sum + (parseFloat(entry[key]) || 0),
|
|
446
|
-
0
|
|
447
|
-
);
|
|
504
|
+
const subtotal = calculateTotal(entry, column.keys);
|
|
448
505
|
return new Intl.NumberFormat('en-AU', {
|
|
449
506
|
style: 'currency',
|
|
450
507
|
currency: 'AUD',
|
|
@@ -595,6 +652,54 @@ const GenericEditableTable = ({
|
|
|
595
652
|
</td>
|
|
596
653
|
</tr>
|
|
597
654
|
))}
|
|
655
|
+
{/* Render subtotal row for group only if a total column exists */}
|
|
656
|
+
{totalColumnIndex !== -1 && (
|
|
657
|
+
<tr className={styles.subtotalRow}>
|
|
658
|
+
{columns.map((col, index) => {
|
|
659
|
+
if (
|
|
660
|
+
index ===
|
|
661
|
+
totalColumnIndex - 1 &&
|
|
662
|
+
totalColumnIndex > 0
|
|
663
|
+
) {
|
|
664
|
+
return (
|
|
665
|
+
<td key={col.id}>
|
|
666
|
+
Subtotal
|
|
667
|
+
</td>
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
if (
|
|
671
|
+
index === totalColumnIndex
|
|
672
|
+
) {
|
|
673
|
+
const groupTotal =
|
|
674
|
+
group.entries.reduce(
|
|
675
|
+
(sum, entry) =>
|
|
676
|
+
sum +
|
|
677
|
+
calculateTotal(
|
|
678
|
+
entry,
|
|
679
|
+
columns[
|
|
680
|
+
totalColumnIndex
|
|
681
|
+
].keys
|
|
682
|
+
),
|
|
683
|
+
0
|
|
684
|
+
);
|
|
685
|
+
return (
|
|
686
|
+
<td key={col.id}>
|
|
687
|
+
{groupTotal.toLocaleString(
|
|
688
|
+
'en-AU',
|
|
689
|
+
{
|
|
690
|
+
style: 'currency',
|
|
691
|
+
currency:
|
|
692
|
+
'AUD',
|
|
693
|
+
}
|
|
694
|
+
)}
|
|
695
|
+
</td>
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
return <td key={col.id} />;
|
|
699
|
+
})}
|
|
700
|
+
<td></td>
|
|
701
|
+
</tr>
|
|
702
|
+
)}
|
|
598
703
|
</React.Fragment>
|
|
599
704
|
))}
|
|
600
705
|
</tbody>
|
|
@@ -676,38 +781,46 @@ const GenericEditableTable = ({
|
|
|
676
781
|
</td>
|
|
677
782
|
</tr>
|
|
678
783
|
))}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
784
|
+
{/* Render overall subtotal row only if a total column exists */}
|
|
785
|
+
{totalColumnIndex !== -1 && (
|
|
786
|
+
<tr className={styles.subtotalRow}>
|
|
787
|
+
{columns.map((col, index) => {
|
|
788
|
+
if (
|
|
789
|
+
index === totalColumnIndex - 1 &&
|
|
790
|
+
totalColumnIndex > 0
|
|
791
|
+
) {
|
|
792
|
+
return <td key={col.id}>Subtotal</td>;
|
|
793
|
+
}
|
|
794
|
+
if (index === totalColumnIndex) {
|
|
795
|
+
const overallTotal = sortBySortOrder(
|
|
796
|
+
localData
|
|
797
|
+
).reduce(
|
|
798
|
+
(sum, entry) =>
|
|
799
|
+
sum +
|
|
800
|
+
calculateTotal(
|
|
801
|
+
entry,
|
|
802
|
+
columns[totalColumnIndex]
|
|
803
|
+
.keys
|
|
804
|
+
),
|
|
805
|
+
0
|
|
806
|
+
);
|
|
807
|
+
return (
|
|
808
|
+
<td key={col.id}>
|
|
809
|
+
{overallTotal.toLocaleString(
|
|
810
|
+
'en-AU',
|
|
811
|
+
{
|
|
812
|
+
style: 'currency',
|
|
813
|
+
currency: 'AUD',
|
|
814
|
+
}
|
|
815
|
+
)}
|
|
816
|
+
</td>
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
return <td key={col.id} />;
|
|
820
|
+
})}
|
|
821
|
+
<td></td>
|
|
822
|
+
</tr>
|
|
823
|
+
)}
|
|
711
824
|
</tbody>
|
|
712
825
|
</table>
|
|
713
826
|
) : (
|
|
@@ -223,40 +223,79 @@
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
.cpicker {
|
|
226
|
-
|
|
226
|
+
position: relative;
|
|
227
227
|
display: flex;
|
|
228
|
-
flex-wrap: nowrap;
|
|
229
228
|
align-items: center;
|
|
230
229
|
|
|
231
230
|
&__btn {
|
|
232
|
-
flex: 1;
|
|
233
|
-
|
|
234
231
|
button {
|
|
235
|
-
width:
|
|
236
|
-
height:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
232
|
+
width: 28px;
|
|
233
|
+
height: 28px;
|
|
234
|
+
background-color: #f0f0f0;
|
|
235
|
+
border: 1px solid #ccc;
|
|
236
|
+
border-radius: 6px;
|
|
237
|
+
display: flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
justify-content: center;
|
|
243
240
|
cursor: pointer;
|
|
244
|
-
transition:
|
|
241
|
+
transition: background 0.2s;
|
|
245
242
|
|
|
246
243
|
&:hover {
|
|
247
|
-
background-color: #
|
|
248
|
-
border-color: #666;
|
|
244
|
+
background-color: #e0e0e0;
|
|
249
245
|
}
|
|
250
246
|
|
|
251
247
|
&:focus {
|
|
252
248
|
outline: none;
|
|
253
|
-
box-shadow: 0 0
|
|
249
|
+
box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
|
|
254
250
|
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
255
254
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
255
|
+
.colour_popover {
|
|
256
|
+
position: absolute;
|
|
257
|
+
background: white;
|
|
258
|
+
border-radius: 8px;
|
|
259
|
+
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15);
|
|
260
|
+
padding: 10px;
|
|
261
|
+
min-width: 200px;
|
|
262
|
+
display: flex;
|
|
263
|
+
flex-direction: column;
|
|
264
|
+
align-items: center;
|
|
265
|
+
z-index: 1000;
|
|
266
|
+
|
|
267
|
+
.clearContainer {
|
|
268
|
+
width: 100%;
|
|
269
|
+
display: flex;
|
|
270
|
+
justify-content: center;
|
|
271
|
+
margin-top: 8px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.clearButton {
|
|
275
|
+
width: 100%; /* Make button span the entire width */
|
|
276
|
+
background: rgba(
|
|
277
|
+
var(--primary-rgb),
|
|
278
|
+
0.1
|
|
279
|
+
); /* Soft transparent background */
|
|
280
|
+
color: var(--primary-color);
|
|
281
|
+
border: none;
|
|
282
|
+
border-top: 1px solid rgba(var(--primary-rgb), 0.2); /* Subtle separator */
|
|
283
|
+
border-radius: 0 0 8px 8px; /* Match colour picker's bottom corners */
|
|
284
|
+
font-size: 13px;
|
|
285
|
+
padding: 8px;
|
|
286
|
+
cursor: pointer;
|
|
287
|
+
transition: background 0.2s, border 0.2s, box-shadow 0.2s;
|
|
288
|
+
font-weight: 500;
|
|
289
|
+
text-align: center;
|
|
290
|
+
box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.05);
|
|
291
|
+
|
|
292
|
+
&:hover {
|
|
293
|
+
background: rgba(var(--primary-rgb), 0.15);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
&:active {
|
|
297
|
+
background: rgba(var(--primary-rgb), 0.25);
|
|
298
|
+
box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.15);
|
|
260
299
|
}
|
|
261
300
|
}
|
|
262
301
|
}
|