@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,220 @@
|
|
|
1
|
+
import { computed, ref, watch, type CSSProperties, type ComputedRef, type Ref } from 'vue';
|
|
2
|
+
import { loadJson, saveJson } from '../../utils/storage';
|
|
3
|
+
import type { RegisterColumn } from './registerTypes';
|
|
4
|
+
|
|
5
|
+
type PersistedColumns = {
|
|
6
|
+
order?: string[];
|
|
7
|
+
widths?: Record<string, number>;
|
|
8
|
+
visible?: string[];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type UseRegisterColumnStateArgs = {
|
|
12
|
+
columns: ComputedRef<RegisterColumn[]>;
|
|
13
|
+
visibleColumnKeys: ComputedRef<string[] | undefined>;
|
|
14
|
+
storageKey: ComputedRef<string | undefined>;
|
|
15
|
+
showStatusColumn: ComputedRef<boolean>;
|
|
16
|
+
statusColWidth: ComputedRef<number>;
|
|
17
|
+
emitVisibleColumnKeys: (value: string[]) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type RegisterColumnState = {
|
|
21
|
+
localOrder: Ref<string[]>;
|
|
22
|
+
localWidths: Ref<Record<string, number>>;
|
|
23
|
+
visibleColumns: ComputedRef<RegisterColumn[]>;
|
|
24
|
+
gridTemplateColumns: ComputedRef<string>;
|
|
25
|
+
colWidth: (column: RegisterColumn) => number;
|
|
26
|
+
stickyStyle: (column: RegisterColumn) => CSSProperties;
|
|
27
|
+
cellStickyStyle: (column: RegisterColumn) => CSSProperties;
|
|
28
|
+
setVisible: (keys: string[]) => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function isAccidentalVisible(value: string[] | null) {
|
|
32
|
+
return !!value && value.length === 1 && value[0] === 'display';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useRegisterColumnState(args: UseRegisterColumnStateArgs): RegisterColumnState {
|
|
36
|
+
const localOrder = ref<string[]>(args.columns.value.map((column) => column.key));
|
|
37
|
+
const localWidths = ref<Record<string, number>>({});
|
|
38
|
+
const localVisible = ref<string[] | null>(args.visibleColumnKeys.value ? [...args.visibleColumnKeys.value] : null);
|
|
39
|
+
const persistEnabled = ref(false);
|
|
40
|
+
const derivedFromPartialColumns = ref(args.columns.value.length <= 1);
|
|
41
|
+
|
|
42
|
+
function ensureStateAfterColumnsReady() {
|
|
43
|
+
const columns = args.columns.value;
|
|
44
|
+
if (columns.length <= 1) return;
|
|
45
|
+
|
|
46
|
+
const keys = columns.map((column) => column.key);
|
|
47
|
+
if (derivedFromPartialColumns.value) {
|
|
48
|
+
if (localOrder.value.length <= 1 && localOrder.value[0] === 'display') {
|
|
49
|
+
localOrder.value = [...keys];
|
|
50
|
+
}
|
|
51
|
+
if (isAccidentalVisible(localVisible.value)) {
|
|
52
|
+
localVisible.value = null;
|
|
53
|
+
}
|
|
54
|
+
derivedFromPartialColumns.value = false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isAccidentalVisible(localVisible.value) && keys.length > 1) {
|
|
58
|
+
localVisible.value = null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!persistEnabled.value) persistEnabled.value = true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function resetLocalStateFromColumns() {
|
|
65
|
+
const columns = args.columns.value;
|
|
66
|
+
localOrder.value = columns.map((column) => column.key);
|
|
67
|
+
localWidths.value = {};
|
|
68
|
+
localVisible.value = args.visibleColumnKeys.value ? [...args.visibleColumnKeys.value] : null;
|
|
69
|
+
derivedFromPartialColumns.value = columns.length <= 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function hydrateFromStorage() {
|
|
73
|
+
const storageKey = args.storageKey.value;
|
|
74
|
+
if (!storageKey) return;
|
|
75
|
+
|
|
76
|
+
const saved = loadJson<PersistedColumns | string[] | null>(storageKey, null);
|
|
77
|
+
if (!saved) return;
|
|
78
|
+
|
|
79
|
+
if (Array.isArray(saved)) {
|
|
80
|
+
localVisible.value = [...saved];
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (saved.order?.length) localOrder.value = saved.order;
|
|
85
|
+
if (saved.widths) localWidths.value = saved.widths;
|
|
86
|
+
if (Array.isArray(saved.visible)) localVisible.value = [...saved.visible];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
watch(
|
|
90
|
+
args.storageKey,
|
|
91
|
+
(next, prev) => {
|
|
92
|
+
if (!next) return;
|
|
93
|
+
if (prev && next === prev) return;
|
|
94
|
+
resetLocalStateFromColumns();
|
|
95
|
+
hydrateFromStorage();
|
|
96
|
+
ensureStateAfterColumnsReady();
|
|
97
|
+
},
|
|
98
|
+
{ immediate: true },
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
watch(
|
|
102
|
+
() => args.columns.value.map((column) => column.key).join('|'),
|
|
103
|
+
() => {
|
|
104
|
+
ensureStateAfterColumnsReady();
|
|
105
|
+
},
|
|
106
|
+
{ immediate: true },
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
watch(
|
|
110
|
+
[localOrder, localWidths, localVisible],
|
|
111
|
+
() => {
|
|
112
|
+
const storageKey = args.storageKey.value;
|
|
113
|
+
if (!storageKey || !persistEnabled.value) return;
|
|
114
|
+
|
|
115
|
+
saveJson<PersistedColumns>(storageKey, {
|
|
116
|
+
order: localOrder.value,
|
|
117
|
+
widths: localWidths.value,
|
|
118
|
+
visible: localVisible.value ?? undefined,
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
{ deep: true },
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
watch(
|
|
125
|
+
args.visibleColumnKeys,
|
|
126
|
+
(value) => {
|
|
127
|
+
if (!value) return;
|
|
128
|
+
localVisible.value = [...value];
|
|
129
|
+
},
|
|
130
|
+
{ deep: true },
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const orderedColumns = computed(() => {
|
|
134
|
+
const byKey = new Map(args.columns.value.map((column) => [column.key, column] as const));
|
|
135
|
+
const result: RegisterColumn[] = [];
|
|
136
|
+
|
|
137
|
+
for (const key of localOrder.value) {
|
|
138
|
+
const column = byKey.get(key);
|
|
139
|
+
if (column) result.push(column);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const column of args.columns.value) {
|
|
143
|
+
if (!result.find((entry) => entry.key === column.key)) result.push(column);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return result;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const visibleColumns = computed(() => {
|
|
150
|
+
const keys = localVisible.value ?? args.visibleColumnKeys.value ?? args.columns.value.map((column) => column.key);
|
|
151
|
+
const visibleSet = new Set(keys);
|
|
152
|
+
return orderedColumns.value.filter((column) => visibleSet.has(column.key));
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
function setVisible(keys: string[]) {
|
|
156
|
+
localVisible.value = keys;
|
|
157
|
+
args.emitVisibleColumnKeys(keys);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function colWidth(column: RegisterColumn) {
|
|
161
|
+
const width = localWidths.value[column.key] ?? column.width ?? 140;
|
|
162
|
+
const minWidth = column.minWidth ?? 80;
|
|
163
|
+
return Math.max(minWidth, width);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const gridTemplateColumns = computed(() => {
|
|
167
|
+
const columns = [
|
|
168
|
+
...(args.showStatusColumn.value ? [`${args.statusColWidth.value}px`] : []),
|
|
169
|
+
...visibleColumns.value.map((column) => `${colWidth(column)}px`),
|
|
170
|
+
];
|
|
171
|
+
return columns.join(' ');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const stickyLeftOffsets = computed(() => {
|
|
175
|
+
let left = args.showStatusColumn.value ? args.statusColWidth.value : 0;
|
|
176
|
+
const offsets: Record<string, number> = {};
|
|
177
|
+
|
|
178
|
+
for (const column of visibleColumns.value) {
|
|
179
|
+
if (column.pinned === 'left') {
|
|
180
|
+
offsets[column.key] = left;
|
|
181
|
+
left += colWidth(column);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return offsets;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
function stickyStyle(column: RegisterColumn): CSSProperties {
|
|
189
|
+
if (column.pinned !== 'left') return {};
|
|
190
|
+
const left = stickyLeftOffsets.value[column.key] ?? (args.showStatusColumn.value ? args.statusColWidth.value : 0);
|
|
191
|
+
return {
|
|
192
|
+
position: 'sticky',
|
|
193
|
+
left: `${left}px`,
|
|
194
|
+
zIndex: 2,
|
|
195
|
+
background: 'var(--ngb-grid-header)',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function cellStickyStyle(column: RegisterColumn): CSSProperties {
|
|
200
|
+
if (column.pinned !== 'left') return {};
|
|
201
|
+
const left = stickyLeftOffsets.value[column.key] ?? (args.showStatusColumn.value ? args.statusColWidth.value : 0);
|
|
202
|
+
return {
|
|
203
|
+
position: 'sticky',
|
|
204
|
+
left: `${left}px`,
|
|
205
|
+
zIndex: 1,
|
|
206
|
+
background: 'inherit',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
localOrder,
|
|
212
|
+
localWidths,
|
|
213
|
+
visibleColumns,
|
|
214
|
+
gridTemplateColumns,
|
|
215
|
+
colWidth,
|
|
216
|
+
stickyStyle,
|
|
217
|
+
cellStickyStyle,
|
|
218
|
+
setVisible,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { computed, ref, watch, type ComputedRef } from 'vue';
|
|
2
|
+
import type { DisplayRow, RegisterDataRow, RegisterSortSpec, RowStatus } from './registerTypes';
|
|
3
|
+
import { isDisplayDataRow } from './registerTypes';
|
|
4
|
+
|
|
5
|
+
type UseRegisterRowsArgs = {
|
|
6
|
+
rows: ComputedRef<RegisterDataRow[]>;
|
|
7
|
+
groupBy: ComputedRef<string[]>;
|
|
8
|
+
sortBy: ComputedRef<RegisterSortSpec[]>;
|
|
9
|
+
defaultExpanded: ComputedRef<boolean>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function compareRows(left: RegisterDataRow, right: RegisterDataRow, sortBy: RegisterSortSpec[]): number {
|
|
13
|
+
for (const sort of sortBy) {
|
|
14
|
+
const leftValue = left[sort.key];
|
|
15
|
+
const rightValue = right[sort.key];
|
|
16
|
+
|
|
17
|
+
if (leftValue === rightValue) continue;
|
|
18
|
+
if (leftValue == null) return sort.dir === 'asc' ? -1 : 1;
|
|
19
|
+
if (rightValue == null) return sort.dir === 'asc' ? 1 : -1;
|
|
20
|
+
|
|
21
|
+
const result = leftValue > rightValue ? 1 : -1;
|
|
22
|
+
return sort.dir === 'asc' ? result : -result;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function groupKeyPart(row: RegisterDataRow, key: string) {
|
|
29
|
+
const value = row[key];
|
|
30
|
+
return value == null || value === '' ? '—' : String(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function collectGroupIds(rows: RegisterDataRow[], keys: string[], path: string[] = []): string[] {
|
|
34
|
+
if (keys.length === 0) return [];
|
|
35
|
+
|
|
36
|
+
const key = keys[0];
|
|
37
|
+
const buckets = new Map<string, RegisterDataRow[]>();
|
|
38
|
+
for (const row of rows) {
|
|
39
|
+
const label = groupKeyPart(row, key);
|
|
40
|
+
const bucket = buckets.get(label);
|
|
41
|
+
if (bucket) bucket.push(row);
|
|
42
|
+
else buckets.set(label, [row]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ids: string[] = [];
|
|
46
|
+
for (const [label, bucket] of buckets.entries()) {
|
|
47
|
+
const groupId = [...path, `${key}:${label}`].join('|');
|
|
48
|
+
ids.push(groupId);
|
|
49
|
+
ids.push(...collectGroupIds(bucket, keys.slice(1), [...path, `${key}:${label}`]));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return ids;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildGroups(rows: RegisterDataRow[], keys: string[], expandedGroups: Set<string>, path: string[] = []): DisplayRow[] {
|
|
56
|
+
if (keys.length === 0) {
|
|
57
|
+
return rows.map((row) => ({ ...row, type: 'row', __index: 0 }));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const key = keys[0];
|
|
61
|
+
const buckets = new Map<string, RegisterDataRow[]>();
|
|
62
|
+
for (const row of rows) {
|
|
63
|
+
const label = groupKeyPart(row, key);
|
|
64
|
+
const bucket = buckets.get(label);
|
|
65
|
+
if (bucket) bucket.push(row);
|
|
66
|
+
else buckets.set(label, [row]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const output: DisplayRow[] = [];
|
|
70
|
+
for (const [label, bucket] of buckets.entries()) {
|
|
71
|
+
const groupId = [...path, `${key}:${label}`].join('|');
|
|
72
|
+
const totals = bucket.reduce(
|
|
73
|
+
(accumulator, row) => {
|
|
74
|
+
accumulator.debit += Number(row.debit ?? 0);
|
|
75
|
+
accumulator.credit += Number(row.credit ?? 0);
|
|
76
|
+
return accumulator;
|
|
77
|
+
},
|
|
78
|
+
{ debit: 0, credit: 0 },
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
output.push({
|
|
82
|
+
type: 'group',
|
|
83
|
+
key: `g:${groupId}`,
|
|
84
|
+
groupId,
|
|
85
|
+
label,
|
|
86
|
+
count: bucket.length,
|
|
87
|
+
totalDebit: totals.debit,
|
|
88
|
+
totalCredit: totals.credit,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (expandedGroups.has(groupId)) {
|
|
92
|
+
output.push(...buildGroups(bucket, keys.slice(1), expandedGroups, [...path, `${key}:${label}`]));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return output;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function inferRegisterRowStatus(row: RegisterDataRow): RowStatus {
|
|
100
|
+
const explicit = row.__status;
|
|
101
|
+
if (explicit === 'active' || explicit === 'saved' || explicit === 'posted' || explicit === 'marked') return explicit;
|
|
102
|
+
|
|
103
|
+
if (row.isMarkedForDeletion === true || row.isDeleted === true) return 'marked';
|
|
104
|
+
|
|
105
|
+
const status = row.status;
|
|
106
|
+
if (typeof status === 'number') {
|
|
107
|
+
if (status === 2) return 'posted';
|
|
108
|
+
if (status === 3) return 'marked';
|
|
109
|
+
return 'saved';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (typeof row.isActive === 'boolean') return row.isActive ? 'active' : 'saved';
|
|
113
|
+
|
|
114
|
+
return 'active';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function useRegisterRows(args: UseRegisterRowsArgs) {
|
|
118
|
+
const hasGroups = computed(() => args.groupBy.value.length > 0);
|
|
119
|
+
const expandedGroups = ref<Set<string>>(new Set());
|
|
120
|
+
|
|
121
|
+
const sortedRows = computed(() => {
|
|
122
|
+
const rows = args.rows.value.map((row) => ({ ...row }));
|
|
123
|
+
if (!args.sortBy.value.length) return rows;
|
|
124
|
+
return rows.sort((left, right) => compareRows(left, right, args.sortBy.value));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const allGroupIds = computed(() => {
|
|
128
|
+
if (!hasGroups.value) return [];
|
|
129
|
+
return Array.from(new Set(collectGroupIds(sortedRows.value, args.groupBy.value)));
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const allGroupsExpanded = computed(() => {
|
|
133
|
+
const ids = allGroupIds.value;
|
|
134
|
+
if (!ids.length) return false;
|
|
135
|
+
for (const id of ids) {
|
|
136
|
+
if (!expandedGroups.value.has(id)) return false;
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
function isGroupExpanded(id: string) {
|
|
142
|
+
return expandedGroups.value.has(id);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function toggleGroup(id: string) {
|
|
146
|
+
const next = new Set(expandedGroups.value);
|
|
147
|
+
if (next.has(id)) next.delete(id);
|
|
148
|
+
else next.add(id);
|
|
149
|
+
expandedGroups.value = next;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function toggleAllGroups() {
|
|
153
|
+
if (!hasGroups.value) return;
|
|
154
|
+
const ids = allGroupIds.value;
|
|
155
|
+
if (!ids.length) return;
|
|
156
|
+
expandedGroups.value = allGroupsExpanded.value ? new Set() : new Set(ids);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
watch(
|
|
160
|
+
() => [args.defaultExpanded.value, allGroupIds.value.join('|')],
|
|
161
|
+
([isDefaultExpanded]) => {
|
|
162
|
+
if (!isDefaultExpanded) return;
|
|
163
|
+
const ids = allGroupIds.value;
|
|
164
|
+
if (!ids.length) return;
|
|
165
|
+
expandedGroups.value = new Set(ids);
|
|
166
|
+
},
|
|
167
|
+
{ immediate: true },
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const displayRows = computed<DisplayRow[]>(() => {
|
|
171
|
+
if (!args.groupBy.value.length) {
|
|
172
|
+
return sortedRows.value.map((row, index) => ({ ...row, type: 'row', __index: index }));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const grouped = buildGroups(sortedRows.value, args.groupBy.value, expandedGroups.value);
|
|
176
|
+
let index = 0;
|
|
177
|
+
|
|
178
|
+
return grouped.map((row) => {
|
|
179
|
+
if (isDisplayDataRow(row)) return { ...row, __index: index++ };
|
|
180
|
+
return row;
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const totals = computed(() => {
|
|
185
|
+
return args.rows.value.reduce(
|
|
186
|
+
(accumulator, row) => {
|
|
187
|
+
accumulator.debit += Number(row.debit ?? 0);
|
|
188
|
+
accumulator.credit += Number(row.credit ?? 0);
|
|
189
|
+
return accumulator;
|
|
190
|
+
},
|
|
191
|
+
{ debit: 0, credit: 0 },
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const dataRows = computed(() => displayRows.value.filter(isDisplayDataRow));
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
hasGroups,
|
|
199
|
+
displayRows,
|
|
200
|
+
dataRows,
|
|
201
|
+
totals,
|
|
202
|
+
allGroupIds,
|
|
203
|
+
allGroupsExpanded,
|
|
204
|
+
isGroupExpanded,
|
|
205
|
+
toggleGroup,
|
|
206
|
+
toggleAllGroups,
|
|
207
|
+
inferRowStatus: inferRegisterRowStatus,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { computed, onBeforeUnmount, onMounted, ref, type ComputedRef, type Ref } from 'vue';
|
|
2
|
+
import type { DisplayRow } from './registerTypes';
|
|
3
|
+
|
|
4
|
+
type UseRegisterViewportArgs = {
|
|
5
|
+
viewport: Ref<HTMLElement | null>;
|
|
6
|
+
heightPx: ComputedRef<number>;
|
|
7
|
+
rowHeight: ComputedRef<number>;
|
|
8
|
+
displayRows: ComputedRef<DisplayRow[]>;
|
|
9
|
+
overscan?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function useRegisterViewport(args: UseRegisterViewportArgs) {
|
|
13
|
+
const overscan = args.overscan ?? 8;
|
|
14
|
+
const scrollTop = ref(0);
|
|
15
|
+
const viewportHeight = ref(args.heightPx.value);
|
|
16
|
+
|
|
17
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
18
|
+
|
|
19
|
+
onMounted(() => {
|
|
20
|
+
viewportHeight.value = args.viewport.value?.clientHeight ?? args.heightPx.value;
|
|
21
|
+
|
|
22
|
+
if (typeof ResizeObserver !== 'undefined' && args.viewport.value) {
|
|
23
|
+
resizeObserver = new ResizeObserver(() => {
|
|
24
|
+
viewportHeight.value = args.viewport.value?.clientHeight ?? args.heightPx.value;
|
|
25
|
+
});
|
|
26
|
+
resizeObserver.observe(args.viewport.value);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
onBeforeUnmount(() => {
|
|
31
|
+
if (resizeObserver) {
|
|
32
|
+
resizeObserver.disconnect();
|
|
33
|
+
resizeObserver = null;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function onScroll() {
|
|
38
|
+
scrollTop.value = args.viewport.value?.scrollTop ?? 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const totalHeight = computed(() => args.displayRows.value.length * args.rowHeight.value);
|
|
42
|
+
const startIndex = computed(() => Math.max(0, Math.floor(scrollTop.value / args.rowHeight.value) - overscan));
|
|
43
|
+
const endIndex = computed(() => {
|
|
44
|
+
const visibleCount = Math.ceil(viewportHeight.value / args.rowHeight.value) + overscan * 2;
|
|
45
|
+
return Math.min(args.displayRows.value.length, startIndex.value + visibleCount);
|
|
46
|
+
});
|
|
47
|
+
const offsetTop = computed(() => startIndex.value * args.rowHeight.value);
|
|
48
|
+
const visibleRows = computed(() => args.displayRows.value.slice(startIndex.value, endIndex.value));
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
onScroll,
|
|
52
|
+
totalHeight,
|
|
53
|
+
offsetTop,
|
|
54
|
+
visibleRows,
|
|
55
|
+
};
|
|
56
|
+
}
|