@robosystems/core 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/actions/entity-actions.d.ts +8 -0
- package/actions/entity-actions.js +16 -0
- package/actions/graph-actions.d.ts +3 -0
- package/actions/graph-actions.js +13 -0
- package/auth-components/AppSwitcher.d.ts +6 -0
- package/auth-components/AppSwitcher.js +28 -0
- package/auth-components/AuthGuard.d.ts +7 -0
- package/auth-components/AuthGuard.js +31 -0
- package/auth-components/AuthProvider.d.ts +9 -0
- package/auth-components/AuthProvider.js +565 -0
- package/auth-components/SessionWarningDialog.d.ts +1 -0
- package/auth-components/SessionWarningDialog.js +51 -0
- package/auth-components/SignInForm.d.ts +20 -0
- package/auth-components/SignInForm.js +139 -0
- package/auth-components/SignUpForm.d.ts +15 -0
- package/auth-components/SignUpForm.js +167 -0
- package/auth-components/TurnstileWidget.d.ts +99 -0
- package/auth-components/TurnstileWidget.js +224 -0
- package/auth-components/index.d.ts +6 -0
- package/auth-components/index.js +6 -0
- package/auth-core/cleanup.d.ts +44 -0
- package/auth-core/cleanup.js +157 -0
- package/auth-core/client.d.ts +121 -0
- package/auth-core/client.js +767 -0
- package/auth-core/config.d.ts +19 -0
- package/auth-core/config.js +56 -0
- package/auth-core/hooks.d.ts +19 -0
- package/auth-core/hooks.js +63 -0
- package/auth-core/index.d.ts +6 -0
- package/auth-core/index.js +6 -0
- package/auth-core/sso.d.ts +42 -0
- package/auth-core/sso.js +281 -0
- package/auth-core/token-storage.d.ts +58 -0
- package/auth-core/token-storage.js +177 -0
- package/auth-core/types.d.ts +145 -0
- package/auth-core/types.js +1 -0
- package/components/EntitySelector.d.ts +44 -0
- package/components/EntitySelector.js +64 -0
- package/components/EntitySelectorCore.d.ts +45 -0
- package/components/EntitySelectorCore.js +67 -0
- package/components/GraphSelectorCore.d.ts +21 -0
- package/components/GraphSelectorCore.js +39 -0
- package/components/PageLayout.d.ts +8 -0
- package/components/PageLayout.js +5 -0
- package/components/RepositoryGuard.d.ts +68 -0
- package/components/RepositoryGuard.js +91 -0
- package/components/console/ConsoleContent.d.ts +4 -0
- package/components/console/ConsoleContent.js +612 -0
- package/components/console/ConsoleMarkdown.d.ts +11 -0
- package/components/console/ConsoleMarkdown.js +49 -0
- package/components/console/ProgressiveText.d.ts +6 -0
- package/components/console/ProgressiveText.js +21 -0
- package/components/console/graphAwareConfig.d.ts +70 -0
- package/components/console/graphAwareConfig.js +411 -0
- package/components/console/index.d.ts +6 -0
- package/components/console/index.js +4 -0
- package/components/console/types.d.ts +84 -0
- package/components/console/types.js +1 -0
- package/components/graph-filters.d.ts +75 -0
- package/components/graph-filters.js +111 -0
- package/components/index.d.ts +10 -0
- package/components/index.js +9 -0
- package/components/repositories/ActiveSubscriptions.d.ts +26 -0
- package/components/repositories/ActiveSubscriptions.js +103 -0
- package/components/repositories/BrowseRepositories.d.ts +11 -0
- package/components/repositories/BrowseRepositories.js +184 -0
- package/components/repositories/index.d.ts +2 -0
- package/components/repositories/index.js +2 -0
- package/components/search/SearchContent.d.ts +4 -0
- package/components/search/SearchContent.js +168 -0
- package/components/search/index.d.ts +2 -0
- package/components/search/index.js +1 -0
- package/components/search/types.d.ts +15 -0
- package/components/search/types.js +1 -0
- package/contexts/entity-context.d.ts +28 -0
- package/contexts/entity-context.js +89 -0
- package/contexts/graph-context.d.ts +27 -0
- package/contexts/graph-context.js +128 -0
- package/contexts/index.d.ts +5 -0
- package/contexts/index.js +5 -0
- package/contexts/org-context.d.ts +25 -0
- package/contexts/org-context.js +64 -0
- package/contexts/service-offerings-context.d.ts +45 -0
- package/contexts/service-offerings-context.js +98 -0
- package/contexts/sidebar-context.d.ts +18 -0
- package/contexts/sidebar-context.js +44 -0
- package/hooks/index.d.ts +6 -0
- package/hooks/index.js +6 -0
- package/hooks/use-api-error.d.ts +3 -0
- package/hooks/use-api-error.js +24 -0
- package/hooks/use-media-query.d.ts +1 -0
- package/hooks/use-media-query.js +21 -0
- package/hooks/use-streaming-query.d.ts +22 -0
- package/hooks/use-streaming-query.js +142 -0
- package/hooks/use-toast.d.ts +37 -0
- package/hooks/use-toast.js +117 -0
- package/hooks/use-user-limits.d.ts +12 -0
- package/hooks/use-user-limits.js +73 -0
- package/hooks/use-user.d.ts +11 -0
- package/hooks/use-user.js +39 -0
- package/index.d.ts +29 -0
- package/index.js +77 -0
- package/lib/entity-cookie.d.ts +10 -0
- package/lib/entity-cookie.js +38 -0
- package/lib/graph-cookie.d.ts +11 -0
- package/lib/graph-cookie.js +62 -0
- package/lib/graph-tiers.d.ts +41 -0
- package/lib/graph-tiers.js +107 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/sidebar-cookie.d.ts +11 -0
- package/lib/sidebar-cookie.js +42 -0
- package/library/colors.d.ts +4 -0
- package/library/colors.js +41 -0
- package/library/components/ClassificationPicker.d.ts +8 -0
- package/library/components/ClassificationPicker.js +55 -0
- package/library/components/ElementBrowser.d.ts +11 -0
- package/library/components/ElementBrowser.js +106 -0
- package/library/components/ElementDetail.d.ts +7 -0
- package/library/components/ElementDetail.js +97 -0
- package/library/components/LibraryHierarchy.d.ts +13 -0
- package/library/components/LibraryHierarchy.js +380 -0
- package/library/components/TaxonomySidebar.d.ts +6 -0
- package/library/components/TaxonomySidebar.js +45 -0
- package/library/index.d.ts +8 -0
- package/library/index.js +7 -0
- package/package.json +37 -0
- package/research/CoverageCard.d.ts +12 -0
- package/research/CoverageCard.js +15 -0
- package/research/CoverageGrid.d.ts +6 -0
- package/research/CoverageGrid.js +9 -0
- package/research/CoverageHistory.d.ts +5 -0
- package/research/CoverageHistory.js +7 -0
- package/research/ResearchArticle.d.ts +15 -0
- package/research/ResearchArticle.js +26 -0
- package/research/catalog.d.ts +14 -0
- package/research/catalog.js +42 -0
- package/research/index.d.ts +6 -0
- package/research/index.js +9 -0
- package/research/types.d.ts +38 -0
- package/research/types.js +4 -0
- package/task-monitoring/hooks.d.ts +26 -0
- package/task-monitoring/hooks.js +126 -0
- package/task-monitoring/index.d.ts +5 -0
- package/task-monitoring/index.js +7 -0
- package/task-monitoring/operationErrors.d.ts +82 -0
- package/task-monitoring/operationErrors.js +251 -0
- package/task-monitoring/operationHooks.d.ts +117 -0
- package/task-monitoring/operationHooks.js +536 -0
- package/task-monitoring/operationMonitor.d.ts +78 -0
- package/task-monitoring/operationMonitor.js +200 -0
- package/task-monitoring/operationTypes.d.ts +236 -0
- package/task-monitoring/operationTypes.js +60 -0
- package/task-monitoring/taskMonitor.d.ts +14 -0
- package/task-monitoring/taskMonitor.js +132 -0
- package/task-monitoring/types.d.ts +31 -0
- package/task-monitoring/types.js +1 -0
- package/theme/flowbite-theme.d.ts +758 -0
- package/theme/flowbite-theme.js +765 -0
- package/theme/index.d.ts +1 -0
- package/theme/index.js +1 -0
- package/types/entity.d.ts +19 -0
- package/types/index.d.ts +2 -0
- package/types/index.js +1 -0
- package/types/user.d.ts +6 -0
- package/ui-components/ConfirmModal.d.ts +41 -0
- package/ui-components/ConfirmModal.js +19 -0
- package/ui-components/EmptyState.d.ts +22 -0
- package/ui-components/EmptyState.js +10 -0
- package/ui-components/LoadingState.d.ts +27 -0
- package/ui-components/LoadingState.js +12 -0
- package/ui-components/Logo.d.ts +28 -0
- package/ui-components/Logo.js +95 -0
- package/ui-components/Spinner.d.ts +37 -0
- package/ui-components/Spinner.js +48 -0
- package/ui-components/StatCard.d.ts +21 -0
- package/ui-components/StatCard.js +10 -0
- package/ui-components/api-keys/ApiKeyDisplay.d.ts +13 -0
- package/ui-components/api-keys/ApiKeyDisplay.js +7 -0
- package/ui-components/api-keys/ApiKeyTable.d.ts +9 -0
- package/ui-components/api-keys/ApiKeyTable.js +17 -0
- package/ui-components/api-keys/ApiKeysCard.d.ts +6 -0
- package/ui-components/api-keys/ApiKeysCard.js +97 -0
- package/ui-components/api-keys/CreateApiKeyModal.d.ts +11 -0
- package/ui-components/api-keys/CreateApiKeyModal.js +47 -0
- package/ui-components/api-keys/SecureApiKeyField.d.ts +13 -0
- package/ui-components/api-keys/SecureApiKeyField.js +64 -0
- package/ui-components/api-keys/index.d.ts +5 -0
- package/ui-components/api-keys/index.js +5 -0
- package/ui-components/chat/ChatHeader.d.ts +12 -0
- package/ui-components/chat/ChatHeader.js +5 -0
- package/ui-components/chat/ChatInputArea.d.ts +16 -0
- package/ui-components/chat/ChatInputArea.js +9 -0
- package/ui-components/chat/ChatMessage.d.ts +7 -0
- package/ui-components/chat/ChatMessage.js +24 -0
- package/ui-components/chat/DeepResearchToggle.d.ts +8 -0
- package/ui-components/chat/DeepResearchToggle.js +13 -0
- package/ui-components/chat/index.d.ts +9 -0
- package/ui-components/chat/index.js +8 -0
- package/ui-components/chat/types.d.ts +23 -0
- package/ui-components/chat/types.js +51 -0
- package/ui-components/forms/PasswordRequirements.d.ts +10 -0
- package/ui-components/forms/PasswordRequirements.js +15 -0
- package/ui-components/forms/SettingsCard.d.ts +11 -0
- package/ui-components/forms/SettingsCard.js +5 -0
- package/ui-components/forms/SettingsFormField.d.ts +18 -0
- package/ui-components/forms/SettingsFormField.js +17 -0
- package/ui-components/forms/StatusAlert.d.ts +8 -0
- package/ui-components/forms/StatusAlert.js +7 -0
- package/ui-components/forms/index.d.ts +4 -0
- package/ui-components/forms/index.js +4 -0
- package/ui-components/index.d.ts +13 -0
- package/ui-components/index.js +18 -0
- package/ui-components/layout/CoreNavbar.d.ts +14 -0
- package/ui-components/layout/CoreNavbar.js +53 -0
- package/ui-components/layout/CoreSidebar.d.ts +28 -0
- package/ui-components/layout/CoreSidebar.js +62 -0
- package/ui-components/layout/LandingFooter.d.ts +28 -0
- package/ui-components/layout/LandingFooter.js +34 -0
- package/ui-components/layout/PageContainer.d.ts +8 -0
- package/ui-components/layout/PageContainer.js +17 -0
- package/ui-components/layout/PageHeader.d.ts +23 -0
- package/ui-components/layout/PageHeader.js +12 -0
- package/ui-components/layout/SettingsContainer.d.ts +10 -0
- package/ui-components/layout/SettingsContainer.js +9 -0
- package/ui-components/layout/ThemeToggle.d.ts +4 -0
- package/ui-components/layout/ThemeToggle.js +6 -0
- package/ui-components/layout/index.d.ts +7 -0
- package/ui-components/layout/index.js +7 -0
- package/ui-components/settings/GeneralInformationCard.d.ts +12 -0
- package/ui-components/settings/GeneralInformationCard.js +72 -0
- package/ui-components/settings/PasswordInformationCard.d.ts +10 -0
- package/ui-components/settings/PasswordInformationCard.js +167 -0
- package/ui-components/settings/SettingsPageHeader.d.ts +7 -0
- package/ui-components/settings/SettingsPageHeader.js +6 -0
- package/ui-components/settings/index.d.ts +3 -0
- package/ui-components/settings/index.js +3 -0
- package/ui-components/support/SupportModal.d.ts +15 -0
- package/ui-components/support/SupportModal.js +74 -0
- package/ui-components/support/index.d.ts +2 -0
- package/ui-components/support/index.js +1 -0
- package/ui-components/types/index.d.ts +39 -0
- package/ui-components/types/index.js +1 -0
- package/utils/index.d.ts +31 -0
- package/utils/index.js +38 -0
- package/utils/turnstile-config.d.ts +11 -0
- package/utils/turnstile-config.js +19 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Alert, Badge, Button, Card, Select, Spinner, TextInput, } from 'flowbite-react';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
import { HiInformationCircle, HiSearch } from 'react-icons/hi';
|
|
6
|
+
import { classificationColor } from '../colors';
|
|
7
|
+
import { ClassificationPicker } from './ClassificationPicker';
|
|
8
|
+
const PAGE_SIZE = 50;
|
|
9
|
+
export function ElementBrowser({ client, graphId, taxonomyId, taxonomies, onTaxonomyChange, selectedElementId, onSelectElement, }) {
|
|
10
|
+
const [elements, setElements] = useState([]);
|
|
11
|
+
const [state, setState] = useState('idle');
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
const [search, setSearch] = useState('');
|
|
14
|
+
const [offset, setOffset] = useState(0);
|
|
15
|
+
const [classification, setClassification] = useState(null);
|
|
16
|
+
const [activity, setActivity] = useState(null);
|
|
17
|
+
const activeQuery = search.trim();
|
|
18
|
+
const searchMode = activeQuery.length > 0;
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
let cancelled = false;
|
|
21
|
+
setState('loading');
|
|
22
|
+
setError(null);
|
|
23
|
+
const run = async () => {
|
|
24
|
+
try {
|
|
25
|
+
const rows = searchMode
|
|
26
|
+
? await client.searchLibraryElements(graphId, activeQuery, {
|
|
27
|
+
limit: PAGE_SIZE,
|
|
28
|
+
})
|
|
29
|
+
: await client.listLibraryElements(graphId, {
|
|
30
|
+
taxonomyId: taxonomyId !== null && taxonomyId !== void 0 ? taxonomyId : undefined,
|
|
31
|
+
classification: classification && classification !== 'abstract'
|
|
32
|
+
? classification
|
|
33
|
+
: undefined,
|
|
34
|
+
activityType: activity !== null && activity !== void 0 ? activity : undefined,
|
|
35
|
+
isAbstract: classification === 'abstract'
|
|
36
|
+
? true
|
|
37
|
+
: classification !== null
|
|
38
|
+
? false
|
|
39
|
+
: null,
|
|
40
|
+
limit: PAGE_SIZE,
|
|
41
|
+
offset,
|
|
42
|
+
});
|
|
43
|
+
if (cancelled)
|
|
44
|
+
return;
|
|
45
|
+
setElements(rows);
|
|
46
|
+
setState('ready');
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (cancelled)
|
|
50
|
+
return;
|
|
51
|
+
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
52
|
+
setState('error');
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
run();
|
|
56
|
+
return () => {
|
|
57
|
+
cancelled = true;
|
|
58
|
+
};
|
|
59
|
+
}, [
|
|
60
|
+
client,
|
|
61
|
+
graphId,
|
|
62
|
+
taxonomyId,
|
|
63
|
+
offset,
|
|
64
|
+
classification,
|
|
65
|
+
activity,
|
|
66
|
+
searchMode,
|
|
67
|
+
activeQuery,
|
|
68
|
+
]);
|
|
69
|
+
const handleSearchChange = (value) => {
|
|
70
|
+
setSearch(value);
|
|
71
|
+
setOffset(0);
|
|
72
|
+
};
|
|
73
|
+
const handleClassificationChange = (value) => {
|
|
74
|
+
setClassification(value);
|
|
75
|
+
setOffset(0);
|
|
76
|
+
};
|
|
77
|
+
const handleActivityChange = (value) => {
|
|
78
|
+
setActivity(value);
|
|
79
|
+
setOffset(0);
|
|
80
|
+
};
|
|
81
|
+
const handleTaxonomyChange = (id) => {
|
|
82
|
+
setOffset(0);
|
|
83
|
+
onTaxonomyChange === null || onTaxonomyChange === void 0 ? void 0 : onTaxonomyChange(id);
|
|
84
|
+
};
|
|
85
|
+
const showTaxonomyDropdown = taxonomies && onTaxonomyChange;
|
|
86
|
+
return (_jsx("section", { className: "col-span-12 min-h-0 md:col-span-5", children: _jsxs(Card, { className: "flex h-full flex-col overflow-hidden", children: [_jsx("h2", { className: "font-heading shrink-0 text-lg font-semibold text-gray-900 dark:text-white", children: "Elements" }), _jsxs("div", { className: "flex gap-2", children: [showTaxonomyDropdown && (_jsx(Select, { sizing: "sm", value: taxonomyId !== null && taxonomyId !== void 0 ? taxonomyId : '', onChange: (e) => handleTaxonomyChange(e.target.value), className: "shrink-0", "aria-label": "Taxonomy", children: taxonomies.map((t) => {
|
|
87
|
+
var _a;
|
|
88
|
+
return (_jsxs("option", { value: t.id, children: [(_a = t.standard) !== null && _a !== void 0 ? _a : t.name, t.version ? ` ${t.version}` : '', typeof t.elementCount === 'number'
|
|
89
|
+
? ` (${t.elementCount.toLocaleString()})`
|
|
90
|
+
: ''] }, t.id));
|
|
91
|
+
}) })), _jsx(TextInput, { icon: HiSearch, sizing: "sm", placeholder: "Search qname, name, label\u2026", value: search, onChange: (e) => handleSearchChange(e.target.value), className: "flex-1" })] }), _jsx("div", { className: "space-y-1", children: _jsx(ClassificationPicker, { selected: classification, onSelect: handleClassificationChange, activity: activity, onActivityChange: handleActivityChange, disabled: searchMode }) }), state === 'loading' && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400", children: [_jsx(Spinner, { size: "sm" }), " Loading\u2026"] })), state === 'error' && (_jsx(Alert, { color: "failure", icon: HiInformationCircle, children: error })), state === 'ready' && elements.length === 0 && (_jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: "No elements match these filters." })), _jsx("ul", { className: "min-h-0 flex-1 space-y-1 overflow-y-auto", children: elements.map((el) => {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
const isSelected = el.id === selectedElementId;
|
|
94
|
+
return (_jsx("li", { children: _jsxs("button", { onClick: () => onSelectElement(el.id), className: `w-full rounded px-3 py-2 text-left text-sm transition-colors ${isSelected
|
|
95
|
+
? 'bg-primary-100 text-primary-900 dark:bg-primary-900/50 dark:text-primary-100'
|
|
96
|
+
: 'text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-800'}`, children: [_jsx("div", { className: "truncate font-mono text-sm", title: el.qname, children: el.qname }), _jsxs("div", { className: "mt-0.5 flex items-center justify-between gap-2", children: [_jsx("span", { className: "truncate text-xs text-gray-500 dark:text-gray-400", title: el.name, children: el.name }), _jsx(Badge, { color: el.elementType === 'hypercube'
|
|
97
|
+
? 'indigo'
|
|
98
|
+
: el.isAbstract
|
|
99
|
+
? 'purple'
|
|
100
|
+
: classificationColor((_a = el.trait) !== null && _a !== void 0 ? _a : ''), size: "xs", className: "shrink-0", children: el.elementType === 'hypercube'
|
|
101
|
+
? 'hypercube'
|
|
102
|
+
: el.isAbstract
|
|
103
|
+
? 'abstract'
|
|
104
|
+
: ((_b = el.trait) !== null && _b !== void 0 ? _b : '—') })] })] }) }, el.id));
|
|
105
|
+
}) }), !searchMode && state === 'ready' && elements.length === PAGE_SIZE && (_jsxs("div", { className: "flex shrink-0 justify-between pt-2", children: [_jsx(Button, { size: "xs", color: "gray", disabled: offset === 0, onClick: () => setOffset(Math.max(0, offset - PAGE_SIZE)), children: "\u2190 Previous" }), _jsxs("span", { className: "self-center text-xs text-gray-500 dark:text-gray-400", children: [offset + 1, "\u2013", offset + elements.length] }), _jsx(Button, { size: "xs", color: "gray", onClick: () => setOffset(offset + PAGE_SIZE), children: "Next \u2192" })] }))] }) }));
|
|
106
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { LibraryClient } from '@robosystems/client/clients';
|
|
2
|
+
export declare function ElementDetail({ client, graphId, elementId, onSelectElement, }: {
|
|
3
|
+
client: LibraryClient;
|
|
4
|
+
graphId: string;
|
|
5
|
+
elementId: string | null;
|
|
6
|
+
onSelectElement: (id: string) => void;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Alert, Badge, Card, Spinner } from 'flowbite-react';
|
|
4
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
5
|
+
import { HiExternalLink, HiInformationCircle } from 'react-icons/hi';
|
|
6
|
+
import { arcTypeColor, classificationColor } from '../colors';
|
|
7
|
+
const LABEL_ROLE_ORDER = [
|
|
8
|
+
'standard',
|
|
9
|
+
'verbose',
|
|
10
|
+
'terse',
|
|
11
|
+
'documentation',
|
|
12
|
+
'periodStart',
|
|
13
|
+
'periodEnd',
|
|
14
|
+
'negated',
|
|
15
|
+
'total',
|
|
16
|
+
'commentaryGuidance',
|
|
17
|
+
'other',
|
|
18
|
+
];
|
|
19
|
+
export function ElementDetail({ client, graphId, elementId, onSelectElement, }) {
|
|
20
|
+
const [element, setElement] = useState(null);
|
|
21
|
+
const [arcs, setArcs] = useState([]);
|
|
22
|
+
const [classifications, setClassifications] = useState([]);
|
|
23
|
+
const [state, setState] = useState('idle');
|
|
24
|
+
const [error, setError] = useState(null);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!elementId) {
|
|
27
|
+
setElement(null);
|
|
28
|
+
setArcs([]);
|
|
29
|
+
setClassifications([]);
|
|
30
|
+
setError(null);
|
|
31
|
+
setState('idle');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
let cancelled = false;
|
|
35
|
+
setState('loading');
|
|
36
|
+
setError(null);
|
|
37
|
+
Promise.all([
|
|
38
|
+
client.getLibraryElement(graphId, { id: elementId }),
|
|
39
|
+
client.getLibraryElementArcs(graphId, elementId),
|
|
40
|
+
client.getLibraryElementClassifications(graphId, elementId),
|
|
41
|
+
])
|
|
42
|
+
.then(([el, arcRows, classRows]) => {
|
|
43
|
+
if (cancelled)
|
|
44
|
+
return;
|
|
45
|
+
setElement(el);
|
|
46
|
+
setArcs(arcRows);
|
|
47
|
+
setClassifications(classRows);
|
|
48
|
+
setState('ready');
|
|
49
|
+
})
|
|
50
|
+
.catch((err) => {
|
|
51
|
+
if (cancelled)
|
|
52
|
+
return;
|
|
53
|
+
setError(err instanceof Error ? err.message : 'Failed to load element');
|
|
54
|
+
setState('error');
|
|
55
|
+
});
|
|
56
|
+
return () => {
|
|
57
|
+
cancelled = true;
|
|
58
|
+
};
|
|
59
|
+
}, [client, graphId, elementId]);
|
|
60
|
+
const arcsByTaxonomy = useMemo(() => {
|
|
61
|
+
var _a, _b, _c, _d, _e, _f;
|
|
62
|
+
const groups = new Map();
|
|
63
|
+
for (const arc of arcs) {
|
|
64
|
+
const key = (_c = (_b = (_a = arc.taxonomyStandard) !== null && _a !== void 0 ? _a : arc.structureName) !== null && _b !== void 0 ? _b : arc.taxonomyName) !== null && _c !== void 0 ? _c : 'other';
|
|
65
|
+
const label = (_f = (_e = (_d = arc.taxonomyStandard) !== null && _d !== void 0 ? _d : arc.structureName) !== null && _e !== void 0 ? _e : arc.taxonomyName) !== null && _f !== void 0 ? _f : 'other';
|
|
66
|
+
if (!groups.has(key))
|
|
67
|
+
groups.set(key, { label, arcs: [] });
|
|
68
|
+
groups.get(key).arcs.push(arc);
|
|
69
|
+
}
|
|
70
|
+
return groups;
|
|
71
|
+
}, [arcs]);
|
|
72
|
+
const classificationsByCategory = useMemo(() => {
|
|
73
|
+
const groups = new Map();
|
|
74
|
+
for (const cls of classifications) {
|
|
75
|
+
if (!groups.has(cls.category))
|
|
76
|
+
groups.set(cls.category, []);
|
|
77
|
+
groups.get(cls.category).push(cls);
|
|
78
|
+
}
|
|
79
|
+
return groups;
|
|
80
|
+
}, [classifications]);
|
|
81
|
+
const sortedLabels = useMemo(() => {
|
|
82
|
+
if (!element)
|
|
83
|
+
return [];
|
|
84
|
+
return [...element.labels].sort((a, b) => {
|
|
85
|
+
const ia = LABEL_ROLE_ORDER.indexOf(a.role);
|
|
86
|
+
const ib = LABEL_ROLE_ORDER.indexOf(b.role);
|
|
87
|
+
return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
|
|
88
|
+
});
|
|
89
|
+
}, [element]);
|
|
90
|
+
return (_jsx("section", { className: "col-span-12 min-h-0 md:col-span-7", children: _jsxs(Card, { className: "flex h-full flex-col overflow-hidden", children: [state === 'idle' && (_jsx("div", { className: "flex h-full items-center justify-center text-sm text-gray-500 dark:text-gray-400", children: "Select an element to view details" })), state === 'loading' && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [_jsx(Spinner, { size: "sm" }), " Loading element\u2026"] })), state === 'error' && (_jsx(Alert, { color: "failure", icon: HiInformationCircle, children: error !== null && error !== void 0 ? error : 'Failed to load element' })), state === 'ready' && element && (_jsxs("div", { className: "min-h-0 flex-1 space-y-4 overflow-y-auto pr-1", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-mono text-lg font-semibold break-words text-gray-900 dark:text-white", title: element.qname, children: element.qname }), _jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: element.name })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [element.trait && (_jsx(Badge, { color: classificationColor(element.trait), title: "FASB elementsOfFinancialStatements", children: element.trait })), _jsx(Badge, { color: "gray", children: element.balanceType }), _jsx(Badge, { color: "gray", children: element.periodType }), _jsx(Badge, { color: "gray", children: element.elementType }), element.isAbstract && _jsx(Badge, { color: "purple", children: "abstract" }), element.isMonetary && _jsx(Badge, { color: "indigo", children: "monetary" }), _jsx(Badge, { color: "info", children: element.source })] }), classificationsByCategory.size > 0 && (_jsxs("div", { children: [_jsx("h3", { className: "font-heading mb-2 text-sm font-semibold text-gray-900 dark:text-white", children: "Classifications" }), _jsx("div", { className: "space-y-2", children: Array.from(classificationsByCategory.entries()).map(([category, items]) => (_jsxs("div", { children: [_jsx("div", { className: "mb-1 px-1 font-mono text-[11px] font-semibold tracking-wider text-gray-500 uppercase dark:text-gray-400", children: category }), _jsx("div", { className: "flex flex-wrap gap-1", children: items.map((cls) => (_jsx(Badge, { color: category === 'elementsOfFinancialStatements'
|
|
91
|
+
? classificationColor(cls.identifier)
|
|
92
|
+
: 'gray', size: "xs", children: cls.identifier }, cls.identifier))) })] }, category))) })] })), sortedLabels.length > 0 && (_jsxs("div", { children: [_jsx("h3", { className: "font-heading mb-2 text-sm font-semibold text-gray-900 dark:text-white", children: "Labels" }), _jsx("ul", { className: "space-y-1", children: sortedLabels.map((lab, i) => (_jsxs("li", { className: "rounded bg-gray-50 p-2 dark:bg-gray-800", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400", children: [_jsx("span", { className: "font-mono", children: lab.role }), _jsx("span", { children: "\u00B7" }), _jsx("span", { children: lab.language })] }), _jsx("p", { className: "text-sm text-gray-900 dark:text-gray-100", children: lab.text })] }, `${lab.role}-${lab.language}-${i}`))) })] })), element.references.length > 0 && (_jsxs("div", { children: [_jsx("h3", { className: "font-heading mb-2 text-sm font-semibold text-gray-900 dark:text-white", children: "References" }), _jsx("ul", { className: "space-y-1", children: element.references.map((ref, i) => (_jsxs("li", { className: "text-sm text-gray-700 dark:text-gray-300", children: [ref.refType && (_jsx(Badge, { color: "gray", size: "xs", className: "mr-2", children: ref.refType })), ref.citation, ref.uri && (_jsx("a", { href: ref.uri, target: "_blank", rel: "noopener noreferrer", className: "text-primary-600 dark:text-primary-400 ml-2 inline-flex items-center gap-1 hover:underline", children: _jsx(HiExternalLink, { className: "h-3 w-3" }) }))] }, i))) })] })), arcs.length > 0 && (_jsxs("div", { children: [_jsxs("h3", { className: "font-heading mb-1 text-sm font-semibold text-gray-900 dark:text-white", children: ["Mappings (", arcs.length, ")"] }), _jsx("p", { className: "mb-2 text-xs text-gray-500 dark:text-gray-400", children: "Cross-taxonomy arcs \u2014 equivalence, generalization, and type-subtype bridges. Click a peer to jump to it." }), _jsx("div", { className: "space-y-3", children: Array.from(arcsByTaxonomy.entries()).map(([key, { label, arcs: groupArcs }]) => (_jsxs("div", { children: [_jsxs("div", { className: "mb-1 flex items-center justify-between px-1", children: [_jsx("span", { className: "font-mono text-[11px] font-semibold tracking-wider text-gray-500 uppercase dark:text-gray-400", children: label }), _jsx(Badge, { color: "gray", size: "xs", children: groupArcs.length })] }), _jsx("ul", { className: "space-y-0.5", children: groupArcs.map((arc) => (_jsx("li", { children: _jsxs("button", { onClick: () => onSelectElement(arc.peer.id), className: "w-full rounded px-2 py-1 text-left transition-colors hover:bg-gray-100 dark:hover:bg-gray-800", title: `${arc.associationType} · ${arc.direction}`, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "shrink-0 text-xs text-gray-400 dark:text-gray-500", title: arc.direction === 'outgoing'
|
|
93
|
+
? 'this element → peer'
|
|
94
|
+
: 'peer → this element', children: arc.direction === 'outgoing' ? '→' : '←' }), _jsx("span", { className: "text-primary-700 dark:text-primary-300 truncate font-mono text-xs", children: arc.peer.qname }), _jsx(Badge, { color: arcTypeColor(arc.associationType), size: "xs", className: "ml-auto shrink-0", children: arc.associationType })] }), arc.peer.name &&
|
|
95
|
+
arc.peer.name !==
|
|
96
|
+
arc.peer.qname.split(':').pop() && (_jsx("div", { className: "truncate pl-5 text-xs text-gray-500 dark:text-gray-400", children: arc.peer.name }))] }) }, arc.id))) })] }, key))) })] }))] }))] }) }));
|
|
97
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LibraryClient, LibraryTaxonomy } from '@robosystems/client/clients';
|
|
2
|
+
export declare function LibraryHierarchy({ client, graphId, taxonomies, baseStandard, selectedTaxonomyId, selectedElementId, onSelectElement, }: {
|
|
3
|
+
client: LibraryClient;
|
|
4
|
+
graphId: string;
|
|
5
|
+
/** All taxonomies visible at this graph_id — used to resolve the arc-owning taxonomy. */
|
|
6
|
+
taxonomies: LibraryTaxonomy[];
|
|
7
|
+
/** Base reporting standard whose hierarchy to show (e.g. "rs-gaap", "fac"). */
|
|
8
|
+
baseStandard: string | null;
|
|
9
|
+
/** The selected taxonomy id — drives the Chart-of-Accounts parent_id tree. */
|
|
10
|
+
selectedTaxonomyId: string | null;
|
|
11
|
+
selectedElementId: string | null;
|
|
12
|
+
onSelectElement: (id: string) => void;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { clients } from '@robosystems/client/clients';
|
|
4
|
+
import { Alert, Badge, Card, Select, Spinner } from 'flowbite-react';
|
|
5
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
6
|
+
import { HiChevronDown, HiChevronRight, HiInformationCircle, } from 'react-icons/hi';
|
|
7
|
+
import { classificationColor } from '../colors';
|
|
8
|
+
/**
|
|
9
|
+
* Arc types the hierarchy view can walk, with the taxonomy-standard suffix
|
|
10
|
+
* that owns each. The calc DAG lives in ``{base}-calculations`` and the
|
|
11
|
+
* presentation networks in ``{base}-presentation`` — the arcs are NOT on the
|
|
12
|
+
* base reporting taxonomy, so we resolve the owning taxonomy by name. The
|
|
13
|
+
* general-special / type-subtype lattice is intentionally not exposed here:
|
|
14
|
+
* it's substrate (render fallback + classification), not a curated browse.
|
|
15
|
+
*/
|
|
16
|
+
const ARC_TYPES = [
|
|
17
|
+
{ value: 'presentation', label: 'Presentation', suffix: 'presentation' },
|
|
18
|
+
{ value: 'calculation', label: 'Calculation', suffix: 'calculations' },
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Keep only genuine reporting styles in the structure picker. The arc-owning
|
|
22
|
+
* taxonomy also carries two kinds of structure that aren't styles:
|
|
23
|
+
* • the empty seed-time catch-all (blockType 'custom', 0 arcs); and
|
|
24
|
+
* • the auto-derived base networks — the type-subtype lattice projected as
|
|
25
|
+
* presentation (role '…-pres-bs|is|cf'), an exhaustive, unordered substrate
|
|
26
|
+
* the curated styles are carved from, not a statement layout.
|
|
27
|
+
* Both are substrate, not something to scope a view to.
|
|
28
|
+
*/
|
|
29
|
+
function isReportingStyle(s) {
|
|
30
|
+
var _a;
|
|
31
|
+
if (s.blockType === 'custom')
|
|
32
|
+
return false;
|
|
33
|
+
return !/-pres-(bs|is|cf)$/i.test((_a = s.roleUri) !== null && _a !== void 0 ? _a : '');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Order reporting styles by financial-statement sequence (BS → IS → SE → CF)
|
|
37
|
+
* instead of the backend's name order. ``block_type`` encodes the statement;
|
|
38
|
+
* the sort is stable, so styles within one statement keep their seed order and
|
|
39
|
+
* non-statement structures (calc rules, disclosure tables) keep theirs.
|
|
40
|
+
*/
|
|
41
|
+
const STATEMENT_ORDER = {
|
|
42
|
+
balance_sheet: 0,
|
|
43
|
+
income_statement: 1,
|
|
44
|
+
equity_statement: 2,
|
|
45
|
+
cash_flow_statement: 3,
|
|
46
|
+
};
|
|
47
|
+
function byStatementOrder(a, b) {
|
|
48
|
+
var _a, _b;
|
|
49
|
+
return (((_a = STATEMENT_ORDER[a.blockType]) !== null && _a !== void 0 ? _a : 99) - ((_b = STATEMENT_ORDER[b.blockType]) !== null && _b !== void 0 ? _b : 99));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The structure to land on for a given arc type. Presentation opens on a
|
|
53
|
+
* coherent statement — the balance sheet — rather than "All structures",
|
|
54
|
+
* whose union blends every statement (and the base networks) into an
|
|
55
|
+
* incoherent multi-root tree. Calculation defaults to "All structures"
|
|
56
|
+
* (null): there the union IS the single coherent calc DAG.
|
|
57
|
+
*/
|
|
58
|
+
function defaultStructureId(arcType, structures) {
|
|
59
|
+
var _a, _b;
|
|
60
|
+
if (arcType !== 'presentation')
|
|
61
|
+
return null;
|
|
62
|
+
return (_b = (_a = structures.find((s) => s.blockType === 'balance_sheet')) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
|
|
63
|
+
}
|
|
64
|
+
const INITIAL_EXPAND_DEPTH = 2;
|
|
65
|
+
const FETCH_PAGE = 1000;
|
|
66
|
+
/** Fetch every arc for a taxonomy+type(+structure), paging past the cap. */
|
|
67
|
+
async function fetchAllArcs(client, graphId, taxonomyId, associationType, structureId) {
|
|
68
|
+
const all = [];
|
|
69
|
+
let offset = 0;
|
|
70
|
+
// Guard against a runaway loop if count is ever inconsistent.
|
|
71
|
+
for (let page = 0; page < 50; page++) {
|
|
72
|
+
const { arcs, count } = await client.listLibraryTaxonomyArcs(graphId, taxonomyId, {
|
|
73
|
+
associationType,
|
|
74
|
+
structureId: structureId !== null && structureId !== void 0 ? structureId : undefined,
|
|
75
|
+
limit: FETCH_PAGE,
|
|
76
|
+
offset,
|
|
77
|
+
});
|
|
78
|
+
all.push(...arcs);
|
|
79
|
+
if (arcs.length === 0 || all.length >= count)
|
|
80
|
+
break;
|
|
81
|
+
offset += FETCH_PAGE;
|
|
82
|
+
}
|
|
83
|
+
return all;
|
|
84
|
+
}
|
|
85
|
+
/** Assemble a forest from a flat arc list (roots = a `from` that's never a `to`). */
|
|
86
|
+
function buildForest(arcs) {
|
|
87
|
+
var _a, _b, _c;
|
|
88
|
+
const meta = new Map();
|
|
89
|
+
const childEdges = new Map();
|
|
90
|
+
const froms = new Set();
|
|
91
|
+
const tos = new Set();
|
|
92
|
+
for (const a of arcs) {
|
|
93
|
+
if (!a.fromElementId || !a.toElementId)
|
|
94
|
+
continue;
|
|
95
|
+
meta.set(a.fromElementId, {
|
|
96
|
+
qname: a.fromElementQname,
|
|
97
|
+
name: a.fromElementName,
|
|
98
|
+
trait: a.fromElementTrait,
|
|
99
|
+
isAbstract: (_a = a.fromElementIsAbstract) !== null && _a !== void 0 ? _a : false,
|
|
100
|
+
});
|
|
101
|
+
meta.set(a.toElementId, {
|
|
102
|
+
qname: a.toElementQname,
|
|
103
|
+
name: a.toElementName,
|
|
104
|
+
trait: a.toElementTrait,
|
|
105
|
+
isAbstract: (_b = a.toElementIsAbstract) !== null && _b !== void 0 ? _b : false,
|
|
106
|
+
});
|
|
107
|
+
froms.add(a.fromElementId);
|
|
108
|
+
tos.add(a.toElementId);
|
|
109
|
+
const edges = (_c = childEdges.get(a.fromElementId)) !== null && _c !== void 0 ? _c : [];
|
|
110
|
+
// Dedupe by target — the same parent→child arc recurs across structures
|
|
111
|
+
// (the "All structures" blend), which would otherwise render duplicate
|
|
112
|
+
// sibling nodes (and collide on React keys).
|
|
113
|
+
if (!edges.some((e) => e.childId === a.toElementId)) {
|
|
114
|
+
edges.push({
|
|
115
|
+
childId: a.toElementId,
|
|
116
|
+
weight: a.weight,
|
|
117
|
+
order: a.orderValue,
|
|
118
|
+
});
|
|
119
|
+
childEdges.set(a.fromElementId, edges);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const node = (id, weight, order, seen) => {
|
|
123
|
+
var _a, _b, _c, _d, _e;
|
|
124
|
+
const m = meta.get(id);
|
|
125
|
+
const self = {
|
|
126
|
+
id,
|
|
127
|
+
qname: (_a = m === null || m === void 0 ? void 0 : m.qname) !== null && _a !== void 0 ? _a : null,
|
|
128
|
+
name: (_b = m === null || m === void 0 ? void 0 : m.name) !== null && _b !== void 0 ? _b : null,
|
|
129
|
+
trait: (_c = m === null || m === void 0 ? void 0 : m.trait) !== null && _c !== void 0 ? _c : null,
|
|
130
|
+
isAbstract: (_d = m === null || m === void 0 ? void 0 : m.isAbstract) !== null && _d !== void 0 ? _d : false,
|
|
131
|
+
weight,
|
|
132
|
+
order,
|
|
133
|
+
children: [],
|
|
134
|
+
};
|
|
135
|
+
if (seen.has(id))
|
|
136
|
+
return self; // cycle guard
|
|
137
|
+
const next = new Set(seen);
|
|
138
|
+
next.add(id);
|
|
139
|
+
const edges = [...((_e = childEdges.get(id)) !== null && _e !== void 0 ? _e : [])].sort((a, b) => { var _a, _b; return ((_a = a.order) !== null && _a !== void 0 ? _a : 0) - ((_b = b.order) !== null && _b !== void 0 ? _b : 0); });
|
|
140
|
+
self.children = edges.map((e) => node(e.childId, e.weight, e.order, next));
|
|
141
|
+
return self;
|
|
142
|
+
};
|
|
143
|
+
const roots = [...froms]
|
|
144
|
+
.filter((id) => !tos.has(id))
|
|
145
|
+
.sort((a, b) => { var _a, _b, _c, _d; return ((_b = (_a = meta.get(a)) === null || _a === void 0 ? void 0 : _a.qname) !== null && _b !== void 0 ? _b : '').localeCompare((_d = (_c = meta.get(b)) === null || _c === void 0 ? void 0 : _c.qname) !== null && _d !== void 0 ? _d : ''); });
|
|
146
|
+
return roots.map((id) => node(id, null, null, new Set()));
|
|
147
|
+
}
|
|
148
|
+
// QB's standard CoA ordering by AccountType — kept in lockstep with the Chart
|
|
149
|
+
// of Accounts page (ledger/chart-of-accounts). Type-first (not code-first)
|
|
150
|
+
// because QB returns code == account name when account-numbering is off (the
|
|
151
|
+
// sandbox default), which would otherwise degrade to alphabetic-by-name and
|
|
152
|
+
// lose the asset → liability → equity → income → expense grouping.
|
|
153
|
+
const ACCOUNT_TYPE_ORDER = {
|
|
154
|
+
Bank: 0,
|
|
155
|
+
'Accounts Receivable': 1,
|
|
156
|
+
'Other Current Asset': 2,
|
|
157
|
+
'Fixed Asset': 3,
|
|
158
|
+
'Other Asset': 4,
|
|
159
|
+
'Accounts Payable': 5,
|
|
160
|
+
'Credit Card': 6,
|
|
161
|
+
'Other Current Liability': 7,
|
|
162
|
+
'Long Term Liability': 8,
|
|
163
|
+
Equity: 9,
|
|
164
|
+
Income: 10,
|
|
165
|
+
'Cost of Goods Sold': 11,
|
|
166
|
+
Expense: 12,
|
|
167
|
+
'Other Income': 13,
|
|
168
|
+
'Other Expense': 14,
|
|
169
|
+
};
|
|
170
|
+
function compareAccountNodes(a, b) {
|
|
171
|
+
var _a, _b, _c, _d, _e, _f;
|
|
172
|
+
const ta = (_a = ACCOUNT_TYPE_ORDER[a.accountType || '']) !== null && _a !== void 0 ? _a : 99;
|
|
173
|
+
const tb = (_b = ACCOUNT_TYPE_ORDER[b.accountType || '']) !== null && _b !== void 0 ? _b : 99;
|
|
174
|
+
if (ta !== tb)
|
|
175
|
+
return ta - tb;
|
|
176
|
+
const ca = (_c = a.code) !== null && _c !== void 0 ? _c : '';
|
|
177
|
+
const cb = (_d = b.code) !== null && _d !== void 0 ? _d : '';
|
|
178
|
+
if (ca !== cb)
|
|
179
|
+
return ca.localeCompare(cb, undefined, { numeric: true });
|
|
180
|
+
return ((_e = a.name) !== null && _e !== void 0 ? _e : '').localeCompare((_f = b.name) !== null && _f !== void 0 ? _f : '');
|
|
181
|
+
}
|
|
182
|
+
function mapAccountNode(n) {
|
|
183
|
+
var _a, _b, _c, _d;
|
|
184
|
+
return {
|
|
185
|
+
id: n.id,
|
|
186
|
+
qname: (_a = n.code) !== null && _a !== void 0 ? _a : null,
|
|
187
|
+
name: (_b = n.name) !== null && _b !== void 0 ? _b : null,
|
|
188
|
+
trait: (_c = n.trait) !== null && _c !== void 0 ? _c : null,
|
|
189
|
+
isAbstract: false,
|
|
190
|
+
weight: null,
|
|
191
|
+
order: null,
|
|
192
|
+
children: [...((_d = n.children) !== null && _d !== void 0 ? _d : [])]
|
|
193
|
+
.sort(compareAccountNodes)
|
|
194
|
+
.map(mapAccountNode),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/** Node ids deeper than INITIAL_EXPAND_DEPTH — collapsed on first render. */
|
|
198
|
+
function deepNodeIds(forest) {
|
|
199
|
+
const out = new Set();
|
|
200
|
+
const walk = (n, depth) => {
|
|
201
|
+
if (depth >= INITIAL_EXPAND_DEPTH && n.children.length > 0)
|
|
202
|
+
out.add(n.id);
|
|
203
|
+
n.children.forEach((c) => walk(c, depth + 1));
|
|
204
|
+
};
|
|
205
|
+
forest.forEach((r) => walk(r, 0));
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
export function LibraryHierarchy({ client, graphId, taxonomies, baseStandard, selectedTaxonomyId, selectedElementId, onSelectElement, }) {
|
|
209
|
+
var _a;
|
|
210
|
+
const [arcType, setArcType] = useState('presentation');
|
|
211
|
+
const [structures, setStructures] = useState([]);
|
|
212
|
+
const [structureId, setStructureId] = useState(null);
|
|
213
|
+
const [arcs, setArcs] = useState([]);
|
|
214
|
+
const [state, setState] = useState('idle');
|
|
215
|
+
const [error, setError] = useState(null);
|
|
216
|
+
const [collapsed, setCollapsed] = useState(new Set());
|
|
217
|
+
// The taxonomy id the current `structures` + `structureId` belong to. Gates
|
|
218
|
+
// arc-loading until the structure list (and its default selection) catches up
|
|
219
|
+
// with a new arc type, so we never fetch the "All structures" union as an
|
|
220
|
+
// intermediate state when entering Presentation.
|
|
221
|
+
const [structuresTaxonomyId, setStructuresTaxonomyId] = useState(null);
|
|
222
|
+
const [coaForest, setCoaForest] = useState([]);
|
|
223
|
+
// A Chart of Accounts has no presentation/calc arcs — its hierarchy is the
|
|
224
|
+
// account parent/sub-account tree carried on each element's parentId. Detect
|
|
225
|
+
// it from the selected taxonomy's type and render that tree instead.
|
|
226
|
+
const isCoa = useMemo(() => {
|
|
227
|
+
var _a;
|
|
228
|
+
return ((_a = taxonomies.find((t) => t.id === selectedTaxonomyId)) === null || _a === void 0 ? void 0 : _a.taxonomyType) ===
|
|
229
|
+
'chart_of_accounts';
|
|
230
|
+
}, [taxonomies, selectedTaxonomyId]);
|
|
231
|
+
// Resolve the taxonomy that owns this arc type for the chosen base standard.
|
|
232
|
+
const arcTaxonomy = useMemo(() => {
|
|
233
|
+
var _a, _b;
|
|
234
|
+
if (!baseStandard)
|
|
235
|
+
return null;
|
|
236
|
+
const suffix = (_a = ARC_TYPES.find((t) => t.value === arcType)) === null || _a === void 0 ? void 0 : _a.suffix;
|
|
237
|
+
return ((_b = taxonomies.find((t) => t.standard === `${baseStandard}-${suffix}`)) !== null && _b !== void 0 ? _b : null);
|
|
238
|
+
}, [taxonomies, baseStandard, arcType]);
|
|
239
|
+
// Load the structure list for the picker whenever the owning taxonomy
|
|
240
|
+
// changes, then select the per-arc-type default structure.
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
if (!arcTaxonomy) {
|
|
243
|
+
setStructures([]);
|
|
244
|
+
setStructureId(null);
|
|
245
|
+
setStructuresTaxonomyId(null);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
let cancelled = false;
|
|
249
|
+
client
|
|
250
|
+
.listLibraryStructures(graphId, { taxonomyId: arcTaxonomy.id })
|
|
251
|
+
.then((rows) => {
|
|
252
|
+
if (cancelled)
|
|
253
|
+
return;
|
|
254
|
+
const filtered = rows.filter(isReportingStyle).sort(byStatementOrder);
|
|
255
|
+
setStructures(filtered);
|
|
256
|
+
setStructureId(defaultStructureId(arcType, filtered));
|
|
257
|
+
setStructuresTaxonomyId(arcTaxonomy.id);
|
|
258
|
+
})
|
|
259
|
+
.catch((err) => {
|
|
260
|
+
// Non-fatal: the tree still loads with "All structures"; the picker
|
|
261
|
+
// just won't appear. Log so the absence is diagnosable.
|
|
262
|
+
if (!cancelled) {
|
|
263
|
+
console.error('[LibraryHierarchy] failed to load structures', err);
|
|
264
|
+
setStructures([]);
|
|
265
|
+
setStructureId(null);
|
|
266
|
+
setStructuresTaxonomyId(arcTaxonomy.id);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
return () => {
|
|
270
|
+
cancelled = true;
|
|
271
|
+
};
|
|
272
|
+
}, [client, graphId, arcTaxonomy, arcType]);
|
|
273
|
+
// Load arcs for the selected taxonomy + arc type (+ optional structure scope).
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
if (isCoa)
|
|
276
|
+
return; // CoA uses the parentId tree below, not arcs.
|
|
277
|
+
if (!arcTaxonomy) {
|
|
278
|
+
setArcs([]);
|
|
279
|
+
setState('ready');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// Wait until the structure list (and its default selection) matches this
|
|
283
|
+
// taxonomy — otherwise switching arc types would briefly fetch with a
|
|
284
|
+
// stale / "All structures" scope before the default lands.
|
|
285
|
+
if (structuresTaxonomyId !== arcTaxonomy.id) {
|
|
286
|
+
setState('loading');
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
let cancelled = false;
|
|
290
|
+
setState('loading');
|
|
291
|
+
setError(null);
|
|
292
|
+
fetchAllArcs(client, graphId, arcTaxonomy.id, arcType, structureId)
|
|
293
|
+
.then((rows) => {
|
|
294
|
+
if (cancelled)
|
|
295
|
+
return;
|
|
296
|
+
setArcs(rows);
|
|
297
|
+
setState('ready');
|
|
298
|
+
})
|
|
299
|
+
.catch((err) => {
|
|
300
|
+
if (cancelled)
|
|
301
|
+
return;
|
|
302
|
+
setError(err instanceof Error ? err.message : 'Failed to load hierarchy');
|
|
303
|
+
setState('error');
|
|
304
|
+
});
|
|
305
|
+
return () => {
|
|
306
|
+
cancelled = true;
|
|
307
|
+
};
|
|
308
|
+
}, [
|
|
309
|
+
client,
|
|
310
|
+
graphId,
|
|
311
|
+
arcTaxonomy,
|
|
312
|
+
arcType,
|
|
313
|
+
structureId,
|
|
314
|
+
structuresTaxonomyId,
|
|
315
|
+
isCoa,
|
|
316
|
+
]);
|
|
317
|
+
// Load the Chart of Accounts tree — the backend returns it built and
|
|
318
|
+
// active-only (same source as the Chart of Accounts page); we re-order it by
|
|
319
|
+
// AccountType so the layout matches that page rather than the backend's code
|
|
320
|
+
// order (which degrades to alphabetic-by-name when account numbering is off).
|
|
321
|
+
useEffect(() => {
|
|
322
|
+
if (!isCoa) {
|
|
323
|
+
setCoaForest([]);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
let cancelled = false;
|
|
327
|
+
setState('loading');
|
|
328
|
+
setError(null);
|
|
329
|
+
clients.ledger
|
|
330
|
+
.getAccountTree(graphId)
|
|
331
|
+
.then((tree) => {
|
|
332
|
+
var _a;
|
|
333
|
+
if (cancelled)
|
|
334
|
+
return;
|
|
335
|
+
const roots = ((_a = tree === null || tree === void 0 ? void 0 : tree.roots) !== null && _a !== void 0 ? _a : []);
|
|
336
|
+
setCoaForest([...roots].sort(compareAccountNodes).map(mapAccountNode));
|
|
337
|
+
setState('ready');
|
|
338
|
+
})
|
|
339
|
+
.catch((err) => {
|
|
340
|
+
if (cancelled)
|
|
341
|
+
return;
|
|
342
|
+
setError(err instanceof Error ? err.message : 'Failed to load accounts');
|
|
343
|
+
setState('error');
|
|
344
|
+
});
|
|
345
|
+
return () => {
|
|
346
|
+
cancelled = true;
|
|
347
|
+
};
|
|
348
|
+
}, [isCoa, graphId]);
|
|
349
|
+
const forest = useMemo(() => (isCoa ? coaForest : buildForest(arcs)), [isCoa, coaForest, arcs]);
|
|
350
|
+
// Reset the collapse state to the default expand depth on each new forest.
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
setCollapsed(deepNodeIds(forest));
|
|
353
|
+
}, [forest]);
|
|
354
|
+
const toggle = (id) => setCollapsed((prev) => {
|
|
355
|
+
const next = new Set(prev);
|
|
356
|
+
if (next.has(id))
|
|
357
|
+
next.delete(id);
|
|
358
|
+
else
|
|
359
|
+
next.add(id);
|
|
360
|
+
return next;
|
|
361
|
+
});
|
|
362
|
+
const showWeights = !isCoa && arcType === 'calculation';
|
|
363
|
+
return (_jsx("section", { className: "col-span-12 min-h-0 md:col-span-5", children: _jsxs(Card, { className: "flex h-full flex-col overflow-hidden", children: [_jsxs("div", { className: "flex shrink-0 flex-wrap items-center justify-between gap-2", children: [_jsx("h2", { className: "font-heading text-lg font-semibold text-gray-900 dark:text-white", children: "Hierarchy" }), !isCoa && (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("div", { className: "flex overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700", role: "group", "aria-label": "Arc type", children: ARC_TYPES.map((t) => (_jsx("button", { onClick: () => setArcType(t.value), "aria-pressed": arcType === t.value, className: `px-3 py-1 text-xs font-medium transition-colors ${arcType === t.value
|
|
364
|
+
? 'bg-primary-600 text-white'
|
|
365
|
+
: 'bg-white text-gray-600 hover:bg-gray-100 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700'}`, children: t.label }, t.value))) }), structures.length > 0 && (_jsxs(Select, { sizing: "sm", value: structureId !== null && structureId !== void 0 ? structureId : '', onChange: (e) => setStructureId(e.target.value || null), "aria-label": "Structure", children: [_jsx("option", { value: "", children: "All structures" }), structures.map((s) => (_jsx("option", { value: s.id, children: s.name }, s.id)))] }))] }))] }), state === 'loading' && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400", children: [_jsx(Spinner, { size: "sm" }), " Loading hierarchy\u2026"] })), state === 'error' && (_jsx(Alert, { color: "failure", icon: HiInformationCircle, children: error })), state === 'ready' && !isCoa && !arcTaxonomy && (_jsxs(Alert, { color: "info", icon: HiInformationCircle, children: ["No ", (_a = ARC_TYPES.find((t) => t.value === arcType)) === null || _a === void 0 ? void 0 : _a.label.toLowerCase(), ' ', "hierarchy is published for", ' ', _jsx("span", { className: "font-mono", children: baseStandard !== null && baseStandard !== void 0 ? baseStandard : 'this taxonomy' }), "."] })), state === 'ready' && !isCoa && arcTaxonomy && forest.length === 0 && (_jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: "No arcs in this hierarchy." })), state === 'ready' && isCoa && forest.length === 0 && (_jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: "No accounts in this chart of accounts." })), state === 'ready' && forest.length > 0 && (_jsx("div", { role: "tree", "aria-label": "Taxonomy hierarchy", className: "min-h-0 flex-1 overflow-auto pr-1 font-mono text-sm", children: forest.map((root) => (_jsx(HierarchyRow, { path: root.id, node: root, depth: 0, showWeights: showWeights, collapsed: collapsed, onToggle: toggle, selectedElementId: selectedElementId, onSelectElement: onSelectElement }, root.id))) }))] }) }));
|
|
366
|
+
}
|
|
367
|
+
function HierarchyRow({ node, path, depth, showWeights, collapsed, onToggle, selectedElementId, onSelectElement, }) {
|
|
368
|
+
var _a, _b, _c;
|
|
369
|
+
const hasChildren = node.children.length > 0;
|
|
370
|
+
const isCollapsed = collapsed.has(node.id);
|
|
371
|
+
const isSelected = node.id === selectedElementId;
|
|
372
|
+
const label = (_b = (_a = node.name) !== null && _a !== void 0 ? _a : node.qname) !== null && _b !== void 0 ? _b : node.id;
|
|
373
|
+
return (_jsxs("div", { role: "treeitem", "aria-level": depth + 1, "aria-selected": isSelected, "aria-expanded": hasChildren ? !isCollapsed : undefined, children: [_jsxs("div", { className: `flex items-center gap-1 rounded px-1 py-0.5 ${isSelected
|
|
374
|
+
? 'bg-primary-100 dark:bg-primary-900/50'
|
|
375
|
+
: 'hover:bg-gray-100 dark:hover:bg-gray-800'}`, style: { paddingLeft: `${depth * 16}px` }, children: [hasChildren ? (_jsx("button", { onClick: () => onToggle(node.id), className: "shrink-0 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200", "aria-label": isCollapsed ? `Expand ${label}` : `Collapse ${label}`, children: isCollapsed ? (_jsx(HiChevronRight, { className: "h-4 w-4" })) : (_jsx(HiChevronDown, { className: "h-4 w-4" })) })) : (_jsx("span", { className: "inline-block w-4 shrink-0" })), showWeights && node.weight != null && (_jsx("span", { className: `w-3 shrink-0 text-center font-bold ${node.weight >= 0
|
|
376
|
+
? 'text-green-600 dark:text-green-400'
|
|
377
|
+
: 'text-red-600 dark:text-red-400'}`, title: `calc weight ${node.weight}`, children: node.weight >= 0 ? '+' : '−' })), _jsxs("button", { onClick: () => onSelectElement(node.id), className: "flex min-w-0 flex-1 items-center gap-2 text-left", title: (_c = node.qname) !== null && _c !== void 0 ? _c : undefined, children: [_jsx("span", { className: `truncate ${isSelected
|
|
378
|
+
? 'text-primary-900 dark:text-primary-100'
|
|
379
|
+
: 'text-gray-800 dark:text-gray-100'}`, children: label }), node.isAbstract ? (_jsx(Badge, { color: "purple", size: "xs", className: "shrink-0", children: "abstract" })) : node.trait ? (_jsx(Badge, { color: classificationColor(node.trait), size: "xs", className: "shrink-0", children: node.trait })) : null] })] }), hasChildren && !isCollapsed && (_jsx("div", { role: "group", children: node.children.map((c) => (_jsx(HierarchyRow, { path: `${path}/${c.id}`, node: c, depth: depth + 1, showWeights: showWeights, collapsed: collapsed, onToggle: onToggle, selectedElementId: selectedElementId, onSelectElement: onSelectElement }, `${path}/${c.id}`))) }))] }));
|
|
380
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { LibraryTaxonomy } from '@robosystems/client/clients';
|
|
2
|
+
export declare function TaxonomySidebar({ taxonomies, selectedId, onSelect, }: {
|
|
3
|
+
taxonomies: LibraryTaxonomy[];
|
|
4
|
+
selectedId: string | null;
|
|
5
|
+
onSelect: (id: string) => void;
|
|
6
|
+
}): import("react").JSX.Element;
|