updates
This commit is contained in:
parent
5a63f00f51
commit
8984ddd9dc
2363
package-lock.json
generated
2363
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@ -9,36 +9,37 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/vue": "^1.7.7",
|
"@headlessui/vue": "^1.7.13",
|
||||||
"@heroicons/vue": "^2.0.13",
|
"@heroicons/vue": "^2.0.18",
|
||||||
"@vuepic/vue-datepicker": "^3.6.5",
|
"@vuepic/vue-datepicker": "^5.1.2",
|
||||||
"@vueuse/core": "^9.10.0",
|
"@vueuse/core": "^10.1.2",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.omit": "^4.5.0",
|
"lodash.omit": "^4.5.0",
|
||||||
"lodash.pick": "^4.4.0",
|
"lodash.pick": "^4.4.0",
|
||||||
"lodash.set": "^4.3.2",
|
"lodash.set": "^4.3.2",
|
||||||
"pinia": "^2.0.28",
|
"pinia": "^2.1.3",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.62.1",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.3.4",
|
||||||
"vue-material-design-icons": "^5.1.2",
|
"vue-material-design-icons": "^5.2.0",
|
||||||
"vue-router": "^4.1.6"
|
"vue-router": "^4.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@tailwindcss/line-clamp": "^0.4.2",
|
"@tailwindcss/line-clamp": "^0.4.4",
|
||||||
"@types/lodash.get": "^4.4.7",
|
"@types/lodash.get": "^4.4.7",
|
||||||
"@types/lodash.omit": "^4.5.7",
|
"@types/lodash.omit": "^4.5.7",
|
||||||
"@types/lodash.pick": "^4.4.7",
|
"@types/lodash.pick": "^4.4.7",
|
||||||
"@types/lodash.set": "^4.3.7",
|
"@types/lodash.set": "^4.3.7",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.2.3",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.14",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.23",
|
||||||
"prettier": "^2.8.3",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-tailwindcss": "^0.2.1",
|
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||||
"tailwindcss": "^3.2.4",
|
"tailwindcss": "^3.3.2",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^5.0.4",
|
||||||
"vite": "^4.0.0",
|
"vite": "^4.3.8",
|
||||||
"vue-tsc": "^1.0.11"
|
"vue-tsc": "^1.6.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,12 @@
|
|||||||
>
|
>
|
||||||
<template #input="{ invalid, fieldFQN, value, setValue }">
|
<template #input="{ invalid, fieldFQN, value, setValue }">
|
||||||
<Datepicker
|
<Datepicker
|
||||||
text-input
|
v-bind="props"
|
||||||
arrow-navigation
|
|
||||||
:class="[invalid ? 'dp__invalid' : '']"
|
:class="[invalid ? 'dp__invalid' : '']"
|
||||||
:uid="fieldFQN"
|
:uid="fieldFQN"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:disabled="disabled"
|
:state="undefined"
|
||||||
:clearable="clearable"
|
:format="format"
|
||||||
v-bind="$attrs"
|
|
||||||
:model-value="(value as string)"
|
:model-value="(value as string)"
|
||||||
@update:model-value="setValue"
|
@update:model-value="setValue"
|
||||||
/>
|
/>
|
||||||
@ -26,10 +24,12 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import FormField from '../FormField.vue';
|
import FormField from '../FormField.vue';
|
||||||
import Datepicker from '@vuepic/vue-datepicker';
|
import Datepicker, { VueDatePickerProps } from '@vuepic/vue-datepicker';
|
||||||
import { ExtractComponentProps } from '../../../utils/extract-component-props';
|
import '@vuepic/vue-datepicker/dist/main.css';
|
||||||
|
import { Omit } from 'lodash';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
interface DatePickerProps extends ExtractComponentProps<typeof Datepicker> {
|
interface DatePickerProps extends VueDatePickerProps {
|
||||||
label: string;
|
label: string;
|
||||||
name: string;
|
name: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@ -41,5 +41,14 @@ interface DatePickerProps extends ExtractComponentProps<typeof Datepicker> {
|
|||||||
// https://vue3datepicker.com/
|
// https://vue3datepicker.com/
|
||||||
const props = withDefaults(defineProps<DatePickerProps>(), {
|
const props = withDefaults(defineProps<DatePickerProps>(), {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
textInput: true,
|
||||||
|
arrowNavigation: true,
|
||||||
|
enableTimePicker: true,
|
||||||
|
autoPosition: true,
|
||||||
|
is24: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const format = computed(() =>
|
||||||
|
props.format ?? props.enableTimePicker ? `dd/MM/yyyy, HH:mm` : 'dd/MM/yyyy'
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative h-full w-full" :class="{ 'bg-white': !transparent }">
|
<div class="relative w-full" :class="{ 'bg-white': !transparent }">
|
||||||
<div
|
<div
|
||||||
class="relative h-full w-full overflow-hidden"
|
class="relative h-full w-full overflow-hidden"
|
||||||
:class="{ 'bg-gray-100': !transparent }"
|
:class="{ 'bg-gray-100': !transparent }"
|
||||||
@ -21,7 +21,7 @@
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute top-0 left-0 h-full w-full"
|
class="pointer-events-none absolute left-0 top-0 h-full w-full"
|
||||||
v-if="!editable"
|
v-if="!editable"
|
||||||
:style="{
|
:style="{
|
||||||
transformOrigin: 'top left',
|
transformOrigin: 'top left',
|
||||||
|
@ -24,9 +24,15 @@
|
|||||||
name="type"
|
name="type"
|
||||||
label="Type"
|
label="Type"
|
||||||
/>
|
/>
|
||||||
<FormField name="barcode" label="Barcode" />
|
|
||||||
|
<div class="grid space-y-2 sm:grid-cols-2 sm:space-x-2 sm:space-y-0">
|
||||||
|
<FormField name="barcode" label="Barcode" />
|
||||||
|
<FormField name="sku" label="SKU / Product code" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<FormField name="url" label="URL" />
|
<FormField name="url" label="URL" />
|
||||||
<div class="grid space-y-2 sm:grid-cols-3 sm:space-y-0 sm:space-x-2">
|
|
||||||
|
<div class="grid space-y-2 sm:grid-cols-3 sm:space-x-2 sm:space-y-0">
|
||||||
<FormField
|
<FormField
|
||||||
name="weight"
|
name="weight"
|
||||||
type="number"
|
type="number"
|
||||||
@ -76,7 +82,7 @@
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup name="additionalInfo">
|
<FormGroup name="additionalInfo">
|
||||||
<div class="grid space-y-2 sm:grid-cols-3 sm:space-y-0 sm:space-x-2">
|
<div class="grid space-y-2 sm:grid-cols-3 sm:space-x-2 sm:space-y-0">
|
||||||
<FormDateField
|
<FormDateField
|
||||||
name="expiresAt"
|
name="expiresAt"
|
||||||
label="Expires at"
|
label="Expires at"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
@import '@vuepic/vue-datepicker/src/VueDatePicker/style/main.scss';
|
|
||||||
|
|
||||||
.dp__input {
|
.dp__input {
|
||||||
@apply block w-full rounded-md border-gray-300 shadow-sm transition-colors duration-200 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 sm:text-sm;
|
@apply block w-full rounded-md border-gray-300 shadow-sm transition-colors duration-200 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 sm:text-sm;
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
<HousePlanner
|
<HousePlanner
|
||||||
v-if="selectedFloorId"
|
v-if="selectedFloorId"
|
||||||
editable
|
editable
|
||||||
|
class="h-full"
|
||||||
ref="plannerRef"
|
ref="plannerRef"
|
||||||
:key="`planner-${selectedFloorId}`"
|
:key="`planner-${selectedFloorId}`"
|
||||||
:floor-document="floorPlan"
|
:floor-document="floorPlan"
|
||||||
|
@ -1,161 +1,166 @@
|
|||||||
<template>
|
<template>
|
||||||
<PageHead bordered>
|
<div class="min-h-[160vh]">
|
||||||
<div class="flex flex-col">
|
<PageHead bordered>
|
||||||
<h1 class="text-2xl font-bold">{{ floor?.displayName }}</h1>
|
<div class="flex flex-col">
|
||||||
<span class="text-sm font-light text-gray-800 line-clamp-1"
|
<h1 class="text-2xl font-bold">{{ floor?.displayName }}</h1>
|
||||||
>Floor {{ floor?.number }} in {{ building?.displayName }}</span
|
<span class="text-sm font-light text-gray-800 line-clamp-1"
|
||||||
>
|
>Floor {{ floor?.number }} in {{ building?.displayName }}</span
|
||||||
</div>
|
|
||||||
|
|
||||||
<Menu
|
|
||||||
title="Actions"
|
|
||||||
class="self-end"
|
|
||||||
:options="[
|
|
||||||
{
|
|
||||||
title: 'Edit floor plan',
|
|
||||||
link: {
|
|
||||||
name: 'planner',
|
|
||||||
query: { buildingId: building?.id, floorId: floor?.id },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</PageHead>
|
|
||||||
|
|
||||||
<button
|
|
||||||
@click="showMap = !showMap"
|
|
||||||
:class="[
|
|
||||||
showMap ? 'bg-gray-100' : 'rounded-md ring-1 ring-black ring-opacity-5',
|
|
||||||
'ml-auto flex w-full items-center space-x-2 rounded-tl-lg rounded-tr-lg px-2 py-2 shadow-md md:w-auto',
|
|
||||||
'focus:outline-none focus:ring-2 focus:ring-blue-500',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<span>{{ showMap ? 'Hide' : 'Show' }} interactive floor plan</span>
|
|
||||||
<ChevronUpIcon v-if="showMap" class="h-4 w-4" />
|
|
||||||
<ChevronDownIcon v-else class="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
name="menu-transition"
|
|
||||||
enter-active-class="transition-height ease-out duration-200"
|
|
||||||
enter-from-class="h-0"
|
|
||||||
enter-to-class="h-[70vh]"
|
|
||||||
leave-active-class="transition-height ease-in duration-150"
|
|
||||||
leave-from-class="h-[70vh]"
|
|
||||||
leave-to-class="h-0"
|
|
||||||
>
|
|
||||||
<div v-if="showMap" class="z-10 overflow-hidden bg-gray-100 shadow-md">
|
|
||||||
<HousePlanner
|
|
||||||
v-if="floorDocument"
|
|
||||||
:key="`fdoc${floorDocument.id}`"
|
|
||||||
ref="canvas"
|
|
||||||
class="h-[70vh]"
|
|
||||||
:editable="false"
|
|
||||||
:grid="false"
|
|
||||||
transparent
|
|
||||||
headless
|
|
||||||
:floor-document="floorDocument"
|
|
||||||
@click="clickOnRoom()"
|
|
||||||
@zoom="(newZoom) => (canvasZoom = newZoom)"
|
|
||||||
>
|
|
||||||
<RoomPolygon
|
|
||||||
v-for="room of rooms"
|
|
||||||
:room="room"
|
|
||||||
:zoom="canvasZoom"
|
|
||||||
:highlighted="selectedRoom?.id"
|
|
||||||
@clicked-in="(x, y) => clickOnRoom(room, x, y)"
|
|
||||||
@mouse-moved-in="(x, y) => mouseMovedInRoom(room, x, y)"
|
|
||||||
>
|
>
|
||||||
<template v-if="selectedRoom?.id === room.id">
|
</div>
|
||||||
<template v-for="storage of storages">
|
|
||||||
<StorageBubble
|
|
||||||
v-if="
|
|
||||||
!movingBubble ||
|
|
||||||
(storage.id === movingBubble?.id &&
|
|
||||||
isSet(storage) === isSet(movingBubble))
|
|
||||||
"
|
|
||||||
:storage="storage"
|
|
||||||
:class="{
|
|
||||||
'z-20':
|
|
||||||
storage.id === hoveredBubble?.id ||
|
|
||||||
storage.id === selectedStorage?.id ||
|
|
||||||
(isSet(storage) && storage.id === selectedSet?.id),
|
|
||||||
'pointer-events-none': !!movingBubble,
|
|
||||||
'pointer-events-auto': !movingBubble,
|
|
||||||
}"
|
|
||||||
@mouseenter="() => (hoveredBubble = storage)"
|
|
||||||
@start-moving="moveBubble(storage)"
|
|
||||||
>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="flex items-center space-x-1">
|
|
||||||
<span class="text-md font-bold">{{
|
|
||||||
storage.displayName
|
|
||||||
}}</span>
|
|
||||||
<span
|
|
||||||
v-if="isSet(storage)"
|
|
||||||
class="text-[0.75rem] font-bold uppercase text-gray-400"
|
|
||||||
>(Storage Set)</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row items-center space-x-1">
|
<Menu
|
||||||
<button
|
title="Actions"
|
||||||
v-if="!isSet(storage)"
|
class="self-end"
|
||||||
@click.stop="selectAndAdd(storage)"
|
:options="[
|
||||||
class="rounded-full px-1 py-1 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
{
|
||||||
title="Insert into Storage"
|
title: 'Edit floor plan',
|
||||||
>
|
link: {
|
||||||
<ArrowDownOnSquareIcon
|
name: 'planner',
|
||||||
aria-hidden="true"
|
query: { buildingId: building?.id, floorId: floor?.id },
|
||||||
class="h-4 w-4"
|
},
|
||||||
/>
|
},
|
||||||
</button>
|
]"
|
||||||
<button
|
/>
|
||||||
@click.stop="selectStorage(storage)"
|
</PageHead>
|
||||||
class="rounded-full px-1 py-1 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
title="Open Storage"
|
<button
|
||||||
>
|
@click="showMap = !showMap"
|
||||||
<ArrowUpOnSquareIcon aria-hidden="true" class="h-4 w-4" />
|
:class="[
|
||||||
</button>
|
showMap ? 'bg-gray-100' : 'rounded-md ring-1 ring-black ring-opacity-5',
|
||||||
|
'ml-auto flex w-full items-center space-x-2 rounded-tl-lg rounded-tr-lg px-2 py-2 shadow-md md:w-auto',
|
||||||
|
'focus:outline-none focus:ring-2 focus:ring-blue-500',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span>{{ showMap ? 'Hide' : 'Show' }} interactive floor plan</span>
|
||||||
|
<ChevronUpIcon v-if="showMap" class="h-4 w-4" />
|
||||||
|
<ChevronDownIcon v-else class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
name="menu-transition"
|
||||||
|
enter-active-class="transition-height ease-out duration-200"
|
||||||
|
enter-from-class="h-0"
|
||||||
|
enter-to-class="h-[70vh]"
|
||||||
|
leave-active-class="transition-height ease-in duration-150"
|
||||||
|
leave-from-class="h-[70vh]"
|
||||||
|
leave-to-class="h-0"
|
||||||
|
>
|
||||||
|
<div v-if="showMap" class="z-10 overflow-hidden bg-gray-100 shadow-md">
|
||||||
|
<HousePlanner
|
||||||
|
v-if="floorDocument"
|
||||||
|
:key="`fdoc${floorDocument.id}`"
|
||||||
|
ref="canvas"
|
||||||
|
class="h-[70vh]"
|
||||||
|
:editable="false"
|
||||||
|
:grid="false"
|
||||||
|
transparent
|
||||||
|
headless
|
||||||
|
:floor-document="floorDocument"
|
||||||
|
@click="clickOnRoom()"
|
||||||
|
@zoom="(newZoom) => (canvasZoom = newZoom)"
|
||||||
|
>
|
||||||
|
<RoomPolygon
|
||||||
|
v-for="room of rooms"
|
||||||
|
:room="room"
|
||||||
|
:zoom="canvasZoom"
|
||||||
|
:highlighted="selectedRoom?.id"
|
||||||
|
@clicked-in="(x, y) => clickOnRoom(room, x, y)"
|
||||||
|
@mouse-moved-in="(x, y) => mouseMovedInRoom(room, x, y)"
|
||||||
|
>
|
||||||
|
<template v-if="selectedRoom?.id === room.id">
|
||||||
|
<template v-for="storage of storages">
|
||||||
|
<StorageBubble
|
||||||
|
v-if="
|
||||||
|
!movingBubble ||
|
||||||
|
(storage.id === movingBubble?.id &&
|
||||||
|
isSet(storage) === isSet(movingBubble))
|
||||||
|
"
|
||||||
|
:storage="storage"
|
||||||
|
:class="{
|
||||||
|
'z-20':
|
||||||
|
storage.id === hoveredBubble?.id ||
|
||||||
|
storage.id === selectedStorage?.id ||
|
||||||
|
(isSet(storage) && storage.id === selectedSet?.id),
|
||||||
|
'pointer-events-none': !!movingBubble,
|
||||||
|
'pointer-events-auto': !movingBubble,
|
||||||
|
}"
|
||||||
|
@mouseenter="() => (hoveredBubble = storage)"
|
||||||
|
@start-moving="moveBubble(storage)"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center space-x-1">
|
||||||
|
<span class="text-md font-bold">{{
|
||||||
|
storage.displayName
|
||||||
|
}}</span>
|
||||||
|
<span
|
||||||
|
v-if="isSet(storage)"
|
||||||
|
class="text-[0.75rem] font-bold uppercase text-gray-400"
|
||||||
|
>(Storage Set)</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-row items-center space-x-1">
|
||||||
|
<button
|
||||||
|
v-if="!isSet(storage)"
|
||||||
|
@click.stop="selectAndAdd(storage)"
|
||||||
|
class="rounded-full px-1 py-1 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
title="Insert into Storage"
|
||||||
|
>
|
||||||
|
<ArrowDownOnSquareIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
class="h-4 w-4"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click.stop="selectStorage(storage)"
|
||||||
|
class="rounded-full px-1 py-1 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
title="Open Storage"
|
||||||
|
>
|
||||||
|
<ArrowUpOnSquareIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
class="h-4 w-4"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<span class="text-sm" v-if="!isSet(storage)"
|
||||||
<span class="text-sm" v-if="!isSet(storage)"
|
>Stored items:
|
||||||
>Stored items:
|
{{ (storage as StorageListItem).itemCount }}</span
|
||||||
{{ (storage as StorageListItem).itemCount }}</span
|
>
|
||||||
>
|
<span class="text-sm" v-else
|
||||||
<span class="text-sm" v-else
|
>Storages:
|
||||||
>Storages:
|
{{ (storage as StorageSetListItem).storages.length }}</span
|
||||||
{{ (storage as StorageSetListItem).storages.length }}</span
|
>
|
||||||
>
|
</StorageBubble>
|
||||||
</StorageBubble>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</RoomPolygon>
|
||||||
</RoomPolygon>
|
</HousePlanner>
|
||||||
</HousePlanner>
|
</div>
|
||||||
</div>
|
</Transition>
|
||||||
</Transition>
|
|
||||||
|
|
||||||
<ItemSelector
|
<ItemSelector
|
||||||
:class="{ 'mt-2': !showMap }"
|
:class="{ 'mt-2': !showMap }"
|
||||||
:rooms="rooms"
|
:rooms="rooms"
|
||||||
:storages="storages"
|
:storages="storages"
|
||||||
:selected-room="selectedRoom"
|
:selected-room="selectedRoom"
|
||||||
:selected-storage="selectedStorage"
|
:selected-storage="selectedStorage"
|
||||||
:selected-set="selectedSet"
|
:selected-set="selectedSet"
|
||||||
@select-room="selectRoomFromList"
|
@select-room="selectRoomFromList"
|
||||||
@select-storage="selectStorage"
|
@select-storage="selectStorage"
|
||||||
@new-storage="addNewStorage"
|
@new-storage="addNewStorage"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StorageView
|
<StorageView
|
||||||
v-if="selectedStorage && building"
|
v-if="selectedStorage && building"
|
||||||
ref="storageViewRef"
|
ref="storageViewRef"
|
||||||
@storage-added="newStoredItemAdded"
|
@storage-added="newStoredItemAdded"
|
||||||
:storage="selectedStorage"
|
:storage="selectedStorage"
|
||||||
:building="building"
|
:building="building"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NewStorageModal ref="newStorage" @added="storageAdded" />
|
<NewStorageModal ref="newStorage" @added="storageAdded" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
Loading…
Reference in New Issue
Block a user