Skip to content

Commit

Permalink
Merge pull request #212 from tonyaellie/make-ItemViewTable-headers-cu…
Browse files Browse the repository at this point in the history
…stomisable

make ItemViewTable headers customisable
  • Loading branch information
katosdev authored Sep 13, 2024
2 parents 6800c21 + b18f0c7 commit 405d0c7
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 23 deletions.
2 changes: 1 addition & 1 deletion frontend/components/Base/Card.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="card bg-base-100 shadow-xl sm:rounded-lg">
<div class="card bg-base-100 shadow-xl rounded-lg">
<div v-if="$slots.title" class="px-4 py-5 sm:px-6">
<component :is="collapsable ? 'button' : 'div'" v-on="collapsable ? { click: toggle } : {}">
<h3 class="flex items-center text-lg font-medium leading-6">
Expand Down
2 changes: 2 additions & 0 deletions frontend/components/Item/View/Table.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export type TableHeader = {
value: keyof ItemSummary;
sortable?: boolean;
align?: "left" | "center" | "right";
enabled: boolean;
type?: "price" | "boolean" | "name" | "location" | "date";
};

export type TableData = Record<string, any>;
119 changes: 98 additions & 21 deletions frontend/components/Item/View/Table.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<template>
<BaseCard class="overflow-hidden">
<BaseCard>
<table class="table w-full">
<thead>
<tr>
<th
v-for="h in headers"
v-for="h in headers.filter(h => h.enabled)"
:key="h.value"
class="text-no-transform cursor-pointer bg-neutral text-sm text-neutral-content"
@click="sortBy(h.value)"
Expand Down Expand Up @@ -35,7 +35,7 @@
<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"
v-for="h in headers.filter(h => h.enabled)"
:key="`${h.value}-${i}`"
class="bg-base-100"
:class="{
Expand All @@ -44,27 +44,76 @@
'text-left': h.align === 'left',
}"
>
<template v-if="cell(h) === 'cell-name'">
<template v-if="h.type === 'name'">
<NuxtLink class="hover" :to="`/item/${d.id}`">
{{ d.name }}
</NuxtLink>
</template>
<template v-else-if="cell(h) === 'cell-purchasePrice'">
<template v-else-if="h.type === 'price'">
<Currency :amount="d.purchasePrice" />
</template>
<template v-else-if="cell(h) === 'cell-insured'">
<template v-else-if="h.type === 'boolean'">
<MdiCheck v-if="d.insured" class="inline size-5 text-green-500" />
<MdiClose v-else class="inline size-5 text-red-500" />
</template>
<template v-else-if="h.type === 'location'">
<NuxtLink v-if="d.location" class="hover:link" :to="`/location/${d.location.id}`">
{{ d.location.name }}
</NuxtLink>
</template>
<template v-else-if="h.type === 'date'">
<DateTime :date="d[h.value]" datetime-type="date" />
</template>
<slot v-else :name="cell(h)" v-bind="{ item: d }">
{{ extractValue(d, h.value) }}
</slot>
</td>
</tr>
</tbody>
</table>
<div v-if="items.length > 10" class="flex justify-end gap-3 border-t p-3">
<div class="flex items-center">Rows per page</div>
<div
class="flex items-center justify-end gap-3 border-t p-3"
:class="{
hidden: disableControls,
}"
>
<div class="dropdown dropdown-top dropdown-hover">
<label tabindex="0" class="btn btn-square btn-outline btn-sm m-1">
<MdiTableCog />
</label>
<ul tabindex="0" class="dropdown-content rounded-box flex w-64 flex-col gap-2 bg-base-100 p-2 pl-3 shadow">
<li>Headers:</li>
<li v-for="(h, i) in headers" class="flex flex-row items-center gap-1">
<button
class="btn btn-square btn-ghost btn-xs"
:class="{
'btn-disabled': i === 0,
}"
@click="moveHeader(i, i - 1)"
>
<MdiArrowUp />
</button>
<button
class="btn btn-square btn-ghost btn-xs"
:class="{
'btn-disabled': i === headers.length - 1,
}"
@click="moveHeader(i, i + 1)"
>
<MdiArrowDown />
</button>
<input
:id="h.value"
type="checkbox"
class="checkbox checkbox-primary"
:checked="h.enabled"
@change="toggleHeader(h.value)"
/>
<label class="label-text" :for="h.value"> {{ h.text }} </label>
</li>
</ul>
</div>
<div class="hidden md:block">Rows per page</div>
<select v-model.number="pagination.rowsPerPage" class="select select-primary select-sm">
<option :value="10">10</option>
<option :value="25">25</option>
Expand All @@ -87,25 +136,53 @@
import MdiArrowUp from "~icons/mdi/arrow-up";
import MdiCheck from "~icons/mdi/check";
import MdiClose from "~icons/mdi/close";
import MdiTableCog from "~icons/mdi/table-cog";
type Props = {
items: ItemSummary[];
disableControls?: boolean;
};
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 preferences = useViewPreferences();
const defaultHeaders = [
{ text: "Name", value: "name", enabled: true, type: "name" },
{ text: "Quantity", value: "quantity", align: "center", enabled: true },
{ text: "Insured", value: "insured", align: "center", enabled: true, type: "boolean" },
{ text: "Price", value: "purchasePrice", align: "center", enabled: true, type: "price" },
{ text: "Location", value: "location", align: "center", enabled: false, type: "location" },
{ text: "Archived", value: "archived", align: "center", enabled: false, type: "boolean" },
{ text: "Created At", value: "createdAt", align: "center", enabled: false, type: "date" },
{ text: "Updated At", value: "updatedAt", align: "center", enabled: false, type: "date" },
] satisfies TableHeader[];
const headers = ref<TableHeader[]>(
(preferences.value.tableHeaders ?? []).concat(
defaultHeaders.filter(h => !preferences.value.tableHeaders?.find(h2 => h2.value === h.value))
)
);
console.log(headers.value);
const toggleHeader = (value: string) => {
const header = headers.value.find(h => h.value === value);
if (header) {
header.enabled = !header.enabled; // Toggle the 'enabled' state
}
preferences.value.tableHeaders = headers.value;
};
const moveHeader = (from: number, to: number) => {
const header = headers.value[from];
headers.value.splice(from, 1);
headers.value.splice(to, 0, header);
preferences.value.tableHeaders = headers.value;
};
const pagination = reactive({
descending: false,
page: 1,
Expand Down Expand Up @@ -207,18 +284,18 @@

<style scoped>
:where(.table *:first-child) :where(*:first-child) :where(th, td):first-child {
border-top-left-radius: 0px;
border-top-left-radius: 0.5rem;
}
:where(.table *:first-child) :where(*:first-child) :where(th, td):last-child {
border-top-right-radius: 0px;
border-top-right-radius: 0.5rem;
}
:where(.table *:last-child) :where(*:last-child) :where(th, td):first-child {
border-bottom-left-radius: 0px;
border-bottom-left-radius: 0.5rem;
}
:where(.table *:last-child) :where(*:last-child) :where(th, td):last-child {
border-bottom-right-radius: 0px;
border-bottom-right-radius: 0.5rem;
}
</style>
2 changes: 2 additions & 0 deletions frontend/composables/use-preferences.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Ref } from "vue";
import type { TableHeader } from "components/Item/View/Table.types";
import type { DaisyTheme } from "~~/lib/data/themes";

export type ViewType = "table" | "card" | "tree";
Expand All @@ -10,6 +11,7 @@ export type LocationViewPreferences = {
itemDisplayView: ViewType;
theme: DaisyTheme;
itemsPerTablePage: number;
tableHeaders?: TableHeader[];
displayHeaderDecor: boolean;
language?: string;
};
Expand Down
2 changes: 1 addition & 1 deletion frontend/pages/home/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<Subtitle> Recently Added </Subtitle>

<BaseCard v-if="breakpoints.lg">
<ItemViewTable :items="itemTable.items" />
<ItemViewTable :items="itemTable.items" disable-controls />
</BaseCard>
<div v-else class="grid grid-cols-1 gap-4 md:grid-cols-2">
<ItemCard v-for="item in itemTable.items" :key="item.id" :item="item" />
Expand Down

0 comments on commit 405d0c7

Please sign in to comment.