Skip to content

Commit c222dc3

Browse files
committed
Client - Made improvements to EntityTable.vue based on feedback from Reviewer.
: - i18n Keys not being found fixed , - label is now mandatory, - Labels are now translated directly in the actions files where they are called up - Removed some of the logic and put it in composables (Headers, Table Items, Cell Formatter and pagination)
1 parent f6ee587 commit c222dc3

14 files changed

Lines changed: 334 additions & 187 deletions

File tree

client/src/components/blocks/entity-table/EntityTable.vue

Lines changed: 43 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
:headers="computedHeaders"
44
:items="items"
55
:items-length="internalItemsLength"
6-
:items-per-page="itemsPerPage"
6+
:page="currentPage"
7+
:items-per-page="currentItemsPerPage"
78
:items-per-page-options="itemsPerPageOptions"
9+
:sort-by="sortByForTable"
810
:loading="loading"
911
:loading-text="$t('general.loading')"
1012
:no-data-text="$t('general.noData')"
@@ -15,7 +17,7 @@
1517
<template #item="{ item }">
1618
<tr
1719
:class="{ 'v-data-table__tr--clickable': !!detailRoute }"
18-
@click="onRowClick(getRawItem(item))"
20+
@click="navigateToItem(getRawItem(item))"
1921
>
2022
<td
2123
v-for="header in computedHeaders"
@@ -26,42 +28,10 @@
2628
"
2729
>
2830
<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+
/>
6535
</template>
6636

6737
<template v-else>
@@ -90,74 +60,33 @@
9060
</template>
9161

9262
<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+
/>
14571
</template>
14672
</v-data-table-server>
14773
</template>
14874

14975
<script setup lang="ts">
150-
import { computed } from "vue";
15176
import type {
15277
TableHeader,
15378
TableItem,
15479
TableAction,
15580
CellFormatter,
15681
} from "@/components/blocks/entity-table/types.ts";
15782
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";
15888
import type { ServerTablePaging } from "@/models/types.ts";
15989
import type { RouteName } from "@/router/routeNames.ts";
160-
import { translate } from "@/utils/generalUtils.ts";
16190
16291
const props = withDefaults(
16392
defineProps<{
@@ -197,90 +126,28 @@ const { navigateToItem } = useTableNavigation(
197126
props.routeParamKey,
198127
);
199128
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+
);
279133
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),
285152
});
286153
</script>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<template>
2+
<div class="pt-4">
3+
<v-divider />
4+
5+
<v-row no-gutters class="px-5 pt-4 pb-3 align-center">
6+
<v-col cols="12" md="4" class="d-none d-md-block" />
7+
8+
<v-col cols="12" md="4" class="d-flex justify-center">
9+
<v-pagination
10+
:model-value="page"
11+
:length="pageCount"
12+
:total-visible="5"
13+
active-color="primary"
14+
density="comfortable"
15+
rounded="lg"
16+
variant="text"
17+
prev-icon="mdi-chevron-left"
18+
next-icon="mdi-chevron-right"
19+
class="text-medium-emphasis"
20+
@update:model-value="emit('update:page', $event)"
21+
/>
22+
</v-col>
23+
24+
<v-col
25+
cols="12"
26+
md="4"
27+
class="d-flex justify-center justify-md-end"
28+
>
29+
<div
30+
class="d-flex flex-wrap align-center justify-center justify-md-end ga-3"
31+
>
32+
<span
33+
class="text-body-2 text-medium-emphasis font-weight-medium text-no-wrap"
34+
>
35+
{{ $t("general.itemsPerPage") }}
36+
</span>
37+
38+
<v-select
39+
:model-value="itemsPerPage"
40+
:items="itemsPerPageOptions"
41+
base-color="primary"
42+
color="primary"
43+
max-width="90"
44+
min-width="90"
45+
variant="outlined"
46+
density="compact"
47+
rounded="lg"
48+
hide-details
49+
class="text-body-2 font-weight-semibold"
50+
@update:model-value="
51+
emit('update:items-per-page', $event)
52+
"
53+
/>
54+
</div>
55+
</v-col>
56+
</v-row>
57+
</div>
58+
</template>
59+
60+
<script setup lang="ts">
61+
defineProps<{
62+
page: number;
63+
itemsPerPage: number;
64+
pageCount: number;
65+
itemsPerPageOptions: number[];
66+
}>();
67+
68+
const emit = defineEmits<{
69+
"update:page": [page: number];
70+
"update:items-per-page": [itemsPerPage: number];
71+
}>();
72+
</script>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<template>
2+
<div class="d-flex justify-center align-center ga-1">
3+
<v-hover
4+
v-for="action in visibleActions"
5+
:key="action.key"
6+
v-slot="{ isHovering, props: hoverProps }"
7+
>
8+
<v-btn
9+
v-bind="hoverProps"
10+
:icon="action.icon"
11+
:disabled="action.disabled?.(item) ?? false"
12+
:aria-label="action.label"
13+
variant="text"
14+
density="comfortable"
15+
size="small"
16+
rounded="lg"
17+
class="text-medium-emphasis"
18+
:color="isHovering ? (action.color ?? 'primary') : undefined"
19+
@click.stop="action.onClick(item)"
20+
/>
21+
</v-hover>
22+
</div>
23+
</template>
24+
25+
<script setup lang="ts">
26+
import { computed } from "vue";
27+
import type {
28+
TableAction,
29+
TableItem,
30+
} from "@/components/blocks/entity-table/types.ts";
31+
32+
const props = defineProps<{
33+
item: TableItem;
34+
actions: TableAction[];
35+
}>();
36+
37+
const visibleActions = computed(() =>
38+
props.actions.filter((a) => a.visible?.(props.item) ?? true),
39+
);
40+
</script>

0 commit comments

Comments
 (0)