@nestledjs/data-browser 0.1.13 → 0.1.15

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.
@@ -132,7 +132,8 @@ function AdminDataCreatePageContent({
132
132
  }) {
133
133
  const navigate = useNavigate();
134
134
  const {
135
- sdk
135
+ sdk,
136
+ databaseModels
136
137
  } = useAdminDataContext();
137
138
  const [submissionState, setSubmissionState] = useState({
138
139
  status: "idle"
@@ -202,7 +203,7 @@ function AdminDataCreatePageContent({
202
203
  columnNumber: 17
203
204
  } }, "Return to Data Browser"))))));
204
205
  }
205
- const formFields = buildFormFields(sdk, model, "create", void 0, submissionState.status === "loading", basePath);
206
+ const formFields = buildFormFields(sdk, model, "create", void 0, submissionState.status === "loading", basePath, databaseModels);
206
207
  const handleSubmit = async (formData) => {
207
208
  try {
208
209
  setSubmissionState({
@@ -166,7 +166,8 @@ function AdminDataEditPageContent({
166
166
  }) {
167
167
  const navigate = useNavigate();
168
168
  const {
169
- sdk
169
+ sdk,
170
+ databaseModels
170
171
  } = useAdminDataContext();
171
172
  const [submissionState, setSubmissionState] = useState({
172
173
  status: "idle"
@@ -423,7 +424,7 @@ function AdminDataEditPageContent({
423
424
  columnNumber: 17
424
425
  } }, "Back to List"))))));
425
426
  }
426
- const formFields = buildFormFields(sdk, model, "update", item, submissionState.status === "loading", basePath);
427
+ const formFields = buildFormFields(sdk, model, "update", item, submissionState.status === "loading", basePath, databaseModels);
427
428
  const initialValues = {};
428
429
  if (item) {
429
430
  const editableFields = model.fields.filter((field) => {
@@ -121,7 +121,12 @@ function AdminDataListPage({
121
121
  id: value
122
122
  };
123
123
  } else {
124
- filters2[key] = value;
124
+ const scalarField = model.fields.find((f) => f.name === key && !f.relationName);
125
+ if (scalarField) {
126
+ filters2[key] = value;
127
+ } else {
128
+ console.warn(`[AdminList] URL parameter "${key}" does not exist on model "${model.name}" - ignoring`);
129
+ }
125
130
  }
126
131
  }
127
132
  }
@@ -361,129 +366,129 @@ function AdminDataListPage({
361
366
  if (!model) {
362
367
  return /* @__PURE__ */ React.createElement("div", { className: "min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8", __self: this, __source: {
363
368
  fileName: _jsxFileName,
364
- lineNumber: 451,
369
+ lineNumber: 458,
365
370
  columnNumber: 7
366
371
  } }, /* @__PURE__ */ React.createElement("div", { className: "mt-8 sm:mx-auto sm:w-full sm:max-w-md", __self: this, __source: {
367
372
  fileName: _jsxFileName,
368
- lineNumber: 452,
373
+ lineNumber: 459,
369
374
  columnNumber: 9
370
375
  } }, /* @__PURE__ */ React.createElement("div", { className: "bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10", __self: this, __source: {
371
376
  fileName: _jsxFileName,
372
- lineNumber: 453,
377
+ lineNumber: 460,
373
378
  columnNumber: 11
374
379
  } }, /* @__PURE__ */ React.createElement("div", { className: "text-center", __self: this, __source: {
375
380
  fileName: _jsxFileName,
376
- lineNumber: 454,
381
+ lineNumber: 461,
377
382
  columnNumber: 13
378
383
  } }, /* @__PURE__ */ React.createElement("svg", { className: "mx-auto h-12 w-12 text-red-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", __self: this, __source: {
379
384
  fileName: _jsxFileName,
380
- lineNumber: 455,
385
+ lineNumber: 462,
381
386
  columnNumber: 15
382
387
  } }, /* @__PURE__ */ React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z", __self: this, __source: {
383
388
  fileName: _jsxFileName,
384
- lineNumber: 461,
389
+ lineNumber: 468,
385
390
  columnNumber: 17
386
391
  } })), /* @__PURE__ */ React.createElement("h2", { className: "mt-4 text-lg font-medium text-gray-900", __self: this, __source: {
387
392
  fileName: _jsxFileName,
388
- lineNumber: 468,
393
+ lineNumber: 475,
389
394
  columnNumber: 15
390
395
  } }, "Invalid Data Type"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-gray-600", __self: this, __source: {
391
396
  fileName: _jsxFileName,
392
- lineNumber: 469,
397
+ lineNumber: 476,
393
398
  columnNumber: 15
394
399
  } }, 'The data type "', pluralParam, '" is not recognized.'), /* @__PURE__ */ React.createElement("div", { className: "mt-6", __self: this, __source: {
395
400
  fileName: _jsxFileName,
396
- lineNumber: 472,
401
+ lineNumber: 479,
397
402
  columnNumber: 15
398
403
  } }, /* @__PURE__ */ React.createElement(Link, { to: basePath, className: "w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-web hover:bg-green-web-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web", __self: this, __source: {
399
404
  fileName: _jsxFileName,
400
- lineNumber: 473,
405
+ lineNumber: 480,
401
406
  columnNumber: 17
402
407
  } }, "Return to Data Browser"))))));
403
408
  }
404
409
  if (!query) {
405
410
  return /* @__PURE__ */ React.createElement("div", { className: "min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8", __self: this, __source: {
406
411
  fileName: _jsxFileName,
407
- lineNumber: 489,
412
+ lineNumber: 496,
408
413
  columnNumber: 7
409
414
  } }, /* @__PURE__ */ React.createElement("div", { className: "mt-8 sm:mx-auto sm:w-full sm:max-w-md", __self: this, __source: {
410
415
  fileName: _jsxFileName,
411
- lineNumber: 490,
416
+ lineNumber: 497,
412
417
  columnNumber: 9
413
418
  } }, /* @__PURE__ */ React.createElement("div", { className: "bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10", __self: this, __source: {
414
419
  fileName: _jsxFileName,
415
- lineNumber: 491,
420
+ lineNumber: 498,
416
421
  columnNumber: 11
417
422
  } }, /* @__PURE__ */ React.createElement("div", { className: "text-center", __self: this, __source: {
418
423
  fileName: _jsxFileName,
419
- lineNumber: 492,
424
+ lineNumber: 499,
420
425
  columnNumber: 13
421
426
  } }, /* @__PURE__ */ React.createElement("svg", { className: "mx-auto h-12 w-12 text-yellow-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", __self: this, __source: {
422
427
  fileName: _jsxFileName,
423
- lineNumber: 493,
428
+ lineNumber: 500,
424
429
  columnNumber: 15
425
430
  } }, /* @__PURE__ */ React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.728-.833-2.498 0L5.316 15c-.77.833.192 2.5 1.732 2.5z", __self: this, __source: {
426
431
  fileName: _jsxFileName,
427
- lineNumber: 499,
432
+ lineNumber: 506,
428
433
  columnNumber: 17
429
434
  } })), /* @__PURE__ */ React.createElement("h2", { className: "mt-4 text-lg font-medium text-gray-900", __self: this, __source: {
430
435
  fileName: _jsxFileName,
431
- lineNumber: 506,
436
+ lineNumber: 513,
432
437
  columnNumber: 15
433
438
  } }, "GraphQL Schema Not Available"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-gray-600", __self: this, __source: {
434
439
  fileName: _jsxFileName,
435
- lineNumber: 509,
440
+ lineNumber: 516,
436
441
  columnNumber: 15
437
442
  } }, 'No GraphQL query found for "', model.name, '". The admin schema may need to be regenerated.'), /* @__PURE__ */ React.createElement("div", { className: "mt-6 space-y-3", __self: this, __source: {
438
443
  fileName: _jsxFileName,
439
- lineNumber: 513,
444
+ lineNumber: 520,
440
445
  columnNumber: 15
441
446
  } }, /* @__PURE__ */ React.createElement("button", { onClick: () => window.location.reload(), className: "w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-web hover:bg-green-web-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web", __self: this, __source: {
442
447
  fileName: _jsxFileName,
443
- lineNumber: 514,
448
+ lineNumber: 521,
444
449
  columnNumber: 17
445
450
  } }, "Reload Page"), /* @__PURE__ */ React.createElement(Link, { to: basePath, className: "w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web", __self: this, __source: {
446
451
  fileName: _jsxFileName,
447
- lineNumber: 520,
452
+ lineNumber: 527,
448
453
  columnNumber: 17
449
454
  } }, "Return to Data Browser"))))));
450
455
  }
451
456
  const createLink = `${basePath}/${kebabCase(model.name)}/create`;
452
457
  const columnSelector = /* @__PURE__ */ React.createElement("div", { className: "relative", "data-dropdown": "column-selector", __self: this, __source: {
453
458
  fileName: _jsxFileName,
454
- lineNumber: 539,
459
+ lineNumber: 546,
455
460
  columnNumber: 5
456
461
  } }, /* @__PURE__ */ React.createElement("button", { onClick: () => setShowColumnSelector(!showColumnSelector), className: "inline-flex items-center px-3 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web", __self: this, __source: {
457
462
  fileName: _jsxFileName,
458
- lineNumber: 540,
463
+ lineNumber: 547,
459
464
  columnNumber: 7
460
465
  } }, "Columns", /* @__PURE__ */ React.createElement("svg", { className: "-mr-0.5 ml-2 h-4 w-4", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", __self: this, __source: {
461
466
  fileName: _jsxFileName,
462
- lineNumber: 545,
467
+ lineNumber: 552,
463
468
  columnNumber: 9
464
469
  } }, /* @__PURE__ */ React.createElement("path", { fillRule: "evenodd", d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", clipRule: "evenodd", __self: this, __source: {
465
470
  fileName: _jsxFileName,
466
- lineNumber: 551,
471
+ lineNumber: 558,
467
472
  columnNumber: 11
468
473
  } }))), showColumnSelector && /* @__PURE__ */ React.createElement("div", { className: "absolute right-0 mt-2 w-80 bg-white rounded-md shadow-lg z-50 border border-gray-200", __self: this, __source: {
469
474
  fileName: _jsxFileName,
470
- lineNumber: 560,
475
+ lineNumber: 567,
471
476
  columnNumber: 9
472
477
  } }, /* @__PURE__ */ React.createElement("div", { className: "p-4", __self: this, __source: {
473
478
  fileName: _jsxFileName,
474
- lineNumber: 561,
479
+ lineNumber: 568,
475
480
  columnNumber: 11
476
481
  } }, /* @__PURE__ */ React.createElement("h3", { className: "text-sm font-medium text-gray-900 mb-3", __self: this, __source: {
477
482
  fileName: _jsxFileName,
478
- lineNumber: 562,
483
+ lineNumber: 569,
479
484
  columnNumber: 13
480
485
  } }, "Visible Columns"), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 max-h-64 overflow-y-auto", __self: this, __source: {
481
486
  fileName: _jsxFileName,
482
- lineNumber: 563,
487
+ lineNumber: 570,
483
488
  columnNumber: 13
484
489
  } }, fieldNames.map((field) => /* @__PURE__ */ React.createElement("label", { key: field, className: "flex items-start gap-2 cursor-pointer", __self: this, __source: {
485
490
  fileName: _jsxFileName,
486
- lineNumber: 565,
491
+ lineNumber: 572,
487
492
  columnNumber: 17
488
493
  } }, /* @__PURE__ */ React.createElement("input", { type: "checkbox", checked: visibleColumns.includes(field), onChange: (e) => {
489
494
  const newColumns = e.target.checked ? [...visibleColumns, field] : visibleColumns.filter((col) => col !== field);
@@ -491,22 +496,22 @@ function AdminDataListPage({
491
496
  AdminLocalStorage.setColumnVisibility(model.name, newColumns);
492
497
  }, className: "h-4 w-4 mt-0.5 flex-shrink-0 text-green-web focus:ring-green-web border-gray-300 rounded", __self: this, __source: {
493
498
  fileName: _jsxFileName,
494
- lineNumber: 566,
499
+ lineNumber: 573,
495
500
  columnNumber: 19
496
501
  } }), /* @__PURE__ */ React.createElement("span", { className: "text-sm text-gray-700 leading-5", __self: this, __source: {
497
502
  fileName: _jsxFileName,
498
- lineNumber: 578,
503
+ lineNumber: 585,
499
504
  columnNumber: 19
500
505
  } }, formatFieldName(field))))), /* @__PURE__ */ React.createElement("div", { className: "mt-3 pt-3 border-t border-gray-200 flex justify-between", __self: this, __source: {
501
506
  fileName: _jsxFileName,
502
- lineNumber: 582,
507
+ lineNumber: 589,
503
508
  columnNumber: 13
504
509
  } }, /* @__PURE__ */ React.createElement("button", { onClick: () => {
505
510
  setVisibleColumns(fieldNames);
506
511
  AdminLocalStorage.setColumnVisibility(model.name, fieldNames);
507
512
  }, className: "text-xs text-green-web hover:text-green-web-800", __self: this, __source: {
508
513
  fileName: _jsxFileName,
509
- lineNumber: 583,
514
+ lineNumber: 590,
510
515
  columnNumber: 15
511
516
  } }, "Select All"), /* @__PURE__ */ React.createElement("button", { onClick: () => {
512
517
  const defaults = fieldNames.slice(0, 8);
@@ -514,44 +519,44 @@ function AdminDataListPage({
514
519
  AdminLocalStorage.setColumnVisibility(model.name, defaults);
515
520
  }, className: "text-xs text-gray-600 hover:text-gray-800", __self: this, __source: {
516
521
  fileName: _jsxFileName,
517
- lineNumber: 592,
522
+ lineNumber: 599,
518
523
  columnNumber: 15
519
524
  } }, "Reset to Defaults")))));
520
525
  const searchFieldSelector = /* @__PURE__ */ React.createElement("div", { className: "relative", "data-dropdown": "search-field-selector", __self: this, __source: {
521
526
  fileName: _jsxFileName,
522
- lineNumber: 611,
527
+ lineNumber: 618,
523
528
  columnNumber: 5
524
529
  } }, /* @__PURE__ */ React.createElement("button", { onClick: () => setShowSearchFieldSelector(!showSearchFieldSelector), className: "h-full px-3 border-l border-gray-300 text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600", "aria-label": "Configure search fields", __self: this, __source: {
525
530
  fileName: _jsxFileName,
526
- lineNumber: 612,
531
+ lineNumber: 619,
527
532
  columnNumber: 7
528
533
  } }, /* @__PURE__ */ React.createElement("svg", { className: "h-4 w-4", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", __self: this, __source: {
529
534
  fileName: _jsxFileName,
530
- lineNumber: 617,
535
+ lineNumber: 624,
531
536
  columnNumber: 9
532
537
  } }, /* @__PURE__ */ React.createElement("path", { fillRule: "evenodd", d: "M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3zm11.707 4.707a1 1 0 00-1.414-1.414L10 9.586 8.707 8.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z", clipRule: "evenodd", __self: this, __source: {
533
538
  fileName: _jsxFileName,
534
- lineNumber: 623,
539
+ lineNumber: 630,
535
540
  columnNumber: 11
536
541
  } }))), showSearchFieldSelector && /* @__PURE__ */ React.createElement("div", { className: "absolute right-0 mt-2 w-72 bg-white rounded-md shadow-lg z-50 border border-gray-200", __self: this, __source: {
537
542
  fileName: _jsxFileName,
538
- lineNumber: 632,
543
+ lineNumber: 639,
539
544
  columnNumber: 9
540
545
  } }, /* @__PURE__ */ React.createElement("div", { className: "p-4", __self: this, __source: {
541
546
  fileName: _jsxFileName,
542
- lineNumber: 633,
547
+ lineNumber: 640,
543
548
  columnNumber: 11
544
549
  } }, /* @__PURE__ */ React.createElement("h3", { className: "text-sm font-medium text-gray-900 mb-3", __self: this, __source: {
545
550
  fileName: _jsxFileName,
546
- lineNumber: 634,
551
+ lineNumber: 641,
547
552
  columnNumber: 13
548
553
  } }, "Search Fields"), searchableFieldNames.length > 0 ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "space-y-2 max-h-48 overflow-y-auto", __self: this, __source: {
549
554
  fileName: _jsxFileName,
550
- lineNumber: 637,
555
+ lineNumber: 644,
551
556
  columnNumber: 17
552
557
  } }, searchableFieldNames.map((field) => /* @__PURE__ */ React.createElement("label", { key: field, className: "flex items-start gap-2 cursor-pointer", __self: this, __source: {
553
558
  fileName: _jsxFileName,
554
- lineNumber: 639,
559
+ lineNumber: 646,
555
560
  columnNumber: 21
556
561
  } }, /* @__PURE__ */ React.createElement("input", { type: "checkbox", checked: searchFields.includes(field), onChange: (e) => {
557
562
  const newFields = e.target.checked ? [...searchFields, field] : searchFields.filter((f) => f !== field);
@@ -559,22 +564,22 @@ function AdminDataListPage({
559
564
  AdminLocalStorage.setSearchFields(model.name, newFields);
560
565
  }, className: "h-4 w-4 mt-0.5 flex-shrink-0 text-green-web focus:ring-green-web border-gray-300 rounded", __self: this, __source: {
561
566
  fileName: _jsxFileName,
562
- lineNumber: 640,
567
+ lineNumber: 647,
563
568
  columnNumber: 23
564
569
  } }), /* @__PURE__ */ React.createElement("span", { className: "text-sm text-gray-700 leading-5", __self: this, __source: {
565
570
  fileName: _jsxFileName,
566
- lineNumber: 652,
571
+ lineNumber: 659,
567
572
  columnNumber: 23
568
573
  } }, formatFieldName(field))))), /* @__PURE__ */ React.createElement("div", { className: "mt-3 pt-3 border-t border-gray-200 flex justify-between", __self: this, __source: {
569
574
  fileName: _jsxFileName,
570
- lineNumber: 658,
575
+ lineNumber: 665,
571
576
  columnNumber: 17
572
577
  } }, /* @__PURE__ */ React.createElement("button", { onClick: () => {
573
578
  setSearchFields(searchableFieldNames);
574
579
  AdminLocalStorage.setSearchFields(model.name, searchableFieldNames);
575
580
  }, className: "text-xs text-green-web hover:text-green-web-800 mr-3", __self: this, __source: {
576
581
  fileName: _jsxFileName,
577
- lineNumber: 659,
582
+ lineNumber: 666,
578
583
  columnNumber: 19
579
584
  } }, "Select All"), /* @__PURE__ */ React.createElement("button", { onClick: () => {
580
585
  const defaults = getDefaultSearchFields(searchableFieldNames);
@@ -582,28 +587,28 @@ function AdminDataListPage({
582
587
  AdminLocalStorage.setSearchFields(model.name, defaults);
583
588
  }, className: "text-xs text-gray-600 hover:text-gray-800", __self: this, __source: {
584
589
  fileName: _jsxFileName,
585
- lineNumber: 668,
590
+ lineNumber: 675,
586
591
  columnNumber: 19
587
592
  } }, "Reset to Defaults"))) : /* @__PURE__ */ React.createElement("div", { className: "text-sm text-gray-500", __self: this, __source: {
588
593
  fileName: _jsxFileName,
589
- lineNumber: 681,
594
+ lineNumber: 688,
590
595
  columnNumber: 15
591
596
  } }, "No searchable text fields available"))));
592
597
  const filterPanel = showFilters && /* @__PURE__ */ React.createElement("div", { className: "mb-4 bg-gray-50 border border-gray-200 rounded-lg p-4", __self: this, __source: {
593
598
  fileName: _jsxFileName,
594
- lineNumber: 691,
599
+ lineNumber: 698,
595
600
  columnNumber: 5
596
601
  } }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between mb-3", __self: this, __source: {
597
602
  fileName: _jsxFileName,
598
- lineNumber: 692,
603
+ lineNumber: 699,
599
604
  columnNumber: 7
600
605
  } }, /* @__PURE__ */ React.createElement("h3", { className: "text-sm font-medium text-gray-900", __self: this, __source: {
601
606
  fileName: _jsxFileName,
602
- lineNumber: 693,
607
+ lineNumber: 700,
603
608
  columnNumber: 9
604
609
  } }, "Filters"), /* @__PURE__ */ React.createElement("div", { className: "flex items-center space-x-2", __self: this, __source: {
605
610
  fileName: _jsxFileName,
606
- lineNumber: 694,
611
+ lineNumber: 701,
607
612
  columnNumber: 9
608
613
  } }, /* @__PURE__ */ React.createElement("button", { onClick: () => {
609
614
  setFilters({});
@@ -612,23 +617,23 @@ function AdminDataListPage({
612
617
  });
613
618
  }, className: "text-xs text-gray-600 hover:text-gray-800", __self: this, __source: {
614
619
  fileName: _jsxFileName,
615
- lineNumber: 695,
620
+ lineNumber: 702,
616
621
  columnNumber: 11
617
622
  } }, "Clear All"), /* @__PURE__ */ React.createElement("button", { onClick: () => setShowFilters(false), className: "text-gray-400 hover:text-gray-600", __self: this, __source: {
618
623
  fileName: _jsxFileName,
619
- lineNumber: 704,
624
+ lineNumber: 711,
620
625
  columnNumber: 11
621
626
  } }, /* @__PURE__ */ React.createElement("svg", { className: "h-5 w-5", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", __self: this, __source: {
622
627
  fileName: _jsxFileName,
623
- lineNumber: 708,
628
+ lineNumber: 715,
624
629
  columnNumber: 13
625
630
  } }, /* @__PURE__ */ React.createElement("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd", __self: this, __source: {
626
631
  fileName: _jsxFileName,
627
- lineNumber: 714,
632
+ lineNumber: 721,
628
633
  columnNumber: 15
629
634
  } }))))), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", __self: this, __source: {
630
635
  fileName: _jsxFileName,
631
- lineNumber: 724,
636
+ lineNumber: 731,
632
637
  columnNumber: 7
633
638
  } }, filterableFieldNames.map((fieldName) => {
634
639
  const field = model.fields.find((f) => f.name === fieldName);
@@ -651,21 +656,21 @@ function AdminDataListPage({
651
656
  if (field.relationName && !field.isList) {
652
657
  return /* @__PURE__ */ React.createElement(RelationFilterField, { key: fieldName, fieldName, relatedModelName: field.type, currentValue, onChange: handleChange, __self: this, __source: {
653
658
  fileName: _jsxFileName,
654
- lineNumber: 744,
659
+ lineNumber: 751,
655
660
  columnNumber: 15
656
661
  } });
657
662
  }
658
663
  if (field.type.toLowerCase() === "datetime" || field.type.toLowerCase() === "date") {
659
664
  return /* @__PURE__ */ React.createElement(DateRangeFilter, { key: fieldName, fieldName, currentValue, onChange: handleChange, __self: this, __source: {
660
665
  fileName: _jsxFileName,
661
- lineNumber: 757,
666
+ lineNumber: 764,
662
667
  columnNumber: 15
663
668
  } });
664
669
  }
665
670
  if (["int", "bigint", "float", "decimal"].includes(field.type.toLowerCase())) {
666
671
  return /* @__PURE__ */ React.createElement(NumberRangeFilter, { key: fieldName, fieldName, fieldType: field.type.toLowerCase(), currentValue, onChange: handleChange, __self: this, __source: {
667
672
  fileName: _jsxFileName,
668
- lineNumber: 769,
673
+ lineNumber: 776,
669
674
  columnNumber: 15
670
675
  } });
671
676
  }
@@ -674,23 +679,23 @@ function AdminDataListPage({
674
679
  if (enumValues) {
675
680
  return /* @__PURE__ */ React.createElement("div", { key: fieldName, className: "space-y-1", __self: this, __source: {
676
681
  fileName: _jsxFileName,
677
- lineNumber: 784,
682
+ lineNumber: 791,
678
683
  columnNumber: 17
679
684
  } }, /* @__PURE__ */ React.createElement("label", { className: "block text-sm font-medium text-gray-700", __self: this, __source: {
680
685
  fileName: _jsxFileName,
681
- lineNumber: 785,
686
+ lineNumber: 792,
682
687
  columnNumber: 19
683
688
  } }, formatFieldName(fieldName)), /* @__PURE__ */ React.createElement("select", { value: typeof currentValue === "string" ? currentValue : "", onChange: (e) => handleChange(e.target.value || void 0), className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-web focus:border-green-web text-sm", __self: this, __source: {
684
689
  fileName: _jsxFileName,
685
- lineNumber: 788,
690
+ lineNumber: 795,
686
691
  columnNumber: 19
687
692
  } }, /* @__PURE__ */ React.createElement("option", { value: "", __self: this, __source: {
688
693
  fileName: _jsxFileName,
689
- lineNumber: 793,
694
+ lineNumber: 800,
690
695
  columnNumber: 21
691
696
  } }, "All"), enumValues.map((val) => /* @__PURE__ */ React.createElement("option", { key: val, value: val, __self: this, __source: {
692
697
  fileName: _jsxFileName,
693
- lineNumber: 795,
698
+ lineNumber: 802,
694
699
  columnNumber: 23
695
700
  } }, val))));
696
701
  }
@@ -698,121 +703,121 @@ function AdminDataListPage({
698
703
  if (field.type.toLowerCase() === "boolean") {
699
704
  return /* @__PURE__ */ React.createElement("div", { key: fieldName, className: "space-y-1", __self: this, __source: {
700
705
  fileName: _jsxFileName,
701
- lineNumber: 808,
706
+ lineNumber: 815,
702
707
  columnNumber: 15
703
708
  } }, /* @__PURE__ */ React.createElement("label", { className: "block text-sm font-medium text-gray-700", __self: this, __source: {
704
709
  fileName: _jsxFileName,
705
- lineNumber: 809,
710
+ lineNumber: 816,
706
711
  columnNumber: 17
707
712
  } }, formatFieldName(fieldName)), /* @__PURE__ */ React.createElement("select", { value: currentValue === void 0 || currentValue === null ? "" : currentValue.toString(), onChange: (e) => {
708
713
  const value = e.target.value;
709
714
  handleChange(value === "" ? void 0 : value === "true");
710
715
  }, className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-web focus:border-green-web text-sm", __self: this, __source: {
711
716
  fileName: _jsxFileName,
712
- lineNumber: 812,
717
+ lineNumber: 819,
713
718
  columnNumber: 17
714
719
  } }, /* @__PURE__ */ React.createElement("option", { value: "", __self: this, __source: {
715
720
  fileName: _jsxFileName,
716
- lineNumber: 820,
721
+ lineNumber: 827,
717
722
  columnNumber: 19
718
723
  } }, "All"), /* @__PURE__ */ React.createElement("option", { value: "true", __self: this, __source: {
719
724
  fileName: _jsxFileName,
720
- lineNumber: 821,
725
+ lineNumber: 828,
721
726
  columnNumber: 19
722
727
  } }, "Yes"), /* @__PURE__ */ React.createElement("option", { value: "false", __self: this, __source: {
723
728
  fileName: _jsxFileName,
724
- lineNumber: 822,
729
+ lineNumber: 829,
725
730
  columnNumber: 19
726
731
  } }, "No")));
727
732
  }
728
733
  return /* @__PURE__ */ React.createElement("div", { key: fieldName, className: "space-y-1", __self: this, __source: {
729
734
  fileName: _jsxFileName,
730
- lineNumber: 830,
735
+ lineNumber: 837,
731
736
  columnNumber: 13
732
737
  } }, /* @__PURE__ */ React.createElement("label", { className: "block text-sm font-medium text-gray-700", __self: this, __source: {
733
738
  fileName: _jsxFileName,
734
- lineNumber: 831,
739
+ lineNumber: 838,
735
740
  columnNumber: 15
736
741
  } }, formatFieldName(fieldName)), /* @__PURE__ */ React.createElement("input", { type: "text", value: typeof currentValue === "string" ? currentValue : "", onChange: (e) => handleChange(e.target.value || void 0), placeholder: `Filter by ${formatFieldName(fieldName).toLowerCase()}...`, className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-web focus:border-green-web text-sm", __self: this, __source: {
737
742
  fileName: _jsxFileName,
738
- lineNumber: 834,
743
+ lineNumber: 841,
739
744
  columnNumber: 15
740
745
  } }));
741
746
  })));
742
747
  const searchFilter = /* @__PURE__ */ React.createElement("div", { className: "mb-4 flex items-center justify-between", __self: this, __source: {
743
748
  fileName: _jsxFileName,
744
- lineNumber: 849,
749
+ lineNumber: 856,
745
750
  columnNumber: 5
746
751
  } }, /* @__PURE__ */ React.createElement("div", { className: "flex-1 max-w-lg", __self: this, __source: {
747
752
  fileName: _jsxFileName,
748
- lineNumber: 850,
753
+ lineNumber: 857,
749
754
  columnNumber: 7
750
755
  } }, /* @__PURE__ */ React.createElement("label", { htmlFor: "search", className: "sr-only", __self: this, __source: {
751
756
  fileName: _jsxFileName,
752
- lineNumber: 851,
757
+ lineNumber: 858,
753
758
  columnNumber: 9
754
759
  } }, "Search"), /* @__PURE__ */ React.createElement("div", { className: "relative", __self: this, __source: {
755
760
  fileName: _jsxFileName,
756
- lineNumber: 854,
761
+ lineNumber: 861,
757
762
  columnNumber: 9
758
763
  } }, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none", __self: this, __source: {
759
764
  fileName: _jsxFileName,
760
- lineNumber: 855,
765
+ lineNumber: 862,
761
766
  columnNumber: 11
762
767
  } }, loading && search !== debouncedSearch ? /* @__PURE__ */ React.createElement("svg", { className: "animate-spin h-5 w-5 text-gray-400", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", __self: this, __source: {
763
768
  fileName: _jsxFileName,
764
- lineNumber: 857,
769
+ lineNumber: 864,
765
770
  columnNumber: 15
766
771
  } }, /* @__PURE__ */ React.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4", __self: this, __source: {
767
772
  fileName: _jsxFileName,
768
- lineNumber: 863,
773
+ lineNumber: 870,
769
774
  columnNumber: 17
770
775
  } }), /* @__PURE__ */ React.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z", __self: this, __source: {
771
776
  fileName: _jsxFileName,
772
- lineNumber: 871,
777
+ lineNumber: 878,
773
778
  columnNumber: 17
774
779
  } })) : /* @__PURE__ */ React.createElement("svg", { className: "h-5 w-5 text-gray-400", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", __self: this, __source: {
775
780
  fileName: _jsxFileName,
776
- lineNumber: 878,
781
+ lineNumber: 885,
777
782
  columnNumber: 15
778
783
  } }, /* @__PURE__ */ React.createElement("path", { fillRule: "evenodd", d: "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", clipRule: "evenodd", __self: this, __source: {
779
784
  fileName: _jsxFileName,
780
- lineNumber: 885,
785
+ lineNumber: 892,
781
786
  columnNumber: 17
782
787
  } }))), /* @__PURE__ */ React.createElement("input", { id: "search", name: "search", className: "block w-full pl-10 pr-12 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-green-web focus:border-green-web sm:text-sm", placeholder: `Search ${searchFields.length > 0 ? searchFields.map((field) => field.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (str) => str.toUpperCase())).join(", ") : getPluralName(model.name).toLowerCase()}...`, type: "search", value: state.search || "", onChange: (e) => dispatch({
783
788
  type: "SET_SEARCH",
784
789
  payload: e.target.value
785
790
  }), __self: this, __source: {
786
791
  fileName: _jsxFileName,
787
- lineNumber: 893,
792
+ lineNumber: 900,
788
793
  columnNumber: 11
789
794
  } }), searchableFieldNames.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "absolute inset-y-0 right-0 flex items-center", __self: this, __source: {
790
795
  fileName: _jsxFileName,
791
- lineNumber: 903,
796
+ lineNumber: 910,
792
797
  columnNumber: 13
793
798
  } }, searchFieldSelector))), /* @__PURE__ */ React.createElement("div", { className: "ml-4 flex space-x-2", __self: this, __source: {
794
799
  fileName: _jsxFileName,
795
- lineNumber: 909,
800
+ lineNumber: 916,
796
801
  columnNumber: 7
797
802
  } }, /* @__PURE__ */ React.createElement("button", { onClick: () => setShowFilters(!showFilters), className: `inline-flex items-center px-3 py-2 border text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web ${showFilters || Object.keys(filters).length > 0 ? "border-green-web text-green-web bg-green-50 hover:bg-green-100" : "border-gray-300 text-gray-700 bg-white hover:bg-gray-50"}`, __self: this, __source: {
798
803
  fileName: _jsxFileName,
799
- lineNumber: 910,
804
+ lineNumber: 917,
800
805
  columnNumber: 9
801
806
  } }, /* @__PURE__ */ React.createElement("svg", { className: "w-4 h-4 mr-2", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", __self: this, __source: {
802
807
  fileName: _jsxFileName,
803
- lineNumber: 918,
808
+ lineNumber: 925,
804
809
  columnNumber: 11
805
810
  } }, /* @__PURE__ */ React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z", __self: this, __source: {
806
811
  fileName: _jsxFileName,
807
- lineNumber: 919,
812
+ lineNumber: 926,
808
813
  columnNumber: 13
809
814
  } })), "Filters", Object.keys(filters).length > 0 && /* @__PURE__ */ React.createElement("span", { className: "ml-1 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-white bg-green-web rounded-full", __self: this, __source: {
810
815
  fileName: _jsxFileName,
811
- lineNumber: 928,
816
+ lineNumber: 935,
812
817
  columnNumber: 13
813
818
  } }, Object.keys(filters).length)), columnSelector, /* @__PURE__ */ React.createElement(Link, { to: createLink, className: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-600", __self: this, __source: {
814
819
  fileName: _jsxFileName,
815
- lineNumber: 934,
820
+ lineNumber: 941,
816
821
  columnNumber: 9
817
822
  } }, "Create New")));
818
823
  return /* @__PURE__ */ React.createElement(React.Fragment, null, filterPanel, searchFilter, /* @__PURE__ */ React.createElement(DataTable, { data: items, path: createLink.replace("/create", ""), fields: visibleColumns.length > 0 ? visibleColumns : fieldNames, pagination, setSkip: (skip2) => dispatch({
@@ -820,7 +825,7 @@ function AdminDataListPage({
820
825
  payload: skip2
821
826
  }), sort: state.sort, setSort: setSortSafely, __self: this, __source: {
822
827
  fileName: _jsxFileName,
823
- lineNumber: 948,
828
+ lineNumber: 955,
824
829
  columnNumber: 7
825
830
  } }));
826
831
  }
@@ -829,7 +834,7 @@ function AdminDataErrorBoundary({
829
834
  }) {
830
835
  return /* @__PURE__ */ React.createElement(ErrorBoundary, { error, __self: this, __source: {
831
836
  fileName: _jsxFileName,
832
- lineNumber: 962,
837
+ lineNumber: 969,
833
838
  columnNumber: 10
834
839
  } });
835
840
  }
@@ -17,7 +17,7 @@ export declare function getMutationName(model: DatabaseModel, operation: 'create
17
17
  /**
18
18
  * Build form fields for a model and operation
19
19
  */
20
- export declare function buildFormFields(sdk: any, model: DatabaseModel, operation: 'create' | 'update', currentItem?: any, isSubmitting?: boolean, basePath?: string): FormField[];
20
+ export declare function buildFormFields(sdk: any, model: DatabaseModel, operation: 'create' | 'update', currentItem?: any, isSubmitting?: boolean, basePath?: string, databaseModels?: DatabaseModel[]): FormField[];
21
21
  /**
22
22
  * Clean form input data for GraphQL mutations
23
23
  * Removes Apollo metadata and system fields
@@ -88,7 +88,19 @@ function toLowerCamelCase(str) {
88
88
  if (!str) return "";
89
89
  return str.charAt(0).toLowerCase() + str.slice(1);
90
90
  }
91
- function buildFormFields(sdk, model, operation, currentItem, isSubmitting, basePath = "/admin/data") {
91
+ function findForeignKeyFieldName(relatedModel, currentModelName, relationName) {
92
+ var _a;
93
+ if (!relatedModel) return null;
94
+ const foreignKeyField = relatedModel.fields.find((f) => {
95
+ if (f.type !== currentModelName) return false;
96
+ if (!f.relationName) return false;
97
+ if (f.isList) return false;
98
+ if (relationName && f.relationName !== relationName) return false;
99
+ return true;
100
+ });
101
+ return ((_a = foreignKeyField == null ? void 0 : foreignKeyField.relationFromFields) == null ? void 0 : _a[0]) || null;
102
+ }
103
+ function buildFormFields(sdk, model, operation, currentItem, isSubmitting, basePath = "/admin/data", databaseModels) {
92
104
  const editableFields = model.fields.filter((field) => {
93
105
  if (operation === "create" && field.isId) return false;
94
106
  if (field.isReadOnly || field.isGenerated) return false;
@@ -291,16 +303,17 @@ function buildFormFields(sdk, model, operation, currentItem, isSubmitting, baseP
291
303
  formFields.push(formField);
292
304
  });
293
305
  listRelationFields.forEach((field) => {
294
- var _a, _b;
306
+ var _a;
295
307
  const label = field.name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (str) => str.toUpperCase());
296
308
  if (operation === "update" && currentItem) {
297
309
  const pluralModelName = getPluralName(field.type);
298
310
  const relatedModelKebab = toKebabCase(pluralModelName);
299
311
  const displayName = field.type.replace(/([a-z])([A-Z])/g, "$1 $2");
300
312
  const pluralDisplayName = getPluralName(displayName);
301
- const foreignKeyFieldName = ((_a = field.relationToFields) == null ? void 0 : _a[0]) || `${toLowerCamelCase(model.name)}Id`;
313
+ const relatedModel = databaseModels == null ? void 0 : databaseModels.find((m) => m.name === field.type);
314
+ const foreignKeyFieldName = findForeignKeyFieldName(relatedModel, model.name, field.relationName) || `${toLowerCamelCase(model.name)}Id`;
302
315
  const filterUrl = `${basePath}/${relatedModelKebab}?${foreignKeyFieldName}=${currentItem.id}`;
303
- const countData = (_b = currentItem._count) == null ? void 0 : _b[field.name];
316
+ const countData = (_a = currentItem._count) == null ? void 0 : _a[field.name];
304
317
  const countText = countData !== void 0 ? ` (${countData})` : "";
305
318
  const formField = FormFieldClass.content(field.name, {
306
319
  content: React.createElement("div", { className: "py-2" }, [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestledjs/data-browser",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Universal admin data browser for Nestled framework projects with full CRUD operations",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -39,7 +39,7 @@
39
39
  "@heroicons/react": "^2.0.0",
40
40
  "@nestledjs/forms": "^0.5.0",
41
41
  "@nestledjs/helpers": "^0.1.0",
42
- "@nestledjs/shared-components": "^0.1.12",
42
+ "@nestledjs/shared-components": "^0.1.15",
43
43
  "react": "^19.0.0",
44
44
  "react-router": "^7.0.0"
45
45
  },