@fgv/ts-res-ui-components 5.0.0-21 → 5.0.0-23

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