mirror of
https://github.com/hay-kot/homebox.git
synced 2025-07-26 20:40:26 +00:00
feat: items-editor (#5)
* format readme * update logo * format html * add logo to docs * repository for document and document tokens * add attachments type and repository * autogenerate types via scripts * use autogenerated types * attachment type updates * add insured and quantity fields for items * implement HasID interface for entities * implement label updates for items * implement service update method * WIP item update client side actions * check err on attachment * finish types for basic items editor * remove unused var * house keeping
This commit is contained in:
parent
fbc364dcd2
commit
95ab14b866
125 changed files with 15626 additions and 1791 deletions
|
@ -1,4 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { ItemUpdate } from "~~/lib/api/types/data-contracts";
|
||||
|
||||
definePageMeta({
|
||||
layout: "home",
|
||||
});
|
||||
|
@ -6,9 +8,20 @@
|
|||
const route = useRoute();
|
||||
const api = useUserApi();
|
||||
const toast = useNotifier();
|
||||
const preferences = useViewPreferences();
|
||||
|
||||
const itemId = computed<string>(() => route.params.id as string);
|
||||
|
||||
const { data: locations } = useAsyncData(async () => {
|
||||
const { data } = await api.locations.getAll();
|
||||
return data.items;
|
||||
});
|
||||
|
||||
const { data: labels } = useAsyncData(async () => {
|
||||
const { data } = await api.labels.getAll();
|
||||
return data.items;
|
||||
});
|
||||
|
||||
const { data: item } = useAsyncData(async () => {
|
||||
const { data, error } = await api.items.get(itemId.value);
|
||||
if (error) {
|
||||
|
@ -16,11 +29,30 @@
|
|||
navigateTo("/home");
|
||||
return;
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
async function saveItem() {
|
||||
const payload: ItemUpdate = {
|
||||
...item.value,
|
||||
locationId: item.value.location?.id,
|
||||
labelIds: item.value.labels.map(l => l.id),
|
||||
};
|
||||
|
||||
const { error } = await api.items.update(itemId.value, payload);
|
||||
|
||||
if (error) {
|
||||
toast.error("Failed to save item");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success("Item saved");
|
||||
navigateTo("/item/" + itemId.value);
|
||||
}
|
||||
|
||||
type FormField = {
|
||||
type: "text" | "textarea" | "select" | "date";
|
||||
type: "text" | "textarea" | "select" | "date" | "label" | "location" | "number" | "checkbox";
|
||||
label: string;
|
||||
ref: string;
|
||||
};
|
||||
|
@ -31,6 +63,11 @@
|
|||
label: "Name",
|
||||
ref: "name",
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
label: "Quantity",
|
||||
ref: "quantity",
|
||||
},
|
||||
{
|
||||
type: "textarea",
|
||||
label: "Description",
|
||||
|
@ -56,6 +93,11 @@
|
|||
label: "Notes",
|
||||
ref: "notes",
|
||||
},
|
||||
{
|
||||
type: "checkbox",
|
||||
label: "Insured",
|
||||
ref: "insured",
|
||||
},
|
||||
];
|
||||
|
||||
const purchaseFields: FormField[] = [
|
||||
|
@ -76,6 +118,24 @@
|
|||
},
|
||||
];
|
||||
|
||||
const warrantyFields: FormField[] = [
|
||||
{
|
||||
type: "checkbox",
|
||||
label: "Lifetime Warranty",
|
||||
ref: "lifetimeWarranty",
|
||||
},
|
||||
{
|
||||
type: "date",
|
||||
label: "Warranty Expires",
|
||||
ref: "warrantyExpires",
|
||||
},
|
||||
{
|
||||
type: "textarea",
|
||||
label: "Warranty Notes",
|
||||
ref: "warrantyDetails",
|
||||
},
|
||||
];
|
||||
|
||||
const soldFields = [
|
||||
{
|
||||
type: "text",
|
||||
|
@ -97,51 +157,193 @@
|
|||
|
||||
<template>
|
||||
<BaseContainer v-if="item" class="pb-8">
|
||||
<div class="space-y-4">
|
||||
<div class="overflow-hidden card bg-base-100 shadow-xl sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg font-medium leading-6">Item Details</h3>
|
||||
</div>
|
||||
<div class="border-t border-gray-300 sm:p-0">
|
||||
<div v-for="field in mainFields" :key="field.ref" class="sm:divide-y sm:divide-gray-300 grid grid-cols-1">
|
||||
<div class="pt-2 pb-4 sm:px-6 border-b border-gray-300">
|
||||
<FormTextArea v-if="field.type === 'textarea'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
<FormTextField v-else-if="field.type === 'text'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
<FormDatePicker v-else-if="field.type === 'date'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
</div>
|
||||
<section class="px-3">
|
||||
<div class="space-y-4">
|
||||
<div class="overflow-hidden card bg-base-100 shadow-xl sm:rounded-lg">
|
||||
<BaseSectionHeader v-if="item" class="p-5">
|
||||
<Icon name="mdi-package-variant" class="-mt-1 mr-2 text-gray-600" />
|
||||
<span class="text-gray-600">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
<p class="text-sm text-gray-600 font-bold pb-0 mb-0">Quantity {{ item.quantity }}</p>
|
||||
<template #after>
|
||||
<div class="modal-action mt-3">
|
||||
<div class="mr-auto tooltip" data-tip="Hide the cruft! ">
|
||||
<label class="label cursor-pointer mr-auto">
|
||||
<input v-model="preferences.editorSimpleView" type="checkbox" class="toggle toggle-primary" />
|
||||
<span class="label-text ml-4"> Simple View </span>
|
||||
</label>
|
||||
</div>
|
||||
<BaseButton size="sm" @click="saveItem">
|
||||
<template #icon>
|
||||
<Icon name="mdi-content-save-outline" />
|
||||
</template>
|
||||
Save
|
||||
</BaseButton>
|
||||
</div>
|
||||
</template>
|
||||
</BaseSectionHeader>
|
||||
<div class="px-5 mb-6 grid md:grid-cols-2 gap-4">
|
||||
<FormSelect v-model="item.location" label="Location" :items="locations ?? []" select-first />
|
||||
<FormMultiselect v-model="item.labels" label="Labels" :items="labels ?? []" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg font-medium leading-6">Purchase Details</h3>
|
||||
</div>
|
||||
<div class="border-t border-gray-300 sm:p-0">
|
||||
<div v-for="field in purchaseFields" :key="field.ref" class="sm:divide-y sm:divide-gray-300 grid grid-cols-1">
|
||||
<div class="pt-2 pb-4 sm:px-6 border-b border-gray-300">
|
||||
<FormTextArea v-if="field.type === 'textarea'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
<FormTextField v-else-if="field.type === 'text'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
<FormDatePicker v-else-if="field.type === 'date'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
<div class="border-t border-gray-300 sm:p-0">
|
||||
<div v-for="field in mainFields" :key="field.ref" class="sm:divide-y sm:divide-gray-300 grid grid-cols-1">
|
||||
<div class="pt-2 px-4 pb-4 sm:px-6 border-b border-gray-300">
|
||||
<FormTextArea v-if="field.type === 'textarea'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
<FormTextField
|
||||
v-else-if="field.type === 'text'"
|
||||
v-model="item[field.ref]"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
<FormTextField
|
||||
v-else-if="field.type === 'number'"
|
||||
v-model.number="item[field.ref]"
|
||||
type="number"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
<FormDatePicker
|
||||
v-else-if="field.type === 'date'"
|
||||
v-model="item[field.ref]"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
<FormCheckbox
|
||||
v-else-if="field.type === 'checkbox'"
|
||||
v-model="item[field.ref]"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg font-medium leading-6">Sold Details</h3>
|
||||
<div v-if="!preferences.editorSimpleView" class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg font-medium leading-6">Purchase Details</h3>
|
||||
</div>
|
||||
<div class="border-t border-gray-300 sm:p-0">
|
||||
<div
|
||||
v-for="field in purchaseFields"
|
||||
:key="field.ref"
|
||||
class="sm:divide-y sm:divide-gray-300 grid grid-cols-1"
|
||||
>
|
||||
<div class="pt-2 px-4 pb-4 sm:px-6 border-b border-gray-300">
|
||||
<FormTextArea v-if="field.type === 'textarea'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
<FormTextField
|
||||
v-else-if="field.type === 'text'"
|
||||
v-model="item[field.ref]"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
<FormTextField
|
||||
v-else-if="field.type === 'number'"
|
||||
v-model.number="item[field.ref]"
|
||||
type="number"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
<FormDatePicker
|
||||
v-else-if="field.type === 'date'"
|
||||
v-model="item[field.ref]"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
<FormCheckbox
|
||||
v-else-if="field.type === 'checkbox'"
|
||||
v-model="item[field.ref]"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-300 sm:p-0">
|
||||
<div v-for="field in soldFields" :key="field.ref" class="sm:divide-y sm:divide-gray-300 grid grid-cols-1">
|
||||
<div class="pt-2 pb-4 sm:px-6 border-b border-gray-300">
|
||||
<FormTextArea v-if="field.type === 'textarea'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
<FormTextField v-else-if="field.type === 'text'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
<FormDatePicker v-else-if="field.type === 'date'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
|
||||
<div v-if="!preferences.editorSimpleView" class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg font-medium leading-6">Warranty Details</h3>
|
||||
</div>
|
||||
<div class="border-t border-gray-300 sm:p-0">
|
||||
<div
|
||||
v-for="field in warrantyFields"
|
||||
:key="field.ref"
|
||||
class="sm:divide-y sm:divide-gray-300 grid grid-cols-1"
|
||||
>
|
||||
<div class="pt-2 px-4 pb-4 sm:px-6 border-b border-gray-300">
|
||||
<FormTextArea v-if="field.type === 'textarea'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
<FormTextField
|
||||
v-else-if="field.type === 'text'"
|
||||
v-model="item[field.ref]"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
<FormTextField
|
||||
v-else-if="field.type === 'number'"
|
||||
v-model.number="item[field.ref]"
|
||||
type="number"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
<FormDatePicker
|
||||
v-else-if="field.type === 'date'"
|
||||
v-model="item[field.ref]"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
<FormCheckbox
|
||||
v-else-if="field.type === 'checkbox'"
|
||||
v-model="item[field.ref]"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!preferences.editorSimpleView" class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg font-medium leading-6">Sold Details</h3>
|
||||
</div>
|
||||
<div class="border-t border-gray-300 sm:p-0">
|
||||
<div v-for="field in soldFields" :key="field.ref" class="sm:divide-y sm:divide-gray-300 grid grid-cols-1">
|
||||
<div class="pt-2 pb-4 px-4 sm:px-6 border-b border-gray-300">
|
||||
<FormTextArea v-if="field.type === 'textarea'" v-model="item[field.ref]" :label="field.label" inline />
|
||||
<FormTextField
|
||||
v-else-if="field.type === 'text'"
|
||||
v-model="item[field.ref]"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
<FormTextField
|
||||
v-else-if="field.type === 'number'"
|
||||
v-model.number="item[field.ref]"
|
||||
type="number"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
<FormDatePicker
|
||||
v-else-if="field.type === 'date'"
|
||||
v-model="item[field.ref]"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
<FormCheckbox
|
||||
v-else-if="field.type === 'checkbox'"
|
||||
v-model="item[field.ref]"
|
||||
:label="field.label"
|
||||
inline
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseContainer>
|
||||
</template>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue