@fgv/ts-res-ui-components 5.0.0-10
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/.rush/temp/03c8b056281d9db0a97d8a6e25eea798a160d393.tar.log +271 -0
- package/.rush/temp/chunked-rush-logs/ts-res-ui-components.build.chunks.jsonl +9 -0
- package/.rush/temp/operation/build/all.log +9 -0
- package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
- package/.rush/temp/operation/build/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +1111 -0
- package/README.md +18 -0
- package/REFACTORING_PLAN.md +171 -0
- package/config/jest.config.json +16 -0
- package/config/jest.setup.js +64 -0
- package/config/rig.json +16 -0
- package/lib/components/common/QualifierContextControl.d.ts +14 -0
- package/lib/components/common/QualifierContextControl.d.ts.map +1 -0
- package/lib/components/common/QualifierContextControl.js +78 -0
- package/lib/components/common/QualifierContextControl.js.map +1 -0
- package/lib/components/common/ResourceListView.d.ts +11 -0
- package/lib/components/common/ResourceListView.d.ts.map +1 -0
- package/lib/components/common/ResourceListView.js +20 -0
- package/lib/components/common/ResourceListView.js.map +1 -0
- package/lib/components/common/ResourceTreeView.d.ts +12 -0
- package/lib/components/common/ResourceTreeView.d.ts.map +1 -0
- package/lib/components/common/ResourceTreeView.js +162 -0
- package/lib/components/common/ResourceTreeView.js.map +1 -0
- package/lib/components/forms/HierarchyEditor.d.ts +10 -0
- package/lib/components/forms/HierarchyEditor.d.ts.map +1 -0
- package/lib/components/forms/HierarchyEditor.js +106 -0
- package/lib/components/forms/HierarchyEditor.js.map +1 -0
- package/lib/components/forms/QualifierEditForm.d.ts +11 -0
- package/lib/components/forms/QualifierEditForm.d.ts.map +1 -0
- package/lib/components/forms/QualifierEditForm.js +181 -0
- package/lib/components/forms/QualifierEditForm.js.map +1 -0
- package/lib/components/forms/QualifierTypeEditForm.d.ts +10 -0
- package/lib/components/forms/QualifierTypeEditForm.d.ts.map +1 -0
- package/lib/components/forms/QualifierTypeEditForm.js +172 -0
- package/lib/components/forms/QualifierTypeEditForm.js.map +1 -0
- package/lib/components/forms/ResourceTypeEditForm.d.ts +10 -0
- package/lib/components/forms/ResourceTypeEditForm.d.ts.map +1 -0
- package/lib/components/forms/ResourceTypeEditForm.js +188 -0
- package/lib/components/forms/ResourceTypeEditForm.js.map +1 -0
- package/lib/components/forms/index.d.ts +9 -0
- package/lib/components/forms/index.d.ts.map +1 -0
- package/lib/components/forms/index.js +5 -0
- package/lib/components/forms/index.js.map +1 -0
- package/lib/components/orchestrator/ResourceOrchestrator.d.ts +14 -0
- package/lib/components/orchestrator/ResourceOrchestrator.d.ts.map +1 -0
- package/lib/components/orchestrator/ResourceOrchestrator.js +278 -0
- package/lib/components/orchestrator/ResourceOrchestrator.js.map +1 -0
- package/lib/components/views/CompiledView/index.d.ts +5 -0
- package/lib/components/views/CompiledView/index.d.ts.map +1 -0
- package/lib/components/views/CompiledView/index.js +595 -0
- package/lib/components/views/CompiledView/index.js.map +1 -0
- package/lib/components/views/ConfigurationView/index.d.ts +5 -0
- package/lib/components/views/ConfigurationView/index.d.ts.map +1 -0
- package/lib/components/views/ConfigurationView/index.js +363 -0
- package/lib/components/views/ConfigurationView/index.js.map +1 -0
- package/lib/components/views/FilterView/index.d.ts +5 -0
- package/lib/components/views/FilterView/index.d.ts.map +1 -0
- package/lib/components/views/FilterView/index.js +463 -0
- package/lib/components/views/FilterView/index.js.map +1 -0
- package/lib/components/views/ImportView/index.d.ts +5 -0
- package/lib/components/views/ImportView/index.d.ts.map +1 -0
- package/lib/components/views/ImportView/index.js +514 -0
- package/lib/components/views/ImportView/index.js.map +1 -0
- package/lib/components/views/ResolutionView/EditableJsonView.d.ts +21 -0
- package/lib/components/views/ResolutionView/EditableJsonView.d.ts.map +1 -0
- package/lib/components/views/ResolutionView/EditableJsonView.js +109 -0
- package/lib/components/views/ResolutionView/EditableJsonView.js.map +1 -0
- package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts +19 -0
- package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts.map +1 -0
- package/lib/components/views/ResolutionView/ResolutionEditControls.js +82 -0
- package/lib/components/views/ResolutionView/ResolutionEditControls.js.map +1 -0
- package/lib/components/views/ResolutionView/index.d.ts +5 -0
- package/lib/components/views/ResolutionView/index.d.ts.map +1 -0
- package/lib/components/views/ResolutionView/index.js +255 -0
- package/lib/components/views/ResolutionView/index.js.map +1 -0
- package/lib/components/views/SourceView/index.d.ts +5 -0
- package/lib/components/views/SourceView/index.d.ts.map +1 -0
- package/lib/components/views/SourceView/index.js +316 -0
- package/lib/components/views/SourceView/index.js.map +1 -0
- package/lib/components/views/ZipLoaderView/index.d.ts +5 -0
- package/lib/components/views/ZipLoaderView/index.d.ts.map +1 -0
- package/lib/components/views/ZipLoaderView/index.js +313 -0
- package/lib/components/views/ZipLoaderView/index.js.map +1 -0
- package/lib/hooks/useConfigurationState.d.ts +46 -0
- package/lib/hooks/useConfigurationState.d.ts.map +1 -0
- package/lib/hooks/useConfigurationState.js +239 -0
- package/lib/hooks/useConfigurationState.js.map +1 -0
- package/lib/hooks/useFilterState.d.ts +7 -0
- package/lib/hooks/useFilterState.d.ts.map +1 -0
- package/lib/hooks/useFilterState.js +80 -0
- package/lib/hooks/useFilterState.js.map +1 -0
- package/lib/hooks/useResolutionState.d.ts +8 -0
- package/lib/hooks/useResolutionState.d.ts.map +1 -0
- package/lib/hooks/useResolutionState.js +253 -0
- package/lib/hooks/useResolutionState.js.map +1 -0
- package/lib/hooks/useResourceData.d.ts +19 -0
- package/lib/hooks/useResourceData.d.ts.map +1 -0
- package/lib/hooks/useResourceData.js +368 -0
- package/lib/hooks/useResourceData.js.map +1 -0
- package/lib/hooks/useViewState.d.ts +10 -0
- package/lib/hooks/useViewState.d.ts.map +1 -0
- package/lib/hooks/useViewState.js +29 -0
- package/lib/hooks/useViewState.js.map +1 -0
- package/lib/index.d.ts +27 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +34 -0
- package/lib/index.js.map +1 -0
- package/lib/test/helpers/testDataLoader.d.ts +37 -0
- package/lib/test/helpers/testDataLoader.d.ts.map +1 -0
- package/lib/test/helpers/testDataLoader.js +171 -0
- package/lib/test/helpers/testDataLoader.js.map +1 -0
- package/lib/test/unit/utils/configurationUtils.test.d.ts +2 -0
- package/lib/test/unit/utils/configurationUtils.test.d.ts.map +1 -0
- package/lib/test/unit/utils/configurationUtils.test.js +497 -0
- package/lib/test/unit/utils/configurationUtils.test.js.map +1 -0
- package/lib/test/unit/utils/fileProcessing.test.d.ts +2 -0
- package/lib/test/unit/utils/fileProcessing.test.d.ts.map +1 -0
- package/lib/test/unit/utils/fileProcessing.test.js +321 -0
- package/lib/test/unit/utils/fileProcessing.test.js.map +1 -0
- package/lib/test/unit/utils/filterResources.test.d.ts +2 -0
- package/lib/test/unit/utils/filterResources.test.d.ts.map +1 -0
- package/lib/test/unit/utils/filterResources.test.js +403 -0
- package/lib/test/unit/utils/filterResources.test.js.map +1 -0
- package/lib/test/unit/utils/resolutionEditing.test.d.ts +2 -0
- package/lib/test/unit/utils/resolutionEditing.test.d.ts.map +1 -0
- package/lib/test/unit/utils/resolutionEditing.test.js +439 -0
- package/lib/test/unit/utils/resolutionEditing.test.js.map +1 -0
- package/lib/test/unit/utils/resolutionUtils.test.d.ts +2 -0
- package/lib/test/unit/utils/resolutionUtils.test.d.ts.map +1 -0
- package/lib/test/unit/utils/resolutionUtils.test.js +397 -0
- package/lib/test/unit/utils/resolutionUtils.test.js.map +1 -0
- package/lib/test/unit/utils/tsResIntegration.test.d.ts +2 -0
- package/lib/test/unit/utils/tsResIntegration.test.d.ts.map +1 -0
- package/lib/test/unit/utils/tsResIntegration.test.js +376 -0
- package/lib/test/unit/utils/tsResIntegration.test.js.map +1 -0
- package/lib/types/index.d.ts +251 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +2 -0
- package/lib/types/index.js.map +1 -0
- package/lib/utils/configurationUtils.d.ts +74 -0
- package/lib/utils/configurationUtils.d.ts.map +1 -0
- package/lib/utils/configurationUtils.js +359 -0
- package/lib/utils/configurationUtils.js.map +1 -0
- package/lib/utils/fileProcessing.d.ts +18 -0
- package/lib/utils/fileProcessing.d.ts.map +1 -0
- package/lib/utils/fileProcessing.js +142 -0
- package/lib/utils/fileProcessing.js.map +1 -0
- package/lib/utils/filterResources.d.ts +38 -0
- package/lib/utils/filterResources.d.ts.map +1 -0
- package/lib/utils/filterResources.js +153 -0
- package/lib/utils/filterResources.js.map +1 -0
- package/lib/utils/resolutionEditing.d.ts +58 -0
- package/lib/utils/resolutionEditing.d.ts.map +1 -0
- package/lib/utils/resolutionEditing.js +246 -0
- package/lib/utils/resolutionEditing.js.map +1 -0
- package/lib/utils/resolutionUtils.d.ts +28 -0
- package/lib/utils/resolutionUtils.d.ts.map +1 -0
- package/lib/utils/resolutionUtils.js +216 -0
- package/lib/utils/resolutionUtils.js.map +1 -0
- package/lib/utils/tsResIntegration.d.ts +71 -0
- package/lib/utils/tsResIntegration.d.ts.map +1 -0
- package/lib/utils/tsResIntegration.js +294 -0
- package/lib/utils/tsResIntegration.js.map +1 -0
- package/lib/utils/zipLoader/browserZipLoader.d.ts +48 -0
- package/lib/utils/zipLoader/browserZipLoader.d.ts.map +1 -0
- package/lib/utils/zipLoader/browserZipLoader.js +247 -0
- package/lib/utils/zipLoader/browserZipLoader.js.map +1 -0
- package/lib/utils/zipLoader/index.d.ts +8 -0
- package/lib/utils/zipLoader/index.d.ts.map +1 -0
- package/lib/utils/zipLoader/index.js +13 -0
- package/lib/utils/zipLoader/index.js.map +1 -0
- package/lib/utils/zipLoader/nodeZipBuilder.d.ts +55 -0
- package/lib/utils/zipLoader/nodeZipBuilder.d.ts.map +1 -0
- package/lib/utils/zipLoader/nodeZipBuilder.js +98 -0
- package/lib/utils/zipLoader/nodeZipBuilder.js.map +1 -0
- package/lib/utils/zipLoader/types.d.ts +139 -0
- package/lib/utils/zipLoader/types.d.ts.map +1 -0
- package/lib/utils/zipLoader/types.js +2 -0
- package/lib/utils/zipLoader/types.js.map +1 -0
- package/lib/utils/zipLoader/zipUtils.d.ts +53 -0
- package/lib/utils/zipLoader/zipUtils.d.ts.map +1 -0
- package/lib/utils/zipLoader/zipUtils.js +229 -0
- package/lib/utils/zipLoader/zipUtils.js.map +1 -0
- package/package.json +69 -0
- package/rush-logs/ts-res-ui-components.build.cache.log +3 -0
- package/rush-logs/ts-res-ui-components.build.log +9 -0
- package/src/components/common/QualifierContextControl.tsx +151 -0
- package/src/components/common/ResourceListView.tsx +63 -0
- package/src/components/common/ResourceTreeView.tsx +271 -0
- package/src/components/forms/HierarchyEditor.tsx +204 -0
- package/src/components/forms/QualifierEditForm.tsx +355 -0
- package/src/components/forms/QualifierTypeEditForm.tsx +347 -0
- package/src/components/forms/ResourceTypeEditForm.tsx +331 -0
- package/src/components/forms/index.ts +11 -0
- package/src/components/orchestrator/ResourceOrchestrator.tsx +372 -0
- package/src/components/views/CompiledView/index.tsx +922 -0
- package/src/components/views/ConfigurationView/index.tsx +800 -0
- package/src/components/views/FilterView/index.tsx +825 -0
- package/src/components/views/ImportView/index.tsx +717 -0
- package/src/components/views/ResolutionView/EditableJsonView.tsx +214 -0
- package/src/components/views/ResolutionView/ResolutionEditControls.tsx +170 -0
- package/src/components/views/ResolutionView/index.tsx +591 -0
- package/src/components/views/SourceView/index.tsx +536 -0
- package/src/components/views/ZipLoaderView/index.tsx +485 -0
- package/src/hooks/useConfigurationState.ts +374 -0
- package/src/hooks/useFilterState.ts +97 -0
- package/src/hooks/useResolutionState.ts +355 -0
- package/src/hooks/useResourceData.ts +467 -0
- package/src/hooks/useViewState.ts +44 -0
- package/src/index.ts +45 -0
- package/src/test/helpers/testDataLoader.ts +195 -0
- package/src/test/unit/utils/configurationUtils.test.ts +630 -0
- package/src/test/unit/utils/fileProcessing.test.ts +391 -0
- package/src/test/unit/utils/filterResources.test.ts +574 -0
- package/src/test/unit/utils/resolutionEditing.test.ts +556 -0
- package/src/test/unit/utils/resolutionUtils.test.ts +521 -0
- package/src/test/unit/utils/tsResIntegration.test.ts +433 -0
- package/src/types/index.ts +322 -0
- package/src/utils/configurationUtils.ts +424 -0
- package/src/utils/fileProcessing.ts +160 -0
- package/src/utils/filterResources.ts +206 -0
- package/src/utils/resolutionEditing.ts +319 -0
- package/src/utils/resolutionUtils.ts +289 -0
- package/src/utils/tsResIntegration.ts +440 -0
- package/src/utils/zipLoader/browserZipLoader.ts +319 -0
- package/src/utils/zipLoader/index.ts +26 -0
- package/src/utils/zipLoader/nodeZipBuilder.ts +153 -0
- package/src/utils/zipLoader/types.ts +175 -0
- package/src/utils/zipLoader/zipUtils.ts +266 -0
- package/temp/build/typescript/ts_gZid87Hu.json +1 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
import React, { useState, useCallback, useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
CogIcon,
|
|
4
|
+
DocumentTextIcon,
|
|
5
|
+
PlusIcon,
|
|
6
|
+
TrashIcon,
|
|
7
|
+
ArrowDownTrayIcon,
|
|
8
|
+
ArrowUpTrayIcon,
|
|
9
|
+
ExclamationTriangleIcon,
|
|
10
|
+
CheckCircleIcon,
|
|
11
|
+
InformationCircleIcon,
|
|
12
|
+
EyeIcon,
|
|
13
|
+
PencilIcon
|
|
14
|
+
} from '@heroicons/react/24/outline';
|
|
15
|
+
import { ConfigurationViewProps } from '../../../types';
|
|
16
|
+
import { useConfigurationState } from '../../../hooks/useConfigurationState';
|
|
17
|
+
import { Config, QualifierTypes, Qualifiers, ResourceTypes } from '@fgv/ts-res';
|
|
18
|
+
import { QualifierTypeEditForm } from '../../forms/QualifierTypeEditForm';
|
|
19
|
+
import { QualifierEditForm } from '../../forms/QualifierEditForm';
|
|
20
|
+
import { ResourceTypeEditForm } from '../../forms/ResourceTypeEditForm';
|
|
21
|
+
|
|
22
|
+
export const ConfigurationView: React.FC<ConfigurationViewProps> = ({
|
|
23
|
+
configuration,
|
|
24
|
+
onConfigurationChange,
|
|
25
|
+
onSave,
|
|
26
|
+
hasUnsavedChanges,
|
|
27
|
+
onMessage,
|
|
28
|
+
className = ''
|
|
29
|
+
}) => {
|
|
30
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
31
|
+
const [editingQualifierType, setEditingQualifierType] = useState<{
|
|
32
|
+
item: QualifierTypes.Config.ISystemQualifierTypeConfig;
|
|
33
|
+
index: number;
|
|
34
|
+
} | null>(null);
|
|
35
|
+
const [editingQualifier, setEditingQualifier] = useState<{
|
|
36
|
+
item: Qualifiers.IQualifierDecl;
|
|
37
|
+
index: number;
|
|
38
|
+
} | null>(null);
|
|
39
|
+
const [editingResourceType, setEditingResourceType] = useState<{
|
|
40
|
+
item: ResourceTypes.Config.IResourceTypeConfig;
|
|
41
|
+
index: number;
|
|
42
|
+
} | null>(null);
|
|
43
|
+
const [showAddQualifierType, setShowAddQualifierType] = useState(false);
|
|
44
|
+
const [showAddQualifier, setShowAddQualifier] = useState(false);
|
|
45
|
+
const [showAddResourceType, setShowAddResourceType] = useState(false);
|
|
46
|
+
|
|
47
|
+
const { state, actions, templates } = useConfigurationState(
|
|
48
|
+
configuration || undefined,
|
|
49
|
+
onConfigurationChange,
|
|
50
|
+
hasUnsavedChanges
|
|
51
|
+
? undefined
|
|
52
|
+
: (changes) => {
|
|
53
|
+
// Only notify if we weren't already told there are unsaved changes
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Handle file import
|
|
58
|
+
const handleFileImport = useCallback(
|
|
59
|
+
(files: FileList | null) => {
|
|
60
|
+
if (!files || files.length === 0) return;
|
|
61
|
+
|
|
62
|
+
const file = files[0];
|
|
63
|
+
const reader = new FileReader();
|
|
64
|
+
|
|
65
|
+
reader.onload = (e) => {
|
|
66
|
+
const content = e.target?.result as string;
|
|
67
|
+
if (content) {
|
|
68
|
+
const result = actions.importFromJson(content);
|
|
69
|
+
if (result.isSuccess()) {
|
|
70
|
+
onMessage?.('success', `Configuration imported from ${file.name}`);
|
|
71
|
+
} else {
|
|
72
|
+
onMessage?.('error', `Import failed: ${result.message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
reader.onerror = () => {
|
|
78
|
+
onMessage?.('error', `Failed to read file: ${file.name}`);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
reader.readAsText(file);
|
|
82
|
+
},
|
|
83
|
+
[actions, onMessage]
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Handle export
|
|
87
|
+
const handleExport = useCallback(() => {
|
|
88
|
+
const result = actions.exportToJson({ format: 'json', pretty: true });
|
|
89
|
+
if (result.isSuccess()) {
|
|
90
|
+
const blob = new Blob([result.value], { type: 'application/json' });
|
|
91
|
+
const url = URL.createObjectURL(blob);
|
|
92
|
+
const a = document.createElement('a');
|
|
93
|
+
a.href = url;
|
|
94
|
+
a.download = 'ts-res-configuration.json';
|
|
95
|
+
document.body.appendChild(a);
|
|
96
|
+
a.click();
|
|
97
|
+
document.body.removeChild(a);
|
|
98
|
+
URL.revokeObjectURL(url);
|
|
99
|
+
onMessage?.('success', 'Configuration exported successfully');
|
|
100
|
+
} else {
|
|
101
|
+
onMessage?.('error', `Export failed: ${result.message}`);
|
|
102
|
+
}
|
|
103
|
+
}, [actions, onMessage]);
|
|
104
|
+
|
|
105
|
+
// Handle template loading
|
|
106
|
+
const handleLoadTemplate = useCallback(
|
|
107
|
+
(templateId: string) => {
|
|
108
|
+
const result = actions.loadTemplate(templateId);
|
|
109
|
+
if (result.isSuccess()) {
|
|
110
|
+
const template = templates.find((t) => t.id === templateId);
|
|
111
|
+
onMessage?.('success', `Loaded template: ${template?.name}`);
|
|
112
|
+
} else {
|
|
113
|
+
onMessage?.('error', `Failed to load template: ${result.message}`);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
[actions, templates, onMessage]
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Handle save
|
|
120
|
+
const handleSave = useCallback(() => {
|
|
121
|
+
if (onSave) {
|
|
122
|
+
onSave(state.currentConfiguration);
|
|
123
|
+
actions.applyConfiguration();
|
|
124
|
+
onMessage?.('success', 'Configuration saved successfully');
|
|
125
|
+
}
|
|
126
|
+
}, [onSave, state.currentConfiguration, actions, onMessage]);
|
|
127
|
+
|
|
128
|
+
if (!configuration && !state.currentConfiguration) {
|
|
129
|
+
return (
|
|
130
|
+
<div className={`p-6 ${className}`}>
|
|
131
|
+
<div className="flex items-center space-x-3 mb-6">
|
|
132
|
+
<CogIcon className="h-8 w-8 text-blue-600" />
|
|
133
|
+
<h2 className="text-2xl font-bold text-gray-900">Configuration</h2>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8 text-center">
|
|
137
|
+
<div className="max-w-2xl mx-auto">
|
|
138
|
+
<h3 className="text-xl font-semibold text-gray-900 mb-4">No Configuration Available</h3>
|
|
139
|
+
<p className="text-gray-600 mb-6">
|
|
140
|
+
Load a configuration to manage qualifiers, qualifier types, and resource types.
|
|
141
|
+
</p>
|
|
142
|
+
<div className="bg-blue-50 rounded-lg p-4">
|
|
143
|
+
<p className="text-sm text-blue-800">
|
|
144
|
+
<strong>Configuration Manager:</strong> Define and manage system configurations for resource
|
|
145
|
+
management, including qualifiers, qualifier types, and resource types.
|
|
146
|
+
</p>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<div className={`p-6 ${className}`}>
|
|
156
|
+
<div className="flex items-center justify-between mb-6">
|
|
157
|
+
<div className="flex items-center space-x-3">
|
|
158
|
+
<CogIcon className="h-8 w-8 text-blue-600" />
|
|
159
|
+
<h2 className="text-2xl font-bold text-gray-900">Configuration</h2>
|
|
160
|
+
{state.hasUnsavedChanges && (
|
|
161
|
+
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
|
|
162
|
+
Unsaved Changes
|
|
163
|
+
</span>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div className="flex items-center space-x-2">
|
|
168
|
+
{/* Template Selection */}
|
|
169
|
+
<select
|
|
170
|
+
onChange={(e) => e.target.value && handleLoadTemplate(e.target.value)}
|
|
171
|
+
value=""
|
|
172
|
+
className="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
173
|
+
>
|
|
174
|
+
<option value="">Load Template...</option>
|
|
175
|
+
{templates.map((template) => (
|
|
176
|
+
<option key={template.id} value={template.id}>
|
|
177
|
+
{template.name}
|
|
178
|
+
</option>
|
|
179
|
+
))}
|
|
180
|
+
</select>
|
|
181
|
+
|
|
182
|
+
{/* Import Button */}
|
|
183
|
+
<button
|
|
184
|
+
onClick={() => fileInputRef.current?.click()}
|
|
185
|
+
className="inline-flex items-center px-3 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
186
|
+
>
|
|
187
|
+
<ArrowUpTrayIcon className="w-4 h-4 mr-2" />
|
|
188
|
+
Import
|
|
189
|
+
</button>
|
|
190
|
+
|
|
191
|
+
{/* Export Button */}
|
|
192
|
+
<button
|
|
193
|
+
onClick={handleExport}
|
|
194
|
+
className="inline-flex items-center px-3 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
195
|
+
>
|
|
196
|
+
<ArrowDownTrayIcon className="w-4 h-4 mr-2" />
|
|
197
|
+
Export
|
|
198
|
+
</button>
|
|
199
|
+
|
|
200
|
+
{/* View Toggle */}
|
|
201
|
+
<button
|
|
202
|
+
onClick={actions.toggleJsonView}
|
|
203
|
+
className={`inline-flex items-center px-3 py-2 border rounded-md text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 ${
|
|
204
|
+
state.isJsonView
|
|
205
|
+
? 'border-blue-600 text-blue-600 bg-blue-50'
|
|
206
|
+
: 'border-gray-300 text-gray-700 bg-white hover:bg-gray-50'
|
|
207
|
+
}`}
|
|
208
|
+
>
|
|
209
|
+
{state.isJsonView ? (
|
|
210
|
+
<>
|
|
211
|
+
<PencilIcon className="w-4 h-4 mr-2" />
|
|
212
|
+
Form View
|
|
213
|
+
</>
|
|
214
|
+
) : (
|
|
215
|
+
<>
|
|
216
|
+
<EyeIcon className="w-4 h-4 mr-2" />
|
|
217
|
+
JSON View
|
|
218
|
+
</>
|
|
219
|
+
)}
|
|
220
|
+
</button>
|
|
221
|
+
|
|
222
|
+
{/* Save Button */}
|
|
223
|
+
{onSave && (
|
|
224
|
+
<button
|
|
225
|
+
onClick={handleSave}
|
|
226
|
+
disabled={!state.hasUnsavedChanges}
|
|
227
|
+
className={`inline-flex items-center px-4 py-2 border border-transparent rounded-md text-sm font-medium ${
|
|
228
|
+
state.hasUnsavedChanges
|
|
229
|
+
? 'text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500'
|
|
230
|
+
: 'text-gray-400 bg-gray-200 cursor-not-allowed'
|
|
231
|
+
}`}
|
|
232
|
+
>
|
|
233
|
+
Save
|
|
234
|
+
</button>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
{/* Validation Status */}
|
|
240
|
+
{!state.validation.isValid && (
|
|
241
|
+
<div className="mb-6 bg-red-50 border border-red-200 rounded-lg p-4">
|
|
242
|
+
<div className="flex items-center space-x-2 text-red-600 mb-2">
|
|
243
|
+
<ExclamationTriangleIcon className="w-5 h-5" />
|
|
244
|
+
<span className="font-medium">Configuration Issues</span>
|
|
245
|
+
</div>
|
|
246
|
+
<ul className="text-sm text-red-700 space-y-1">
|
|
247
|
+
{state.validation.errors.map((error, index) => (
|
|
248
|
+
<li key={index}>• {error}</li>
|
|
249
|
+
))}
|
|
250
|
+
</ul>
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
|
|
254
|
+
{/* Warnings */}
|
|
255
|
+
{state.validation.warnings.length > 0 && (
|
|
256
|
+
<div className="mb-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
|
257
|
+
<div className="flex items-center space-x-2 text-yellow-600 mb-2">
|
|
258
|
+
<InformationCircleIcon className="w-5 h-5" />
|
|
259
|
+
<span className="font-medium">Configuration Warnings</span>
|
|
260
|
+
</div>
|
|
261
|
+
<ul className="text-sm text-yellow-700 space-y-1">
|
|
262
|
+
{state.validation.warnings.map((warning, index) => (
|
|
263
|
+
<li key={index}>• {warning}</li>
|
|
264
|
+
))}
|
|
265
|
+
</ul>
|
|
266
|
+
</div>
|
|
267
|
+
)}
|
|
268
|
+
|
|
269
|
+
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
|
270
|
+
{state.isJsonView ? (
|
|
271
|
+
// JSON Editor View
|
|
272
|
+
<div className="p-6">
|
|
273
|
+
<div className="flex items-center justify-between mb-4">
|
|
274
|
+
<h3 className="text-lg font-semibold text-gray-900">JSON Configuration</h3>
|
|
275
|
+
<button
|
|
276
|
+
onClick={() => {
|
|
277
|
+
const result = actions.applyJsonChanges();
|
|
278
|
+
if (result.isSuccess()) {
|
|
279
|
+
onMessage?.('success', 'JSON changes applied');
|
|
280
|
+
} else {
|
|
281
|
+
onMessage?.('error', `JSON error: ${result.message}`);
|
|
282
|
+
}
|
|
283
|
+
}}
|
|
284
|
+
disabled={!!state.jsonError}
|
|
285
|
+
className={`px-4 py-2 rounded-md text-sm font-medium ${
|
|
286
|
+
state.jsonError
|
|
287
|
+
? 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
|
288
|
+
: 'bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500'
|
|
289
|
+
}`}
|
|
290
|
+
>
|
|
291
|
+
Apply Changes
|
|
292
|
+
</button>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
{state.jsonError && (
|
|
296
|
+
<div className="mb-4 bg-red-50 border border-red-200 rounded-lg p-3">
|
|
297
|
+
<p className="text-sm text-red-700">{state.jsonError}</p>
|
|
298
|
+
</div>
|
|
299
|
+
)}
|
|
300
|
+
|
|
301
|
+
<textarea
|
|
302
|
+
value={state.jsonString}
|
|
303
|
+
onChange={(e) => actions.updateJsonString(e.target.value)}
|
|
304
|
+
className="w-full h-96 px-3 py-2 border border-gray-300 rounded-md font-mono text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
305
|
+
placeholder="Enter JSON configuration..."
|
|
306
|
+
/>
|
|
307
|
+
</div>
|
|
308
|
+
) : (
|
|
309
|
+
// Form View
|
|
310
|
+
<div>
|
|
311
|
+
{/* Tab Navigation */}
|
|
312
|
+
<div className="border-b border-gray-200">
|
|
313
|
+
<nav className="-mb-px flex space-x-8 px-6">
|
|
314
|
+
{[
|
|
315
|
+
{
|
|
316
|
+
key: 'qualifiers' as const,
|
|
317
|
+
label: 'Qualifiers',
|
|
318
|
+
count: state.currentConfiguration.qualifiers?.length || 0
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
key: 'qualifierTypes' as const,
|
|
322
|
+
label: 'Qualifier Types',
|
|
323
|
+
count: state.currentConfiguration.qualifierTypes?.length || 0
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
key: 'resourceTypes' as const,
|
|
327
|
+
label: 'Resource Types',
|
|
328
|
+
count: state.currentConfiguration.resourceTypes?.length || 0
|
|
329
|
+
}
|
|
330
|
+
].map((tab) => (
|
|
331
|
+
<button
|
|
332
|
+
key={tab.key}
|
|
333
|
+
onClick={() => actions.setActiveTab(tab.key)}
|
|
334
|
+
className={`py-4 px-1 border-b-2 font-medium text-sm whitespace-nowrap ${
|
|
335
|
+
state.activeTab === tab.key
|
|
336
|
+
? 'border-blue-600 text-blue-600'
|
|
337
|
+
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
|
338
|
+
}`}
|
|
339
|
+
>
|
|
340
|
+
{tab.label}
|
|
341
|
+
<span className="ml-2 bg-gray-100 text-gray-900 rounded-full px-2.5 py-0.5 text-xs font-medium">
|
|
342
|
+
{tab.count}
|
|
343
|
+
</span>
|
|
344
|
+
</button>
|
|
345
|
+
))}
|
|
346
|
+
</nav>
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
{/* Tab Content */}
|
|
350
|
+
<div className="p-6">
|
|
351
|
+
{state.activeTab === 'qualifiers' && (
|
|
352
|
+
<QualifiersPanel
|
|
353
|
+
qualifiers={state.currentConfiguration.qualifiers || []}
|
|
354
|
+
qualifierTypes={state.currentConfiguration.qualifierTypes || []}
|
|
355
|
+
onUpdateItem={actions.updateQualifier}
|
|
356
|
+
onRemove={actions.removeQualifier}
|
|
357
|
+
onShowAdd={() => setShowAddQualifier(true)}
|
|
358
|
+
onEdit={(item, index) => setEditingQualifier({ item, index })}
|
|
359
|
+
/>
|
|
360
|
+
)}
|
|
361
|
+
|
|
362
|
+
{state.activeTab === 'qualifierTypes' && (
|
|
363
|
+
<QualifierTypesPanel
|
|
364
|
+
qualifierTypes={state.currentConfiguration.qualifierTypes || []}
|
|
365
|
+
onUpdateItem={actions.updateQualifierType}
|
|
366
|
+
onRemove={actions.removeQualifierType}
|
|
367
|
+
onShowAdd={() => setShowAddQualifierType(true)}
|
|
368
|
+
onEdit={(item, index) => setEditingQualifierType({ item, index })}
|
|
369
|
+
/>
|
|
370
|
+
)}
|
|
371
|
+
|
|
372
|
+
{state.activeTab === 'resourceTypes' && (
|
|
373
|
+
<ResourceTypesPanel
|
|
374
|
+
resourceTypes={state.currentConfiguration.resourceTypes || []}
|
|
375
|
+
onUpdateItem={actions.updateResourceType}
|
|
376
|
+
onRemove={actions.removeResourceType}
|
|
377
|
+
onShowAdd={() => setShowAddResourceType(true)}
|
|
378
|
+
onEdit={(item, index) => setEditingResourceType({ item, index })}
|
|
379
|
+
/>
|
|
380
|
+
)}
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
)}
|
|
384
|
+
</div>
|
|
385
|
+
|
|
386
|
+
{/* Hidden file input */}
|
|
387
|
+
<input
|
|
388
|
+
ref={fileInputRef}
|
|
389
|
+
type="file"
|
|
390
|
+
accept=".json"
|
|
391
|
+
onChange={(e) => handleFileImport(e.target.files)}
|
|
392
|
+
className="hidden"
|
|
393
|
+
/>
|
|
394
|
+
|
|
395
|
+
{/* Edit Modals */}
|
|
396
|
+
{showAddQualifierType && (
|
|
397
|
+
<QualifierTypeEditForm
|
|
398
|
+
onSave={(qualifierType) => {
|
|
399
|
+
actions.addQualifierType(qualifierType);
|
|
400
|
+
setShowAddQualifierType(false);
|
|
401
|
+
onMessage?.('success', `Added qualifier type: ${qualifierType.name}`);
|
|
402
|
+
}}
|
|
403
|
+
onCancel={() => setShowAddQualifierType(false)}
|
|
404
|
+
existingNames={(state.currentConfiguration.qualifierTypes || []).map((qt) => qt.name)}
|
|
405
|
+
/>
|
|
406
|
+
)}
|
|
407
|
+
|
|
408
|
+
{editingQualifierType && (
|
|
409
|
+
<QualifierTypeEditForm
|
|
410
|
+
qualifierType={editingQualifierType.item}
|
|
411
|
+
onSave={(qualifierType) => {
|
|
412
|
+
actions.updateQualifierType(editingQualifierType.index, qualifierType);
|
|
413
|
+
setEditingQualifierType(null);
|
|
414
|
+
onMessage?.('success', `Updated qualifier type: ${qualifierType.name}`);
|
|
415
|
+
}}
|
|
416
|
+
onCancel={() => setEditingQualifierType(null)}
|
|
417
|
+
existingNames={(state.currentConfiguration.qualifierTypes || [])
|
|
418
|
+
.filter((_, i) => i !== editingQualifierType.index)
|
|
419
|
+
.map((qt) => qt.name)}
|
|
420
|
+
/>
|
|
421
|
+
)}
|
|
422
|
+
|
|
423
|
+
{showAddQualifier && (
|
|
424
|
+
<QualifierEditForm
|
|
425
|
+
qualifierTypes={state.currentConfiguration.qualifierTypes || []}
|
|
426
|
+
onSave={(qualifier) => {
|
|
427
|
+
actions.addQualifier(qualifier);
|
|
428
|
+
setShowAddQualifier(false);
|
|
429
|
+
onMessage?.('success', `Added qualifier: ${qualifier.name}`);
|
|
430
|
+
}}
|
|
431
|
+
onCancel={() => setShowAddQualifier(false)}
|
|
432
|
+
existingNames={(state.currentConfiguration.qualifiers || []).map((q) => q.name)}
|
|
433
|
+
/>
|
|
434
|
+
)}
|
|
435
|
+
|
|
436
|
+
{editingQualifier && (
|
|
437
|
+
<QualifierEditForm
|
|
438
|
+
qualifier={editingQualifier.item}
|
|
439
|
+
qualifierTypes={state.currentConfiguration.qualifierTypes || []}
|
|
440
|
+
onSave={(qualifier) => {
|
|
441
|
+
actions.updateQualifier(editingQualifier.index, qualifier);
|
|
442
|
+
setEditingQualifier(null);
|
|
443
|
+
onMessage?.('success', `Updated qualifier: ${qualifier.name}`);
|
|
444
|
+
}}
|
|
445
|
+
onCancel={() => setEditingQualifier(null)}
|
|
446
|
+
existingNames={(state.currentConfiguration.qualifiers || [])
|
|
447
|
+
.filter((_, i) => i !== editingQualifier.index)
|
|
448
|
+
.map((q) => q.name)}
|
|
449
|
+
/>
|
|
450
|
+
)}
|
|
451
|
+
|
|
452
|
+
{showAddResourceType && (
|
|
453
|
+
<ResourceTypeEditForm
|
|
454
|
+
onSave={(resourceType) => {
|
|
455
|
+
actions.addResourceType(resourceType);
|
|
456
|
+
setShowAddResourceType(false);
|
|
457
|
+
onMessage?.('success', `Added resource type: ${resourceType.name}`);
|
|
458
|
+
}}
|
|
459
|
+
onCancel={() => setShowAddResourceType(false)}
|
|
460
|
+
existingNames={(state.currentConfiguration.resourceTypes || []).map((rt) => rt.name)}
|
|
461
|
+
/>
|
|
462
|
+
)}
|
|
463
|
+
|
|
464
|
+
{editingResourceType && (
|
|
465
|
+
<ResourceTypeEditForm
|
|
466
|
+
resourceType={editingResourceType.item}
|
|
467
|
+
onSave={(resourceType) => {
|
|
468
|
+
actions.updateResourceType(editingResourceType.index, resourceType);
|
|
469
|
+
setEditingResourceType(null);
|
|
470
|
+
onMessage?.('success', `Updated resource type: ${resourceType.name}`);
|
|
471
|
+
}}
|
|
472
|
+
onCancel={() => setEditingResourceType(null)}
|
|
473
|
+
existingNames={(state.currentConfiguration.resourceTypes || [])
|
|
474
|
+
.filter((_, i) => i !== editingResourceType.index)
|
|
475
|
+
.map((rt) => rt.name)}
|
|
476
|
+
/>
|
|
477
|
+
)}
|
|
478
|
+
</div>
|
|
479
|
+
);
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// Comprehensive panel components with full editing capabilities
|
|
483
|
+
interface QualifierTypesPanelProps {
|
|
484
|
+
qualifierTypes: QualifierTypes.Config.ISystemQualifierTypeConfig[];
|
|
485
|
+
onUpdateItem: (index: number, qualifierType: QualifierTypes.Config.ISystemQualifierTypeConfig) => void;
|
|
486
|
+
onRemove: (index: number) => void;
|
|
487
|
+
onShowAdd: () => void;
|
|
488
|
+
onEdit: (item: QualifierTypes.Config.ISystemQualifierTypeConfig, index: number) => void;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const QualifierTypesPanel: React.FC<QualifierTypesPanelProps> = ({
|
|
492
|
+
qualifierTypes,
|
|
493
|
+
onRemove,
|
|
494
|
+
onShowAdd,
|
|
495
|
+
onEdit
|
|
496
|
+
}) => {
|
|
497
|
+
const getConfigurationSummary = (type: QualifierTypes.Config.ISystemQualifierTypeConfig): string => {
|
|
498
|
+
if (!type.configuration) return 'No configuration';
|
|
499
|
+
const config = type.configuration as any;
|
|
500
|
+
const details: string[] = [];
|
|
501
|
+
|
|
502
|
+
if (config.allowContextList) details.push('Context List');
|
|
503
|
+
if (type.systemType === 'literal') {
|
|
504
|
+
if (config.caseSensitive === false) details.push('Case Insensitive');
|
|
505
|
+
if (config.enumeratedValues?.length) details.push(`${config.enumeratedValues.length} values`);
|
|
506
|
+
}
|
|
507
|
+
if (type.systemType === 'territory') {
|
|
508
|
+
if (config.acceptLowercase) details.push('Accept Lowercase');
|
|
509
|
+
if (config.allowedTerritories?.length) details.push(`${config.allowedTerritories.length} territories`);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return details.length > 0 ? details.join(', ') : 'Default settings';
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
return (
|
|
516
|
+
<div>
|
|
517
|
+
<div className="flex items-center justify-between mb-4">
|
|
518
|
+
<h3 className="text-lg font-semibold text-gray-900">Qualifier Types</h3>
|
|
519
|
+
<button
|
|
520
|
+
onClick={onShowAdd}
|
|
521
|
+
className="inline-flex items-center px-3 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
|
522
|
+
>
|
|
523
|
+
<PlusIcon className="w-4 h-4 mr-2" />
|
|
524
|
+
Add Type
|
|
525
|
+
</button>
|
|
526
|
+
</div>
|
|
527
|
+
|
|
528
|
+
{qualifierTypes.length === 0 ? (
|
|
529
|
+
<div className="text-center py-8 text-gray-500">
|
|
530
|
+
<CogIcon className="w-12 h-12 mx-auto mb-4 text-gray-400" />
|
|
531
|
+
<p>No qualifier types defined</p>
|
|
532
|
+
<p className="text-sm">Add a qualifier type to get started</p>
|
|
533
|
+
</div>
|
|
534
|
+
) : (
|
|
535
|
+
<div className="space-y-3">
|
|
536
|
+
{qualifierTypes.map((type, index) => (
|
|
537
|
+
<div
|
|
538
|
+
key={index}
|
|
539
|
+
className="bg-gray-50 p-4 rounded-lg border border-gray-200 hover:border-gray-300 transition-colors"
|
|
540
|
+
>
|
|
541
|
+
<div className="flex items-start justify-between">
|
|
542
|
+
<div className="flex-1">
|
|
543
|
+
<div className="flex items-center space-x-2 mb-2">
|
|
544
|
+
<h4 className="font-medium text-gray-900">{type.name}</h4>
|
|
545
|
+
<span
|
|
546
|
+
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
|
547
|
+
type.systemType === 'language'
|
|
548
|
+
? 'bg-blue-100 text-blue-800'
|
|
549
|
+
: type.systemType === 'territory'
|
|
550
|
+
? 'bg-green-100 text-green-800'
|
|
551
|
+
: 'bg-purple-100 text-purple-800'
|
|
552
|
+
}`}
|
|
553
|
+
>
|
|
554
|
+
{type.systemType}
|
|
555
|
+
</span>
|
|
556
|
+
</div>
|
|
557
|
+
<p className="text-sm text-gray-600 mb-2">{getConfigurationSummary(type)}</p>
|
|
558
|
+
</div>
|
|
559
|
+
<div className="flex items-center space-x-2 ml-4">
|
|
560
|
+
<button
|
|
561
|
+
onClick={() => onEdit(type, index)}
|
|
562
|
+
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded"
|
|
563
|
+
title="Edit qualifier type"
|
|
564
|
+
>
|
|
565
|
+
<PencilIcon className="w-4 h-4" />
|
|
566
|
+
</button>
|
|
567
|
+
<button
|
|
568
|
+
onClick={() => onRemove(index)}
|
|
569
|
+
className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded"
|
|
570
|
+
title="Delete qualifier type"
|
|
571
|
+
>
|
|
572
|
+
<TrashIcon className="w-4 h-4" />
|
|
573
|
+
</button>
|
|
574
|
+
</div>
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
))}
|
|
578
|
+
</div>
|
|
579
|
+
)}
|
|
580
|
+
</div>
|
|
581
|
+
);
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
interface QualifiersPanelProps {
|
|
585
|
+
qualifiers: Qualifiers.IQualifierDecl[];
|
|
586
|
+
qualifierTypes: QualifierTypes.Config.ISystemQualifierTypeConfig[];
|
|
587
|
+
onUpdateItem: (index: number, qualifier: Qualifiers.IQualifierDecl) => void;
|
|
588
|
+
onRemove: (index: number) => void;
|
|
589
|
+
onShowAdd: () => void;
|
|
590
|
+
onEdit: (item: Qualifiers.IQualifierDecl, index: number) => void;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const QualifiersPanel: React.FC<QualifiersPanelProps> = ({
|
|
594
|
+
qualifiers,
|
|
595
|
+
qualifierTypes,
|
|
596
|
+
onRemove,
|
|
597
|
+
onShowAdd,
|
|
598
|
+
onEdit
|
|
599
|
+
}) => {
|
|
600
|
+
// Sort qualifiers by priority (highest first)
|
|
601
|
+
const sortedQualifiers = [...qualifiers].sort((a, b) => b.defaultPriority - a.defaultPriority);
|
|
602
|
+
|
|
603
|
+
const getQualifierSummary = (qualifier: Qualifiers.IQualifierDecl): string => {
|
|
604
|
+
const qualifierType = qualifierTypes.find((qt) => qt.name === qualifier.typeName);
|
|
605
|
+
const details: string[] = [];
|
|
606
|
+
|
|
607
|
+
if (qualifier.token) details.push(`Token: ${qualifier.token}`);
|
|
608
|
+
if (qualifier.defaultValue) details.push(`Default: ${qualifier.defaultValue}`);
|
|
609
|
+
if (qualifier.tokenIsOptional) details.push('Optional Token');
|
|
610
|
+
if (qualifierType) details.push(`System: ${qualifierType.systemType}`);
|
|
611
|
+
|
|
612
|
+
return details.join(' • ');
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
return (
|
|
616
|
+
<div>
|
|
617
|
+
<div className="flex items-center justify-between mb-4">
|
|
618
|
+
<h3 className="text-lg font-semibold text-gray-900">Qualifiers</h3>
|
|
619
|
+
<button
|
|
620
|
+
onClick={onShowAdd}
|
|
621
|
+
disabled={qualifierTypes.length === 0}
|
|
622
|
+
className="inline-flex items-center px-3 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
|
|
623
|
+
title={qualifierTypes.length === 0 ? 'Add qualifier types first' : 'Add qualifier'}
|
|
624
|
+
>
|
|
625
|
+
<PlusIcon className="w-4 h-4 mr-2" />
|
|
626
|
+
Add Qualifier
|
|
627
|
+
</button>
|
|
628
|
+
</div>
|
|
629
|
+
|
|
630
|
+
{qualifierTypes.length === 0 ? (
|
|
631
|
+
<div className="text-center py-8 text-gray-500">
|
|
632
|
+
<ExclamationTriangleIcon className="w-12 h-12 mx-auto mb-4 text-amber-400" />
|
|
633
|
+
<p>No qualifier types available</p>
|
|
634
|
+
<p className="text-sm">Create qualifier types first before adding qualifiers</p>
|
|
635
|
+
</div>
|
|
636
|
+
) : qualifiers.length === 0 ? (
|
|
637
|
+
<div className="text-center py-8 text-gray-500">
|
|
638
|
+
<CogIcon className="w-12 h-12 mx-auto mb-4 text-gray-400" />
|
|
639
|
+
<p>No qualifiers defined</p>
|
|
640
|
+
<p className="text-sm">Add a qualifier to get started</p>
|
|
641
|
+
</div>
|
|
642
|
+
) : (
|
|
643
|
+
<div className="space-y-3">
|
|
644
|
+
{sortedQualifiers.map((qualifier, index) => {
|
|
645
|
+
const originalIndex = qualifiers.findIndex((q) => q === qualifier);
|
|
646
|
+
const qualifierType = qualifierTypes.find((qt) => qt.name === qualifier.typeName);
|
|
647
|
+
|
|
648
|
+
return (
|
|
649
|
+
<div
|
|
650
|
+
key={originalIndex}
|
|
651
|
+
className="bg-gray-50 p-4 rounded-lg border border-gray-200 hover:border-gray-300 transition-colors"
|
|
652
|
+
>
|
|
653
|
+
<div className="flex items-center justify-between">
|
|
654
|
+
<div className="flex-1">
|
|
655
|
+
{/* Header line with name, type, and token */}
|
|
656
|
+
<div className="flex items-center space-x-3 mb-2">
|
|
657
|
+
<h4 className="font-medium text-gray-900">{qualifier.name}</h4>
|
|
658
|
+
<span className="text-gray-600 text-sm">{qualifier.typeName}</span>
|
|
659
|
+
{qualifier.token && (
|
|
660
|
+
<span className="px-2 py-1 text-xs font-medium bg-purple-100 text-purple-700 rounded">
|
|
661
|
+
token: {qualifier.token}
|
|
662
|
+
</span>
|
|
663
|
+
)}
|
|
664
|
+
{!qualifierType && (
|
|
665
|
+
<span className="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 rounded-full">
|
|
666
|
+
Missing Type
|
|
667
|
+
</span>
|
|
668
|
+
)}
|
|
669
|
+
</div>
|
|
670
|
+
{/* Bottom line with type and priority */}
|
|
671
|
+
<div className="flex items-center justify-between text-sm text-gray-600">
|
|
672
|
+
<span>Type: {qualifier.typeName}</span>
|
|
673
|
+
<span>Priority: {qualifier.defaultPriority}</span>
|
|
674
|
+
</div>
|
|
675
|
+
</div>
|
|
676
|
+
<div className="flex items-center space-x-2 ml-4">
|
|
677
|
+
<button
|
|
678
|
+
onClick={() => onEdit(qualifier, originalIndex)}
|
|
679
|
+
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded"
|
|
680
|
+
title="Edit qualifier"
|
|
681
|
+
>
|
|
682
|
+
<PencilIcon className="w-4 h-4" />
|
|
683
|
+
</button>
|
|
684
|
+
<button
|
|
685
|
+
onClick={() => onRemove(originalIndex)}
|
|
686
|
+
className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded"
|
|
687
|
+
title="Delete qualifier"
|
|
688
|
+
>
|
|
689
|
+
<TrashIcon className="w-4 h-4" />
|
|
690
|
+
</button>
|
|
691
|
+
</div>
|
|
692
|
+
</div>
|
|
693
|
+
</div>
|
|
694
|
+
);
|
|
695
|
+
})}
|
|
696
|
+
</div>
|
|
697
|
+
)}
|
|
698
|
+
</div>
|
|
699
|
+
);
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
interface ResourceTypesPanelProps {
|
|
703
|
+
resourceTypes: ResourceTypes.Config.IResourceTypeConfig[];
|
|
704
|
+
onUpdateItem: (index: number, resourceType: ResourceTypes.Config.IResourceTypeConfig) => void;
|
|
705
|
+
onRemove: (index: number) => void;
|
|
706
|
+
onShowAdd: () => void;
|
|
707
|
+
onEdit: (item: ResourceTypes.Config.IResourceTypeConfig, index: number) => void;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const ResourceTypesPanel: React.FC<ResourceTypesPanelProps> = ({
|
|
711
|
+
resourceTypes,
|
|
712
|
+
onRemove,
|
|
713
|
+
onShowAdd,
|
|
714
|
+
onEdit
|
|
715
|
+
}) => {
|
|
716
|
+
const getTypeNameBadgeColor = (typeName: string): string => {
|
|
717
|
+
switch (typeName) {
|
|
718
|
+
case 'string':
|
|
719
|
+
return 'bg-blue-100 text-blue-800';
|
|
720
|
+
case 'object':
|
|
721
|
+
return 'bg-green-100 text-green-800';
|
|
722
|
+
case 'array':
|
|
723
|
+
return 'bg-purple-100 text-purple-800';
|
|
724
|
+
case 'number':
|
|
725
|
+
return 'bg-yellow-100 text-yellow-800';
|
|
726
|
+
case 'boolean':
|
|
727
|
+
return 'bg-red-100 text-red-800';
|
|
728
|
+
default:
|
|
729
|
+
return 'bg-gray-100 text-gray-800';
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
return (
|
|
734
|
+
<div>
|
|
735
|
+
<div className="flex items-center justify-between mb-4">
|
|
736
|
+
<h3 className="text-lg font-semibold text-gray-900">Resource Types</h3>
|
|
737
|
+
<button
|
|
738
|
+
onClick={onShowAdd}
|
|
739
|
+
className="inline-flex items-center px-3 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
|
740
|
+
>
|
|
741
|
+
<PlusIcon className="w-4 h-4 mr-2" />
|
|
742
|
+
Add Type
|
|
743
|
+
</button>
|
|
744
|
+
</div>
|
|
745
|
+
|
|
746
|
+
{resourceTypes.length === 0 ? (
|
|
747
|
+
<div className="text-center py-8 text-gray-500">
|
|
748
|
+
<CogIcon className="w-12 h-12 mx-auto mb-4 text-gray-400" />
|
|
749
|
+
<p>No resource types defined</p>
|
|
750
|
+
<p className="text-sm">Add a resource type to get started</p>
|
|
751
|
+
</div>
|
|
752
|
+
) : (
|
|
753
|
+
<div className="space-y-3">
|
|
754
|
+
{resourceTypes.map((type, index) => (
|
|
755
|
+
<div
|
|
756
|
+
key={index}
|
|
757
|
+
className="bg-gray-50 p-4 rounded-lg border border-gray-200 hover:border-gray-300 transition-colors"
|
|
758
|
+
>
|
|
759
|
+
<div className="flex items-start justify-between">
|
|
760
|
+
<div className="flex-1">
|
|
761
|
+
<div className="flex items-center space-x-2 mb-2">
|
|
762
|
+
<h4 className="font-medium text-gray-900">{type.name}</h4>
|
|
763
|
+
<span
|
|
764
|
+
className={`px-2 py-1 text-xs font-medium rounded-full ${getTypeNameBadgeColor(
|
|
765
|
+
type.typeName
|
|
766
|
+
)}`}
|
|
767
|
+
>
|
|
768
|
+
{type.typeName}
|
|
769
|
+
</span>
|
|
770
|
+
</div>
|
|
771
|
+
<p className="text-sm text-gray-600">
|
|
772
|
+
Defines how resources of this type are processed and validated
|
|
773
|
+
</p>
|
|
774
|
+
</div>
|
|
775
|
+
<div className="flex items-center space-x-2 ml-4">
|
|
776
|
+
<button
|
|
777
|
+
onClick={() => onEdit(type, index)}
|
|
778
|
+
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded"
|
|
779
|
+
title="Edit resource type"
|
|
780
|
+
>
|
|
781
|
+
<PencilIcon className="w-4 h-4" />
|
|
782
|
+
</button>
|
|
783
|
+
<button
|
|
784
|
+
onClick={() => onRemove(index)}
|
|
785
|
+
className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded"
|
|
786
|
+
title="Delete resource type"
|
|
787
|
+
>
|
|
788
|
+
<TrashIcon className="w-4 h-4" />
|
|
789
|
+
</button>
|
|
790
|
+
</div>
|
|
791
|
+
</div>
|
|
792
|
+
</div>
|
|
793
|
+
))}
|
|
794
|
+
</div>
|
|
795
|
+
)}
|
|
796
|
+
</div>
|
|
797
|
+
);
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
export default ConfigurationView;
|