|
3 | 3 | :headers="computedHeaders" |
4 | 4 | :items="items" |
5 | 5 | :items-length="internalItemsLength" |
6 | | - :items-per-page="itemsPerPage" |
| 6 | + :page="currentPage" |
| 7 | + :items-per-page="currentItemsPerPage" |
7 | 8 | :items-per-page-options="itemsPerPageOptions" |
| 9 | + :sort-by="sortByForTable" |
8 | 10 | :loading="loading" |
9 | 11 | :loading-text="$t('general.loading')" |
10 | 12 | :no-data-text="$t('general.noData')" |
|
15 | 17 | <template #item="{ item }"> |
16 | 18 | <tr |
17 | 19 | :class="{ 'v-data-table__tr--clickable': !!detailRoute }" |
18 | | - @click="onRowClick(getRawItem(item))" |
| 20 | + @click="navigateToItem(getRawItem(item))" |
19 | 21 | > |
20 | 22 | <td |
21 | 23 | v-for="header in computedHeaders" |
|
26 | 28 | " |
27 | 29 | > |
28 | 30 | <template v-if="header.key === '_actions'"> |
29 | | - <div class="d-flex justify-center align-center ga-1"> |
30 | | - <v-hover |
31 | | - v-for="action in visibleActions( |
32 | | - getRawItem(item), |
33 | | - )" |
34 | | - :key="action.key" |
35 | | - v-slot="{ isHovering, props: hoverProps }" |
36 | | - > |
37 | | - <v-btn |
38 | | - v-bind="hoverProps" |
39 | | - :icon="action.icon" |
40 | | - :disabled=" |
41 | | - action.disabled?.(getRawItem(item)) ?? |
42 | | - false |
43 | | - " |
44 | | - :aria-label=" |
45 | | - action.labelKey |
46 | | - ? $t(action.labelKey) |
47 | | - : undefined |
48 | | - " |
49 | | - variant="text" |
50 | | - density="comfortable" |
51 | | - size="small" |
52 | | - rounded="lg" |
53 | | - class="text-medium-emphasis" |
54 | | - :color=" |
55 | | - isHovering |
56 | | - ? (action.color ?? 'primary') |
57 | | - : undefined |
58 | | - " |
59 | | - @click.stop=" |
60 | | - action.onClick(getRawItem(item)) |
61 | | - " |
62 | | - /> |
63 | | - </v-hover> |
64 | | - </div> |
| 31 | + <TableRowActions |
| 32 | + :item="getRawItem(item)" |
| 33 | + :actions="actions ?? []" |
| 34 | + /> |
65 | 35 | </template> |
66 | 36 |
|
67 | 37 | <template v-else> |
|
90 | 60 | </template> |
91 | 61 |
|
92 | 62 | <template #bottom> |
93 | | - <div class="pt-4"> |
94 | | - <v-divider /> |
95 | | - |
96 | | - <v-row no-gutters class="px-5 pt-4 pb-3 align-center"> |
97 | | - <v-col cols="12" md="4" class="d-none d-md-block" /> |
98 | | - |
99 | | - <v-col cols="12" md="4" class="d-flex justify-center"> |
100 | | - <v-pagination |
101 | | - v-model="currentPage" |
102 | | - :length="pageCount" |
103 | | - :total-visible="10" |
104 | | - active-color="primary" |
105 | | - density="comfortable" |
106 | | - rounded="lg" |
107 | | - variant="text" |
108 | | - prev-icon="mdi-chevron-left" |
109 | | - next-icon="mdi-chevron-right" |
110 | | - class="text-medium-emphasis" |
111 | | - /> |
112 | | - </v-col> |
113 | | - |
114 | | - <v-col |
115 | | - cols="12" |
116 | | - md="4" |
117 | | - class="d-flex justify-center justify-md-end" |
118 | | - > |
119 | | - <div |
120 | | - class="d-flex flex-wrap align-center justify-center justify-md-end ga-3" |
121 | | - > |
122 | | - <span |
123 | | - class="text-body-2 text-medium-emphasis font-weight-medium text-no-wrap" |
124 | | - > |
125 | | - {{ translate("general.itemsPerPage") }} |
126 | | - </span> |
127 | | - |
128 | | - <v-select |
129 | | - v-model="localItemsPerPage" |
130 | | - :items="itemsPerPageOptions" |
131 | | - base-color="primary" |
132 | | - color="primary" |
133 | | - max-width="90" |
134 | | - min-width="90" |
135 | | - variant="outlined" |
136 | | - density="compact" |
137 | | - rounded="lg" |
138 | | - hide-details |
139 | | - class="text-body-2 font-weight-semibold" |
140 | | - /> |
141 | | - </div> |
142 | | - </v-col> |
143 | | - </v-row> |
144 | | - </div> |
| 63 | + <TableFooter |
| 64 | + :page="currentPage" |
| 65 | + :items-per-page="currentItemsPerPage" |
| 66 | + :page-count="resolvedPageCount" |
| 67 | + :items-per-page-options="itemsPerPageOptions" |
| 68 | + @update:page="onPageUpdate" |
| 69 | + @update:items-per-page="onItemsPerPageUpdate" |
| 70 | + /> |
145 | 71 | </template> |
146 | 72 | </v-data-table-server> |
147 | 73 | </template> |
148 | 74 |
|
149 | 75 | <script setup lang="ts"> |
150 | | -import { computed } from "vue"; |
151 | 76 | import type { |
152 | 77 | TableHeader, |
153 | 78 | TableItem, |
154 | 79 | TableAction, |
155 | 80 | CellFormatter, |
156 | 81 | } from "@/components/blocks/entity-table/types.ts"; |
157 | 82 | import { useTableNavigation } from "@/components/blocks/entity-table/composables/useTableNavigation.ts"; |
| 83 | +import { useTableHeaders } from "@/components/blocks/entity-table/composables/useTableHeaders.ts"; |
| 84 | +import { useTableItems } from "@/components/blocks/entity-table/composables/useTableItems.ts"; |
| 85 | +import { useTablePagination } from "@/components/blocks/entity-table/composables/useTablePagination.ts"; |
| 86 | +import TableRowActions from "@/components/blocks/entity-table/components/TableRowActions.vue"; |
| 87 | +import TableFooter from "@/components/blocks/entity-table/components/TableFooter.vue"; |
158 | 88 | import type { ServerTablePaging } from "@/models/types.ts"; |
159 | 89 | import type { RouteName } from "@/router/routeNames.ts"; |
160 | | -import { translate } from "@/utils/generalUtils.ts"; |
161 | 90 |
|
162 | 91 | const props = withDefaults( |
163 | 92 | defineProps<{ |
@@ -197,90 +126,28 @@ const { navigateToItem } = useTableNavigation( |
197 | 126 | props.routeParamKey, |
198 | 127 | ); |
199 | 128 |
|
200 | | -const computedHeaders = computed(() => { |
201 | | - const base = [...props.headers]; |
202 | | - if (props.actions?.length) { |
203 | | - base.push({ |
204 | | - title: "", |
205 | | - key: "_actions", |
206 | | - sortable: false, |
207 | | - width: "1%", |
208 | | - align: "center", |
209 | | - }); |
210 | | - } |
211 | | - return base; |
212 | | -}); |
213 | | -
|
214 | | -function getRawItem(item: unknown): TableItem { |
215 | | - if ( |
216 | | - item && |
217 | | - typeof item === "object" && |
218 | | - "raw" in item && |
219 | | - item.raw && |
220 | | - typeof item.raw === "object" |
221 | | - ) { |
222 | | - return item.raw as TableItem; |
223 | | - } |
224 | | - return item as TableItem; |
225 | | -} |
226 | | -
|
227 | | -function formatCell(key: string, item: TableItem): string { |
228 | | - const formatter = props.cellFormatters?.[key]; |
229 | | - if (formatter) return formatter(item[key], item); |
230 | | - const value = item[key]; |
231 | | - if (value === null || value === undefined) return ""; |
232 | | - return String(value); |
233 | | -} |
234 | | -
|
235 | | -function visibleActions(item: TableItem): TableAction[] { |
236 | | - if (!props.actions) return []; |
237 | | - return props.actions.filter((a) => a.visible?.(item) ?? true); |
238 | | -} |
239 | | -
|
240 | | -function onRowClick(item: TableItem) { |
241 | | - if (!props.detailRoute) return; |
242 | | - navigateToItem(item); |
243 | | -} |
244 | | -
|
245 | | -const itemsPerPageOptions = computed(() => { |
246 | | - const opts = [5]; |
247 | | - if (props.pageCount > 1 || props.items.length > 5) opts.push(10); |
248 | | - if (props.pageCount > 2 || props.items.length > 10) opts.push(15); |
249 | | - return opts; |
250 | | -}); |
251 | | -
|
252 | | -const currentPage = computed({ |
253 | | - get: () => props.options?.page ?? 1, |
254 | | - set: (page: number) => { |
255 | | - emit("update:options", { |
256 | | - page, |
257 | | - itemsPerPage: props.options?.itemsPerPage ?? props.itemsPerPage, |
258 | | - sortBy: props.options?.sortBy ?? [], |
259 | | - }); |
260 | | - }, |
261 | | -}); |
262 | | -
|
263 | | -const localItemsPerPage = computed({ |
264 | | - get: () => props.options?.itemsPerPage ?? props.itemsPerPage, |
265 | | - set: (itemsPerPage: number) => { |
266 | | - emit("update:options", { |
267 | | - page: 1, |
268 | | - itemsPerPage, |
269 | | - sortBy: props.options?.sortBy ?? [], |
270 | | - }); |
271 | | - }, |
272 | | -}); |
273 | | -
|
274 | | -const internalItemsLength = computed(() => { |
275 | | - if (props.itemsLength !== undefined) return props.itemsLength; |
276 | | -
|
277 | | - const page = props.options?.page ?? 1; |
278 | | - const perPage = props.options?.itemsPerPage ?? props.itemsPerPage; |
| 129 | +const { computedHeaders } = useTableHeaders( |
| 130 | + () => props.headers, |
| 131 | + () => !!props.actions?.length, |
| 132 | +); |
279 | 133 |
|
280 | | - if (props.pageCount === 0) return props.items.length; |
281 | | - if (page === props.pageCount) { |
282 | | - return (props.pageCount - 1) * perPage + props.items.length; |
283 | | - } |
284 | | - return props.pageCount * perPage; |
| 134 | +const { getRawItem, formatCell } = useTableItems(() => props.cellFormatters); |
| 135 | +
|
| 136 | +const { |
| 137 | + currentPage, |
| 138 | + currentItemsPerPage, |
| 139 | + resolvedPageCount, |
| 140 | + itemsPerPageOptions, |
| 141 | + internalItemsLength, |
| 142 | + sortByForTable, |
| 143 | + onPageUpdate, |
| 144 | + onItemsPerPageUpdate, |
| 145 | +} = useTablePagination({ |
| 146 | + options: () => props.options, |
| 147 | + itemsPerPage: () => props.itemsPerPage, |
| 148 | + pageCount: () => props.pageCount, |
| 149 | + items: () => props.items, |
| 150 | + itemsLength: () => props.itemsLength, |
| 151 | + emit: (options) => emit("update:options", options), |
285 | 152 | }); |
286 | 153 | </script> |
0 commit comments