button
This commit is contained in:
parent
710a904323
commit
5a63f00f51
29
src/components/Button.vue
Normal file
29
src/components/Button.vue
Normal 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>
|
@ -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>
|
||||
|
@ -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' : ''
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
11
src/components/menu/menu.interfaces.ts
Normal file
11
src/components/menu/menu.interfaces.ts
Normal 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;
|
||||
}
|
@ -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 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>
|
||||
<div class="bg-white px-4 py-5 sm:p-6">
|
||||
<div class="space-y-5">
|
||||
<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 button-type="submit">Sign in</Button>
|
||||
</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>
|
||||
</div>
|
||||
</form>
|
||||
</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;
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
<Menu
|
||||
title="Actions"
|
||||
class="self-end"
|
||||
:options="[
|
||||
{
|
||||
title: 'Edit floor plans',
|
||||
|
@ -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)"
|
||||
|
@ -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)"
|
||||
|
@ -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" />
|
||||
|
Loading…
Reference in New Issue
Block a user