Files
homebox/frontend/components/Item/View/table/columns.ts
2025-11-18 22:31:30 +00:00

283 lines
8.6 KiB
TypeScript

import type { Column, ColumnDef } from "@tanstack/vue-table";
import { h } from "vue";
import DropdownAction from "./data-table-dropdown.vue";
import { ArrowDown, ArrowUpDown, Check, X } from "lucide-vue-next";
import Button from "~/components/ui/button/Button.vue";
import Checkbox from "~/components/Form/Checkbox.vue";
import type { ItemSummary } from "~/lib/api/types/data-contracts";
import Currency from "~/components/global/Currency.vue";
import DateTime from "~/components/global/DateTime.vue";
import { cn } from "~/lib/utils";
/**
* Create columns with i18n support.
* Pass `t` from useI18n() when creating the columns in your component.
*/
export function makeColumns({
t,
refresh,
disableSort,
}: {
t: (key: string) => string;
refresh?: () => void;
disableSort?: boolean;
}): ColumnDef<ItemSummary>[] {
const sortable = (column: Column<ItemSummary, unknown>, key: string) => {
const sortState = column.getIsSorted(); // 'asc' | 'desc' | false
if (!sortState) {
// show the neutral up/down icon when not sorted
return [t(key), h(ArrowUpDown, { class: cn(["ml-2 h-4 w-4 opacity-40", disableSort && "opacity-0"]) })];
}
// show a single arrow that points up for asc (rotate-180) and down for desc
return [
t(key),
h(ArrowDown, {
class: cn([
"ml-2 h-4 w-4 transition-transform opacity-100",
sortState === "asc" ? "rotate-180" : "",
disableSort && "opacity-0",
]),
}),
];
};
return [
{
id: "select",
header: ({ table }) =>
h(Checkbox, {
modelValue: table.getIsAllPageRowsSelected()
? true
: table.getSelectedRowModel().rows.length > 0
? ("indeterminate" as unknown as boolean) // :)
: false,
"onUpdate:modelValue": (value: boolean) => table.toggleAllPageRowsSelected(!!value),
ariaLabel: t("components.item.view.selectable.select_all"),
}),
cell: ({ row }) =>
h(Checkbox, {
modelValue: row.getIsSelected(),
"onUpdate:modelValue": (value: boolean) => row.toggleSelected(!!value),
ariaLabel: t("components.item.view.selectable.select_row"),
}),
enableHiding: false,
},
{
id: "assetId",
accessorKey: "assetId",
header: ({ column }) =>
h(
Button,
{
variant: "ghost",
onClick: () => !disableSort && column.toggleSorting(column.getIsSorted() === "asc"),
},
() => sortable(column, "items.asset_id")
),
cell: ({ row }) => h("div", { class: "text-sm" }, String(row.getValue("assetId") ?? "")),
},
{
id: "name",
accessorKey: "name",
header: ({ column }) =>
h(
Button,
{
variant: "ghost",
onClick: () => !disableSort && column.toggleSorting(column.getIsSorted() === "asc"),
},
() => sortable(column, "items.name")
),
cell: ({ row }) => h("span", { class: "text-sm font-medium" }, row.getValue("name")),
},
{
id: "quantity",
accessorKey: "quantity",
header: ({ column }) =>
h(
Button,
{
variant: "ghost",
onClick: () => !disableSort && column.toggleSorting(column.getIsSorted() === "asc"),
},
() => sortable(column, "items.quantity")
),
cell: ({ row }) => h("div", { class: "text-center" }, String(row.getValue("quantity") ?? "")),
},
{
id: "insured",
accessorKey: "insured",
header: ({ column }) =>
h(
Button,
{
variant: "ghost",
onClick: () => !disableSort && column.toggleSorting(column.getIsSorted() === "asc"),
},
() => sortable(column, "items.insured")
),
cell: ({ row }) => {
const val = row.getValue("insured");
return h(
"div",
{ class: "block mx-auto w-min" },
val ? h(Check, { class: "h-4 w-4 text-green-500" }) : h(X, { class: "h-4 w-4 text-destructive" })
);
},
},
{
id: "purchasePrice",
accessorKey: "purchasePrice",
header: ({ column }) =>
h(
Button,
{
variant: "ghost",
onClick: () => !disableSort && column.toggleSorting(column.getIsSorted() === "asc"),
},
() => sortable(column, "items.purchase_price")
),
cell: ({ row }) =>
h("div", { class: "text-center" }, h(Currency, { amount: Number(row.getValue("purchasePrice")) })),
},
{
id: "location",
accessorKey: "location",
header: ({ column }) =>
h(
Button,
{
variant: "ghost",
onClick: () => !disableSort && column.toggleSorting(column.getIsSorted() === "asc"),
},
() => sortable(column, "items.location")
),
cell: ({ row }) => {
const loc = (row.original as ItemSummary).location as { id: string; name: string } | null;
if (loc) {
return h("a", { href: `/location/${loc.id}`, class: "hover:underline text-sm" }, loc.name);
}
return h("div", { class: "text-sm text-muted-foreground" }, "");
},
},
{
id: "archived",
accessorKey: "archived",
header: ({ column }) =>
h(
Button,
{
variant: "ghost",
onClick: () => !disableSort && column.toggleSorting(column.getIsSorted() === "asc"),
},
() => sortable(column, "items.archived")
),
cell: ({ row }) => {
const val = row.getValue("archived");
return h(
"div",
{ class: "block mx-auto w-min" },
val ? h(Check, { class: "h-4 w-4 text-green-500" }) : h(X, { class: "h-4 w-4 text-destructive" })
);
},
},
{
id: "createdAt",
accessorKey: "createdAt",
header: ({ column }) =>
h(
Button,
{
variant: "ghost",
onClick: () => !disableSort && column.toggleSorting(column.getIsSorted() === "asc"),
},
() => sortable(column, "items.created_at")
),
cell: ({ row }) =>
h(
"div",
{ class: "text-center text-sm" },
h(DateTime, { date: row.getValue("createdAt") as Date, datetimeType: "date" })
),
},
{
id: "updatedAt",
accessorKey: "updatedAt",
header: ({ column }) =>
h(
Button,
{
variant: "ghost",
onClick: () => !disableSort && column.toggleSorting(column.getIsSorted() === "asc"),
},
() => sortable(column, "items.updated_at")
),
cell: ({ row }) =>
h(
"div",
{ class: "text-center text-sm" },
h(DateTime, { date: row.getValue("updatedAt") as Date, datetimeType: "date" })
),
},
{
id: "actions",
enableHiding: false,
header: ({ table }) => {
const selectedCount = table.getSelectedRowModel().rows.length;
return h(
"div",
{
class: [
"relative inline-flex items-center",
selectedCount === 0 ? "opacity-50 pointer-events-none" : "",
].join(" "),
},
[
h(DropdownAction, {
multi: {
items: table.getSelectedRowModel().rows,
columns: table.getAllColumns(),
},
onExpand: () => {
table.getSelectedRowModel().rows.forEach(row => row.toggleExpanded());
},
view: "table",
onRefresh: () => refresh?.(),
table,
}),
selectedCount > 0 &&
h(
"span",
{
class: "-right-1 -top-1 absolute flex size-4",
},
h(
"span",
{
class:
"relative flex size-4 items-center justify-center rounded-full bg-primary p-1 text-primary-foreground text-xs pointer-events-none whitespace-nowrap",
},
String(selectedCount)
)
),
]
);
},
cell: ({ row, table }) => {
const item = row.original;
return h(
"div",
{ class: "relative" },
h(DropdownAction, {
item,
onExpand: row.toggleExpanded,
view: "table",
onRefresh: () => refresh?.(),
table,
})
);
},
},
];
}