Skip to content

Commit

Permalink
feat: Ability to create an account from transaction creation modal
Browse files Browse the repository at this point in the history
  • Loading branch information
letehaha committed Sep 20, 2024
1 parent 2aaf898 commit ff1df74
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 140 deletions.
24 changes: 24 additions & 0 deletions src/components/dialogs/create-account-dialog.vue
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>
88 changes: 88 additions & 0 deletions src/components/fields/select-field-lib.vue
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>
118 changes: 118 additions & 0 deletions src/components/forms/create-account-form.vue
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>
16 changes: 14 additions & 2 deletions src/components/modals/modify-record/components/account-field.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,25 @@
</template>
<template v-else>
<form-row>
<select-field
<select-field-lib
label="Account"
placeholder="Select account"
:values="accounts"
label-key="name"
value-key="id"
:disabled="disabled || fromAccountDisabled"
is-value-preselected
:model-value="account"
@update:model-value="updateFormAccount"
/>
>
<template #select-bottom-content>
<CreateAccountDialog>
<UiButton type="button" class="mt-4 w-full" variant="link">
Add new account +
</UiButton>
</CreateAccountDialog>
</template>
</select-field-lib>
</form-row>
</template>
</template>
Expand All @@ -63,7 +72,10 @@ import { AccountModel, TRANSACTION_TYPES } from "shared-types";
import { ROUTES_NAMES } from "@/routes";
import CreateAccountDialog from "@/components/dialogs/create-account-dialog.vue";
import SelectField from "@/components/fields/select-field.vue";
import UiButton from "@/components/lib/ui/button/Button.vue";
import SelectFieldLib from "@/components/fields/select-field-lib.vue";
import InputField from "@/components/fields/input-field.vue";
import FormRow from "./form-row.vue";
Expand Down
6 changes: 1 addition & 5 deletions src/components/modals/modify-record/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ref, watch, computed, onMounted, nextTick, onUnmounted } from "vue";
import { useRoute } from "vue-router";
import { storeToRefs } from "pinia";
import { useQueryClient } from "@tanstack/vue-query";
import { useEventListener, watchOnce } from "@vueuse/core";
import { watchOnce } from "@vueuse/core";
import {
TRANSACTION_TYPES,
PAYMENT_TYPES,
Expand Down Expand Up @@ -360,10 +360,6 @@ onMounted(() => {
onUnmounted(() => {
(previouslyFocusedElement.value as HTMLElement).focus();
});
useEventListener(document, "keydown", (event) => {
if (event.key === "Escape") closeModal();
});
</script>

<template>
Expand Down
Loading

0 comments on commit ff1df74

Please sign in to comment.