updates, i18n start
This commit is contained in:
parent
8984ddd9dc
commit
8b2e2dd0aa
1651
package-lock.json
generated
1651
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
46
package.json
46
package.json
@ -9,37 +9,39 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/vue": "^1.7.13",
|
"@headlessui/vue": "^1.7.16",
|
||||||
"@heroicons/vue": "^2.0.18",
|
"@heroicons/vue": "^2.0.18",
|
||||||
"@vuepic/vue-datepicker": "^5.1.2",
|
"@vuepic/vue-datepicker": "^7.2.2",
|
||||||
"@vueuse/core": "^10.1.2",
|
"@vueuse/core": "^10.5.0",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^4.0.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"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.1.3",
|
"pinia": "^2.1.7",
|
||||||
"sass": "^1.62.1",
|
"sass": "^1.69.5",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.8",
|
||||||
|
"vue-i18n": "^9.6.5",
|
||||||
"vue-material-design-icons": "^5.2.0",
|
"vue-material-design-icons": "^5.2.0",
|
||||||
"vue-router": "^4.2.1"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.6",
|
||||||
"@tailwindcss/line-clamp": "^0.4.4",
|
"@tailwindcss/line-clamp": "^0.4.4",
|
||||||
"@types/lodash.get": "^4.4.7",
|
"@types/lodash.get": "^4.4.9",
|
||||||
"@types/lodash.omit": "^4.5.7",
|
"@types/lodash.omit": "^4.5.9",
|
||||||
"@types/lodash.pick": "^4.4.7",
|
"@types/lodash.pick": "^4.4.9",
|
||||||
"@types/lodash.set": "^4.3.7",
|
"@types/lodash.set": "^4.3.9",
|
||||||
"@vitejs/plugin-vue": "^4.2.3",
|
"@vitejs/plugin-vue": "^4.4.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.16",
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.31",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^3.0.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
"prettier-plugin-tailwindcss": "^0.5.6",
|
||||||
"tailwindcss": "^3.3.2",
|
"tailwindcss": "^3.3.5",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^4.3.8",
|
"vite": "^4.5.0",
|
||||||
"vue-tsc": "^1.6.5"
|
"vue-tsc": "^1.8.22"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
:step="step"
|
:step="step"
|
||||||
:min="min"
|
:min="min"
|
||||||
:max="max"
|
:max="max"
|
||||||
|
:autocomplete="autocomplete"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
@ -73,13 +74,14 @@ const props = withDefaults(
|
|||||||
step?: string;
|
step?: string;
|
||||||
min?: string;
|
min?: string;
|
||||||
max?: string;
|
max?: string;
|
||||||
|
autocomplete?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
required: false,
|
required: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
readonly: false,
|
readonly: false,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -96,11 +98,11 @@ const registerField = inject<(field: string) => void>('registerField');
|
|||||||
const unregisterField = inject<(field: string) => void>('unregisterField');
|
const unregisterField = inject<(field: string) => void>('unregisterField');
|
||||||
|
|
||||||
const fieldName = computed(() =>
|
const fieldName = computed(() =>
|
||||||
formGroup?.value ? `${formGroup.value}.${props.name}` : props.name
|
formGroup?.value ? `${formGroup.value}.${props.name}` : props.name,
|
||||||
);
|
);
|
||||||
|
|
||||||
const forId = computed(
|
const forId = computed(
|
||||||
() => `${props.forIdPrefix || 'form'}-${fieldName.value}`
|
() => `${props.forIdPrefix || 'form'}-${fieldName.value}`,
|
||||||
);
|
);
|
||||||
const value = computed(() => get(formData?.value, fieldName.value));
|
const value = computed(() => get(formData?.value, fieldName.value));
|
||||||
const errors = computed(() => formErrors?.value[fieldName.value] || []);
|
const errors = computed(() => formErrors?.value[fieldName.value] || []);
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
v-bind="props"
|
v-bind="props"
|
||||||
:class="[invalid ? 'dp__invalid' : '']"
|
:class="[invalid ? 'dp__invalid' : '']"
|
||||||
:uid="fieldFQN"
|
:uid="fieldFQN"
|
||||||
:placeholder="placeholder"
|
|
||||||
:state="undefined"
|
:state="undefined"
|
||||||
:format="format"
|
:format="format"
|
||||||
:model-value="(value as string)"
|
:model-value="(value as string)"
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<PopoverButton
|
<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-blue-500"
|
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-blue-500"
|
||||||
>
|
>
|
||||||
<span class="sr-only">Open menu</span>
|
<span class="sr-only">{{ $t('common.openMenu') }}</span>
|
||||||
<Bars3Icon class="h-6 w-6" aria-hidden="true" />
|
<Bars3Icon class="h-6 w-6" aria-hidden="true" />
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
</div>
|
</div>
|
||||||
@ -23,7 +23,7 @@
|
|||||||
'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',
|
'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>Buildings</span>
|
<span>{{ $t('common.buildings') }}</span>
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
:class="[
|
:class="[
|
||||||
open ? 'text-gray-600' : 'text-gray-400',
|
open ? 'text-gray-600' : 'text-gray-400',
|
||||||
@ -47,7 +47,7 @@
|
|||||||
<div
|
<div
|
||||||
class="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"
|
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">
|
<div class="relative bg-white pb-4 pt-4 sm:gap-5">
|
||||||
<template v-for="building of buildings">
|
<template v-for="building of buildings">
|
||||||
<Building :building="building" />
|
<Building :building="building" />
|
||||||
</template>
|
</template>
|
||||||
@ -60,7 +60,7 @@
|
|||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-base font-medium text-gray-500 hover:text-gray-900"
|
class="text-base font-medium text-gray-500 hover:text-gray-900"
|
||||||
>Groups</a
|
>{{ $t('common.groups') }}</a
|
||||||
>
|
>
|
||||||
</PopoverGroup>
|
</PopoverGroup>
|
||||||
<div class="hidden items-center justify-end md:flex md:flex-1 lg:w-0">
|
<div class="hidden items-center justify-end md:flex md:flex-1 lg:w-0">
|
||||||
@ -84,7 +84,7 @@
|
|||||||
<div
|
<div
|
||||||
class="divide-y-2 divide-gray-50 rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5"
|
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="px-5 pb-6 pt-5">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<router-link to="/"><HomeIcon class="h-8 w-8" /></router-link>
|
<router-link to="/"><HomeIcon class="h-8 w-8" /></router-link>
|
||||||
@ -93,13 +93,15 @@
|
|||||||
<PopoverButton
|
<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-blue-500"
|
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-blue-500"
|
||||||
>
|
>
|
||||||
<span class="sr-only">Close menu</span>
|
<span class="sr-only">{{ $t('common.closeMenu') }}</span>
|
||||||
<XMarkIcon class="h-6 w-6" aria-hidden="true" />
|
<XMarkIcon class="h-6 w-6" aria-hidden="true" />
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<span class="text-lg font-bold">Buildings</span>
|
<span class="text-lg font-bold">{{
|
||||||
|
$t('common.buildings')
|
||||||
|
}}</span>
|
||||||
<div class="-mx-5">
|
<div class="-mx-5">
|
||||||
<template v-for="building of buildings">
|
<template v-for="building of buildings">
|
||||||
<Building :building="building" />
|
<Building :building="building" />
|
||||||
@ -107,7 +109,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-6 py-6 px-5">
|
<div class="space-y-6 px-5 py-6">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<UserPill :user="user" />
|
<UserPill :user="user" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Menu title="User" :options="[{ title: 'Logout' }]">
|
<Menu :title="$t('common.user')" :options="[{ title: $t('login.logout') }]">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
class="flex flex-row items-center space-x-2 rounded-full px-1 py-1 pr-2 text-gray-500 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
class="flex flex-row items-center space-x-2 rounded-full px-1 py-1 pr-2 text-gray-500 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
@ -22,7 +22,7 @@ import { ChevronDownIcon, UserIcon } from '@heroicons/vue/24/outline';
|
|||||||
import { User } from '../../interfaces/user.interfaces';
|
import { User } from '../../interfaces/user.interfaces';
|
||||||
import Menu from '../menu/Menu.vue';
|
import Menu from '../menu/Menu.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
user: User;
|
user: User;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute top-0 left-0 z-10 w-screen sm:w-auto"
|
class="pointer-events-none absolute left-0 top-0 z-10 w-screen sm:w-auto"
|
||||||
>
|
>
|
||||||
<Transition
|
<Transition
|
||||||
enter-active-class="transition-max-height ease-out duration-200 overflow-hidden"
|
enter-active-class="transition-max-height ease-out duration-200 overflow-hidden"
|
||||||
@ -16,12 +16,12 @@
|
|||||||
>
|
>
|
||||||
<div class="h-14 px-2 py-2.5">
|
<div class="h-14 px-2 py-2.5">
|
||||||
<div class="flex flex-row items-center space-x-4 px-4">
|
<div class="flex flex-row items-center space-x-4 px-4">
|
||||||
<button @click="router.go(-1)" title="Go back">
|
<button @click="router.go(-1)" :title="$t('common.back')">
|
||||||
<span class="sr-only">Go back</span
|
<span class="sr-only">{{ $t('common.back') }}</span
|
||||||
><ChevronLeftIcon class="-ml-4 h-6 w-6" />
|
><ChevronLeftIcon class="-ml-4 h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
<div class="flex flex-row items-center space-x-4">
|
<div class="flex flex-row items-center space-x-4">
|
||||||
<label for="building">Building:</label>
|
<label for="building">{{ $t('common.building') }}:</label>
|
||||||
<select
|
<select
|
||||||
id="building"
|
id="building"
|
||||||
class="rounded-sm border-gray-300 py-1 focus:ring-2 focus:ring-blue-200"
|
class="rounded-sm border-gray-300 py-1 focus:ring-2 focus:ring-blue-200"
|
||||||
@ -29,7 +29,7 @@
|
|||||||
@change="
|
@change="
|
||||||
emit(
|
emit(
|
||||||
'update:selectedBuildingId',
|
'update:selectedBuildingId',
|
||||||
Number(($event.target as HTMLSelectElement).value)
|
Number(($event.target as HTMLSelectElement).value),
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
@ -42,7 +42,7 @@
|
|||||||
class="flex flex-row items-center space-x-4"
|
class="flex flex-row items-center space-x-4"
|
||||||
v-if="selectedBuildingId"
|
v-if="selectedBuildingId"
|
||||||
>
|
>
|
||||||
<label for="floor">Floor:</label>
|
<label for="floor">{{ $t('common.floor') }}:</label>
|
||||||
<select
|
<select
|
||||||
id="floor"
|
id="floor"
|
||||||
class="rounded-sm border-gray-300 py-1 focus:ring-2 focus:ring-blue-200"
|
class="rounded-sm border-gray-300 py-1 focus:ring-2 focus:ring-blue-200"
|
||||||
@ -50,7 +50,7 @@
|
|||||||
@change="
|
@change="
|
||||||
emit(
|
emit(
|
||||||
'update:selectedFloorId',
|
'update:selectedFloorId',
|
||||||
Number(($event.target as HTMLSelectElement).value)
|
Number(($event.target as HTMLSelectElement).value),
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
@ -59,7 +59,9 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="selectedFloorId">{{ status }}</div>
|
<div v-if="selectedFloorId">
|
||||||
|
{{ $t('planner.status.' + status) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -67,7 +69,7 @@
|
|||||||
<button
|
<button
|
||||||
:class="[
|
:class="[
|
||||||
'bg-white',
|
'bg-white',
|
||||||
'pointer-events-auto -mt-0.5 ml-2 h-8 rounded-br-md rounded-bl-md px-2 py-2 ring-1 ring-black ring-opacity-5',
|
'pointer-events-auto -mt-0.5 ml-2 h-8 rounded-bl-md rounded-br-md px-2 py-2 ring-1 ring-black ring-opacity-5',
|
||||||
]"
|
]"
|
||||||
:title="`${open ? 'Hide' : 'Open'} panel`"
|
:title="`${open ? 'Hide' : 'Open'} panel`"
|
||||||
:aria-expanded="open"
|
:aria-expanded="open"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<PlannerSidebar title="Layers">
|
<PlannerSidebar :title="$t('planner.panel.layers.title')">
|
||||||
<div class="bg-white-50 flex h-full flex-col overflow-auto">
|
<div class="bg-white-50 flex h-full flex-col overflow-auto">
|
||||||
<template v-for="layer of layers">
|
<template v-for="layer of layers">
|
||||||
<button
|
<button
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<PlannerSidebar title="Properties">
|
<PlannerSidebar :title="$t('planner.panel.properties.title')">
|
||||||
<div
|
<div
|
||||||
class="bg-white-50 flex h-full flex-col overflow-auto"
|
class="bg-white-50 flex h-full flex-col overflow-auto"
|
||||||
v-if="selectedObject && applicableProperties"
|
v-if="selectedObject && applicableProperties"
|
||||||
@ -24,6 +24,9 @@ import {
|
|||||||
} from './interfaces/properties.interfaces';
|
} from './interfaces/properties.interfaces';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import PropertyFormItem from './PropertyFormItem.vue';
|
import PropertyFormItem from './PropertyFormItem.vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
layers: Layer[];
|
layers: Layer[];
|
||||||
@ -35,44 +38,64 @@ const emit = defineEmits<{
|
|||||||
layerId: number,
|
layerId: number,
|
||||||
objectId: number,
|
objectId: number,
|
||||||
key: string,
|
key: string,
|
||||||
value: unknown
|
value: unknown,
|
||||||
): void;
|
): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const commonProps: ObjectProperty[] = [
|
const commonProps: ObjectProperty[] = [
|
||||||
{ key: 'name', title: 'Name', type: 'string' },
|
{ key: 'name', title: t('planner.properties.name'), type: 'string' },
|
||||||
{ key: 'visible', title: 'Visible', type: 'boolean', groupable: true },
|
{
|
||||||
|
key: 'visible',
|
||||||
|
title: t('planner.properties.visible'),
|
||||||
|
type: 'boolean',
|
||||||
|
groupable: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const lineProps: ObjectProperty[] = [
|
const lineProps: ObjectProperty[] = [
|
||||||
...commonProps,
|
...commonProps,
|
||||||
{ key: 'width', title: 'Line Width', type: 'number', groupable: true },
|
{
|
||||||
{ key: 'color', title: 'Color', type: 'color', groupable: true },
|
key: 'width',
|
||||||
|
title: t('planner.properties.width'),
|
||||||
|
type: 'number',
|
||||||
|
groupable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'color',
|
||||||
|
title: t('planner.properties.color'),
|
||||||
|
type: 'color',
|
||||||
|
groupable: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'lineCap',
|
key: 'lineCap',
|
||||||
title: 'Line Cap Style',
|
title: t('planner.properties.lineCap'),
|
||||||
type: 'select',
|
type: 'select',
|
||||||
groupable: true,
|
groupable: true,
|
||||||
options: [
|
options: [
|
||||||
{ value: undefined, title: '' },
|
{ value: undefined, title: '' },
|
||||||
{ value: 'butt', title: 'Butt' },
|
{ value: 'butt', title: t('planner.properties.butt') },
|
||||||
{ value: 'round', title: 'Round' },
|
{ value: 'round', title: t('planner.properties.round') },
|
||||||
{ value: 'square', title: 'Square' },
|
{ value: 'square', title: t('planner.properties.square') },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'lineJoin',
|
key: 'lineJoin',
|
||||||
title: 'Line Join Style',
|
title: t('planner.properties.lineJoin'),
|
||||||
type: 'select',
|
type: 'select',
|
||||||
groupable: true,
|
groupable: true,
|
||||||
options: [
|
options: [
|
||||||
{ value: undefined, title: '' },
|
{ value: undefined, title: '' },
|
||||||
{ value: 'miter', title: 'Miter' },
|
{ value: 'miter', title: t('planner.properties.miter') },
|
||||||
{ value: 'bevel', title: 'Bevel' },
|
{ value: 'bevel', title: t('planner.properties.bevel') },
|
||||||
{ value: 'round', title: 'Round' },
|
{ value: 'round', title: t('planner.properties.round') },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ key: 'closed', title: 'Closed', type: 'boolean', groupable: true },
|
{
|
||||||
|
key: 'closed',
|
||||||
|
title: t('planner.properties.closed'),
|
||||||
|
type: 'boolean',
|
||||||
|
groupable: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const objectTypeProperties: ObjectProperties[] = [
|
const objectTypeProperties: ObjectProperties[] = [
|
||||||
@ -95,20 +118,20 @@ const objectTypeProperties: ObjectProperties[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const currentLayer = computed(() =>
|
const currentLayer = computed(() =>
|
||||||
props.layers.find((layer) => layer.active && layer.visible)
|
props.layers.find((layer) => layer.active && layer.visible),
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO multi edit
|
// TODO multi edit
|
||||||
const selectedObject = computed(
|
const selectedObject = computed(
|
||||||
() => currentLayer.value?.contents?.filter((obj) => obj.selected)[0]
|
() => currentLayer.value?.contents?.filter((obj) => obj.selected)[0],
|
||||||
);
|
);
|
||||||
|
|
||||||
const applicableProperties = computed(
|
const applicableProperties = computed(
|
||||||
() =>
|
() =>
|
||||||
selectedObject.value &&
|
selectedObject.value &&
|
||||||
objectTypeProperties.find(
|
objectTypeProperties.find(
|
||||||
(prop) => prop.type === selectedObject.value?.type
|
(prop) => prop.type === selectedObject.value?.type,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateProp = (prop: ObjectProperty, value: unknown) => {
|
const updateProp = (prop: ObjectProperty, value: unknown) => {
|
||||||
@ -119,7 +142,7 @@ const updateProp = (prop: ObjectProperty, value: unknown) => {
|
|||||||
currentLayer.value!.id,
|
currentLayer.value!.id,
|
||||||
selectedObject.value!.id,
|
selectedObject.value!.id,
|
||||||
prop.key,
|
prop.key,
|
||||||
value
|
value,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,13 +3,18 @@
|
|||||||
<button
|
<button
|
||||||
:class="[
|
:class="[
|
||||||
open ? 'bg-gray-200' : 'bg-white',
|
open ? 'bg-gray-200' : 'bg-white',
|
||||||
'h-8 rounded-tl-md rounded-bl-md px-2 py-2 ring-1 ring-black ring-opacity-5',
|
'h-8 rounded-bl-md rounded-tl-md px-2 py-2 ring-1 ring-black ring-opacity-5',
|
||||||
]"
|
]"
|
||||||
:title="`${open ? 'Hide' : 'Open'} panel`"
|
:title="`${$t(open ? 'common.hide' : 'common.open')} ${$t(
|
||||||
|
'planner.panel.open',
|
||||||
|
)}`"
|
||||||
:aria-expanded="open"
|
:aria-expanded="open"
|
||||||
@click="() => (open = !open)"
|
@click="() => (open = !open)"
|
||||||
>
|
>
|
||||||
<span class="sr-only">{{ open ? 'Hide' : 'Open' }} panel</span>
|
<span class="sr-only"
|
||||||
|
>{{ $t(open ? 'common.hide' : 'common.open') }}
|
||||||
|
{{ $t('planner.panel.open') }}</span
|
||||||
|
>
|
||||||
<ChevronDoubleRightIcon class="h-4 w-4" v-if="open" />
|
<ChevronDoubleRightIcon class="h-4 w-4" v-if="open" />
|
||||||
<ChevronDoubleLeftIcon class="h-4 w-4" v-else />
|
<ChevronDoubleLeftIcon class="h-4 w-4" v-else />
|
||||||
</button>
|
</button>
|
||||||
|
12
src/i18n.ts
Normal file
12
src/i18n.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { createI18n } from 'vue-i18n';
|
||||||
|
import en from '@/locales/en.json';
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
locale: 'en',
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
globalInjection: true,
|
||||||
|
legacy: false,
|
||||||
|
messages: { en },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
86
src/locales/en.json
Normal file
86
src/locales/en.json
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in to your account",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"email": "Email address",
|
||||||
|
"password": "Password",
|
||||||
|
"logout": "Logout"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"user": "User",
|
||||||
|
"users": "Users",
|
||||||
|
"actions": "Actions",
|
||||||
|
"back": "Go back",
|
||||||
|
"building": "Building",
|
||||||
|
"buildings": "Buildings",
|
||||||
|
"group": "Group",
|
||||||
|
"groups": "Groups",
|
||||||
|
"floor": "Floor",
|
||||||
|
"floors": "Floors",
|
||||||
|
"hide": "Hide",
|
||||||
|
"show": "Show",
|
||||||
|
"open": "Open",
|
||||||
|
"close": "Close",
|
||||||
|
"storage": "Storage",
|
||||||
|
"storages": "Storages",
|
||||||
|
"set": "Set",
|
||||||
|
"storageSet": "Storage set",
|
||||||
|
"openMenu": "Open menu",
|
||||||
|
"closeMenu": "Close menu"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Dashboard",
|
||||||
|
"expiringSoon": "Expiring soon",
|
||||||
|
"intro": "Hello, {name}"
|
||||||
|
},
|
||||||
|
"buildings": {
|
||||||
|
"editPlans": "Edit floor plans",
|
||||||
|
"editPlan": "Edit floor plan",
|
||||||
|
"floorIn": "Floor {floor} in {building}",
|
||||||
|
"floor": "Floor {floor}",
|
||||||
|
"interactive": "interactive floor plan"
|
||||||
|
},
|
||||||
|
"planner": {
|
||||||
|
"choose": "Choose a floor to edit from the top left",
|
||||||
|
"status": {
|
||||||
|
"modified": "Modified",
|
||||||
|
"noChanges": "No changes",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"saved": "Saved!",
|
||||||
|
"failed": "Failed to save!"
|
||||||
|
},
|
||||||
|
"panel": {
|
||||||
|
"open": "panel",
|
||||||
|
"layers": {
|
||||||
|
"title": "Layers"
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"title": "Properties"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": "Name",
|
||||||
|
"visible": "Visible",
|
||||||
|
"width": "Line Width",
|
||||||
|
"color": "Color",
|
||||||
|
"lineCap": "Line Cap Style",
|
||||||
|
"butt": "Butt",
|
||||||
|
"round": "Round",
|
||||||
|
"square": "Square",
|
||||||
|
"lineJoin": "Line Join Style",
|
||||||
|
"miter": "Miter",
|
||||||
|
"bevel": "Bevel",
|
||||||
|
"closed": "Closed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"selectRoom": "Select Room",
|
||||||
|
"selectStorageSet": "Select Storage / Set",
|
||||||
|
"selectStorage": "Select Storage",
|
||||||
|
"move": "Move storage",
|
||||||
|
"set": "set",
|
||||||
|
"add": "Add Storage",
|
||||||
|
"addSet": "Add Storage Set",
|
||||||
|
"storedItems": "Stored items"
|
||||||
|
}
|
||||||
|
}
|
@ -3,11 +3,13 @@ import './style.scss';
|
|||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
|
import i18n from './i18n';
|
||||||
|
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.use(i18n);
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { User } from '../interfaces/user.interfaces';
|
import { User } from '../interfaces/user.interfaces';
|
||||||
import { useLocalStorage } from '@vueuse/core';
|
import { useLocalStorage } from '@vueuse/core';
|
||||||
import jwtDecode from 'jwt-decode';
|
import { jwtDecode } from 'jwt-decode';
|
||||||
import { BACKEND_URL } from '../constants';
|
import { BACKEND_URL } from '../constants';
|
||||||
import jfetch from '../utils/jfetch';
|
import jfetch from '../utils/jfetch';
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ export const useUserStore = defineStore('user', {
|
|||||||
Authorization: `Basic ${header}`,
|
Authorization: `Basic ${header}`,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
}),
|
}),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
this.accessToken = tokenResponse.access_token;
|
this.accessToken = tokenResponse.access_token;
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<StandardLayout>
|
<StandardLayout>
|
||||||
<PageHead bordered><h1 class="text-2xl font-bold">Dashboard</h1></PageHead>
|
<PageHead bordered
|
||||||
|
><h1 class="text-2xl font-bold">{{ $t('dashboard.title') }}</h1></PageHead
|
||||||
|
>
|
||||||
|
|
||||||
<p>Hello, {{ user.name }}</p>
|
<p>{{ $t('dashboard.intro', { name: user.name }) }}</p>
|
||||||
|
|
||||||
<template v-if="expiringItems?.length">
|
<template v-if="expiringItems?.length">
|
||||||
<PageHead class="mt-4"
|
<PageHead class="mt-4"
|
||||||
><h2 class="text-xl font-bold">Expiring soon</h2></PageHead
|
><h2 class="text-xl font-bold">
|
||||||
|
{{ $t('dashboard.expiringSoon') }}
|
||||||
|
</h2></PageHead
|
||||||
>
|
>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -21,7 +25,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<PageHead class="mt-4">
|
<PageHead class="mt-4">
|
||||||
<h2 class="text-xl font-bold">Buildings</h2>
|
<h2 class="text-xl font-bold">{{ $t('common.buildings') }}</h2>
|
||||||
</PageHead>
|
</PageHead>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -20,15 +20,15 @@
|
|||||||
(layers, rooms, boundingBox) =>
|
(layers, rooms, boundingBox) =>
|
||||||
updateDocument(layers, rooms, boundingBox)
|
updateDocument(layers, rooms, boundingBox)
|
||||||
"
|
"
|
||||||
@edited="status = 'Modified'"
|
@edited="status = 'modified'"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="flex h-full w-full select-none items-center justify-center text-lg"
|
class="flex h-full w-full select-none items-center justify-center text-lg"
|
||||||
v-else
|
v-else
|
||||||
>
|
>
|
||||||
<span class="font-bold uppercase text-gray-300"
|
<span class="font-bold uppercase text-gray-300">{{
|
||||||
>Choose a floor to edit from the top left</span
|
$t('planner.choose')
|
||||||
>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -52,13 +52,13 @@ const route = useRoute();
|
|||||||
const { buildings, floors } = storeToRefs(building);
|
const { buildings, floors } = storeToRefs(building);
|
||||||
const selectedBuildingId = ref<number>();
|
const selectedBuildingId = ref<number>();
|
||||||
const selectedFloorId = ref<number>();
|
const selectedFloorId = ref<number>();
|
||||||
const status = ref('No changes');
|
const status = ref('noChanges');
|
||||||
const plannerRef = ref<InstanceType<typeof HousePlanner>>();
|
const plannerRef = ref<InstanceType<typeof HousePlanner>>();
|
||||||
|
|
||||||
const currentFloor = computed(
|
const currentFloor = computed(
|
||||||
() =>
|
() =>
|
||||||
selectedFloorId.value &&
|
selectedFloorId.value &&
|
||||||
floors.value.find((floor) => floor.id === selectedFloorId.value)
|
floors.value.find((floor) => floor.id === selectedFloorId.value),
|
||||||
);
|
);
|
||||||
const floorPlan = computed(
|
const floorPlan = computed(
|
||||||
() =>
|
() =>
|
||||||
@ -67,7 +67,7 @@ const floorPlan = computed(
|
|||||||
...defaultRoomData,
|
...defaultRoomData,
|
||||||
...JSON.parse(currentFloor.value.plan || '{}'),
|
...JSON.parse(currentFloor.value.plan || '{}'),
|
||||||
id: selectedFloorId.value,
|
id: selectedFloorId.value,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const buildingSelected = async (id: number) => {
|
const buildingSelected = async (id: number) => {
|
||||||
@ -99,7 +99,7 @@ const updateRooms = async (data: FloorDocument, rooms: Line[]) => {
|
|||||||
const createdRooms = await building.upsertFloorRooms(
|
const createdRooms = await building.upsertFloorRooms(
|
||||||
selectedBuildingId.value,
|
selectedBuildingId.value,
|
||||||
currentFloor.value.number,
|
currentFloor.value.number,
|
||||||
extractedRooms
|
extractedRooms,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (createdRooms?.length) {
|
if (createdRooms?.length) {
|
||||||
@ -122,25 +122,25 @@ const updateRooms = async (data: FloorDocument, rooms: Line[]) => {
|
|||||||
const updateDocument = async (
|
const updateDocument = async (
|
||||||
data: FloorDocument,
|
data: FloorDocument,
|
||||||
rooms: Line[],
|
rooms: Line[],
|
||||||
boundingBox?: Vec2Box
|
boundingBox?: Vec2Box,
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
!selectedBuildingId.value ||
|
!selectedBuildingId.value ||
|
||||||
!selectedFloorId.value ||
|
!selectedFloorId.value ||
|
||||||
selectedFloorId.value !== data.id ||
|
selectedFloorId.value !== data.id ||
|
||||||
!currentFloor.value ||
|
!currentFloor.value ||
|
||||||
status.value === 'Saving...'
|
status.value === 'saving'
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
status.value = 'Saving...';
|
status.value = 'saving';
|
||||||
try {
|
try {
|
||||||
data = await updateRooms(data, rooms);
|
data = await updateRooms(data, rooms);
|
||||||
|
|
||||||
// Prevent useless requests
|
// Prevent useless requests
|
||||||
const floorPlan = JSON.stringify({ ...data, boundingBox });
|
const floorPlan = JSON.stringify({ ...data, boundingBox });
|
||||||
if (currentFloor.value.plan === floorPlan) {
|
if (currentFloor.value.plan === floorPlan) {
|
||||||
status.value = 'Saved!';
|
status.value = 'saved';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,12 +149,12 @@ const updateDocument = async (
|
|||||||
currentFloor.value.number,
|
currentFloor.value.number,
|
||||||
{
|
{
|
||||||
plan: floorPlan,
|
plan: floorPlan,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
status.value = 'Saved!';
|
status.value = 'saved';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to save floor document: ${(e as Error).stack}`);
|
console.error(`Failed to save floor document: ${(e as Error).stack}`);
|
||||||
status.value = 'Failed to save!';
|
status.value = 'failed';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex min-h-full items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8"
|
class="flex min-h-full items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8"
|
||||||
>
|
>
|
||||||
<div class="w-full max-w-md space-y-8">
|
<div class="w-full max-w-md space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900"
|
class="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900"
|
||||||
>
|
>
|
||||||
Sign in to your account
|
{{ $t('login.title') }}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<Transition
|
<Transition
|
||||||
@ -33,12 +33,22 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="bg-white px-4 py-5 sm:p-6">
|
<div class="bg-white px-4 py-5 sm:p-6">
|
||||||
<div class="space-y-5">
|
<div class="space-y-5">
|
||||||
<FormField name="email" label="Email address" type="email" />
|
<FormField
|
||||||
<FormField name="password" label="Password" type="password" />
|
name="email"
|
||||||
|
:label="$t('login.email')"
|
||||||
|
type="email"
|
||||||
|
autocomplete="username"
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
name="password"
|
||||||
|
:label="$t('login.password')"
|
||||||
|
type="password"
|
||||||
|
autocomplete="current-password"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 px-4 py-3 text-right sm:px-6">
|
<div class="bg-gray-50 px-4 py-3 text-right sm:px-6">
|
||||||
<Button button-type="submit">Sign in</Button>
|
<Button button-type="submit">{{ $t('login.submit') }}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -3,17 +3,17 @@
|
|||||||
<PageHead bordered>
|
<PageHead bordered>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<h1 class="text-2xl font-bold">{{ building?.displayName }}</h1>
|
<h1 class="text-2xl font-bold">{{ building?.displayName }}</h1>
|
||||||
<span class="text-sm font-light text-gray-800 line-clamp-1">{{
|
<span class="line-clamp-1 text-sm font-light text-gray-800">{{
|
||||||
building?.address
|
building?.address
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
title="Actions"
|
:title="$t('common.actions')"
|
||||||
class="self-end"
|
class="self-end"
|
||||||
:options="[
|
:options="[
|
||||||
{
|
{
|
||||||
title: 'Edit floor plans',
|
title: $t('buildings.editPlans'),
|
||||||
link: { name: 'planner', query: { buildingId: building?.id } },
|
link: { name: 'planner', query: { buildingId: building?.id } },
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</PageHead>
|
</PageHead>
|
||||||
|
|
||||||
<div class="mb-4 flex items-center justify-between">
|
<div class="mb-4 flex items-center justify-between">
|
||||||
<h2 class="text-xl font-bold">Floors</h2>
|
<h2 class="text-xl font-bold">{{ $t('common.floors') }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<ImageBox
|
<ImageBox
|
||||||
:title="floor.displayName"
|
:title="floor.displayName"
|
||||||
:subtitle="`Floor ${floor.number}`"
|
:subtitle="$t('buildings.floor', { floor: floor.number })"
|
||||||
:route-link="{
|
:route-link="{
|
||||||
name: 'floor',
|
name: 'floor',
|
||||||
params: { id: building, number: floor.number },
|
params: { id: building, number: floor.number },
|
||||||
|
@ -3,17 +3,20 @@
|
|||||||
<PageHead bordered>
|
<PageHead bordered>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<h1 class="text-2xl font-bold">{{ floor?.displayName }}</h1>
|
<h1 class="text-2xl font-bold">{{ floor?.displayName }}</h1>
|
||||||
<span class="text-sm font-light text-gray-800 line-clamp-1"
|
<span class="line-clamp-1 text-sm font-light text-gray-800">{{
|
||||||
>Floor {{ floor?.number }} in {{ building?.displayName }}</span
|
$t('buildings.floorIn', {
|
||||||
>
|
floor: floor?.number,
|
||||||
|
building: building?.displayName,
|
||||||
|
})
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
title="Actions"
|
:title="$t('common.actions')"
|
||||||
class="self-end"
|
class="self-end"
|
||||||
:options="[
|
:options="[
|
||||||
{
|
{
|
||||||
title: 'Edit floor plan',
|
title: $t('buildings.editPlan'),
|
||||||
link: {
|
link: {
|
||||||
name: 'planner',
|
name: 'planner',
|
||||||
query: { buildingId: building?.id, floorId: floor?.id },
|
query: { buildingId: building?.id, floorId: floor?.id },
|
||||||
@ -31,7 +34,10 @@
|
|||||||
'focus:outline-none focus:ring-2 focus:ring-blue-500',
|
'focus:outline-none focus:ring-2 focus:ring-blue-500',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<span>{{ showMap ? 'Hide' : 'Show' }} interactive floor plan</span>
|
<span
|
||||||
|
>{{ $t(showMap ? 'common.hide' : 'common.show') }}
|
||||||
|
{{ $t('buildings.interactive') }}</span
|
||||||
|
>
|
||||||
<ChevronUpIcon v-if="showMap" class="h-4 w-4" />
|
<ChevronUpIcon v-if="showMap" class="h-4 w-4" />
|
||||||
<ChevronDownIcon v-else class="h-4 w-4" />
|
<ChevronDownIcon v-else class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
@ -95,7 +101,7 @@
|
|||||||
<span
|
<span
|
||||||
v-if="isSet(storage)"
|
v-if="isSet(storage)"
|
||||||
class="text-[0.75rem] font-bold uppercase text-gray-400"
|
class="text-[0.75rem] font-bold uppercase text-gray-400"
|
||||||
>(Storage Set)</span
|
>({{ $t('common.storageSet') }})</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -124,11 +130,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm" v-if="!isSet(storage)"
|
<span class="text-sm" v-if="!isSet(storage)"
|
||||||
>Stored items:
|
>{{ $t('storage.storedItems') }}:
|
||||||
{{ (storage as StorageListItem).itemCount }}</span
|
{{ (storage as StorageListItem).itemCount }}</span
|
||||||
>
|
>
|
||||||
<span class="text-sm" v-else
|
<span class="text-sm" v-else
|
||||||
>Storages:
|
>{{ $t('common.storages') }}:
|
||||||
{{ (storage as StorageSetListItem).storages.length }}</span
|
{{ (storage as StorageSetListItem).storages.length }}</span
|
||||||
>
|
>
|
||||||
</StorageBubble>
|
</StorageBubble>
|
||||||
@ -218,14 +224,15 @@ const selectedStorage = ref<StorageListItem>();
|
|||||||
const newStorage = ref<InstanceType<typeof NewStorageModal>>();
|
const newStorage = ref<InstanceType<typeof NewStorageModal>>();
|
||||||
const storageViewRef = ref<InstanceType<typeof StorageView>>();
|
const storageViewRef = ref<InstanceType<typeof StorageView>>();
|
||||||
|
|
||||||
const floor = computed(() =>
|
const floor = computed(
|
||||||
|
() =>
|
||||||
building.value?.floors.find(
|
building.value?.floors.find(
|
||||||
(floor) => floor.number === Number(route.params.number)
|
(floor) => floor.number === Number(route.params.number),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const floorDocument = computed(
|
const floorDocument = computed(
|
||||||
() => floor.value?.plan && JSON.parse(floor.value.plan)
|
() => floor.value?.plan && JSON.parse(floor.value.plan),
|
||||||
);
|
);
|
||||||
|
|
||||||
const clickOnRoom = (room?: RoomLayoutObject, x?: number, y?: number) => {
|
const clickOnRoom = (room?: RoomLayoutObject, x?: number, y?: number) => {
|
||||||
@ -266,7 +273,7 @@ const setBubbleLocation = async (x: number, y: number) => {
|
|||||||
location: `${x},${y}`,
|
location: `${x},${y}`,
|
||||||
},
|
},
|
||||||
headers: authHeader.value,
|
headers: authHeader.value,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
movingBubble.value = undefined;
|
movingBubble.value = undefined;
|
||||||
};
|
};
|
||||||
@ -276,7 +283,7 @@ const addNewStorage = (set: boolean | StorageSetListItem) => {
|
|||||||
newStorage.value?.openModal(
|
newStorage.value?.openModal(
|
||||||
building.value,
|
building.value,
|
||||||
selectedRoom.value as unknown as RoomListItem,
|
selectedRoom.value as unknown as RoomListItem,
|
||||||
set
|
set,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -290,7 +297,7 @@ const selectRoomFromList = (room: RoomLayoutObject) => {
|
|||||||
|
|
||||||
const selectStorage = async (
|
const selectStorage = async (
|
||||||
storage: StorageSharedType,
|
storage: StorageSharedType,
|
||||||
parentSet?: StorageSetListItem
|
parentSet?: StorageSetListItem,
|
||||||
) => {
|
) => {
|
||||||
if (isSet(storage)) {
|
if (isSet(storage)) {
|
||||||
selectedSet.value = storage as StorageSetListItem;
|
selectedSet.value = storage as StorageSetListItem;
|
||||||
@ -325,7 +332,7 @@ const rooms = computed<RoomLayoutObject[]>(
|
|||||||
storages: [],
|
storages: [],
|
||||||
} as RoomLayoutObject;
|
} as RoomLayoutObject;
|
||||||
})) ||
|
})) ||
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const getRoomStorages = async (room: RoomLayoutObject) => {
|
const getRoomStorages = async (room: RoomLayoutObject) => {
|
||||||
@ -335,13 +342,13 @@ const getRoomStorages = async (room: RoomLayoutObject) => {
|
|||||||
`${BACKEND_URL}/storage/room/${room.id}?includeWithSets=false`,
|
`${BACKEND_URL}/storage/room/${room.id}?includeWithSets=false`,
|
||||||
{
|
{
|
||||||
headers: authHeader.value,
|
headers: authHeader.value,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const { data: setList } = await jfetch(
|
const { data: setList } = await jfetch(
|
||||||
`${BACKEND_URL}/storage/set/room/${room.id}`,
|
`${BACKEND_URL}/storage/set/room/${room.id}`,
|
||||||
{
|
{
|
||||||
headers: authHeader.value,
|
headers: authHeader.value,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
storages.value = [...storageList, ...setList];
|
storages.value = [...storageList, ...setList];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
@click.stop
|
||||||
class="custom-storage absolute z-10"
|
class="custom-storage absolute z-10"
|
||||||
:style="getStoragePosition(storage)"
|
:style="getStoragePosition(storage)"
|
||||||
>
|
>
|
||||||
@ -13,11 +14,11 @@
|
|||||||
class="absolute -bottom-2 left-1/2 h-0 w-0 -translate-x-1/2 border-x-8 border-t-[16px] border-x-transparent border-t-white"
|
class="absolute -bottom-2 left-1/2 h-0 w-0 -translate-x-1/2 border-x-8 border-t-[16px] border-x-transparent border-t-white"
|
||||||
></div>
|
></div>
|
||||||
<button
|
<button
|
||||||
class="absolute left-1/2 bottom-0.5 -translate-x-1/2 rounded-full bg-gray-50 px-1 py-1 ring-1 ring-black ring-opacity-5"
|
class="absolute bottom-0.5 left-1/2 -translate-x-1/2 rounded-full bg-gray-50 px-1 py-1 ring-1 ring-black ring-opacity-5"
|
||||||
@click.stop="emit('startMoving')"
|
@click.stop="emit('startMoving')"
|
||||||
title="Move storage"
|
:title="$t('storage.move')"
|
||||||
>
|
>
|
||||||
<span class="sr-only">Move storage</span>
|
<span class="sr-only">{{ $t('storage.move') }}</span>
|
||||||
<ArrowsPointingOutIcon class="h-4 w-4" />
|
<ArrowsPointingOutIcon class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<div
|
<div
|
||||||
class="border-b-4 border-gray-300 bg-gray-100 px-2 py-2 text-center font-bold"
|
class="border-b-4 border-gray-300 bg-gray-100 px-2 py-2 text-center font-bold"
|
||||||
>
|
>
|
||||||
Select Room
|
{{ $t('storage.selectRoom') }}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-for="room of rooms"
|
v-for="room of rooms"
|
||||||
@ -20,7 +20,7 @@
|
|||||||
selectedRoom?.id === room.id
|
selectedRoom?.id === room.id
|
||||||
? 'bg-blue-100 hover:bg-blue-200'
|
? 'bg-blue-100 hover:bg-blue-200'
|
||||||
: 'hover:bg-blue-100',
|
: 'hover:bg-blue-100',
|
||||||
'flex items-center justify-between border-b-1 border-gray-100 py-2 px-2',
|
'flex items-center justify-between border-b-1 border-gray-100 px-2 py-2',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<span>{{ room.displayName }}</span>
|
<span>{{ room.displayName }}</span>
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<div
|
<div
|
||||||
class="border-b-4 border-gray-300 bg-gray-100 px-2 py-2 text-center font-bold"
|
class="border-b-4 border-gray-300 bg-gray-100 px-2 py-2 text-center font-bold"
|
||||||
>
|
>
|
||||||
Select Storage / Set
|
{{ $t('storage.selectStorageSet') }}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-for="storage of storages"
|
v-for="storage of storages"
|
||||||
@ -47,7 +47,7 @@
|
|||||||
isStorageOrSetSelected(storage)
|
isStorageOrSetSelected(storage)
|
||||||
? 'bg-blue-100 hover:bg-blue-200'
|
? 'bg-blue-100 hover:bg-blue-200'
|
||||||
: 'hover:bg-blue-100',
|
: 'hover:bg-blue-100',
|
||||||
'flex items-center justify-between border-b-1 border-gray-100 py-2 px-2',
|
'flex items-center justify-between border-b-1 border-gray-100 px-2 py-2',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@ -57,7 +57,7 @@
|
|||||||
>({{ (storage as StorageListItem).itemCount }})</span
|
>({{ (storage as StorageListItem).itemCount }})</span
|
||||||
>
|
>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
(set) ({{
|
({{ $t('storage.set') }}) ({{
|
||||||
(storage as StorageSetListItem).storages.length
|
(storage as StorageSetListItem).storages.length
|
||||||
}})</span
|
}})</span
|
||||||
>
|
>
|
||||||
@ -70,14 +70,16 @@
|
|||||||
@click="emit('newStorage', false)"
|
@click="emit('newStorage', false)"
|
||||||
class="flex items-center space-x-1 text-blue-500 hover:text-blue-600 hover:underline"
|
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>
|
<PlusIcon class="h-4 w-4" />
|
||||||
|
<span>{{ $t('storage.add') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
<button
|
<button
|
||||||
@click="emit('newStorage', true)"
|
@click="emit('newStorage', true)"
|
||||||
class="flex items-center space-x-1 text-blue-500 hover:text-blue-600 hover:underline"
|
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>
|
<PlusIcon class="h-4 w-4" />
|
||||||
|
<span>{{ $t('storage.addSet') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -92,7 +94,7 @@
|
|||||||
<div
|
<div
|
||||||
class="border-b-4 border-gray-300 bg-gray-100 px-2 py-2 text-center font-bold"
|
class="border-b-4 border-gray-300 bg-gray-100 px-2 py-2 text-center font-bold"
|
||||||
>
|
>
|
||||||
Select Storage
|
{{ $t('storage.selectStorage') }}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-for="storage of selectedSet.storages"
|
v-for="storage of selectedSet.storages"
|
||||||
@ -102,7 +104,7 @@
|
|||||||
isStorageSelected(storage)
|
isStorageSelected(storage)
|
||||||
? 'bg-blue-100 hover:bg-blue-200'
|
? 'bg-blue-100 hover:bg-blue-200'
|
||||||
: 'hover:bg-blue-100',
|
: 'hover:bg-blue-100',
|
||||||
'flex items-center justify-between border-b-1 border-gray-100 py-2 px-2',
|
'flex items-center justify-between border-b-1 border-gray-100 px-2 py-2',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<span>{{ storage.displayName }} ({{ storage.itemCount }})</span>
|
<span>{{ storage.displayName }} ({{ storage.itemCount }})</span>
|
||||||
@ -114,7 +116,7 @@
|
|||||||
@click="emit('newStorage', selectedSet!)"
|
@click="emit('newStorage', selectedSet!)"
|
||||||
class="flex items-center space-x-1 text-blue-500 hover:text-blue-600 hover:underline"
|
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>
|
<PlusIcon class="h-4 w-4" /> <span>{{ $t('storage.add') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -153,7 +155,7 @@ const emit = defineEmits<{
|
|||||||
(
|
(
|
||||||
e: 'selectStorage',
|
e: 'selectStorage',
|
||||||
storage: StorageSharedType,
|
storage: StorageSharedType,
|
||||||
parentSet?: StorageSetListItem
|
parentSet?: StorageSetListItem,
|
||||||
): void;
|
): void;
|
||||||
(e: 'selectRoom', room: RoomLayoutObject): void;
|
(e: 'selectRoom', room: RoomLayoutObject): void;
|
||||||
(e: 'newStorage', set: boolean | StorageSetListItem): void;
|
(e: 'newStorage', set: boolean | StorageSetListItem): void;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<h2 class="text-2xl font-bold">{{ storage.displayName }}</h2>
|
<h2 class="text-2xl font-bold">{{ storage.displayName }}</h2>
|
||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
title="Actions"
|
:title="$t('common.actions')"
|
||||||
class="self-end"
|
class="self-end"
|
||||||
:options="[
|
:options="[
|
||||||
{
|
{
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<template #actions>
|
<template #actions>
|
||||||
<Menu
|
<Menu
|
||||||
title=""
|
title=""
|
||||||
button-class="px-0.5 py-0.5"
|
button-class="!px-0.5 !py-0.5"
|
||||||
:options="[
|
:options="[
|
||||||
{ title: 'Change details' },
|
{ title: 'Change details' },
|
||||||
{ title: 'Add transaction' },
|
{ title: 'Add transaction' },
|
||||||
|
@ -8,5 +8,5 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require('@tailwindcss/forms'), require('@tailwindcss/line-clamp')],
|
plugins: [require('@tailwindcss/forms')],
|
||||||
};
|
};
|
||||||
|
@ -9,10 +9,27 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": ["ESNext", "DOM"],
|
"lib": [
|
||||||
|
"ESNext",
|
||||||
|
"DOM"
|
||||||
|
],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"noEmit": true
|
"noEmit": true,
|
||||||
},
|
"paths": {
|
||||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
"@/*": [
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"./src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue"
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -4,4 +4,9 @@ import vue from '@vitejs/plugin-vue';
|
|||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': '/src',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user