df-ae-forms-package 1.0.98 → 1.1.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/dist/index.js CHANGED
@@ -4334,20 +4334,57 @@ const DraggableGridComponent = ({ component, selectedComponent, mode, onComponen
4334
4334
  e.currentTarget.style.backgroundColor = '#ef4444';
4335
4335
  }, children: jsxRuntime.jsx(lucideReact.Trash2, { size: 12 }) })] }))] }));
4336
4336
  };
4337
- const GridDropZone = ({ gridComponents, mode, onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, selectedComponent, renderFormComponent, gridId, formData = {}, formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChange, onAttachmentChange, columnView = false, shouldShowComponent }) => {
4337
+ // Sub-component for the drop zone within the grid
4338
+ const GridDropZone = ({ gridComponents, mode, onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, selectedComponent, renderFormComponent, gridId, formData, formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChange, onAttachmentChange, columnView, shouldShowComponent }) => {
4338
4339
  const { setNodeRef, isOver } = core.useDroppable({
4339
4340
  id: `grid-drop-zone-${gridId}`,
4340
- disabled: mode !== 'edit'
4341
+ disabled: mode !== 'edit',
4342
+ data: {
4343
+ isGridDropZone: true,
4344
+ gridId: gridId
4345
+ }
4346
+ });
4347
+ // Sensors for drag and drop
4348
+ const sensors = core.useSensors(core.useSensor(core.PointerSensor, {
4349
+ activationConstraint: {
4350
+ distance: 8,
4351
+ },
4352
+ }), core.useSensor(core.KeyboardSensor, {
4353
+ coordinateGetter: sortable.sortableKeyboardCoordinates,
4354
+ }));
4355
+ const handleDragEnd = ((event) => {
4356
+ const { active, over } = event;
4357
+ if (over && active.id !== over.id) {
4358
+ gridComponents.findIndex((item) => item.id === active.id);
4359
+ gridComponents.findIndex((item) => item.id === over.id);
4360
+ }
4341
4361
  });
4342
- return (jsxRuntime.jsx("div", { ref: setNodeRef, className: "grid-drop-zone", style: {
4343
- border: isOver ? '2px dashed #3b82f6' : '2px dashed #d1d5db',
4362
+ return (jsxRuntime.jsx("div", { ref: setNodeRef, className: `grid-drop-zone ${gridComponents.length === 0 ? 'empty' : ''}`, style: {
4363
+ border: isOver ? '2px dashed #3b82f6' : '1px dashed #d1d5db',
4344
4364
  borderRadius: '8px',
4345
4365
  padding: '16px',
4346
- backgroundColor: isOver ? 'var(--df-color-primary-light)' : 'var(--df-color-fb-container)',
4347
- minHeight: '120px',
4348
- transition: 'all 0.2s ease',
4349
- position: 'relative'
4350
- }, children: gridComponents.length > 0 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(sortable.SortableContext, { items: gridComponents.map(c => c.id), strategy: sortable.horizontalListSortingStrategy, children: jsxRuntime.jsx("div", { style: {
4366
+ backgroundColor: isOver ? 'var(--df-color-primary-light)' : '#f9fafb',
4367
+ minHeight: '100px',
4368
+ transition: 'all 0.2s ease'
4369
+ }, children: gridComponents.length === 0 ? (jsxRuntime.jsxs("div", { style: {
4370
+ textAlign: 'center',
4371
+ color: 'var(--df-color-text-light)',
4372
+ fontSize: '14px',
4373
+ padding: '40px 20px',
4374
+ display: 'flex',
4375
+ flexDirection: 'column',
4376
+ alignItems: 'center',
4377
+ gap: '8px',
4378
+ backgroundColor: 'var(--df-color-fb-container)',
4379
+ border: '1px dashed var(--df-color-fb-border)',
4380
+ borderRadius: '8px'
4381
+ }, children: [jsxRuntime.jsx("div", { style: {
4382
+ fontWeight: '500',
4383
+ color: isOver ? 'var(--df-color-primary)' : 'var(--df-color-text-dark)'
4384
+ }, children: isOver ? 'Drop components here' : 'Empty DataGrid' }), jsxRuntime.jsx("div", { style: {
4385
+ fontSize: '12px',
4386
+ color: '#9ca3af'
4387
+ }, children: "Drag and drop components here to create your grid" })] })) : (jsxRuntime.jsxs(core.DndContext, { sensors: sensors, onDragEnd: handleDragEnd, children: [jsxRuntime.jsx(sortable.SortableContext, { items: gridComponents.map(c => c.id), strategy: columnView ? sortable.verticalListSortingStrategy : sortable.horizontalListSortingStrategy, children: jsxRuntime.jsx("div", { style: {
4351
4388
  display: 'flex',
4352
4389
  flexDirection: columnView ? 'column' : 'row',
4353
4390
  flexWrap: 'nowrap',
@@ -4372,28 +4409,14 @@ const GridDropZone = ({ gridComponents, mode, onComponentSelect, onComponentDele
4372
4409
  minHeight: '40px',
4373
4410
  display: 'flex',
4374
4411
  alignItems: 'center',
4375
- justifyContent: 'center'
4376
- }, children: isOver ? (jsxRuntime.jsx("span", { style: { color: '#3b82f6', fontWeight: '500' }, children: "Drop component here to add to grid" })) : (jsxRuntime.jsx("span", { children: "+ Drop more components here" })) })] })) : (jsxRuntime.jsxs("div", { style: {
4377
- textAlign: 'center',
4378
- color: 'var(--df-color-text-light)',
4379
- fontSize: '14px',
4380
- padding: '40px 20px',
4381
- display: 'flex',
4382
- flexDirection: 'column',
4383
- alignItems: 'center',
4384
- gap: '8px',
4385
- backgroundColor: 'var(--df-color-fb-container)',
4386
- border: '1px dashed var(--df-color-fb-border)',
4387
- borderRadius: '8px'
4388
- }, children: [jsxRuntime.jsx("div", { style: {
4389
- fontWeight: '500',
4390
- color: isOver ? 'var(--df-color-primary)' : 'var(--df-color-text-dark)'
4391
- }, children: isOver ? 'Drop components here' : 'Empty DataGrid' }), jsxRuntime.jsx("div", { style: {
4392
- fontSize: '12px',
4393
- color: '#9ca3af'
4394
- }, children: "Drag and drop components here to create your grid" })] })) }));
4412
+ justifyContent: 'center',
4413
+ marginTop: '12px'
4414
+ }, children: isOver ? (jsxRuntime.jsx("span", { style: { color: '#3b82f6', fontWeight: '500' }, children: "Drop component here to add to grid" })) : (jsxRuntime.jsx("span", { children: "+ Drop more components here" })) })] })) }));
4395
4415
  };
4396
- 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 }) => {
4416
+ // Sub-component for displaying entries (TableView)
4417
+ const TableView = ({ templateComponents, dataEntries, renderFormComponent, mode, allowAddRemoveEntries, addAnotherText, removeText, maxEntries, minEntries, displayAsGrid = true, onAddEntry, onRemoveEntry, formData, // Use current formData to render values
4418
+ formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChange, onAttachmentChange, columnView, shouldShowComponent }) => {
4419
+ const _formData = formData || {};
4397
4420
  const visibleTemplateComponents = React.useMemo(() => {
4398
4421
  if (!shouldShowComponent)
4399
4422
  return templateComponents;
@@ -4455,21 +4478,23 @@ const TableView = ({ templateComponents, dataEntries, renderFormComponent, mode
4455
4478
  whiteSpace: 'nowrap',
4456
4479
  overflow: 'hidden',
4457
4480
  textOverflow: 'ellipsis'
4458
- }, children: component.basic?.label || `Column ${index + 1}` }, `header-${component.id}`))) })), dataEntries.length > 0 ? (dataEntries.map((entry, entryIndex) => (jsxRuntime.jsxs("div", { className: "table-row", style: {
4459
- display: 'grid',
4460
- // Change grid columns to 1fr for column view
4461
- gridTemplateColumns: columnView
4462
- ? '1fr'
4463
- : `repeat(${visibleTemplateComponents.length}, minmax(150px, 1fr))`,
4481
+ }, children: component.basic?.label || `Column ${index + 1}` }, `header-${component.id || index}`))) })), dataEntries.length > 0 ? (dataEntries.map((entry, entryIndex) => (jsxRuntime.jsxs("div", { className: "table-row", style: {
4482
+ // Use flex column for column view, grid for row view
4483
+ display: columnView ? 'flex' : 'grid',
4484
+ flexDirection: columnView ? 'column' : 'row',
4485
+ gridTemplateColumns: !columnView
4486
+ ? `repeat(${visibleTemplateComponents.length}, minmax(150px, 1fr))`
4487
+ : undefined,
4464
4488
  borderBottom: entryIndex < dataEntries.length - 1 ? '1px solid var(--df-color-fb-border)' : 'none',
4465
4489
  backgroundColor: entryIndex % 2 === 0 ? 'var(--df-color-fb-container)' : 'var(--df-color-fb-bg)',
4466
4490
  position: 'relative',
4467
- minWidth: columnView ? '100%' : `${visibleTemplateComponents.length * 150}px`
4491
+ minWidth: columnView ? '100%' : `${visibleTemplateComponents.length * 150}px`,
4492
+ padding: columnView ? '16px' : '0'
4468
4493
  }, children: [visibleTemplateComponents.map((templateComponent, componentIndex) => {
4469
4494
  let entryComponent = entry.components?.[componentIndex];
4470
4495
  if (!entryComponent) {
4471
4496
  // Use entry.id (which is unique) instead of entryIndex to prevent data collisions
4472
- const uniqueId = `${templateComponent.id}-${entry.id}-${componentIndex}`;
4497
+ const uniqueId = `${templateComponent.id || 'comp'}-${entry.id}-${componentIndex}`;
4473
4498
  entryComponent = {
4474
4499
  ...templateComponent,
4475
4500
  id: uniqueId,
@@ -4482,7 +4507,7 @@ const TableView = ({ templateComponents, dataEntries, renderFormComponent, mode
4482
4507
  else {
4483
4508
  entryComponent = {
4484
4509
  ...entryComponent,
4485
- id: entryComponent.id,
4510
+ id: entryComponent.id || `${templateComponent.id || 'comp'}-${entry.id}-${componentIndex}`,
4486
4511
  basic: {
4487
4512
  ...entryComponent.basic,
4488
4513
  showLabel: columnView // Show label in column view
@@ -4490,21 +4515,24 @@ const TableView = ({ templateComponents, dataEntries, renderFormComponent, mode
4490
4515
  };
4491
4516
  }
4492
4517
  return (jsxRuntime.jsx("div", { style: {
4493
- padding: '12px 16px',
4518
+ padding: columnView ? '12px 0' : '12px 16px',
4494
4519
  borderRight: !columnView && componentIndex < visibleTemplateComponents.length - 1 ? '1px solid var(--df-color-fb-border)' : 'none',
4495
4520
  // Add bottom border for fields in column view except the last one
4496
4521
  borderBottom: columnView && componentIndex < visibleTemplateComponents.length - 1 ? '1px dashed var(--df-color-fb-border)' : 'none',
4497
- minHeight: '60px',
4522
+ minHeight: columnView ? 'auto' : '60px',
4498
4523
  minWidth: columnView ? '100%' : '150px',
4524
+ width: columnView ? '100%' : 'auto',
4499
4525
  display: 'flex',
4500
- alignItems: 'center',
4501
- overflow: 'hidden'
4526
+ flexDirection: columnView ? 'column' : 'row',
4527
+ alignItems: columnView ? 'stretch' : 'center',
4528
+ overflow: 'hidden',
4529
+ gap: columnView ? '8px' : '0'
4502
4530
  }, children: jsxRuntime.jsx("div", { style: {
4503
4531
  width: '100%',
4504
- minWidth: '120px',
4532
+ minWidth: columnView ? '100%' : '120px',
4505
4533
  overflow: 'hidden'
4506
4534
  }, children: renderFormComponent(entryComponent, !columnView) }) }, `${entry.id}-${componentIndex}`));
4507
- }), mode === 'test' && allowAddRemoveEntries && dataEntries.length > minEntries && (jsxRuntime.jsx("button", { onClick: () => onRemoveEntry?.(entryIndex), disabled: dataEntries.length <= minEntries, style: {
4535
+ }), mode === 'test' && allowAddRemoveEntries && dataEntries.length > minEntries && (jsxRuntime.jsx("button", { onClick: () => onRemoveEntry(entryIndex), disabled: dataEntries.length <= minEntries, style: {
4508
4536
  position: 'absolute',
4509
4537
  top: '8px',
4510
4538
  right: '8px',
@@ -4538,7 +4566,7 @@ const TableView = ({ templateComponents, dataEntries, renderFormComponent, mode
4538
4566
  position: 'sticky',
4539
4567
  bottom: 0,
4540
4568
  zIndex: 5
4541
- }, children: jsxRuntime.jsxs("button", { onClick: onAddEntry, disabled: dataEntries.length >= maxEntries, style: {
4569
+ }, children: jsxRuntime.jsxs("button", { onClick: () => { console.log('[TableView] Add Entry button clicked (grid view)', 'entries:', dataEntries.length, 'max:', maxEntries); onAddEntry(); }, disabled: dataEntries.length >= maxEntries, style: {
4542
4570
  padding: '8px 16px',
4543
4571
  backgroundColor: dataEntries.length >= maxEntries ? '#f3f4f6' : '#10b981',
4544
4572
  color: dataEntries.length >= maxEntries ? '#9ca3af' : '#ffffff',
@@ -4576,7 +4604,7 @@ const TableView = ({ templateComponents, dataEntries, renderFormComponent, mode
4576
4604
  fontWeight: '600',
4577
4605
  color: 'var(--df-color-text-dark)',
4578
4606
  fontSize: '14px'
4579
- }, children: ["Entry #", entryIndex + 1] }), mode === 'test' && allowAddRemoveEntries && dataEntries.length > 1 && (jsxRuntime.jsx("button", { onClick: () => onRemoveEntry?.(entryIndex), disabled: dataEntries.length <= minEntries, style: {
4607
+ }, children: ["Entry #", entryIndex + 1] }), mode === 'test' && allowAddRemoveEntries && dataEntries.length > 1 && (jsxRuntime.jsx("button", { onClick: () => onRemoveEntry(entryIndex), disabled: dataEntries.length <= minEntries, style: {
4580
4608
  padding: '4px 8px',
4581
4609
  backgroundColor: dataEntries.length <= minEntries ? '#f3f4f6' : '#ef4444',
4582
4610
  color: dataEntries.length <= minEntries ? '#9ca3af' : '#ffffff',
@@ -4592,40 +4620,39 @@ const TableView = ({ templateComponents, dataEntries, renderFormComponent, mode
4592
4620
  height: '24px',
4593
4621
  justifyContent: 'center'
4594
4622
  }, title: removeText, children: jsxRuntime.jsx("span", { style: { fontSize: '14px' }, children: "\u00D7" }) }))] }), jsxRuntime.jsx("div", { style: {
4595
- display: 'grid',
4596
- gridTemplateColumns: columnView ? '1fr' : 'repeat(auto-fit, minmax(200px, 1fr))',
4623
+ display: columnView ? 'flex' : 'grid',
4624
+ flexDirection: columnView ? 'column' : 'row',
4625
+ gridTemplateColumns: !columnView ? 'repeat(auto-fit, minmax(200px, 1fr))' : undefined,
4597
4626
  gap: '16px'
4598
4627
  }, children: visibleTemplateComponents.map((templateComponent, componentIndex) => {
4599
- // Find the corresponding component in this entry by index first, then by name+label
4600
4628
  let entryComponent = entry.components?.[componentIndex];
4601
- // If no component at this index, try to find by name+label
4602
4629
  if (!entryComponent) {
4603
4630
  entryComponent = entry.components?.find((comp) => comp.name === templateComponent.name &&
4604
4631
  comp.basic?.label === templateComponent.basic?.label);
4605
4632
  }
4606
4633
  if (!entryComponent) {
4607
- // Use entry.id (which is unique) instead of entryIndex
4608
- const uniqueId = `${templateComponent.id}-${entry.id}-${componentIndex}`;
4634
+ const uniqueId = `${templateComponent.id || 'comp'}-${entry.id}-${componentIndex}`;
4609
4635
  entryComponent = {
4610
4636
  ...templateComponent,
4611
4637
  id: uniqueId,
4612
4638
  basic: {
4613
4639
  ...templateComponent.basic,
4614
- showLabel: false // Hide label in datagrid cells
4640
+ showLabel: columnView // Show label in column view, hide in grid view
4615
4641
  }
4616
4642
  };
4617
4643
  }
4618
4644
  else {
4619
- // Preserve the original ID to maintain form value connections
4620
4645
  entryComponent = {
4621
4646
  ...entryComponent,
4622
- id: entryComponent.id, // Keep the original ID
4647
+ id: entryComponent.id || `${templateComponent.id || 'comp'}-${entry.id}-${componentIndex}`,
4623
4648
  basic: {
4624
4649
  ...entryComponent.basic,
4625
- showLabel: false // Hide label in datagrid cells
4650
+ showLabel: columnView // Show label in column view, hide in grid view
4626
4651
  }
4627
4652
  };
4628
4653
  }
4654
+ if (!entryComponent)
4655
+ return null;
4629
4656
  return (jsxRuntime.jsxs("div", { style: {
4630
4657
  display: 'flex',
4631
4658
  flexDirection: 'column',
@@ -4661,7 +4688,7 @@ const TableView = ({ templateComponents, dataEntries, renderFormComponent, mode
4661
4688
  borderRadius: '8px',
4662
4689
  display: 'flex',
4663
4690
  justifyContent: 'center'
4664
- }, children: jsxRuntime.jsxs("button", { onClick: onAddEntry, disabled: dataEntries.length >= maxEntries, style: {
4691
+ }, children: jsxRuntime.jsxs("button", { onClick: () => { console.log('[TableView] Add Entry button clicked (list view)', 'entries:', dataEntries.length, 'max:', maxEntries); onAddEntry(); }, disabled: dataEntries.length >= maxEntries, style: {
4665
4692
  padding: '8px 16px',
4666
4693
  backgroundColor: dataEntries.length >= maxEntries ? '#f3f4f6' : '#10b981',
4667
4694
  color: dataEntries.length >= maxEntries ? '#9ca3af' : '#ffffff',
@@ -4674,14 +4701,21 @@ const TableView = ({ templateComponents, dataEntries, renderFormComponent, mode
4674
4701
  alignItems: 'center',
4675
4702
  gap: '8px',
4676
4703
  transition: 'all 0.2s ease'
4704
+ }, onMouseEnter: (e) => {
4705
+ if (dataEntries.length < maxEntries) {
4706
+ e.currentTarget.style.backgroundColor = '#059669';
4707
+ }
4708
+ }, onMouseLeave: (e) => {
4709
+ e.currentTarget.style.backgroundColor = '#10b981';
4677
4710
  }, children: [jsxRuntime.jsx("span", { children: "+" }), addAnotherText] }) }))] }));
4678
4711
  };
4679
4712
  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 }) => {
4680
4713
  const [isCollapsed, setIsCollapsed] = React.useState(false);
4681
4714
  const hasInitialized = React.useRef(false);
4682
4715
  // Get all components in the grid and sanitize them to ensure no data leaks into templates
4683
- let gridComponents = (properties.templateComponents || []).map(comp => ({
4716
+ let gridComponents = (properties.templateComponents || []).map((comp, index) => ({
4684
4717
  ...comp,
4718
+ id: comp.id || `${id}-template-${index}`, // CRITICAL: Ensure template components have stable IDs
4685
4719
  basic: {
4686
4720
  ...comp.basic,
4687
4721
  // Use an empty string instead of undefined to satisfy component typing
@@ -4692,9 +4726,10 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4692
4726
  const dataEntries = properties.entries || [];
4693
4727
  // Fallback: If no template components but we have entries, extract template from first entry
4694
4728
  if (gridComponents.length === 0 && dataEntries.length > 0 && dataEntries[0].components) {
4695
- gridComponents = dataEntries[0].components.map((comp) => ({
4729
+ gridComponents = dataEntries[0].components.map((comp, index) => ({
4696
4730
  ...comp,
4697
- id: comp.id?.replace(/-entry-\d+$/, '') || comp.id, // Remove entry suffix for template
4731
+ // Remove entry suffix for template, use stable ID if missing
4732
+ id: comp.id?.replace(/-entry-.*$/, '') || `${id}-template-${index}`,
4698
4733
  basic: {
4699
4734
  ...comp.basic,
4700
4735
  // Clear any data values while keeping types happy
@@ -4762,12 +4797,9 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4762
4797
  // Check if entry is missing any template components or if existing components need updates
4763
4798
  return gridComponents.some((templateComp, componentIndex) => {
4764
4799
  const existingComponent = entry.components?.[componentIndex];
4765
- if (!existingComponent) {
4766
- return true; // Missing component at this index
4800
+ if (!existingComponent || !existingComponent.id) {
4801
+ return true; // Missing component or missing ID at this index
4767
4802
  }
4768
- // We don't check for ID matches here anymore
4769
- // const expectedId = `${templateComp.id}-entry-${entry.index}-${componentIndex}`
4770
- // const hasProperId = existingComponent.id === expectedId
4771
4803
  // Check if existing component needs to be updated with new template properties
4772
4804
  // Compare key properties that should be synced
4773
4805
  const needsPropertyUpdate = JSON.stringify(existingComponent.basic?.options) !== JSON.stringify(templateComp.basic?.options) ||
@@ -4775,7 +4807,6 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4775
4807
  existingComponent.basic?.defaultValue !== templateComp.basic?.defaultValue ||
4776
4808
  existingComponent.basic?.label !== templateComp.basic?.label ||
4777
4809
  existingComponent.validation?.required !== templateComp.validation?.required;
4778
- // We do not check for ID mismatch anymore as we want to preserve unique IDs
4779
4810
  return needsPropertyUpdate;
4780
4811
  });
4781
4812
  });
@@ -4796,7 +4827,8 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4796
4827
  // Update existing component with new template properties while ensuring unique ID and preserving form values
4797
4828
  const updatedComponent = {
4798
4829
  ...templateComp,
4799
- id: existingComponent.id, // Preserve existing ID !!
4830
+ // Preserve existing ID or generate one if missing
4831
+ id: existingComponent.id || `${templateComp.id}-${entry.id}-${componentIndex}`,
4800
4832
  basic: {
4801
4833
  ...templateComp.basic,
4802
4834
  showLabel: false, // Hide label in datagrid cells
@@ -4905,6 +4937,10 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4905
4937
  // In edit/preview modes, don't handle value changes as components are read-only
4906
4938
  }, [mode, onValueChange]);
4907
4939
  const handleAddEntry = React.useCallback(() => {
4940
+ console.log('[DfFormDataGrid] handleAddEntry called');
4941
+ console.log('[DfFormDataGrid] gridComponents:', gridComponents.length, gridComponents.map(c => c.id));
4942
+ console.log('[DfFormDataGrid] current entries:', properties.entries?.length);
4943
+ console.log('[DfFormDataGrid] onValueChange exists:', !!onValueChange);
4908
4944
  // Use timestamp and random string to ensure uniqueness even if entries are deleted
4909
4945
  const uniqueSuffix = `${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
4910
4946
  const newEntryId = `entry-${uniqueSuffix}`;
@@ -4926,13 +4962,19 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
4926
4962
  }),
4927
4963
  styles: {}
4928
4964
  };
4965
+ console.log('[DfFormDataGrid] newEntry created:', newEntry.id, 'with', newEntry.components.length, 'components');
4929
4966
  const updatedEntries = [...properties.entries, newEntry];
4967
+ console.log('[DfFormDataGrid] updatedEntries count:', updatedEntries.length);
4930
4968
  if (onValueChange) {
4969
+ console.log('[DfFormDataGrid] calling onValueChange with updated entries');
4931
4970
  onValueChange({
4932
4971
  id,
4933
4972
  value: { ...properties, entries: updatedEntries }
4934
4973
  });
4935
4974
  }
4975
+ else {
4976
+ console.log('[DfFormDataGrid] WARNING: onValueChange is not defined!');
4977
+ }
4936
4978
  onEntryAdd?.();
4937
4979
  }, [properties, onValueChange, id, onEntryAdd, gridComponents]);
4938
4980
  const handleRemoveEntry = React.useCallback((entryIndex) => {
@@ -5029,7 +5071,7 @@ const DfFormDataGrid = ({ id, properties, mode = 'edit', formData = {}, onValueC
5029
5071
  , {
5030
5072
  // Cast to FormComponentType[] to satisfy TableView typing; gridComponents
5031
5073
  // are always child form components of the datagrid.
5032
- 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 })) }))] }));
5074
+ 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 })) }))] }));
5033
5075
  };
5034
5076
 
5035
5077
  const DraggableChild = ({ child, selectedChild, mode, onChildSelect, onChildDelete, renderFormComponent, isOverlay = false, isChildrenEditMode = false, formData = {}, formTemplateId, onThresholdActionCompletion, onThresholdIssueRaised, onNotesChange, onAttachmentChange, workOrderNumber, assetNumber, user, onCreateIssue, onUpdateIssue }) => {
@@ -5420,7 +5462,7 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5420
5462
  });
5421
5463
  };
5422
5464
  // Validate component IDs for uniqueness without mutating original props
5423
- const getValidatedComponents = (components) => {
5465
+ const getValidatedComponents = (components, parentId) => {
5424
5466
  return components.map((component, index) => {
5425
5467
  let validatedComponent = { ...component };
5426
5468
  // Ensure component has an ID
@@ -5429,33 +5471,42 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5429
5471
  const name = validatedComponent.name || 'component';
5430
5472
  const label = validatedComponent.basic?.label || '';
5431
5473
  const safeLabel = label.replace(/[^a-zA-Z0-9]/g, '').toLowerCase().substring(0, 10);
5432
- // Generate a unique ID
5433
- validatedComponent.id = `${name}-${safeLabel || 'gen'}-${Date.now()}-${Math.random().toString(36).substr(2, 6)}-${index}`;
5474
+ // Generate a stable unique ID if parentId is provided, otherwise fallback to semi-stable
5475
+ if (parentId) {
5476
+ validatedComponent.id = `${parentId}-${name}-${index}`;
5477
+ }
5478
+ else {
5479
+ // For root components, use label if available for stability
5480
+ const labelPart = safeLabel ? `-${safeLabel}` : '';
5481
+ validatedComponent.id = `${name}${labelPart}-${index}`;
5482
+ }
5434
5483
  console.warn(`[DfFormPreview] Assigned missing ID: ${validatedComponent.id}`);
5435
5484
  }
5436
5485
  else {
5437
5486
  // Check for duplicates
5438
5487
  if (seenIds.has(validatedComponent.id)) {
5439
5488
  console.error(`[DfFormPreview] Duplicate component ID detected: ${validatedComponent.id}`);
5440
- // Generate a unique ID for duplicate
5441
- validatedComponent.id = `${validatedComponent.id}-duplicate-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
5489
+ // Generate a unique ID for duplicate - using index to keep it somewhat stable
5490
+ validatedComponent.id = `${validatedComponent.id}-dup-${index}`;
5442
5491
  console.warn(`[DfFormPreview] Generated new unique ID: ${validatedComponent.id}`);
5443
5492
  }
5444
5493
  }
5445
5494
  seenIds.add(validatedComponent.id);
5446
5495
  // Recursively validate nested components
5447
5496
  if (validatedComponent.children && Array.isArray(validatedComponent.children)) {
5448
- validatedComponent.children = getValidatedComponents(validatedComponent.children);
5497
+ validatedComponent.children = getValidatedComponents(validatedComponent.children, validatedComponent.id);
5449
5498
  }
5450
5499
  if (validatedComponent.cells && Array.isArray(validatedComponent.cells)) {
5451
- validatedComponent.cells = validatedComponent.cells.map((row) => {
5500
+ validatedComponent.cells = validatedComponent.cells.map((row, rowIndex) => {
5452
5501
  const normalizedRow = normalizeTableRow(row);
5453
5502
  if (normalizedRow.length > 0) {
5454
- return normalizedRow.map((cell) => {
5503
+ return normalizedRow.map((cell, cellIndex) => {
5455
5504
  if (cell && cell.components && Array.isArray(cell.components)) {
5505
+ // Stable ID for table cells: tableId-row-X-cell-Y
5506
+ const cellContextId = `${validatedComponent.id}-row-${rowIndex}-cell-${cellIndex}`;
5456
5507
  return {
5457
5508
  ...cell,
5458
- components: getValidatedComponents(cell.components)
5509
+ components: getValidatedComponents(cell.components, cellContextId)
5459
5510
  };
5460
5511
  }
5461
5512
  return cell;
@@ -5467,9 +5518,10 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
5467
5518
  if (validatedComponent.entries && Array.isArray(validatedComponent.entries)) {
5468
5519
  validatedComponent.entries = validatedComponent.entries.map((entry) => {
5469
5520
  if (entry && entry.components && Array.isArray(entry.components)) {
5521
+ // Stable ID for datagrid entries: datagridId-entryId or using entry.id
5470
5522
  return {
5471
5523
  ...entry,
5472
- components: getValidatedComponents(entry.components)
5524
+ components: getValidatedComponents(entry.components, entry.id || `${validatedComponent.id}-entry-${entry.index}`)
5473
5525
  };
5474
5526
  }
5475
5527
  return entry;
@@ -6452,12 +6504,9 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6452
6504
  onFormDataChange?.(updatedComponents);
6453
6505
  } }) }));
6454
6506
  case 'datagrid':
6455
- // Align package datagrid wiring with main app behaviour:
6456
- // - Let DfFormDataGrid manage entry structure via onValueChange
6457
- // - Use the shared onFormValueChange handler for nested field values
6458
- // - Keep notes/attachments wiring as before
6459
6507
  return (jsxRuntime.jsx(DfFormDataGrid, { ...commonProps, properties: component, formData: formValues, formTemplateId: formTemplateId, mode: commonProps.mode, onThresholdActionCompletion: handleThresholdActionCompletion, onThresholdIssueRaised: handleThresholdIssueRaised, onNotesChange: (componentId, notes) => {
6460
6508
  handleComponentNotesChange(componentId, notes);
6509
+ // Handle notes change for datagrid entry components
6461
6510
  const updatedComponents = localFormComponents.map(comp => {
6462
6511
  if (comp.id === component.id && comp.entries) {
6463
6512
  const updatedEntries = comp.entries.map((entry) => {
@@ -6485,6 +6534,7 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6485
6534
  onFormDataChange?.(updatedComponents);
6486
6535
  }, onAttachmentChange: (componentId, attachments) => {
6487
6536
  handleComponentAttachmentChange(componentId, attachments);
6537
+ // Handle attachment change for datagrid entry components
6488
6538
  const updatedComponents = localFormComponents.map(comp => {
6489
6539
  if (comp.id === component.id && comp.entries) {
6490
6540
  const updatedEntries = comp.entries.map((entry) => {
@@ -6510,7 +6560,116 @@ onComponentSelect, onComponentDelete, onComponentEdit, onComponentUpdate, select
6510
6560
  return comp;
6511
6561
  });
6512
6562
  onFormDataChange?.(updatedComponents);
6513
- }, onValueChange: onFormValueChange, renderFormComponent: (field) => {
6563
+ }, onValueChange: (change) => {
6564
+ console.log('[DfFormPreview] datagrid onValueChange received:', change.id, 'hasEntries:', change.value && typeof change.value === 'object' && 'entries' in change.value);
6565
+ // Handle datagrid value changes (entries updates)
6566
+ if (change.id === component.id && change.value && typeof change.value === 'object' && 'entries' in change.value) {
6567
+ console.log('[DfFormPreview] datagrid entries update - entries count:', change.value.entries?.length);
6568
+ // Update localFormComponents with new entries structure
6569
+ const updatedComponents = localFormComponents.map(comp => {
6570
+ if (comp.id === component.id) {
6571
+ return {
6572
+ ...comp,
6573
+ ...change.value
6574
+ };
6575
+ }
6576
+ return comp;
6577
+ });
6578
+ // CRITICAL: Update local state immediately so new entries render without Angular round-trip
6579
+ setLocalFormComponents(updatedComponents);
6580
+ onFormDataChange?.(updatedComponents);
6581
+ // Also update formValues for nested components
6582
+ if (change.value.entries && Array.isArray(change.value.entries)) {
6583
+ change.value.entries.forEach((entry) => {
6584
+ if (entry.components && Array.isArray(entry.components)) {
6585
+ entry.components.forEach((nestedComp) => {
6586
+ const nestedValue = formValues[nestedComp.id];
6587
+ if (nestedValue !== undefined) ;
6588
+ else {
6589
+ // Initialize with defaultValue if available
6590
+ const defaultValue = nestedComp.basic?.defaultValue;
6591
+ if (defaultValue !== undefined) {
6592
+ setFormValues(prev => ({
6593
+ ...prev,
6594
+ [nestedComp.id]: defaultValue
6595
+ }));
6596
+ }
6597
+ }
6598
+ });
6599
+ }
6600
+ });
6601
+ }
6602
+ }
6603
+ else {
6604
+ // For nested component value changes, use the regular handler
6605
+ onFormValueChange(change);
6606
+ }
6607
+ }, onEntryAdd: () => {
6608
+ // CRITICAL: Entry has already been added via onValueChange in DfFormDataGrid
6609
+ // Get the updated component from localFormComponents (which should have been updated by onValueChange)
6610
+ const currentComponent = localFormComponents.find(comp => comp.id === component.id);
6611
+ if (currentComponent && currentComponent.entries) {
6612
+ // Entry should already be in the component via onValueChange
6613
+ // Just ensure localFormComponents is in sync (no-op if already synced)
6614
+ const updatedComponents = localFormComponents.map(comp => {
6615
+ if (comp.id === component.id) {
6616
+ // Ensure entries are properly structured
6617
+ return {
6618
+ ...comp,
6619
+ entries: comp.entries || []
6620
+ };
6621
+ }
6622
+ return comp;
6623
+ });
6624
+ onFormDataChange?.(updatedComponents);
6625
+ }
6626
+ else {
6627
+ // Fallback: If component doesn't have entries yet, try to get from formValues
6628
+ setTimeout(() => {
6629
+ const datagridValue = formValues[component.id];
6630
+ if (datagridValue && typeof datagridValue === 'object' && 'entries' in datagridValue) {
6631
+ const updatedComponents = localFormComponents.map(comp => {
6632
+ if (comp.id === component.id) {
6633
+ return {
6634
+ ...comp,
6635
+ entries: datagridValue.entries
6636
+ };
6637
+ }
6638
+ return comp;
6639
+ });
6640
+ onFormDataChange?.(updatedComponents);
6641
+ }
6642
+ }, 100);
6643
+ }
6644
+ }, onEntryRemove: (entryIndex) => {
6645
+ // Handle entry remove - update form components
6646
+ const updatedComponents = localFormComponents.map(comp => {
6647
+ if (comp.id === component.id && comp.entries) {
6648
+ const currentEntries = comp.entries || [];
6649
+ const updatedEntries = currentEntries
6650
+ .filter((_, index) => index !== entryIndex)
6651
+ .map((entry, index) => ({
6652
+ ...entry,
6653
+ index,
6654
+ id: `entry-${comp.id}-${index}`,
6655
+ components: entry.components?.map((comp, compIndex) => {
6656
+ const templateComp = (comp.templateComponents || [])[compIndex];
6657
+ return {
6658
+ ...comp,
6659
+ id: templateComp ? `${templateComp.id}-entry-${index}-${compIndex}` : comp.id
6660
+ };
6661
+ }) || []
6662
+ }));
6663
+ return {
6664
+ ...comp,
6665
+ entries: updatedEntries
6666
+ };
6667
+ }
6668
+ return comp;
6669
+ });
6670
+ onFormDataChange?.(updatedComponents);
6671
+ }, renderFormComponent: (field) => {
6672
+ // Ensure the nested component gets the proper form value
6514
6673
  return renderFormComponent(field);
6515
6674
  } }));
6516
6675
  case 'file':