@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,922 @@
1
+ import React, { useState, useMemo, useCallback, useEffect } from 'react';
2
+ import {
3
+ CubeIcon,
4
+ FolderIcon,
5
+ FolderOpenIcon,
6
+ DocumentTextIcon,
7
+ ChevronRightIcon,
8
+ ChevronDownIcon,
9
+ DocumentArrowDownIcon,
10
+ CodeBracketIcon,
11
+ ChevronUpIcon,
12
+ ArchiveBoxIcon
13
+ } from '@heroicons/react/24/outline';
14
+ import { CompiledViewProps, ProcessedResources, FilterState, FilterResult } from '../../../types';
15
+ import { ResourceJson, Config, Bundle, Resources } from '@fgv/ts-res';
16
+
17
+ interface TreeNode {
18
+ id: string;
19
+ name: string;
20
+ type: 'folder' | 'resource' | 'section';
21
+ children?: TreeNode[];
22
+ data?: any;
23
+ }
24
+
25
+ export const CompiledView: React.FC<CompiledViewProps> = ({
26
+ resources,
27
+ filterState,
28
+ filterResult,
29
+ useNormalization: useNormalizationProp = false,
30
+ onExport,
31
+ onMessage,
32
+ className = ''
33
+ }) => {
34
+ const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
35
+ const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set(['root', 'resources']));
36
+ const [showJsonView, setShowJsonView] = useState(false);
37
+ const [useNormalization, setUseNormalization] = useState(useNormalizationProp);
38
+
39
+ // Update normalization default when bundle state changes
40
+ useEffect(() => {
41
+ if (resources?.isLoadedFromBundle && !useNormalization) {
42
+ setUseNormalization(true);
43
+ }
44
+ }, [resources?.isLoadedFromBundle, useNormalization]);
45
+
46
+ // Use filtered resources when filtering is active and successful
47
+ const isFilteringActive = filterState?.enabled && filterResult?.success === true;
48
+ const activeProcessedResources = isFilteringActive ? filterResult?.processedResources : resources;
49
+
50
+ // Get the active compiled collection
51
+ const activeCompiledCollection = useMemo(() => {
52
+ return isFilteringActive
53
+ ? filterResult?.processedResources?.compiledCollection
54
+ : resources?.compiledCollection;
55
+ }, [
56
+ isFilteringActive,
57
+ filterResult?.processedResources?.compiledCollection,
58
+ resources?.compiledCollection
59
+ ]);
60
+
61
+ // Build tree structure using the compiled collection
62
+ const treeData = useMemo(() => {
63
+ if (!activeCompiledCollection) {
64
+ return null;
65
+ }
66
+
67
+ const tree: TreeNode = {
68
+ id: 'root',
69
+ name: 'Compiled Resources',
70
+ type: 'folder',
71
+ children: []
72
+ };
73
+
74
+ try {
75
+ // Resources section using the compiled collection
76
+ const resourcesCount = activeCompiledCollection.resources?.length || 0;
77
+ const resourcesSection: TreeNode = {
78
+ id: 'resources',
79
+ name: `Resources (${resourcesCount})`,
80
+ type: 'section',
81
+ children: []
82
+ };
83
+
84
+ // Get all resource IDs from the compiled collection
85
+ if (activeCompiledCollection.resources && activeCompiledCollection.resources.length > 0) {
86
+ resourcesSection.children = activeCompiledCollection.resources.map((resource) => ({
87
+ id: `resource-${resource.id}`,
88
+ name: String(resource.id || 'unnamed'),
89
+ type: 'resource' as const,
90
+ data: { type: 'compiled-resource', resource }
91
+ }));
92
+ }
93
+
94
+ tree.children!.push(resourcesSection);
95
+
96
+ // Collectors section - showing from compiled collection
97
+ tree.children!.push({
98
+ id: 'qualifiers',
99
+ name: `Qualifiers (${activeCompiledCollection.qualifiers?.length || 0})`,
100
+ type: 'section',
101
+ data: { type: 'qualifiers', items: activeCompiledCollection.qualifiers }
102
+ });
103
+
104
+ tree.children!.push({
105
+ id: 'qualifier-types',
106
+ name: `Qualifier Types (${activeCompiledCollection.qualifierTypes?.length || 0})`,
107
+ type: 'section',
108
+ data: { type: 'qualifier-types', items: activeCompiledCollection.qualifierTypes }
109
+ });
110
+
111
+ tree.children!.push({
112
+ id: 'resource-types',
113
+ name: `Resource Types (${activeCompiledCollection.resourceTypes?.length || 0})`,
114
+ type: 'section',
115
+ data: { type: 'resource-types', items: activeCompiledCollection.resourceTypes }
116
+ });
117
+
118
+ tree.children!.push({
119
+ id: 'conditions',
120
+ name: `Conditions (${activeCompiledCollection.conditions?.length || 0})`,
121
+ type: 'section',
122
+ data: { type: 'conditions', items: activeCompiledCollection.conditions }
123
+ });
124
+
125
+ tree.children!.push({
126
+ id: 'condition-sets',
127
+ name: `Condition Sets (${activeCompiledCollection.conditionSets?.length || 0})`,
128
+ type: 'section',
129
+ data: { type: 'condition-sets', items: activeCompiledCollection.conditionSets }
130
+ });
131
+
132
+ tree.children!.push({
133
+ id: 'decisions',
134
+ name: `Decisions (${activeCompiledCollection.decisions?.length || 0})`,
135
+ type: 'section',
136
+ data: { type: 'decisions', items: activeCompiledCollection.decisions }
137
+ });
138
+ } catch (error) {
139
+ onMessage?.('error', `Error building tree: ${error instanceof Error ? error.message : String(error)}`);
140
+ }
141
+
142
+ return tree;
143
+ }, [activeCompiledCollection, onMessage]);
144
+
145
+ const handleExportCompiledData = useCallback(async () => {
146
+ if (!activeProcessedResources?.compiledCollection) {
147
+ onMessage?.('error', 'No compiled data available to export');
148
+ return;
149
+ }
150
+
151
+ let compiledCollection = activeProcessedResources.compiledCollection;
152
+
153
+ if (useNormalization && resources?.activeConfiguration) {
154
+ const systemConfigResult = Config.SystemConfiguration.create(resources.activeConfiguration);
155
+ if (systemConfigResult.isSuccess()) {
156
+ // Check if we have a ResourceManagerBuilder (which supports normalization)
157
+ if ('getCompiledResourceCollection' in activeProcessedResources.system.resourceManager) {
158
+ const resourceManagerResult = Bundle.BundleNormalizer.normalize(
159
+ activeProcessedResources.system.resourceManager as Resources.ResourceManagerBuilder,
160
+ systemConfigResult.value
161
+ );
162
+
163
+ if (resourceManagerResult.isSuccess()) {
164
+ const normalizedCompiledResult = resourceManagerResult.value.getCompiledResourceCollection({
165
+ includeMetadata: true
166
+ });
167
+ if (normalizedCompiledResult.isSuccess()) {
168
+ compiledCollection = normalizedCompiledResult.value;
169
+ } else {
170
+ console.warn('Failed to get normalized compiled collection:', normalizedCompiledResult.message);
171
+ }
172
+ } else {
173
+ console.warn('Failed to normalize bundle:', resourceManagerResult.message);
174
+ }
175
+ }
176
+ // For IResourceManager from bundles, the compiled collection is already normalized
177
+ } else {
178
+ console.warn('Failed to create system configuration for normalization:', systemConfigResult.message);
179
+ }
180
+ }
181
+
182
+ const compiledData = {
183
+ ...compiledCollection,
184
+ metadata: {
185
+ exportedAt: new Date().toISOString(),
186
+ type: isFilteringActive ? 'ts-res-filtered-compiled-collection' : 'ts-res-compiled-collection',
187
+ normalized: useNormalization,
188
+ ...(resources?.isLoadedFromBundle && { loadedFromBundle: true }),
189
+ ...(isFilteringActive && { filterContext: filterState?.appliedValues })
190
+ }
191
+ };
192
+
193
+ onExport?.(compiledData, 'json');
194
+ }, [
195
+ activeProcessedResources,
196
+ onMessage,
197
+ isFilteringActive,
198
+ filterState?.appliedValues,
199
+ useNormalization,
200
+ resources,
201
+ onExport
202
+ ]);
203
+
204
+ const handleExportBundle = useCallback(async () => {
205
+ if (!activeProcessedResources?.system?.resourceManager || !resources?.activeConfiguration) {
206
+ onMessage?.('error', 'No resource manager or configuration available to create bundle');
207
+ return;
208
+ }
209
+
210
+ const systemConfigResult = Config.SystemConfiguration.create(resources.activeConfiguration);
211
+ if (systemConfigResult.isFailure()) {
212
+ onMessage?.('error', `Failed to create system configuration: ${systemConfigResult.message}`);
213
+ return;
214
+ }
215
+
216
+ const systemConfig = systemConfigResult.value;
217
+
218
+ const bundleParams: Bundle.IBundleCreateParams = {
219
+ version: '1.0.0',
220
+ description: isFilteringActive
221
+ ? 'Bundle exported from ts-res-ui-components (filtered)'
222
+ : 'Bundle exported from ts-res-ui-components',
223
+ normalize: true
224
+ };
225
+
226
+ // Check if we have a ResourceManagerBuilder (which supports bundle creation)
227
+ if (!('getCompiledResourceCollection' in activeProcessedResources.system.resourceManager)) {
228
+ onMessage?.('error', 'Bundle export is not supported for resources loaded from bundles');
229
+ return;
230
+ }
231
+
232
+ const bundleResult = Bundle.BundleBuilder.create(
233
+ activeProcessedResources.system.resourceManager as Resources.ResourceManagerBuilder,
234
+ systemConfig,
235
+ bundleParams
236
+ );
237
+
238
+ if (bundleResult.isFailure()) {
239
+ onMessage?.('error', `Failed to create bundle: ${bundleResult.message}`);
240
+ return;
241
+ }
242
+
243
+ const bundle = bundleResult.value;
244
+
245
+ const exportBundle = {
246
+ ...bundle,
247
+ exportMetadata: {
248
+ exportedAt: new Date().toISOString(),
249
+ exportedFrom: 'ts-res-ui-components',
250
+ type: isFilteringActive ? 'ts-res-bundle-filtered' : 'ts-res-bundle',
251
+ ...(isFilteringActive && { filterContext: filterState?.appliedValues })
252
+ }
253
+ };
254
+
255
+ onExport?.(exportBundle, 'bundle');
256
+ }, [
257
+ activeProcessedResources?.system?.resourceManager,
258
+ resources?.activeConfiguration,
259
+ onMessage,
260
+ isFilteringActive,
261
+ filterState?.appliedValues,
262
+ onExport
263
+ ]);
264
+
265
+ const handleNodeClick = (node: TreeNode) => {
266
+ setSelectedNodeId(node.id);
267
+ onMessage?.('info', `Selected: ${node.name}`);
268
+
269
+ if (node.type === 'folder' || (node.type === 'section' && node.children)) {
270
+ setExpandedNodes((prev) => {
271
+ const newExpanded = new Set(prev);
272
+ if (newExpanded.has(node.id)) {
273
+ newExpanded.delete(node.id);
274
+ } else {
275
+ newExpanded.add(node.id);
276
+ }
277
+ return newExpanded;
278
+ });
279
+ }
280
+ };
281
+
282
+ const renderTreeNode = (node: TreeNode, level = 0): React.ReactNode => {
283
+ const isExpanded = expandedNodes.has(node.id);
284
+ const isSelected = selectedNodeId === node.id;
285
+ const hasChildren = node.children && node.children.length > 0;
286
+
287
+ return (
288
+ <div key={node.id}>
289
+ <div
290
+ className={`flex items-center px-2 py-1 cursor-pointer hover:bg-gray-100 ${
291
+ isSelected ? 'bg-blue-50 border-r-2 border-blue-500' : ''
292
+ }`}
293
+ style={{ paddingLeft: `${8 + level * 16}px` }}
294
+ onClick={() => handleNodeClick(node)}
295
+ >
296
+ {hasChildren && (
297
+ <div className="w-4 h-4 mr-1 flex items-center justify-center">
298
+ {isExpanded ? (
299
+ <ChevronDownIcon className="w-3 h-3 text-gray-500" />
300
+ ) : (
301
+ <ChevronRightIcon className="w-3 h-3 text-gray-500" />
302
+ )}
303
+ </div>
304
+ )}
305
+ {!hasChildren && <div className="w-5 mr-1" />}
306
+
307
+ <div className="w-4 h-4 mr-2 flex items-center justify-center">
308
+ {node.type === 'folder' ? (
309
+ isExpanded ? (
310
+ <FolderOpenIcon className="w-4 h-4 text-blue-500" />
311
+ ) : (
312
+ <FolderIcon className="w-4 h-4 text-blue-500" />
313
+ )
314
+ ) : node.type === 'resource' ? (
315
+ <DocumentTextIcon className="w-4 h-4 text-green-500" />
316
+ ) : (
317
+ <CubeIcon className="w-4 h-4 text-purple-500" />
318
+ )}
319
+ </div>
320
+
321
+ <span className={`text-sm ${isSelected ? 'font-medium text-blue-900' : 'text-gray-700'}`}>
322
+ {node.name}
323
+ </span>
324
+ </div>
325
+
326
+ {hasChildren && isExpanded && (
327
+ <div>{node.children!.map((child) => renderTreeNode(child, level + 1))}</div>
328
+ )}
329
+ </div>
330
+ );
331
+ };
332
+
333
+ if (!resources) {
334
+ return (
335
+ <div className={`p-6 ${className}`}>
336
+ <div className="flex items-center space-x-3 mb-6">
337
+ <CubeIcon className="h-8 w-8 text-blue-600" />
338
+ <h2 className="text-2xl font-bold text-gray-900">Compiled Resources</h2>
339
+ </div>
340
+
341
+ <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8 text-center">
342
+ <div className="max-w-2xl mx-auto">
343
+ <h3 className="text-xl font-semibold text-gray-900 mb-4">No Compiled Resources</h3>
344
+ <p className="text-gray-600 mb-6">
345
+ Import resources to explore the compiled resource collection.
346
+ </p>
347
+ </div>
348
+ </div>
349
+ </div>
350
+ );
351
+ }
352
+
353
+ const selectedNode = selectedNodeId ? findNodeById(treeData!, selectedNodeId) : null;
354
+
355
+ return (
356
+ <div className={`p-6 ${className}`}>
357
+ <div className="flex items-center justify-between mb-6">
358
+ <div className="flex items-center space-x-3">
359
+ <CubeIcon className="h-8 w-8 text-blue-600" />
360
+ <h2 className="text-2xl font-bold text-gray-900">Compiled Resources</h2>
361
+ {isFilteringActive && (
362
+ <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
363
+ Filtered
364
+ </span>
365
+ )}
366
+ </div>
367
+ {activeProcessedResources && (
368
+ <div className="flex items-center space-x-2">
369
+ <button
370
+ onClick={handleExportCompiledData}
371
+ className="inline-flex items-center px-3 py-1.5 border border-gray-300 text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
372
+ >
373
+ <DocumentArrowDownIcon className="h-4 w-4 mr-1" />
374
+ Export JSON
375
+ </button>
376
+ <button
377
+ onClick={handleExportBundle}
378
+ className="inline-flex items-center px-3 py-1.5 border border-blue-300 text-xs font-medium rounded text-blue-700 bg-blue-50 hover:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
379
+ >
380
+ <ArchiveBoxIcon className="h-4 w-4 mr-1" />
381
+ Export Bundle
382
+ </button>
383
+ </div>
384
+ )}
385
+ </div>
386
+
387
+ {/* Controls Panel */}
388
+ {activeProcessedResources && (
389
+ <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6">
390
+ <div className="flex items-center justify-between">
391
+ <div className="flex items-center space-x-6">
392
+ {/* Normalization Toggle */}
393
+ <div className="flex items-center space-x-2">
394
+ {resources?.isLoadedFromBundle ? (
395
+ <ArchiveBoxIcon className="h-4 w-4 text-blue-600" />
396
+ ) : (
397
+ <CubeIcon className="h-4 w-4 text-gray-600" />
398
+ )}
399
+ <label className="text-sm font-medium text-gray-700">Normalize Output:</label>
400
+ <button
401
+ onClick={() => setUseNormalization(!useNormalization)}
402
+ className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${
403
+ useNormalization ? 'bg-blue-600' : 'bg-gray-300'
404
+ }`}
405
+ >
406
+ <span
407
+ className={`inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${
408
+ useNormalization ? 'translate-x-5' : 'translate-x-1'
409
+ }`}
410
+ />
411
+ </button>
412
+ <span className="text-xs text-gray-500">{useNormalization ? 'ON' : 'OFF'}</span>
413
+ </div>
414
+
415
+ {/* JSON View Toggle */}
416
+ <button
417
+ onClick={() => setShowJsonView(!showJsonView)}
418
+ className="inline-flex items-center px-3 py-1.5 text-sm font-medium text-gray-700 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
419
+ >
420
+ <CodeBracketIcon className="h-4 w-4 mr-2" />
421
+ {showJsonView ? 'Hide' : 'Show'} JSON
422
+ {showJsonView ? (
423
+ <ChevronUpIcon className="h-4 w-4 ml-2" />
424
+ ) : (
425
+ <ChevronDownIcon className="h-4 w-4 ml-2" />
426
+ )}
427
+ </button>
428
+ </div>
429
+ </div>
430
+
431
+ {/* JSON View */}
432
+ {showJsonView && (
433
+ <div className="mt-4">
434
+ <div className="bg-gray-50 rounded-lg border border-gray-200 p-4">
435
+ <pre className="text-xs text-gray-800 bg-white p-3 rounded border overflow-x-auto max-h-64 overflow-y-auto">
436
+ {JSON.stringify(activeProcessedResources.compiledCollection, null, 2)}
437
+ </pre>
438
+ </div>
439
+ </div>
440
+ )}
441
+ </div>
442
+ )}
443
+
444
+ <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
445
+ <div className="flex flex-col lg:flex-row gap-6 h-[600px]">
446
+ {/* Tree Navigation */}
447
+ <div className="lg:w-1/2 flex flex-col">
448
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">Compiled Collection</h3>
449
+ <div className="flex-1 overflow-y-auto border border-gray-200 rounded-lg bg-gray-50">
450
+ {treeData && renderTreeNode(treeData)}
451
+ </div>
452
+ </div>
453
+
454
+ {/* Details Panel */}
455
+ <div className="lg:w-1/2 flex flex-col">
456
+ {selectedNode ? (
457
+ <NodeDetail node={selectedNode} />
458
+ ) : (
459
+ <div className="flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50">
460
+ <div className="text-center">
461
+ <CubeIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" />
462
+ <p className="text-gray-500">Select an item to view details</p>
463
+ </div>
464
+ </div>
465
+ )}
466
+ </div>
467
+ </div>
468
+ </div>
469
+ </div>
470
+ );
471
+ };
472
+
473
+ // Helper function to find node by ID
474
+ const findNodeById = (tree: TreeNode, id: string): TreeNode | null => {
475
+ if (tree.id === id) return tree;
476
+ if (tree.children) {
477
+ for (const child of tree.children) {
478
+ const found = findNodeById(child, id);
479
+ if (found) return found;
480
+ }
481
+ }
482
+ return null;
483
+ };
484
+
485
+ // Runtime-powered NodeDetail component
486
+ interface NodeDetailProps {
487
+ node: TreeNode;
488
+ }
489
+
490
+ const NodeDetail: React.FC<NodeDetailProps> = ({ node }) => {
491
+ const [showRawJson, setShowRawJson] = useState(false);
492
+
493
+ const renderDetails = () => {
494
+ if (!node.data) {
495
+ return (
496
+ <div className="space-y-4">
497
+ <div className="bg-white rounded-lg border p-4">
498
+ <h4 className="font-medium text-gray-700 mb-2">📁 {node.name}</h4>
499
+ <p className="text-sm text-gray-600">
500
+ {node.children ? `Contains ${node.children.length} items` : 'Empty folder'}
501
+ </p>
502
+ </div>
503
+ </div>
504
+ );
505
+ }
506
+
507
+ const { type, resourceId, manager } = node.data;
508
+
509
+ switch (type) {
510
+ case 'runtime-resource':
511
+ return renderRuntimeResource(resourceId, manager);
512
+
513
+ case 'qualifiers':
514
+ case 'qualifier-types':
515
+ case 'resource-types':
516
+ case 'conditions':
517
+ case 'condition-sets':
518
+ case 'decisions':
519
+ return renderRuntimeCollection(type, manager);
520
+
521
+ default:
522
+ return renderRawData();
523
+ }
524
+ };
525
+
526
+ // New function that uses runtime objects instead of manual JSON manipulation
527
+ const renderRuntimeResource = (resourceId: string, manager: any) => {
528
+ // Get the full runtime resource object with all its rich data
529
+ const resourceResult = manager.getBuiltResource(resourceId);
530
+
531
+ if (resourceResult.isFailure()) {
532
+ return (
533
+ <div className="bg-white rounded-lg border p-4">
534
+ <h4 className="font-medium text-red-700 mb-2">Error Loading Resource</h4>
535
+ <p className="text-sm text-red-600">{resourceResult.message}</p>
536
+ </div>
537
+ );
538
+ }
539
+
540
+ const resource = resourceResult.value;
541
+
542
+ return (
543
+ <div className="space-y-6">
544
+ {/* Resource Details Header */}
545
+ <div className="bg-gray-50 rounded-lg p-4">
546
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">Compiled Resource Details</h3>
547
+
548
+ <div className="bg-white rounded-lg border p-4 space-y-3">
549
+ <div className="flex items-start">
550
+ <span className="font-semibold text-gray-700 w-32">ID:</span>
551
+ <span className="font-mono text-gray-900">{resource.id}</span>
552
+ </div>
553
+
554
+ <div className="flex items-start">
555
+ <span className="font-semibold text-gray-700 w-32">Resource Type:</span>
556
+ <span className="text-gray-900">
557
+ {resource.resourceType.name || resource.resourceType.key}
558
+ <span className="ml-2 text-xs font-mono bg-gray-100 px-2 py-1 rounded">
559
+ ({resource.resourceType.index})
560
+ </span>
561
+ </span>
562
+ </div>
563
+
564
+ <div className="flex items-start">
565
+ <span className="font-semibold text-gray-700 w-32">Decision:</span>
566
+ <span className="text-gray-900">
567
+ <span className="font-mono">
568
+ Decision {resource.decision.baseDecision?.index ?? resource.decision.index ?? 'unknown'}{' '}
569
+ with {resource.decision.candidates.length} candidates
570
+ </span>
571
+ </span>
572
+ </div>
573
+ </div>
574
+ </div>
575
+
576
+ {/* Candidates Section - Now with full candidate data including bodies! */}
577
+ <div className="bg-gray-50 rounded-lg p-4">
578
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">Candidates</h3>
579
+
580
+ <div className="space-y-4">
581
+ {resource.candidates.map((candidate: any, candidateIdx: number) => {
582
+ // Get the corresponding decision candidate to access condition sets
583
+ const decisionCandidate = resource.decision.candidates[candidateIdx];
584
+
585
+ return (
586
+ <div key={candidateIdx} className="bg-white rounded-lg border p-4">
587
+ <div className="mb-3">
588
+ <h4 className="font-semibold text-gray-900">
589
+ Candidate {candidateIdx + 1}
590
+ {candidate.isPartial && (
591
+ <span className="ml-2 bg-yellow-100 text-yellow-800 px-2 py-1 rounded text-xs">
592
+ Partial
593
+ </span>
594
+ )}
595
+ </h4>
596
+ </div>
597
+
598
+ {/* Candidate JSON Data - This is the actual resource content! */}
599
+ <div className="bg-gray-50 rounded p-3 mb-3">
600
+ <h6 className="text-sm font-semibold text-gray-700 mb-2">Resource Content:</h6>
601
+ <pre className="text-sm font-mono text-gray-800 whitespace-pre-wrap">
602
+ {JSON.stringify(candidate.json, null, 2)}
603
+ </pre>
604
+ </div>
605
+
606
+ {/* Conditions from the condition set */}
607
+ {decisionCandidate?.conditionSet &&
608
+ decisionCandidate.conditionSet.conditions.length > 0 && (
609
+ <div className="border-t pt-3">
610
+ <h5 className="text-sm font-semibold text-gray-700 mb-2">
611
+ Condition Set {decisionCandidate.conditionSet.index}:
612
+ </h5>
613
+ <div className="space-y-2">
614
+ {decisionCandidate.conditionSet.conditions.map((condition: any, idx: number) => (
615
+ <div key={idx} className="flex items-center text-sm bg-blue-50 rounded px-3 py-2">
616
+ <span className="font-mono text-blue-700 mr-2 text-xs">
617
+ {condition.index.toString().padStart(2, '0')}:
618
+ </span>
619
+ <span className="font-medium text-blue-900 mr-2">
620
+ {condition.qualifier.name}
621
+ </span>
622
+ <span className="text-blue-700 mr-2">{condition.operator}</span>
623
+ <span className="font-mono text-blue-800">{condition.value}</span>
624
+ <span className="ml-auto text-xs text-blue-600">
625
+ Priority: {condition.priority}
626
+ {condition.scoreAsDefault !== undefined && (
627
+ <span className="ml-2">Default: {condition.scoreAsDefault}</span>
628
+ )}
629
+ </span>
630
+ </div>
631
+ ))}
632
+ </div>
633
+ </div>
634
+ )}
635
+
636
+ {(!decisionCandidate?.conditionSet ||
637
+ decisionCandidate.conditionSet.conditions.length === 0) && (
638
+ <div className="border-t pt-3">
639
+ <span className="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded">
640
+ No conditions (default candidate)
641
+ </span>
642
+ </div>
643
+ )}
644
+ </div>
645
+ );
646
+ })}
647
+ </div>
648
+ </div>
649
+ </div>
650
+ );
651
+ };
652
+
653
+ // New function that uses runtime collections instead of raw JSON
654
+ const renderRuntimeCollection = (collectionType: string, manager: any) => {
655
+ let collector: any;
656
+ let title: string;
657
+
658
+ switch (collectionType) {
659
+ case 'qualifiers':
660
+ collector = manager.qualifiers;
661
+ title = 'Qualifiers';
662
+ break;
663
+ case 'qualifier-types':
664
+ collector = manager.qualifierTypes;
665
+ title = 'Qualifier Types';
666
+ break;
667
+ case 'resource-types':
668
+ collector = manager.resourceTypes;
669
+ title = 'Resource Types';
670
+ break;
671
+ case 'conditions':
672
+ collector = manager.conditions;
673
+ title = 'Conditions';
674
+ break;
675
+ case 'condition-sets':
676
+ collector = manager.conditionSets;
677
+ title = 'Condition Sets';
678
+ break;
679
+ case 'decisions':
680
+ collector = manager.decisions;
681
+ title = 'Decisions';
682
+ break;
683
+ default:
684
+ return (
685
+ <div className="bg-white rounded-lg border p-4">
686
+ <p className="text-sm text-gray-500">Unknown collection type: {collectionType}</p>
687
+ </div>
688
+ );
689
+ }
690
+
691
+ const items = Array.from(collector.values());
692
+
693
+ return (
694
+ <div className="bg-gray-50 rounded-lg p-4">
695
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">
696
+ {title} <span className="text-sm font-normal text-gray-600">({items.length})</span>
697
+ </h3>
698
+
699
+ {items.length === 0 ? (
700
+ <div className="bg-white rounded-lg border p-4">
701
+ <p className="text-sm text-gray-500">No {title.toLowerCase()} available</p>
702
+ </div>
703
+ ) : (
704
+ <div className="space-y-3 max-h-96 overflow-y-auto">
705
+ {items.map((item: any, index: number) => (
706
+ <div key={index} className="bg-white rounded-lg border p-4">
707
+ <div className="flex items-start justify-between mb-3">
708
+ <div>
709
+ <h4 className="font-semibold text-gray-900">
710
+ {getItemDisplayName(item, collectionType, index)}
711
+ </h4>
712
+ <p className="text-sm text-gray-600 font-mono mt-1">
713
+ {getItemDisplayKey(item, collectionType)}
714
+ </p>
715
+ </div>
716
+ {item.index !== undefined && (
717
+ <span className="bg-gray-100 text-gray-700 px-2 py-1 rounded text-xs font-mono">
718
+ Index: {item.index}
719
+ </span>
720
+ )}
721
+ </div>
722
+
723
+ <div className="border-t pt-3">{renderRuntimeItemDetail(item, collectionType)}</div>
724
+ </div>
725
+ ))}
726
+ </div>
727
+ )}
728
+ </div>
729
+ );
730
+ };
731
+
732
+ // Helper functions for runtime object display
733
+ const getItemDisplayName = (item: any, collectionType: string, index: number): string => {
734
+ switch (collectionType) {
735
+ case 'qualifiers':
736
+ return `${item.index}: ${item.name} (${item.type.name})`;
737
+ case 'qualifier-types':
738
+ return `${item.index}: ${item.name} (${item.systemType})`;
739
+ case 'resource-types':
740
+ return `${item.index}: ${item.name || item.key}`;
741
+ case 'conditions':
742
+ return `${item.index.toString().padStart(2, '0')}: ${item.qualifier.name} ${item.operator} ${
743
+ item.value
744
+ }`;
745
+ case 'condition-sets':
746
+ return `${item.index}: ${
747
+ item.conditions.length === 0 ? 'Unconditional' : `${item.conditions.length} conditions`
748
+ }`;
749
+ case 'decisions':
750
+ return `${item.baseDecision?.index ?? item.index}: Decision with ${
751
+ item.candidates.length
752
+ } candidates`;
753
+ default:
754
+ return `${index}: Item ${index}`;
755
+ }
756
+ };
757
+
758
+ const getItemDisplayKey = (item: any, collectionType: string): string => {
759
+ try {
760
+ switch (collectionType) {
761
+ case 'qualifiers':
762
+ return `defaultPriority: ${item?.defaultPriority ?? 'N/A'}`;
763
+ case 'qualifier-types':
764
+ return `allowContextList: ${item?.allowContextList ?? 'N/A'}`;
765
+ case 'resource-types':
766
+ return `key: ${item?.key ?? 'N/A'}`;
767
+ case 'conditions':
768
+ return `priority: ${item?.priority ?? 'N/A'}`;
769
+ case 'condition-sets':
770
+ return (
771
+ item?.conditions
772
+ ?.map((c: any) => `${c?.qualifier?.name ?? 'unknown'}=${c?.value ?? 'unknown'}`)
773
+ .join(', ') || 'default'
774
+ );
775
+ case 'decisions':
776
+ return `${item?.conditionSets?.length ?? 0} condition sets`;
777
+ default:
778
+ return '';
779
+ }
780
+ } catch (error) {
781
+ console.warn('Error in getItemDisplayKey:', error, { item, collectionType });
782
+ return 'Display error';
783
+ }
784
+ };
785
+
786
+ const renderRuntimeItemDetail = (item: any, collectionType: string) => {
787
+ switch (collectionType) {
788
+ case 'qualifiers':
789
+ return (
790
+ <div className="space-y-2">
791
+ <div>
792
+ <span className="font-medium">Type:</span> {item.type.name}
793
+ </div>
794
+ <div>
795
+ <span className="font-medium">Default Priority:</span> {item.defaultPriority}
796
+ </div>
797
+ </div>
798
+ );
799
+ case 'qualifier-types':
800
+ return (
801
+ <div className="space-y-2">
802
+ <div>
803
+ <span className="font-medium">System Type:</span> {item.systemType}
804
+ </div>
805
+ <div>
806
+ <span className="font-medium">Allow Context List:</span> {item.allowContextList ? 'Yes' : 'No'}
807
+ </div>
808
+ {item.enumeratedValues && (
809
+ <div>
810
+ <span className="font-medium">Enumerated Values:</span> {item.enumeratedValues.join(', ')}
811
+ </div>
812
+ )}
813
+ </div>
814
+ );
815
+ case 'resource-types':
816
+ return (
817
+ <div className="space-y-2">
818
+ <div>
819
+ <span className="font-medium">Key:</span> {item.key}
820
+ </div>
821
+ <div>
822
+ <span className="font-medium">Name:</span> {item.name}
823
+ </div>
824
+ </div>
825
+ );
826
+ case 'conditions':
827
+ return (
828
+ <div className="space-y-2">
829
+ <div>
830
+ <span className="font-medium">Qualifier:</span> {item.qualifier.name}
831
+ </div>
832
+ <div>
833
+ <span className="font-medium">Operator:</span> {item.operator}
834
+ </div>
835
+ <div>
836
+ <span className="font-medium">Value:</span> {item.value}
837
+ </div>
838
+ <div>
839
+ <span className="font-medium">Priority:</span> {item.priority}
840
+ </div>
841
+ {item.scoreAsDefault !== undefined && (
842
+ <div>
843
+ <span className="font-medium">Score As Default:</span> {item.scoreAsDefault}
844
+ </div>
845
+ )}
846
+ </div>
847
+ );
848
+ case 'condition-sets':
849
+ return (
850
+ <div className="space-y-2">
851
+ <div>
852
+ <span className="font-medium">Conditions:</span> {item.conditions?.length ?? 0}
853
+ </div>
854
+ {(item.conditions?.length ?? 0) > 0 && (
855
+ <div className="space-y-1">
856
+ {item.conditions?.map((condition: any, idx: number) => (
857
+ <div key={idx} className="text-xs bg-blue-50 rounded px-2 py-1">
858
+ {condition.qualifier.name} {condition.operator} {condition.value} (p:{condition.priority})
859
+ </div>
860
+ ))}
861
+ </div>
862
+ )}
863
+ </div>
864
+ );
865
+ case 'decisions':
866
+ return (
867
+ <div className="space-y-2">
868
+ <div>
869
+ <span className="font-medium">Condition Sets:</span> {item.conditionSets?.length ?? 0}
870
+ </div>
871
+ <div>
872
+ <span className="font-medium">Candidates:</span> {item.candidates?.length ?? 0}
873
+ </div>
874
+ </div>
875
+ );
876
+ default:
877
+ return (
878
+ <pre className="text-xs bg-gray-50 p-2 rounded overflow-x-auto">
879
+ {JSON.stringify(item, null, 2)}
880
+ </pre>
881
+ );
882
+ }
883
+ };
884
+
885
+ const renderRawData = () => {
886
+ return (
887
+ <div className="bg-white rounded-lg border p-4">
888
+ <h4 className="font-medium text-gray-700 mb-2">{node.name}</h4>
889
+ <pre className="text-xs bg-gray-50 p-2 rounded overflow-x-auto max-h-96 overflow-y-auto">
890
+ {JSON.stringify(node.data, null, 2)}
891
+ </pre>
892
+ </div>
893
+ );
894
+ };
895
+
896
+ return (
897
+ <div className="flex flex-col h-full">
898
+ <div className="flex items-center justify-between mb-4">
899
+ <h3 className="text-lg font-semibold text-gray-900">Details</h3>
900
+ <button
901
+ onClick={() => setShowRawJson(!showRawJson)}
902
+ className="text-xs text-gray-500 hover:text-gray-700 px-2 py-1 rounded hover:bg-gray-100"
903
+ >
904
+ {showRawJson ? 'Rich View' : 'Raw JSON'}
905
+ </button>
906
+ </div>
907
+ <div className="flex-1 overflow-y-auto">
908
+ {showRawJson ? (
909
+ <div className="bg-white rounded-lg border p-4">
910
+ <pre className="text-xs bg-gray-50 p-3 rounded overflow-x-auto">
911
+ {JSON.stringify(node.data, null, 2)}
912
+ </pre>
913
+ </div>
914
+ ) : (
915
+ renderDetails()
916
+ )}
917
+ </div>
918
+ </div>
919
+ );
920
+ };
921
+
922
+ export default CompiledView;