@visns-studio/visns-components 5.15.29 → 5.15.31

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.
@@ -6,6 +6,7 @@ import StandardModal from './StandardModal';
6
6
  import { arrayMove } from '@dnd-kit/sortable';
7
7
  import {
8
8
  DndContext,
9
+ DragOverlay,
9
10
  closestCenter,
10
11
  KeyboardSensor,
11
12
  PointerSensor,
@@ -33,6 +34,10 @@ import {
33
34
  Trash2 as TrashCan,
34
35
  Edit as Pencil,
35
36
  GripVertical as ChevronVertical,
37
+ ArrowUpDown,
38
+ Search,
39
+ ChevronDown,
40
+ ChevronRight,
36
41
  } from 'lucide-react';
37
42
  import { confirmDialog } from '../utils/ConfirmDialog';
38
43
 
@@ -107,6 +112,16 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
107
112
  const [modalFormShow, setModalFormShow] = useState(false);
108
113
  const [roles, setRoles] = useState([]);
109
114
  const [hoveredField, setHoveredField] = useState(null);
115
+ const [activeId, setActiveId] = useState(null);
116
+ const [searchQuery, setSearchQuery] = useState('');
117
+ const [moveToDialog, setMoveToDialog] = useState({
118
+ show: false,
119
+ fieldId: null,
120
+ currentPos: 0,
121
+ });
122
+ const [moveToPosition, setMoveToPosition] = useState('');
123
+ const [collapsedSections, setCollapsedSections] = useState(new Set());
124
+ const sortTriggeredRef = useRef(false);
110
125
 
111
126
  const canvasTypes = SketchConfig.canvasTypes;
112
127
 
@@ -203,6 +218,57 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
203
218
  return tooltip.trim();
204
219
  };
205
220
 
221
+ // Get field IDs that should be collapsed (under collapsed sections)
222
+ const getCollapsedFieldIds = () => {
223
+ if (!data.detail || collapsedSections.size === 0) return new Set();
224
+ const collapsed = new Set();
225
+ let currentlyCollapsed = false;
226
+ for (const field of data.detail) {
227
+ if (field.type === 'section') {
228
+ currentlyCollapsed = collapsedSections.has(field.id);
229
+ } else if (currentlyCollapsed) {
230
+ collapsed.add(field.id);
231
+ }
232
+ }
233
+ return collapsed;
234
+ };
235
+
236
+ const toggleSectionCollapse = (sectionId) => {
237
+ setCollapsedSections((prev) => {
238
+ const newSet = new Set(prev);
239
+ if (newSet.has(sectionId)) {
240
+ newSet.delete(sectionId);
241
+ } else {
242
+ newSet.add(sectionId);
243
+ }
244
+ return newSet;
245
+ });
246
+ };
247
+
248
+ const handleMoveTo = (fieldId, targetPosition) => {
249
+ const currentIndex = data.detail.findIndex(
250
+ (item) => item.id === fieldId
251
+ );
252
+ const targetIndex = targetPosition - 1;
253
+ if (
254
+ currentIndex !== -1 &&
255
+ targetIndex >= 0 &&
256
+ targetIndex < data.detail.length &&
257
+ currentIndex !== targetIndex
258
+ ) {
259
+ sortTriggeredRef.current = true;
260
+ const newData = arrayMove(data.detail, currentIndex, targetIndex);
261
+ setData((items) => ({
262
+ ...items,
263
+ detail: newData,
264
+ }));
265
+ }
266
+ setMoveToDialog({ show: false, fieldId: null, currentPos: 0 });
267
+ setMoveToPosition('');
268
+ };
269
+
270
+ const collapsedFieldIds = getCollapsedFieldIds();
271
+
206
272
  // Create sortable field item using @dnd-kit
207
273
  const SortableFieldItem = ({ field, index: counter }) => {
208
274
  const {
@@ -224,38 +290,73 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
224
290
  const style = {
225
291
  transform: CSS.Transform.toString(transform),
226
292
  transition: isDragging ? 'none' : transition,
227
- opacity: isDragging ? 0.5 : 1,
228
293
  minHeight: '64.2px',
229
294
  position: 'relative',
230
295
  zIndex: isDragging ? 1000 : 'auto',
231
- // Prevent stretching during drag by setting responsive dimensions
232
- ...(isDragging && {
233
- width:
234
- field.size === 'full'
235
- ? 'min(600px, 90vw)'
236
- : 'min(300px, 45vw)',
237
- maxWidth: field.size === 'full' ? '800px' : '400px',
238
- flexShrink: 0,
239
- flexGrow: 0,
240
- display: 'block',
241
- }),
242
296
  };
243
297
 
244
298
  const fieldClassName = renderClassName(field);
245
299
 
300
+ // Search dimming
301
+ const isDimmed =
302
+ searchQuery &&
303
+ !(
304
+ field.label
305
+ ?.toLowerCase()
306
+ .includes(searchQuery.toLowerCase()) ||
307
+ field.type
308
+ ?.toLowerCase()
309
+ .includes(searchQuery.toLowerCase()) ||
310
+ field.id
311
+ ?.toLowerCase()
312
+ .includes(searchQuery.toLowerCase())
313
+ );
314
+
315
+ // Collapse logic
316
+ const isCollapsed = collapsedFieldIds.has(field.id);
317
+ const isSection = field.type === 'section';
318
+ const isSectionCollapsed =
319
+ isSection && collapsedSections.has(field.id);
320
+
321
+ // When dragging, show a dashed placeholder
322
+ if (isDragging) {
323
+ return (
324
+ <div
325
+ ref={setNodeRef}
326
+ style={style}
327
+ className={`${fieldClassName} ${styles.dragPlaceholder}`}
328
+ data-field-size={field.size}
329
+ />
330
+ );
331
+ }
332
+
246
333
  return (
247
334
  <div
248
335
  ref={setNodeRef}
249
- style={style}
336
+ style={{
337
+ ...style,
338
+ opacity: isDimmed ? 0.25 : 1,
339
+ }}
250
340
  className={`${fieldClassName} ${styles.interactiveField} ${
251
341
  styles.previewField
252
- } ${isDragging ? styles.dragging : ''}`}
253
- data-tooltip-id={hoveredField === field.id ? null : "field-tooltip"}
254
- data-tooltip-content={hoveredField === field.id ? null : getFieldTooltip(field)}
342
+ } ${isCollapsed ? styles.collapsedField : ''}`}
343
+ data-tooltip-id={
344
+ hoveredField === field.id ? null : 'field-tooltip'
345
+ }
346
+ data-tooltip-content={
347
+ hoveredField === field.id
348
+ ? null
349
+ : getFieldTooltip(field)
350
+ }
255
351
  data-tooltip-place="top"
256
352
  data-tooltip-delay-show={200}
257
353
  data-field-size={field.size}
258
354
  >
355
+ {/* Field index badge */}
356
+ <span className={styles.fieldIndex}>
357
+ #{counter + 1}
358
+ </span>
359
+
259
360
  {/* Field info - always visible */}
260
361
  <div className={styles.fieldInfo}>
261
362
  <span className={styles.fieldInfoText}>
@@ -263,8 +364,12 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
263
364
  </span>
264
365
  </div>
265
366
 
266
- {/* Field controls - only visible on hover */}
267
- <div className={styles.fieldControls}>
367
+ {/* Field controls - drag handle always visible */}
368
+ <div
369
+ className={styles.fieldControls}
370
+ onMouseEnter={() => setHoveredField(field.id)}
371
+ onMouseLeave={() => setHoveredField(null)}
372
+ >
268
373
  <div
269
374
  {...attributes}
270
375
  {...listeners}
@@ -272,12 +377,59 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
272
377
  data-tooltip-id="action-tooltip"
273
378
  data-tooltip-content="Drag to Reorder"
274
379
  data-tooltip-place="top"
275
- onMouseEnter={() => setHoveredField(field.id)}
276
- onMouseLeave={() => setHoveredField(null)}
277
380
  >
278
381
  <ChevronVertical strokeWidth={3} size={24} />
279
382
  </div>
280
383
  <div className={styles.fieldActions}>
384
+ {isSection && (
385
+ <button
386
+ className={styles.collapseToggle}
387
+ onClick={(e) => {
388
+ e.preventDefault();
389
+ e.stopPropagation();
390
+ toggleSectionCollapse(field.id);
391
+ }}
392
+ data-tooltip-id="action-tooltip"
393
+ data-tooltip-content={
394
+ isSectionCollapsed
395
+ ? 'Expand Section'
396
+ : 'Collapse Section'
397
+ }
398
+ data-tooltip-place="top"
399
+ >
400
+ {isSectionCollapsed ? (
401
+ <ChevronRight
402
+ strokeWidth={3}
403
+ size={20}
404
+ />
405
+ ) : (
406
+ <ChevronDown
407
+ strokeWidth={3}
408
+ size={20}
409
+ />
410
+ )}
411
+ </button>
412
+ )}
413
+ <ArrowUpDown
414
+ className={styles.moveIcon}
415
+ strokeWidth={3}
416
+ size={20}
417
+ onClick={(e) => {
418
+ e.preventDefault();
419
+ e.stopPropagation();
420
+ setMoveToDialog({
421
+ show: true,
422
+ fieldId: field.id,
423
+ currentPos: counter + 1,
424
+ });
425
+ setMoveToPosition(
426
+ String(counter + 1)
427
+ );
428
+ }}
429
+ data-tooltip-id="action-tooltip"
430
+ data-tooltip-content="Move to Position"
431
+ data-tooltip-place="top"
432
+ />
281
433
  <Pencil
282
434
  className={styles.editIcon}
283
435
  strokeWidth={3}
@@ -290,8 +442,6 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
290
442
  data-tooltip-id="action-tooltip"
291
443
  data-tooltip-content="Edit Field"
292
444
  data-tooltip-place="top"
293
- onMouseEnter={() => setHoveredField(field.id)}
294
- onMouseLeave={() => setHoveredField(null)}
295
445
  />
296
446
  <TrashCan
297
447
  className={styles.deleteIcon}
@@ -300,17 +450,20 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
300
450
  onClick={(e) => {
301
451
  e.preventDefault();
302
452
  e.stopPropagation();
303
- handleDeleteField(field.id, field.label);
453
+ handleDeleteField(
454
+ field.id,
455
+ field.label
456
+ );
304
457
  }}
305
458
  data-tooltip-id="action-tooltip"
306
459
  data-tooltip-content="Delete Field"
307
460
  data-tooltip-place="top"
308
- onMouseEnter={() => setHoveredField(field.id)}
309
- onMouseLeave={() => setHoveredField(null)}
310
461
  />
311
462
  </div>
312
463
  </div>
313
- <div className={styles.fieldContent}>{renderField(field)}</div>
464
+ <div className={styles.fieldContent}>
465
+ {renderField(field)}
466
+ </div>
314
467
  </div>
315
468
  );
316
469
  };
@@ -462,7 +615,16 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
462
615
  return closestCenter(args);
463
616
  };
464
617
 
618
+ const handleDragStart = (event) => {
619
+ setActiveId(event.active.id);
620
+ };
621
+
622
+ const handleDragCancel = () => {
623
+ setActiveId(null);
624
+ };
625
+
465
626
  const handleDragEnd = (event) => {
627
+ setActiveId(null);
466
628
  const { active, over } = event;
467
629
 
468
630
  if (active.id !== over?.id && over) {
@@ -474,6 +636,7 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
474
636
  );
475
637
 
476
638
  if (oldIndex !== -1 && newIndex !== -1) {
639
+ sortTriggeredRef.current = true;
477
640
  const newData = arrayMove(data.detail, oldIndex, newIndex);
478
641
 
479
642
  setData((items) => ({
@@ -1125,7 +1288,12 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
1125
1288
  };
1126
1289
 
1127
1290
  useEffect(() => {
1128
- if (data.detail && data.detail.length > 0) {
1291
+ if (
1292
+ data.detail &&
1293
+ data.detail.length > 0 &&
1294
+ sortTriggeredRef.current
1295
+ ) {
1296
+ sortTriggeredRef.current = false;
1129
1297
  saveOnSort();
1130
1298
  }
1131
1299
  }, [data.detail]);
@@ -1162,6 +1330,56 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
1162
1330
  : 'Form Template Preview'}
1163
1331
  </span>
1164
1332
  </div>
1333
+ {data.detail && data.detail.length > 5 && (
1334
+ <div className={styles.searchBar}>
1335
+ <Search
1336
+ size={16}
1337
+ className={styles.searchIcon}
1338
+ />
1339
+ <input
1340
+ type="text"
1341
+ placeholder="Search fields by label, type, or ID..."
1342
+ value={searchQuery}
1343
+ onChange={(e) =>
1344
+ setSearchQuery(e.target.value)
1345
+ }
1346
+ className={styles.searchInput}
1347
+ />
1348
+ {searchQuery && (
1349
+ <span className={styles.searchCount}>
1350
+ {
1351
+ data.detail.filter(
1352
+ (f) =>
1353
+ f.label
1354
+ ?.toLowerCase()
1355
+ .includes(
1356
+ searchQuery.toLowerCase()
1357
+ ) ||
1358
+ f.type
1359
+ ?.toLowerCase()
1360
+ .includes(
1361
+ searchQuery.toLowerCase()
1362
+ ) ||
1363
+ f.id
1364
+ ?.toLowerCase()
1365
+ .includes(
1366
+ searchQuery.toLowerCase()
1367
+ )
1368
+ ).length
1369
+ }{' '}
1370
+ of {data.detail.length}
1371
+ </span>
1372
+ )}
1373
+ {searchQuery && (
1374
+ <button
1375
+ className={styles.searchClear}
1376
+ onClick={() => setSearchQuery('')}
1377
+ >
1378
+ <CircleX size={16} />
1379
+ </button>
1380
+ )}
1381
+ </div>
1382
+ )}
1165
1383
  {data.detail && data.detail.length > 0 ? (
1166
1384
  <div className={styles.modal__content}>
1167
1385
  <div className={styles.formcontainer}>
@@ -1170,7 +1388,15 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
1170
1388
  collisionDetection={
1171
1389
  customCollisionDetection
1172
1390
  }
1391
+ onDragStart={handleDragStart}
1173
1392
  onDragEnd={handleDragEnd}
1393
+ onDragCancel={handleDragCancel}
1394
+ autoScroll={{
1395
+ threshold: {
1396
+ x: 0.2,
1397
+ y: 0.15,
1398
+ },
1399
+ }}
1174
1400
  >
1175
1401
  <SortableContext
1176
1402
  items={data.detail.map(
@@ -1192,6 +1418,39 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
1192
1418
  )}
1193
1419
  </div>
1194
1420
  </SortableContext>
1421
+ <DragOverlay>
1422
+ {activeId
1423
+ ? (() => {
1424
+ const activeField =
1425
+ data.detail.find(
1426
+ (f) =>
1427
+ f.id ===
1428
+ activeId
1429
+ );
1430
+ if (!activeField)
1431
+ return null;
1432
+ const overlayClassName =
1433
+ renderClassName(
1434
+ activeField
1435
+ );
1436
+ return (
1437
+ <div
1438
+ className={`${overlayClassName} ${styles.dragOverlayItem}`}
1439
+ >
1440
+ <div
1441
+ className={
1442
+ styles.fieldContent
1443
+ }
1444
+ >
1445
+ {renderField(
1446
+ activeField
1447
+ )}
1448
+ </div>
1449
+ </div>
1450
+ );
1451
+ })()
1452
+ : null}
1453
+ </DragOverlay>
1195
1454
  </DndContext>
1196
1455
  </div>
1197
1456
  </div>
@@ -1545,146 +1804,169 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
1545
1804
  dataField.type
1546
1805
  ) ? (
1547
1806
  <div className={styles.formItemFull}>
1548
- <table
1549
- className={styles.optionsTable}
1807
+ <div
1808
+ className={
1809
+ styles.optionsHeader
1810
+ }
1550
1811
  >
1551
- <thead>
1552
- <tr>
1553
- <th>
1554
- {dataField.type ===
1555
- 'table_text' ||
1556
- dataField.type ===
1557
- 'table_radio'
1558
- ? 'Column Header'
1559
- : 'Label'}
1560
- </th>
1561
- <th
1562
- style={{
1563
- width: '80px',
1564
- }}
1565
- >
1566
- Actions
1567
- </th>
1568
- </tr>
1569
- </thead>
1570
- <tbody>
1571
- {dataField.options.length >
1572
- 0 ? (
1573
- dataField.options.map(
1574
- (
1575
- option,
1576
- optionKey
1577
- ) => (
1578
- <tr
1579
- key={`option-${optionKey}`}
1580
- >
1581
- <td>
1582
- <input
1583
- type="text"
1584
- value={
1585
- option.label
1586
- }
1587
- onChange={(
1588
- e
1589
- ) => {
1590
- const newOptions =
1591
- [
1592
- ...dataField.options,
1593
- ];
1594
- newOptions[
1595
- optionKey
1596
- ].label =
1597
- e.target.value;
1598
- newOptions[
1599
- optionKey
1600
- ].id =
1601
- `${slugify(e.target.value)}-`;
1602
- setDataField(
1603
- (
1604
- items
1605
- ) => ({
1606
- ...items,
1607
- options:
1608
- newOptions,
1609
- })
1610
- );
1611
- }}
1612
- />
1613
- </td>
1614
- <td>
1615
- <button
1616
- className={`${styles.tableActionBtn} ${styles.delete}`}
1617
- onClick={
1618
- handleDeleteOption
1619
- }
1620
- data-counter={
1621
- optionKey
1622
- }
1623
- data-tooltip-id="system-tooltip"
1624
- data-tooltip-content={
1625
- 'Delete Option'
1626
- }
1627
- >
1628
- <TrashCan
1629
- strokeWidth={
1630
- 2
1631
- }
1632
- size={
1633
- 14
1634
- }
1635
- />
1636
- </button>
1637
- </td>
1638
- </tr>
1639
- )
1640
- )
1641
- ) : (
1642
- <tr>
1643
- <td colSpan="2">
1644
- No entry have
1645
- been added.
1646
- </td>
1647
- </tr>
1648
- )}
1649
- </tbody>
1650
- <tfoot>
1651
- <tr>
1652
- <td>
1653
- <input
1654
- type="text"
1655
- name="label"
1656
- onChange={
1657
- handleDataOption
1658
- }
1659
- value={
1660
- dataOption.label
1661
- }
1662
- placeholder="Enter label..."
1663
- />
1664
- </td>
1665
- <td>
1666
- <button
1667
- className={`${styles.tableActionBtn} ${styles.add}`}
1668
- onClick={
1669
- handleAddOption
1670
- }
1671
- data-tooltip-id="system-tooltip"
1672
- data-tooltip-content={
1673
- 'Add Option'
1812
+ <span>
1813
+ Options
1814
+ </span>
1815
+ {dataField.options
1816
+ .length > 0 && (
1817
+ <span
1818
+ className={
1819
+ styles.optionsBadge
1820
+ }
1821
+ >
1822
+ {
1823
+ dataField
1824
+ .options
1825
+ .length
1826
+ }
1827
+ </span>
1828
+ )}
1829
+ </div>
1830
+ {dataField.options.length >
1831
+ 0 ? (
1832
+ <div
1833
+ className={
1834
+ styles.optionsList
1835
+ }
1836
+ >
1837
+ {dataField.options.map(
1838
+ (
1839
+ option,
1840
+ optionKey
1841
+ ) => (
1842
+ <div
1843
+ key={`option-${optionKey}`}
1844
+ className={
1845
+ styles.optionItem
1674
1846
  }
1675
1847
  >
1676
- <CirclePlus
1677
- strokeWidth={
1678
- 2
1848
+ <span
1849
+ className={
1850
+ styles.optionNumber
1851
+ }
1852
+ >
1853
+ {optionKey +
1854
+ 1}
1855
+ </span>
1856
+ <input
1857
+ type="text"
1858
+ value={
1859
+ option.label
1860
+ }
1861
+ className={
1862
+ styles.optionInput
1679
1863
  }
1680
- size={14}
1864
+ onChange={(
1865
+ e
1866
+ ) => {
1867
+ const newOptions =
1868
+ [
1869
+ ...dataField.options,
1870
+ ];
1871
+ newOptions[
1872
+ optionKey
1873
+ ].label =
1874
+ e.target.value;
1875
+ newOptions[
1876
+ optionKey
1877
+ ].id =
1878
+ `${slugify(e.target.value)}-`;
1879
+ setDataField(
1880
+ (
1881
+ items
1882
+ ) => ({
1883
+ ...items,
1884
+ options:
1885
+ newOptions,
1886
+ })
1887
+ );
1888
+ }}
1681
1889
  />
1682
- Add
1683
- </button>
1684
- </td>
1685
- </tr>
1686
- </tfoot>
1687
- </table>
1890
+ <button
1891
+ className={
1892
+ styles.optionDelete
1893
+ }
1894
+ onClick={
1895
+ handleDeleteOption
1896
+ }
1897
+ data-counter={
1898
+ optionKey
1899
+ }
1900
+ data-tooltip-id="system-tooltip"
1901
+ data-tooltip-content="Delete Option"
1902
+ >
1903
+ <TrashCan
1904
+ strokeWidth={
1905
+ 2
1906
+ }
1907
+ size={
1908
+ 14
1909
+ }
1910
+ />
1911
+ </button>
1912
+ </div>
1913
+ )
1914
+ )}
1915
+ </div>
1916
+ ) : (
1917
+ <div
1918
+ className={
1919
+ styles.optionsEmpty
1920
+ }
1921
+ >
1922
+ No options added yet
1923
+ </div>
1924
+ )}
1925
+ <div
1926
+ className={
1927
+ styles.optionAddRow
1928
+ }
1929
+ >
1930
+ <input
1931
+ type="text"
1932
+ name="label"
1933
+ onChange={
1934
+ handleDataOption
1935
+ }
1936
+ value={
1937
+ dataOption.label
1938
+ }
1939
+ placeholder="Type new option and press Add..."
1940
+ className={
1941
+ styles.optionAddInput
1942
+ }
1943
+ onKeyDown={(e) => {
1944
+ if (
1945
+ e.key ===
1946
+ 'Enter'
1947
+ ) {
1948
+ e.preventDefault();
1949
+ handleAddOption(
1950
+ e
1951
+ );
1952
+ }
1953
+ }}
1954
+ />
1955
+ <button
1956
+ className={`${styles.tableActionBtn} ${styles.add}`}
1957
+ onClick={
1958
+ handleAddOption
1959
+ }
1960
+ data-tooltip-id="system-tooltip"
1961
+ data-tooltip-content="Add Option"
1962
+ >
1963
+ <CirclePlus
1964
+ strokeWidth={2}
1965
+ size={14}
1966
+ />
1967
+ Add
1968
+ </button>
1969
+ </div>
1688
1970
  </div>
1689
1971
  ) : null}
1690
1972
 
@@ -1692,297 +1974,103 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
1692
1974
  dataField.type
1693
1975
  ) ? (
1694
1976
  <div className={styles.formItemFull}>
1695
- <table
1696
- className={styles.optionsTable}
1697
- >
1698
- <thead>
1699
- <tr>
1700
- <th>Column Header</th>
1701
- <th>Column Type</th>
1702
- <th
1703
- style={{
1704
- width: '80px',
1705
- }}
1706
- >
1707
- Actions
1708
- </th>
1709
- </tr>
1710
- </thead>
1711
- <tbody>
1712
- {dataField.options.length >
1713
- 0 ? (
1714
- dataField.options.map(
1715
- (
1716
- option,
1717
- optionKey
1718
- ) => (
1719
- <tr
1720
- key={`option-${optionKey}`}
1721
- >
1722
- <td>
1723
- <input
1724
- type="text"
1725
- value={
1726
- option.label
1727
- }
1728
- onChange={(
1729
- e
1730
- ) => {
1731
- const newOptions =
1732
- [
1733
- ...dataField.options,
1734
- ];
1735
- newOptions[
1736
- optionKey
1737
- ].label =
1738
- e.target.value;
1739
- newOptions[
1740
- optionKey
1741
- ].id =
1742
- `${slugify(e.target.value)}-`;
1743
- setDataField(
1744
- (
1745
- items
1746
- ) => ({
1747
- ...items,
1748
- options:
1749
- newOptions,
1750
- })
1751
- );
1752
- }}
1753
- />
1754
- </td>
1755
- <td>
1756
- <select
1757
- value={
1758
- option.type
1759
- }
1760
- onChange={(
1761
- e
1762
- ) => {
1763
- const newOptions =
1764
- [
1765
- ...dataField.options,
1766
- ];
1767
- newOptions[
1768
- optionKey
1769
- ].type =
1770
- e.target.value;
1771
- setDataField(
1772
- (
1773
- items
1774
- ) => ({
1775
- ...items,
1776
- options:
1777
- newOptions,
1778
- })
1779
- );
1780
- }}
1781
- >
1782
- <option value="">
1783
- No
1784
- Type
1785
- Selected
1786
- </option>
1787
- {fieldTypes
1788
- .filter(
1789
- (
1790
- item
1791
- ) => {
1792
- const excludedKeywords =
1793
- [
1794
- 'dropdown',
1795
- 'dynamicdata',
1796
- 'file',
1797
- 'image',
1798
- 'plaintext',
1799
- 'plaintextheading',
1800
- 'section',
1801
- 'signature',
1802
- 'table',
1803
- ];
1804
- // Exclude 'checkbox' only if dataField.type is 'table_text'
1805
- if (
1806
- dataField.type ===
1807
- 'table_text'
1808
- ) {
1809
- // excludedKeywords.push(
1810
- // 'checkbox'
1811
- // );
1812
- }
1813
- return !excludedKeywords.some(
1814
- (
1815
- keyword
1816
- ) =>
1817
- item.value.includes(
1818
- keyword
1819
- )
1820
- );
1821
- }
1822
- )
1823
- .map(
1824
- (
1825
- item,
1826
- itemKey
1827
- ) => (
1828
- <option
1829
- value={
1830
- item.value
1831
- }
1832
- key={`type-form-${item.value}-${itemKey}`}
1833
- >
1834
- {
1835
- item.label
1836
- }
1837
- </option>
1838
- )
1839
- )}
1840
- </select>
1841
- </td>
1842
- <td>
1843
- <button
1844
- className={`${styles.tableActionBtn} ${styles.delete}`}
1845
- onClick={
1846
- handleDeleteOption
1847
- }
1848
- data-counter={
1849
- optionKey
1850
- }
1851
- data-tooltip-id="system-tooltip"
1852
- data-tooltip-content={
1853
- 'Delete Option'
1854
- }
1855
- >
1856
- <TrashCan
1857
- strokeWidth={
1858
- 2
1859
- }
1860
- size={
1861
- 14
1862
- }
1863
- />
1864
- </button>
1865
- </td>
1866
- </tr>
1867
- )
1868
- )
1869
- ) : (
1870
- <tr>
1871
- <td colSpan="3">
1872
- No entry has
1873
- been added.
1874
- </td>
1875
- </tr>
1876
- )}
1877
- </tbody>
1878
- <tfoot>
1879
- <tr>
1880
- <td>
1977
+ <div className={styles.optionsHeader}>
1978
+ <span>Columns</span>
1979
+ {dataField.options.length > 0 && (
1980
+ <span className={styles.optionsBadge}>
1981
+ {dataField.options.length}
1982
+ </span>
1983
+ )}
1984
+ </div>
1985
+ {dataField.options.length > 0 ? (
1986
+ <div className={styles.optionsList}>
1987
+ {dataField.options.map((option, optionKey) => (
1988
+ <div key={`option-${optionKey}`} className={styles.optionItem}>
1989
+ <span className={styles.optionNumber}>{optionKey + 1}</span>
1881
1990
  <input
1882
1991
  type="text"
1883
- name="label"
1884
- onChange={
1885
- handleDataOption
1886
- }
1887
- value={
1888
- dataOption.label
1889
- }
1890
- placeholder="Enter column header..."
1992
+ value={option.label}
1993
+ className={styles.optionInput}
1994
+ placeholder="Column header..."
1995
+ onChange={(e) => {
1996
+ const newOptions = [...dataField.options];
1997
+ newOptions[optionKey].label = e.target.value;
1998
+ newOptions[optionKey].id = `${slugify(e.target.value)}-`;
1999
+ setDataField((items) => ({ ...items, options: newOptions }));
2000
+ }}
1891
2001
  />
1892
- </td>
1893
- <td>
1894
2002
  <select
1895
- name="type"
1896
- value={
1897
- dataOption.type
1898
- }
1899
- onChange={
1900
- handleDataOption
1901
- }
2003
+ value={option.type}
2004
+ className={styles.optionSelect}
2005
+ onChange={(e) => {
2006
+ const newOptions = [...dataField.options];
2007
+ newOptions[optionKey].type = e.target.value;
2008
+ setDataField((items) => ({ ...items, options: newOptions }));
2009
+ }}
1902
2010
  >
1903
- <option value="">
1904
- Please
1905
- Select an
1906
- Option
1907
- </option>
2011
+ <option value="">No Type</option>
1908
2012
  {fieldTypes
1909
- .filter(
1910
- (
1911
- item
1912
- ) => {
1913
- const excludedKeywords =
1914
- [
1915
- 'dropdown',
1916
- 'dynamicdata',
1917
- 'file',
1918
- 'image',
1919
- 'plaintext',
1920
- 'plaintextheading',
1921
- 'section',
1922
- 'signature',
1923
- 'table',
1924
- ];
1925
- // Exclude 'checkbox' only if dataField.type is 'table_text'
1926
- if (
1927
- dataField.type ===
1928
- 'table_text'
1929
- ) {
1930
- // excludedKeywords.push(
1931
- // 'checkbox'
1932
- // );
1933
- }
1934
- return !excludedKeywords.some(
1935
- (
1936
- keyword
1937
- ) =>
1938
- item.value.includes(
1939
- keyword
1940
- )
1941
- );
1942
- }
1943
- )
1944
- .map(
1945
- (
1946
- item,
1947
- itemKey
1948
- ) => (
1949
- <option
1950
- value={
1951
- item.value
1952
- }
1953
- key={`type-${item.value}-${itemKey}`}
1954
- >
1955
- {
1956
- item.label
1957
- }
1958
- </option>
1959
- )
1960
- )}
2013
+ .filter((item) => {
2014
+ const excludedKeywords = ['dropdown','dynamicdata','file','image','plaintext','plaintextheading','section','signature','table'];
2015
+ return !excludedKeywords.some((keyword) => item.value.includes(keyword));
2016
+ })
2017
+ .map((item, itemKey) => (
2018
+ <option value={item.value} key={`type-form-${item.value}-${itemKey}`}>{item.label}</option>
2019
+ ))}
1961
2020
  </select>
1962
- </td>
1963
- <td>
1964
2021
  <button
1965
- className={`${styles.tableActionBtn} ${styles.add}`}
1966
- onClick={
1967
- handleAddOption
1968
- }
2022
+ className={styles.optionDelete}
2023
+ onClick={handleDeleteOption}
2024
+ data-counter={optionKey}
1969
2025
  data-tooltip-id="system-tooltip"
1970
- data-tooltip-content={
1971
- 'Add Option'
1972
- }
2026
+ data-tooltip-content="Delete Column"
1973
2027
  >
1974
- <CirclePlus
1975
- strokeWidth={
1976
- 2
1977
- }
1978
- size={14}
1979
- />
1980
- Add
2028
+ <TrashCan strokeWidth={2} size={14} />
1981
2029
  </button>
1982
- </td>
1983
- </tr>
1984
- </tfoot>
1985
- </table>
2030
+ </div>
2031
+ ))}
2032
+ </div>
2033
+ ) : (
2034
+ <div className={styles.optionsEmpty}>No columns added yet</div>
2035
+ )}
2036
+ <div className={styles.optionAddRow}>
2037
+ <input
2038
+ type="text"
2039
+ name="label"
2040
+ onChange={handleDataOption}
2041
+ value={dataOption.label}
2042
+ placeholder="Type column header..."
2043
+ className={styles.optionAddInput}
2044
+ onKeyDown={(e) => {
2045
+ if (e.key === 'Enter') { e.preventDefault(); handleAddOption(e); }
2046
+ }}
2047
+ />
2048
+ <select
2049
+ name="type"
2050
+ value={dataOption.type}
2051
+ onChange={handleDataOption}
2052
+ className={styles.optionSelect}
2053
+ >
2054
+ <option value="">Select Type</option>
2055
+ {fieldTypes
2056
+ .filter((item) => {
2057
+ const excludedKeywords = ['dropdown','dynamicdata','file','image','plaintext','plaintextheading','section','signature','table'];
2058
+ return !excludedKeywords.some((keyword) => item.value.includes(keyword));
2059
+ })
2060
+ .map((item, itemKey) => (
2061
+ <option value={item.value} key={`type-${item.value}-${itemKey}`}>{item.label}</option>
2062
+ ))}
2063
+ </select>
2064
+ <button
2065
+ className={`${styles.tableActionBtn} ${styles.add}`}
2066
+ onClick={handleAddOption}
2067
+ data-tooltip-id="system-tooltip"
2068
+ data-tooltip-content="Add Column"
2069
+ >
2070
+ <CirclePlus strokeWidth={2} size={14} />
2071
+ Add
2072
+ </button>
2073
+ </div>
1986
2074
  </div>
1987
2075
  ) : null}
1988
2076
 
@@ -2061,135 +2149,68 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
2061
2149
  dataField.type
2062
2150
  ) ? (
2063
2151
  <div className={styles.formItemFull}>
2064
- <table
2065
- className={styles.optionsTable}
2066
- >
2067
- <thead>
2068
- <tr>
2069
- <th>Row Label</th>
2070
- <th
2071
- style={{
2072
- width: '80px',
2073
- }}
2074
- >
2075
- Actions
2076
- </th>
2077
- </tr>
2078
- </thead>
2079
- <tbody>
2080
- {dataField.rows?.length >
2081
- 0 ? (
2082
- dataField.rows.map(
2083
- (row, rowKey) => (
2084
- <tr
2085
- key={`row-${rowKey}`}
2086
- >
2087
- <td>
2088
- <input
2089
- type="text"
2090
- value={
2091
- row.label
2092
- }
2093
- onChange={(
2094
- e
2095
- ) => {
2096
- const newRows =
2097
- [
2098
- ...dataField.rows,
2099
- ];
2100
- newRows[
2101
- rowKey
2102
- ].label =
2103
- e.target.value;
2104
- newRows[
2105
- rowKey
2106
- ].id =
2107
- `${slugify(e.target.value)}-`;
2108
- setDataField(
2109
- (
2110
- items
2111
- ) => ({
2112
- ...items,
2113
- rows: newRows,
2114
- })
2115
- );
2116
- }}
2117
- />
2118
- </td>
2119
- <td>
2120
- <button
2121
- className={`${styles.tableActionBtn} ${styles.delete}`}
2122
- onClick={
2123
- handleDeleteRowOption
2124
- }
2125
- data-counter={
2126
- rowKey
2127
- }
2128
- data-tooltip-id="system-tooltip"
2129
- data-tooltip-content={
2130
- 'Delete Row'
2131
- }
2132
- >
2133
- <TrashCan
2134
- strokeWidth={
2135
- 2
2136
- }
2137
- size={
2138
- 14
2139
- }
2140
- />
2141
- </button>
2142
- </td>
2143
- </tr>
2144
- )
2145
- )
2146
- ) : (
2147
- <tr>
2148
- <td colSpan="2">
2149
- No entry has
2150
- been added.
2151
- </td>
2152
- </tr>
2153
- )}
2154
- </tbody>
2155
- <tfoot>
2156
- <tr>
2157
- <td>
2152
+ <div className={styles.optionsHeader}>
2153
+ <span>Rows</span>
2154
+ {dataField.rows?.length > 0 && (
2155
+ <span className={styles.optionsBadge}>
2156
+ {dataField.rows.length}
2157
+ </span>
2158
+ )}
2159
+ </div>
2160
+ {dataField.rows?.length > 0 ? (
2161
+ <div className={styles.optionsList}>
2162
+ {dataField.rows.map((row, rowKey) => (
2163
+ <div key={`row-${rowKey}`} className={styles.optionItem}>
2164
+ <span className={styles.optionNumber}>{rowKey + 1}</span>
2158
2165
  <input
2159
2166
  type="text"
2160
- name="label"
2161
- onChange={
2162
- handleRowOption
2163
- }
2164
- value={
2165
- rowOption.label
2166
- }
2167
- placeholder="Enter row label..."
2167
+ value={row.label}
2168
+ className={styles.optionInput}
2169
+ placeholder="Row label..."
2170
+ onChange={(e) => {
2171
+ const newRows = [...dataField.rows];
2172
+ newRows[rowKey].label = e.target.value;
2173
+ newRows[rowKey].id = `${slugify(e.target.value)}-`;
2174
+ setDataField((items) => ({ ...items, rows: newRows }));
2175
+ }}
2168
2176
  />
2169
- </td>
2170
- <td>
2171
2177
  <button
2172
- className={`${styles.tableActionBtn} ${styles.add}`}
2173
- onClick={
2174
- handleAddRowOption
2175
- }
2178
+ className={styles.optionDelete}
2179
+ onClick={handleDeleteRowOption}
2180
+ data-counter={rowKey}
2176
2181
  data-tooltip-id="system-tooltip"
2177
- data-tooltip-content={
2178
- 'Add Row'
2179
- }
2182
+ data-tooltip-content="Delete Row"
2180
2183
  >
2181
- <CirclePlus
2182
- strokeWidth={
2183
- 2
2184
- }
2185
- size={14}
2186
- />
2187
- Add
2184
+ <TrashCan strokeWidth={2} size={14} />
2188
2185
  </button>
2189
- </td>
2190
- </tr>
2191
- </tfoot>
2192
- </table>
2186
+ </div>
2187
+ ))}
2188
+ </div>
2189
+ ) : (
2190
+ <div className={styles.optionsEmpty}>No rows added yet</div>
2191
+ )}
2192
+ <div className={styles.optionAddRow}>
2193
+ <input
2194
+ type="text"
2195
+ name="label"
2196
+ onChange={handleRowOption}
2197
+ value={rowOption.label}
2198
+ placeholder="Type new row label and press Add..."
2199
+ className={styles.optionAddInput}
2200
+ onKeyDown={(e) => {
2201
+ if (e.key === 'Enter') { e.preventDefault(); handleAddRowOption(e); }
2202
+ }}
2203
+ />
2204
+ <button
2205
+ className={`${styles.tableActionBtn} ${styles.add}`}
2206
+ onClick={handleAddRowOption}
2207
+ data-tooltip-id="system-tooltip"
2208
+ data-tooltip-content="Add Row"
2209
+ >
2210
+ <CirclePlus strokeWidth={2} size={14} />
2211
+ Add
2212
+ </button>
2213
+ </div>
2193
2214
  </div>
2194
2215
  ) : null}
2195
2216
 
@@ -2322,6 +2343,83 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
2322
2343
  </div>
2323
2344
  </StandardModal>
2324
2345
 
2346
+ {/* Move-to-position dialog */}
2347
+ {moveToDialog.show && (
2348
+ <div
2349
+ className={styles.moveToOverlay}
2350
+ onClick={() =>
2351
+ setMoveToDialog({
2352
+ show: false,
2353
+ fieldId: null,
2354
+ currentPos: 0,
2355
+ })
2356
+ }
2357
+ >
2358
+ <div
2359
+ className={styles.moveToDialog}
2360
+ onClick={(e) => e.stopPropagation()}
2361
+ >
2362
+ <h3>Move Field to Position</h3>
2363
+ <p>
2364
+ Current position: {moveToDialog.currentPos} of{' '}
2365
+ {data.detail.length}
2366
+ </p>
2367
+ <div className={styles.moveToInputGroup}>
2368
+ <input
2369
+ type="number"
2370
+ min="1"
2371
+ max={data.detail.length}
2372
+ value={moveToPosition}
2373
+ onChange={(e) =>
2374
+ setMoveToPosition(e.target.value)
2375
+ }
2376
+ autoFocus
2377
+ onKeyDown={(e) => {
2378
+ if (e.key === 'Enter') {
2379
+ handleMoveTo(
2380
+ moveToDialog.fieldId,
2381
+ parseInt(moveToPosition)
2382
+ );
2383
+ } else if (e.key === 'Escape') {
2384
+ setMoveToDialog({
2385
+ show: false,
2386
+ fieldId: null,
2387
+ currentPos: 0,
2388
+ });
2389
+ }
2390
+ }}
2391
+ />
2392
+ <span>of {data.detail.length}</span>
2393
+ </div>
2394
+ <div className={styles.moveToActions}>
2395
+ <button
2396
+ className={styles.btn}
2397
+ onClick={() =>
2398
+ handleMoveTo(
2399
+ moveToDialog.fieldId,
2400
+ parseInt(moveToPosition)
2401
+ )
2402
+ }
2403
+ >
2404
+ Move
2405
+ </button>
2406
+ <button
2407
+ className={`${styles.btn} ${styles.btnCancel}`}
2408
+ onClick={() =>
2409
+ setMoveToDialog({
2410
+ show: false,
2411
+ fieldId: null,
2412
+ currentPos: 0,
2413
+ })
2414
+ }
2415
+ >
2416
+ Cancel
2417
+ </button>
2418
+ </div>
2419
+ </div>
2420
+ </div>
2421
+ )}
2422
+
2325
2423
  {/* React Tooltips */}
2326
2424
  <Tooltip
2327
2425
  id="field-tooltip"