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>
|
</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>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -127,17 +132,14 @@ import FormSelectField from '../form/fields/FormSelectField.vue';
|
|||||||
import { IsRequired, MinLength } from '../form/validators';
|
import { IsRequired, MinLength } from '../form/validators';
|
||||||
import Modal from '../Modal.vue';
|
import Modal from '../Modal.vue';
|
||||||
import { ItemType, ItemTypeName } from '../../enums/item-type.enum';
|
import { ItemType, ItemTypeName } from '../../enums/item-type.enum';
|
||||||
import {
|
import { TransactionType } from '../../enums/transaction-type.enum';
|
||||||
TransactionType,
|
|
||||||
TransactionTypeDescription,
|
|
||||||
TransactionTypeName,
|
|
||||||
} from '../../enums/transaction-type.enum';
|
|
||||||
import FormGroup from '../form/FormGroup.vue';
|
import FormGroup from '../form/FormGroup.vue';
|
||||||
import FormDateField from '../form/fields/FormDateField.vue';
|
import FormDateField from '../form/fields/FormDateField.vue';
|
||||||
import FormAutocompleteField from '../form/fields/FormAutocompleteField.vue';
|
import FormAutocompleteField from '../form/fields/FormAutocompleteField.vue';
|
||||||
import { FormValidator } from '../form/validator.types';
|
import { FormValidator } from '../form/validator.types';
|
||||||
import deepUnref from '../../utils/deep-unref';
|
import deepUnref from '../../utils/deep-unref';
|
||||||
import FormTextareaField from '../form/fields/FormTextareaField.vue';
|
import FormTextareaField from '../form/fields/FormTextareaField.vue';
|
||||||
|
import Button from '../Button.vue';
|
||||||
|
|
||||||
const defaults: any = {
|
const defaults: any = {
|
||||||
item: null,
|
item: null,
|
||||||
@ -159,6 +161,7 @@ const data: any = ref({
|
|||||||
...deepUnref(defaults),
|
...deepUnref(defaults),
|
||||||
});
|
});
|
||||||
const error = ref('');
|
const error = ref('');
|
||||||
|
const submitlock = ref(false);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'added', storage: StoredItem): void;
|
(e: 'added', storage: StoredItem): void;
|
||||||
@ -256,7 +259,10 @@ const startAddingNewItem = (insert: string) => {
|
|||||||
|
|
||||||
const onSubmit = async (form: FormSubmit) => {
|
const onSubmit = async (form: FormSubmit) => {
|
||||||
error.value = '';
|
error.value = '';
|
||||||
if (!form.isValid || !storage.value) return;
|
if (!form.isValid || !storage.value) {
|
||||||
|
submitlock.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
...form.formData,
|
...form.formData,
|
||||||
@ -281,6 +287,10 @@ const onSubmit = async (form: FormSubmit) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emit('added', createdStoredItem);
|
emit('added', createdStoredItem);
|
||||||
|
if (submitlock.value) {
|
||||||
|
submitlock.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
modalRef.value?.closeModal();
|
modalRef.value?.closeModal();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -19,19 +19,10 @@
|
|||||||
</FormField>
|
</FormField>
|
||||||
<FormColorField name="color" label="Color" />
|
<FormColorField name="color" label="Color" />
|
||||||
<div class="flex justify-end space-x-1">
|
<div class="flex justify-end space-x-1">
|
||||||
<button
|
<Button button-type="submit" @click="submitlock = true"
|
||||||
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"
|
>Submit and add another</Button
|
||||||
type="submit"
|
|
||||||
@click="submitlock = true"
|
|
||||||
>
|
>
|
||||||
Submit and add another
|
<Button button-type="submit">Submit</Button>
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
@ -60,6 +51,7 @@ import FormSelectField from '../form/fields/FormSelectField.vue';
|
|||||||
import { IsRequired, MinLength } from '../form/validators';
|
import { IsRequired, MinLength } from '../form/validators';
|
||||||
import Modal from '../Modal.vue';
|
import Modal from '../Modal.vue';
|
||||||
import FormColorField from '../form/fields/FormColorField.vue';
|
import FormColorField from '../form/fields/FormColorField.vue';
|
||||||
|
import Button from '../Button.vue';
|
||||||
|
|
||||||
const { authHeader } = useAccessToken();
|
const { authHeader } = useAccessToken();
|
||||||
const defaults = {
|
const defaults = {
|
||||||
@ -123,7 +115,10 @@ defineExpose({
|
|||||||
const onSubmit = async (value: FormSubmit) => {
|
const onSubmit = async (value: FormSubmit) => {
|
||||||
error.value = '';
|
error.value = '';
|
||||||
|
|
||||||
if (!value.isValid || !room.value) return;
|
if (!value.isValid || !room.value) {
|
||||||
|
submitlock.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const createURL = `${BACKEND_URL}/storage${
|
const createURL = `${BACKEND_URL}/storage${
|
||||||
set.value === true ? '/set' : ''
|
set.value === true ? '/set' : ''
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
>
|
>
|
||||||
<MenuItems
|
<MenuItems
|
||||||
:class="[
|
: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',
|
'ring-1 ring-black ring-opacity-5 focus:outline-none',
|
||||||
positionClass,
|
positionClass,
|
||||||
]"
|
]"
|
||||||
@ -65,31 +65,25 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';
|
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';
|
||||||
import { ChevronDownIcon } from '@heroicons/vue/24/outline';
|
import { ChevronDownIcon } from '@heroicons/vue/24/outline';
|
||||||
import { Component, computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { RouteLocationRaw, RouterLink } from 'vue-router';
|
import { RouterLink } from 'vue-router';
|
||||||
|
import { MenuOption as MenuOptionType } from './menu.interfaces';
|
||||||
import MenuOption from './MenuOption.vue';
|
import MenuOption from './MenuOption.vue';
|
||||||
|
|
||||||
export interface MenuOption {
|
|
||||||
title: string;
|
|
||||||
key?: string;
|
|
||||||
link?: RouteLocationRaw;
|
|
||||||
icon?: Component;
|
|
||||||
onClick?: () => void;
|
|
||||||
component?: Component;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
title: string;
|
title: string;
|
||||||
options: MenuOption[];
|
options: MenuOptionType[];
|
||||||
buttonClass?: string;
|
buttonClass?: string;
|
||||||
hideChevron?: boolean;
|
hideChevron?: boolean;
|
||||||
position?: 'left' | 'center' | 'right';
|
position?: 'left' | 'center' | 'right';
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const positionClass = computed(() => {
|
const positionClass = computed(() => {
|
||||||
if (!props.position || props.position === 'right') return 'right-0';
|
if (!props.position || props.position === 'right')
|
||||||
if (props.position === 'center') return 'left-1/2 -translate-x-1/2';
|
return 'right-0 origin-top-right';
|
||||||
if (props.position === 'left') return 'left-0';
|
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 '';
|
return '';
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MenuOption } from './Menu.vue';
|
import { MenuOption } from './menu.interfaces';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
active: boolean;
|
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>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
<form
|
<Form
|
||||||
@submit.prevent="doLogin"
|
@submit="doLogin"
|
||||||
class="mt-8 overflow-hidden shadow sm:rounded-md"
|
class="mt-8 overflow-hidden shadow sm:rounded-md"
|
||||||
|
:validators="validators"
|
||||||
>
|
>
|
||||||
<div class="bg-white px-4 py-5 sm:p-6">
|
<div>
|
||||||
<div class="space-y-5">
|
<div class="bg-white px-4 py-5 sm:p-6">
|
||||||
<div class="space-y-1">
|
<div class="space-y-5">
|
||||||
<label for="email" class="block text-sm font-medium text-gray-700"
|
<FormField name="email" label="Email address" type="email" />
|
||||||
>Email address</label
|
<FormField name="password" label="Password" type="password" />
|
||||||
>
|
|
||||||
<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>
|
||||||
</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>
|
||||||
<div class="bg-gray-50 px-4 py-3 text-right sm:px-6">
|
</Form>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -80,25 +49,37 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
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 { useUserStore } from '../store/user.store';
|
||||||
import { JFetchError } from '../utils/jfetch';
|
import { JFetchError } from '../utils/jfetch';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const errorMessage = ref<string>('');
|
const errorMessage = ref<string>('');
|
||||||
const loginForm = ref<{
|
const validators = [
|
||||||
email: string;
|
{
|
||||||
password: string;
|
field: 'email',
|
||||||
}>({
|
validators: [IsRequired(), IsEmail()],
|
||||||
email: '',
|
},
|
||||||
password: '',
|
{
|
||||||
});
|
field: 'password',
|
||||||
|
validators: [IsRequired()],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const doLogin = async () => {
|
const doLogin = async (submit: FormSubmit) => {
|
||||||
errorMessage.value = '';
|
errorMessage.value = '';
|
||||||
|
if (!submit.isValid) return;
|
||||||
try {
|
try {
|
||||||
const { email, password } = loginForm.value;
|
const { email, password } = submit.formData;
|
||||||
await userStore.login({ email, password });
|
await userStore.login({
|
||||||
|
email: email as string,
|
||||||
|
password: password as string,
|
||||||
|
});
|
||||||
router.replace({ name: 'dashboard' });
|
router.replace({ name: 'dashboard' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = e as JFetchError;
|
const error = e as JFetchError;
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
title="Actions"
|
title="Actions"
|
||||||
|
class="self-end"
|
||||||
:options="[
|
:options="[
|
||||||
{
|
{
|
||||||
title: 'Edit floor plans',
|
title: 'Edit floor plans',
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
title="Actions"
|
title="Actions"
|
||||||
|
class="self-end"
|
||||||
:options="[
|
:options="[
|
||||||
{
|
{
|
||||||
title: 'Edit floor plan',
|
title: 'Edit floor plan',
|
||||||
@ -80,6 +81,7 @@
|
|||||||
storage.id === selectedStorage?.id ||
|
storage.id === selectedStorage?.id ||
|
||||||
(isSet(storage) && storage.id === selectedSet?.id),
|
(isSet(storage) && storage.id === selectedSet?.id),
|
||||||
'pointer-events-none': !!movingBubble,
|
'pointer-events-none': !!movingBubble,
|
||||||
|
'pointer-events-auto': !movingBubble,
|
||||||
}"
|
}"
|
||||||
@mouseenter="() => (hoveredBubble = storage)"
|
@mouseenter="() => (hoveredBubble = storage)"
|
||||||
@start-moving="moveBubble(storage)"
|
@start-moving="moveBubble(storage)"
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
highlighted
|
highlighted
|
||||||
? ''
|
? ''
|
||||||
: 'flex cursor-pointer items-center justify-center transition-transform hover:scale-105',
|
: 'flex cursor-pointer items-center justify-center transition-transform hover:scale-105',
|
||||||
'pointer-events-auto absolute',
|
' absolute',
|
||||||
]"
|
]"
|
||||||
:style="roomPositionCSS"
|
:style="roomPositionCSS"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="absolute"
|
class="pointer-events-auto absolute"
|
||||||
:style="roomPolygonCSS"
|
:style="roomPolygonCSS"
|
||||||
@click.stop="clickOnRoom($event)"
|
@click.stop="clickOnRoom($event)"
|
||||||
@mousemove="moveOverRoom($event)"
|
@mousemove="moveOverRoom($event)"
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
title="Actions"
|
title="Actions"
|
||||||
|
class="self-end"
|
||||||
:options="[
|
:options="[
|
||||||
{
|
{
|
||||||
title: 'Add new item',
|
title: 'Add new item',
|
||||||
@ -31,6 +32,7 @@
|
|||||||
:options="[
|
:options="[
|
||||||
{ title: 'Change details' },
|
{ title: 'Change details' },
|
||||||
{ title: 'Add transaction' },
|
{ title: 'Add transaction' },
|
||||||
|
{ title: 'Move' },
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<EllipsisVerticalIcon class="h-5 w-5" />
|
<EllipsisVerticalIcon class="h-5 w-5" />
|
||||||
|
Loading…
Reference in New Issue
Block a user