es-application-template 0.0.7 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5403,19 +5403,26 @@ const Sidebar = (props) => {
5403
5403
  toggleSidebar();
5404
5404
  }, 400);
5405
5405
  };
5406
- const sidebar = (jsxRuntime.jsxs("div", { tabIndex: 0, className: classNames__default["default"]({ 'd-none': !open }), onKeyDown: (e) => {
5406
+ const sidebar = (jsxRuntime.jsxs("div", { tabIndex: 0, className: classNames__default["default"]('es-application-sidebar-root', {
5407
+ 'is-hidden': !open
5408
+ }), onKeyDown: (e) => {
5407
5409
  if ((!keyboard) && e.code === 'Escape' && flag) {
5408
5410
  handClose();
5409
5411
  e.stopPropagation();
5410
5412
  e.preventDefault();
5411
5413
  }
5412
- }, children: [jsxRuntime.jsx("div", { className: classNames__default["default"]('offcanvas-backdrop fade', backdropClassName, {
5413
- 'd-none': !flag || hiddenBackground,
5414
+ }, children: [jsxRuntime.jsx("div", { className: classNames__default["default"]('es-application-sidebar-backdrop', backdropClassName, {
5415
+ 'is-hidden': !flag || hiddenBackground,
5414
5416
  show: flag
5415
- }), style: { zIndex: zIndex - 1 }, onClick: handClose }), jsxRuntime.jsxs("div", { className: classNames__default["default"]('customizer d-none d-md-block ', `customizer-${width ?? 600}`, panelClassName, {
5417
+ }), style: { zIndex: zIndex - 1 }, onClick: handClose }), jsxRuntime.jsxs("div", { className: classNames__default["default"]('es-application-sidebar', panelClassName, {
5416
5418
  open: flag,
5417
5419
  fullscreen: isFullScreen
5418
- }), style: { zIndex }, children: [jsxRuntime.jsxs("div", { style: {
5420
+ }), style: {
5421
+ '--es-application-sidebar-width': typeof width === 'number'
5422
+ ? `${width}px`
5423
+ : width ?? '600px',
5424
+ zIndex
5425
+ }, children: [jsxRuntime.jsxs("div", { style: {
5419
5426
  position: 'fixed',
5420
5427
  top: '50%',
5421
5428
  transform: 'translate(-50%, -50%)',
@@ -5577,83 +5584,156 @@ const ConfigTreeJsonDrawer = ({ open, onClose, rows, mode = 'VIEW', onApply, fil
5577
5584
  return (jsxRuntime.jsx(Sidebar, { open: open, toggleSidebar: onClose, width: 760, keyboard: true, hiddenFullScreenButton: true, zIndex: 1061, children: jsxRuntime.jsxs("div", { className: 'h-100 bg-white d-flex flex-column', children: [jsxRuntime.jsx(ModalHeader, { typeModal: 'Detail', handleModal: onClose, title: mode === 'IMPORT' ? 'Nhập JSON thiết lập' : 'Xem / Xuất JSON thiết lập' }), mode === 'VIEW' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: 'd-flex justify-content-end gap-50 border-bottom p-1', children: [jsxRuntime.jsx(reactstrap.Button, { type: 'button', color: 'outline-primary', className: 'be-button', disabled: currentPreview.errors.length > 0, onClick: () => { void handleCopy(); }, children: "Sao ch\u00E9p" }), jsxRuntime.jsx(reactstrap.Button, { type: 'button', color: 'primary', className: 'be-button', disabled: currentPreview.errors.length > 0, onClick: () => downloadJson(currentJson, fileName), children: "T\u1EA3i file JSON" })] }), jsxRuntime.jsx("div", { className: 'flex-grow-1 overflow-auto', style: { minHeight: 0 }, children: jsxRuntime.jsx("div", { className: 'p-1', children: currentPreview.errors.length > 0 ? (jsxRuntime.jsxs("div", { className: 'alert alert-danger py-50 px-1 mb-0', children: [jsxRuntime.jsx("div", { className: 'fw-bolder mb-25', children: "D\u1EEF li\u1EC7u nh\u00E1p ch\u01B0a h\u1EE3p l\u1EC7:" }), currentPreview.errors.slice(0, 5).map((error, index) => (jsxRuntime.jsxs("div", { children: ["\u2022 ", error] }, `${index}-${error}`)))] })) : (jsxRuntime.jsx("pre", { className: 'bg-light border rounded p-1 mb-0', children: currentJson })) }) })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: 'border-bottom p-1', children: [jsxRuntime.jsxs("div", { className: 'd-flex align-items-end gap-1 flex-wrap', children: [jsxRuntime.jsxs("div", { style: { width: 190 }, children: [jsxRuntime.jsx(reactstrap.Label, { className: 'mb-25', children: "Ch\u1EBF \u0111\u1ED9 nh\u1EADp" }), jsxRuntime.jsxs(reactstrap.Input, { type: 'select', value: importMode, onChange: event => setImportMode(event.target.value), children: [jsxRuntime.jsx("option", { value: 'MERGE', children: "H\u1EE3p nh\u1EA5t (khuy\u1EBFn ngh\u1ECB)" }), jsxRuntime.jsx("option", { value: 'REPLACE', children: "Thay th\u1EBF to\u00E0n b\u1ED9" })] })] }), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("input", { ref: fileInputRef, type: 'file', accept: '.json,application/json', hidden: true, onChange: event => { void handleFileChange(event); } }), jsxRuntime.jsx(reactstrap.Button, { type: 'button', color: 'outline-primary', className: 'be-button', onClick: () => fileInputRef.current?.click(), children: "Ch\u1ECDn file .json" })] })] }), jsxRuntime.jsx("div", { className: 'small text-muted mt-50', children: "H\u1EE3p nh\u1EA5t: key m\u1EDBi \u0111\u01B0\u1EE3c th\u00EAm, key tr\u00F9ng \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt, key kh\u00E1c gi\u1EEF nguy\u00EAn. Thay th\u1EBF: d\u00F9ng JSON import l\u00E0m to\u00E0n b\u1ED9 d\u1EEF li\u1EC7u nh\u00E1p hi\u1EC7n t\u1EA1i." })] }), jsxRuntime.jsxs("div", { className: 'flex-grow-1 min-h-0 d-flex flex-column p-1', children: [jsxRuntime.jsx(reactstrap.Label, { for: 'config-json-import', className: 'mb-50', children: "D\u00E1n JSON ho\u1EB7c ch\u1ECDn file" }), jsxRuntime.jsx(reactstrap.Input, { id: 'config-json-import', type: 'textarea', className: 'font-monospace flex-grow-1', style: { minHeight: 280, resize: 'none' }, value: importText, placeholder: 'D\u00E1n JSON thi\u1EBFt l\u1EADp t\u1EA1i \u0111\u00E2y...', onChange: event => setImportText(event.target.value) }), !importText.trim() ? (jsxRuntime.jsx("div", { className: 'alert alert-info py-50 px-1 mt-1 mb-0', children: "Ch\u01B0a c\u00F3 d\u1EEF li\u1EC7u import. C\u00F3 th\u1EC3 d\u00F9ng ChatGPT t\u1EA1o file JSON r\u1ED3i d\u00E1n ho\u1EB7c t\u1EA3i file l\u00EAn." })) : importPreview.errors.length > 0 ? (jsxRuntime.jsxs("div", { className: 'alert alert-danger py-50 px-1 mt-1 mb-0', children: [jsxRuntime.jsx("div", { className: 'fw-bolder mb-25', children: "JSON ch\u01B0a h\u1EE3p l\u1EC7:" }), importPreview.errors.slice(0, 5).map((error, index) => (jsxRuntime.jsxs("div", { children: ["\u2022 ", error] }, `${index}-${error}`))), importPreview.errors.length > 5 ? (jsxRuntime.jsxs("div", { children: ["\u2022 ... v\u00E0 ", importPreview.errors.length - 5, " l\u1ED7i kh\u00E1c"] })) : null] })) : (jsxRuntime.jsxs("div", { className: 'alert alert-success py-50 px-1 mt-1 mb-0', children: ["JSON h\u1EE3p l\u1EC7. Sau khi \u00E1p d\u1EE5ng, Grid s\u1EBD c\u00F3 ", countConfigNodes(importPreview.rows), " node. D\u1EEF li\u1EC7u ch\u1EC9 l\u00E0 nh\u00E1p cho \u0111\u1EBFn khi b\u1EA5m L\u01B0u \u1EDF m\u00E0n ch\u00EDnh."] }))] }), importMode === 'REPLACE' ? (jsxRuntime.jsx("div", { className: 'alert alert-warning rounded-0 border-start-0 border-end-0 mb-0 py-50 px-1', children: "Ch\u1EBF \u0111\u1ED9 Thay th\u1EBF s\u1EBD b\u1ECF to\u00E0n b\u1ED9 key hi\u1EC7n c\u00F3 kh\u1ECFi d\u1EEF li\u1EC7u nh\u00E1p tr\u01B0\u1EDBc khi \u00E1p d\u1EE5ng JSON import." })) : null, jsxRuntime.jsxs("div", { className: 'd-flex justify-content-end gap-50 border-top p-1', children: [jsxRuntime.jsx(reactstrap.Button, { type: 'button', color: 'secondary', outline: true, className: 'be-button', onClick: onClose, children: "H\u1EE7y" }), jsxRuntime.jsx(reactstrap.Button, { type: 'button', color: 'primary', className: 'be-button', disabled: !importPreview.ready || !onApply, onClick: handleApply, children: "\u00C1p d\u1EE5ng v\u00E0o Grid" })] })] }))] }) }));
5578
5585
  };
5579
5586
 
5580
- const SELECT_MENU_PORTAL_STYLES = {
5581
- menuPortal: (base) => ({
5582
- ...base,
5583
- zIndex: 1100
5584
- })
5585
- };
5586
- const UI_KEY_SAMPLES = [
5587
+ const UI_RULES = [
5588
+ {
5589
+ id: 'list-grid-visible',
5590
+ section: 'list-grid',
5591
+ category: 'ui-list',
5592
+ pattern: 'ui.list.grid.<field>.visible',
5593
+ type: 'boolean',
5594
+ defaultValue: true,
5595
+ examples: ['ui.list.grid.email.visible'],
5596
+ description: 'Ẩn hoặc hiện một cột trên Grid của danh sách.'
5597
+ },
5587
5598
  {
5599
+ id: 'list-toolbar-visible',
5600
+ section: 'list-grid',
5601
+ category: 'ui-list',
5588
5602
  pattern: 'ui.list.toolbar.<keyCode>.visible',
5589
5603
  type: 'boolean',
5590
- examples: ['ui.list.toolbar.status.visible']
5604
+ defaultValue: true,
5605
+ examples: ['ui.list.toolbar.status.visible'],
5606
+ description: 'Ẩn hoặc hiện một điều khiển trên toolbar của danh sách.'
5591
5607
  },
5592
5608
  {
5609
+ id: 'list-toolbar-disabled',
5610
+ section: 'list-grid',
5611
+ category: 'ui-list',
5593
5612
  pattern: 'ui.list.toolbar.<keyCode>.disabled',
5594
5613
  type: 'boolean',
5595
- examples: ['ui.list.toolbar.status.disabled']
5614
+ defaultValue: false,
5615
+ examples: ['ui.list.toolbar.status.disabled'],
5616
+ description: 'Bật hoặc khóa một điều khiển trên toolbar của danh sách.'
5596
5617
  },
5597
5618
  {
5619
+ id: 'list-action-visible',
5620
+ section: 'list-grid',
5621
+ category: 'ui-list',
5598
5622
  pattern: 'ui.list.action.<id>.visible',
5599
5623
  type: 'boolean',
5600
- examples: ['ui.list.action.DELETE.visible']
5624
+ defaultValue: true,
5625
+ examples: ['ui.list.action.DELETE.visible'],
5626
+ description: 'Ẩn hoặc hiện một action của dòng hay danh sách.'
5601
5627
  },
5602
5628
  {
5629
+ id: 'list-action-disabled',
5630
+ section: 'list-grid',
5631
+ category: 'ui-list',
5603
5632
  pattern: 'ui.list.action.<id>.disabled',
5604
5633
  type: 'boolean',
5605
- examples: ['ui.list.action.DELETE.disabled']
5634
+ defaultValue: false,
5635
+ examples: ['ui.list.action.DELETE.disabled'],
5636
+ description: 'Bật hoặc khóa một action của dòng hay danh sách.'
5606
5637
  },
5607
5638
  {
5639
+ id: 'form-field-visible',
5640
+ section: 'form-field',
5641
+ category: 'ui-form',
5608
5642
  pattern: 'ui.form.field.<name>.visible',
5609
5643
  type: 'boolean',
5610
- examples: ['ui.form.field.email.visible']
5644
+ defaultValue: true,
5645
+ examples: ['ui.form.field.email.visible'],
5646
+ description: 'Ẩn hoặc hiện field trên form.'
5611
5647
  },
5612
5648
  {
5649
+ id: 'form-field-required',
5650
+ section: 'form-field',
5651
+ category: 'ui-form',
5613
5652
  pattern: 'ui.form.field.<name>.required',
5614
5653
  type: 'boolean',
5615
- examples: ['ui.form.field.email.required']
5654
+ defaultValue: false,
5655
+ examples: ['ui.form.field.email.required'],
5656
+ description: 'Quy định field có bắt buộc nhập hay không.'
5616
5657
  },
5617
5658
  {
5659
+ id: 'form-field-disabled',
5660
+ section: 'form-field',
5661
+ category: 'ui-form',
5618
5662
  pattern: 'ui.form.field.<name>.disabled',
5619
5663
  type: 'boolean',
5620
- examples: ['ui.form.field.email.disabled']
5664
+ defaultValue: false,
5665
+ examples: ['ui.form.field.email.disabled'],
5666
+ description: 'Bật hoặc khóa tương tác với field.'
5621
5667
  },
5622
5668
  {
5669
+ id: 'form-field-readonly',
5670
+ section: 'form-field',
5671
+ category: 'ui-form',
5623
5672
  pattern: 'ui.form.field.<name>.readOnly',
5624
5673
  type: 'boolean',
5625
- examples: ['ui.form.field.email.readOnly']
5674
+ defaultValue: false,
5675
+ examples: ['ui.form.field.email.readOnly'],
5676
+ description: 'Quy định field chỉ đọc hay có thể chỉnh sửa.'
5626
5677
  },
5627
5678
  {
5679
+ id: 'form-action-visible',
5680
+ section: 'form-action',
5681
+ category: 'ui-form',
5628
5682
  pattern: 'ui.form.action.<id>.visible',
5629
5683
  type: 'boolean',
5630
- examples: ['ui.form.action.SAVE.visible']
5684
+ defaultValue: true,
5685
+ examples: ['ui.form.action.SAVE.visible'],
5686
+ description: 'Ẩn hoặc hiện một action trên form.'
5631
5687
  },
5632
5688
  {
5689
+ id: 'form-action-disabled',
5690
+ section: 'form-action',
5691
+ category: 'ui-form',
5633
5692
  pattern: 'ui.form.action.<id>.disabled',
5634
5693
  type: 'boolean',
5635
- examples: ['ui.form.action.SAVE.disabled']
5694
+ defaultValue: false,
5695
+ examples: ['ui.form.action.SAVE.disabled'],
5696
+ description: 'Bật hoặc khóa một action trên form.'
5636
5697
  },
5637
5698
  {
5699
+ id: 'form-tab-visible',
5700
+ section: 'form-structure',
5701
+ category: 'ui-form',
5638
5702
  pattern: 'ui.form.tab.<key>.visible',
5639
5703
  type: 'boolean',
5640
- examples: ['ui.form.tab.permission.visible']
5704
+ defaultValue: true,
5705
+ examples: ['ui.form.tab.permission.visible'],
5706
+ description: 'Ẩn hoặc hiện tab trên form.'
5641
5707
  },
5642
5708
  {
5709
+ id: 'form-step-visible',
5710
+ section: 'form-structure',
5711
+ category: 'ui-form',
5643
5712
  pattern: 'ui.form.step.<key>.visible',
5644
5713
  type: 'boolean',
5645
- examples: ['ui.form.step.confirm.visible']
5714
+ defaultValue: true,
5715
+ examples: ['ui.form.step.confirm.visible'],
5716
+ description: 'Ẩn hoặc hiện một bước trong quy trình form.'
5646
5717
  },
5647
5718
  {
5719
+ id: 'form-section-visible',
5720
+ section: 'form-structure',
5721
+ category: 'ui-form',
5648
5722
  pattern: 'ui.form.section.<key>.visible',
5649
5723
  type: 'boolean',
5650
- examples: ['ui.form.section.security.visible']
5724
+ defaultValue: true,
5725
+ examples: ['ui.form.section.security.visible'],
5726
+ description: 'Ẩn hoặc hiện một khu vực trên form.'
5651
5727
  }
5652
5728
  ];
5653
- const EXPLICIT_KEY_SAMPLES = [
5729
+ const NON_UI_RULES = [
5654
5730
  {
5731
+ id: 'behavior-boolean',
5732
+ section: 'other-settings',
5733
+ category: 'behavior',
5655
5734
  pattern: 'behavior.<meaning>',
5656
5735
  type: 'boolean',
5736
+ sampleValue: true,
5657
5737
  examples: [
5658
5738
  'behavior.allowDelete',
5659
5739
  'behavior.allowEdit',
@@ -5663,182 +5743,389 @@ const EXPLICIT_KEY_SAMPLES = [
5663
5743
  'behavior.allowChangeStatus',
5664
5744
  'behavior.allowSort',
5665
5745
  'behavior.autoSubmit'
5666
- ]
5746
+ ],
5747
+ description: 'Điều khiển một hành vi nghiệp vụ có ý nghĩa ổn định.'
5667
5748
  },
5668
5749
  {
5750
+ id: 'default-number',
5751
+ section: 'other-settings',
5752
+ category: 'default',
5669
5753
  pattern: 'default.<fieldOrMeaning>',
5670
5754
  type: 'number',
5671
- examples: [
5672
- 'default.status',
5673
- 'default.pageSize'
5674
- ]
5755
+ sampleValue: 1,
5756
+ examples: ['default.status', 'default.pageSize'],
5757
+ description: 'Giá trị số mặc định cho field hoặc hành vi.'
5675
5758
  },
5676
5759
  {
5760
+ id: 'default-string',
5761
+ section: 'other-settings',
5762
+ category: 'default',
5677
5763
  pattern: 'default.<fieldOrMeaning>',
5678
5764
  type: 'string',
5679
- examples: [
5680
- 'default.note',
5681
- 'default.currencyCode'
5682
- ]
5765
+ sampleValue: 'VND',
5766
+ examples: ['default.note', 'default.currencyCode'],
5767
+ description: 'Giá trị chuỗi mặc định cho field hoặc hành vi.'
5683
5768
  },
5684
5769
  {
5770
+ id: 'default-date',
5771
+ section: 'other-settings',
5772
+ category: 'default',
5685
5773
  pattern: 'default.<fieldOrMeaning>',
5686
5774
  type: 'date',
5687
- examples: ['default.startDate']
5775
+ sampleValue: '2026-01-01',
5776
+ examples: ['default.startDate'],
5777
+ description: 'Giá trị ngày mặc định.'
5688
5778
  },
5689
5779
  {
5780
+ id: 'default-datetime',
5781
+ section: 'other-settings',
5782
+ category: 'default',
5690
5783
  pattern: 'default.<fieldOrMeaning>',
5691
5784
  type: 'datetime',
5692
- examples: ['default.executeAt']
5785
+ sampleValue: '2026-01-01T08:00:00+07:00',
5786
+ examples: ['default.executeAt'],
5787
+ description: 'Giá trị ngày giờ mặc định.'
5693
5788
  },
5694
5789
  {
5790
+ id: 'default-guid',
5791
+ section: 'other-settings',
5792
+ category: 'default',
5695
5793
  pattern: 'default.<fieldOrMeaning>',
5696
5794
  type: 'guid',
5697
- examples: [
5698
- 'default.departmentId',
5699
- 'default.warehouseId'
5700
- ]
5795
+ sampleValue: '00000000-0000-0000-0000-000000000001',
5796
+ examples: ['default.departmentId', 'default.warehouseId'],
5797
+ description: 'Giá trị định danh mặc định.'
5701
5798
  },
5702
5799
  {
5800
+ id: 'option-json',
5801
+ section: 'other-settings',
5802
+ category: 'option',
5703
5803
  pattern: 'option.<meaning>',
5704
5804
  type: 'json',
5805
+ sampleValue: [0, 1],
5705
5806
  examples: [
5706
5807
  'option.allowedStatuses',
5707
5808
  'option.allowedTypes',
5708
5809
  'option.exportFormats'
5709
- ]
5810
+ ],
5811
+ description: 'Danh sách hoặc cấu trúc tùy chọn có schema rõ ràng.'
5710
5812
  },
5711
5813
  {
5814
+ id: 'custom-domain-boolean',
5815
+ section: 'other-settings',
5816
+ category: 'custom',
5712
5817
  pattern: '<domain>.<meaning>',
5713
5818
  type: 'boolean',
5819
+ sampleValue: true,
5714
5820
  examples: [
5715
5821
  'approval.autoSubmit',
5716
5822
  'approval.requireComment',
5717
5823
  'password.requireChangeOnFirstLogin',
5718
5824
  'confirmation.requireRedeployConfirmation'
5719
- ]
5825
+ ],
5826
+ description: 'Cờ tùy chỉnh cho một miền nghiệp vụ chưa thuộc pattern chung.'
5720
5827
  },
5721
5828
  {
5829
+ id: 'custom-domain-number',
5830
+ section: 'other-settings',
5831
+ category: 'custom',
5722
5832
  pattern: '<domain>.<meaning>',
5723
5833
  type: 'number',
5834
+ sampleValue: 30,
5724
5835
  examples: [
5725
5836
  'integration.timeoutSeconds',
5726
5837
  'integration.retryCount',
5727
5838
  'calculation.decimalPlaces'
5728
- ]
5839
+ ],
5840
+ description: 'Giá trị số tùy chỉnh cho một miền nghiệp vụ.'
5729
5841
  },
5730
5842
  {
5843
+ id: 'custom-domain-string',
5844
+ section: 'other-settings',
5845
+ category: 'custom',
5731
5846
  pattern: '<domain>.<meaning>',
5732
5847
  type: 'string',
5733
- examples: ['calculation.roundingMode']
5848
+ sampleValue: 'HALF_UP',
5849
+ examples: ['calculation.roundingMode'],
5850
+ description: 'Giá trị chuỗi tùy chỉnh cho một miền nghiệp vụ.'
5734
5851
  },
5735
5852
  {
5853
+ id: 'custom-root-number',
5854
+ section: 'other-settings',
5855
+ category: 'custom',
5736
5856
  pattern: '<key>',
5737
5857
  type: 'number',
5738
- examples: ['maxDetailRows']
5858
+ sampleValue: 100,
5859
+ examples: ['maxDetailRows'],
5860
+ description: 'Key gốc chỉ dùng khi không có namespace phù hợp và ý nghĩa vẫn rõ ràng.'
5739
5861
  }
5740
5862
  ];
5741
- const KEY_SAMPLES = [
5742
- ...UI_KEY_SAMPLES,
5743
- ...EXPLICIT_KEY_SAMPLES
5863
+ const SETTING_KEY_RULES = [
5864
+ ...UI_RULES,
5865
+ ...NON_UI_RULES
5744
5866
  ];
5745
- const CATEGORY_OPTIONS = [
5746
- {
5747
- value: 'ui-list',
5748
- label: 'UI danh sách'
5749
- },
5750
- {
5751
- value: 'ui-form',
5752
- label: 'UI biểu mẫu'
5753
- },
5754
- {
5755
- value: 'behavior',
5756
- label: 'Hành vi'
5757
- },
5758
- {
5759
- value: 'default',
5760
- label: 'Mặc định'
5761
- },
5762
- {
5763
- value: 'option',
5764
- label: 'Tùy chọn'
5867
+ const SETTING_KEY_CONVENTION = {
5868
+ id: 'becoxy-setting-key-convention',
5869
+ version: 1,
5870
+ title: 'BECoxy Setting Key Convention',
5871
+ objective: [
5872
+ 'Các thiết lập UI và behavior phải được đọc qua setting.get(key, fallback).',
5873
+ 'Không hard-code cấu hình nếu giá trị cần được quản trị từ Setting.'
5874
+ ],
5875
+ keyStructure: {
5876
+ pattern: '<Resource>.<setting-key>',
5877
+ explanation: [
5878
+ 'Resource là mã ổn định của feature hoặc tài nguyên đang được cấu hình.',
5879
+ 'Phần setting key phải tuân thủ các pattern bên dưới.',
5880
+ 'Không nhúng productCode, userId hoặc scope vào key.'
5881
+ ],
5882
+ examples: [
5883
+ 'RESOURCE_CODE.ui.list.toolbar.status.visible',
5884
+ 'RESOURCE_CODE.ui.form.field.email.required',
5885
+ 'RESOURCE_CODE.ui.form.action.SAVE.disabled'
5886
+ ]
5765
5887
  },
5766
- {
5767
- value: 'custom',
5768
- label: 'Tùy chỉnh'
5769
- }
5770
- ];
5771
- const TYPE_OPTIONS = Array.from(new Set(KEY_SAMPLES.map(row => row.type))).map(value => ({
5772
- value,
5773
- label: value
5774
- }));
5775
- const getSampleCategory = (pattern) => {
5776
- if (pattern.startsWith('ui.list.')) {
5777
- return 'ui-list';
5778
- }
5779
- if (pattern.startsWith('ui.form.')) {
5780
- return 'ui-form';
5781
- }
5782
- if (pattern.startsWith('behavior.')) {
5783
- return 'behavior';
5784
- }
5785
- if (pattern.startsWith('default.')) {
5786
- return 'default';
5888
+ categories: [
5889
+ { value: 'ui-list', label: 'UI danh sách' },
5890
+ { value: 'ui-form', label: 'UI biểu mẫu' },
5891
+ { value: 'behavior', label: 'Hành vi' },
5892
+ { value: 'default', label: 'Mặc định' },
5893
+ { value: 'option', label: 'Tùy chọn' },
5894
+ { value: 'custom', label: 'Tùy chỉnh' }
5895
+ ],
5896
+ ruleSections: [
5897
+ {
5898
+ id: 'list-grid',
5899
+ title: 'LIST / GRID',
5900
+ description: 'Cấu hình cột Grid, toolbar và action trên màn danh sách.'
5901
+ },
5902
+ {
5903
+ id: 'form-field',
5904
+ title: 'FORM FIELD',
5905
+ description: 'Cấu hình trạng thái hiển thị và tương tác của field.'
5906
+ },
5907
+ {
5908
+ id: 'form-action',
5909
+ title: 'FORM ACTION',
5910
+ description: 'Cấu hình action trên form.'
5911
+ },
5912
+ {
5913
+ id: 'form-structure',
5914
+ title: 'FORM STRUCTURE',
5915
+ description: 'Cấu hình tab, step và section trên form.'
5916
+ },
5917
+ {
5918
+ id: 'other-settings',
5919
+ title: 'BEHAVIOR / DEFAULT / OPTION / CUSTOM',
5920
+ description: 'Các pattern ngoài UI chỉ dùng khi đúng ý nghĩa nghiệp vụ.'
5921
+ }
5922
+ ],
5923
+ rules: SETTING_KEY_RULES,
5924
+ guidance: [
5925
+ {
5926
+ title: 'CÁCH ĐỌC SETTING',
5927
+ paragraphs: ['Chỉ dùng public API của package:'],
5928
+ examples: [
5929
+ 'setting.get<T>(key)',
5930
+ 'setting.get<T>(key, fallback)',
5931
+ "setting.get('RESOURCE_CODE.ui.form.field.Code.visible', true)",
5932
+ "setting.get('RESOURCE_CODE.ui.form.action.SAVE.disabled', false)"
5933
+ ],
5934
+ items: [
5935
+ 'Không tạo useSetting(resource).',
5936
+ 'Không tạo useFeatureSetting hoặc useFieldSetting.',
5937
+ 'Không tạo getBoolean, getNumber hoặc getJson.',
5938
+ 'Không tạo SettingResourceScope.'
5939
+ ]
5940
+ },
5941
+ {
5942
+ title: 'FALLBACK',
5943
+ paragraphs: ['Boolean UI setting phải có fallback rõ ràng khi phù hợp.'],
5944
+ items: [
5945
+ 'visible: fallback thường là true.',
5946
+ 'disabled: fallback thường là false.',
5947
+ 'required: fallback thường là false.',
5948
+ 'readOnly: fallback thường là false.'
5949
+ ]
5950
+ },
5951
+ {
5952
+ title: 'NAMING',
5953
+ items: [
5954
+ 'Không dùng label hiển thị hoặc nội dung đã dịch làm key.',
5955
+ 'ID, action và key phải ổn định khi thay đổi ngôn ngữ.',
5956
+ 'Không nhúng productCode, userId hoặc scope vào key.',
5957
+ 'Sai: ui.form.field.Tên người dùng.visible.',
5958
+ 'Đúng: ui.form.field.username.visible.',
5959
+ 'Sai: PRODUCT.RESOURCE_CODE.ui.form.field.Code.visible.',
5960
+ 'Đúng: RESOURCE_CODE.ui.form.field.Code.visible.'
5961
+ ]
5962
+ },
5963
+ {
5964
+ title: 'SCOPE / PRECEDENCE',
5965
+ paragraphs: ['Effective Setting được giải quyết theo thứ tự SYSTEM → PRODUCT → USER → setting.get(...).'],
5966
+ items: [
5967
+ 'Feature code không tự xử lý precedence.',
5968
+ 'Feature code không tự request riêng SYSTEM, PRODUCT và USER.',
5969
+ 'Scope, Product và User được runtime quản lý ngoài key.'
5970
+ ]
5971
+ },
5972
+ {
5973
+ title: 'KHI THÊM SETTING MỚI',
5974
+ items: [
5975
+ 'Kiểm tra pattern hiện có và ưu tiên reuse convention.',
5976
+ 'Chỉ tạo namespace mới khi behavior không thuộc pattern hiện có.',
5977
+ 'Xác định type, fallback và description rõ nghĩa.',
5978
+ 'Thêm key vào Setting catalog khi cần quản trị.',
5979
+ 'Không tạo API wrapper riêng chỉ cho một key.'
5980
+ ]
5981
+ },
5982
+ {
5983
+ title: 'VÍ DỤ ÁP DỤNG',
5984
+ examples: [
5985
+ "const canShowCode = setting.get('RESOURCE_CODE.ui.form.field.Code.visible', true)",
5986
+ "const codeRequired = setting.get('RESOURCE_CODE.ui.form.field.Code.required', false)",
5987
+ "const saveDisabled = setting.get('RESOURCE_CODE.ui.form.action.SAVE.disabled', false)"
5988
+ ]
5989
+ },
5990
+ {
5991
+ title: 'NGUYÊN TẮC CHO AI/CODEX',
5992
+ items: [
5993
+ 'Đọc source hiện tại trước khi thay đổi.',
5994
+ 'Xác định resource hiện tại.',
5995
+ 'Liệt kê component, action và field cần cấu hình.',
5996
+ 'Ánh xạ vào convention có sẵn; không tự phát minh key khi pattern đã bao phủ.',
5997
+ 'Dùng setting.get(..., fallback).',
5998
+ 'Không thêm hook hoặc public Setting API mới.',
5999
+ 'Không thay đổi Setting Runtime.',
6000
+ 'Không thêm selector Scope, Product hoặc User vào feature.',
6001
+ 'Chỉ sửa các file thực sự sử dụng setting.'
6002
+ ]
6003
+ }
6004
+ ]
6005
+ };
6006
+ const getRuleSampleValue = (rule) => {
6007
+ if (rule.sampleValue !== undefined) {
6008
+ return rule.sampleValue;
5787
6009
  }
5788
- if (pattern.startsWith('option.')) {
5789
- return 'option';
6010
+ if (rule.defaultValue !== undefined) {
6011
+ return rule.defaultValue;
5790
6012
  }
5791
- return 'custom';
6013
+ return null;
6014
+ };
6015
+ const setNestedLeaf = (target, path, leaf) => {
6016
+ const segments = path.split('.').filter(Boolean);
6017
+ let current = target;
6018
+ segments.forEach((segment, index) => {
6019
+ if (index === segments.length - 1) {
6020
+ current[segment] = leaf;
6021
+ return;
6022
+ }
6023
+ const next = current[segment];
6024
+ if (!next || typeof next !== 'object' || Array.isArray(next)) {
6025
+ current[segment] = {};
6026
+ }
6027
+ current = current[segment];
6028
+ });
6029
+ };
6030
+ const buildSettingImportExample = (resourceCode = 'RESOURCE_CODE', convention = SETTING_KEY_CONVENTION) => {
6031
+ const resource = resourceCode.trim() || 'RESOURCE_CODE';
6032
+ const settings = {};
6033
+ convention.rules.forEach(rule => {
6034
+ const example = rule.examples[0];
6035
+ if (!example) {
6036
+ return;
6037
+ }
6038
+ setNestedLeaf(settings, example, {
6039
+ type: rule.type,
6040
+ value: getRuleSampleValue(rule),
6041
+ description: rule.description
6042
+ });
6043
+ });
6044
+ return {
6045
+ [resource]: settings
6046
+ };
5792
6047
  };
5793
- const filterSamples = (rows, searchTerm, category, type) => {
6048
+ const serializeSettingImportExample = (resourceCode = 'RESOURCE_CODE', convention = SETTING_KEY_CONVENTION) => JSON.stringify(buildSettingImportExample(resourceCode, convention), null, 2);
6049
+ const filterSettingKeyRules = (rows, searchTerm, category, type) => {
5794
6050
  const keyword = searchTerm.trim().toLowerCase();
5795
6051
  return rows.filter(row => {
5796
6052
  const matchesKeyword = !keyword || [
5797
6053
  row.pattern,
5798
6054
  row.type,
6055
+ row.description,
5799
6056
  ...row.examples
5800
6057
  ].some(value => value.toLowerCase().includes(keyword));
5801
- const matchesCategory = !category || getSampleCategory(row.pattern) === category;
6058
+ const matchesCategory = !category || row.category === category;
5802
6059
  const matchesType = !type || row.type === type;
5803
- return matchesKeyword &&
5804
- matchesCategory &&
5805
- matchesType;
6060
+ return matchesKeyword && matchesCategory && matchesType;
5806
6061
  });
5807
6062
  };
6063
+
6064
+ const RESOURCE_CODE_PLACEHOLDER = 'RESOURCE_CODE';
6065
+ const SELECT_MENU_PORTAL_STYLES = {
6066
+ menuPortal: (base) => ({
6067
+ ...base,
6068
+ zIndex: 1100
6069
+ })
6070
+ };
6071
+ const TYPE_OPTIONS = Array.from(new Set(SETTING_KEY_CONVENTION.rules.map(row => row.type))).map(value => ({
6072
+ value,
6073
+ label: value
6074
+ }));
5808
6075
  const TechnicalText = ({ children }) => (jsxRuntime.jsx("span", { className: 'config-key-sample-text text-break', children: children }));
5809
6076
  const KeySampleTable = ({ rows, onCopy }) => (jsxRuntime.jsx("div", { className: 'table-responsive', children: jsxRuntime.jsxs("table", { className: 'table table-sm table-bordered align-middle mb-0', style: {
5810
6077
  tableLayout: 'fixed',
5811
6078
  minWidth: 930
5812
- }, children: [jsxRuntime.jsx("caption", { className: 'visually-hidden', children: "Danh s\u00E1ch m\u1EABu key c\u1EA5u h\u00ECnh" }), jsxRuntime.jsx("thead", { children: jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("th", { scope: 'col', style: { width: 360 }, children: "M\u1EABu key \u0111\u1EA7y \u0111\u1EE7" }), jsxRuntime.jsx("th", { scope: 'col', style: { width: 110 }, children: "Type" }), jsxRuntime.jsx("th", { scope: 'col', children: "V\u00ED d\u1EE5" })] }) }), jsxRuntime.jsx("tbody", { children: rows.map(row => (jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("td", { children: jsxRuntime.jsx(TechnicalText, { children: row.pattern }) }), jsxRuntime.jsx("td", { children: row.type }), jsxRuntime.jsx("td", { children: row.examples.map(example => (jsxRuntime.jsxs("div", { className: 'config-key-sample-copy-row', children: [jsxRuntime.jsx(TechnicalText, { children: example }), jsxRuntime.jsx("button", { type: 'button', className: 'config-key-sample-copy-button', onClick: () => onCopy(example), "aria-label": `Sao chép key ${example}`, "data-tooltip-id": 'tooltip', "data-tooltip-content": 'Sao ch\u00E9p key', children: jsxRuntime.jsx(becoxyIcons.Copy, { fontSize: 15 }) })] }, example))) })] }, `${row.pattern}-${row.type}`))) })] }) }));
6079
+ }, children: [jsxRuntime.jsx("caption", { className: 'visually-hidden', children: "Danh s\u00E1ch m\u1EABu key c\u1EA5u h\u00ECnh" }), jsxRuntime.jsx("thead", { children: jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("th", { scope: 'col', style: { width: 360 }, children: "M\u1EABu key \u0111\u1EA7y \u0111\u1EE7" }), jsxRuntime.jsx("th", { scope: 'col', style: { width: 110 }, children: "Type" }), jsxRuntime.jsx("th", { scope: 'col', children: "V\u00ED d\u1EE5" })] }) }), jsxRuntime.jsx("tbody", { children: rows.map(row => (jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("td", { children: jsxRuntime.jsx(TechnicalText, { children: `${RESOURCE_CODE_PLACEHOLDER}.${row.pattern}` }) }), jsxRuntime.jsx("td", { children: row.type }), jsxRuntime.jsx("td", { children: row.examples.map(example => (jsxRuntime.jsxs("div", { className: 'config-key-sample-copy-row', children: [jsxRuntime.jsx(TechnicalText, { children: example }), jsxRuntime.jsx("button", { type: 'button', className: 'config-key-sample-copy-button', onClick: () => onCopy(`${RESOURCE_CODE_PLACEHOLDER}.${example}`), "aria-label": `Sao chép key ${RESOURCE_CODE_PLACEHOLDER}.${example}`, "data-tooltip-id": 'tooltip', "data-tooltip-content": 'Sao ch\u00E9p key', children: jsxRuntime.jsx(becoxyIcons.Copy, { fontSize: 15 }) })] }, example))) })] }, row.id))) })] }) }));
5813
6080
  const ConfigTreeRulesDrawer = ({ open, onClose }) => {
6081
+ const [activeTab, setActiveTab] = react.useState('samples');
5814
6082
  const [searchTerm, setSearchTerm] = react.useState('');
5815
6083
  const [categoryFilter, setCategoryFilter] = react.useState(null);
5816
6084
  const [typeFilter, setTypeFilter] = react.useState(null);
5817
6085
  react.useEffect(() => {
5818
6086
  if (!open) {
6087
+ setActiveTab('samples');
5819
6088
  setSearchTerm('');
5820
6089
  setCategoryFilter(null);
5821
6090
  setTypeFilter(null);
5822
6091
  }
5823
6092
  }, [open]);
5824
- const filteredSamples = react.useMemo(() => filterSamples(KEY_SAMPLES, searchTerm, categoryFilter?.value, typeFilter?.value), [
5825
- categoryFilter,
5826
- searchTerm,
5827
- typeFilter
5828
- ]);
5829
- const copyKey = async (value) => {
6093
+ const filteredSamples = react.useMemo(() => filterSettingKeyRules(SETTING_KEY_CONVENTION.rules, searchTerm, categoryFilter?.value, typeFilter?.value), [categoryFilter, searchTerm, typeFilter]);
6094
+ const importExampleText = react.useMemo(() => serializeSettingImportExample(RESOURCE_CODE_PLACEHOLDER), []);
6095
+ const copyText = async (value, successMessage) => {
5830
6096
  try {
5831
6097
  await navigator.clipboard.writeText(value);
5832
- notificationSuccess('Đã sao chép key');
6098
+ notificationSuccess(successMessage);
6099
+ }
6100
+ catch {
6101
+ notificationError('Không thể sao chép nội dung');
6102
+ }
6103
+ };
6104
+ const downloadImportExample = () => {
6105
+ try {
6106
+ const blob = new Blob([importExampleText], { type: 'application/json;charset=utf-8' });
6107
+ const url = URL.createObjectURL(blob);
6108
+ const link = document.createElement('a');
6109
+ link.href = url;
6110
+ link.download = 'becoxy-setting-resource-example.json';
6111
+ document.body.appendChild(link);
6112
+ link.click();
6113
+ link.remove();
6114
+ URL.revokeObjectURL(url);
6115
+ notificationSuccess('Đã tải JSON mẫu để import');
5833
6116
  }
5834
6117
  catch {
5835
- notificationError('Không thể sao chép key');
6118
+ notificationError('Không thể tải JSON mẫu');
5836
6119
  }
5837
6120
  };
5838
- return (jsxRuntime.jsx(Sidebar, { open: open, toggleSidebar: onClose, width: 1000, keyboard: true, hiddenFullScreenButton: true, zIndex: 1061, panelClassName: 'config-tree-rules-drawer', children: jsxRuntime.jsxs("div", { className: 'config-key-sample-layout h-100 bg-white d-flex flex-column', children: [jsxRuntime.jsx(ModalHeader, { typeModal: 'Setting', handleModal: onClose, title: 'M\u1EABu key c\u1EA5u h\u00ECnh' }), jsxRuntime.jsxs("div", { className: 'config-key-sample-toolbar border-bottom p-1 flex-shrink-0 d-flex align-items-center justify-content-start flex-wrap gap-1', children: [jsxRuntime.jsx(reactstrap.Input, { type: 'search', bsSize: 'sm', value: searchTerm, onChange: event => setSearchTerm(event.target.value), placeholder: 'T\u00ECm m\u1EABu key...', "aria-label": 'T\u00ECm m\u1EABu key', style: {
6121
+ return (jsxRuntime.jsx(Sidebar, { open: open, toggleSidebar: onClose, width: 1000, keyboard: true, hiddenFullScreenButton: true, zIndex: 1061, panelClassName: 'config-tree-rules-drawer', children: jsxRuntime.jsxs("div", { className: 'config-key-sample-layout h-100 bg-white d-flex flex-column', children: [jsxRuntime.jsx(ModalHeader, { typeModal: 'Setting', handleModal: onClose, title: 'M\u1EABu key c\u1EA5u h\u00ECnh' }), activeTab === 'samples' ? (jsxRuntime.jsxs("div", { className: 'config-key-sample-toolbar border-bottom p-1 flex-shrink-0 d-flex align-items-center justify-content-start flex-wrap gap-1', children: [jsxRuntime.jsx(reactstrap.Input, { type: 'search', bsSize: 'sm', value: searchTerm, onChange: event => setSearchTerm(event.target.value), placeholder: 'T\u00ECm m\u1EABu key...', "aria-label": 'T\u00ECm m\u1EABu key', style: {
5839
6122
  width: 300,
5840
6123
  maxWidth: '100%'
5841
- } }), jsxRuntime.jsx("div", { style: { width: 200 }, children: jsxRuntime.jsx(Select__default["default"], { options: CATEGORY_OPTIONS, value: categoryFilter, onChange: (option) => setCategoryFilter(option), isClearable: true, menuPortalTarget: document.body, menuPosition: 'fixed', styles: SELECT_MENU_PORTAL_STYLES, placeholder: 'T\u1EA5t c\u1EA3 nh\u00F3m m\u1EABu', className: 'react-select w-100', classNamePrefix: 'select' }) }), jsxRuntime.jsx("div", { style: { width: 180 }, children: jsxRuntime.jsx(Select__default["default"], { options: TYPE_OPTIONS, value: typeFilter, onChange: (option) => setTypeFilter(option), isClearable: true, menuPortalTarget: document.body, menuPosition: 'fixed', styles: SELECT_MENU_PORTAL_STYLES, placeholder: 'T\u1EA5t c\u1EA3 Type', className: 'react-select w-100', classNamePrefix: 'select' }) })] }), jsxRuntime.jsxs("div", { className: 'config-key-sample-content p-1 overflow-auto flex-grow-1', children: [filteredSamples.length > 0 ? (jsxRuntime.jsx(KeySampleTable, { rows: filteredSamples, onCopy: copyKey })) : null, filteredSamples.length === 0 ? (jsxRuntime.jsx("div", { className: 'config-key-sample-empty text-center py-2', children: "Kh\u00F4ng t\u00ECm th\u1EA5y m\u1EABu key." })) : null] })] }) }));
6124
+ } }), jsxRuntime.jsx("div", { style: { width: 200 }, children: jsxRuntime.jsx(Select__default["default"], { options: SETTING_KEY_CONVENTION.categories, value: categoryFilter, onChange: (option) => setCategoryFilter(option), isClearable: true, menuPortalTarget: document.body, menuPosition: 'fixed', styles: SELECT_MENU_PORTAL_STYLES, placeholder: 'T\u1EA5t c\u1EA3 nh\u00F3m m\u1EABu', className: 'react-select w-100', classNamePrefix: 'select' }) }), jsxRuntime.jsx("div", { style: { width: 180 }, children: jsxRuntime.jsx(Select__default["default"], { options: TYPE_OPTIONS, value: typeFilter, onChange: (option) => setTypeFilter(option), isClearable: true, menuPortalTarget: document.body, menuPosition: 'fixed', styles: SELECT_MENU_PORTAL_STYLES, placeholder: 'T\u1EA5t c\u1EA3 Type', className: 'react-select w-100', classNamePrefix: 'select' }) })] })) : null, jsxRuntime.jsxs("div", { className: 'config-key-sample-tabs border-bottom p-1 flex-shrink-0 d-flex align-items-center justify-content-between flex-wrap gap-1', children: [jsxRuntime.jsxs("div", { className: 'd-flex align-items-center gap-50', role: 'tablist', "aria-label": 'N\u1ED9i dung quy t\u1EAFc Setting', children: [jsxRuntime.jsx(reactstrap.Button, { type: 'button', size: 'sm', color: 'primary', outline: activeTab !== 'samples', role: 'tab', "aria-selected": activeTab === 'samples', onClick: () => setActiveTab('samples'), children: "M\u1EABu d\u1EA1ng key" }), jsxRuntime.jsx(reactstrap.Button, { type: 'button', size: 'sm', color: 'primary', outline: activeTab !== 'import-example', role: 'tab', "aria-selected": activeTab === 'import-example', onClick: () => setActiveTab('import-example'), children: "JSON m\u1EABu ho\u00E0n ch\u1EC9nh" })] }), jsxRuntime.jsx("div", { className: 'd-flex align-items-center gap-50', children: activeTab === 'import-example' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs(reactstrap.Button, { type: 'button', size: 'sm', color: 'secondary', outline: true, onClick: () => {
6125
+ void copyText(importExampleText, 'Đã sao chép JSON mẫu');
6126
+ }, children: [jsxRuntime.jsx(becoxyIcons.Copy, { fontSize: 15, className: 'me-50' }), "Copy JSON m\u1EABu"] }), jsxRuntime.jsxs(reactstrap.Button, { type: 'button', size: 'sm', color: 'secondary', outline: true, onClick: downloadImportExample, children: [jsxRuntime.jsx(becoxyIcons.Download, { fontSize: 15, className: 'me-50' }), "T\u1EA3i JSON m\u1EABu"] })] })) : null })] }), jsxRuntime.jsx("div", { className: 'config-key-sample-content p-1 overflow-auto flex-grow-1', role: 'tabpanel', children: activeTab === 'samples' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [filteredSamples.length > 0 ? (jsxRuntime.jsx(KeySampleTable, { rows: filteredSamples, onCopy: value => {
6127
+ void copyText(value, 'Đã sao chép key');
6128
+ } })) : null, filteredSamples.length === 0 ? (jsxRuntime.jsx("div", { className: 'config-key-sample-empty text-center py-2', children: "Kh\u00F4ng t\u00ECm th\u1EA5y m\u1EABu key." })) : null] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: 'alert alert-warning py-50 px-1 mb-1', children: ["Thay ", jsxRuntime.jsx("strong", { children: "RESOURCE_CODE" }), " b\u1EB1ng m\u00E3 Resource th\u1EF1c t\u1EBF tr\u01B0\u1EDBc khi import."] }), jsxRuntime.jsx("pre", { className: 'config-key-convention-text mb-0', children: importExampleText })] })) })] }) }));
5842
6129
  };
5843
6130
 
5844
6131
  const ConfigTreeGridFooter = ({ rows }) => {