refactor: editor page (#276)

This commit is contained in:
Hayden 2023-02-13 10:43:09 -09:00 committed by GitHub
parent 9361997a42
commit 986d2c586e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 82 additions and 34 deletions

View file

@ -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;
}

View file

@ -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<T>(obj: T, keys: Array<keyof T> = []): 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);
}
});

View file

@ -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<ItemOut>(() => 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<T> = { [k in keyof T]: T[k] extends string ? k : never }[keyof T];
type OnlyString<T> = { [k in StringKeys<T>]: string };
type NumberKeys<T> = { [k in keyof T]: T[k] extends number ? k : never }[keyof T];
type OnlyNumber<T> = { [k in NumberKeys<T>]: number };
type TextFormField = {
type: "text" | "textarea";
label: string;
ref: keyof ItemOut;
// key of ItemOut where the value is a string
ref: keyof OnlyString<ItemOut>;
};
type NumberFormField = {
type: "number";
label: string;
ref: keyof OnlyNumber<ItemOut> | keyof OnlyString<ItemOut>;
};
// 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<T> = { [k in keyof T]: T[k] extends boolean ? k : never }[keyof T];
type OnlyBoolean<T> = { [k in BooleanKeys<T>]: boolean };
interface BoolFormField {
type: "checkbox";
label: string;
ref: keyof OnlyBoolean<ItemOut>;
}
type DateKeys<T> = { [k in keyof T]: T[k] extends Date | string ? k : never }[keyof T];
type OnlyDate<T> = { [k in DateKeys<T>]: Date | string };
type DateFormField = {
type: "date";
label: string;
ref: keyof OnlyDate<ItemOut>;
};
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 @@
<section>
<div class="space-y-6">
<div class="card bg-base-100 shadow-xl sm:rounded-lg overflow-visible">
<BaseSectionHeader v-if="item" class="p-5">
<span class="text-base-content"> Edit </span>
<template #after>
<div class="modal-action mt-3">
<div class="mr-auto tooltip" data-tip="Show Advanced Options">
<label class="label cursor-pointer mr-auto">
<input v-model="preferences.editorAdvancedView" type="checkbox" class="toggle toggle-primary" />
<span class="label-text ml-4"> Advanced </span>
</label>
</div>
<BaseButton size="sm" @click="saveItem">
<template #icon>
<Icon name="mdi-content-save-outline" />
</template>
Save
</BaseButton>
<BaseCard class="overflow-visible">
<template #title> Edit Details </template>
<template #title-actions>
<div class="flex flex-wrap justify-between items-center mt-2 gap-4">
<div class="mr-auto tooltip" data-tip="Show Advanced Options">
<label class="label cursor-pointer mr-auto">
<input v-model="preferences.editorAdvancedView" type="checkbox" class="toggle toggle-primary" />
<span class="label-text ml-4"> Advanced </span>
</label>
</div>
</template>
</BaseSectionHeader>
<div class="px-5 mb-6 grid md:grid-cols-2 gap-4">
<BaseButton size="sm" @click="saveItem">
<template #icon>
<Icon name="mdi-content-save-outline" />
</template>
Save
</BaseButton>
</div>
</template>
<div class="px-5 pt-2 border-t mb-6 grid md:grid-cols-2 gap-4">
<LocationSelector v-model="item.location" />
<FormMultiselect v-model="item.labels" label="Labels" :items="labels ?? []" />
<Autocomplete
v-if="preferences.editorAdvancedView"
v-model="parent"
@ -404,11 +444,11 @@
</div>
</div>
</div>
</div>
</BaseCard>
<BaseCard>
<template #title> Custom Fields </template>
<div class="px-5 divide-y divide-gray-300 space-y-4">
<div class="px-5 border-t divide-y divide-gray-300 space-y-4">
<div
v-for="(field, idx) in item.fields"
:key="`field-${idx}`"