146 lines
4.6 KiB
TypeScript
146 lines
4.6 KiB
TypeScript
import type { ColumnDef, FilterFn } from '@tanstack/vue-table'
|
|
import { ArrowUpDown, Pencil, Trash2 } from '@lucide/vue'
|
|
import { h } from 'vue'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Switch } from '@/components/ui/switch'
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from '@/components/ui/tooltip'
|
|
|
|
export interface Plan {
|
|
id: string
|
|
name: string
|
|
description: string | null
|
|
price: number
|
|
limits: Record<string, any> | null
|
|
is_active: boolean | number
|
|
}
|
|
|
|
const nameFilter: FilterFn<Plan> = (row, _columnId, filterValue) => {
|
|
const search = (filterValue as string).toLowerCase()
|
|
const name = (row.original.name as string).toLowerCase()
|
|
return name.includes(search)
|
|
}
|
|
|
|
const booleanFilter: FilterFn<Plan> = (row, columnId, filterValue) => {
|
|
if (!filterValue || filterValue === '') return true
|
|
const raw = row.original[columnId as keyof Plan]
|
|
const cellValue = raw === true || raw === 1 || raw === '1'
|
|
const filterBool = filterValue === 'true'
|
|
return cellValue === filterBool
|
|
}
|
|
|
|
export function getColumns(callbacks: {
|
|
onEdit: (plan: Plan) => void
|
|
onDelete: (plan: Plan) => void
|
|
onToggleStatus: (plan: Plan, value: boolean) => void
|
|
}): ColumnDef<Plan>[] {
|
|
return [
|
|
{
|
|
id: 'name_search',
|
|
accessorFn: row => row.name,
|
|
header: ({ column }) => {
|
|
return h(Button, {
|
|
variant: 'ghost',
|
|
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
|
|
}, () => ['Nama', h(ArrowUpDown, { class: 'ml-2 h-4 w-4' })])
|
|
},
|
|
cell: ({ row }) => h('div', { class: 'font-medium' }, row.original.name),
|
|
filterFn: nameFilter,
|
|
enableHiding: false,
|
|
},
|
|
{
|
|
accessorKey: 'price',
|
|
header: ({ column }) => {
|
|
return h(Button, {
|
|
variant: 'ghost',
|
|
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
|
|
}, () => ['Harga', h(ArrowUpDown, { class: 'ml-2 h-4 w-4' })])
|
|
},
|
|
cell: ({ row }) => {
|
|
const val = row.original.price
|
|
const formatted = val.toLocaleString('id-ID')
|
|
return h('div', { class: 'flex items-center gap-1 text-sm' }, [
|
|
h('span', { class: 'text-muted-foreground' }, 'Rp'),
|
|
h('span', { class: 'font-medium' }, formatted),
|
|
])
|
|
},
|
|
},
|
|
{
|
|
accessorKey: 'description',
|
|
header: 'Deskripsi',
|
|
cell: ({ row }) => {
|
|
const desc = row.getValue('description') as string | null
|
|
return h('div', { class: 'text-muted-foreground truncate max-w-[300px]' }, desc ?? '-')
|
|
},
|
|
},
|
|
{
|
|
accessorKey: 'is_active',
|
|
header: 'Status',
|
|
filterFn: booleanFilter,
|
|
cell: ({ row }) => {
|
|
const raw = row.getValue('is_active')
|
|
const isActive = raw === true || raw === 1 || raw === '1'
|
|
const plan = row.original
|
|
return h('div', { class: 'flex items-center gap-2' }, [
|
|
h(Switch, {
|
|
modelValue: isActive,
|
|
'onUpdate:modelValue': (val: boolean) => callbacks.onToggleStatus(plan, val),
|
|
}),
|
|
h(Badge, {
|
|
variant: 'outline',
|
|
class: isActive
|
|
? 'border-emerald-500 text-emerald-600 bg-emerald-50'
|
|
: 'border-red-500 text-red-600 bg-red-50',
|
|
}, () => isActive ? 'Aktif' : 'Nonaktif'),
|
|
])
|
|
},
|
|
},
|
|
{
|
|
id: 'actions',
|
|
header: ' ',
|
|
size: 0,
|
|
minSize: 0,
|
|
enableHiding: false,
|
|
cell: ({ row }) => {
|
|
const plan = row.original
|
|
|
|
return h(TooltipProvider, null, () =>
|
|
h('div', { class: 'flex items-center gap-1' }, [
|
|
h(Tooltip, null, () => [
|
|
h(TooltipTrigger, { asChild: true }, () =>
|
|
h(Button, {
|
|
variant: 'ghost',
|
|
size: 'icon',
|
|
class: 'h-8 w-8 text-yellow-500',
|
|
onClick: () => callbacks.onEdit(plan),
|
|
}, () =>
|
|
h(Pencil, { class: 'h-4 w-4' })
|
|
)
|
|
),
|
|
h(TooltipContent, null, () => 'Edit'),
|
|
]),
|
|
h(Tooltip, null, () => [
|
|
h(TooltipTrigger, { asChild: true }, () =>
|
|
h(Button, {
|
|
variant: 'ghost',
|
|
size: 'icon',
|
|
class: 'h-8 w-8 text-destructive',
|
|
onClick: () => callbacks.onDelete(plan),
|
|
}, () =>
|
|
h(Trash2, { class: 'h-4 w-4' })
|
|
)
|
|
),
|
|
h(TooltipContent, null, () => 'Hapus'),
|
|
]),
|
|
])
|
|
)
|
|
},
|
|
},
|
|
]
|
|
}
|