@ngbplatform/ui 1.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 +201 -0
- package/README.md +44 -0
- package/package.json +61 -0
- package/public/favicon.svg +6 -0
- package/public/silent-check-sso.html +8 -0
- package/src/assets/ngb_logo.svg +17 -0
- package/src/assets/ngb_logo_color.svg +17 -0
- package/src/index.ts +1023 -0
- package/src/ngb/README.md +27 -0
- package/src/ngb/accounting/NgbAccountingPeriodClosingPage.vue +1157 -0
- package/src/ngb/accounting/NgbChartOfAccountEditor.vue +590 -0
- package/src/ngb/accounting/NgbChartOfAccountsPage.vue +420 -0
- package/src/ngb/accounting/NgbGeneralJournalEntryEditPage.vue +796 -0
- package/src/ngb/accounting/NgbGeneralJournalEntryLinesEditor.vue +454 -0
- package/src/ngb/accounting/NgbGeneralJournalEntryListPage.vue +186 -0
- package/src/ngb/accounting/api.ts +79 -0
- package/src/ngb/accounting/generalJournalEntry.ts +127 -0
- package/src/ngb/accounting/generalJournalEntryApi.ts +112 -0
- package/src/ngb/accounting/generalJournalEntryTypes.ts +155 -0
- package/src/ngb/accounting/navigation.ts +83 -0
- package/src/ngb/accounting/periodClosing.ts +75 -0
- package/src/ngb/accounting/periodClosingApi.ts +60 -0
- package/src/ngb/accounting/periodClosingTypes.ts +73 -0
- package/src/ngb/accounting/types.ts +57 -0
- package/src/ngb/api/audit.ts +23 -0
- package/src/ngb/api/catalogs.ts +50 -0
- package/src/ngb/api/contracts.ts +285 -0
- package/src/ngb/api/documents.ts +170 -0
- package/src/ngb/api/http.ts +446 -0
- package/src/ngb/api/lookups.ts +14 -0
- package/src/ngb/api/types.ts +6 -0
- package/src/ngb/auth/index.ts +13 -0
- package/src/ngb/auth/keycloak.ts +374 -0
- package/src/ngb/auth/router.ts +35 -0
- package/src/ngb/auth/types.ts +12 -0
- package/src/ngb/auth/useAuthStore.ts +209 -0
- package/src/ngb/command-palette/NgbCommandPaletteDialog.vue +342 -0
- package/src/ngb/command-palette/NgbCommandPaletteEmptyState.vue +55 -0
- package/src/ngb/command-palette/NgbCommandPaletteFooterHints.vue +29 -0
- package/src/ngb/command-palette/api.ts +10 -0
- package/src/ngb/command-palette/config.ts +16 -0
- package/src/ngb/command-palette/presentation.ts +85 -0
- package/src/ngb/command-palette/search.ts +130 -0
- package/src/ngb/command-palette/staticItems.ts +243 -0
- package/src/ngb/command-palette/storage.ts +45 -0
- package/src/ngb/command-palette/store.ts +716 -0
- package/src/ngb/command-palette/types.ts +120 -0
- package/src/ngb/command-palette/useCommandPaletteHotkeys.ts +27 -0
- package/src/ngb/command-palette/useCommandPalettePageContext.ts +18 -0
- package/src/ngb/components/NgbConfirmDialog.vue +63 -0
- package/src/ngb/components/NgbDialog.vue +44 -0
- package/src/ngb/components/NgbDrawer.vue +156 -0
- package/src/ngb/components/NgbHeaderActionCluster.vue +140 -0
- package/src/ngb/components/NgbModalShell.vue +99 -0
- package/src/ngb/components/NgbToolbar.vue +14 -0
- package/src/ngb/components/forms/NgbFormLayout.vue +13 -0
- package/src/ngb/components/forms/NgbFormRow.vue +21 -0
- package/src/ngb/components/forms/NgbFormSection.vue +16 -0
- package/src/ngb/components/forms/NgbValidationSummary.vue +18 -0
- package/src/ngb/components/navigation/NgbNavigationTree.vue +68 -0
- package/src/ngb/components/navigation/NgbTreeNode.vue +94 -0
- package/src/ngb/components/register/NgbColumnChooser.vue +101 -0
- package/src/ngb/components/register/NgbRegisterGrid.vue +473 -0
- package/src/ngb/components/register/registerTypes.ts +51 -0
- package/src/ngb/components/register/useRegisterColumnResize.ts +99 -0
- package/src/ngb/components/register/useRegisterColumnState.ts +220 -0
- package/src/ngb/components/register/useRegisterRows.ts +209 -0
- package/src/ngb/components/register/useRegisterViewport.ts +56 -0
- package/src/ngb/editor/NgbDocumentEffectsPage.vue +512 -0
- package/src/ngb/editor/NgbDocumentFlowPage.vue +409 -0
- package/src/ngb/editor/NgbDocumentPrintPage.vue +712 -0
- package/src/ngb/editor/NgbEditorDiscardDialog.vue +40 -0
- package/src/ngb/editor/NgbEntityAuditSidebar.vue +288 -0
- package/src/ngb/editor/NgbEntityEditor.vue +250 -0
- package/src/ngb/editor/NgbEntityEditorDrawerActions.vue +88 -0
- package/src/ngb/editor/NgbEntityEditorHeader.vue +114 -0
- package/src/ngb/editor/catalogNavigation.ts +13 -0
- package/src/ngb/editor/config.ts +252 -0
- package/src/ngb/editor/documentCopyDraft.ts +245 -0
- package/src/ngb/editor/documentEffects.ts +43 -0
- package/src/ngb/editor/documentEffectsDateFormatting.ts +18 -0
- package/src/ngb/editor/documentNavigation.ts +115 -0
- package/src/ngb/editor/documentStatus.ts +45 -0
- package/src/ngb/editor/entityEditorErrors.ts +162 -0
- package/src/ngb/editor/entityEditorPersistence.ts +241 -0
- package/src/ngb/editor/extensions.ts +40 -0
- package/src/ngb/editor/types.ts +295 -0
- package/src/ngb/editor/useConfiguredEntityEditorDocumentActions.ts +183 -0
- package/src/ngb/editor/useDocumentEditorDrawerState.ts +152 -0
- package/src/ngb/editor/useEditorDrawerState.ts +88 -0
- package/src/ngb/editor/useEntityEditorBusinessContext.ts +97 -0
- package/src/ngb/editor/useEntityEditorCapabilities.ts +190 -0
- package/src/ngb/editor/useEntityEditorCommandPalette.ts +154 -0
- package/src/ngb/editor/useEntityEditorCommitHandlers.ts +102 -0
- package/src/ngb/editor/useEntityEditorHeaderActions.ts +253 -0
- package/src/ngb/editor/useEntityEditorLeaveGuard.ts +66 -0
- package/src/ngb/editor/useEntityEditorNavigationActions.ts +235 -0
- package/src/ngb/editor/useEntityEditorOutputs.ts +59 -0
- package/src/ngb/editor/useEntityEditorPageActions.ts +57 -0
- package/src/ngb/editor/useRouteQueryEditorDrawer.ts +173 -0
- package/src/ngb/env/runtimeConfig.ts +25 -0
- package/src/ngb/lookup/config.ts +42 -0
- package/src/ngb/lookup/defaultConfig.ts +136 -0
- package/src/ngb/lookup/navigation.ts +76 -0
- package/src/ngb/lookup/prefetch.ts +40 -0
- package/src/ngb/lookup/store.ts +261 -0
- package/src/ngb/metadata/NgbDocumentListFiltersDrawer.vue +78 -0
- package/src/ngb/metadata/NgbDocumentPeriodFilter.vue +53 -0
- package/src/ngb/metadata/NgbEntityForm.vue +84 -0
- package/src/ngb/metadata/NgbEntityFormFieldsBlock.vue +110 -0
- package/src/ngb/metadata/NgbEntityListPageHeader.vue +80 -0
- package/src/ngb/metadata/NgbFilterFieldControl.vue +104 -0
- package/src/ngb/metadata/NgbMetadataCatalogEditPage.vue +42 -0
- package/src/ngb/metadata/NgbMetadataCatalogListPage.vue +269 -0
- package/src/ngb/metadata/NgbMetadataDocumentEditPage.vue +68 -0
- package/src/ngb/metadata/NgbMetadataDocumentListPage.vue +353 -0
- package/src/ngb/metadata/NgbMetadataFieldRenderer.vue +124 -0
- package/src/ngb/metadata/NgbMetadataLookupControl.vue +92 -0
- package/src/ngb/metadata/NgbRecycleBinFilter.vue +53 -0
- package/src/ngb/metadata/NgbRegisterPageLayout.vue +143 -0
- package/src/ngb/metadata/config.ts +28 -0
- package/src/ngb/metadata/dataTypes.ts +27 -0
- package/src/ngb/metadata/entityForm.ts +114 -0
- package/src/ngb/metadata/entityModel.ts +34 -0
- package/src/ngb/metadata/entityValueFormatting.ts +83 -0
- package/src/ngb/metadata/fieldRendererState.ts +84 -0
- package/src/ngb/metadata/filtering.ts +153 -0
- package/src/ngb/metadata/lookup.ts +25 -0
- package/src/ngb/metadata/monthPagedListQuery.ts +108 -0
- package/src/ngb/metadata/normalization.ts +100 -0
- package/src/ngb/metadata/referenceHydration.ts +99 -0
- package/src/ngb/metadata/register.ts +135 -0
- package/src/ngb/metadata/routePages.ts +86 -0
- package/src/ngb/metadata/store.ts +35 -0
- package/src/ngb/metadata/types.ts +276 -0
- package/src/ngb/metadata/useMetadataListFilters.ts +306 -0
- package/src/ngb/metadata/useMetadataRegisterPageData.ts +246 -0
- package/src/ngb/metadata/useValidationFocus.ts +49 -0
- package/src/ngb/primitives/NgbBadge.vue +31 -0
- package/src/ngb/primitives/NgbButton.vue +53 -0
- package/src/ngb/primitives/NgbCheckbox.vue +33 -0
- package/src/ngb/primitives/NgbDatePicker.vue +141 -0
- package/src/ngb/primitives/NgbIcon.vue +545 -0
- package/src/ngb/primitives/NgbInput.vue +57 -0
- package/src/ngb/primitives/NgbLookup.vue +264 -0
- package/src/ngb/primitives/NgbMonthPicker.vue +132 -0
- package/src/ngb/primitives/NgbMultiSelect.vue +179 -0
- package/src/ngb/primitives/NgbPeriodPicker.vue +125 -0
- package/src/ngb/primitives/NgbPickerNavButton.vue +31 -0
- package/src/ngb/primitives/NgbPickerPopover.vue +79 -0
- package/src/ngb/primitives/NgbRadioGroup.vue +53 -0
- package/src/ngb/primitives/NgbSelect.vue +108 -0
- package/src/ngb/primitives/NgbStatusIcon.vue +63 -0
- package/src/ngb/primitives/NgbSwitch.vue +42 -0
- package/src/ngb/primitives/NgbTabs.vue +116 -0
- package/src/ngb/primitives/NgbToastHost.vue +49 -0
- package/src/ngb/primitives/NgbTooltip.vue +27 -0
- package/src/ngb/primitives/iconNames.ts +83 -0
- package/src/ngb/primitives/toast.ts +66 -0
- package/src/ngb/primitives/useAsyncComboboxQuery.ts +85 -0
- package/src/ngb/primitives/useFloatingDropdownPosition.ts +129 -0
- package/src/ngb/reporting/NgbReportComposerCollectionSection.vue +106 -0
- package/src/ngb/reporting/NgbReportComposerPanel.vue +1187 -0
- package/src/ngb/reporting/NgbReportDateRangeFilter.vue +54 -0
- package/src/ngb/reporting/NgbReportPage.vue +1124 -0
- package/src/ngb/reporting/NgbReportSheet.vue +467 -0
- package/src/ngb/reporting/api.ts +35 -0
- package/src/ngb/reporting/composer.ts +904 -0
- package/src/ngb/reporting/config.ts +120 -0
- package/src/ngb/reporting/defaultConfig.ts +15 -0
- package/src/ngb/reporting/lookupFilters.ts +43 -0
- package/src/ngb/reporting/navigation.ts +181 -0
- package/src/ngb/reporting/pageHelpers.ts +73 -0
- package/src/ngb/reporting/pageSession.ts +92 -0
- package/src/ngb/reporting/paging.ts +66 -0
- package/src/ngb/reporting/types.ts +333 -0
- package/src/ngb/router/backNavigation.ts +122 -0
- package/src/ngb/router/queryParams.ts +129 -0
- package/src/ngb/router/queryState.ts +121 -0
- package/src/ngb/router/routeAliases.ts +66 -0
- package/src/ngb/router/routeParams.ts +15 -0
- package/src/ngb/router/shareLink.ts +39 -0
- package/src/ngb/security/NgbAccessAuditPanel.vue +32 -0
- package/src/ngb/security/NgbAccessDeniedState.vue +27 -0
- package/src/ngb/security/NgbEffectiveAccessPanel.vue +60 -0
- package/src/ngb/security/NgbPermissionMatrix.vue +160 -0
- package/src/ngb/security/NgbRoleEditorPage.vue +319 -0
- package/src/ngb/security/NgbRolesPage.vue +125 -0
- package/src/ngb/security/NgbUserEditorPage.vue +615 -0
- package/src/ngb/security/NgbUsersPage.vue +125 -0
- package/src/ngb/security/api.ts +91 -0
- package/src/ngb/security/audit.ts +21 -0
- package/src/ngb/security/permissions.ts +65 -0
- package/src/ngb/security/types.ts +157 -0
- package/src/ngb/security/useAccessStore.ts +69 -0
- package/src/ngb/site/NgbDashboardAsOfToolbar.vue +100 -0
- package/src/ngb/site/NgbDashboardStatusBanner.vue +91 -0
- package/src/ngb/site/NgbLogo.vue +44 -0
- package/src/ngb/site/NgbPageHeader.vue +49 -0
- package/src/ngb/site/NgbSiteShell.vue +286 -0
- package/src/ngb/site/NgbSiteSidebar.vue +270 -0
- package/src/ngb/site/NgbTopBar.vue +425 -0
- package/src/ngb/site/NgbTrendChart.vue +224 -0
- package/src/ngb/site/dashboardData.ts +324 -0
- package/src/ngb/site/mainMenuStore.ts +49 -0
- package/src/ngb/site/types.ts +31 -0
- package/src/ngb/site/useDashboardPageState.ts +98 -0
- package/src/ngb/site/useTheme.ts +95 -0
- package/src/ngb/utils/clone.ts +43 -0
- package/src/ngb/utils/dateValues.ts +107 -0
- package/src/ngb/utils/errorMessage.ts +14 -0
- package/src/ngb/utils/guid.ts +21 -0
- package/src/ngb/utils/stableValue.ts +21 -0
- package/src/ngb/utils/storage.ts +135 -0
- package/src/styles/tailwind.css +165 -0
- package/src/vite/publicAssets.ts +104 -0
- package/vite-public-assets.js +68 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import NgbHeaderActionCluster from '../components/NgbHeaderActionCluster.vue';
|
|
3
|
+
import NgbBadge from '../primitives/NgbBadge.vue';
|
|
4
|
+
import NgbIcon from '../primitives/NgbIcon.vue';
|
|
5
|
+
import NgbPageHeader from '../site/NgbPageHeader.vue';
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
DocumentHeaderActionGroup,
|
|
9
|
+
DocumentHeaderActionItem,
|
|
10
|
+
EditorKind,
|
|
11
|
+
EditorMode,
|
|
12
|
+
EntityHeaderIconAction,
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(defineProps<{
|
|
16
|
+
kind: EditorKind;
|
|
17
|
+
mode: EditorMode;
|
|
18
|
+
canBack: boolean;
|
|
19
|
+
title: string;
|
|
20
|
+
subtitle?: string;
|
|
21
|
+
documentStatusLabel: string;
|
|
22
|
+
documentStatusTone: 'neutral' | 'success' | 'warn';
|
|
23
|
+
loading: boolean;
|
|
24
|
+
saving: boolean;
|
|
25
|
+
pageActions?: EntityHeaderIconAction[];
|
|
26
|
+
documentPrimaryActions: DocumentHeaderActionItem[];
|
|
27
|
+
documentMoreActionGroups: DocumentHeaderActionGroup[];
|
|
28
|
+
}>(), {
|
|
29
|
+
subtitle: undefined,
|
|
30
|
+
pageActions: () => [],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const emit = defineEmits<{
|
|
34
|
+
(e: 'back'): void;
|
|
35
|
+
(e: 'close'): void;
|
|
36
|
+
(e: 'action', action: string): void;
|
|
37
|
+
}>();
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<template v-if="kind === 'document'">
|
|
42
|
+
<template v-if="mode === 'page'">
|
|
43
|
+
<NgbPageHeader :title="title" :can-back="canBack" @back="emit('back')">
|
|
44
|
+
<template #secondary>
|
|
45
|
+
<div class="flex min-w-0 items-center">
|
|
46
|
+
<NgbBadge :tone="documentStatusTone">{{ documentStatusLabel }}</NgbBadge>
|
|
47
|
+
</div>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<template #actions>
|
|
51
|
+
<NgbHeaderActionCluster
|
|
52
|
+
:primary-actions="documentPrimaryActions"
|
|
53
|
+
:more-groups="documentMoreActionGroups"
|
|
54
|
+
:close-disabled="loading || saving"
|
|
55
|
+
@action="(action) => emit('action', action)"
|
|
56
|
+
@close="emit('close')"
|
|
57
|
+
/>
|
|
58
|
+
</template>
|
|
59
|
+
</NgbPageHeader>
|
|
60
|
+
</template>
|
|
61
|
+
|
|
62
|
+
<div v-else class="border-b border-ngb-border bg-ngb-card px-5 pt-4 pb-4">
|
|
63
|
+
<div class="min-w-0 w-full">
|
|
64
|
+
<div class="font-semibold leading-tight truncate whitespace-nowrap text-lg">
|
|
65
|
+
{{ title }}
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div class="mt-3 flex items-center justify-between gap-3">
|
|
70
|
+
<div class="min-w-0 flex items-center gap-2">
|
|
71
|
+
<NgbBadge :tone="documentStatusTone">{{ documentStatusLabel }}</NgbBadge>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div class="flex shrink-0 items-center gap-2">
|
|
75
|
+
<NgbHeaderActionCluster
|
|
76
|
+
:primary-actions="documentPrimaryActions"
|
|
77
|
+
:more-groups="documentMoreActionGroups"
|
|
78
|
+
:close-disabled="loading || saving"
|
|
79
|
+
@action="(action) => emit('action', action)"
|
|
80
|
+
@close="emit('close')"
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<NgbPageHeader
|
|
88
|
+
v-else-if="mode === 'page'"
|
|
89
|
+
:title="title"
|
|
90
|
+
:can-back="canBack"
|
|
91
|
+
@back="emit('back')"
|
|
92
|
+
>
|
|
93
|
+
<template #secondary>
|
|
94
|
+
<div v-if="subtitle" class="text-xs text-ngb-muted truncate">{{ subtitle }}</div>
|
|
95
|
+
</template>
|
|
96
|
+
|
|
97
|
+
<template #actions>
|
|
98
|
+
<button
|
|
99
|
+
v-for="item in pageActions"
|
|
100
|
+
:key="item.key"
|
|
101
|
+
class="ngb-iconbtn"
|
|
102
|
+
:disabled="item.disabled"
|
|
103
|
+
:title="item.title"
|
|
104
|
+
@click="emit('action', item.key)"
|
|
105
|
+
>
|
|
106
|
+
<NgbIcon :name="item.icon" />
|
|
107
|
+
</button>
|
|
108
|
+
|
|
109
|
+
<button class="ngb-iconbtn" :disabled="loading || saving" title="Close" @click="emit('close')">
|
|
110
|
+
<NgbIcon name="x" />
|
|
111
|
+
</button>
|
|
112
|
+
</template>
|
|
113
|
+
</NgbPageHeader>
|
|
114
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { resolveNgbEditorRouting } from './config';
|
|
2
|
+
|
|
3
|
+
export function buildCatalogListUrl(catalogType: string): string {
|
|
4
|
+
return resolveNgbEditorRouting().buildCatalogListUrl(catalogType);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function buildCatalogFullPageUrl(catalogType: string, id?: string | null): string {
|
|
8
|
+
return resolveNgbEditorRouting().buildCatalogFullPageUrl(catalogType, id);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildCatalogCompactPageUrl(catalogType: string, id?: string | null): string {
|
|
12
|
+
return resolveNgbEditorRouting().buildCatalogCompactPageUrl(catalogType, id);
|
|
13
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import type { Awaitable, EntityFormModel, LookupStoreApi } from '../metadata/types';
|
|
2
|
+
import type {
|
|
3
|
+
AuditLogPage,
|
|
4
|
+
DocumentHeaderActionItem,
|
|
5
|
+
DocumentEffects,
|
|
6
|
+
DocumentRecord,
|
|
7
|
+
EntityEditorContext,
|
|
8
|
+
EditorAuditBehavior,
|
|
9
|
+
EditorAuditLoadOptions,
|
|
10
|
+
EditorDocumentEffectsBehavior,
|
|
11
|
+
EditorPrintBehavior,
|
|
12
|
+
DocumentUiEffects,
|
|
13
|
+
RelationshipGraph,
|
|
14
|
+
} from './types';
|
|
15
|
+
|
|
16
|
+
export type EditorRoutingConfig = {
|
|
17
|
+
buildCatalogListUrl?: (catalogType: string) => string;
|
|
18
|
+
buildCatalogFullPageUrl?: (catalogType: string, id?: string | null) => string;
|
|
19
|
+
buildCatalogCompactPageUrl?: (catalogType: string, id?: string | null) => string;
|
|
20
|
+
buildDocumentFullPageUrl?: (documentType: string, id?: string | null) => string;
|
|
21
|
+
buildDocumentCompactPageUrl?: (documentType: string, id?: string | null) => string;
|
|
22
|
+
buildDocumentEffectsPageUrl?: (documentType: string, id: string) => string;
|
|
23
|
+
buildDocumentFlowPageUrl?: (documentType: string, id: string) => string;
|
|
24
|
+
buildDocumentPrintPageUrl?: (documentType: string, id: string, options?: { autoPrint?: boolean }) => string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type EditorEntityBehaviorArgs = {
|
|
28
|
+
context: EntityEditorContext;
|
|
29
|
+
model: EntityFormModel;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type EditorComputedDisplayMode = 'always' | 'new_or_draft';
|
|
33
|
+
|
|
34
|
+
export type EditorEntityProfile = {
|
|
35
|
+
tags?: string[];
|
|
36
|
+
sanitizeWatchFields?: string[];
|
|
37
|
+
computedDisplayWatchFields?: string[];
|
|
38
|
+
computedDisplayMode?: EditorComputedDisplayMode;
|
|
39
|
+
sanitizeModelForEditing?: (args: EditorEntityBehaviorArgs) => void;
|
|
40
|
+
syncComputedDisplay?: (args: EditorEntityBehaviorArgs) => void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type EditorDocumentActionGroup = {
|
|
44
|
+
key: string;
|
|
45
|
+
label: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type EditorConfiguredDocumentAction = {
|
|
49
|
+
item: DocumentHeaderActionItem;
|
|
50
|
+
group?: EditorDocumentActionGroup | null;
|
|
51
|
+
run: () => Awaitable<void>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type ResolveEditorDocumentActionsArgs = {
|
|
55
|
+
context: EntityEditorContext;
|
|
56
|
+
documentId: string;
|
|
57
|
+
model: EntityFormModel;
|
|
58
|
+
uiEffects: DocumentUiEffects | null;
|
|
59
|
+
loading: boolean;
|
|
60
|
+
saving: boolean;
|
|
61
|
+
navigate: (to: string | null | undefined) => void;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type EditorFrameworkConfig = {
|
|
65
|
+
routing?: EditorRoutingConfig;
|
|
66
|
+
loadDocumentById: (documentType: string, id: string) => Promise<DocumentRecord>;
|
|
67
|
+
loadDocumentEffects: (documentType: string, id: string, limit?: number) => Promise<DocumentEffects>;
|
|
68
|
+
loadDocumentGraph: (documentType: string, id: string, depth?: number, maxNodes?: number) => Promise<RelationshipGraph>;
|
|
69
|
+
loadEntityAuditLog: (
|
|
70
|
+
entityKind: number,
|
|
71
|
+
entityId: string,
|
|
72
|
+
options?: EditorAuditLoadOptions,
|
|
73
|
+
) => Promise<AuditLogPage>;
|
|
74
|
+
lookupStore?: LookupStoreApi | null;
|
|
75
|
+
audit?: EditorAuditBehavior;
|
|
76
|
+
effects?: EditorDocumentEffectsBehavior;
|
|
77
|
+
print?: EditorPrintBehavior;
|
|
78
|
+
resolveEntityProfile?: (context: EntityEditorContext) => EditorEntityProfile | null;
|
|
79
|
+
resolveDocumentActions?: (args: ResolveEditorDocumentActionsArgs) => EditorConfiguredDocumentAction[] | null;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let editorFrameworkConfig: EditorFrameworkConfig | null = null;
|
|
83
|
+
|
|
84
|
+
function normalizePathSegment(value: string | null | undefined): string {
|
|
85
|
+
return String(value ?? '').trim();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function appendQuery(path: string, query: Record<string, string | null | undefined>): string {
|
|
89
|
+
const params = new URLSearchParams();
|
|
90
|
+
|
|
91
|
+
for (const [key, value] of Object.entries(query)) {
|
|
92
|
+
const normalized = normalizePathSegment(value);
|
|
93
|
+
if (!normalized) continue;
|
|
94
|
+
params.set(key, normalized);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const serialized = params.toString();
|
|
98
|
+
return serialized ? `${path}?${serialized}` : path;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function defaultBuildCatalogListUrl(catalogType: string): string {
|
|
102
|
+
const type = encodeURIComponent(normalizePathSegment(catalogType));
|
|
103
|
+
return `/catalogs/${type}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function defaultBuildCatalogFullPageUrl(catalogType: string, id?: string | null): string {
|
|
107
|
+
const normalizedId = normalizePathSegment(id);
|
|
108
|
+
if (!normalizedId) return `${defaultBuildCatalogListUrl(catalogType)}/new`;
|
|
109
|
+
return `${defaultBuildCatalogListUrl(catalogType)}/${encodeURIComponent(normalizedId)}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function defaultBuildCatalogCompactPageUrl(catalogType: string, id?: string | null): string {
|
|
113
|
+
const normalizedId = normalizePathSegment(id);
|
|
114
|
+
if (!normalizedId) return appendQuery(defaultBuildCatalogListUrl(catalogType), { panel: 'new' });
|
|
115
|
+
return appendQuery(defaultBuildCatalogListUrl(catalogType), { panel: 'edit', id: normalizedId });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function defaultBuildDocumentFullPageUrl(documentType: string, id?: string | null): string {
|
|
119
|
+
const type = encodeURIComponent(normalizePathSegment(documentType));
|
|
120
|
+
const normalizedId = normalizePathSegment(id);
|
|
121
|
+
if (!normalizedId) return `/documents/${type}/new`;
|
|
122
|
+
return `/documents/${type}/${encodeURIComponent(normalizedId)}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function defaultBuildDocumentCompactPageUrl(documentType: string, id?: string | null): string {
|
|
126
|
+
const type = encodeURIComponent(normalizePathSegment(documentType));
|
|
127
|
+
const normalizedId = normalizePathSegment(id);
|
|
128
|
+
if (!normalizedId) return `/documents/${type}?panel=new`;
|
|
129
|
+
return appendQuery(`/documents/${type}`, { panel: 'edit', id: normalizedId });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function defaultBuildDocumentEffectsPageUrl(documentType: string, id: string): string {
|
|
133
|
+
const type = encodeURIComponent(normalizePathSegment(documentType));
|
|
134
|
+
return `/documents/${type}/${encodeURIComponent(normalizePathSegment(id))}/effects`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function defaultBuildDocumentFlowPageUrl(documentType: string, id: string): string {
|
|
138
|
+
const type = encodeURIComponent(normalizePathSegment(documentType));
|
|
139
|
+
return `/documents/${type}/${encodeURIComponent(normalizePathSegment(id))}/flow`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function defaultBuildDocumentPrintPageUrl(
|
|
143
|
+
documentType: string,
|
|
144
|
+
id: string,
|
|
145
|
+
options?: { autoPrint?: boolean },
|
|
146
|
+
): string {
|
|
147
|
+
const type = encodeURIComponent(normalizePathSegment(documentType));
|
|
148
|
+
const base = `/documents/${type}/${encodeURIComponent(normalizePathSegment(id))}/print`;
|
|
149
|
+
return options?.autoPrint ? `${base}?autoprint=1` : base;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function configureNgbEditor(config: EditorFrameworkConfig): void {
|
|
153
|
+
editorFrameworkConfig = config;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function getConfiguredNgbEditor(): EditorFrameworkConfig {
|
|
157
|
+
if (!editorFrameworkConfig) {
|
|
158
|
+
throw new Error('NGB editor framework is not configured. Call configureNgbEditor(...) during app bootstrap.');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return editorFrameworkConfig;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function maybeGetConfiguredNgbEditor(): EditorFrameworkConfig | null {
|
|
165
|
+
return editorFrameworkConfig;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function resolveNgbEditorRouting(): Required<EditorRoutingConfig> {
|
|
169
|
+
return {
|
|
170
|
+
buildCatalogListUrl:
|
|
171
|
+
editorFrameworkConfig?.routing?.buildCatalogListUrl ?? defaultBuildCatalogListUrl,
|
|
172
|
+
buildCatalogFullPageUrl:
|
|
173
|
+
editorFrameworkConfig?.routing?.buildCatalogFullPageUrl ?? defaultBuildCatalogFullPageUrl,
|
|
174
|
+
buildCatalogCompactPageUrl:
|
|
175
|
+
editorFrameworkConfig?.routing?.buildCatalogCompactPageUrl ?? defaultBuildCatalogCompactPageUrl,
|
|
176
|
+
buildDocumentFullPageUrl:
|
|
177
|
+
editorFrameworkConfig?.routing?.buildDocumentFullPageUrl ?? defaultBuildDocumentFullPageUrl,
|
|
178
|
+
buildDocumentCompactPageUrl:
|
|
179
|
+
editorFrameworkConfig?.routing?.buildDocumentCompactPageUrl ?? defaultBuildDocumentCompactPageUrl,
|
|
180
|
+
buildDocumentEffectsPageUrl:
|
|
181
|
+
editorFrameworkConfig?.routing?.buildDocumentEffectsPageUrl ?? defaultBuildDocumentEffectsPageUrl,
|
|
182
|
+
buildDocumentFlowPageUrl:
|
|
183
|
+
editorFrameworkConfig?.routing?.buildDocumentFlowPageUrl ?? defaultBuildDocumentFlowPageUrl,
|
|
184
|
+
buildDocumentPrintPageUrl:
|
|
185
|
+
editorFrameworkConfig?.routing?.buildDocumentPrintPageUrl ?? defaultBuildDocumentPrintPageUrl,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function resolveNgbEditorEntityProfile(context: EntityEditorContext): EditorEntityProfile {
|
|
190
|
+
return editorFrameworkConfig?.resolveEntityProfile?.(context) ?? {};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function resolveNgbEditorDocumentActions(
|
|
194
|
+
args: ResolveEditorDocumentActionsArgs,
|
|
195
|
+
): EditorConfiguredDocumentAction[] {
|
|
196
|
+
return editorFrameworkConfig?.resolveDocumentActions?.(args) ?? [];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function sanitizeNgbEditorModelForEditing(
|
|
200
|
+
context: EntityEditorContext,
|
|
201
|
+
model: EntityFormModel,
|
|
202
|
+
): void {
|
|
203
|
+
resolveNgbEditorEntityProfile(context).sanitizeModelForEditing?.({ context, model });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function syncNgbEditorComputedDisplay(
|
|
207
|
+
context: EntityEditorContext,
|
|
208
|
+
model: EntityFormModel,
|
|
209
|
+
): void {
|
|
210
|
+
resolveNgbEditorEntityProfile(context).syncComputedDisplay?.({ context, model });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function resolveNgbEditorAuditBehavior(override?: EditorAuditBehavior): EditorAuditBehavior {
|
|
214
|
+
const defaultHiddenFieldNames = [
|
|
215
|
+
'created_at_utc',
|
|
216
|
+
'updated_at_utc',
|
|
217
|
+
'deleted_at_utc',
|
|
218
|
+
'marked_for_deletion_at_utc',
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
hiddenFieldNames: [
|
|
223
|
+
...defaultHiddenFieldNames,
|
|
224
|
+
...(editorFrameworkConfig?.audit?.hiddenFieldNames ?? []),
|
|
225
|
+
...(override?.hiddenFieldNames ?? []),
|
|
226
|
+
],
|
|
227
|
+
explicitFieldLabels: {
|
|
228
|
+
...(editorFrameworkConfig?.audit?.explicitFieldLabels ?? {}),
|
|
229
|
+
...(override?.explicitFieldLabels ?? {}),
|
|
230
|
+
},
|
|
231
|
+
actionTitles: {
|
|
232
|
+
...(editorFrameworkConfig?.audit?.actionTitles ?? {}),
|
|
233
|
+
...(override?.actionTitles ?? {}),
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function resolveNgbEditorEffectsBehavior(
|
|
239
|
+
override?: EditorDocumentEffectsBehavior,
|
|
240
|
+
): EditorDocumentEffectsBehavior {
|
|
241
|
+
return {
|
|
242
|
+
...(editorFrameworkConfig?.effects ?? {}),
|
|
243
|
+
...(override ?? {}),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function resolveNgbEditorPrintBehavior(override?: EditorPrintBehavior): EditorPrintBehavior {
|
|
248
|
+
return {
|
|
249
|
+
...(editorFrameworkConfig?.print ?? {}),
|
|
250
|
+
...(override ?? {}),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canUseStorage,
|
|
3
|
+
listStorageKeys,
|
|
4
|
+
readStorageString,
|
|
5
|
+
removeStorageItem,
|
|
6
|
+
writeStorageString,
|
|
7
|
+
} from '../utils/storage';
|
|
8
|
+
import type { JsonObject, JsonValue, RecordFields, RecordParts } from '../metadata/types';
|
|
9
|
+
|
|
10
|
+
const STORAGE_KEY_PREFIX = 'ngb:document-copy-draft:';
|
|
11
|
+
const SNAPSHOT_VERSION = 1;
|
|
12
|
+
const MAX_SNAPSHOT_AGE_MS = 6 * 60 * 60 * 1000;
|
|
13
|
+
|
|
14
|
+
type StoredDocumentCopyDraft = {
|
|
15
|
+
version: number;
|
|
16
|
+
documentType: string;
|
|
17
|
+
fields: RecordFields;
|
|
18
|
+
parts?: RecordParts | null;
|
|
19
|
+
createdAtUtc: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type DocumentCopyDraftSnapshot = {
|
|
23
|
+
documentType: string;
|
|
24
|
+
fields: RecordFields;
|
|
25
|
+
parts?: RecordParts | null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type CopyDraftStore = {
|
|
29
|
+
kind: 'session' | 'local' | 'memory';
|
|
30
|
+
getItem(key: string): string | null;
|
|
31
|
+
setItem(key: string, value: string): void;
|
|
32
|
+
removeItem(key: string): void;
|
|
33
|
+
keys(): string[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type GlobalWithCopyDraftStore = typeof globalThis & {
|
|
37
|
+
__ngbDocumentCopyDraftMemoryStore?: Map<string, string>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function getMemoryStore(): Map<string, string> {
|
|
41
|
+
const root = globalThis as GlobalWithCopyDraftStore;
|
|
42
|
+
if (!root.__ngbDocumentCopyDraftMemoryStore) {
|
|
43
|
+
root.__ngbDocumentCopyDraftMemoryStore = new Map<string, string>();
|
|
44
|
+
}
|
|
45
|
+
return root.__ngbDocumentCopyDraftMemoryStore;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getStores(): CopyDraftStore[] {
|
|
49
|
+
const stores: CopyDraftStore[] = [];
|
|
50
|
+
if (canUseStorage('session')) {
|
|
51
|
+
stores.push({
|
|
52
|
+
kind: 'session',
|
|
53
|
+
getItem: (key) => readStorageString('session', key),
|
|
54
|
+
setItem: (key, value) => {
|
|
55
|
+
void writeStorageString('session', key, value);
|
|
56
|
+
},
|
|
57
|
+
removeItem: (key) => removeStorageItem('session', key),
|
|
58
|
+
keys: () => listStorageKeys('session'),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (canUseStorage('local')) {
|
|
63
|
+
stores.push({
|
|
64
|
+
kind: 'local',
|
|
65
|
+
getItem: (key) => readStorageString('local', key),
|
|
66
|
+
setItem: (key, value) => {
|
|
67
|
+
void writeStorageString('local', key, value);
|
|
68
|
+
},
|
|
69
|
+
removeItem: (key) => removeStorageItem('local', key),
|
|
70
|
+
keys: () => listStorageKeys('local'),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const memory = getMemoryStore();
|
|
75
|
+
stores.push({
|
|
76
|
+
kind: 'memory',
|
|
77
|
+
getItem: (key) => memory.get(key) ?? null,
|
|
78
|
+
setItem: (key, value) => {
|
|
79
|
+
memory.set(key, value);
|
|
80
|
+
},
|
|
81
|
+
removeItem: (key) => {
|
|
82
|
+
memory.delete(key);
|
|
83
|
+
},
|
|
84
|
+
keys: () => Array.from(memory.keys()),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return stores;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildStorageKey(token: string): string {
|
|
91
|
+
return `${STORAGE_KEY_PREFIX}${token}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function createToken(): string {
|
|
95
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
96
|
+
return `${Date.now().toString(36)}-${random}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
100
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function cleanupExpiredSnapshots(store: CopyDraftStore): void {
|
|
104
|
+
const now = Date.now();
|
|
105
|
+
|
|
106
|
+
for (const key of store.keys()) {
|
|
107
|
+
if (!key || !key.startsWith(STORAGE_KEY_PREFIX)) continue;
|
|
108
|
+
|
|
109
|
+
const raw = store.getItem(key);
|
|
110
|
+
if (!raw) {
|
|
111
|
+
store.removeItem(key);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const parsed = JSON.parse(raw) as Partial<StoredDocumentCopyDraft>;
|
|
117
|
+
const createdAtMs = Date.parse(String(parsed.createdAtUtc ?? ''));
|
|
118
|
+
if (!Number.isFinite(createdAtMs) || now - createdAtMs > MAX_SNAPSHOT_AGE_MS) {
|
|
119
|
+
store.removeItem(key);
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
store.removeItem(key);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parseSnapshot(raw: string | null): StoredDocumentCopyDraft | null {
|
|
128
|
+
if (!raw) return null;
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const parsed = JSON.parse(raw) as Partial<StoredDocumentCopyDraft>;
|
|
132
|
+
if (parsed.version !== SNAPSHOT_VERSION) return null;
|
|
133
|
+
if (typeof parsed.documentType !== 'string' || !parsed.documentType.trim()) return null;
|
|
134
|
+
if (!isRecord(parsed.fields)) return null;
|
|
135
|
+
if (parsed.parts != null && !isRecord(parsed.parts)) return null;
|
|
136
|
+
if (typeof parsed.createdAtUtc !== 'string' || !parsed.createdAtUtc.trim()) return null;
|
|
137
|
+
return {
|
|
138
|
+
version: SNAPSHOT_VERSION,
|
|
139
|
+
documentType: parsed.documentType,
|
|
140
|
+
fields: parsed.fields as RecordFields,
|
|
141
|
+
parts: (parsed.parts ?? null) as RecordParts | null,
|
|
142
|
+
createdAtUtc: parsed.createdAtUtc,
|
|
143
|
+
};
|
|
144
|
+
} catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function sanitizeForStorage(value: unknown, seen: WeakSet<object> = new WeakSet()): JsonValue {
|
|
150
|
+
if (value == null) return null;
|
|
151
|
+
|
|
152
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (typeof value === 'bigint') return value.toString();
|
|
157
|
+
if (typeof value === 'function' || typeof value === 'symbol') return null;
|
|
158
|
+
|
|
159
|
+
if (Array.isArray(value)) {
|
|
160
|
+
return value.map((item) => sanitizeForStorage(item, seen));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (typeof value === 'object') {
|
|
164
|
+
if (seen.has(value)) return null;
|
|
165
|
+
seen.add(value);
|
|
166
|
+
|
|
167
|
+
const out: JsonObject = {};
|
|
168
|
+
for (const [key, item] of Object.entries(value)) {
|
|
169
|
+
if (item === undefined) continue;
|
|
170
|
+
out[key] = sanitizeForStorage(item, seen);
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function buildPayload(snapshot: DocumentCopyDraftSnapshot): StoredDocumentCopyDraft {
|
|
179
|
+
return {
|
|
180
|
+
version: SNAPSHOT_VERSION,
|
|
181
|
+
documentType: snapshot.documentType,
|
|
182
|
+
fields: (sanitizeForStorage(snapshot.fields ?? {}) ?? {}) as RecordFields,
|
|
183
|
+
parts: sanitizeForStorage(snapshot.parts ?? null) as RecordParts | null,
|
|
184
|
+
createdAtUtc: new Date().toISOString(),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function writeToStore(store: CopyDraftStore, key: string, value: string): boolean {
|
|
189
|
+
try {
|
|
190
|
+
store.setItem(key, value);
|
|
191
|
+
return true;
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function saveDocumentCopyDraft(snapshot: DocumentCopyDraftSnapshot): string | null {
|
|
198
|
+
const token = createToken();
|
|
199
|
+
const stores = getStores();
|
|
200
|
+
const payload = buildPayload(snapshot);
|
|
201
|
+
const serialized = JSON.stringify(payload);
|
|
202
|
+
const key = buildStorageKey(token);
|
|
203
|
+
|
|
204
|
+
for (const store of stores) {
|
|
205
|
+
cleanupExpiredSnapshots(store);
|
|
206
|
+
if (writeToStore(store, key, serialized)) return token;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function readDocumentCopyDraft(
|
|
213
|
+
token: string | null | undefined,
|
|
214
|
+
expectedDocumentType?: string | null,
|
|
215
|
+
): DocumentCopyDraftSnapshot | null {
|
|
216
|
+
const normalizedToken = String(token ?? '').trim();
|
|
217
|
+
if (!normalizedToken) return null;
|
|
218
|
+
|
|
219
|
+
const key = buildStorageKey(normalizedToken);
|
|
220
|
+
for (const store of getStores()) {
|
|
221
|
+
cleanupExpiredSnapshots(store);
|
|
222
|
+
|
|
223
|
+
const parsed = parseSnapshot(store.getItem(key));
|
|
224
|
+
if (!parsed) continue;
|
|
225
|
+
if (expectedDocumentType && parsed.documentType !== expectedDocumentType) return null;
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
documentType: parsed.documentType,
|
|
229
|
+
fields: parsed.fields,
|
|
230
|
+
parts: parsed.parts ?? null,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function clearDocumentCopyDraft(token: string | null | undefined): void {
|
|
238
|
+
const normalizedToken = String(token ?? '').trim();
|
|
239
|
+
if (!normalizedToken) return;
|
|
240
|
+
|
|
241
|
+
const key = buildStorageKey(normalizedToken);
|
|
242
|
+
for (const store of getStores()) {
|
|
243
|
+
store.removeItem(key);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { isEmptyGuid, isNonEmptyGuid, shortGuid } from '../utils/guid';
|
|
2
|
+
import type { DocumentEffects, EffectAccount } from './types';
|
|
3
|
+
|
|
4
|
+
type CoaLabelResolver = (id: unknown) => string;
|
|
5
|
+
|
|
6
|
+
export function resolveEffectAccountLabel(
|
|
7
|
+
account: EffectAccount | null | undefined,
|
|
8
|
+
accountId: string | null | undefined,
|
|
9
|
+
resolveCoaLabel?: CoaLabelResolver | null,
|
|
10
|
+
): string {
|
|
11
|
+
const code = String(account?.code ?? '').trim();
|
|
12
|
+
const name = String(account?.name ?? '').trim();
|
|
13
|
+
if (code && name) return `${code} — ${name}`;
|
|
14
|
+
if (code) return code;
|
|
15
|
+
if (name) return name;
|
|
16
|
+
if (resolveCoaLabel && isNonEmptyGuid(accountId) && !isEmptyGuid(accountId)) return resolveCoaLabel(accountId);
|
|
17
|
+
return '—';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function finalizeEffectDimensionSummary(
|
|
21
|
+
items: string[],
|
|
22
|
+
fallbackDimensionSetId?: string | null,
|
|
23
|
+
): string | string[] {
|
|
24
|
+
const visibleItems = items.filter((item) => !!item && item !== '—');
|
|
25
|
+
if (visibleItems.length > 0) return visibleItems;
|
|
26
|
+
if (isNonEmptyGuid(fallbackDimensionSetId) && !isEmptyGuid(fallbackDimensionSetId)) return shortGuid(fallbackDimensionSetId);
|
|
27
|
+
return '—';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function collectAccountingEntryAccountIds(snapshot: DocumentEffects | null | undefined): string[] {
|
|
31
|
+
const ids = new Set<string>();
|
|
32
|
+
|
|
33
|
+
for (const entry of snapshot?.accountingEntries ?? []) {
|
|
34
|
+
if (!entry.debitAccount && isNonEmptyGuid(entry.debitAccountId) && !isEmptyGuid(entry.debitAccountId)) {
|
|
35
|
+
ids.add(entry.debitAccountId);
|
|
36
|
+
}
|
|
37
|
+
if (!entry.creditAccount && isNonEmptyGuid(entry.creditAccountId) && !isEmptyGuid(entry.creditAccountId)) {
|
|
38
|
+
ids.add(entry.creditAccountId);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return Array.from(ids);
|
|
43
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function formatOccurredAtUtcValue(value: string, locales?: Intl.LocalesArgument): string {
|
|
2
|
+
const date = new Date(value)
|
|
3
|
+
if (Number.isNaN(date.getTime())) return value
|
|
4
|
+
|
|
5
|
+
const isUtcMidnight =
|
|
6
|
+
date.getUTCHours() === 0 &&
|
|
7
|
+
date.getUTCMinutes() === 0 &&
|
|
8
|
+
date.getUTCSeconds() === 0 &&
|
|
9
|
+
date.getUTCMilliseconds() === 0
|
|
10
|
+
|
|
11
|
+
if (isUtcMidnight) {
|
|
12
|
+
return date.toLocaleDateString(locales, {
|
|
13
|
+
timeZone: 'UTC',
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return date.toLocaleString(locales)
|
|
18
|
+
}
|