df-ae-forms-package 1.0.96 → 1.0.98

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/dist/index.d.ts CHANGED
@@ -716,27 +716,38 @@ declare const DfFormSection: React.FC<DfFormSectionProps>;
716
716
  interface DfFormDataGridProps {
717
717
  id: string;
718
718
  properties: IDataGridComponent;
719
+ validationErrors?: Record<string, any>;
720
+ formValue?: any;
721
+ formData?: Record<string, any>;
722
+ readonly?: boolean;
723
+ disabled?: boolean;
724
+ touchedFields?: Record<string, boolean>;
725
+ formSubmitted?: boolean;
719
726
  mode?: 'edit' | 'preview' | 'test';
720
- formData?: any;
721
727
  onValueChange?: (change: IFormControlChange) => void;
728
+ onBlur?: () => void;
729
+ onFocus?: () => void;
722
730
  onSelect?: () => void;
723
731
  isSelected?: boolean;
724
732
  className?: string;
725
- onDataGridSelect?: (component: IDataGridComponent) => void;
733
+ onDataGridSelect?: (dataGrid: IDataGridComponent) => void;
734
+ onDataGridDelete?: (dataGridId: string) => void;
735
+ onEntryChange?: (entryIndex: number, components: FormComponentType[]) => void;
726
736
  onComponentSelect?: (component: FormComponentType) => void;
727
737
  onComponentDelete?: (component: FormComponentType, event: React.MouseEvent) => void;
728
738
  onComponentEdit?: (component: FormComponentType) => void;
729
739
  onComponentUpdate?: (componentId: string, updates: Partial<FormComponentType>) => void;
730
740
  selectedComponent?: FormComponentType | null;
731
- renderFormComponent?: (component: FormComponentType, hideLabel?: boolean) => React.ReactNode;
741
+ renderFormComponent?: (field: FormComponentType, hideLabel?: boolean) => React.ReactNode;
742
+ onDataGridUpdate?: (dataGridId: string, updates: Partial<IDataGridComponent>) => void;
732
743
  onEntryAdd?: () => void;
733
- onEntryRemove?: (index: number) => void;
744
+ onEntryRemove?: (entryIndex: number) => void;
734
745
  formTemplateId?: string;
735
746
  onThresholdActionCompletion?: (conditionId: string, action: 'notes' | 'attachments' | 'email', completed: boolean) => void;
736
747
  onThresholdIssueRaised?: (conditionId: string) => void;
737
748
  onNotesChange?: (componentId: string, notes: string) => void;
738
749
  onAttachmentChange?: (componentId: string, attachments: File[] | null) => void;
739
- shouldShowComponent?: (componentId: string) => boolean;
750
+ shouldShowComponent?: (id: string) => boolean;
740
751
  }
741
752
  declare const DfFormDataGrid: React.FC<DfFormDataGridProps>;
742
753
 
package/dist/index.esm.js CHANGED
@@ -2,8 +2,8 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
3
3
  import { AlertTriangle, X as X$1, User, Calendar, MessageSquare, UploadCloud, Paperclip, Loader2, Navigation, MapPin, Bold, Italic, List, ListOrdered, Image as Image$1, Check, Mail, AlertCircle, ChevronUp, ChevronDown, ChevronRight, Grid, GripVertical, Edit, Trash2, Table } from 'lucide-react';
4
4
  import require$$0, { createPortal } from 'react-dom';
5
- import { useDroppable, useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext } from '@dnd-kit/core';
6
- import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, horizontalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
5
+ import { useDroppable } from '@dnd-kit/core';
6
+ import { SortableContext, horizontalListSortingStrategy, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
7
7
  import { CSS } from '@dnd-kit/utilities';
8
8
  import { v4 } from 'uuid';
9
9
 
@@ -4332,57 +4332,20 @@ const DraggableGridComponent = ({ component, selectedComponent, mode, onComponen
4332
4332
  e.currentTarget.style.backgroundColor = '#ef4444';
4333
4333
  }, children: jsx(Trash2, { size: 12 }) })] }))] }));
4334
4334
  };
4335
- // Sub-component for the drop zone within the grid
4336
- const GridDropZone = ({ gridComponents, mode, onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, selectedComponent, renderFormComponent, gridId, formData, formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChange, onAttachmentChange, columnView, shouldShowComponent }) => {
4335
+ const GridDropZone = ({ gridComponents, mode, onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, selectedComponent, renderFormComponent, gridId, formData = {}, formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChange, onAttachmentChange, columnView = false, shouldShowComponent }) => {
4337
4336
  const { setNodeRef, isOver } = useDroppable({
4338
4337
  id: `grid-drop-zone-${gridId}`,
4339
- disabled: mode !== 'edit',
4340
- data: {
4341
- isGridDropZone: true,
4342
- gridId: gridId
4343
- }
4344
- });
4345
- // Sensors for drag and drop
4346
- const sensors = useSensors(useSensor(PointerSensor, {
4347
- activationConstraint: {
4348
- distance: 8,
4349
- },
4350
- }), useSensor(KeyboardSensor, {
4351
- coordinateGetter: sortableKeyboardCoordinates,
4352
- }));
4353
- const handleDragEnd = ((event) => {
4354
- const { active, over } = event;
4355
- if (over && active.id !== over.id) {
4356
- gridComponents.findIndex((item) => item.id === active.id);
4357
- gridComponents.findIndex((item) => item.id === over.id);
4358
- }
4338
+ disabled: mode !== 'edit'
4359
4339
  });
4360
- return (jsx("div", { ref: setNodeRef, className: `grid-drop-zone ${gridComponents.length === 0 ? 'empty' : ''}`, style: {
4361
- border: isOver ? '2px dashed #3b82f6' : '1px dashed #d1d5db',
4340
+ return (jsx("div", { ref: setNodeRef, className: "grid-drop-zone", style: {
4341
+ border: isOver ? '2px dashed #3b82f6' : '2px dashed #d1d5db',
4362
4342
  borderRadius: '8px',
4363
4343
  padding: '16px',
4364
- backgroundColor: isOver ? 'var(--df-color-primary-light)' : '#f9fafb',
4365
- minHeight: '100px',
4366
- transition: 'all 0.2s ease'
4367
- }, children: gridComponents.length === 0 ? (jsxs("div", { style: {
4368
- textAlign: 'center',
4369
- color: 'var(--df-color-text-light)',
4370
- fontSize: '14px',
4371
- padding: '40px 20px',
4372
- display: 'flex',
4373
- flexDirection: 'column',
4374
- alignItems: 'center',
4375
- gap: '8px',
4376
- backgroundColor: 'var(--df-color-fb-container)',
4377
- border: '1px dashed var(--df-color-fb-border)',
4378
- borderRadius: '8px'
4379
- }, children: [jsx("div", { style: {
4380
- fontWeight: '500',
4381
- color: isOver ? 'var(--df-color-primary)' : 'var(--df-color-text-dark)'
4382
- }, children: isOver ? 'Drop components here' : 'Empty DataGrid' }), jsx("div", { style: {
4383
- fontSize: '12px',
4384
- color: '#9ca3af'
4385
- }, children: "Drag and drop components here to create your grid" })] })) : (jsxs(DndContext, { sensors: sensors, onDragEnd: handleDragEnd, children: [jsx(SortableContext, { items: gridComponents.map(c => c.id), strategy: columnView ? verticalListSortingStrategy : horizontalListSortingStrategy, children: jsx("div", { style: {
4344
+ backgroundColor: isOver ? 'var(--df-color-primary-light)' : 'var(--df-color-fb-container)',
4345
+ minHeight: '120px',
4346
+ transition: 'all 0.2s ease',
4347
+ position: 'relative'
4348
+ }, children: gridComponents.length > 0 ? (jsxs(Fragment, { children: [jsx(SortableContext, { items: gridComponents.map(c => c.id), strategy: horizontalListSortingStrategy, children: jsx("div", { style: {
4386
4349
  display: 'flex',
4387
4350
  flexDirection: columnView ? 'column' : 'row',
4388
4351
  flexWrap: 'nowrap',
@@ -4407,14 +4370,28 @@ const GridDropZone = ({ gridComponents, mode, onComponentSelect, onComponentDele
4407
4370
  minHeight: '40px',
4408
4371
  display: 'flex',
4409
4372
  alignItems: 'center',
4410
- justifyContent: 'center',
4411
- marginTop: '12px'
4412
- }, children: isOver ? (jsx("span", { style: { color: '#3b82f6', fontWeight: '500' }, children: "Drop component here to add to grid" })) : (jsx("span", { children: "+ Drop more components here" })) })] })) }));
4373
+ justifyContent: 'center'
4374
+ }, children: isOver ? (jsx("span", { style: { color: '#3b82f6', fontWeight: '500' }, children: "Drop component here to add to grid" })) : (jsx("span", { children: "+ Drop more components here" })) })] })) : (jsxs("div", { style: {
4375
+ textAlign: 'center',
4376
+ color: 'var(--df-color-text-light)',
4377
+ fontSize: '14px',
4378
+ padding: '40px 20px',
4379
+ display: 'flex',
4380
+ flexDirection: 'column',
4381
+ alignItems: 'center',
4382
+ gap: '8px',
4383
+ backgroundColor: 'var(--df-color-fb-container)',
4384
+ border: '1px dashed var(--df-color-fb-border)',
4385
+ borderRadius: '8px'
4386
+ }, children: [jsx("div", { style: {
4387
+ fontWeight: '500',
4388
+ color: isOver ? 'var(--df-color-primary)' : 'var(--df-color-text-dark)'
4389
+ }, children: isOver ? 'Drop components here' : 'Empty DataGrid' }), jsx("div", { style: {
4390
+ fontSize: '12px',
4391
+ color: '#9ca3af'
4392
+ }, children: "Drag and drop components here to create your grid" })] })) }));
4413
4393
  };
4414
- // Sub-component for displaying entries (TableView)
4415
- const TableView = ({ templateComponents, dataEntries, renderFormComponent, mode, allowAddRemoveEntries, addAnotherText, removeText, maxEntries, minEntries, displayAsGrid = true, onAddEntry, onRemoveEntry, formData, // Use current formData to render values
4416
- formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChange, onAttachmentChange, columnView, shouldShowComponent }) => {
4417
- const _formData = formData || {};
4394
+ const TableView = ({ templateComponents, dataEntries, renderFormComponent, mode = 'preview', allowAddRemoveEntries = true, addAnotherText = 'Add Another', removeText = 'Remove', maxEntries = 10, minEntries = 1, displayAsGrid = true, onAddEntry, onRemoveEntry, formData: _formData = {}, formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChange, onAttachmentChange, columnView = false, shouldShowComponent }) => {
4418
4395
  const visibleTemplateComponents = React.useMemo(() => {
4419
4396
  if (!shouldShowComponent)
4420
4397
  return templateComponents;
@@ -4476,18 +4453,16 @@ formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChan
4476
4453
  whiteSpace: 'nowrap',
4477
4454
  overflow: 'hidden',
4478
4455
  textOverflow: 'ellipsis'
4479
- }, children: component.basic?.label || `Column ${index + 1}` }, `header-${component.id || index}`))) })), dataEntries.length > 0 ? (dataEntries.map((entry, entryIndex) => (jsxs("div", { className: "table-row", style: {
4480
- // Use flex column for column view, grid for row view
4481
- display: columnView ? 'flex' : 'grid',
4482
- flexDirection: columnView ? 'column' : 'row',
4483
- gridTemplateColumns: !columnView
4484
- ? `repeat(${visibleTemplateComponents.length}, minmax(150px, 1fr))`
4485
- : undefined,
4456
+ }, children: component.basic?.label || `Column ${index + 1}` }, `header-${component.id}`))) })), dataEntries.length > 0 ? (dataEntries.map((entry, entryIndex) => (jsxs("div", { className: "table-row", style: {
4457
+ display: 'grid',
4458
+ // Change grid columns to 1fr for column view
4459
+ gridTemplateColumns: columnView
4460
+ ? '1fr'
4461
+ : `repeat(${visibleTemplateComponents.length}, minmax(150px, 1fr))`,
4486
4462
  borderBottom: entryIndex < dataEntries.length - 1 ? '1px solid var(--df-color-fb-border)' : 'none',
4487
4463
  backgroundColor: entryIndex % 2 === 0 ? 'var(--df-color-fb-container)' : 'var(--df-color-fb-bg)',
4488
4464
  position: 'relative',
4489
- minWidth: columnView ? '100%' : `${visibleTemplateComponents.length * 150}px`,
4490
- padding: columnView ? '16px' : '0'
4465
+ minWidth: columnView ? '100%' : `${visibleTemplateComponents.length * 150}px`
4491
4466
  }, children: [visibleTemplateComponents.map((templateComponent, componentIndex) => {
4492
4467
  let entryComponent = entry.components?.[componentIndex];
4493
4468
  if (!entryComponent) {
@@ -4513,24 +4488,21 @@ formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChan
4513
4488
  };
4514
4489
  }
4515
4490
  return (jsx("div", { style: {
4516
- padding: columnView ? '12px 0' : '12px 16px',
4491
+ padding: '12px 16px',
4517
4492
  borderRight: !columnView && componentIndex < visibleTemplateComponents.length - 1 ? '1px solid var(--df-color-fb-border)' : 'none',
4518
4493
  // Add bottom border for fields in column view except the last one
4519
4494
  borderBottom: columnView && componentIndex < visibleTemplateComponents.length - 1 ? '1px dashed var(--df-color-fb-border)' : 'none',
4520
- minHeight: columnView ? 'auto' : '60px',
4495
+ minHeight: '60px',
4521
4496
  minWidth: columnView ? '100%' : '150px',
4522
- width: columnView ? '100%' : 'auto',
4523
4497
  display: 'flex',
4524
- flexDirection: columnView ? 'column' : 'row',
4525
- alignItems: columnView ? 'stretch' : 'center',
4526
- overflow: 'hidden',
4527
- gap: columnView ? '8px' : '0'
4498
+ alignItems: 'center',
4499
+ overflow: 'hidden'
4528
4500
  }, children: jsx("div", { style: {
4529
4501
  width: '100%',
4530
- minWidth: columnView ? '100%' : '120px',
4502
+ minWidth: '120px',
4531
4503
  overflow: 'hidden'
4532
4504
  }, children: renderFormComponent(entryComponent, !columnView) }) }, `${entry.id}-${componentIndex}`));
4533
- }), mode === 'test' && allowAddRemoveEntries && dataEntries.length > minEntries && (jsx("button", { onClick: () => onRemoveEntry(entryIndex), disabled: dataEntries.length <= minEntries, style: {
4505
+ }), mode === 'test' && allowAddRemoveEntries && dataEntries.length > minEntries && (jsx("button", { onClick: () => onRemoveEntry?.(entryIndex), disabled: dataEntries.length <= minEntries, style: {
4534
4506
  position: 'absolute',
4535
4507
  top: '8px',
4536
4508
  right: '8px',
@@ -4564,7 +4536,7 @@ formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChan
4564
4536
  position: 'sticky',
4565
4537
  bottom: 0,
4566
4538
  zIndex: 5
4567
- }, children: jsxs("button", { onClick: () => { console.log('[TableView] Add Entry button clicked (grid view)', 'entries:', dataEntries.length, 'max:', maxEntries); onAddEntry(); }, disabled: dataEntries.length >= maxEntries, style: {
4539
+ }, children: jsxs("button", { onClick: onAddEntry, disabled: dataEntries.length >= maxEntries, style: {
4568
4540
  padding: '8px 16px',
4569
4541
  backgroundColor: dataEntries.length >= maxEntries ? '#f3f4f6' : '#10b981',
4570
4542
  color: dataEntries.length >= maxEntries ? '#9ca3af' : '#ffffff',
@@ -4602,7 +4574,7 @@ formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChan
4602
4574
  fontWeight: '600',
4603
4575
  color: 'var(--df-color-text-dark)',
4604
4576
  fontSize: '14px'
4605
- }, children: ["Entry #", entryIndex + 1] }), mode === 'test' && allowAddRemoveEntries && dataEntries.length > 1 && (jsx("button", { onClick: () => onRemoveEntry(entryIndex), disabled: dataEntries.length <= minEntries, style: {
4577
+ }, children: ["Entry #", entryIndex + 1] }), mode === 'test' && allowAddRemoveEntries && dataEntries.length > 1 && (jsx("button", { onClick: () => onRemoveEntry?.(entryIndex), disabled: dataEntries.length <= minEntries, style: {
4606
4578
  padding: '4px 8px',
4607
4579
  backgroundColor: dataEntries.length <= minEntries ? '#f3f4f6' : '#ef4444',
4608
4580
  color: dataEntries.length <= minEntries ? '#9ca3af' : '#ffffff',
@@ -4618,39 +4590,40 @@ formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChan
4618
4590
  height: '24px',
4619
4591
  justifyContent: 'center'
4620
4592
  }, title: removeText, children: jsx("span", { style: { fontSize: '14px' }, children: "\u00D7" }) }))] }), jsx("div", { style: {
4621
- display: columnView ? 'flex' : 'grid',
4622
- flexDirection: columnView ? 'column' : 'row',
4623
- gridTemplateColumns: !columnView ? 'repeat(auto-fit, minmax(200px, 1fr))' : undefined,
4593
+ display: 'grid',
4594
+ gridTemplateColumns: columnView ? '1fr' : 'repeat(auto-fit, minmax(200px, 1fr))',
4624
4595
  gap: '16px'
4625
4596
  }, children: visibleTemplateComponents.map((templateComponent, componentIndex) => {
4597
+ // Find the corresponding component in this entry by index first, then by name+label
4626
4598
  let entryComponent = entry.components?.[componentIndex];
4599
+ // If no component at this index, try to find by name+label
4627
4600
  if (!entryComponent) {
4628
4601
  entryComponent = entry.components?.find((comp) => comp.name === templateComponent.name &&
4629
4602
  comp.basic?.label === templateComponent.basic?.label);
4630
4603
  }
4631
4604
  if (!entryComponent) {
4605
+ // Use entry.id (which is unique) instead of entryIndex
4632
4606
  const uniqueId = `${templateComponent.id}-${entry.id}-${componentIndex}`;
4633
4607
  entryComponent = {
4634
4608
  ...templateComponent,
4635
4609
  id: uniqueId,
4636
4610
  basic: {
4637
4611
  ...templateComponent.basic,
4638
- showLabel: columnView // Show label in column view, hide in grid view
4612
+ showLabel: false // Hide label in datagrid cells
4639
4613
  }
4640
4614
  };
4641
4615
  }
4642
4616
  else {
4617
+ // Preserve the original ID to maintain form value connections
4643
4618
  entryComponent = {
4644
4619
  ...entryComponent,
4645
- id: entryComponent.id,
4620
+ id: entryComponent.id, // Keep the original ID
4646
4621
  basic: {
4647
4622
  ...entryComponent.basic,
4648
- showLabel: columnView // Show label in column view, hide in grid view
4623
+ showLabel: false // Hide label in datagrid cells
4649
4624
  }
4650
4625
  };
4651
4626
  }
4652
- if (!entryComponent)
4653
- return null;
4654
4627
  return (jsxs("div", { style: {
4655
4628
  display: 'flex',
4656
4629
  flexDirection: 'column',
@@ -4686,7 +4659,7 @@ formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChan
4686
4659
  borderRadius: '8px',
4687
4660
  display: 'flex',
4688
4661
  justifyContent: 'center'
4689
- }, children: jsxs("button", { onClick: () => { console.log('[TableView] Add Entry button clicked (list view)', 'entries:', dataEntries.length, 'max:', maxEntries); onAddEntry(); }, disabled: dataEntries.length >= maxEntries, style: {
4662
+ }, children: jsxs("button", { onClick: onAddEntry, disabled: dataEntries.length >= maxEntries, style: {
4690
4663
  padding: '8px 16px',
4691
4664
  backgroundColor: dataEntries.length >= maxEntries ? '#f3f4f6' : '#10b981',
4692
4665
  color: dataEntries.length >= maxEntries ? '#9ca3af' : '#ffffff',
@@ -4699,19 +4672,11 @@ formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChan
4699
4672
  alignItems: 'center',
4700
4673
  gap: '8px',
4701
4674
  transition: 'all 0.2s ease'
4702
- }, onMouseEnter: (e) => {
4703
- if (dataEntries.length < maxEntries) {
4704
- e.currentTarget.style.backgroundColor = '#059669';
4705
- }
4706
- }, onMouseLeave: (e) => {
4707
- e.currentTarget.style.backgroundColor = '#10b981';
4708
4675
  }, children: [jsx("span", { children: "+" }), addAnotherText] }) }))] }));
4709
4676
  };
4710
4677
  const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueChange, onSelect, isSelected = false, className = '', onDataGridSelect, onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, selectedComponent, renderFormComponent, onEntryAdd, onEntryRemove, formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChange, onAttachmentChange, shouldShowComponent }) => {
4711
4678
  const [isCollapsed, setIsCollapsed] = useState(false);
4712
4679
  const hasInitialized = useRef(false);
4713
- // Track local form values for entry components to prevent value loss during parent round-trip
4714
- const localFormValuesRef = useRef({});
4715
4680
  // Get all components in the grid and sanitize them to ensure no data leaks into templates
4716
4681
  let gridComponents = (properties.templateComponents || []).map(comp => ({
4717
4682
  ...comp,
@@ -4789,6 +4754,87 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4789
4754
  }
4790
4755
  }
4791
4756
  }
4757
+ else {
4758
+ // Update existing entries to include all template components and sync their properties
4759
+ const needsUpdate = dataEntries.some(entry => {
4760
+ // Check if entry is missing any template components or if existing components need updates
4761
+ return gridComponents.some((templateComp, componentIndex) => {
4762
+ const existingComponent = entry.components?.[componentIndex];
4763
+ if (!existingComponent) {
4764
+ return true; // Missing component at this index
4765
+ }
4766
+ // We don't check for ID matches here anymore
4767
+ // const expectedId = `${templateComp.id}-entry-${entry.index}-${componentIndex}`
4768
+ // const hasProperId = existingComponent.id === expectedId
4769
+ // Check if existing component needs to be updated with new template properties
4770
+ // Compare key properties that should be synced
4771
+ const needsPropertyUpdate = JSON.stringify(existingComponent.basic?.options) !== JSON.stringify(templateComp.basic?.options) ||
4772
+ existingComponent.basic?.placeholder !== templateComp.basic?.placeholder ||
4773
+ existingComponent.basic?.defaultValue !== templateComp.basic?.defaultValue ||
4774
+ existingComponent.basic?.label !== templateComp.basic?.label ||
4775
+ existingComponent.validation?.required !== templateComp.validation?.required;
4776
+ // We do not check for ID mismatch anymore as we want to preserve unique IDs
4777
+ return needsPropertyUpdate;
4778
+ });
4779
+ });
4780
+ if (needsUpdate && onValueChange) {
4781
+ const updatedEntries = dataEntries.map(entry => {
4782
+ // Use index-based matching to ensure each template component maps to the correct entry component
4783
+ const updatedComponents = gridComponents.map((templateComp, componentIndex) => {
4784
+ // Find existing component by index first
4785
+ let existingComponent = entry.components?.[componentIndex];
4786
+ // If no component at this index, try to find by name+label (for backward compatibility)
4787
+ if (!existingComponent) {
4788
+ existingComponent = entry.components?.find((comp) => comp.name === templateComp.name &&
4789
+ comp.basic?.label === templateComp.basic?.label);
4790
+ }
4791
+ // Always ensure a valid ID exists, but respect existing one if possible
4792
+ // const uniqueId = `${templateComp.id}-entry-${entry.index}-${componentIndex}`
4793
+ if (existingComponent) {
4794
+ // Update existing component with new template properties while ensuring unique ID and preserving form values
4795
+ const updatedComponent = {
4796
+ ...templateComp,
4797
+ id: existingComponent.id, // Preserve existing ID !!
4798
+ basic: {
4799
+ ...templateComp.basic,
4800
+ showLabel: false, // Hide label in datagrid cells
4801
+ // Preserve any user-entered values
4802
+ value: existingComponent.basic?.value || templateComp.basic?.defaultValue || ''
4803
+ }
4804
+ };
4805
+ return updatedComponent;
4806
+ }
4807
+ else {
4808
+ // Create new component based on template
4809
+ // Only for NEW components in existing entries (e.g. column added) do we generate a new ID
4810
+ const uniqueSuffix = `${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
4811
+ const newId = `${templateComp.id}-${uniqueSuffix}-${componentIndex}`;
4812
+ const newComponent = {
4813
+ ...templateComp,
4814
+ id: newId,
4815
+ basic: {
4816
+ ...templateComp.basic,
4817
+ showLabel: false // Hide label in datagrid cells
4818
+ }
4819
+ };
4820
+ return newComponent;
4821
+ }
4822
+ });
4823
+ return {
4824
+ ...entry,
4825
+ components: updatedComponents
4826
+ };
4827
+ });
4828
+ const newValue = { ...properties, entries: updatedEntries };
4829
+ // Only call onValueChange if the data actually changed
4830
+ if (JSON.stringify(newValue) !== JSON.stringify(properties)) {
4831
+ onValueChange({
4832
+ id,
4833
+ value: newValue
4834
+ });
4835
+ }
4836
+ }
4837
+ }
4792
4838
  }
4793
4839
  }, [gridComponents, dataEntries, id, onValueChange, properties, mode, properties.templateComponents]);
4794
4840
  const handleDataGridClick = useCallback((event) => {
@@ -4845,9 +4891,8 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4845
4891
  // Handle component value change for form data updates (test mode)
4846
4892
  const handleComponentValueChange = useCallback((change) => {
4847
4893
  if (mode === 'test') {
4848
- // CRITICAL: Store value locally to prevent loss during parent round-trip
4849
- localFormValuesRef.current[change.id] = change.value;
4850
- // Also propagate up to parent for persistence
4894
+ // In test mode, update form data through the parent's onValueChange
4895
+ // This allows the form data to be updated for interactive components
4851
4896
  if (onValueChange) {
4852
4897
  onValueChange({
4853
4898
  id: change.id,
@@ -4858,10 +4903,6 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4858
4903
  // In edit/preview modes, don't handle value changes as components are read-only
4859
4904
  }, [mode, onValueChange]);
4860
4905
  const handleAddEntry = useCallback(() => {
4861
- console.log('[DfFormDataGrid] handleAddEntry called');
4862
- console.log('[DfFormDataGrid] gridComponents:', gridComponents.length, gridComponents.map(c => c.id));
4863
- console.log('[DfFormDataGrid] current entries:', properties.entries?.length);
4864
- console.log('[DfFormDataGrid] onValueChange exists:', !!onValueChange);
4865
4906
  // Use timestamp and random string to ensure uniqueness even if entries are deleted
4866
4907
  const uniqueSuffix = `${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
4867
4908
  const newEntryId = `entry-${uniqueSuffix}`;
@@ -4883,19 +4924,13 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4883
4924
  }),
4884
4925
  styles: {}
4885
4926
  };
4886
- console.log('[DfFormDataGrid] newEntry created:', newEntry.id, 'with', newEntry.components.length, 'components');
4887
4927
  const updatedEntries = [...properties.entries, newEntry];
4888
- console.log('[DfFormDataGrid] updatedEntries count:', updatedEntries.length);
4889
4928
  if (onValueChange) {
4890
- console.log('[DfFormDataGrid] calling onValueChange with updated entries');
4891
4929
  onValueChange({
4892
4930
  id,
4893
4931
  value: { ...properties, entries: updatedEntries }
4894
4932
  });
4895
4933
  }
4896
- else {
4897
- console.log('[DfFormDataGrid] WARNING: onValueChange is not defined!');
4898
- }
4899
4934
  onEntryAdd?.();
4900
4935
  }, [properties, onValueChange, id, onEntryAdd, gridComponents]);
4901
4936
  const handleRemoveEntry = useCallback((entryIndex) => {
@@ -4911,11 +4946,7 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4911
4946
  }, [properties, onValueChange, id, onEntryRemove]);
4912
4947
  // Use our own render function to ensure proper onComponentUpdate handling
4913
4948
  const renderComponent = useCallback((field, hideLabel = false) => {
4914
- // CRITICAL: Use local values first, then fall back to formData prop, then defaults
4915
- // This ensures typed values in entry 2+ persist immediately
4916
- const formValue = mode === 'test'
4917
- ? (localFormValuesRef.current[field.id] ?? formData[field.id] ?? field.basic?.value ?? ('defaultValue' in field.basic ? field.basic.defaultValue || '' : ''))
4918
- : ('defaultValue' in field.basic ? field.basic.defaultValue || '' : '');
4949
+ const formValue = mode === 'test' ? (formData[field.id] || ('defaultValue' in field.basic ? field.basic.defaultValue || '' : '')) : ('defaultValue' in field.basic ? field.basic.defaultValue || '' : '');
4919
4950
  const commonProps = {
4920
4951
  id: field.id,
4921
4952
  properties: field,
@@ -4996,7 +5027,7 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4996
5027
  , {
4997
5028
  // Cast to FormComponentType[] to satisfy TableView typing; gridComponents
4998
5029
  // are always child form components of the datagrid.
4999
- templateComponents: gridComponents, dataEntries: dataEntries, renderFormComponent: renderFormComponent || renderComponent, mode: mode, allowAddRemoveEntries: properties.datagrid?.allowAddRemoveEntries ?? true, addAnotherText: properties.datagrid?.addAnotherText ?? 'Add Entry', removeText: properties.datagrid?.removeText ?? 'Remove', maxEntries: properties.datagrid?.maxEntries ?? 10, minEntries: properties.datagrid?.minEntries ?? 1, displayAsGrid: properties.datagrid?.displayAsGrid ?? true, onAddEntry: handleAddEntry, onRemoveEntry: handleRemoveEntry, formData: formData, formTemplateId: formTemplateId, onThresholdActionCompletion: onThresholdActionCompletion, onThresholdIssueRaised: onThresholdIssueRaised, onNotesChange: onNotesChange, onAttachmentChange: onAttachmentChange, columnView: properties.datagrid?.columnView, shouldShowComponent: shouldShowComponent })) }))] }));
5030
+ templateComponents: gridComponents, dataEntries: dataEntries, renderFormComponent: renderFormComponent || renderComponent, mode: mode, allowAddRemoveEntries: properties.datagrid?.allowAddRemoveEntries ?? true, addAnotherText: properties.datagrid?.addAnotherText ?? 'Add Entry', removeText: properties.datagrid?.removeText ?? 'Remove', maxEntries: properties.datagrid?.maxEntries ?? 10, minEntries: properties.datagrid?.minEntries ?? 1, displayAsGrid: properties.datagrid?.displayAsGrid ?? true, onAddEntry: onEntryAdd ? onEntryAdd : handleAddEntry, onRemoveEntry: onEntryRemove ? onEntryRemove : handleRemoveEntry, formData: formData, formTemplateId: formTemplateId, onThresholdActionCompletion: onThresholdActionCompletion, onThresholdIssueRaised: onThresholdIssueRaised, onNotesChange: onNotesChange, onAttachmentChange: onAttachmentChange, columnView: properties.datagrid?.columnView, shouldShowComponent: shouldShowComponent })) }))] }));
5000
5031
  };
5001
5032
 
5002
5033
  const DraggableChild = ({ child, selectedChild, mode, onChildSelect, onChildDelete, renderFormComponent, isOverlay = false, isChildrenEditMode = false, formData = {}, formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChange, onAttachmentChange, workOrderNumber, assetNumber, user, onCreateIssue, onUpdateIssue }) => {
@@ -6419,9 +6450,12 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6419
6450
  onFormDataChange?.(updatedComponents);
6420
6451
  } }) }));
6421
6452
  case 'datagrid':
6453
+ // Align package datagrid wiring with main app behaviour:
6454
+ // - Let DfFormDataGrid manage entry structure via onValueChange
6455
+ // - Use the shared onFormValueChange handler for nested field values
6456
+ // - Keep notes/attachments wiring as before
6422
6457
  return (jsx(DfFormDataGrid, { ...commonProps, properties: component, formData: formValues, formTemplateId: formTemplateId, mode: commonProps.mode, onThresholdActionCompletion: handleThresholdActionCompletion, onThresholdIssueRaised: handleThresholdIssueRaised, onNotesChange: (componentId, notes) => {
6423
6458
  handleComponentNotesChange(componentId, notes);
6424
- // Handle notes change for datagrid entry components
6425
6459
  const updatedComponents = localFormComponents.map(comp => {
6426
6460
  if (comp.id === component.id && comp.entries) {
6427
6461
  const updatedEntries = comp.entries.map((entry) => {
@@ -6449,7 +6483,6 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6449
6483
  onFormDataChange?.(updatedComponents);
6450
6484
  }, onAttachmentChange: (componentId, attachments) => {
6451
6485
  handleComponentAttachmentChange(componentId, attachments);
6452
- // Handle attachment change for datagrid entry components
6453
6486
  const updatedComponents = localFormComponents.map(comp => {
6454
6487
  if (comp.id === component.id && comp.entries) {
6455
6488
  const updatedEntries = comp.entries.map((entry) => {
@@ -6475,116 +6508,7 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6475
6508
  return comp;
6476
6509
  });
6477
6510
  onFormDataChange?.(updatedComponents);
6478
- }, onValueChange: (change) => {
6479
- console.log('[DfFormPreview] datagrid onValueChange received:', change.id, 'hasEntries:', change.value && typeof change.value === 'object' && 'entries' in change.value);
6480
- // Handle datagrid value changes (entries updates)
6481
- if (change.id === component.id && change.value && typeof change.value === 'object' && 'entries' in change.value) {
6482
- console.log('[DfFormPreview] datagrid entries update - entries count:', change.value.entries?.length);
6483
- // Update localFormComponents with new entries structure
6484
- const updatedComponents = localFormComponents.map(comp => {
6485
- if (comp.id === component.id) {
6486
- return {
6487
- ...comp,
6488
- ...change.value
6489
- };
6490
- }
6491
- return comp;
6492
- });
6493
- // CRITICAL: Update local state immediately so new entries render without Angular round-trip
6494
- setLocalFormComponents(updatedComponents);
6495
- onFormDataChange?.(updatedComponents);
6496
- // Also update formValues for nested components
6497
- if (change.value.entries && Array.isArray(change.value.entries)) {
6498
- change.value.entries.forEach((entry) => {
6499
- if (entry.components && Array.isArray(entry.components)) {
6500
- entry.components.forEach((nestedComp) => {
6501
- const nestedValue = formValues[nestedComp.id];
6502
- if (nestedValue !== undefined) ;
6503
- else {
6504
- // Initialize with defaultValue if available
6505
- const defaultValue = nestedComp.basic?.defaultValue;
6506
- if (defaultValue !== undefined) {
6507
- setFormValues(prev => ({
6508
- ...prev,
6509
- [nestedComp.id]: defaultValue
6510
- }));
6511
- }
6512
- }
6513
- });
6514
- }
6515
- });
6516
- }
6517
- }
6518
- else {
6519
- // For nested component value changes, use the regular handler
6520
- onFormValueChange(change);
6521
- }
6522
- }, onEntryAdd: () => {
6523
- // CRITICAL: Entry has already been added via onValueChange in DfFormDataGrid
6524
- // Get the updated component from localFormComponents (which should have been updated by onValueChange)
6525
- const currentComponent = localFormComponents.find(comp => comp.id === component.id);
6526
- if (currentComponent && currentComponent.entries) {
6527
- // Entry should already be in the component via onValueChange
6528
- // Just ensure localFormComponents is in sync (no-op if already synced)
6529
- const updatedComponents = localFormComponents.map(comp => {
6530
- if (comp.id === component.id) {
6531
- // Ensure entries are properly structured
6532
- return {
6533
- ...comp,
6534
- entries: comp.entries || []
6535
- };
6536
- }
6537
- return comp;
6538
- });
6539
- onFormDataChange?.(updatedComponents);
6540
- }
6541
- else {
6542
- // Fallback: If component doesn't have entries yet, try to get from formValues
6543
- setTimeout(() => {
6544
- const datagridValue = formValues[component.id];
6545
- if (datagridValue && typeof datagridValue === 'object' && 'entries' in datagridValue) {
6546
- const updatedComponents = localFormComponents.map(comp => {
6547
- if (comp.id === component.id) {
6548
- return {
6549
- ...comp,
6550
- entries: datagridValue.entries
6551
- };
6552
- }
6553
- return comp;
6554
- });
6555
- onFormDataChange?.(updatedComponents);
6556
- }
6557
- }, 100);
6558
- }
6559
- }, onEntryRemove: (entryIndex) => {
6560
- // Handle entry remove - update form components
6561
- const updatedComponents = localFormComponents.map(comp => {
6562
- if (comp.id === component.id && comp.entries) {
6563
- const currentEntries = comp.entries || [];
6564
- const updatedEntries = currentEntries
6565
- .filter((_, index) => index !== entryIndex)
6566
- .map((entry, index) => ({
6567
- ...entry,
6568
- index,
6569
- id: `entry-${comp.id}-${index}`,
6570
- components: entry.components?.map((comp, compIndex) => {
6571
- const templateComp = (comp.templateComponents || [])[compIndex];
6572
- return {
6573
- ...comp,
6574
- id: templateComp ? `${templateComp.id}-entry-${index}-${compIndex}` : comp.id
6575
- };
6576
- }) || []
6577
- }));
6578
- return {
6579
- ...comp,
6580
- entries: updatedEntries
6581
- };
6582
- }
6583
- return comp;
6584
- });
6585
- onFormDataChange?.(updatedComponents);
6586
- }, renderFormComponent: (field) => {
6587
- // Ensure the nested component gets the proper form value
6511
+ }, onValueChange: onFormValueChange, renderFormComponent: (field) => {
6588
6512
  return renderFormComponent(field);
6589
6513
  } }));
6590
6514
  case 'file':