137 lines
4.3 KiB
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>
|