@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,493 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { ResourceDetailData } from '../../../types';
3
-
4
- /**
5
- * Props for the SourceResourceDetail component.
6
- *
7
- * @public
8
- */
9
- export interface SourceResourceDetailProps {
10
- /** The resource ID to display details for */
11
- resourceId: string;
12
- /** Processed resources containing the resource data */
13
- processedResources: any;
14
- /** Optional callback for handling component messages */
15
- onMessage?: (type: 'info' | 'warning' | 'error' | 'success', message: string) => void;
16
- /** Optional CSS classes to apply to the container */
17
- className?: string;
18
- /** Optional title for the detail panel */
19
- title?: string;
20
- // Dual-resource comparison mode props
21
- /** Optional original resources for comparison mode */
22
- originalProcessedResources?: any;
23
- /** Optional filter context for comparison */
24
- filterContext?: Record<string, string | undefined>;
25
- /** Whether to show comparison view */
26
- showComparison?: boolean;
27
- // Configurable toggle labels
28
- /** Label for primary resource view (default: 'Current') */
29
- primaryLabel?: string;
30
- /** Label for secondary resource view (default: 'Original') */
31
- secondaryLabel?: string;
32
- }
33
-
34
- /**
35
- * A comprehensive component for displaying detailed information about a specific resource.
36
- *
37
- * SourceResourceDetail provides an in-depth view of a resource's properties, candidates,
38
- * conditions, and metadata. It supports both single resource display and comparison mode
39
- * for viewing differences between filtered and original resources. The component automatically
40
- * extracts and presents resource information in a structured, readable format.
41
- *
42
- * @example
43
- * ```tsx
44
- * import { SourceResourceDetail } from '@fgv/ts-res-ui-components';
45
- *
46
- * function ResourceInspector() {
47
- * const [selectedResourceId, setSelectedResourceId] = useState<string>('user.welcome');
48
- * const processedResources = useProcessedResources();
49
- *
50
- * return (
51
- * <SourceResourceDetail
52
- * resourceId={selectedResourceId}
53
- * processedResources={processedResources}
54
- * title="Resource Inspector"
55
- * onMessage={(type, msg) => console.log(`${type}: ${msg}`)}
56
- * className="border rounded-lg p-4"
57
- * />
58
- * );
59
- * }
60
- * ```
61
- *
62
- * @example
63
- * ```tsx
64
- * // Using comparison mode to show filtered vs original resources
65
- * import { SourceResourceDetail, FilterTools } from '@fgv/ts-res-ui-components';
66
- *
67
- * function FilteredResourceComparison() {
68
- * const { state: filterState } = FilterTools.useFilterState();
69
- * const originalResources = useOriginalResources();
70
- * const filteredResources = filterState.filteredResources;
71
- * const [selectedId, setSelectedId] = useState<string | null>(null);
72
- *
73
- * if (!selectedId) {
74
- * return <div>Select a resource to compare</div>;
75
- * }
76
- *
77
- * return (
78
- * <SourceResourceDetail
79
- * resourceId={selectedId}
80
- * processedResources={filteredResources}
81
- * originalProcessedResources={originalResources}
82
- * filterContext={filterState.appliedValues}
83
- * showComparison={true}
84
- * primaryLabel="Filtered"
85
- * secondaryLabel="Original"
86
- * title="Resource Comparison"
87
- * className="comparison-panel"
88
- * />
89
- * );
90
- * }
91
- * ```
92
- *
93
- * @example
94
- * ```tsx
95
- * // Integration with orchestrator for comprehensive resource details
96
- * import { SourceResourceDetail, ResourceTools } from '@fgv/ts-res-ui-components';
97
- *
98
- * function OrchestratorResourceDetail() {
99
- * const { state, actions } = ResourceTools.useResourceData();
100
- *
101
- * if (!state.selectedResourceId || !state.resources) {
102
- * return <div className="p-4 text-gray-500">No resource selected</div>;
103
- * }
104
- *
105
- * return (
106
- * <div className="resource-detail-container">
107
- * <div className="detail-header">
108
- * <h2>Resource: {state.selectedResourceId}</h2>
109
- * <button onClick={() => actions.selectResource(null)}>Clear</button>
110
- * </div>
111
- * <SourceResourceDetail
112
- * resourceId={state.selectedResourceId}
113
- * processedResources={state.resources}
114
- * originalProcessedResources={state.filterResult ? state.resources : undefined}
115
- * filterContext={state.filterState.appliedValues}
116
- * showComparison={!!state.filterResult}
117
- * title="Resource Details"
118
- * onMessage={actions.addMessage}
119
- * className="detail-content"
120
- * />
121
- * </div>
122
- * );
123
- * }
124
- * ```
125
- *
126
- * @public
127
- */
128
- export const SourceResourceDetail: React.FC<SourceResourceDetailProps> = ({
129
- resourceId,
130
- processedResources,
131
- onMessage,
132
- className = '',
133
- title = 'Resource Details',
134
- originalProcessedResources,
135
- filterContext,
136
- showComparison = false,
137
- primaryLabel = 'Current',
138
- secondaryLabel = 'Original'
139
- }) => {
140
- const [resourceDetail, setResourceDetail] = useState<ResourceDetailData | null>(null);
141
- const [originalResourceDetail, setOriginalResourceDetail] = useState<ResourceDetailData | null>(null);
142
- const [isLoading, setIsLoading] = useState(false);
143
- const [error, setError] = useState<string | null>(null);
144
- const [showFilteredView, setShowFilteredView] = useState(true);
145
-
146
- useEffect(() => {
147
- const loadResourceDetail = () => {
148
- setIsLoading(true);
149
- setError(null);
150
-
151
- try {
152
- // Helper function to extract resource detail from processed resources
153
- const extractResourceDetail = (resources: any, isOriginal = false): ResourceDetailData | null => {
154
- const resourceManager = resources.system.resourceManager;
155
- const resourceResult = resourceManager.getBuiltResource(resourceId);
156
-
157
- if (!resourceResult.isSuccess()) {
158
- return null;
159
- }
160
-
161
- const resource = resourceResult.value;
162
- let candidateDetails: any[] = [];
163
-
164
- // Check if candidates have conditions property (ResourceManagerBuilder format)
165
- if (resource.candidates.length > 0 && 'conditions' in resource.candidates[0]) {
166
- // ResourceManagerBuilder format with full condition details
167
- candidateDetails = resource.candidates.map((candidate: any) => ({
168
- json: candidate.json,
169
- conditions:
170
- candidate.conditions?.conditions?.map((condition: any) => ({
171
- qualifier: condition.qualifier.name,
172
- operator: condition.operator,
173
- value: condition.value,
174
- priority: condition.priority,
175
- scoreAsDefault: condition.scoreAsDefault
176
- })) || [],
177
- isPartial: candidate.isPartial,
178
- mergeMethod: candidate.mergeMethod
179
- }));
180
- } else {
181
- // IResourceManager format - extract conditions from compiled collection
182
- const compiledCollection = resources.compiledCollection;
183
- const compiledResource = compiledCollection?.resources?.find((r: any) => r.id === resourceId);
184
-
185
- candidateDetails = resource.candidates.map((candidate: any, index: number) => {
186
- // Try to get conditions from the compiled collection
187
- let conditions: any[] = [];
188
-
189
- if (compiledResource && compiledCollection) {
190
- const decision = compiledCollection.decisions?.[compiledResource.decision];
191
- if (decision?.conditionSets && index < decision.conditionSets.length) {
192
- const conditionSetIndex = decision.conditionSets[index];
193
- const conditionSet = compiledCollection.conditionSets?.[conditionSetIndex];
194
-
195
- if (conditionSet?.conditions) {
196
- conditions = conditionSet.conditions
197
- .map((condIndex: number) => {
198
- const condition = compiledCollection.conditions?.[condIndex];
199
- const qualifier = compiledCollection.qualifiers?.[condition?.qualifierIndex];
200
- return condition && qualifier
201
- ? {
202
- qualifier: qualifier.name,
203
- operator: condition.operator || 'eq',
204
- value: condition.value,
205
- priority: condition.priority || qualifier.defaultPriority || 500,
206
- scoreAsDefault: condition.scoreAsDefault
207
- }
208
- : null;
209
- })
210
- .filter(Boolean);
211
- }
212
- }
213
- }
214
-
215
- return {
216
- json: candidate.json,
217
- conditions: conditions,
218
- isPartial: candidate.isPartial,
219
- mergeMethod: candidate.mergeMethod
220
- };
221
- });
222
- }
223
-
224
- return {
225
- id: resource.id,
226
- resourceType: resource.resourceType.key || resource.resourceType.name || 'unknown',
227
- candidateCount: resource.candidates.length,
228
- candidates: candidateDetails
229
- };
230
- };
231
-
232
- // Load current/filtered resource detail
233
- const currentDetail = extractResourceDetail(processedResources, false);
234
- if (currentDetail) {
235
- setResourceDetail(currentDetail);
236
- } else {
237
- setError(`Failed to load resource details for: ${resourceId}`);
238
- onMessage?.('error', `Failed to load resource details for: ${resourceId}`);
239
- return;
240
- }
241
-
242
- // Load original resource detail if in comparison mode
243
- if (showComparison && originalProcessedResources) {
244
- const originalDetail = extractResourceDetail(originalProcessedResources, true);
245
- if (originalDetail) {
246
- setOriginalResourceDetail(originalDetail);
247
- }
248
- } else {
249
- setOriginalResourceDetail(null);
250
- }
251
-
252
- onMessage?.('info', `Loaded details for resource: ${resourceId}`);
253
- } catch (err) {
254
- const errorMsg = `Error loading resource details: ${
255
- err instanceof Error ? err.message : String(err)
256
- }`;
257
- setError(errorMsg);
258
- onMessage?.('error', errorMsg);
259
- } finally {
260
- setIsLoading(false);
261
- }
262
- };
263
-
264
- loadResourceDetail();
265
- }, [resourceId, processedResources, originalProcessedResources, showComparison, onMessage]);
266
-
267
- if (isLoading) {
268
- return (
269
- <div className={`flex flex-col h-full ${className}`}>
270
- <h3 className="text-lg font-semibold text-gray-900 mb-4">{title}</h3>
271
- <div className="flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50">
272
- <div className="text-center">
273
- <div className="animate-spin h-8 w-8 border-4 border-blue-600 border-t-transparent rounded-full mx-auto mb-4"></div>
274
- <p className="text-gray-500">Loading resource details...</p>
275
- </div>
276
- </div>
277
- </div>
278
- );
279
- }
280
-
281
- if (error) {
282
- return (
283
- <div className={`flex flex-col h-full ${className}`}>
284
- <h3 className="text-lg font-semibold text-gray-900 mb-4">{title}</h3>
285
- <div className="flex-1 border border-gray-200 rounded-lg p-4 bg-red-50">
286
- <div className="text-center">
287
- <p className="text-red-600 font-medium mb-2">Error Loading Resource</p>
288
- <p className="text-red-500 text-sm">{error}</p>
289
- </div>
290
- </div>
291
- </div>
292
- );
293
- }
294
-
295
- if (!resourceDetail) {
296
- return (
297
- <div className={`flex flex-col h-full ${className}`}>
298
- <h3 className="text-lg font-semibold text-gray-900 mb-4">{title}</h3>
299
- <div className="flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50">
300
- <p className="text-gray-500">No resource details available</p>
301
- </div>
302
- </div>
303
- );
304
- }
305
-
306
- // Determine which resource detail to show and comparison state
307
- const currentDetail =
308
- showComparison && !showFilteredView && originalResourceDetail ? originalResourceDetail : resourceDetail;
309
- const isShowingPrimary = !showComparison || showFilteredView;
310
-
311
- return (
312
- <div className={`flex flex-col h-full ${className}`}>
313
- <div className="flex items-center justify-between mb-4">
314
- <h3 className="text-lg font-semibold text-gray-900">{title}</h3>
315
- {showComparison && originalResourceDetail && (
316
- <div className="flex items-center space-x-2">
317
- <span className="text-xs text-gray-500">View:</span>
318
- <button
319
- onClick={() => setShowFilteredView(false)}
320
- className={`px-2 py-1 text-xs rounded ${
321
- !showFilteredView
322
- ? 'bg-blue-100 text-blue-800 font-medium'
323
- : 'text-gray-600 hover:bg-gray-100'
324
- }`}
325
- >
326
- {secondaryLabel} ({originalResourceDetail.candidateCount})
327
- </button>
328
- <button
329
- onClick={() => setShowFilteredView(true)}
330
- className={`px-2 py-1 text-xs rounded ${
331
- showFilteredView
332
- ? 'bg-purple-100 text-purple-800 font-medium'
333
- : 'text-gray-600 hover:bg-gray-100'
334
- }`}
335
- >
336
- {primaryLabel} ({resourceDetail.candidateCount})
337
- </button>
338
- </div>
339
- )}
340
- </div>
341
-
342
- <div className="flex-1 overflow-y-auto border border-gray-200 rounded-lg p-4 bg-gray-50">
343
- <div className="space-y-6">
344
- {/* Resource Overview */}
345
- <div>
346
- <h4 className="font-medium text-gray-700 mb-3">Resource Overview</h4>
347
- <div className="bg-white p-4 rounded-lg border space-y-3">
348
- <div>
349
- <span className="text-sm font-medium text-gray-600">Fully Qualified ID:</span>
350
- <code className="text-sm bg-gray-100 px-2 py-1 rounded ml-2 break-all">
351
- {currentDetail.id}
352
- </code>
353
- </div>
354
- <div>
355
- <span className="text-sm font-medium text-gray-600">Resource Type:</span>
356
- <span className="ml-2 text-sm">{currentDetail.resourceType}</span>
357
- </div>
358
- <div>
359
- <span className="text-sm font-medium text-gray-600">Candidate Count:</span>
360
- <span
361
- className={`ml-2 text-sm font-medium ${
362
- showComparison && originalResourceDetail
363
- ? currentDetail.candidateCount === 0
364
- ? 'text-red-600'
365
- : currentDetail.candidateCount <
366
- (isShowingPrimary
367
- ? originalResourceDetail.candidateCount
368
- : resourceDetail.candidateCount)
369
- ? 'text-amber-600'
370
- : 'text-green-600'
371
- : 'text-blue-600'
372
- }`}
373
- >
374
- {currentDetail.candidateCount}
375
- {showComparison &&
376
- originalResourceDetail &&
377
- currentDetail.candidateCount !==
378
- (isShowingPrimary
379
- ? originalResourceDetail.candidateCount
380
- : resourceDetail.candidateCount) && (
381
- <span className="text-gray-400 ml-1">
382
- (was{' '}
383
- {isShowingPrimary
384
- ? originalResourceDetail.candidateCount
385
- : resourceDetail.candidateCount}
386
- )
387
- </span>
388
- )}
389
- </span>
390
- </div>
391
- {showComparison && filterContext && (
392
- <div>
393
- <span className="text-sm font-medium text-gray-600">Filter Context:</span>
394
- <span className="ml-2 text-sm text-purple-700">
395
- {Object.entries(filterContext)
396
- .filter(([, value]) => value !== undefined && value !== '')
397
- .map(([key, value]) => `${key}=${value}`)
398
- .join(', ') || 'No filters'}
399
- </span>
400
- </div>
401
- )}
402
- </div>
403
- </div>
404
-
405
- {/* Candidates */}
406
- <div>
407
- <h4 className="font-medium text-gray-700 mb-3">
408
- {showComparison && !isShowingPrimary
409
- ? `${secondaryLabel} `
410
- : showComparison && isShowingPrimary
411
- ? `${primaryLabel} `
412
- : ''}
413
- Candidates ({currentDetail.candidates.length})
414
- </h4>
415
- {currentDetail.candidates.length > 0 ? (
416
- <div className="space-y-4">
417
- {currentDetail.candidates.map((candidate, index) => (
418
- <div key={index} className="bg-white p-4 rounded-lg border">
419
- <div className="flex items-center justify-between mb-3">
420
- <h5 className="font-medium text-gray-800">Candidate {index + 1}</h5>
421
- <div className="flex items-center space-x-2 text-xs">
422
- {candidate.isPartial && (
423
- <span className="bg-yellow-100 text-yellow-800 px-2 py-1 rounded">Partial</span>
424
- )}
425
- <span className="bg-gray-100 text-gray-700 px-2 py-1 rounded">
426
- {candidate.mergeMethod}
427
- </span>
428
- </div>
429
- </div>
430
-
431
- {/* Conditions */}
432
- {candidate.conditions.length > 0 && (
433
- <div className="mb-3">
434
- <h6 className="text-sm font-medium text-gray-600 mb-2">Conditions:</h6>
435
- <div className="space-y-1">
436
- {candidate.conditions.map((condition, condIndex) => (
437
- <div
438
- key={condIndex}
439
- className="flex items-center text-xs bg-blue-50 px-2 py-1 rounded"
440
- >
441
- <span className="font-medium text-blue-800">{condition.qualifier}</span>
442
- <span className="mx-1 text-blue-600">{condition.operator}</span>
443
- <span className="text-blue-700">{condition.value}</span>
444
- <div className="ml-auto flex items-center space-x-2">
445
- <span className="text-blue-500">priority: {condition.priority}</span>
446
- {condition.scoreAsDefault !== undefined && (
447
- <span className="text-amber-600 font-medium">
448
- default: {condition.scoreAsDefault}
449
- </span>
450
- )}
451
- </div>
452
- </div>
453
- ))}
454
- </div>
455
- </div>
456
- )}
457
-
458
- {candidate.conditions.length === 0 && (
459
- <div className="mb-3">
460
- <span className="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded">
461
- No conditions (default candidate)
462
- </span>
463
- </div>
464
- )}
465
-
466
- {/* JSON Content */}
467
- <div>
468
- <h6 className="text-sm font-medium text-gray-600 mb-2">Content:</h6>
469
- <pre className="text-xs bg-gray-50 p-3 rounded border overflow-x-auto max-h-40">
470
- {JSON.stringify(candidate.json, null, 2)}
471
- </pre>
472
- </div>
473
- </div>
474
- ))}
475
- </div>
476
- ) : (
477
- <div className="bg-red-50 border border-red-200 p-4 rounded-lg">
478
- <p className="text-sm text-red-700 font-medium">No candidates available</p>
479
- <p className="text-xs text-red-600 mt-1">
480
- {showComparison
481
- ? 'This resource has been completely filtered out. Consider adjusting your filter criteria.'
482
- : 'This resource has no candidates defined.'}
483
- </p>
484
- </div>
485
- )}
486
- </div>
487
- </div>
488
- </div>
489
- </div>
490
- );
491
- };
492
-
493
- export default SourceResourceDetail;