This commit is contained in:
Evert Prants 2023-02-04 13:34:50 +02:00
parent 710a904323
commit 5a63f00f51
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
11 changed files with 117 additions and 92 deletions

29
src/components/Button.vue Normal file
View File

@ -0,0 +1,29 @@
<template>
<button
:class="[
'inline-flex justify-center rounded-md border border-transparent py-2 px-4 text-sm font-medium shadow-sm',
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2',
classes,
]"
:type="buttonType"
>
<slot />
</button>
</template>
<script setup lang="ts">
import { computed } from 'vue';
const props = withDefaults(
defineProps<{
buttonType?: 'submit' | 'button';
variant?: 'primary' | 'secondary' | 'tertiary';
}>(),
{ buttonType: 'button', variant: 'primary' }
);
const classes = computed(() => {
if (!props.variant || props.variant === 'primary')
return 'bg-green-600 hover:bg-green-700 text-white';
});
</script>

View File

@ -101,7 +101,12 @@
/>
</FormGroup>
<button type="submit">Submit</button>
<div class="flex justify-end space-x-1">
<Button button-type="submit" @click="submitlock = true"
>Submit and add another</Button
>
<Button button-type="submit">Submit</Button>
</div>
</Form>
</template>
</Modal>
@ -127,17 +132,14 @@ import FormSelectField from '../form/fields/FormSelectField.vue';
import { IsRequired, MinLength } from '../form/validators';
import Modal from '../Modal.vue';
import { ItemType, ItemTypeName } from '../../enums/item-type.enum';
import {
TransactionType,
TransactionTypeDescription,
TransactionTypeName,
} from '../../enums/transaction-type.enum';
import { TransactionType } from '../../enums/transaction-type.enum';
import FormGroup from '../form/FormGroup.vue';
import FormDateField from '../form/fields/FormDateField.vue';
import FormAutocompleteField from '../form/fields/FormAutocompleteField.vue';
import { FormValidator } from '../form/validator.types';
import deepUnref from '../../utils/deep-unref';
import FormTextareaField from '../form/fields/FormTextareaField.vue';
import Button from '../Button.vue';
const defaults: any = {
item: null,
@ -159,6 +161,7 @@ const data: any = ref({
...deepUnref(defaults),
});
const error = ref('');
const submitlock = ref(false);
const emit = defineEmits<{
(e: 'added', storage: StoredItem): void;
@ -256,7 +259,10 @@ const startAddingNewItem = (insert: string) => {
const onSubmit = async (form: FormSubmit) => {
error.value = '';
if (!form.isValid || !storage.value) return;
if (!form.isValid || !storage.value) {
submitlock.value = false;
return;
}
const body = {
...form.formData,
@ -281,6 +287,10 @@ const onSubmit = async (form: FormSubmit) => {
}
emit('added', createdStoredItem);
if (submitlock.value) {
submitlock.value = false;
return;
}
modalRef.value?.closeModal();
};
</script>

View File

@ -19,19 +19,10 @@
</FormField>
<FormColorField name="color" label="Color" />
<div class="flex justify-end space-x-1">
<button
class="inline-flex justify-center rounded-md border border-transparent bg-green-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
type="submit"
@click="submitlock = true"
<Button button-type="submit" @click="submitlock = true"
>Submit and add another</Button
>
Submit and add another
</button>
<button
class="inline-flex justify-center rounded-md border border-transparent bg-green-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
type="submit"
>
Submit
</button>
<Button button-type="submit">Submit</Button>
</div>
</Form>
</template>
@ -60,6 +51,7 @@ import FormSelectField from '../form/fields/FormSelectField.vue';
import { IsRequired, MinLength } from '../form/validators';
import Modal from '../Modal.vue';
import FormColorField from '../form/fields/FormColorField.vue';
import Button from '../Button.vue';
const { authHeader } = useAccessToken();
const defaults = {
@ -123,7 +115,10 @@ defineExpose({
const onSubmit = async (value: FormSubmit) => {
error.value = '';
if (!value.isValid || !room.value) return;
if (!value.isValid || !room.value) {
submitlock.value = false;
return;
}
const createURL = `${BACKEND_URL}/storage${
set.value === true ? '/set' : ''

View File

@ -32,7 +32,7 @@
>
<MenuItems
:class="[
'absolute z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg',
'absolute z-10 mt-2 w-56 divide-y divide-gray-100 rounded-md bg-white shadow-lg',
'ring-1 ring-black ring-opacity-5 focus:outline-none',
positionClass,
]"
@ -65,31 +65,25 @@
<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 { computed } from 'vue';
import { RouterLink } from 'vue-router';
import { MenuOption as MenuOptionType } from './menu.interfaces';
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[];
options: MenuOptionType[];
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';
if (!props.position || props.position === 'right')
return 'right-0 origin-top-right';
if (props.position === 'center')
return 'left-1/2 -translate-x-1/2 origin-top-center';
if (props.position === 'left') return 'left-0 origin-top-left';
return '';
});
</script>

View File

@ -17,7 +17,7 @@
</template>
<script setup lang="ts">
import type { MenuOption } from './Menu.vue';
import { MenuOption } from './menu.interfaces';
const props = defineProps<{
active: boolean;

View File

@ -0,0 +1,11 @@
import { Component } from 'vue';
import { RouteLocationRaw } from 'vue-router';
export interface MenuOption {
title: string;
key?: string;
link?: RouteLocationRaw;
icon?: Component;
onClick?: () => void;
component?: Component;
}

View File

@ -25,54 +25,23 @@
</div>
</div>
</Transition>
<form
@submit.prevent="doLogin"
<Form
@submit="doLogin"
class="mt-8 overflow-hidden shadow sm:rounded-md"
:validators="validators"
>
<div>
<div class="bg-white px-4 py-5 sm:p-6">
<div class="space-y-5">
<div class="space-y-1">
<label for="email" class="block text-sm font-medium text-gray-700"
>Email address</label
>
<input
type="email"
name="email"
id="email"
required
autofocus
autocomplete="email"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"
v-model="loginForm.email"
/>
</div>
<div class="space-y-1">
<label
for="password"
class="block text-sm font-medium text-gray-700"
>Password</label
>
<input
type="password"
name="password"
id="password"
required
autocomplete="current-password"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"
v-model="loginForm.password"
/>
</div>
<FormField name="email" label="Email address" type="email" />
<FormField name="password" label="Password" type="password" />
</div>
</div>
<div class="bg-gray-50 px-4 py-3 text-right sm:px-6">
<button
type="submit"
class="inline-flex justify-center rounded-md border border-transparent bg-green-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Sign in
</button>
<Button button-type="submit">Sign in</Button>
</div>
</form>
</div>
</Form>
</div>
</div>
</template>
@ -80,25 +49,37 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import Button from '../components/Button.vue';
import { FormSubmit } from '../components/form/form.types';
import Form from '../components/form/Form.vue';
import FormField from '../components/form/FormField.vue';
import { IsEmail, IsRequired } from '../components/form/validators';
import { useUserStore } from '../store/user.store';
import { JFetchError } from '../utils/jfetch';
const userStore = useUserStore();
const router = useRouter();
const errorMessage = ref<string>('');
const loginForm = ref<{
email: string;
password: string;
}>({
email: '',
password: '',
});
const validators = [
{
field: 'email',
validators: [IsRequired(), IsEmail()],
},
{
field: 'password',
validators: [IsRequired()],
},
];
const doLogin = async () => {
const doLogin = async (submit: FormSubmit) => {
errorMessage.value = '';
if (!submit.isValid) return;
try {
const { email, password } = loginForm.value;
await userStore.login({ email, password });
const { email, password } = submit.formData;
await userStore.login({
email: email as string,
password: password as string,
});
router.replace({ name: 'dashboard' });
} catch (e) {
const error = e as JFetchError;

View File

@ -10,6 +10,7 @@
<Menu
title="Actions"
class="self-end"
:options="[
{
title: 'Edit floor plans',

View File

@ -9,6 +9,7 @@
<Menu
title="Actions"
class="self-end"
:options="[
{
title: 'Edit floor plan',
@ -80,6 +81,7 @@
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)"

View File

@ -5,12 +5,12 @@
highlighted
? ''
: 'flex cursor-pointer items-center justify-center transition-transform hover:scale-105',
'pointer-events-auto absolute',
' absolute',
]"
:style="roomPositionCSS"
>
<div
class="absolute"
class="pointer-events-auto absolute"
:style="roomPolygonCSS"
@click.stop="clickOnRoom($event)"
@mousemove="moveOverRoom($event)"

View File

@ -5,6 +5,7 @@
<Menu
title="Actions"
class="self-end"
:options="[
{
title: 'Add new item',
@ -31,6 +32,7 @@
:options="[
{ title: 'Change details' },
{ title: 'Add transaction' },
{ title: 'Move' },
]"
>
<EllipsisVerticalIcon class="h-5 w-5" />