parent
b875c2465d
commit
710a904323
@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<div class="relative" ref="wrapper">
|
||||
<slot name="trigger" :title="title" :open="open" :toggle="toggle">
|
||||
<button type="button" @click="() => toggle()" :aria-expanded="open">
|
||||
<span>{{ title }}</span>
|
||||
<ChevronDownIcon class="ml-2 h-5 w-5" />
|
||||
</button>
|
||||
</slot>
|
||||
|
||||
<Transition
|
||||
name="menu-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"
|
||||
>
|
||||
<slot :title="title" :open="open" :toggle="toggle" v-if="open"></slot>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ChevronDownIcon } from '@heroicons/vue/24/outline';
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { onBeforeRouteLeave } from 'vue-router';
|
||||
|
||||
const open = ref(false);
|
||||
const wrapper = ref();
|
||||
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
}>();
|
||||
|
||||
const toggle = (to?: boolean) => {
|
||||
open.value = to ?? !open.value;
|
||||
};
|
||||
|
||||
const event = (e: MouseEvent) => {
|
||||
if (
|
||||
wrapper.value.contains(e.target as HTMLElement) &&
|
||||
!(e.target as HTMLElement).closest('a')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', event);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', event);
|
||||
});
|
||||
|
||||
onBeforeRouteLeave(() => {
|
||||
toggle(false);
|
||||
});
|
||||
</script>
|
@ -1,56 +0,0 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<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="[
|
||||
positionClass,
|
||||
'absolute z-10 -ml-4 mt-1 w-screen max-w-xs transform px-2 sm:px-0 lg:ml-0',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
class="overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ChevronDownIcon } from '@heroicons/vue/24/outline';
|
||||
import { computed } from '@vue/reactivity';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onBeforeRouteLeave } from 'vue-router';
|
||||
import Dropdown from './Dropdown.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
position?: 'left' | 'center' | 'right';
|
||||
}>();
|
||||
|
||||
const positionClass = computed(() => {
|
||||
if (!props.position || props.position === 'center')
|
||||
return 'left-1/2 -translate-x-1/2';
|
||||
if (props.position === 'left') return 'left-0';
|
||||
if (props.position === 'right') return 'right-0';
|
||||
return '';
|
||||
});
|
||||
</script>
|
@ -1,19 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
class="mb-2 flex items-center justify-between border-b-2 border-gray-100 pb-2"
|
||||
:class="[
|
||||
'mb-2 flex items-center justify-between pb-2',
|
||||
bordered ? 'border-b-2 border-gray-100' : '',
|
||||
]"
|
||||
>
|
||||
<slot />
|
||||
<DropdownButton title="Actions" :position="'right'" v-if="withActions">
|
||||
<div class="flex flex-col">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</DropdownButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DropdownButton from './DropdownButton.vue';
|
||||
const props = defineProps<{
|
||||
withActions?: boolean;
|
||||
defineProps<{
|
||||
bordered?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<Menu as="div" class="relative inline-block text-left">
|
||||
<div>
|
||||
<slot name="trigger" :title="title">
|
||||
<MenuButton
|
||||
:class="[
|
||||
'inline-flex w-full justify-center rounded-md bg-opacity-20 px-4 py-2',
|
||||
'text-sm font-medium text-black hover:bg-opacity-30',
|
||||
'focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-opacity-75',
|
||||
buttonClass,
|
||||
]"
|
||||
>
|
||||
<slot name="default">
|
||||
{{ title }}
|
||||
<ChevronDownIcon
|
||||
class="ml-2 -mr-1 h-5 w-5"
|
||||
aria-hidden="true"
|
||||
v-if="!hideChevron"
|
||||
/>
|
||||
</slot>
|
||||
</MenuButton>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<transition
|
||||
enter-active-class="transition duration-100 ease-out"
|
||||
enter-from-class="transform scale-95 opacity-0"
|
||||
enter-to-class="transform scale-100 opacity-100"
|
||||
leave-active-class="transition duration-75 ease-in"
|
||||
leave-from-class="transform scale-100 opacity-100"
|
||||
leave-to-class="transform scale-95 opacity-0"
|
||||
>
|
||||
<MenuItems
|
||||
:class="[
|
||||
'absolute z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg',
|
||||
'ring-1 ring-black ring-opacity-5 focus:outline-none',
|
||||
positionClass,
|
||||
]"
|
||||
>
|
||||
<div class="px-1 py-1">
|
||||
<MenuItem
|
||||
as="template"
|
||||
v-for="(option, index) of options"
|
||||
:key="option.key || index"
|
||||
v-slot="{ active }"
|
||||
>
|
||||
<slot name="option" :option="option" :active="active">
|
||||
<RouterLink v-if="option.link" :to="option.link">
|
||||
<MenuOption :active="active" :option="option" />
|
||||
</RouterLink>
|
||||
<MenuOption
|
||||
v-else
|
||||
:active="active"
|
||||
:option="option"
|
||||
@clicked="() => option.onClick?.()"
|
||||
/>
|
||||
</slot>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</MenuItems>
|
||||
</transition>
|
||||
</Menu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';
|
||||
import { ChevronDownIcon } from '@heroicons/vue/24/outline';
|
||||
import { Component, computed } from 'vue';
|
||||
import { RouteLocationRaw, RouterLink } from 'vue-router';
|
||||
import MenuOption from './MenuOption.vue';
|
||||
|
||||
export interface MenuOption {
|
||||
title: string;
|
||||
key?: string;
|
||||
link?: RouteLocationRaw;
|
||||
icon?: Component;
|
||||
onClick?: () => void;
|
||||
component?: Component;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
options: MenuOption[];
|
||||
buttonClass?: string;
|
||||
hideChevron?: boolean;
|
||||
position?: 'left' | 'center' | 'right';
|
||||
}>();
|
||||
|
||||
const positionClass = computed(() => {
|
||||
if (!props.position || props.position === 'right') return 'right-0';
|
||||
if (props.position === 'center') return 'left-1/2 -translate-x-1/2';
|
||||
if (props.position === 'left') return 'left-0';
|
||||
return '';
|
||||
});
|
||||
</script>
|
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<button
|
||||
:class="[
|
||||
active ? 'bg-blue-500 text-white' : 'text-gray-900',
|
||||
'group flex w-full items-center rounded-md px-2 py-2 text-sm',
|
||||
]"
|
||||
@click="emit('clicked')"
|
||||
>
|
||||
<component v-if="option.icon" :is="option.icon" class="mr-2 h-5 w-5" />
|
||||
<component
|
||||
v-if="option.component"
|
||||
:is="option.component"
|
||||
:option="option"
|
||||
/>
|
||||
<span v-else>{{ option.title }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { MenuOption } from './Menu.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
active: boolean;
|
||||
option: MenuOption;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'clicked'): void;
|
||||
}>();
|
||||
</script>
|
@ -1,173 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'flex flex-col md:grid',
|
||||
selectedSet ? 'md:grid-cols-4' : 'md:grid-cols-3',
|
||||
]"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<button
|
||||
v-for="room of rooms"
|
||||
@click="emit('selectRoom', room)"
|
||||
:class="[
|
||||
selectedRoom?.id === room.id
|
||||
? 'bg-blue-100 hover:bg-blue-200'
|
||||
: 'hover:bg-blue-100',
|
||||
'flex items-center justify-between border-b-2 border-gray-100 py-2 px-2',
|
||||
]"
|
||||
>
|
||||
<span>{{ room.displayName }}</span> <ChevronRightIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<template v-if="selectedRoom?.id">
|
||||
<div class="my-2 flex justify-center md:hidden">
|
||||
<ChevronDoubleDownIcon class="h-6 w-6" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<button
|
||||
v-for="storage of storages"
|
||||
@click="emit('selectStorage', storage)"
|
||||
:class="[
|
||||
(isSet(storage) && selectedSet?.id === storage.id) ||
|
||||
(selectedStorage?.id === storage.id && !isSet(storage))
|
||||
? 'bg-blue-100 hover:bg-blue-200'
|
||||
: 'hover:bg-blue-100',
|
||||
'flex items-center justify-between border-b-2 border-gray-100 py-2 px-2',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
>{{ storage.displayName }}
|
||||
|
||||
<span v-if="!isSet(storage)"
|
||||
>({{ (storage as StorageListItem).itemCount }})</span
|
||||
>
|
||||
<span v-else>
|
||||
(set) ({{
|
||||
(storage as StorageSetListItem).storages.length
|
||||
}})</span
|
||||
>
|
||||
</span>
|
||||
<ChevronRightIcon class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div class="mx-auto flex space-x-2 py-2">
|
||||
<button
|
||||
@click="emit('newStorage', false)"
|
||||
class="flex items-center space-x-1 text-blue-500 hover:text-blue-600 hover:underline"
|
||||
>
|
||||
<PlusIcon class="h-4 w-4" /> <span>Add Storage</span>
|
||||
</button>
|
||||
<span>·</span>
|
||||
<button
|
||||
@click="emit('newStorage', true)"
|
||||
class="flex items-center space-x-1 text-blue-500 hover:text-blue-600 hover:underline"
|
||||
>
|
||||
<PlusIcon class="h-4 w-4" /> <span>Add Storage Set</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="selectedSet">
|
||||
<div class="my-2 flex justify-center md:hidden">
|
||||
<ChevronDoubleDownIcon class="h-6 w-6" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<button
|
||||
v-for="storage of selectedSet.storages"
|
||||
@click="emit('selectStorage', storage, selectedSet)"
|
||||
:class="[
|
||||
selectedStorage?.id === storage.id && !isSet(storage)
|
||||
? 'bg-blue-100 hover:bg-blue-200'
|
||||
: 'hover:bg-blue-100',
|
||||
'flex items-center justify-between border-b-2 border-gray-100 py-2 px-2',
|
||||
]"
|
||||
>
|
||||
<span>{{ storage.displayName }} ({{ storage.itemCount }})</span>
|
||||
<ChevronRightIcon class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div class="mx-auto flex space-x-2 py-2">
|
||||
<button
|
||||
@click="emit('newStorage', selectedSet!)"
|
||||
class="flex items-center space-x-1 text-blue-500 hover:text-blue-600 hover:underline"
|
||||
>
|
||||
<PlusIcon class="h-4 w-4" /> <span>Add Storage</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="selectedStorage?.items">
|
||||
<div class="my-2 flex justify-center md:hidden">
|
||||
<ChevronDoubleDownIcon class="h-6 w-6" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<button
|
||||
v-for="item of selectedStorage.items"
|
||||
:class="[
|
||||
'hover:bg-blue-100',
|
||||
'flex items-center justify-between border-b-2 border-gray-100 py-2 px-2',
|
||||
]"
|
||||
>
|
||||
<span>{{ item.item.displayName }}</span>
|
||||
<ChevronRightIcon class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div class="mx-auto flex space-x-2 py-2">
|
||||
<button
|
||||
@click="emit('addItem', selectedStorage!)"
|
||||
class="flex items-center space-x-1 text-blue-500 hover:text-blue-600 hover:underline"
|
||||
>
|
||||
<PlusIcon class="h-4 w-4" /> <span>Add Items</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
class="my-2 mb-4 flex justify-center md:hidden"
|
||||
v-if="selectedStorage?.items?.length"
|
||||
>
|
||||
<ChevronDoubleDownIcon class="h-6 w-6" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ChevronDoubleDownIcon,
|
||||
ChevronRightIcon,
|
||||
PlusIcon,
|
||||
} from '@heroicons/vue/24/outline';
|
||||
import isSet from '../../../utils/is-storage-set';
|
||||
import {
|
||||
StorageListItem,
|
||||
StorageSetListItem,
|
||||
StorageSharedType,
|
||||
} from '../../../interfaces/storage.interfaces';
|
||||
import { RoomLayoutObject } from '../../../interfaces/room.interfaces';
|
||||
|
||||
const props = defineProps<{
|
||||
rooms: RoomLayoutObject[];
|
||||
storages: StorageSharedType[];
|
||||
selectedRoom?: RoomLayoutObject;
|
||||
selectedSet?: StorageSetListItem;
|
||||
selectedStorage?: StorageListItem;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: 'selectStorage',
|
||||
storage: StorageSharedType,
|
||||
parentSet?: StorageSetListItem
|
||||
): void;
|
||||
(e: 'selectRoom', room: RoomLayoutObject): void;
|
||||
(e: 'newStorage', set: boolean | StorageSetListItem): void;
|
||||
(e: 'addItem', storage: StorageListItem): void;
|
||||
}>();
|
||||
</script>
|
@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
:class="[
|
||||
'flex flex-col md:grid',
|
||||
selectedSet ? 'md:grid-cols-3' : 'md:grid-cols-2',
|
||||
]"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
class="border-b-4 border-gray-300 bg-gray-100 px-2 py-2 text-center font-bold"
|
||||
>
|
||||
Select Room
|
||||
</div>
|
||||
<button
|
||||
v-for="room of rooms"
|
||||
@click="emit('selectRoom', room)"
|
||||
:aria-expanded="selectedRoom?.id === room.id"
|
||||
:class="[
|
||||
selectedRoom?.id === room.id
|
||||
? 'bg-blue-100 hover:bg-blue-200'
|
||||
: 'hover:bg-blue-100',
|
||||
'flex items-center justify-between border-b-1 border-gray-100 py-2 px-2',
|
||||
]"
|
||||
>
|
||||
<span>{{ room.displayName }}</span>
|
||||
<ChevronRightIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<template v-if="selectedRoom?.id">
|
||||
<div class="my-2 flex justify-center md:hidden">
|
||||
<ChevronDoubleDownIcon class="h-6 w-6" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
class="border-b-4 border-gray-300 bg-gray-100 px-2 py-2 text-center font-bold"
|
||||
>
|
||||
Select Storage / Set
|
||||
</div>
|
||||
<button
|
||||
v-for="storage of storages"
|
||||
@click="emit('selectStorage', storage)"
|
||||
:aria-expanded="isStorageOrSetSelected(storage)"
|
||||
:class="[
|
||||
isStorageOrSetSelected(storage)
|
||||
? 'bg-blue-100 hover:bg-blue-200'
|
||||
: 'hover:bg-blue-100',
|
||||
'flex items-center justify-between border-b-1 border-gray-100 py-2 px-2',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
>{{ storage.displayName }}
|
||||
|
||||
<span v-if="!isSet(storage)"
|
||||
>({{ (storage as StorageListItem).itemCount }})</span
|
||||
>
|
||||
<span v-else>
|
||||
(set) ({{
|
||||
(storage as StorageSetListItem).storages.length
|
||||
}})</span
|
||||
>
|
||||
</span>
|
||||
<ChevronRightIcon class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div class="mx-auto flex space-x-2 py-2">
|
||||
<button
|
||||
@click="emit('newStorage', false)"
|
||||
class="flex items-center space-x-1 text-blue-500 hover:text-blue-600 hover:underline"
|
||||
>
|
||||
<PlusIcon class="h-4 w-4" /> <span>Add Storage</span>
|
||||
</button>
|
||||
<span>·</span>
|
||||
<button
|
||||
@click="emit('newStorage', true)"
|
||||
class="flex items-center space-x-1 text-blue-500 hover:text-blue-600 hover:underline"
|
||||
>
|
||||
<PlusIcon class="h-4 w-4" /> <span>Add Storage Set</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="selectedSet">
|
||||
<div class="my-2 flex justify-center md:hidden">
|
||||
<ChevronDoubleDownIcon class="h-6 w-6" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
class="border-b-4 border-gray-300 bg-gray-100 px-2 py-2 text-center font-bold"
|
||||
>
|
||||
Select Storage
|
||||
</div>
|
||||
<button
|
||||
v-for="storage of selectedSet.storages"
|
||||
@click="emit('selectStorage', storage, selectedSet)"
|
||||
:aria-expanded="isStorageSelected(storage)"
|
||||
:class="[
|
||||
isStorageSelected(storage)
|
||||
? 'bg-blue-100 hover:bg-blue-200'
|
||||
: 'hover:bg-blue-100',
|
||||
'flex items-center justify-between border-b-1 border-gray-100 py-2 px-2',
|
||||
]"
|
||||
>
|
||||
<span>{{ storage.displayName }} ({{ storage.itemCount }})</span>
|
||||
<ChevronRightIcon class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div class="mx-auto flex space-x-2 py-2">
|
||||
<button
|
||||
@click="emit('newStorage', selectedSet!)"
|
||||
class="flex items-center space-x-1 text-blue-500 hover:text-blue-600 hover:underline"
|
||||
>
|
||||
<PlusIcon class="h-4 w-4" /> <span>Add Storage</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="my-2 mb-4 flex justify-center" v-if="selectedStorage">
|
||||
<ChevronDoubleDownIcon class="h-6 w-6" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ChevronDoubleDownIcon,
|
||||
ChevronRightIcon,
|
||||
PlusIcon,
|
||||
} from '@heroicons/vue/24/outline';
|
||||
import isSet from '../../../utils/is-storage-set';
|
||||
import {
|
||||
StorageListItem,
|
||||
StorageSetListItem,
|
||||
StorageSharedType,
|
||||
} from '../../../interfaces/storage.interfaces';
|
||||
import { RoomLayoutObject } from '../../../interfaces/room.interfaces';
|
||||
|
||||
const props = defineProps<{
|
||||
rooms: RoomLayoutObject[];
|
||||
storages: StorageSharedType[];
|
||||
selectedRoom?: RoomLayoutObject;
|
||||
selectedSet?: StorageSetListItem;
|
||||
selectedStorage?: StorageListItem;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: 'selectStorage',
|
||||
storage: StorageSharedType,
|
||||
parentSet?: StorageSetListItem
|
||||
): void;
|
||||
(e: 'selectRoom', room: RoomLayoutObject): void;
|
||||
(e: 'newStorage', set: boolean | StorageSetListItem): void;
|
||||
(e: 'addItem', storage: StorageListItem): void;
|
||||
}>();
|
||||
|
||||
const isStorageOrSetSelected = (storage: StorageSharedType) =>
|
||||
(isSet(storage) && props.selectedSet?.id === storage.id) ||
|
||||
(props.selectedStorage?.id === storage.id && !isSet(storage));
|
||||
|
||||
const isStorageSelected = (storage: StorageSharedType) =>
|
||||
props.selectedStorage?.id === storage.id && !isSet(storage);
|
||||
</script>
|
@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div>
|
||||
<PageHead bordered>
|
||||
<h2 class="text-2xl font-bold">{{ storage.displayName }}</h2>
|
||||
|
||||
<Menu
|
||||
title="Actions"
|
||||
:options="[
|
||||
{
|
||||
title: 'Add new item',
|
||||
onClick: addNewItem,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</PageHead>
|
||||
|
||||
<span class="text-xl font-bold text-gray-400" v-if="!storage.items?.length"
|
||||
>No items in storage.</span
|
||||
>
|
||||
<div class="grid gap-2 md:grid-cols-2" v-else>
|
||||
<StoredItemCard
|
||||
v-for="stored of storage.items || []"
|
||||
class="rounded-md bg-white px-2 py-2 shadow-md ring-1 ring-black ring-opacity-5"
|
||||
size="lg"
|
||||
:stored-item="stored"
|
||||
>
|
||||
<template #actions>
|
||||
<Menu
|
||||
title=""
|
||||
button-class="px-0.5 py-0.5"
|
||||
:options="[
|
||||
{ title: 'Change details' },
|
||||
{ title: 'Add transaction' },
|
||||
]"
|
||||
>
|
||||
<EllipsisVerticalIcon class="h-5 w-5" />
|
||||
</Menu>
|
||||
</template>
|
||||
</StoredItemCard>
|
||||
</div>
|
||||
|
||||
<NewItemModal ref="newItem" @added="newStoredItemAdded" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { EllipsisVerticalIcon } from '@heroicons/vue/24/outline';
|
||||
import { ref } from 'vue';
|
||||
import NewItemModal from '../../../../components/item/NewItemModal.vue';
|
||||
import StoredItemCard from '../../../../components/item/StoredItemCard.vue';
|
||||
import Menu from '../../../../components/menu/Menu.vue';
|
||||
import PageHead from '../../../../components/PageHead.vue';
|
||||
import { BuildingListItem } from '../../../../interfaces/building.interfaces';
|
||||
import {
|
||||
StorageListItem,
|
||||
StoredItem,
|
||||
} from '../../../../interfaces/storage.interfaces';
|
||||
|
||||
const newItem = ref<InstanceType<typeof NewItemModal>>();
|
||||
const props = defineProps<{
|
||||
storage: StorageListItem;
|
||||
building: BuildingListItem;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'storageAdded', item: StoredItem): void;
|
||||
}>();
|
||||
|
||||
const newStoredItemAdded = (item: StoredItem) => {
|
||||
emit('storageAdded', item);
|
||||
};
|
||||
|
||||
const addNewItem = () => {
|
||||
newItem.value?.openModal(props.building, props.storage);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
addNewItem,
|
||||
});
|
||||
</script>
|
Loading…
Reference in new issue