core/app/components/data-table/DataTable.vue

137 lines
4.3 KiB
Vue

<script setup lang="ts" generic="TData, TValue">
import type {
ColumnDef,
ColumnFiltersState,
ExpandedState,
SortingState,
} from '@tanstack/vue-table'
import {
FlexRender,
getCoreRowModel,
getExpandedRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useVueTable,
} from '@tanstack/vue-table'
import { ArrowUpDown } from '@lucide/vue'
import { computed, h, ref } from 'vue'
import { Button } from '@/components/ui/button'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
import { valueUpdater } from '@/lib/utils'
import DataTablePagination from './DataTablePagination.vue'
import {
Empty, EmptyDescription,
EmptyHeader, EmptyTitle
} from '@/components/ui/empty'
const props = defineProps<{
columns: ColumnDef<TData, TValue>[]
data: TData[]
}>()
const sorting = ref<SortingState>([])
const columnFilters = ref<ColumnFiltersState>([])
const expanded = ref<ExpandedState>([])
const numberColumn: ColumnDef<TData> = {
id: 'number',
accessorFn: (_, index) => index,
header: ({ column }) => {
return h(Button, {
variant: 'ghost',
class: 'justify-center',
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
}, () => ['#', h(ArrowUpDown, { class: 'ml-2 h-4 w-4' })])
},
cell: ({ row }) => {
return h('div', { class: 'text-center' }, `${row.index + 1}`)
},
enableHiding: false,
}
const allColumns = computed<ColumnDef<TData>[]>(() => [
numberColumn,
...props.columns,
])
const table = useVueTable({
get data() { return props.data },
get columns() { return allColumns.value },
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getExpandedRowModel: getExpandedRowModel(),
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
state: {
get sorting() { return sorting.value },
get columnFilters() { return columnFilters.value },
get expanded() { return expanded.value },
},
})
defineExpose({ table })
</script>
<template>
<div class="w-full">
<slot name="filters" :table="table" />
<div class="rounded-md border">
<Table>
<TableHeader>
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
<TableHead v-for="header in headerGroup.headers" :key="header.id"
:class="['actions', 'number'].includes(header.column.id) ? 'w-0' : ''">
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header"
:props="header.getContext()" />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<template v-if="table.getRowModel().rows?.length">
<template v-for="row in table.getRowModel().rows" :key="row.id">
<TableRow>
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id"
:class="['actions', 'number'].includes(cell.column.id) ? 'w-0' : ''">
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
</TableCell>
</TableRow>
<TableRow v-if="row.getIsExpanded()">
<TableCell :colspan="row.getAllCells().length">
{{ JSON.stringify(row.original) }}
</TableCell>
</TableRow>
</template>
</template>
<TableRow v-else>
<TableCell :colspan="allColumns.length" class="h-24 text-center">
<Empty class="from-muted/50 to-background h-full bg-linear-to-b from-30%">
<EmptyHeader>
<EmptyTitle>Tidak ada data ditemukan</EmptyTitle>
<EmptyDescription>
Sesuaikan filter atau kata kunci pencarian, lalu coba lagi.
</EmptyDescription>
</EmptyHeader>
</Empty>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
<DataTablePagination :table="table" />
</div>
</template>