add support for custom text fields

This commit is contained in:
Hayden 2022-10-15 21:41:27 -08:00
parent 57f9372e49
commit 434f1fa411
11 changed files with 384 additions and 38 deletions

View file

@ -50,17 +50,40 @@
const selectedIdx = ref(-1);
const internalSelected = useVModel(props, "modelValue", emit);
const internalValue = useVModel(props, "value", emit);
watch(selectedIdx, newVal => {
internalSelected.value = props.items[newVal];
});
watch(internalSelected, newVal => {
watch(selectedIdx, newVal => {
if (props.valueKey) {
emit("update:value", newVal[props.valueKey]);
internalValue.value = props.items[newVal][props.valueKey];
}
});
watch(
internalSelected,
() => {
const idx = props.items.findIndex(item => compare(item, internalSelected.value));
selectedIdx.value = idx;
},
{
immediate: true,
}
);
watch(
internalValue,
() => {
const idx = props.items.findIndex(item => compare(item[props.valueKey], internalValue.value));
selectedIdx.value = idx;
},
{
immediate: true,
}
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function compare(a: any, b: any): boolean {
if (a === b) {
@ -73,15 +96,4 @@
return JSON.stringify(a) === JSON.stringify(b);
}
watch(
internalSelected,
() => {
const idx = props.items.findIndex(item => compare(item, internalSelected.value));
selectedIdx.value = idx;
},
{
immediate: true,
}
);
</script>

View file

@ -2,11 +2,23 @@ import { faker } from "@faker-js/faker";
import { expect } from "vitest";
import { overrideParts } from "../../base/urls";
import { PublicApi } from "../../public";
import { LabelCreate, LocationCreate, UserRegistration } from "../../types/data-contracts";
import { ItemField, LabelCreate, LocationCreate, UserRegistration } from "../../types/data-contracts";
import * as config from "../../../../test/config";
import { UserClient } from "../../user";
import { Requests } from "../../../requests";
function itemField(id = null): ItemField {
return {
id,
name: faker.lorem.word(),
type: "text",
textValue: faker.lorem.sentence(),
booleanValue: false,
numberValue: faker.datatype.number(),
timeValue: null,
};
}
/**
* Returns a random user registration object that can be
* used to signup a new user.
@ -72,6 +84,7 @@ export const factories = {
user,
location,
label,
itemField,
client: {
public: publicClient,
user: userClient,

View file

@ -1,7 +1,9 @@
import { faker } from "@faker-js/faker";
import { describe, test, expect } from "vitest";
import { LocationOut } from "../../types/data-contracts";
import { ItemField, LocationOut } from "../../types/data-contracts";
import { AttachmentTypes } from "../../types/non-generated";
import { UserClient } from "../../user";
import { factories } from "../factories";
import { sharedUserClient } from "../test-utils";
describe("user should be able to create an item and add an attachment", () => {
@ -58,4 +60,57 @@ describe("user should be able to create an item and add an attachment", () => {
api.items.delete(item.id);
await cleanup();
});
test("user should be able to create and delete fields on an item", async () => {
const api = await sharedUserClient();
const [location, cleanup] = await useLocation(api);
const { response, data: item } = await api.items.create({
name: faker.vehicle.model(),
labelIds: [],
description: faker.lorem.paragraph(1),
locationId: location.id,
});
expect(response.status).toBe(201);
const fields: ItemField[] = [
factories.itemField(),
factories.itemField(),
factories.itemField(),
factories.itemField(),
];
// Add fields
const itemUpdate = {
...item,
locationId: item.location.id,
labelIds: item.labels.map(l => l.id),
fields,
};
const { response: updateResponse, data: item2 } = await api.items.update(item.id, itemUpdate);
expect(updateResponse.status).toBe(200);
expect(item2.fields).toHaveLength(fields.length);
for (let i = 0; i < fields.length; i++) {
expect(item2.fields[i].name).toBe(fields[i].name);
expect(item2.fields[i].textValue).toBe(fields[i].textValue);
expect(item2.fields[i].numberValue).toBe(fields[i].numberValue);
}
itemUpdate.fields = [fields[0], fields[1]];
const { response: updateResponse2, data: item3 } = await api.items.update(item.id, itemUpdate);
expect(updateResponse2.status).toBe(200);
expect(item3.fields).toHaveLength(2);
for (let i = 0; i < item3.fields.length; i++) {
expect(item3.fields[i].name).toBe(itemUpdate.fields[i].name);
expect(item3.fields[i].textValue).toBe(itemUpdate.fields[i].textValue);
expect(item3.fields[i].numberValue).toBe(itemUpdate.fields[i].numberValue);
}
cleanup();
});
});

View file

@ -51,10 +51,23 @@ export interface ItemCreate {
name: string;
}
export interface ItemField {
booleanValue: boolean;
id: string;
name: string;
numberValue: number;
textValue: string;
timeValue: string;
type: string;
}
export interface ItemOut {
attachments: ItemAttachment[];
createdAt: Date;
description: string;
/** Future */
fields: ItemField[];
id: string;
insured: boolean;
labels: LabelSummary[];
@ -108,6 +121,7 @@ export interface ItemSummary {
export interface ItemUpdate {
description: string;
fields: ItemField[];
id: string;
insured: boolean;
labelIds: string[];

View file

@ -278,6 +278,34 @@
toast.success("Attachment updated");
}
// Custom Fields
// const fieldTypes = [
// {
// name: "Text",
// value: "text",
// },
// {
// name: "Number",
// value: "number",
// },
// {
// name: "Boolean",
// value: "boolean",
// },
// ];
function addField() {
item.value.fields.push({
id: null,
name: "Field Name",
type: "text",
textValue: "",
numberValue: 0,
booleanValue: false,
timeValue: null,
});
}
</script>
<template>
@ -364,6 +392,31 @@
</div>
</div>
<BaseCard>
<template #title> Custom Fields </template>
<div class="px-5 divide-y divide-gray-300 space-y-4">
<div
v-for="(field, idx) in item.fields"
:key="`field-${idx}`"
class="grid grid-cols-2 md:grid-cols-4 gap-2"
>
<!-- <FormSelect v-model:value="field.type" label="Field Type" :items="fieldTypes" value-key="value" /> -->
<FormTextField v-model="field.name" label="Name" />
<div class="flex items-end col-span-3">
<FormTextField v-model="field.textValue" label="Value" />
<div class="tooltip" data-tip="Delete">
<button class="btn btn-sm btn-square mb-2 ml-2" @click="item.fields.splice(idx, 1)">
<Icon name="mdi-delete" />
</button>
</div>
</div>
</div>
</div>
<div class="px-5 pb-4 mt-4 flex justify-end">
<BaseButton size="sm" @click="addField"> Add </BaseButton>
</div>
</BaseCard>
<div
v-if="!preferences.editorSimpleView"
ref="attDropZone"

View file

@ -96,6 +96,10 @@
name: "Notes",
text: item.value?.notes,
},
...item.value.fields.map(field => ({
name: field.name,
text: field.textValue,
})),
];
});