web/app/pages/admin/plans/columns.ts

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'),
]),
])
)
},
},
]
}