feat: implement selectable view + sortable table (#264)

This commit is contained in:
Hayden 2023-02-05 14:00:33 -09:00 committed by GitHub
parent f36f17b57d
commit bd933af874
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 68 additions and 72 deletions

View file

@ -6,20 +6,31 @@
<th <th
v-for="h in headers" v-for="h in headers"
:key="h.value" :key="h.value"
class="text-no-transform text-sm bg-neutral text-neutral-content" class="text-no-transform text-sm bg-neutral text-neutral-content cursor-pointer"
:class="{ @click="sortBy(h.value)"
'text-center': h.align === 'center',
'text-right': h.align === 'right',
'text-left': h.align === 'left',
}"
> >
<template v-if="typeof h === 'string'">{{ h }}</template> <div
<template v-else>{{ h.text }}</template> 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> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(d, i) in data" :key="i" class="hover cursor-pointer" @click="navigateTo(`/item/${d.id}`)"> <tr v-for="(d, i) in data" :key="d.id" class="hover cursor-pointer" @click="navigateTo(`/item/${d.id}`)">
<td <td
v-for="h in headers" v-for="h in headers"
:key="`${h.value}-${i}`" :key="`${h.value}-${i}`"
@ -49,7 +60,7 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div class="border-t p-3 justify-end flex"> <div v-if="hasPrev || hasNext" class="border-t p-3 justify-end flex">
<div class="btn-group"> <div class="btn-group">
<button :disabled="!hasPrev" class="btn btn-sm" @click="prev()">«</button> <button :disabled="!hasPrev" class="btn btn-sm" @click="prev()">«</button>
<button class="btn btn-sm">Page {{ pagination.page }}</button> <button class="btn btn-sm">Page {{ pagination.page }}</button>
@ -80,7 +91,6 @@
}); });
const pagination = reactive({ const pagination = reactive({
sortBy: sortByProperty.value,
descending: false, descending: false,
page: 1, page: 1,
rowsPerPage: 10, rowsPerPage: 10,
@ -97,20 +107,50 @@
return pagination.page > 1; 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) {
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[]>(() => { const data = computed<TableData[]>(() => {
// sort by property // sort by property
let data = [...props.items].sort((a, b) => { let data = [...props.items].sort(itemSort);
const aLower = a[sortByProperty.value]?.toLowerCase();
const bLower = b[sortByProperty.value]?.toLowerCase();
if (aLower < bLower) {
return -1;
}
if (aLower > bLower) {
return 1;
}
return 0;
});
// sort descending // sort descending
if (pagination.descending) { if (pagination.descending) {

View file

@ -82,20 +82,7 @@
<Subtitle> Recently Added </Subtitle> <Subtitle> Recently Added </Subtitle>
<BaseCard v-if="breakpoints.lg"> <BaseCard v-if="breakpoints.lg">
<Table :headers="itemTable.headers" :data="itemTable.items"> <ItemViewTable :items="itemTable.items" />
<template #cell-warranty="{ item }">
<Icon v-if="item.warranty" 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>
<template #cell-purchasePrice="{ item }">
<Currency :amount="item.purchasePrice" />
</template>
<template #cell-location_Name="{ item }">
<NuxtLink class="badge badge-sm badge-primary p-3" :to="`/location/${item.location.id}`">
{{ item.location?.name }}
</NuxtLink>
</template>
</Table>
</BaseCard> </BaseCard>
<div v-else class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div v-else class="grid grid-cols-1 md:grid-cols-2 gap-4">
<ItemCard v-for="item in itemTable.items" :key="item.id" :item="item" /> <ItemCard v-for="item in itemTable.items" :key="item.id" :item="item" />

View file

@ -1,5 +1,3 @@
import { TableHeader } from "~~/components/global/Table.types";
import { UserClient } from "~~/lib/api/user"; import { UserClient } from "~~/lib/api/user";
export function itemsTable(api: UserClient) { export function itemsTable(api: UserClient) {
@ -11,31 +9,8 @@ export function itemsTable(api: UserClient) {
return data.items; return data.items;
}); });
const headers = [
{
text: "Name",
sortable: true,
value: "name",
},
{
text: "Location",
value: "location.name",
},
{
text: "Warranty",
value: "warranty",
align: "center",
},
{
text: "Price",
value: "purchasePrice",
align: "center",
},
] as TableHeader[];
return computed(() => { return computed(() => {
return { return {
headers,
items: items.value || [], items: items.value || [],
}; };
}); });

View file

@ -518,11 +518,8 @@
</div> </div>
</section> </section>
<section v-if="!hasNested" class="my-6"> <section v-if="!hasNested && item.children.length > 0" class="my-6">
<BaseSectionHeader v-if="item && item.children && item.children.length > 0"> Child Items </BaseSectionHeader> <ItemViewSelectable :items="item.children" />
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<ItemCard v-for="child in item.children" :key="child.id" :item="child" />
</div>
</section> </section>
</BaseContainer> </BaseContainer>
</template> </template>

View file

@ -156,11 +156,8 @@
<DetailsSection :details="details" /> <DetailsSection :details="details" />
</BaseCard> </BaseCard>
<section v-if="label"> <section v-if="label && label.items && label.items.length > 0">
<BaseSectionHeader class="mb-5"> Items </BaseSectionHeader> <ItemViewSelectable :items="label.items" />
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<ItemCard v-for="item in label.items" :key="item.id" :item="item" />
</div>
</section> </section>
</BaseContainer> </BaseContainer>
</template> </template>