more form changes

This commit is contained in:
Evert Prants 2023-01-26 20:21:41 +02:00
parent c76db68e5b
commit b1f58276d0
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
17 changed files with 547 additions and 151 deletions

60
package-lock.json generated
View File

@ -12,8 +12,10 @@
"@heroicons/vue": "^2.0.13",
"@vueuse/core": "^9.10.0",
"jwt-decode": "^3.1.2",
"lodash.get": "^4.4.2",
"lodash.omit": "^4.5.0",
"lodash.pick": "^4.4.0",
"lodash.set": "^4.3.2",
"pinia": "^2.0.28",
"sass": "^1.57.1",
"vue": "^3.2.45",
@ -22,8 +24,10 @@
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.2",
"@types/lodash.get": "^4.4.7",
"@types/lodash.omit": "^4.5.7",
"@types/lodash.pick": "^4.4.7",
"@types/lodash.set": "^4.3.7",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.21",
@ -479,6 +483,15 @@
"integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
"dev": true
},
"node_modules/@types/lodash.get": {
"version": "4.4.7",
"resolved": "https://registry.npmjs.org/@types/lodash.get/-/lodash.get-4.4.7.tgz",
"integrity": "sha512-af34Mj+KdDeuzsJBxc/XeTtOx0SZHZNLd+hdrn+PcKGQs0EG2TJTzQAOTCZTgDJCArahlCzLWSy8c2w59JRz7Q==",
"dev": true,
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/lodash.omit": {
"version": "4.5.7",
"resolved": "https://registry.npmjs.org/@types/lodash.omit/-/lodash.omit-4.5.7.tgz",
@ -497,6 +510,15 @@
"@types/lodash": "*"
}
},
"node_modules/@types/lodash.set": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/@types/lodash.set/-/lodash.set-4.3.7.tgz",
"integrity": "sha512-bS5Wkg/nrT82YUfkNYPSccFrNZRL+irl7Yt4iM6OTSQ0VZJED2oUIVm15NkNtUAQ8SRhCe+axqERUV6MJgkeEg==",
"dev": true,
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.16",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
@ -1248,6 +1270,11 @@
"node": ">=10"
}
},
"node_modules/lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
},
"node_modules/lodash.omit": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
@ -1258,6 +1285,11 @@
"resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
"integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q=="
},
"node_modules/lodash.set": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
"integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg=="
},
"node_modules/magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
@ -2218,6 +2250,15 @@
"integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
"dev": true
},
"@types/lodash.get": {
"version": "4.4.7",
"resolved": "https://registry.npmjs.org/@types/lodash.get/-/lodash.get-4.4.7.tgz",
"integrity": "sha512-af34Mj+KdDeuzsJBxc/XeTtOx0SZHZNLd+hdrn+PcKGQs0EG2TJTzQAOTCZTgDJCArahlCzLWSy8c2w59JRz7Q==",
"dev": true,
"requires": {
"@types/lodash": "*"
}
},
"@types/lodash.omit": {
"version": "4.5.7",
"resolved": "https://registry.npmjs.org/@types/lodash.omit/-/lodash.omit-4.5.7.tgz",
@ -2236,6 +2277,15 @@
"@types/lodash": "*"
}
},
"@types/lodash.set": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/@types/lodash.set/-/lodash.set-4.3.7.tgz",
"integrity": "sha512-bS5Wkg/nrT82YUfkNYPSccFrNZRL+irl7Yt4iM6OTSQ0VZJED2oUIVm15NkNtUAQ8SRhCe+axqERUV6MJgkeEg==",
"dev": true,
"requires": {
"@types/lodash": "*"
}
},
"@types/web-bluetooth": {
"version": "0.0.16",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
@ -2786,6 +2836,11 @@
"integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==",
"dev": true
},
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
},
"lodash.omit": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
@ -2796,6 +2851,11 @@
"resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
"integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q=="
},
"lodash.set": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
"integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg=="
},
"magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",

View File

@ -13,8 +13,10 @@
"@heroicons/vue": "^2.0.13",
"@vueuse/core": "^9.10.0",
"jwt-decode": "^3.1.2",
"lodash.get": "^4.4.2",
"lodash.omit": "^4.5.0",
"lodash.pick": "^4.4.0",
"lodash.set": "^4.3.2",
"pinia": "^2.0.28",
"sass": "^1.57.1",
"vue": "^3.2.45",
@ -23,8 +25,10 @@
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.2",
"@types/lodash.get": "^4.4.7",
"@types/lodash.omit": "^4.5.7",
"@types/lodash.pick": "^4.4.7",
"@types/lodash.set": "^4.3.7",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.21",

View File

@ -0,0 +1,52 @@
<template>
<div class="flex flex-col">
<button class="px-4 py-4" @click="open = !open" :aria-expanded="open">
<slot name="title" :open="open" :close="close">
<div class="flex items-center justify-between">
<span>{{ title }}</span>
<ChevronDownIcon class="h-6 w-6" v-if="!open" aria-hidden="true" />
<ChevronUpIcon class="h-6 w-6" v-else aria-hidden="true" />
</div>
</slot>
</button>
<Transition
enter-active-class="transition-max-height ease-out duration-500"
enter-from-class="max-h-0"
enter-to-class="max-h-screen"
leave-active-class="transition-max-height ease-in duration-250"
leave-from-class="max-h-screen"
leave-to-class="max-h-0"
>
<div
v-if="open"
:class="[
open ? 'border-t-2 border-gray-100' : '',
'overflow-hidden px-4 py-4',
]"
>
<slot :close="close" />
</div>
</Transition>
</div>
</template>
<script setup lang="ts">
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline';
import { ref } from 'vue';
const props = withDefaults(
defineProps<{
title?: string;
open?: boolean;
}>(),
{
open: false,
}
);
const open = ref(props.open);
const close = () => (open.value = false);
</script>

View File

@ -23,19 +23,18 @@
:onBlur="onBlur"
:setValue="setValue"
>
<FormInput
:for-id="forId"
<input
:type="type"
:id="forId"
:name="name"
:value="value"
:class="inputClass"
:placeholder="placeholder"
:invalid="!!errors?.length"
:disabled="disabled"
@input="onInput"
@change="onChange"
@focus="onFocus"
@blur="onBlur"
@set-value="setValue"
/>
</slot>
<slot />
@ -54,7 +53,6 @@ import get from 'lodash.get';
import { computed, ref, Ref } from 'vue';
import { inject } from 'vue';
import { FormData, FormErrors } from './form.types';
import FormInput from './FormInput.vue';
const props = withDefaults(
defineProps<{
@ -68,10 +66,12 @@ const props = withDefaults(
| 'radio';
label: string;
name: string;
disabled?: boolean;
placeholder?: string;
}>(),
{
type: 'text',
disabled: false,
}
);
@ -90,7 +90,7 @@ const fieldName = computed(() =>
formGroup?.value ? `${formGroup.value}.${props.name}` : props.name
);
const forId = computed(() => `form-${fieldName}`);
const forId = computed(() => `form-${fieldName.value}`);
const value = computed(() => get(formData?.value, fieldName.value));
const errors = computed(() => formErrors?.value[fieldName.value] || []);
@ -122,4 +122,13 @@ const onBlur = () => {
emit('blur');
fieldChange?.(fieldName.value);
};
const inputClass = computed(() => {
return [
errors.value.length
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500',
`mt-1 block w-full rounded-md shadow-sm sm:text-sm transition-colors duration-200`,
];
});
</script>

View File

@ -1,65 +0,0 @@
<template>
<ColorInput
v-if="type === 'color'"
:for-id="forId"
:model-value="(value as string)"
@update:model-value="(newValue: string) => emit('setValue', newValue)"
/>
<input
v-else
:type="type"
:id="forId"
:name="name"
:value="value"
:class="inputClass"
:placeholder="placeholder"
@input="emit('input', $event)"
@change="emit('change', $event)"
@focus="emit('focus', $event)"
@blur="emit('blur', $event)"
/>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import ColorInput from '../ColorInput.vue';
const props = withDefaults(
defineProps<{
type?:
| 'text'
| 'number'
| 'password'
| 'color'
| 'email'
| 'checkbox'
| 'radio';
forId: string;
name: string;
value: unknown;
invalid?: boolean;
placeholder?: string;
}>(),
{
type: 'text',
invalid: false,
}
);
const emit = defineEmits<{
(e: 'setValue', value: unknown): void;
(e: 'input', ev: Event): void;
(e: 'change', ev: Event): void;
(e: 'focus', ev: Event): void;
(e: 'blur', ev: Event): void;
}>();
const inputClass = computed(() => {
return [
props.invalid
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500',
`mt-1 block w-full rounded-md shadow-sm sm:text-sm transition-colors duration-200`,
];
});
</script>

View File

@ -0,0 +1,130 @@
<template>
<Listbox
:modelValue="selected"
@update:modelValue="valueChanged"
:disabled="disabled"
by="value"
>
<div class="relative mt-1">
<ListboxButton :class="inputClass" :id="forId">
<span class="block truncate">{{ selected?.name || '' }}</span>
<span
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
>
<ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
</span>
</ListboxButton>
<Transition
leave-active-class="transition duration-100 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions
class="absolute z-20 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
>
<ListboxOption
v-slot="{ active, selected }"
v-for="option in options"
:key="option.name"
:value="option"
as="template"
>
<li :class="itemClass(active, selected)">
<span
:class="[
selected ? 'font-medium' : 'font-normal',
'block truncate',
]"
>
{{ option.name }}
</span>
<span
v-if="selected"
class="absolute inset-y-0 left-0 flex items-center pl-2 text-blue-600"
>
<CheckIcon class="h-5 w-5" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</Transition>
</div>
</Listbox>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import {
Listbox,
ListboxButton,
ListboxOptions,
ListboxOption,
} from '@headlessui/vue';
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/vue/24/outline';
export interface SelectOption {
value: string | number;
name: string;
description?: string;
disabled?: boolean;
}
const props = withDefaults(
defineProps<{
forId?: string;
modelValue: string | number | null;
options: SelectOption[];
disabled?: boolean;
invalid?: boolean;
placeholder?: string;
}>(),
{
modelValue: null,
disabled: false,
invalid: false,
}
);
const localValue = ref(props.modelValue);
const emit = defineEmits<{
(e: 'update:modelValue', value: string | number | null): void;
}>();
const valueChanged = (option: SelectOption) => {
localValue.value = option.value;
emit('update:modelValue', localValue.value);
};
const selected = computed(() =>
props.options.find((item) => localValue.value === item.value)
);
const inputClass = computed(() => {
return [
props.invalid
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500',
`block w-full rounded-md shadow-sm sm:text-sm transition-colors duration-200`,
'py-2 pl-3 pr-10 text-left cursor-default h-[38px] border-[1px]',
];
});
const itemClass = (active: boolean, selected: boolean) => {
const classList = ['relative cursor-default select-none py-2 pl-8 pr-4'];
if (selected) {
classList.push('text-blue-900');
}
if (active && selected) {
classList.push('bg-blue-200');
} else if (active || selected) {
classList.push('bg-blue-100');
} else {
classList.push('text-gray-900');
}
return classList;
};
</script>

View File

@ -0,0 +1,35 @@
<template>
<FormField
:name="name"
:label="label"
:placeholder="placeholder"
:disabled="disabled"
>
<template #input="{ forId, value, setValue }">
<ColorInput
:for-id="forId"
:model-value="(value as string)"
@update:model-value="setValue"
/>
</template>
<template #default><slot /></template>
</FormField>
</template>
<script setup lang="ts">
import FormField from '../FormField.vue';
import ColorInput from '../base/ColorInput.vue';
const props = withDefaults(
defineProps<{
forId?: string;
label: string;
name: string;
disabled?: boolean;
placeholder?: string;
}>(),
{
disabled: false,
}
);
</script>

View File

@ -0,0 +1,40 @@
<template>
<FormField
:name="name"
:label="label"
:placeholder="placeholder"
:disabled="disabled"
>
<template #input="{ invalid, value, setValue }">
<SelectVue
:for-id="forId"
:invalid="invalid"
:disabled="disabled"
:options="options || []"
:placeholder="placeholder"
:model-value="(value as string)"
@update:model-value="(newValue) => setValue(newValue)"
/>
</template>
<template #default><slot /></template>
</FormField>
</template>
<script setup lang="ts">
import FormField from '../FormField.vue';
import SelectVue, { SelectOption } from '../base/Select.vue';
const props = withDefaults(
defineProps<{
forId?: string;
label: string;
name: string;
options: SelectOption[];
disabled?: boolean;
placeholder?: string;
}>(),
{
disabled: false,
}
);
</script>

View File

@ -1,41 +0,0 @@
<template>
<Dropdown :title="title">
<template #trigger="{ title, open, toggle }">
<button
type="button"
@click="() => toggle()"
:aria-expanded="open"
:class="[
open ? 'text-gray-900' : 'text-gray-500',
'group inline-flex items-center rounded-md bg-white text-base font-medium hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2',
]"
>
<span>{{ title }}</span>
<ChevronDownIcon class="ml-2 h-5 w-5" />
</button>
</template>
<template #default="{ title, open, toggle }">
<div
class="absolute z-10 -ml-4 mt-3 w-screen max-w-xs transform px-2 sm:px-0 lg:left-1/2 lg:ml-0 lg:-translate-x-1/2"
>
<div
class="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"
>
<slot></slot>
</div>
</div>
</template>
</Dropdown>
</template>
<script setup lang="ts">
import { ChevronDownIcon } from '@heroicons/vue/24/outline';
import { onMounted, ref } from 'vue';
import { onBeforeRouteLeave } from 'vue-router';
import Dropdown from '../Dropdown.vue';
const props = defineProps<{
title: string;
}>();
</script>

View File

@ -1,65 +1,143 @@
<template>
<div class="relative border-b-2 border-gray-100 bg-white">
<div class="mx-auto max-w-7xl px-5">
<div
class="flex items-center justify-between py-5 md:justify-start md:space-x-10"
>
<div class="flex w-0 justify-start md:mr-4">
<Popover class="relative border-b-2 border-gray-100 bg-white">
<div class="mx-auto max-w-7xl px-6">
<div class="flex items-center py-6 md:justify-start md:space-x-8">
<div class="mr-auto flex md:mr-0">
<div class="mr-auto flex justify-start md:mr-0">
<router-link to="/"><HomeIcon class="h-8 w-8" /></router-link>
</div>
</div>
<div class="-my-2 -mr-2 md:hidden">
<button
type="button"
<PopoverButton
class="inline-flex items-center justify-center rounded-md bg-white p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
aria-expanded="false"
>
<span class="sr-only">Open menu</span>
<Bars3Icon class="h-6 w-6" />
</button>
<Bars3Icon class="h-6 w-6" aria-hidden="true" />
</PopoverButton>
</div>
<PopoverGroup as="nav" class="hidden space-x-6 md:flex">
<Popover class="relative" v-slot="{ open }">
<PopoverButton
:class="[
open ? 'text-gray-900' : 'text-gray-500',
'group inline-flex items-center rounded-md bg-white text-base font-medium hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2',
]"
>
<span>Buildings</span>
<ChevronDownIcon
:class="[
open ? 'text-gray-600' : 'text-gray-400',
'ml-2 h-5 w-5 group-hover:text-gray-500',
]"
aria-hidden="true"
/>
</PopoverButton>
<nav class="hidden space-x-10 md:flex">
<Dropdown title="My Buildings">
<transition
enter-active-class="transition ease-out duration-200"
enter-from-class="opacity-0 translate-y-1"
enter-to-class="opacity-100 translate-y-0"
leave-active-class="transition ease-in duration-150"
leave-from-class="opacity-100 translate-y-0"
leave-to-class="opacity-0 translate-y-1"
>
<PopoverPanel
class="absolute z-10 -ml-4 mt-3 w-screen max-w-md transform px-2 sm:px-0"
>
<div
class="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"
>
<div class="relative bg-white pt-4 pb-4 sm:gap-5">
<template v-for="building of buildings">
<Building :building="building" />
</template>
</div>
</Dropdown>
<button
type="button"
:class="[
'text-gray-500',
'inline-flex items-center rounded-md bg-white text-base font-medium hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2',
]"
>
<span>Groups</span>
</button>
</nav>
</div>
</PopoverPanel>
</transition>
</Popover>
<a
href="#"
class="text-base font-medium text-gray-500 hover:text-gray-900"
>Groups</a
>
</PopoverGroup>
<div class="hidden items-center justify-end md:flex md:flex-1 lg:w-0">
<UserPill :user="user" />
</div>
</div>
</div>
<transition
enter-active-class="duration-200 ease-out"
enter-from-class="opacity-0 scale-95"
enter-to-class="opacity-100 scale-100"
leave-active-class="duration-100 ease-in"
leave-from-class="opacity-100 scale-100"
leave-to-class="opacity-0 scale-95"
>
<PopoverPanel
focus
class="absolute inset-x-0 top-0 z-50 origin-top-right transform p-2 transition md:hidden"
>
<div
class="divide-y-2 divide-gray-50 rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5"
>
<div class="px-5 pt-5 pb-6">
<div class="flex items-center justify-between">
<div>
<router-link to="/"><HomeIcon class="h-8 w-8" /></router-link>
</div>
<div class="-mr-2">
<PopoverButton
class="inline-flex items-center justify-center rounded-md bg-white p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
>
<span class="sr-only">Close menu</span>
<XMarkIcon class="h-6 w-6" aria-hidden="true" />
</PopoverButton>
</div>
</div>
<div class="mt-6">
<span class="text-lg font-bold">Buildings</span>
<div class="-mx-5">
<template v-for="building of buildings">
<Building :building="building" />
</template>
</div>
</div>
</div>
<div class="space-y-6 py-6 px-5">
<div class="flex justify-center">
<UserPill :user="user" />
</div>
</div>
</div>
</PopoverPanel>
</transition>
</Popover>
</template>
<script setup lang="ts">
import {
Popover,
PopoverButton,
PopoverGroup,
PopoverPanel,
} from '@headlessui/vue';
import {
HomeIcon,
Bars3Icon,
ChevronDownIcon,
UserIcon,
XMarkIcon,
} from '@heroicons/vue/24/outline';
import { storeToRefs } from 'pinia';
import { useUserStore } from '../../store/user.store';
import UserPill from './UserPill.vue';
import Dropdown from './Dropdown.vue';
import Building from './Building.vue';
import { useBuildingStore } from '../../store/building.store';
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
const userStore = useUserStore();
const buildingsStore = useBuildingStore();
const { user } = storeToRefs(userStore);

View File

@ -50,7 +50,7 @@
<script setup lang="ts">
import { computed } from '@vue/reactivity';
import ColorInput from '../ColorInput.vue';
import ColorInput from '../form/base/ColorInput.vue';
import { ObjectProperty } from './interfaces/properties.interfaces';
const props = defineProps<{

View File

@ -8,11 +8,11 @@
<FormAlert :message="error" />
<Form @submit="onSubmit" v-model="data" :validators="validators">
<FormField name="displayName" label="Display Name" />
<FormField name="type" label="Type" />
<FormSelectField :options="selectOptions" name="type" label="Type" />
<FormField name="locationDescription" label="Location description">
<span class="text-sm">Describe the location of this storage</span>
</FormField>
<FormField type="color" name="color" label="Color" />
<FormColorField name="color" label="Color" />
<button type="submit">Submit</button>
</Form>
</template>
@ -20,9 +20,11 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { useAccessToken } from '../../composables/useAccessToken';
import { BACKEND_URL } from '../../constants';
import { StorageSetTypeName } from '../../enums/storage-set-type.enum';
import { StorageTypeName } from '../../enums/storage-type.enum';
import { BuildingListItem } from '../../interfaces/building.interfaces';
import { RoomListItem } from '../../interfaces/room.interfaces';
import {
@ -35,8 +37,11 @@ import { FormSubmit } 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 { SelectOption } from '../form/base/Select.vue';
import { IsRequired, MinLength } from '../form/validators';
import Modal from '../Modal.vue';
import FormColorField from '../form/fields/FormColorField.vue';
const { authHeader } = useAccessToken();
const defaults = {
@ -52,6 +57,16 @@ const building = ref<BuildingListItem>();
const set = ref<StorageSetListItem | boolean>();
const data = ref({ ...defaults });
const error = ref('');
const selectOptions = computed(() => {
const source = set.value === true ? StorageSetTypeName : StorageTypeName;
return Object.keys(source).reduce<SelectOption[]>(
(list, key) => [
...list,
{ value: key.toString(), name: source[key as keyof typeof source] },
],
[]
);
});
const validators = ref([
{

View File

@ -12,3 +12,34 @@ export enum ItemType {
SERVICE = 'SERVICE',
OTHER = 'OTHER',
}
export const ItemTypeName: Record<ItemType, string> = {
[ItemType.ITEM]: 'Generic item',
[ItemType.OBJECT]: 'Generic object',
[ItemType.TECHNOLOGY]: 'Technology',
[ItemType.TOOL]: 'Tool',
[ItemType.FOOD]: 'Food',
[ItemType.MEDICINE]: 'Medicine',
[ItemType.ART]: 'Arts',
[ItemType.CRAFT]: 'Crafts',
[ItemType.COMPOSITION]: 'Composition/Creation',
[ItemType.CLOTHING]: 'Clothing',
[ItemType.SERVICE]: '(Virtual) Service',
[ItemType.OTHER]: 'Other',
};
export const ItemTypeDescription: Record<ItemType, string> = {
[ItemType.ITEM]: 'An item, the classification is up to you',
[ItemType.OBJECT]: 'An object, the classification is up to you',
[ItemType.TECHNOLOGY]:
'Such as computer hardware, electronic components, etc',
[ItemType.TOOL]: 'Tools of any kind',
[ItemType.FOOD]: 'Edible, drinkable or otherwise consumable',
[ItemType.MEDICINE]: 'Medicine',
[ItemType.ART]: 'Artworks, prints',
[ItemType.CRAFT]: 'Crafted items',
[ItemType.COMPOSITION]: 'A combination of items that created a new item',
[ItemType.CLOTHING]: 'Clothing, wearables',
[ItemType.SERVICE]: 'Online service, computer software, etc',
[ItemType.OTHER]: 'Other',
};

View File

@ -9,3 +9,15 @@ export enum StorageSetType {
BOXES = 'BOXES',
OTHER = 'OTHER',
}
export const StorageSetTypeName: Record<StorageSetType, string> = {
[StorageSetType.DRAWERS]: 'Set of drawers',
[StorageSetType.SHELVES]: 'Shelves',
[StorageSetType.CUPBOARD]: 'Cupboard',
[StorageSetType.CLOSET]: 'Closet',
[StorageSetType.FRIDGE]: 'Fridge',
[StorageSetType.FREEZER]: 'Freezer',
[StorageSetType.BOX]: 'Box',
[StorageSetType.BOXES]: 'Boxes',
[StorageSetType.OTHER]: 'Other',
};

View File

@ -12,3 +12,18 @@ export enum StorageType {
PERSON = 'PERSON',
OTHER = 'OTHER',
}
export const StorageTypeName: Record<StorageType, string> = {
[StorageType.DRAWER]: 'Drawer',
[StorageType.SHELF]: 'Shelf',
[StorageType.ASSEMBLY]: 'Assembly',
[StorageType.CUPBOARD]: 'Cupboard',
[StorageType.CLOSET]: 'Closet',
[StorageType.FRIDGE]: 'Fridge',
[StorageType.FREEZER]: 'Freezer',
[StorageType.TABLE]: 'Table',
[StorageType.BOX]: 'Box',
[StorageType.VIRTUAL]: 'Virtual',
[StorageType.PERSON]: 'Person',
[StorageType.OTHER]: 'Other',
};

View File

@ -6,3 +6,24 @@ export enum TransactionType {
MOVED = 'MOVED',
TRANSFERRED = 'TRANSFERRED',
}
export const TransactionTypeName: Record<TransactionType, string> = {
[TransactionType.ACQUIRED]: 'Acquired / Purchased',
[TransactionType.SOLD]: 'Sold',
[TransactionType.DESTROYED]: 'Destroyed (got rid of)',
[TransactionType.BINNED]: 'Binned (threw away)',
[TransactionType.MOVED]: 'Moved',
[TransactionType.TRANSFERRED]: 'Transferred',
};
export const TransactionTypeDescription: Record<TransactionType, string> = {
[TransactionType.ACQUIRED]:
'Items were received into your possession by any means',
[TransactionType.SOLD]:
'Item was sold, optionally mark proceeds with negative price',
[TransactionType.DESTROYED]:
'Burned, broken beyond recognition, unusable etc',
[TransactionType.BINNED]: 'Thrown away for recycle or otherwise',
[TransactionType.MOVED]: 'Moved to another place / storage',
[TransactionType.TRANSFERRED]: 'Transferred to another person / location',
};