From 986d2c586e51bd8bf2ca6d04f1e4c55aa450856e Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 13 Feb 2023 10:43:09 -0900 Subject: [PATCH] refactor: editor page (#276) --- frontend/components/Form/DatePicker.vue | 6 +- frontend/lib/api/base/base-api.ts | 8 +- frontend/pages/item/[id]/index/edit.vue | 102 +++++++++++++++++------- 3 files changed, 82 insertions(+), 34 deletions(-) diff --git a/frontend/components/Form/DatePicker.vue b/frontend/components/Form/DatePicker.vue index d01381f..71d7c08 100644 --- a/frontend/components/Form/DatePicker.vue +++ b/frontend/components/Form/DatePicker.vue @@ -18,7 +18,7 @@ const props = defineProps({ modelValue: { - type: Date, + type: Date as () => Date | string, required: false, default: null, }, @@ -32,6 +32,10 @@ get() { // return modelValue as string as YYYY-MM-DD or null if (validDate(props.modelValue)) { + if (typeof props.modelValue === "string") { + return props.modelValue; + } + return props.modelValue ? props.modelValue.toISOString().split("T")[0] : null; } diff --git a/frontend/lib/api/base/base-api.ts b/frontend/lib/api/base/base-api.ts index 089cfe6..184e4c7 100644 --- a/frontend/lib/api/base/base-api.ts +++ b/frontend/lib/api/base/base-api.ts @@ -5,7 +5,7 @@ const ZERO_DATE = "0001-01-01T00:00:00Z"; type BaseApiType = { createdAt: string; updatedAt: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; }; @@ -26,7 +26,11 @@ export function parseDate(obj: T, keys: Array = []): T { return; } - result[key] = new Date(result[key]); + // transform string to ensure dates are parsed as UTC dates instead of + // localized time stamps + const asStr = result[key] as string; + const cleaned = asStr.replaceAll("-", "/").split("T")[0]; + result[key] = new Date(cleaned); } }); diff --git a/frontend/pages/item/[id]/index/edit.vue b/frontend/pages/item/[id]/index/edit.vue index 0cb1267..f40ce91 100644 --- a/frontend/pages/item/[id]/index/edit.vue +++ b/frontend/pages/item/[id]/index/edit.vue @@ -23,7 +23,7 @@ const labelStore = useLabelStore(); const labels = computed(() => labelStore.labels); - const { data: item, refresh } = useAsyncData(async () => { + const { data: nullableItem, refresh } = useAsyncData(async () => { const { data, error } = await api.items.get(itemId.value); if (error) { toast.error("Failed to load item"); @@ -31,7 +31,8 @@ return; } - if (locations) { + if (locations && data.location?.id) { + // @ts-expect-error - we know the locations is valid const location = locations.value.find(l => l.id === data.location.id); if (location) { data.location = location; @@ -45,11 +46,18 @@ return data; }); + const item = computed(() => nullableItem.value as ItemOut); + onMounted(() => { refresh(); }); async function saveItem() { + if (!item.value.location?.id) { + toast.error("Failed to save item: no location selected"); + return; + } + const payload: ItemUpdate = { ...item.value, locationId: item.value.location?.id, @@ -68,12 +76,47 @@ navigateTo("/item/" + itemId.value); } - type FormField = { - type: "text" | "textarea" | "select" | "date" | "label" | "location" | "number" | "checkbox"; + type StringKeys = { [k in keyof T]: T[k] extends string ? k : never }[keyof T]; + type OnlyString = { [k in StringKeys]: string }; + + type NumberKeys = { [k in keyof T]: T[k] extends number ? k : never }[keyof T]; + type OnlyNumber = { [k in NumberKeys]: number }; + + type TextFormField = { + type: "text" | "textarea"; label: string; - ref: keyof ItemOut; + // key of ItemOut where the value is a string + ref: keyof OnlyString; }; + type NumberFormField = { + type: "number"; + label: string; + ref: keyof OnlyNumber | keyof OnlyString; + }; + + // https://stackoverflow.com/questions/50851263/how-do-i-require-a-keyof-to-be-for-a-property-of-a-specific-type + // I don't know why typescript can't just be normal + type BooleanKeys = { [k in keyof T]: T[k] extends boolean ? k : never }[keyof T]; + type OnlyBoolean = { [k in BooleanKeys]: boolean }; + + interface BoolFormField { + type: "checkbox"; + label: string; + ref: keyof OnlyBoolean; + } + + type DateKeys = { [k in keyof T]: T[k] extends Date | string ? k : never }[keyof T]; + type OnlyDate = { [k in DateKeys]: Date | string }; + + type DateFormField = { + type: "date"; + label: string; + ref: keyof OnlyDate; + }; + + type FormField = TextFormField | BoolFormField | DateFormField | NumberFormField; + const mainFields: FormField[] = [ { type: "text", @@ -163,7 +206,7 @@ }, ]; - const soldFields = [ + const soldFields: FormField[] = [ { type: "text", label: "Sold To", @@ -194,7 +237,7 @@ refAttachmentInput.value.click(); } - function uploadImage(e: InputEvent) { + function uploadImage(e: Event) { const files = (e.target as HTMLInputElement).files; if (!files || !files.item(0)) { return; @@ -273,7 +316,7 @@ editState.type = attachment.type; editState.modal = true; - editState.obj = attachmentOpts.find(o => o.value === attachment.type); + editState.obj = attachmentOpts.find(o => o.value === attachment.type) || attachmentOpts[0]; } async function updateAttachment() { @@ -337,30 +380,27 @@
-
- - Edit - +
-
-
+ -
+