@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.
Files changed (231) hide show
  1. package/.rush/temp/03c8b056281d9db0a97d8a6e25eea798a160d393.tar.log +271 -0
  2. package/.rush/temp/chunked-rush-logs/ts-res-ui-components.build.chunks.jsonl +9 -0
  3. package/.rush/temp/operation/build/all.log +9 -0
  4. package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
  5. package/.rush/temp/operation/build/state.json +3 -0
  6. package/.rush/temp/shrinkwrap-deps.json +1111 -0
  7. package/README.md +18 -0
  8. package/REFACTORING_PLAN.md +171 -0
  9. package/config/jest.config.json +16 -0
  10. package/config/jest.setup.js +64 -0
  11. package/config/rig.json +16 -0
  12. package/lib/components/common/QualifierContextControl.d.ts +14 -0
  13. package/lib/components/common/QualifierContextControl.d.ts.map +1 -0
  14. package/lib/components/common/QualifierContextControl.js +78 -0
  15. package/lib/components/common/QualifierContextControl.js.map +1 -0
  16. package/lib/components/common/ResourceListView.d.ts +11 -0
  17. package/lib/components/common/ResourceListView.d.ts.map +1 -0
  18. package/lib/components/common/ResourceListView.js +20 -0
  19. package/lib/components/common/ResourceListView.js.map +1 -0
  20. package/lib/components/common/ResourceTreeView.d.ts +12 -0
  21. package/lib/components/common/ResourceTreeView.d.ts.map +1 -0
  22. package/lib/components/common/ResourceTreeView.js +162 -0
  23. package/lib/components/common/ResourceTreeView.js.map +1 -0
  24. package/lib/components/forms/HierarchyEditor.d.ts +10 -0
  25. package/lib/components/forms/HierarchyEditor.d.ts.map +1 -0
  26. package/lib/components/forms/HierarchyEditor.js +106 -0
  27. package/lib/components/forms/HierarchyEditor.js.map +1 -0
  28. package/lib/components/forms/QualifierEditForm.d.ts +11 -0
  29. package/lib/components/forms/QualifierEditForm.d.ts.map +1 -0
  30. package/lib/components/forms/QualifierEditForm.js +181 -0
  31. package/lib/components/forms/QualifierEditForm.js.map +1 -0
  32. package/lib/components/forms/QualifierTypeEditForm.d.ts +10 -0
  33. package/lib/components/forms/QualifierTypeEditForm.d.ts.map +1 -0
  34. package/lib/components/forms/QualifierTypeEditForm.js +172 -0
  35. package/lib/components/forms/QualifierTypeEditForm.js.map +1 -0
  36. package/lib/components/forms/ResourceTypeEditForm.d.ts +10 -0
  37. package/lib/components/forms/ResourceTypeEditForm.d.ts.map +1 -0
  38. package/lib/components/forms/ResourceTypeEditForm.js +188 -0
  39. package/lib/components/forms/ResourceTypeEditForm.js.map +1 -0
  40. package/lib/components/forms/index.d.ts +9 -0
  41. package/lib/components/forms/index.d.ts.map +1 -0
  42. package/lib/components/forms/index.js +5 -0
  43. package/lib/components/forms/index.js.map +1 -0
  44. package/lib/components/orchestrator/ResourceOrchestrator.d.ts +14 -0
  45. package/lib/components/orchestrator/ResourceOrchestrator.d.ts.map +1 -0
  46. package/lib/components/orchestrator/ResourceOrchestrator.js +278 -0
  47. package/lib/components/orchestrator/ResourceOrchestrator.js.map +1 -0
  48. package/lib/components/views/CompiledView/index.d.ts +5 -0
  49. package/lib/components/views/CompiledView/index.d.ts.map +1 -0
  50. package/lib/components/views/CompiledView/index.js +595 -0
  51. package/lib/components/views/CompiledView/index.js.map +1 -0
  52. package/lib/components/views/ConfigurationView/index.d.ts +5 -0
  53. package/lib/components/views/ConfigurationView/index.d.ts.map +1 -0
  54. package/lib/components/views/ConfigurationView/index.js +363 -0
  55. package/lib/components/views/ConfigurationView/index.js.map +1 -0
  56. package/lib/components/views/FilterView/index.d.ts +5 -0
  57. package/lib/components/views/FilterView/index.d.ts.map +1 -0
  58. package/lib/components/views/FilterView/index.js +463 -0
  59. package/lib/components/views/FilterView/index.js.map +1 -0
  60. package/lib/components/views/ImportView/index.d.ts +5 -0
  61. package/lib/components/views/ImportView/index.d.ts.map +1 -0
  62. package/lib/components/views/ImportView/index.js +514 -0
  63. package/lib/components/views/ImportView/index.js.map +1 -0
  64. package/lib/components/views/ResolutionView/EditableJsonView.d.ts +21 -0
  65. package/lib/components/views/ResolutionView/EditableJsonView.d.ts.map +1 -0
  66. package/lib/components/views/ResolutionView/EditableJsonView.js +109 -0
  67. package/lib/components/views/ResolutionView/EditableJsonView.js.map +1 -0
  68. package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts +19 -0
  69. package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts.map +1 -0
  70. package/lib/components/views/ResolutionView/ResolutionEditControls.js +82 -0
  71. package/lib/components/views/ResolutionView/ResolutionEditControls.js.map +1 -0
  72. package/lib/components/views/ResolutionView/index.d.ts +5 -0
  73. package/lib/components/views/ResolutionView/index.d.ts.map +1 -0
  74. package/lib/components/views/ResolutionView/index.js +255 -0
  75. package/lib/components/views/ResolutionView/index.js.map +1 -0
  76. package/lib/components/views/SourceView/index.d.ts +5 -0
  77. package/lib/components/views/SourceView/index.d.ts.map +1 -0
  78. package/lib/components/views/SourceView/index.js +316 -0
  79. package/lib/components/views/SourceView/index.js.map +1 -0
  80. package/lib/components/views/ZipLoaderView/index.d.ts +5 -0
  81. package/lib/components/views/ZipLoaderView/index.d.ts.map +1 -0
  82. package/lib/components/views/ZipLoaderView/index.js +313 -0
  83. package/lib/components/views/ZipLoaderView/index.js.map +1 -0
  84. package/lib/hooks/useConfigurationState.d.ts +46 -0
  85. package/lib/hooks/useConfigurationState.d.ts.map +1 -0
  86. package/lib/hooks/useConfigurationState.js +239 -0
  87. package/lib/hooks/useConfigurationState.js.map +1 -0
  88. package/lib/hooks/useFilterState.d.ts +7 -0
  89. package/lib/hooks/useFilterState.d.ts.map +1 -0
  90. package/lib/hooks/useFilterState.js +80 -0
  91. package/lib/hooks/useFilterState.js.map +1 -0
  92. package/lib/hooks/useResolutionState.d.ts +8 -0
  93. package/lib/hooks/useResolutionState.d.ts.map +1 -0
  94. package/lib/hooks/useResolutionState.js +253 -0
  95. package/lib/hooks/useResolutionState.js.map +1 -0
  96. package/lib/hooks/useResourceData.d.ts +19 -0
  97. package/lib/hooks/useResourceData.d.ts.map +1 -0
  98. package/lib/hooks/useResourceData.js +368 -0
  99. package/lib/hooks/useResourceData.js.map +1 -0
  100. package/lib/hooks/useViewState.d.ts +10 -0
  101. package/lib/hooks/useViewState.d.ts.map +1 -0
  102. package/lib/hooks/useViewState.js +29 -0
  103. package/lib/hooks/useViewState.js.map +1 -0
  104. package/lib/index.d.ts +27 -0
  105. package/lib/index.d.ts.map +1 -0
  106. package/lib/index.js +34 -0
  107. package/lib/index.js.map +1 -0
  108. package/lib/test/helpers/testDataLoader.d.ts +37 -0
  109. package/lib/test/helpers/testDataLoader.d.ts.map +1 -0
  110. package/lib/test/helpers/testDataLoader.js +171 -0
  111. package/lib/test/helpers/testDataLoader.js.map +1 -0
  112. package/lib/test/unit/utils/configurationUtils.test.d.ts +2 -0
  113. package/lib/test/unit/utils/configurationUtils.test.d.ts.map +1 -0
  114. package/lib/test/unit/utils/configurationUtils.test.js +497 -0
  115. package/lib/test/unit/utils/configurationUtils.test.js.map +1 -0
  116. package/lib/test/unit/utils/fileProcessing.test.d.ts +2 -0
  117. package/lib/test/unit/utils/fileProcessing.test.d.ts.map +1 -0
  118. package/lib/test/unit/utils/fileProcessing.test.js +321 -0
  119. package/lib/test/unit/utils/fileProcessing.test.js.map +1 -0
  120. package/lib/test/unit/utils/filterResources.test.d.ts +2 -0
  121. package/lib/test/unit/utils/filterResources.test.d.ts.map +1 -0
  122. package/lib/test/unit/utils/filterResources.test.js +403 -0
  123. package/lib/test/unit/utils/filterResources.test.js.map +1 -0
  124. package/lib/test/unit/utils/resolutionEditing.test.d.ts +2 -0
  125. package/lib/test/unit/utils/resolutionEditing.test.d.ts.map +1 -0
  126. package/lib/test/unit/utils/resolutionEditing.test.js +439 -0
  127. package/lib/test/unit/utils/resolutionEditing.test.js.map +1 -0
  128. package/lib/test/unit/utils/resolutionUtils.test.d.ts +2 -0
  129. package/lib/test/unit/utils/resolutionUtils.test.d.ts.map +1 -0
  130. package/lib/test/unit/utils/resolutionUtils.test.js +397 -0
  131. package/lib/test/unit/utils/resolutionUtils.test.js.map +1 -0
  132. package/lib/test/unit/utils/tsResIntegration.test.d.ts +2 -0
  133. package/lib/test/unit/utils/tsResIntegration.test.d.ts.map +1 -0
  134. package/lib/test/unit/utils/tsResIntegration.test.js +376 -0
  135. package/lib/test/unit/utils/tsResIntegration.test.js.map +1 -0
  136. package/lib/types/index.d.ts +251 -0
  137. package/lib/types/index.d.ts.map +1 -0
  138. package/lib/types/index.js +2 -0
  139. package/lib/types/index.js.map +1 -0
  140. package/lib/utils/configurationUtils.d.ts +74 -0
  141. package/lib/utils/configurationUtils.d.ts.map +1 -0
  142. package/lib/utils/configurationUtils.js +359 -0
  143. package/lib/utils/configurationUtils.js.map +1 -0
  144. package/lib/utils/fileProcessing.d.ts +18 -0
  145. package/lib/utils/fileProcessing.d.ts.map +1 -0
  146. package/lib/utils/fileProcessing.js +142 -0
  147. package/lib/utils/fileProcessing.js.map +1 -0
  148. package/lib/utils/filterResources.d.ts +38 -0
  149. package/lib/utils/filterResources.d.ts.map +1 -0
  150. package/lib/utils/filterResources.js +153 -0
  151. package/lib/utils/filterResources.js.map +1 -0
  152. package/lib/utils/resolutionEditing.d.ts +58 -0
  153. package/lib/utils/resolutionEditing.d.ts.map +1 -0
  154. package/lib/utils/resolutionEditing.js +246 -0
  155. package/lib/utils/resolutionEditing.js.map +1 -0
  156. package/lib/utils/resolutionUtils.d.ts +28 -0
  157. package/lib/utils/resolutionUtils.d.ts.map +1 -0
  158. package/lib/utils/resolutionUtils.js +216 -0
  159. package/lib/utils/resolutionUtils.js.map +1 -0
  160. package/lib/utils/tsResIntegration.d.ts +71 -0
  161. package/lib/utils/tsResIntegration.d.ts.map +1 -0
  162. package/lib/utils/tsResIntegration.js +294 -0
  163. package/lib/utils/tsResIntegration.js.map +1 -0
  164. package/lib/utils/zipLoader/browserZipLoader.d.ts +48 -0
  165. package/lib/utils/zipLoader/browserZipLoader.d.ts.map +1 -0
  166. package/lib/utils/zipLoader/browserZipLoader.js +247 -0
  167. package/lib/utils/zipLoader/browserZipLoader.js.map +1 -0
  168. package/lib/utils/zipLoader/index.d.ts +8 -0
  169. package/lib/utils/zipLoader/index.d.ts.map +1 -0
  170. package/lib/utils/zipLoader/index.js +13 -0
  171. package/lib/utils/zipLoader/index.js.map +1 -0
  172. package/lib/utils/zipLoader/nodeZipBuilder.d.ts +55 -0
  173. package/lib/utils/zipLoader/nodeZipBuilder.d.ts.map +1 -0
  174. package/lib/utils/zipLoader/nodeZipBuilder.js +98 -0
  175. package/lib/utils/zipLoader/nodeZipBuilder.js.map +1 -0
  176. package/lib/utils/zipLoader/types.d.ts +139 -0
  177. package/lib/utils/zipLoader/types.d.ts.map +1 -0
  178. package/lib/utils/zipLoader/types.js +2 -0
  179. package/lib/utils/zipLoader/types.js.map +1 -0
  180. package/lib/utils/zipLoader/zipUtils.d.ts +53 -0
  181. package/lib/utils/zipLoader/zipUtils.d.ts.map +1 -0
  182. package/lib/utils/zipLoader/zipUtils.js +229 -0
  183. package/lib/utils/zipLoader/zipUtils.js.map +1 -0
  184. package/package.json +69 -0
  185. package/rush-logs/ts-res-ui-components.build.cache.log +3 -0
  186. package/rush-logs/ts-res-ui-components.build.log +9 -0
  187. package/src/components/common/QualifierContextControl.tsx +151 -0
  188. package/src/components/common/ResourceListView.tsx +63 -0
  189. package/src/components/common/ResourceTreeView.tsx +271 -0
  190. package/src/components/forms/HierarchyEditor.tsx +204 -0
  191. package/src/components/forms/QualifierEditForm.tsx +355 -0
  192. package/src/components/forms/QualifierTypeEditForm.tsx +347 -0
  193. package/src/components/forms/ResourceTypeEditForm.tsx +331 -0
  194. package/src/components/forms/index.ts +11 -0
  195. package/src/components/orchestrator/ResourceOrchestrator.tsx +372 -0
  196. package/src/components/views/CompiledView/index.tsx +922 -0
  197. package/src/components/views/ConfigurationView/index.tsx +800 -0
  198. package/src/components/views/FilterView/index.tsx +825 -0
  199. package/src/components/views/ImportView/index.tsx +717 -0
  200. package/src/components/views/ResolutionView/EditableJsonView.tsx +214 -0
  201. package/src/components/views/ResolutionView/ResolutionEditControls.tsx +170 -0
  202. package/src/components/views/ResolutionView/index.tsx +591 -0
  203. package/src/components/views/SourceView/index.tsx +536 -0
  204. package/src/components/views/ZipLoaderView/index.tsx +485 -0
  205. package/src/hooks/useConfigurationState.ts +374 -0
  206. package/src/hooks/useFilterState.ts +97 -0
  207. package/src/hooks/useResolutionState.ts +355 -0
  208. package/src/hooks/useResourceData.ts +467 -0
  209. package/src/hooks/useViewState.ts +44 -0
  210. package/src/index.ts +45 -0
  211. package/src/test/helpers/testDataLoader.ts +195 -0
  212. package/src/test/unit/utils/configurationUtils.test.ts +630 -0
  213. package/src/test/unit/utils/fileProcessing.test.ts +391 -0
  214. package/src/test/unit/utils/filterResources.test.ts +574 -0
  215. package/src/test/unit/utils/resolutionEditing.test.ts +556 -0
  216. package/src/test/unit/utils/resolutionUtils.test.ts +521 -0
  217. package/src/test/unit/utils/tsResIntegration.test.ts +433 -0
  218. package/src/types/index.ts +322 -0
  219. package/src/utils/configurationUtils.ts +424 -0
  220. package/src/utils/fileProcessing.ts +160 -0
  221. package/src/utils/filterResources.ts +206 -0
  222. package/src/utils/resolutionEditing.ts +319 -0
  223. package/src/utils/resolutionUtils.ts +289 -0
  224. package/src/utils/tsResIntegration.ts +440 -0
  225. package/src/utils/zipLoader/browserZipLoader.ts +319 -0
  226. package/src/utils/zipLoader/index.ts +26 -0
  227. package/src/utils/zipLoader/nodeZipBuilder.ts +153 -0
  228. package/src/utils/zipLoader/types.ts +175 -0
  229. package/src/utils/zipLoader/zipUtils.ts +266 -0
  230. package/temp/build/typescript/ts_gZid87Hu.json +1 -0
  231. 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;