Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@heroicons/vue": "^2.1.1",
"@rushstack/eslint-patch": "^1.7.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/vue-table": "^8.20.5",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vueuse/core": "^10.11.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,28 @@ import {
import type { Client } from '@/packages/api/src';
import { canDeleteClients, canUpdateClients } from '@/utils/permissions';
import MoreOptionsDropdown from '@/packages/ui/src/MoreOptionsDropdown.vue';
import ClientEditModal from '@/Components/Common/Client/ClientEditModal.vue';
import { ref } from 'vue';

const emit = defineEmits<{
delete: [];
edit: [];
archive: [];
}>();
const props = defineProps<{
client: Client;
}>();
const showEditModal = ref(false);
</script>

<template>
<ClientEditModal
:client="client"
v-model:show="showEditModal"></ClientEditModal>
<MoreOptionsDropdown :label="'Actions for Client ' + props.client.name">
<div class="min-w-[150px]">
<button
v-if="canUpdateClients()"
@click="emit('edit')"
@click="showEditModal = true"
:aria-label="'Edit Client ' + props.client.name"
data-testid="client_edit"
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">
Expand Down
200 changes: 191 additions & 9 deletions resources/js/Components/Common/Client/ClientTable.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,157 @@
<script setup lang="ts">
import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
import { UserCircleIcon } from '@heroicons/vue/24/solid';
import { PlusIcon } from '@heroicons/vue/16/solid';
import { type Component, ref } from 'vue';
import {
ChevronUpDownIcon,
ChevronDownIcon,
ChevronUpIcon,
PlusIcon,
} from '@heroicons/vue/16/solid';
import { type Component, computed, h, ref, watchEffect } from 'vue';
import { type Client } from '@/packages/api/src';
import ClientTableRow from '@/Components/Common/Client/ClientTableRow.vue';
import ClientCreateModal from '@/Components/Common/Client/ClientCreateModal.vue';
import ClientTableHeading from '@/Components/Common/Client/ClientTableHeading.vue';
import { canCreateClients } from '@/utils/permissions';

defineProps<{
const props = defineProps<{
clients: Client[];
}>();
const createClient = ref(false);

import {
FlexRender,
getCoreRowModel,
useVueTable,
createColumnHelper,
type SortingState,
getSortedRowModel,
} from '@tanstack/vue-table';
import { storeToRefs } from 'pinia';
import { useProjectsStore } from '@/utils/useProjects';
import { CheckCircleIcon } from '@heroicons/vue/20/solid';
import TableHeading from '@/Components/Common/TableHeading.vue';
import ClientMoreOptionsDropdown from '@/Components/Common/Client/ClientMoreOptionsDropdown.vue';
import { useClientsStore } from '@/utils/useClients';
import ClientEditModal from '@/Components/Common/Client/ClientEditModal.vue';
import TableRow from '@/Components/TableRow.vue';
import TableCell from '@/Components/TableCell.vue';

const columnHelper = createColumnHelper<Client>();
const { projects } = storeToRefs(useProjectsStore());

const columns = computed(() => [
columnHelper.accessor((row) => row.name, {
id: 'name',
cell: (info) => info.getValue(),
header: () => 'Name',
}),
columnHelper.accessor((row) => row, {
id: 'projects',
sortingFn: (a, b) => {
return (
projects.value.filter(
(projects) => projects.client_id === a.original.id
).length -
projects.value.filter(
(projects) => projects.client_id === b.original.id
).length
);
},
cell: (info) =>
h('div', {
innerHTML:
projects.value.filter(
(projects) => projects.client_id === info.getValue().id
).length + ' Projects',
}),
header: () => 'Projects',
}),
columnHelper.accessor((row) => row, {
id: 'status',
enableSorting: false,
cell: (info) =>
h(
'div',
{
class: 'flex space-x-1 items-center',
},
[
h(CheckCircleIcon, {
class: 'w-5',
}),
h('span', {
innerHTML: info.getValue().is_archived
? 'Archived'
: 'Active',
}),
]
),
header: () => 'Status',
}),
columnHelper.display({
id: 'actions',
cell: (info) => {
const showEditModal = ref(false);
return h(
'div',
{
class: 'flex space-x-1 items-center',
},
[
h(ClientEditModal, {
client: info.row.original,
show: showEditModal.value,
}),
h(ClientMoreOptionsDropdown, {
class: 'w-5',
client: info.row.original,
onEdit: () => (showEditModal.value = true),
onArchive: () => {
useClientsStore().updateClient(
info.row.original.id,
{
...info.row.original,
is_archived: !info.row.original.is_archived,
}
);
},
onDelete: () => {
useClientsStore().deleteClient(
info.row.original.id
);
},
}),
]
);
},
}),
]);

const data = ref(props.clients);

watchEffect(() => {
data.value = props.clients;
});

const table = useVueTable({
get data() {
return data.value;
},
onSortingChange: (updaterOrValue) => {
sorting.value =
typeof updaterOrValue === 'function'
? updaterOrValue(sorting.value)
: updaterOrValue;
},
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
state: {
get sorting() {
return sorting.value;
},
},
columns: columns.value,
});
const sorting = ref<SortingState>([]);
</script>

<template>
Expand All @@ -23,7 +162,43 @@ const createClient = ref(false);
data-testid="client_table"
class="grid min-w-full"
style="grid-template-columns: 1fr 150px 200px 80px">
<ClientTableHeading></ClientTableHeading>
<TableHeading>
<TableCell
v-for="header in table.getHeaderGroups()[0].headers"
:key="header.id"
:class="
header.column.getCanSort()
? 'cursor-pointer select-none'
: ''
"
@click="
header.column.getToggleSortingHandler()?.($event)
"
:cell="header">
<FlexRender
v-if="!header.isPlaceholder"
:render="header.column.columnDef.header"
:props="header.getContext()" />
<div class="px-1" v-if="header.column.getCanSort()">
<ChevronUpDownIcon
class="h-4 text-text-tertiary"
v-if="
header.column.getIsSorted() === false
"></ChevronUpDownIcon>
<ChevronDownIcon
class="h-4 text-accent-300"
v-if="
header.column.getIsSorted() === 'desc'
"></ChevronDownIcon>
<ChevronUpIcon
class="h-4 text-accent-300"
v-if="
header.column.getIsSorted() === 'asc'
"></ChevronUpIcon>
</div>
</TableCell>
</TableHeading>

<div
class="col-span-2 py-24 text-center"
v-if="clients.length === 0">
Expand All @@ -40,9 +215,16 @@ const createClient = ref(false);
>Create your First Client
</SecondaryButton>
</div>
<template v-for="client in clients" :key="client.id">
<ClientTableRow :client="client"></ClientTableRow>
</template>
<TableRow v-for="row in table.getRowModel().rows" :key="row.id">
<TableCell
v-for="cell in row.getVisibleCells()"
:key="cell.id"
:cell="cell">
<FlexRender
:render="cell.column.columnDef.cell"
:props="cell.getContext()" />
</TableCell>
</TableRow>
</div>
</div>
</div>
Expand Down
69 changes: 0 additions & 69 deletions resources/js/Components/Common/Client/ClientTableRow.vue

This file was deleted.

2 changes: 1 addition & 1 deletion resources/js/Components/Common/TableHeading.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<template>
<div
class="contents [&>*]:border-row-separator text-xs sm:text-sm [&>*]:border-b [&>*]:border-t [&>*]:bg-row-heading-background">
class="contents [&>*]:border-row-separator text-xs sm:text-sm [&>*]:border-b [&>*]:py-1 [&>*]:border-t [&>*]:bg-row-heading-background">
<slot></slot>
</div>
</template>
Expand Down
Loading
Loading