finish types for basic items editor

This commit is contained in:
Hayden 2022-09-12 14:26:18 -08:00
parent f432330e50
commit fb0898aa71
15 changed files with 259 additions and 60 deletions

View file

@ -942,7 +942,8 @@ const docTemplate = `{
"type": "string"
},
"purchasePrice": {
"type": "number"
"type": "string",
"example": "0"
},
"purchaseTime": {
"description": "Purchase",
@ -959,7 +960,8 @@ const docTemplate = `{
"type": "string"
},
"soldPrice": {
"type": "number"
"type": "string",
"example": "0"
},
"soldTime": {
"description": "Sold",
@ -1025,7 +1027,8 @@ const docTemplate = `{
"type": "string"
},
"purchasePrice": {
"type": "number"
"type": "string",
"example": "0"
},
"purchaseTime": {
"description": "Purchase",
@ -1042,7 +1045,8 @@ const docTemplate = `{
"type": "string"
},
"soldPrice": {
"type": "number"
"type": "string",
"example": "0"
},
"soldTime": {
"description": "Sold",
@ -1105,7 +1109,8 @@ const docTemplate = `{
"type": "string"
},
"purchasePrice": {
"type": "number"
"type": "string",
"example": "0"
},
"purchaseTime": {
"description": "Purchase",
@ -1122,7 +1127,8 @@ const docTemplate = `{
"type": "string"
},
"soldPrice": {
"type": "number"
"type": "string",
"example": "0"
},
"soldTime": {
"description": "Sold",

View file

@ -934,7 +934,8 @@
"type": "string"
},
"purchasePrice": {
"type": "number"
"type": "string",
"example": "0"
},
"purchaseTime": {
"description": "Purchase",
@ -951,7 +952,8 @@
"type": "string"
},
"soldPrice": {
"type": "number"
"type": "string",
"example": "0"
},
"soldTime": {
"description": "Sold",
@ -1017,7 +1019,8 @@
"type": "string"
},
"purchasePrice": {
"type": "number"
"type": "string",
"example": "0"
},
"purchaseTime": {
"description": "Purchase",
@ -1034,7 +1037,8 @@
"type": "string"
},
"soldPrice": {
"type": "number"
"type": "string",
"example": "0"
},
"soldTime": {
"description": "Sold",
@ -1097,7 +1101,8 @@
"type": "string"
},
"purchasePrice": {
"type": "number"
"type": "string",
"example": "0"
},
"purchaseTime": {
"description": "Purchase",
@ -1114,7 +1119,8 @@
"type": "string"
},
"soldPrice": {
"type": "number"
"type": "string",
"example": "0"
},
"soldTime": {
"description": "Sold",

View file

@ -97,7 +97,8 @@ definitions:
purchaseFrom:
type: string
purchasePrice:
type: number
example: "0"
type: string
purchaseTime:
description: Purchase
type: string
@ -109,7 +110,8 @@ definitions:
soldNotes:
type: string
soldPrice:
type: number
example: "0"
type: string
soldTime:
description: Sold
type: string
@ -154,7 +156,8 @@ definitions:
purchaseFrom:
type: string
purchasePrice:
type: number
example: "0"
type: string
purchaseTime:
description: Purchase
type: string
@ -166,7 +169,8 @@ definitions:
soldNotes:
type: string
soldPrice:
type: number
example: "0"
type: string
soldTime:
description: Sold
type: string
@ -209,7 +213,8 @@ definitions:
purchaseFrom:
type: string
purchasePrice:
type: number
example: "0"
type: string
purchaseTime:
description: Purchase
type: string
@ -221,7 +226,8 @@ definitions:
soldNotes:
type: string
soldPrice:
type: number
example: "0"
type: string
soldTime:
description: Sold
type: string

View file

@ -76,8 +76,10 @@ func (e *ItemsRepository) Update(ctx context.Context, data types.ItemUpdate) (*e
SetSoldNotes(data.SoldNotes).
SetNotes(data.Notes).
SetLifetimeWarranty(data.LifetimeWarranty).
SetInsured(data.Insured).
SetWarrantyExpires(data.WarrantyExpires).
SetWarrantyDetails(data.WarrantyDetails)
SetWarrantyDetails(data.WarrantyDetails).
SetQuantity(data.Quantity)
currentLabels, err := e.db.Item.Query().Where(item.ID(data.ID)).QueryLabel().All(ctx)
if err != nil {

View file

@ -39,6 +39,11 @@ func ToItemSummary(item *ent.Item) *types.ItemSummary {
Quantity: item.Quantity,
Insured: item.Insured,
// Warranty
LifetimeWarranty: item.LifetimeWarranty,
WarrantyExpires: item.WarrantyExpires,
WarrantyDetails: item.WarrantyDetails,
// Edges
Location: location,
Labels: labels,

View file

@ -39,12 +39,12 @@ type ItemUpdate struct {
// Purchase
PurchaseTime time.Time `json:"purchaseTime"`
PurchaseFrom string `json:"purchaseFrom"`
PurchasePrice float64 `json:"purchasePrice"`
PurchasePrice float64 `json:"purchasePrice,string"`
// Sold
SoldTime time.Time `json:"soldTime"`
SoldTo string `json:"soldTo"`
SoldPrice float64 `json:"soldPrice"`
SoldPrice float64 `json:"soldPrice,string"`
SoldNotes string `json:"soldNotes"`
// Extras
@ -78,12 +78,12 @@ type ItemSummary struct {
// Purchase
PurchaseTime time.Time `json:"purchaseTime"`
PurchaseFrom string `json:"purchaseFrom"`
PurchasePrice float64 `json:"purchasePrice"`
PurchasePrice float64 `json:"purchasePrice,string"`
// Sold
SoldTime time.Time `json:"soldTime"`
SoldTo string `json:"soldTo"`
SoldPrice float64 `json:"soldPrice"`
SoldPrice float64 `json:"soldPrice,string"`
SoldNotes string `json:"soldNotes"`
// Extras

View file

@ -0,0 +1,35 @@
<template>
<div v-if="!inline" class="form-control w-full">
<label class="label cursor-pointer">
<span class="label-text"> {{ label }}</span>
<input v-model="value" type="checkbox" class="checkbox" />
</label>
</div>
<div v-else class="label cursor-pointer sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
<label>
<span class="label-text">
{{ label }}
</span>
</label>
<input v-model="value" type="checkbox" class="checkbox" />
</div>
</template>
<script setup lang="ts">
const props = defineProps({
modelValue: {
type: Boolean,
default: false,
},
inline: {
type: Boolean,
default: false,
},
label: {
type: String,
default: "",
},
});
const value = useVModel(props, "modelValue");
</script>

View file

@ -96,9 +96,7 @@
});
function select(e: MouseEvent, day: Date) {
console.log(day);
selected.value = day;
console.log(selected.value);
// @ts-ignore - this is a vue3 bug
e.target.blur();
resetTime();

View file

@ -1,9 +1,9 @@
<template>
<div v-if="!inline" class="form-control">
<div v-if="!inline" class="form-control w-full">
<label class="label">
<span class="label-text">{{ label }}</span>
</label>
<textarea v-model="value" class="textarea textarea-bordered h-24" :placeholder="placeholder" />
<textarea ref="el" v-model="value" class="textarea w-full textarea-bordered h-28" :placeholder="placeholder" />
<label v-if="limit" class="label">
<span class="label-text-alt"></span>
<span class="label-text-alt"> {{ valueLen }}/{{ limit }}</span>
@ -14,10 +14,12 @@
<span class="label-text">{{ label }}</span>
</label>
<textarea
ref="el"
v-model="value"
class="textarea textarea-bordered col-span-3 mt-3 h-24"
class="textarea textarea-bordered w-full col-span-3 mt-3 h-28"
auto-grow
:placeholder="placeholder"
auto-height
/>
</div>
</template>
@ -51,6 +53,19 @@
},
});
const el = ref();
function setHeight() {
el.value.style.height = "auto";
el.value.style.height = el.value.scrollHeight + 5 + "px";
}
onUpdated(() => {
console.log("updated");
if (props.inline) {
setHeight();
}
});
const value = useVModel(props, "modelValue", emit);
const valueLen = computed(() => {
return value.value ? value.value.length : 0;

View file

@ -20,3 +20,16 @@ export function validDate(dt: Date | string | null | undefined): boolean {
return true;
}
export function fmtCurrency(value: number | string, currency = "USD", locale = "en-Us"): string {
if (typeof value === "string") {
value = parseFloat(value);
}
const formatter = new Intl.NumberFormat(locale, {
style: "currency",
currency,
minimumFractionDigits: 2,
});
return formatter.format(value);
}

View file

@ -45,17 +45,13 @@ export class BaseAPI {
*/
dropFields<T>(obj: T, keys: Array<keyof T> = []): T {
const result = { ...obj };
console.log("dropFields", result);
[...keys, "createdAt", "updatedAt"].forEach(key => {
console.log(key);
// @ts-ignore - we are checking for the key above
if (hasKey(result, key)) {
// @ts-ignore - we are guarding against this above
delete result[key];
console.log("dropping", key);
}
});
console.log("dropFields", result);
return result;
}
}

View file

@ -70,7 +70,9 @@ export interface ItemOut {
/** Extras */
notes: string;
purchaseFrom: string;
purchasePrice: number;
/** @example 0 */
purchasePrice: string;
/** Purchase */
purchaseTime: Date;
@ -79,7 +81,9 @@ export interface ItemOut {
/** Identifications */
serialNumber: string;
soldNotes: string;
soldPrice: number;
/** @example 0 */
soldPrice: string;
/** Sold */
soldTime: Date;
@ -108,7 +112,9 @@ export interface ItemSummary {
/** Extras */
notes: string;
purchaseFrom: string;
purchasePrice: number;
/** @example 0 */
purchasePrice: string;
/** Purchase */
purchaseTime: Date;
@ -117,7 +123,9 @@ export interface ItemSummary {
/** Identifications */
serialNumber: string;
soldNotes: string;
soldPrice: number;
/** @example 0 */
soldPrice: string;
/** Sold */
soldTime: Date;
@ -145,7 +153,9 @@ export interface ItemUpdate {
/** Extras */
notes: string;
purchaseFrom: string;
purchasePrice: number;
/** @example 0 */
purchasePrice: string;
/** Purchase */
purchaseTime: Date;
@ -154,7 +164,9 @@ export interface ItemUpdate {
/** Identifications */
serialNumber: string;
soldNotes: string;
soldPrice: number;
/** @example 0 */
soldPrice: string;
/** Sold */
soldTime: Date;

View file

@ -55,7 +55,6 @@
function setFile(e: Event & { target: HTMLInputElement }) {
importCsv.value = e.target.files[0];
console.log("importCsv.value", importCsv.value);
}
const toast = useNotifier();

View file

@ -35,11 +35,6 @@
return;
}
originalItem.value = {
name: data.name,
quantity: data.quantity,
};
return data;
});
@ -50,8 +45,6 @@
labelIds: item.value.labels.map(l => l.id),
};
console.log(payload);
const { error } = await api.items.update(itemId.value, payload);
if (error) {
@ -64,7 +57,7 @@
}
type FormField = {
type: "text" | "textarea" | "select" | "date" | "label" | "location";
type: "text" | "textarea" | "select" | "date" | "label" | "location" | "number" | "checkbox";
label: string;
ref: string;
};
@ -75,6 +68,11 @@
label: "Name",
ref: "name",
},
{
type: "number",
label: "Quantity",
ref: "quantity",
},
{
type: "textarea",
label: "Description",
@ -100,6 +98,11 @@
label: "Notes",
ref: "notes",
},
{
type: "checkbox",
label: "Insured",
ref: "insured",
},
];
const purchaseFields: FormField[] = [
@ -120,6 +123,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",
@ -147,13 +168,10 @@
<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">
{{ originalItem.name }}
{{ item.name }}
</span>
<p class="text-sm text-gray-600 font-bold pb-0 mb-0">Quantity {{ originalItem.quantity }}</p>
<p class="text-sm text-gray-600 font-bold pb-0 mb-0">Quantity {{ item.quantity }}</p>
<template #after>
<div v-if="item.labels && item.labels.length > 0" class="flex flex-wrap gap-3 mt-3">
<LabelChip v-for="label in item.labels" :key="label.id" class="badge-primary" :label="label" />
</div>
<div class="modal-action mt-3">
<div class="mr-auto tooltip" data-tip="Hide the cruft! ">
<label class="label cursor-pointer mr-auto">
@ -170,14 +188,14 @@
</div>
</template>
</BaseSectionHeader>
<div class="px-5 mb-6">
<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 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">
<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'"
@ -185,12 +203,25 @@
: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>
@ -206,7 +237,7 @@
: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">
<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'"
@ -214,12 +245,67 @@
: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">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>
@ -231,7 +317,7 @@
</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">
<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'"
@ -239,12 +325,25 @@
: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>

View file

@ -10,7 +10,7 @@
const itemId = computed<string>(() => route.params.id as string);
const preferences = useViewPreferences();
const { data: item } = useAsyncData(async () => {
const { data: item, refresh } = useAsyncData(itemId.value, async () => {
const { data, error } = await api.items.get(itemId.value);
if (error) {
toast.error("Failed to load item");
@ -20,6 +20,11 @@
return data;
});
// Trigger Refresh on navigate
onMounted(() => {
refresh();
});
const itemSummary = computed(() => {
return {
Description: item.value?.description || "",
@ -63,7 +68,7 @@
const purchaseDetails = computed(() => {
return {
"Purchased From": item.value?.purchaseFrom || "",
"Purchased Price": item.value?.purchasePrice || "",
"Purchased Price": item.value?.purchasePrice ? fmtCurrency(item.value.purchasePrice) : "",
"Purchased At": item.value?.purchaseTime || "",
};
});
@ -79,7 +84,7 @@
const soldDetails = computed(() => {
return {
"Sold To": item.value?.soldTo || "",
"Sold Price": item.value?.soldPrice || "",
"Sold Price": item.value?.soldPrice ? fmtCurrency(item.value.soldPrice) : "",
"Sold At": item.value?.soldTime || "",
};
});
@ -117,7 +122,9 @@
<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>
<p class="text-sm text-gray-600 font-bold pb-0 mb-0">
{{ item.location.name }} - Quantity {{ item.quantity }}
</p>
<template #after>
<div v-if="item.labels && item.labels.length > 0" class="flex flex-wrap gap-3 mt-3">
<LabelChip v-for="label in item.labels" :key="label.id" class="badge-primary" :label="label" />