@visns-studio/visns-components 5.11.22 → 5.12.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/README.md CHANGED
@@ -625,6 +625,197 @@ You can add summary calculations to grouped data:
625
625
  />
626
626
  ```
627
627
 
628
+ ### Group-Level Bulk Edit Operations (Enhanced v5.13.0+)
629
+
630
+ The DataGrid component now supports powerful bulk editing capabilities through group-based operations, allowing users to efficiently manage multiple items simultaneously.
631
+
632
+ #### How Group Bulk Edit Works
633
+
634
+ When data is grouped using the `groupBy` or `defaultGroupBy` properties, each group header can display action icons for common operations. The new bulk edit functionality adds an "edit" icon that opens a form modal for updating all items in the group simultaneously.
635
+
636
+ #### Configuration Structure
637
+
638
+ Group operations are configured through the `ajaxSetting.groupBySetting.icons` array:
639
+
640
+ ```jsx
641
+ <DataGrid
642
+ ajaxSetting={{
643
+ url: '/api/data',
644
+ groupBy: ['category'],
645
+ groupBySetting: {
646
+ icons: [
647
+ {
648
+ id: 'edit',
649
+ label: 'Edit Group',
650
+ formModal: {
651
+ title: 'Bulk Edit Items',
652
+ url: '/ajax/data/bulkFormUpdate',
653
+ method: 'POST',
654
+ groupKey: 'category',
655
+ fields: ['assigned_user_id', 'status'],
656
+ data: {
657
+ stage: 'processing'
658
+ }
659
+ },
660
+ show: [
661
+ {
662
+ id: 'status',
663
+ value: 'pending'
664
+ }
665
+ ]
666
+ }
667
+ ]
668
+ }
669
+ }}
670
+ // Other props...
671
+ />
672
+ ```
673
+
674
+ #### Configuration Properties
675
+
676
+ **Icon Configuration:**
677
+ - `id`: Must be set to `'edit'` to enable bulk edit functionality
678
+ - `label`: Display text for the action button
679
+ - `formModal`: Configuration object for the bulk edit form
680
+ - `show`: Optional conditions to control when the icon appears
681
+
682
+ **Form Modal Configuration:**
683
+ - `title`: Title displayed in the bulk edit modal
684
+ - `url`: API endpoint for bulk update requests
685
+ - `method`: HTTP method (typically 'POST')
686
+ - `groupKey`: Field name used for grouping (matches the groupBy value)
687
+ - `fields`: Array of field IDs that can be bulk edited
688
+ - `data`: Additional data to send with the bulk update request
689
+
690
+ #### Form Integration
691
+
692
+ The bulk edit feature integrates seamlessly with the existing Form component. When the edit icon is clicked:
693
+
694
+ 1. A form modal opens with the specified title
695
+ 2. Only the fields listed in the `fields` array are displayed
696
+ 3. The form shows an indication that this is a bulk operation
697
+ 4. On submission, all items in the group are updated simultaneously
698
+
699
+ #### Backend Integration
700
+
701
+ The bulk edit functionality expects a specific API endpoint structure:
702
+
703
+ ```javascript
704
+ // POST /ajax/data/bulkFormUpdate
705
+ {
706
+ "bulkEdit": true,
707
+ "groupValue": "Category Name",
708
+ "groupKey": "category",
709
+ "rowIds": [1, 2, 3, 4],
710
+ "assigned_user_id": 5,
711
+ "status": "in_progress",
712
+ // Additional form fields...
713
+ }
714
+
715
+ // Expected Response:
716
+ {
717
+ "error": "",
718
+ "message": "Bulk update completed successfully",
719
+ "data": {
720
+ "updated_count": 4,
721
+ "group_value": "Category Name",
722
+ "updated_items": [...]
723
+ }
724
+ }
725
+ ```
726
+
727
+ #### Features and Benefits
728
+
729
+ **User Experience:**
730
+ - **No Confirmation Dialog**: Edit actions open the form immediately (unlike timer operations)
731
+ - **No Toast Notifications**: Silent operation that focuses on the form interaction
732
+ - **Visual Group Context**: Modal title includes group information and item count
733
+ - **Field Filtering**: Only relevant fields are shown for bulk editing
734
+
735
+ **Technical Features:**
736
+ - **Transaction Safety**: All updates wrapped in database transactions
737
+ - **Partial Updates**: Only changed fields are updated
738
+ - **Audit Trail**: Full change tracking for compliance
739
+ - **Error Handling**: Graceful handling of validation errors and conflicts
740
+ - **Performance Optimized**: Efficient bulk operations with minimal database calls
741
+
742
+ #### Advanced Example
743
+
744
+ ```jsx
745
+ // Manufacturing workflow with bulk edit capabilities
746
+ <DataGrid
747
+ ajaxSetting={{
748
+ url: '/ajax/manufacturing/items',
749
+ groupBy: ['work_order'],
750
+ groupBySetting: {
751
+ icons: [
752
+ // Bulk edit for user assignment
753
+ {
754
+ id: 'edit',
755
+ label: 'Assign Operator',
756
+ formModal: {
757
+ title: 'Bulk Assign Operator',
758
+ url: '/ajax/manufacturing/bulkAssign',
759
+ method: 'POST',
760
+ groupKey: 'work_order',
761
+ fields: ['operator_id', 'priority'],
762
+ data: {
763
+ stage: 'production',
764
+ update_type: 'assignment'
765
+ }
766
+ },
767
+ show: [
768
+ { id: 'status', value: 'ready' }
769
+ ]
770
+ },
771
+ // Timer operations (existing functionality)
772
+ {
773
+ id: 'clock',
774
+ label: 'Start Production',
775
+ fetch: {
776
+ url: '/ajax/manufacturing/groupTimer',
777
+ method: 'POST',
778
+ data: {
779
+ action: 'start_production',
780
+ timestamp: 'now()'
781
+ }
782
+ }
783
+ }
784
+ ]
785
+ }
786
+ }}
787
+ columns={[
788
+ { name: 'item_code', headerName: 'Item Code' },
789
+ { name: 'operator', headerName: 'Operator' },
790
+ { name: 'status', headerName: 'Status' },
791
+ { name: 'priority', headerName: 'Priority' }
792
+ ]}
793
+ form={{
794
+ fields: [
795
+ {
796
+ id: 'operator_id',
797
+ label: 'Production Operator',
798
+ type: 'dropdown-ajax',
799
+ url: '/ajax/users/operators',
800
+ required: true
801
+ },
802
+ {
803
+ id: 'priority',
804
+ label: 'Priority Level',
805
+ type: 'select',
806
+ options: [
807
+ { value: 'normal', label: 'Normal' },
808
+ { value: 'high', label: 'High' },
809
+ { value: 'urgent', label: 'Urgent' }
810
+ ]
811
+ }
812
+ ]
813
+ }}
814
+ />
815
+ ```
816
+
817
+ This implementation provides a powerful and intuitive way to manage bulk operations in grouped data scenarios, significantly improving workflow efficiency for users managing large datasets.
818
+
628
819
  ##### Customizing Group Rendering
629
820
 
630
821
  You can customize how groups are displayed:
package/package.json CHANGED
@@ -14,8 +14,8 @@
14
14
  "@tinymce/miniature": "^6.0.0",
15
15
  "@tinymce/tinymce-react": "^6.2.1",
16
16
  "@uiw/react-color": "^2.6.0",
17
- "@visns-studio/visns-datagrid-community": "^1.0.23",
18
- "@visns-studio/visns-datagrid-enterprise": "^1.0.23",
17
+ "@visns-studio/visns-datagrid-community": "1.1.0",
18
+ "@visns-studio/visns-datagrid-enterprise": "1.1.0",
19
19
  "@vitejs/plugin-react": "^4.6.0",
20
20
  "add": "^2.0.6",
21
21
  "array-move": "^4.0.0",
@@ -87,7 +87,7 @@
87
87
  "react-dom": "^17.0.0 || ^18.0.0"
88
88
  },
89
89
  "name": "@visns-studio/visns-components",
90
- "version": "5.11.22",
90
+ "version": "5.12.1",
91
91
  "description": "Various packages to assist in the development of our Custom Applications.",
92
92
  "main": "src/index.js",
93
93
  "files": [
@@ -275,6 +275,8 @@ const DataGrid = forwardRef(
275
275
  const [formType, setFormType] = useState('');
276
276
  const [formId, setFormId] = useState(0);
277
277
  const [modalShow, setModalShow] = useState(false);
278
+ const [bulkEditMode, setBulkEditMode] = useState(false);
279
+ const [bulkEditGroupData, setBulkEditGroupData] = useState(null);
278
280
 
279
281
  /** Modal Functions */
280
282
  const modalOpen = (formType, formId) => {
@@ -285,6 +287,8 @@ const DataGrid = forwardRef(
285
287
 
286
288
  const modalClose = () => {
287
289
  setModalShow(false);
290
+ setBulkEditMode(false);
291
+ setBulkEditGroupData(null);
288
292
  };
289
293
 
290
294
  useEffect(() => {
@@ -1209,7 +1213,13 @@ const DataGrid = forwardRef(
1209
1213
  // Get all rows for this group
1210
1214
  const groupRows = getGroupRows(groupValue);
1211
1215
 
1212
- // Show confirmation dialog to prevent accidental clicks
1216
+ // Skip confirmation dialog for edit actions - open modal directly
1217
+ if (iconConfig.id === 'edit' && iconConfig.formModal) {
1218
+ executeGroupAction(groupValue, iconConfig);
1219
+ return;
1220
+ }
1221
+
1222
+ // Show confirmation dialog for other actions to prevent accidental clicks
1213
1223
  const confirmMessage = `Are you sure you want to perform "${iconConfig.label}" action on group "${groupValue}" (${groupRows.length} rows)?`;
1214
1224
 
1215
1225
  confirmDialog({
@@ -1310,7 +1320,10 @@ const DataGrid = forwardRef(
1310
1320
  }
1311
1321
  } else {
1312
1322
  // Fallback to existing switch logic if no fetch config
1313
- toast.info(`${iconConfig.label} - Group: ${groupValue}`);
1323
+ // Skip toast notification for edit actions
1324
+ if (iconConfig.id !== 'edit') {
1325
+ toast.info(`${iconConfig.label} - Group: ${groupValue}`);
1326
+ }
1314
1327
 
1315
1328
  switch (iconConfig.id) {
1316
1329
  case 'clock':
@@ -1329,9 +1342,28 @@ const DataGrid = forwardRef(
1329
1342
  );
1330
1343
  break;
1331
1344
  case 'edit':
1332
- console.log(
1333
- `Bulk edit action for group: ${groupValue}`
1334
- );
1345
+ // Handle bulk edit action
1346
+ if (iconConfig.formModal) {
1347
+ // Get all rows in the group for bulk editing
1348
+ const groupRows = getGroupRows(groupValue);
1349
+ const groupRowIds = groupRows.map(row => row[form?.primaryKey || 'id']);
1350
+
1351
+ // Set bulk edit data
1352
+ setBulkEditMode(true);
1353
+ setBulkEditGroupData({
1354
+ groupValue: groupValue,
1355
+ groupRows: groupRows,
1356
+ groupRowIds: groupRowIds,
1357
+ iconConfig: iconConfig
1358
+ });
1359
+
1360
+ // Open modal for bulk editing
1361
+ modalOpen('update', null);
1362
+ } else {
1363
+ console.log(
1364
+ `Bulk edit action for group: ${groupValue}`
1365
+ );
1366
+ }
1335
1367
  break;
1336
1368
  case 'envelope':
1337
1369
  console.log(
@@ -3621,6 +3653,8 @@ const DataGrid = forwardRef(
3621
3653
  style={style}
3622
3654
  updateForm={setFormData}
3623
3655
  userProfile={userProfile}
3656
+ bulkEditMode={bulkEditMode}
3657
+ bulkEditGroupData={bulkEditGroupData}
3624
3658
  />
3625
3659
  </div>
3626
3660
  </div>
@@ -80,6 +80,8 @@ function Form({
80
80
  type,
81
81
  updateForm,
82
82
  userProfile,
83
+ bulkEditMode = false,
84
+ bulkEditGroupData = null,
83
85
  }) {
84
86
  const navigate = useNavigate();
85
87
  const [fetchTrigger, setFetchTrigger] = useState(false);
@@ -888,34 +890,42 @@ function Form({
888
890
  }
889
891
  method = formSettings.customMethod;
890
892
  } else {
891
- switch (formType) {
892
- case 'create':
893
- if (formSettings.json) {
894
- url += '/json/store';
895
- }
896
- break;
897
- case 'update':
898
- case 'delete':
899
- if (formSettings.json) {
900
- url +=
893
+ // Check if this is bulk edit mode
894
+ if (bulkEditMode && bulkEditGroupData?.iconConfig?.formModal) {
895
+ // Use bulk edit endpoint configuration
896
+ const formModalConfig = bulkEditGroupData.iconConfig.formModal;
897
+ url = formModalConfig.url || url;
898
+ method = formModalConfig.method || 'POST';
899
+ } else {
900
+ switch (formType) {
901
+ case 'create':
902
+ if (formSettings.json) {
903
+ url += '/json/store';
904
+ }
905
+ break;
906
+ case 'update':
907
+ case 'delete':
908
+ if (formSettings.json) {
909
+ url +=
910
+ formType === 'update'
911
+ ? '/json/update'
912
+ : `/json/delete/${
913
+ formData[
914
+ formSettings
915
+ .primaryKey
916
+ ]
917
+ }`;
918
+ } else {
919
+ url += `/${
920
+ formData[formSettings.primaryKey]
921
+ }`;
922
+ }
923
+ method =
901
924
  formType === 'update'
902
- ? '/json/update'
903
- : `/json/delete/${
904
- formData[
905
- formSettings
906
- .primaryKey
907
- ]
908
- }`;
909
- } else {
910
- url += `/${
911
- formData[formSettings.primaryKey]
912
- }`;
913
- }
914
- method =
915
- formType === 'update'
916
- ? 'PUT'
917
- : 'DELETE';
918
- break;
925
+ ? 'PUT'
926
+ : 'DELETE';
927
+ break;
928
+ }
919
929
  }
920
930
  }
921
931
 
@@ -1046,7 +1056,7 @@ function Form({
1046
1056
  }
1047
1057
 
1048
1058
  // Transform keys with dots into nested objects
1049
- const payload = Object.keys(updatedFormData).reduce(
1059
+ let payload = Object.keys(updatedFormData).reduce(
1050
1060
  (acc, key) => {
1051
1061
  if (key.includes('.')) {
1052
1062
  const [root, sub] = key.split('.');
@@ -1060,6 +1070,18 @@ function Form({
1060
1070
  {}
1061
1071
  );
1062
1072
 
1073
+ // Add bulk edit data to payload if in bulk edit mode
1074
+ if (bulkEditMode && bulkEditGroupData) {
1075
+ payload = {
1076
+ ...payload,
1077
+ bulkEdit: true,
1078
+ groupValue: bulkEditGroupData.groupValue,
1079
+ groupKey: bulkEditGroupData.iconConfig.formModal?.groupKey || ajaxSetting?.groupBy?.[0],
1080
+ rowIds: bulkEditGroupData.groupRowIds,
1081
+ ...bulkEditGroupData.iconConfig.formModal?.data
1082
+ };
1083
+ }
1084
+
1063
1085
  const res = await CustomFetch(url, method, payload);
1064
1086
 
1065
1087
  if (res.data.error === '') {
@@ -2233,7 +2255,12 @@ function Form({
2233
2255
  <div className={styles.modalwrap}>
2234
2256
  <div className={styles.modal}>
2235
2257
  <div className={styles.modal__header}>
2236
- <h1>{formSettings[formType]?.title}</h1>
2258
+ <h1>
2259
+ {bulkEditMode && bulkEditGroupData
2260
+ ? `${bulkEditGroupData.iconConfig.formModal?.title || 'Bulk Edit'} - ${bulkEditGroupData.groupValue} (${bulkEditGroupData.groupRows.length} items)`
2261
+ : formSettings[formType]?.title
2262
+ }
2263
+ </h1>
2237
2264
  <button
2238
2265
  className={styles.modal__close}
2239
2266
  onClick={closeModal}
@@ -2265,6 +2292,14 @@ function Form({
2265
2292
  ) {
2266
2293
  return null;
2267
2294
  }
2295
+ // In bulk edit mode, only show fields specified in the formModal configuration
2296
+ if (
2297
+ bulkEditMode &&
2298
+ bulkEditGroupData?.iconConfig?.formModal?.fields &&
2299
+ !bulkEditGroupData.iconConfig.formModal.fields.includes(item.id)
2300
+ ) {
2301
+ return null;
2302
+ }
2268
2303
 
2269
2304
  return (
2270
2305
  <Field
@@ -46,7 +46,7 @@ export const confirmDialog = ({
46
46
  }}
47
47
  style={{
48
48
  padding: '6px 12px',
49
- background: '#d32f2f',
49
+ background: '#2563eb',
50
50
  color: 'white',
51
51
  border: 'none',
52
52
  borderRadius: '4px',