297 lines
7.7 KiB
Vue
297 lines
7.7 KiB
Vue
<template>
|
|
<Modal ref="modalRef" size="xl">
|
|
<template #title> Add a new item </template>
|
|
|
|
<template #default="{ closeModal }">
|
|
<FormAlert :message="error" />
|
|
<Form @submit="onSubmit" v-model="data" :validators="validators">
|
|
<FormAutocompleteField
|
|
v-if="typeof item !== 'string'"
|
|
name="item"
|
|
label="Item"
|
|
required
|
|
:search-fn="searchForItems"
|
|
>
|
|
<template #notfound="{ query }">
|
|
<button @click="startAddingNewItem(query)">Add a new item</button>
|
|
</template>
|
|
</FormAutocompleteField>
|
|
<template v-if="item && typeof item === 'string'">
|
|
<FormField name="displayName" label="Display Name" required />
|
|
<FormSelectField
|
|
:options="itemTypesOptions"
|
|
required
|
|
name="type"
|
|
label="Type"
|
|
/>
|
|
<FormField name="barcode" label="Barcode" />
|
|
<FormField name="url" label="URL" />
|
|
<div class="grid space-y-2 sm:grid-cols-3 sm:space-y-0 sm:space-x-2">
|
|
<FormField
|
|
name="weight"
|
|
type="number"
|
|
step="0.001"
|
|
label="Weight (g)"
|
|
/>
|
|
<FormField
|
|
name="consumable"
|
|
type="checkbox"
|
|
label="Consumable / Food item"
|
|
/>
|
|
<FormField name="public" type="checkbox" label="Public item" />
|
|
</div>
|
|
<FormTextareaField name="notes" label="Notes" />
|
|
</template>
|
|
|
|
<FormGroup name="transactionInfo">
|
|
<FormDateField
|
|
name="actionAt"
|
|
label="Action committed at"
|
|
:disabled="!item"
|
|
required
|
|
/>
|
|
|
|
<div class="grid grid-cols-2 space-x-2 sm:grid-cols-4">
|
|
<FormField
|
|
name="price"
|
|
label="Cost"
|
|
type="number"
|
|
step=".01"
|
|
class="sm:col-span-3"
|
|
:disabled="!item"
|
|
/>
|
|
<FormSelectField
|
|
:options="currencies"
|
|
:disabled="!item"
|
|
name="currency"
|
|
label="Currency"
|
|
/>
|
|
</div>
|
|
|
|
<FormTextareaField
|
|
:disabled="!item"
|
|
name="notes"
|
|
label="Additional notes about the transaction"
|
|
/>
|
|
</FormGroup>
|
|
|
|
<FormGroup name="additionalInfo">
|
|
<div class="grid space-y-2 sm:grid-cols-3 sm:space-y-0 sm:space-x-2">
|
|
<FormDateField
|
|
name="expiresAt"
|
|
label="Expires at"
|
|
:disabled="!item"
|
|
/>
|
|
<FormDateField
|
|
name="acquiredAt"
|
|
label="Acquired at"
|
|
:disabled="!item"
|
|
/>
|
|
<FormDateField
|
|
name="consumedAt"
|
|
label="Consumed at"
|
|
:disabled="!item"
|
|
/>
|
|
</div>
|
|
|
|
<FormTextareaField
|
|
name="notes"
|
|
label="Additional notes about the individual stored item"
|
|
:disabled="!item"
|
|
/>
|
|
</FormGroup>
|
|
|
|
<div class="flex justify-end space-x-1">
|
|
<Button button-type="submit" @click="submitlock = true"
|
|
>Submit and add another</Button
|
|
>
|
|
<Button button-type="submit">Submit</Button>
|
|
</div>
|
|
</Form>
|
|
</template>
|
|
</Modal>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue';
|
|
import { useAccessToken } from '../../composables/useAccessToken';
|
|
import { BACKEND_URL } from '../../constants';
|
|
import { BuildingListItem } from '../../interfaces/building.interfaces';
|
|
import {
|
|
StorageItem,
|
|
StorageListItem,
|
|
StoredItem,
|
|
} from '../../interfaces/storage.interfaces';
|
|
import jfetch from '../../utils/jfetch';
|
|
import takeError from '../../utils/take-error';
|
|
import { FormSubmit, SelectOption } from '../form/form.types';
|
|
import Form from '../form/Form.vue';
|
|
import FormAlert from '../form/FormAlert.vue';
|
|
import FormField from '../form/FormField.vue';
|
|
import FormSelectField from '../form/fields/FormSelectField.vue';
|
|
import { IsRequired, MinLength } from '../form/validators';
|
|
import Modal from '../Modal.vue';
|
|
import { ItemType, ItemTypeName } from '../../enums/item-type.enum';
|
|
import { TransactionType } from '../../enums/transaction-type.enum';
|
|
import FormGroup from '../form/FormGroup.vue';
|
|
import FormDateField from '../form/fields/FormDateField.vue';
|
|
import FormAutocompleteField from '../form/fields/FormAutocompleteField.vue';
|
|
import { FormValidator } from '../form/validator.types';
|
|
import deepUnref from '../../utils/deep-unref';
|
|
import FormTextareaField from '../form/fields/FormTextareaField.vue';
|
|
import Button from '../Button.vue';
|
|
|
|
const defaults: any = {
|
|
item: null,
|
|
transactionInfo: {
|
|
type: TransactionType.ACQUIRED,
|
|
actionAt: new Date(),
|
|
},
|
|
additionalInfo: {
|
|
acquiredAt: new Date(),
|
|
},
|
|
};
|
|
|
|
const { authHeader } = useAccessToken();
|
|
const modalRef = ref<InstanceType<typeof Modal>>();
|
|
const building = ref<BuildingListItem>();
|
|
const storage = ref<StorageListItem>();
|
|
const item = ref<StorageItem | string | null>(null);
|
|
const data: any = ref({
|
|
...deepUnref(defaults),
|
|
});
|
|
const error = ref('');
|
|
const submitlock = ref(false);
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'added', storage: StoredItem): void;
|
|
}>();
|
|
|
|
const itemTypesOptions = computed(() => {
|
|
return Object.keys(ItemTypeName).reduce<SelectOption[]>(
|
|
(list, key) => [
|
|
...list,
|
|
{
|
|
value: key.toString(),
|
|
name: ItemTypeName[key as ItemType],
|
|
},
|
|
],
|
|
[]
|
|
);
|
|
});
|
|
|
|
const validators = ref<FormValidator[]>([
|
|
{
|
|
field: 'displayName',
|
|
validators: [MinLength(3), IsRequired()],
|
|
},
|
|
{
|
|
field: 'type',
|
|
validators: [IsRequired()],
|
|
},
|
|
{
|
|
field: 'transactionInfo.actionAt',
|
|
validators: [IsRequired()],
|
|
},
|
|
]);
|
|
|
|
const currencies = [
|
|
{
|
|
value: 'EUR',
|
|
name: 'EUR',
|
|
},
|
|
{
|
|
value: 'USD',
|
|
name: 'USD',
|
|
},
|
|
];
|
|
|
|
const searchForItems = async (search: string) => {
|
|
if (!search || search.length < 3) return [];
|
|
|
|
const query = new URLSearchParams();
|
|
if (search.startsWith('b:') && search.length > 3) {
|
|
query.append('barcode', search.substring(2));
|
|
} else {
|
|
query.append('searchTerm', search);
|
|
}
|
|
|
|
const { data: list } = await jfetch(
|
|
`${BACKEND_URL}/storage/item?${query.toString()}`,
|
|
{
|
|
headers: authHeader.value,
|
|
}
|
|
);
|
|
return list;
|
|
};
|
|
|
|
defineExpose({
|
|
openModal: (useBuilding: BuildingListItem, useStorage: StorageListItem) => {
|
|
building.value = useBuilding;
|
|
storage.value = useStorage;
|
|
item.value = null;
|
|
data.value = {
|
|
...deepUnref(defaults),
|
|
};
|
|
modalRef.value?.openModal();
|
|
},
|
|
});
|
|
|
|
watch(
|
|
() => data.value.item,
|
|
(selected) => {
|
|
if (selected && selected.id) {
|
|
item.value = selected;
|
|
return;
|
|
}
|
|
item.value = null;
|
|
}
|
|
);
|
|
|
|
const startAddingNewItem = (insert: string) => {
|
|
item.value = insert;
|
|
if (insert.startsWith('b:')) {
|
|
data.value['barcode'] = insert.substring(2);
|
|
} else {
|
|
data.value['displayName'] = insert;
|
|
}
|
|
};
|
|
|
|
const onSubmit = async (form: FormSubmit) => {
|
|
error.value = '';
|
|
if (!form.isValid || !storage.value) {
|
|
submitlock.value = false;
|
|
return;
|
|
}
|
|
|
|
const body = {
|
|
...form.formData,
|
|
};
|
|
delete body.item;
|
|
|
|
const requestUrl = `${BACKEND_URL}/storage/item/${storage.value.id}${
|
|
item.value && typeof item.value !== 'string' ? `/${item.value.id}` : ''
|
|
}`;
|
|
|
|
let createdStoredItem: StoredItem;
|
|
try {
|
|
const { data: response } = await jfetch(requestUrl, {
|
|
method: 'POST',
|
|
headers: authHeader.value,
|
|
body,
|
|
});
|
|
createdStoredItem = response;
|
|
} catch (e) {
|
|
error.value = takeError(e);
|
|
return;
|
|
}
|
|
|
|
emit('added', createdStoredItem);
|
|
if (submitlock.value) {
|
|
submitlock.value = false;
|
|
return;
|
|
}
|
|
modalRef.value?.closeModal();
|
|
};
|
|
</script>
|