update item view to use tab-like pages

This commit is contained in:
Hayden 2023-01-18 15:23:56 -09:00
parent 3d9b155fc3
commit 78afc7a1b2
No known key found for this signature in database
GPG key ID: 17CF79474E257545
3 changed files with 122 additions and 92 deletions

View file

@ -30,6 +30,15 @@
refresh(); refresh();
}); });
const lastRoute = ref(route.fullPath);
watchEffect(() => {
if (lastRoute.value.endsWith("edit")) {
refresh();
}
lastRoute.value = route.fullPath;
});
type FilteredAttachments = { type FilteredAttachments = {
attachments: ItemAttachment[]; attachments: ItemAttachment[];
warranty: ItemAttachment[]; warranty: ItemAttachment[];
@ -325,6 +334,36 @@
onClickOutside(refDialogBody, () => { onClickOutside(refDialogBody, () => {
closeDialog(); closeDialog();
}); });
function getQRCodeUrl(): string {
const currentURL = window.location.href;
return `/api/v1/qrcode?data=${encodeURIComponent(currentURL)}&access_token=${api.items.attachmentToken}`;
}
const currentPath = computed(() => {
return route.path;
});
const tabs = computed(() => {
return [
{
id: "details",
name: "Details",
to: `/item/${itemId.value}`,
},
{
id: "log",
name: "Log",
to: `/item/${itemId.value}/log`,
},
{
id: "edit",
name: "Edit",
to: `/item/${itemId.value}/edit`,
},
];
});
</script> </script>
<template> <template>
@ -343,66 +382,78 @@
<img class="max-w-[80vw] max-h-[80vh]" :src="dialoged.src" /> <img class="max-w-[80vw] max-h-[80vh]" :src="dialoged.src" />
</div> </div>
</dialog> </dialog>
<section>
<BaseSectionHeader>
<Icon name="mdi-package-variant" class="mr-2 -mt-1 text-base-content" />
<span class="text-base-content">
{{ item ? item.name : "" }}
</span>
<div v-if="item.parent" class="text-sm breadcrumbs pb-0">
<ul class="text-base-content/70">
<li>
<NuxtLink :to="`/item/${item.parent.id}`"> {{ item.parent.name }}</NuxtLink>
</li>
<li>{{ item.name }}</li>
</ul>
</div>
<template #description>
<div class="flex flex-wrap gap-2 mt-3">
<NuxtLink ref="badge" class="badge p-3" :to="`/location/${item.location.id}`">
<Icon name="heroicons-map-pin" class="mr-2 swap-on"></Icon>
{{ item.location.name }}
</NuxtLink>
<template v-if="item.labels && item.labels.length > 0">
<LabelChip v-for="label in item.labels" :key="label.id" class="badge-primary" :label="label" />
</template>
</div>
</template>
</BaseSectionHeader>
<div class="flex flex-wrap items-center justify-between mb-6">
<div class="tabs">
<NuxtLink
v-for="t in tabs"
:key="t.id"
:to="t.to"
class="tab tab-bordered lg:tab-lg"
:class="`${t.to === currentPath ? 'tab-active' : ''}`"
>
{{ t.name }}
</NuxtLink>
</div>
<BaseButton class="btn btn-sm" @click="deleteItem()">
<Icon name="mdi-delete" class="mr-2" />
Delete
</BaseButton>
</div>
</section>
<section> <section>
<div class="space-y-6"> <div class="space-y-6">
<BaseCard> <BaseCard v-if="!hasNested">
<template #title> <template #title> Details </template>
<BaseSectionHeader>
<Icon name="mdi-package-variant" class="mr-2 -mt-1 text-base-content" />
<span class="text-base-content">
{{ item ? item.name : "" }}
</span>
<div v-if="item.parent" class="text-sm breadcrumbs pb-0">
<ul class="text-base-content/70">
<li>
<NuxtLink :to="`/item/${item.parent.id}`"> {{ item.parent.name }}</NuxtLink>
</li>
<li>{{ item.name }}</li>
</ul>
</div>
<template #description>
<div class="flex flex-wrap gap-2 mt-3">
<NuxtLink ref="badge" class="badge p-3" :to="`/location/${item.location.id}`">
<Icon name="heroicons-map-pin" class="mr-2 swap-on"></Icon>
{{ item.location.name }}
</NuxtLink>
<template v-if="item.labels && item.labels.length > 0">
<LabelChip v-for="label in item.labels" :key="label.id" class="badge-primary" :label="label" />
</template>
</div>
</template>
</BaseSectionHeader>
</template>
<template #title-actions> <template #title-actions>
<div class="flex flex-wrap justify-between items-center mt-2 gap-4"> <div class="flex flex-wrap justify-between items-center mt-2 gap-4">
<label v-if="!hasNested" class="label cursor-pointer"> <label class="label cursor-pointer">
<input v-model="preferences.showEmpty" type="checkbox" class="toggle toggle-primary" /> <input v-model="preferences.showEmpty" type="checkbox" class="toggle toggle-primary" />
<span class="label-text ml-4"> Show Empty </span> <span class="label-text ml-4"> Show Empty </span>
</label> </label>
<div class="flex flex-wrap justify-end gap-2 ml-auto"> <div class="flex flex-wrap justify-end gap-2 ml-auto">
<BaseButton size="sm" :to="`/item/${itemId}/edit`"> <div class="dropdown dropdown-left">
<template #icon> <label tabindex="0" class="btn btn-circle btn-sm">
<Icon name="mdi-pencil" /> <Icon name="mdi-qrcode" />
</template> </label>
Edit <div tabindex="0" class="card compact dropdown-content shadow-lg bg-base-100 rounded-box w-64">
</BaseButton> <div class="card-body">
<BaseButton size="sm" @click="deleteItem"> <img :src="getQRCodeUrl()" />
<template #icon> </div>
<Icon name="mdi-delete" /> </div>
</template> </div>
Delete
</BaseButton>
<BaseButton size="sm" :to="`/item/${itemId}/log`">
<template #icon>
<Icon name="mdi-post" />
</template>
Log
</BaseButton>
</div> </div>
</div> </div>
</template> </template>
<DetailsSection v-if="!hasNested" :details="itemDetails" /> <DetailsSection :details="itemDetails" />
</BaseCard> </BaseCard>
<NuxtPage :item="item" :page-key="itemId" /> <NuxtPage :item="item" :page-key="itemId" />

View file

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ItemAttachment, ItemUpdate } from "~~/lib/api/types/data-contracts"; import { ItemAttachment, ItemField, ItemUpdate } from "~~/lib/api/types/data-contracts";
import { AttachmentTypes } from "~~/lib/api/types/non-generated"; import { AttachmentTypes } from "~~/lib/api/types/non-generated";
import { useLabelStore } from "~~/stores/labels"; import { useLabelStore } from "~~/stores/labels";
import { useLocationStore } from "~~/stores/locations"; import { useLocationStore } from "~~/stores/locations";
@ -196,11 +196,16 @@
function uploadImage(e: InputEvent) { function uploadImage(e: InputEvent) {
const files = (e.target as HTMLInputElement).files; const files = (e.target as HTMLInputElement).files;
if (!files) { if (!files || !files.item(0)) {
return; return;
} }
uploadAttachment([files.item(0)], AttachmentTypes.Attachment); const first = files.item(0);
if (!first) {
return;
}
uploadAttachment([first], AttachmentTypes.Attachment);
} }
const dropPhoto = (files: File[] | null) => uploadAttachment(files, AttachmentTypes.Photo); const dropPhoto = (files: File[] | null) => uploadAttachment(files, AttachmentTypes.Photo);
@ -210,7 +215,7 @@
const dropReceipt = (files: File[] | null) => uploadAttachment(files, AttachmentTypes.Receipt); const dropReceipt = (files: File[] | null) => uploadAttachment(files, AttachmentTypes.Receipt);
async function uploadAttachment(files: File[] | null, type: AttachmentTypes) { async function uploadAttachment(files: File[] | null, type: AttachmentTypes) {
if (!files && files.length === 0) { if (!files || files.length === 0) {
return; return;
} }
@ -295,22 +300,6 @@
toast.success("Attachment updated"); toast.success("Attachment updated");
} }
// Custom Fields
// const fieldTypes = [
// {
// name: "Text",
// value: "text",
// },
// {
// name: "Number",
// value: "number",
// },
// {
// name: "Boolean",
// value: "boolean",
// },
// ];
function addField() { function addField() {
item.value.fields.push({ item.value.fields.push({
id: null, id: null,
@ -320,7 +309,7 @@
numberValue: 0, numberValue: 0,
booleanValue: false, booleanValue: false,
timeValue: null, timeValue: null,
}); } as unknown as ItemField);
} }
const { query, results } = useItemSearch(api, { immediate: false }); const { query, results } = useItemSearch(api, { immediate: false });
@ -328,7 +317,7 @@
</script> </script>
<template> <template>
<BaseContainer v-if="item" class="pb-8"> <div v-if="item" class="pb-8">
<BaseModal v-model="editState.modal"> <BaseModal v-model="editState.modal">
<template #title> Attachment Edit </template> <template #title> Attachment Edit </template>
@ -346,15 +335,11 @@
</div> </div>
</BaseModal> </BaseModal>
<section class="px-3"> <section>
<div class="space-y-4"> <div class="space-y-6">
<div class="card bg-base-100 shadow-xl sm:rounded-lg overflow-visible"> <div class="card bg-base-100 shadow-xl sm:rounded-lg overflow-visible">
<BaseSectionHeader v-if="item" class="p-5"> <BaseSectionHeader v-if="item" class="p-5">
<Icon name="mdi-package-variant" class="-mt-1 mr-2 text-base-content" /> <span class="text-base-content"> Edit </span>
<span class="text-base-content">
{{ item.name }}
</span>
<p class="text-sm text-base-content font-bold pb-0 mb-0">Quantity {{ item.quantity }}</p>
<template #after> <template #after>
<div class="modal-action mt-3"> <div class="modal-action mt-3">
<div class="mr-auto tooltip" data-tip="Hide the cruft! "> <div class="mr-auto tooltip" data-tip="Hide the cruft! ">
@ -632,5 +617,5 @@
</div> </div>
</div> </div>
</section> </section>
</BaseContainer> </div>
</template> </template>

View file

@ -113,20 +113,6 @@
</BaseModal> </BaseModal>
<section class="space-y-6"> <section class="space-y-6">
<div class="flex">
<BaseButton size="sm" @click="$router.go(-1)">
<template #icon>
<Icon name="mdi-arrow-left" class="h-5 w-5" />
</template>
Back
</BaseButton>
<BaseButton class="ml-auto" size="sm" @click="newEntry()">
<template #icon>
<Icon name="mdi-post" />
</template>
Log Maintenance
</BaseButton>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6"> <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<StatCard <StatCard
v-for="stat in stats" v-for="stat in stats"
@ -137,6 +123,14 @@
:type="stat.type" :type="stat.type"
/> />
</div> </div>
<div class="flex">
<BaseButton class="ml-auto" size="sm" @click="newEntry()">
<template #icon>
<Icon name="mdi-post" />
</template>
Log Maintenance
</BaseButton>
</div>
<div class="container space-y-6"> <div class="container space-y-6">
<BaseCard v-for="e in log.entries" :key="e.id"> <BaseCard v-for="e in log.entries" :key="e.id">
<BaseSectionHeader class="p-6 border-b border-b-gray-300"> <BaseSectionHeader class="p-6 border-b border-b-gray-300">