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