1import {
2 serializeGroupBy,
3 serializeGroupByTotals,
4} from "@framework/ui/GroupBy";
5import type { GroupBySettings } from "@framework/ui/GroupBy";
6import { createResource } from "frappe-ui";
7import { computed, MaybeRefOrGetter, ref, toValue, watch } from "vue";
8
9// useGroupByData through get_grouped_list, so @me and assigned-on filters match the list
10export function useGroupedList(
11 doctype: MaybeRefOrGetter<string>,
12 groupBy: MaybeRefOrGetter<GroupBySettings | null>,
13 filters: MaybeRefOrGetter<unknown>
14) {
15 const pageLength = ref(20);
16 const start = ref(0);
17 const loaded = ref<Record<string, unknown>[]>([]);
18 const lastPageFull = ref(false);
19 const url = "opendesk.api.doc.get_grouped_list";
20
21 const grouping = () => toValue(groupBy) as GroupBySettings;
22 const base = () => ({ doctype: toValue(doctype), filters: toValue(filters) });
23
24 // Load More fetches only the next page, like desk's report view, so groups are never counted.
25 const groups = createResource({
26 url,
27 makeParams: () => ({
28 ...base(),
29 ...serializeGroupBy(grouping()),
30 limit_start: start.value,
31 limit_page_length: pageLength.value,
32 }),
33 onSuccess(data: Record<string, unknown>[]) {
34 loaded.value = start.value ? [...loaded.value, ...data] : data;
35 lastPageFull.value = data.length === pageLength.value;
36 },
37 });
38
39 const totals = createResource({
40 url,
41 makeParams: () => ({
42 ...base(),
43 ...serializeGroupByTotals(grouping()),
44 limit_page_length: 1,
45 }),
46 });
47
48 const wantsTotals = (settings: GroupBySettings | null) =>
49 Boolean(settings?.showTotals && serializeGroupByTotals(settings));
50
51 function reloadGroups() {
52 if (!toValue(groupBy)) return;
53 start.value = 0;
54 groups.fetch();
55 }
56
57 function reload() {
58 reloadGroups();
59 if (wantsTotals(toValue(groupBy))) totals.fetch();
60 }
61
62 function loadMore() {
63 if (!toValue(groupBy)) return;
64 start.value = loaded.value.length;
65 groups.fetch();
66 }
67
68 // Column widths live on the grouping too; only what reaches the query refetches.
69 const query = computed(() => {
70 const settings = toValue(groupBy);
71 if (!settings) return null;
72 return JSON.stringify([serializeGroupBy(settings), settings.showTotals]);
73 });
74
75 watch([() => toValue(doctype), query, () => toValue(filters)], reload, {
76 immediate: true,
77 deep: true,
78 });
79 // the totals don't depend on the page size
80 watch(pageLength, reloadGroups);
81
82 const error = computed(
83 () =>
84 groups.error || (wantsTotals(toValue(groupBy)) && totals.error) || null
85 );
86 // after a failed fetch, show the error rather than the previous groups
87 const rows = computed<Record<string, unknown>[]>(() =>
88 toValue(groupBy) && !error.value ? loaded.value : []
89 );
90
91 return {
92 rows,
93 totals: computed(() =>
94 wantsTotals(toValue(groupBy)) && !error.value
95 ? totals.data?.[0] ?? null
96 : null
97 ),
98 loading: computed(() => Boolean(groups.loading)),
99 error,
100 rowCount: computed(() => rows.value.length),
101 hasNextPage: computed(() => lastPageFull.value),
102 pageLength,
103 loadMore,
104 reload,
105 };
106}
107