forked from mirrors/homebox
add support for custom text fields
This commit is contained in:
parent
57f9372e49
commit
434f1fa411
11 changed files with 384 additions and 38 deletions
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -96,6 +96,10 @@
|
|||
name: "Notes",
|
||||
text: item.value?.notes,
|
||||
},
|
||||
...item.value.fields.map(field => ({
|
||||
name: field.name,
|
||||
text: field.textValue,
|
||||
})),
|
||||
];
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue