Skip to content

Commit 6b84ba6

Browse files
committed
add tanstack table, add clients table
1 parent 8b50f33 commit 6b84ba6

File tree

7 files changed

+259
-81
lines changed

7 files changed

+259
-81
lines changed

package-lock.json

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"@heroicons/vue": "^2.1.1",
3939
"@rushstack/eslint-patch": "^1.7.0",
4040
"@tailwindcss/container-queries": "^0.1.1",
41+
"@tanstack/vue-table": "^8.20.5",
4142
"@vue/eslint-config-prettier": "^9.0.0",
4243
"@vue/eslint-config-typescript": "^13.0.0",
4344
"@vueuse/core": "^10.11.0",

resources/js/Components/Common/Client/ClientMoreOptionsDropdown.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,28 @@ import {
77
import type { Client } from '@/packages/api/src';
88
import { canDeleteClients, canUpdateClients } from '@/utils/permissions';
99
import MoreOptionsDropdown from '@/packages/ui/src/MoreOptionsDropdown.vue';
10+
import ClientEditModal from '@/Components/Common/Client/ClientEditModal.vue';
11+
import { ref } from 'vue';
1012
1113
const emit = defineEmits<{
1214
delete: [];
13-
edit: [];
1415
archive: [];
1516
}>();
1617
const props = defineProps<{
1718
client: Client;
1819
}>();
20+
const showEditModal = ref(false);
1921
</script>
2022

2123
<template>
24+
<ClientEditModal
25+
:client="client"
26+
v-model:show="showEditModal"></ClientEditModal>
2227
<MoreOptionsDropdown :label="'Actions for Client ' + props.client.name">
2328
<div class="min-w-[150px]">
2429
<button
2530
v-if="canUpdateClients()"
26-
@click="emit('edit')"
31+
@click="showEditModal = true"
2732
:aria-label="'Edit Client ' + props.client.name"
2833
data-testid="client_edit"
2934
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">

resources/js/Components/Common/Client/ClientTable.vue

Lines changed: 191 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,157 @@
11
<script setup lang="ts">
22
import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
33
import { UserCircleIcon } from '@heroicons/vue/24/solid';
4-
import { PlusIcon } from '@heroicons/vue/16/solid';
5-
import { type Component, ref } from 'vue';
4+
import {
5+
ChevronUpDownIcon,
6+
ChevronDownIcon,
7+
ChevronUpIcon,
8+
PlusIcon,
9+
} from '@heroicons/vue/16/solid';
10+
import { type Component, computed, h, ref, watchEffect } from 'vue';
611
import { type Client } from '@/packages/api/src';
7-
import ClientTableRow from '@/Components/Common/Client/ClientTableRow.vue';
812
import ClientCreateModal from '@/Components/Common/Client/ClientCreateModal.vue';
9-
import ClientTableHeading from '@/Components/Common/Client/ClientTableHeading.vue';
1013
import { canCreateClients } from '@/utils/permissions';
1114
12-
defineProps<{
15+
const props = defineProps<{
1316
clients: Client[];
1417
}>();
1518
const createClient = ref(false);
19+
20+
import {
21+
FlexRender,
22+
getCoreRowModel,
23+
useVueTable,
24+
createColumnHelper,
25+
type SortingState,
26+
getSortedRowModel,
27+
} from '@tanstack/vue-table';
28+
import { storeToRefs } from 'pinia';
29+
import { useProjectsStore } from '@/utils/useProjects';
30+
import { CheckCircleIcon } from '@heroicons/vue/20/solid';
31+
import TableHeading from '@/Components/Common/TableHeading.vue';
32+
import ClientMoreOptionsDropdown from '@/Components/Common/Client/ClientMoreOptionsDropdown.vue';
33+
import { useClientsStore } from '@/utils/useClients';
34+
import ClientEditModal from '@/Components/Common/Client/ClientEditModal.vue';
35+
import TableRow from '@/Components/TableRow.vue';
36+
import TableCell from '@/Components/TableCell.vue';
37+
38+
const columnHelper = createColumnHelper<Client>();
39+
const { projects } = storeToRefs(useProjectsStore());
40+
41+
const columns = computed(() => [
42+
columnHelper.accessor((row) => row.name, {
43+
id: 'name',
44+
cell: (info) => info.getValue(),
45+
header: () => 'Name',
46+
}),
47+
columnHelper.accessor((row) => row, {
48+
id: 'projects',
49+
sortingFn: (a, b) => {
50+
return (
51+
projects.value.filter(
52+
(projects) => projects.client_id === a.original.id
53+
).length -
54+
projects.value.filter(
55+
(projects) => projects.client_id === b.original.id
56+
).length
57+
);
58+
},
59+
cell: (info) =>
60+
h('div', {
61+
innerHTML:
62+
projects.value.filter(
63+
(projects) => projects.client_id === info.getValue().id
64+
).length + ' Projects',
65+
}),
66+
header: () => 'Projects',
67+
}),
68+
columnHelper.accessor((row) => row, {
69+
id: 'status',
70+
enableSorting: false,
71+
cell: (info) =>
72+
h(
73+
'div',
74+
{
75+
class: 'flex space-x-1 items-center',
76+
},
77+
[
78+
h(CheckCircleIcon, {
79+
class: 'w-5',
80+
}),
81+
h('span', {
82+
innerHTML: info.getValue().is_archived
83+
? 'Archived'
84+
: 'Active',
85+
}),
86+
]
87+
),
88+
header: () => 'Status',
89+
}),
90+
columnHelper.display({
91+
id: 'actions',
92+
cell: (info) => {
93+
const showEditModal = ref(false);
94+
return h(
95+
'div',
96+
{
97+
class: 'flex space-x-1 items-center',
98+
},
99+
[
100+
h(ClientEditModal, {
101+
client: info.row.original,
102+
show: showEditModal.value,
103+
}),
104+
h(ClientMoreOptionsDropdown, {
105+
class: 'w-5',
106+
client: info.row.original,
107+
onEdit: () => (showEditModal.value = true),
108+
onArchive: () => {
109+
useClientsStore().updateClient(
110+
info.row.original.id,
111+
{
112+
...info.row.original,
113+
is_archived: !info.row.original.is_archived,
114+
}
115+
);
116+
},
117+
onDelete: () => {
118+
useClientsStore().deleteClient(
119+
info.row.original.id
120+
);
121+
},
122+
}),
123+
]
124+
);
125+
},
126+
}),
127+
]);
128+
129+
const data = ref(props.clients);
130+
131+
watchEffect(() => {
132+
data.value = props.clients;
133+
});
134+
135+
const table = useVueTable({
136+
get data() {
137+
return data.value;
138+
},
139+
onSortingChange: (updaterOrValue) => {
140+
sorting.value =
141+
typeof updaterOrValue === 'function'
142+
? updaterOrValue(sorting.value)
143+
: updaterOrValue;
144+
},
145+
getCoreRowModel: getCoreRowModel(),
146+
getSortedRowModel: getSortedRowModel(),
147+
state: {
148+
get sorting() {
149+
return sorting.value;
150+
},
151+
},
152+
columns: columns.value,
153+
});
154+
const sorting = ref<SortingState>([]);
16155
</script>
17156

18157
<template>
@@ -23,7 +162,43 @@ const createClient = ref(false);
23162
data-testid="client_table"
24163
class="grid min-w-full"
25164
style="grid-template-columns: 1fr 150px 200px 80px">
26-
<ClientTableHeading></ClientTableHeading>
165+
<TableHeading>
166+
<TableCell
167+
v-for="header in table.getHeaderGroups()[0].headers"
168+
:key="header.id"
169+
:class="
170+
header.column.getCanSort()
171+
? 'cursor-pointer select-none'
172+
: ''
173+
"
174+
@click="
175+
header.column.getToggleSortingHandler()?.($event)
176+
"
177+
:cell="header">
178+
<FlexRender
179+
v-if="!header.isPlaceholder"
180+
:render="header.column.columnDef.header"
181+
:props="header.getContext()" />
182+
<div class="px-1" v-if="header.column.getCanSort()">
183+
<ChevronUpDownIcon
184+
class="h-4 text-text-tertiary"
185+
v-if="
186+
header.column.getIsSorted() === false
187+
"></ChevronUpDownIcon>
188+
<ChevronDownIcon
189+
class="h-4 text-accent-300"
190+
v-if="
191+
header.column.getIsSorted() === 'desc'
192+
"></ChevronDownIcon>
193+
<ChevronUpIcon
194+
class="h-4 text-accent-300"
195+
v-if="
196+
header.column.getIsSorted() === 'asc'
197+
"></ChevronUpIcon>
198+
</div>
199+
</TableCell>
200+
</TableHeading>
201+
27202
<div
28203
class="col-span-2 py-24 text-center"
29204
v-if="clients.length === 0">
@@ -40,9 +215,16 @@ const createClient = ref(false);
40215
>Create your First Client
41216
</SecondaryButton>
42217
</div>
43-
<template v-for="client in clients" :key="client.id">
44-
<ClientTableRow :client="client"></ClientTableRow>
45-
</template>
218+
<TableRow v-for="row in table.getRowModel().rows" :key="row.id">
219+
<TableCell
220+
v-for="cell in row.getVisibleCells()"
221+
:key="cell.id"
222+
:cell="cell">
223+
<FlexRender
224+
:render="cell.column.columnDef.cell"
225+
:props="cell.getContext()" />
226+
</TableCell>
227+
</TableRow>
46228
</div>
47229
</div>
48230
</div>

resources/js/Components/Common/Client/ClientTableRow.vue

Lines changed: 0 additions & 69 deletions
This file was deleted.

resources/js/Components/Common/TableHeading.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<template>
44
<div
5-
class="contents [&>*]:border-row-separator text-xs sm:text-sm [&>*]:border-b [&>*]:border-t [&>*]:bg-row-heading-background">
5+
class="contents [&>*]:border-row-separator text-xs sm:text-sm [&>*]:border-b [&>*]:py-1 [&>*]:border-t [&>*]:bg-row-heading-background">
66
<slot></slot>
77
</div>
88
</template>

0 commit comments

Comments
 (0)