-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Ability to create an account from transaction creation modal
- Loading branch information
Showing
6 changed files
with
250 additions
and
140 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<script setup lang="ts"> | ||
import { ref } from "vue"; | ||
import * as Dialog from "@/components/lib/ui/dialog"; | ||
import CreateAccountForm from "@/components/forms/create-account-form.vue"; | ||
const isOpen = ref(false); | ||
</script> | ||
|
||
<template> | ||
<Dialog.Dialog v-model:open="isOpen" @update:open="isOpen = $event"> | ||
<Dialog.DialogTrigger as-child> | ||
<slot /> | ||
</Dialog.DialogTrigger> | ||
<Dialog.DialogContent | ||
class="sm:max-w-md max-h-[90dvh] grid-rows-[auto_auto_minmax(0,1fr)_auto]" | ||
> | ||
<Dialog.DialogHeader class="mb-4"> | ||
<Dialog.DialogTitle>Create account</Dialog.DialogTitle> | ||
</Dialog.DialogHeader> | ||
|
||
<CreateAccountForm @created="isOpen = false" /> | ||
</Dialog.DialogContent> | ||
</Dialog.Dialog> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<template> | ||
<div> | ||
<template v-if="label"> | ||
<FieldLabel :label="label" /> | ||
</template> | ||
|
||
<Select.Select v-model="selectedKey" :disabled="disabled"> | ||
<Select.SelectTrigger class="w-full"> | ||
<Select.SelectValue :placeholder="placeholder"> | ||
{{ selectedValue ? getLabelFromValue(selectedValue) : placeholder }} | ||
</Select.SelectValue> | ||
</Select.SelectTrigger> | ||
<Select.SelectContent> | ||
<slot name="select-top-content" /> | ||
|
||
<Select.SelectItem | ||
v-for="item in filteredValues" | ||
:key="getKeyFromItem(item)" | ||
:value="getKeyFromItem(item)" | ||
> | ||
{{ getLabelFromValue(item) }} | ||
</Select.SelectItem> | ||
|
||
<slot name="select-bottom-content" /> | ||
</Select.SelectContent> | ||
</Select.Select> | ||
|
||
<FieldError :error-message="errorMessage" /> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts" setup generic="T extends Record<string, any>"> | ||
import { computed } from "vue"; | ||
import * as Select from "@/components/lib/ui/select"; | ||
import FieldError from "./components/field-error.vue"; | ||
import FieldLabel from "./components/field-label.vue"; | ||
const props = withDefaults( | ||
defineProps<{ | ||
modelValue: T | null; | ||
values: T[]; | ||
labelKey?: keyof T | ((value: T) => string) | "label"; | ||
valueKey?: keyof T | ((value: T) => string | number) | "value"; | ||
placeholder?: string; | ||
disabled?: boolean; | ||
errorMessage?: string; | ||
label?: string; | ||
}>(), | ||
{ | ||
placeholder: "Select an option", | ||
disabled: false, | ||
errorMessage: undefined, | ||
labelKey: "label", | ||
valueKey: "value", | ||
label: undefined, | ||
}, | ||
); | ||
const emit = defineEmits<{ | ||
"update:modelValue": [value: T | null]; | ||
}>(); | ||
const selectedValue = computed(() => props.modelValue); | ||
const getLabelFromValue = (value: T): string => { | ||
const { labelKey } = props; | ||
if (typeof labelKey === "function") return labelKey(value); | ||
return String(value[labelKey as keyof T]); | ||
}; | ||
const getValueFromItem = (item: T): string | number => { | ||
const { valueKey } = props; | ||
if (typeof valueKey === "function") return valueKey(item); | ||
return item[valueKey as keyof T] as string | number; | ||
}; | ||
const getKeyFromItem = (item: T): string => String(getValueFromItem(item)); | ||
const filteredValues = computed(() => props.values); | ||
const selectedKey = computed({ | ||
get: () => (selectedValue.value ? getKeyFromItem(selectedValue.value) : ""), | ||
set: (key: string) => { | ||
const newValue = props.values.find((item) => getKeyFromItem(item) === key) ?? null; | ||
emit("update:modelValue", newValue); | ||
}, | ||
}); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
<script setup lang="ts"> | ||
import { computed, reactive, ref } from "vue"; | ||
import { storeToRefs } from "pinia"; | ||
import { useQueryClient } from "@tanstack/vue-query"; | ||
import { useAccountsStore, useCurrenciesStore } from "@/stores"; | ||
import { VUE_QUERY_CACHE_KEYS } from "@/common/const"; | ||
import * as Select from "@/components/lib/ui/select"; | ||
import { useNotificationCenter, NotificationType } from "@/components/notification-center"; | ||
import InputField from "@/components/fields/input-field.vue"; | ||
import UiButton from "@/components/lib/ui/button/Button.vue"; | ||
import FieldLabel from "@/components/fields/components/field-label.vue"; | ||
import AddCurrencyDialog from "@/components/dialogs/add-currency-dialog.vue"; | ||
const emit = defineEmits(["created"]); | ||
const queryClient = useQueryClient(); | ||
const accountsStore = useAccountsStore(); | ||
const currenciesStore = useCurrenciesStore(); | ||
const { addNotification } = useNotificationCenter(); | ||
const { baseCurrency, systemCurrenciesVerbose } = storeToRefs(currenciesStore); | ||
const defaultCurrency = computed( | ||
() => | ||
systemCurrenciesVerbose.value.linked.find((i) => i.id === baseCurrency.value.currencyId).id || | ||
0, | ||
); | ||
const form = reactive<{ | ||
name: string; | ||
currencyId: string; | ||
initialBalance: number; | ||
creditLimit: number; | ||
}>({ | ||
name: "", | ||
currencyId: String(defaultCurrency.value), | ||
initialBalance: 0, | ||
creditLimit: 0, | ||
}); | ||
const isLoading = ref(false); | ||
const submit = async () => { | ||
try { | ||
isLoading.value = true; | ||
await accountsStore.createAccount({ | ||
currencyId: form.currencyId, | ||
name: form.name, | ||
creditLimit: form.creditLimit, | ||
initialBalance: form.initialBalance, | ||
}); | ||
addNotification({ | ||
text: "Created successfully.", | ||
type: NotificationType.success, | ||
}); | ||
queryClient.invalidateQueries({ | ||
queryKey: VUE_QUERY_CACHE_KEYS.allAccounts, | ||
}); | ||
emit("created"); | ||
} catch (e) { | ||
addNotification({ | ||
text: "Unexpected error.", | ||
type: NotificationType.error, | ||
}); | ||
} finally { | ||
isLoading.value = false; | ||
} | ||
}; | ||
</script> | ||
|
||
<template> | ||
<form class="grid gap-6" @submit.prevent="submit"> | ||
<input-field v-model="form.name" label="Account name" placeholder="Account name" /> | ||
|
||
<div> | ||
<FieldLabel label="Currency"> | ||
<Select.Select v-model="form.currencyId"> | ||
<Select.SelectTrigger> | ||
<Select.SelectValue placeholder="Select currency" /> | ||
</Select.SelectTrigger> | ||
<Select.SelectContent> | ||
<template v-for="item of systemCurrenciesVerbose.linked" :key="item.id"> | ||
<Select.SelectItem :value="String(item.id)"> | ||
{{ item.code }} - {{ item.currency }} | ||
</Select.SelectItem> | ||
</template> | ||
|
||
<AddCurrencyDialog @added="form.currencyId = String($event)"> | ||
<ui-button type="button" class="mt-4 w-full" variant="link"> | ||
Add new currency + | ||
</ui-button> | ||
</AddCurrencyDialog> | ||
</Select.SelectContent> | ||
</Select.Select> | ||
</FieldLabel> | ||
</div> | ||
|
||
<input-field | ||
v-model="form.initialBalance" | ||
label="Initial balance" | ||
placeholder="Initial balance" | ||
/> | ||
|
||
<input-field v-model="form.creditLimit" label="Credit limit" placeholder="Credit limit" /> | ||
|
||
<div class="flex"> | ||
<ui-button type="submit" class="ml-auto min-w-[120px]" :disabled="isLoading"> | ||
{{ isLoading ? "Creating..." : "Create" }} | ||
</ui-button> | ||
</div> | ||
</form> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.