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