# 2026-07-24 — DataTable Admin Jenis Bisnis

## Goal
Build reusable DataTable with pagination, sorting, filtering, and actions for admin "Jenis Bisnis" (Business Types) page using Nuxt 4 + shadcn-vue.

## Installed Components
- `@shadcn-vue/select`, `@shadcn-vue/tooltip`, `@shadcn-vue/badge`

## Current Files
| File | Path | Role |
|---|---|---|
| **columns.ts** | `app/pages/admin/business-types/columns.ts` | BusinessType interface, column defs, filter functions, action buttons (Edit/Delete) — co-located with page |
| **index.vue** | `app/pages/admin/business-types/index.vue` | Page: fetches data, passes columns to DataTable, filter UI via `#filters` slot |
| **DataTable.vue** | `app/components/data-table/DataTable.vue` | Reusable generic DataTable — accepts `columns` + `data` props, auto-prepends `#` row-number column, `filters` slot, includes pagination |
| **DataTablePagination.vue** | `app/components/data-table/DataTablePagination.vue` | Pagination sub-component (page size selector, prev/next/first/last buttons) |
| **DataTable.vue** (old) | `app/components/DataTable.vue` | Legacy DataTable with hardcoded columns, drag-and-drop, tabs — NOT used by business-types |

## Refactoring Done
- `columns.ts` moved from `app/components/business-types/columns.ts` → `app/pages/admin/business-types/columns.ts` (co-located with page)
- `index.vue` imports columns via relative path `./columns`
- Actions column simplified: removed Copy (ID) and Eye (Lihat) buttons → now only Edit (Pencil, yellow) and Delete (Trash2, destructive) with Tooltip — no click handlers wired yet

## Bugs Fixed

### 1. Numbering salah di page 2 (page 1: 1-10, page 2: 21-30)
**Root cause:** Formula `pageIndex * pageSize + row.index + 1` double-counts. TanStack Table's `getPaginationRowModel` does NOT reset `row.index` — it stays as the global index from sorted/filtered model. So `row.index` is already `10-19` on page 2, and `1*10 + 10 = 20` gives 21-30.

**Fix:** Use `row.index + 1` directly in `DataTable.vue`:
```ts
cell: ({ row }) => {
  return h('div', { class: 'text-center' }, `${row.index + 1}`)
}
```

### 2. Search filter tidak berfungsi
**Root cause:** `nameAndCodeFilter` menggunakan `row.getValue('name')` dan `row.getValue('code')`, tapi column ID-nya `name_search` (virtual column dengan `accessorFn`), jadi `row.getValue('name')` return `undefined`.

**Fix:** Gunakan `row.original.name` dan `row.original.code` di `columns.ts`:
```ts
const nameAndCodeFilter: FilterFn<BusinessType> = (row, _columnId, filterValue) => {
  const search = (filterValue as string).toLowerCase()
  const name = (row.original.name as string).toLowerCase()
  const code = (row.original.code as string).toLowerCase()
  return name.includes(search) || code.includes(search)
}
```

### 3. Data inactive (is_active: 0) tidak tampil / semua tampil "Aktif"
**Root cause:** API return `is_active` sebagai number `0`/`1`, tapi cell dan filter pakai `!!row.getValue('is_active')`. Operator `!!` pada string `"0"` menghasilkan `true` (non-empty string).

**Fix:** Explicit check untuk number/string/boolean:
```ts
// Cell display
const raw = row.getValue('is_active')
const isActive = raw === true || raw === 1 || raw === '1'

// booleanFilter
const raw = row.original[columnId as keyof BusinessType]
const cellValue = raw === true || raw === 1 || raw === '1'
```

### 4. Select status tidak reflect selection
**Root cause:** `getStatusFilter()` hanya handle boolean dan number, tidak handle string `'true'`/`'false'` dari Select value.

**Fix:**
```ts
function getStatusFilter(table: any) {
  const val = table.getColumn('is_active')?.getFilterValue()
  if (val === true || val === 'true' || val === 1) return 'true'
  if (val === false || val === 'false' || val === 0) return 'false'
  return 'all'
}
```

### 5. Model order salah
**Root cause:** `getPaginationRowModel` di-register SEBELUM `getFilteredRowModel`, jadi pagination jalan duluan sebelum filter.

**Fix:** Reorder di `DataTable.vue`:
```ts
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getExpandedRowModel: getExpandedRowModel(),
```

## Pending
- [ ] Wire `@click` handlers on Edit/Delete action buttons in `columns.ts`
- [ ] Backend: ubah `is_active` default dari `True` → `None` supaya admin page bisa lihat semua data
- [ ] Missing type import: `index.vue` uses `useFetch<BusinessType[]>` tapi `BusinessType` belum di-import (perlu `import { type BusinessType } from './columns'`)

## Backend Note
API `GET /v1/business-types` default `is_active=True` — hanya return data aktif. Perlu ubah backend:
```python
def list_business_types(is_active: bool = None, db: Session = Depends(get_db)):
    return get_business_type_list(db, is_active=is_active)

def get_business_type_list(db: Session, is_active: bool = None):
    query = db.query(BusinessType).filter(BusinessType.deleted_at.is_(None))
    if is_active is not None:
        query = query.filter(BusinessType.is_active == is_active)
    return query.all()
```
Admin page perlu lihat semua data (active + inactive), frontend TanStack Table handle filtering.

## Tech Details
- `useFetch` runs server-side (SSR), so API calls won't appear in browser Network tab
- TanStack Table `row.index` is global index (not page-relative) after pagination
- Filter values stored as strings (`'true'`/`'false'`/`''`), mapped to/from boolean via `getStatusFilter()`
- `name_search` is virtual column with `accessorFn: row => ${row.name} ${row.code}` for combined search
- `app/components/DataTable.vue` is legacy/unused — do not confuse with `app/components/data-table/DataTable.vue`
