<template> <BaseCard> <table class="table w-full"> <thead> <tr> <th v-for="h in headers" :key="h.value" class="text-no-transform text-sm bg-neutral text-neutral-content cursor-pointer" @click="sortBy(h.value)" > <div class="flex items-center gap-1" :class="{ 'justify-center': h.align === 'center', 'justify-start': h.align === 'right', 'justify-end': h.align === 'left', }" > <template v-if="typeof h === 'string'">{{ h }}</template> <template v-else>{{ h.text }}</template> <div :class="`inline-flex ${sortByProperty === h.value ? '' : 'opacity-0'}`"> <span class="swap swap-rotate" :class="{ 'swap-active': pagination.descending }"> <Icon name="mdi-arrow-down" class="swap-on h-5 w-5" /> <Icon name="mdi-arrow-up" class="swap-off h-5 w-5" /> </span> </div> </div> </th> </tr> </thead> <tbody> <tr v-for="(d, i) in data" :key="d.id" class="hover cursor-pointer" @click="navigateTo(`/item/${d.id}`)"> <td v-for="h in headers" :key="`${h.value}-${i}`" class="bg-base-100" :class="{ 'text-center': h.align === 'center', 'text-right': h.align === 'right', 'text-left': h.align === 'left', }" > <template v-if="cell(h) === 'cell-name'"> <NuxtLink class="hover" :to="`/item/${d.id}`"> {{ d.name }} </NuxtLink> </template> <template v-else-if="cell(h) === 'cell-purchasePrice'"> <Currency :amount="d.purchasePrice" /> </template> <template v-else-if="cell(h) === 'cell-insured'"> <Icon v-if="d.insured" name="mdi-check" class="text-green-500 h-5 w-5" /> <Icon v-else name="mdi-close" class="text-red-500 h-5 w-5" /> </template> <slot v-else :name="cell(h)" v-bind="{ item: d }"> {{ extractValue(d, h.value) }} </slot> </td> </tr> </tbody> </table> <div v-if="hasPrev || hasNext" class="border-t p-3 justify-end flex"> <div class="btn-group"> <button :disabled="!hasPrev" class="btn btn-sm" @click="prev()">«</button> <button class="btn btn-sm">Page {{ pagination.page }}</button> <button :disabled="!hasNext" class="btn btn-sm" @click="next()">»</button> </div> </div> </BaseCard> </template> <script setup lang="ts"> import { TableData, TableHeader } from "./Table.types"; import { ItemSummary } from "~~/lib/api/types/data-contracts"; type Props = { items: ItemSummary[]; }; const props = defineProps<Props>(); const sortByProperty = ref<keyof ItemSummary | "">(""); const headers = computed<TableHeader[]>(() => { return [ { text: "Name", value: "name" }, { text: "Quantity", value: "quantity", align: "center" }, { text: "Insured", value: "insured", align: "center" }, { text: "Price", value: "purchasePrice" }, ] as TableHeader[]; }); const pagination = reactive({ descending: false, page: 1, rowsPerPage: 10, rowsNumber: 0, }); const next = () => pagination.page++; const hasNext = computed<boolean>(() => { return pagination.page * pagination.rowsPerPage < props.items.length; }); const prev = () => pagination.page--; const hasPrev = computed<boolean>(() => { return pagination.page > 1; }); function sortBy(property: keyof ItemSummary) { if (sortByProperty.value === property) { pagination.descending = !pagination.descending; } else { pagination.descending = false; } sortByProperty.value = property; } function extractSortable(item: ItemSummary, property: keyof ItemSummary): string | number | boolean { const value = item[property]; if (typeof value === "string") { // Try parse float const parsed = parseFloat(value); if (!isNaN(parsed)) { return parsed; } return value.toLowerCase(); } if (typeof value !== "number" && typeof value !== "boolean") { return ""; } return value; } function itemSort(a: ItemSummary, b: ItemSummary) { if (!sortByProperty.value) { return 0; } const aLower = extractSortable(a, sortByProperty.value); const bLower = extractSortable(b, sortByProperty.value); if (aLower < bLower) { return -1; } if (aLower > bLower) { return 1; } return 0; } const data = computed<TableData[]>(() => { // sort by property let data = [...props.items].sort(itemSort); // sort descending if (pagination.descending) { data.reverse(); } // paginate const start = (pagination.page - 1) * pagination.rowsPerPage; const end = start + pagination.rowsPerPage; data = data.slice(start, end); return data; }); function extractValue(data: TableData, value: string) { const parts = value.split("."); let current = data; for (const part of parts) { current = current[part]; } return current; } function cell(h: TableHeader) { return `cell-${h.value.replace(".", "_")}`; } </script> <style scoped></style>