Files
monica/resources/js/Shared/Modules/Loans.vue

559 lines
20 KiB
Vue
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { nextTick, ref, useTemplateRef } from 'vue';
import { useForm } from '@inertiajs/vue3';
import { flash } from '@/methods';
import { trans } from 'laravel-vue-i18n';
import { DatePicker } from 'v-calendar';
import 'v-calendar/style.css';
import PrettyButton from '@/Shared/Form/PrettyButton.vue';
import PrettySpan from '@/Shared/Form/PrettySpan.vue';
import TextInput from '@/Shared/Form/TextInput.vue';
import TextArea from '@/Shared/Form/TextArea.vue';
import Errors from '@/Shared/Form/Errors.vue';
import ContactSelector from '@/Shared/Form/ContactSelector.vue';
import Dropdown from '@/Shared/Form/Dropdown.vue';
import ContactCard from '@/Shared/ContactCard.vue';
import ArrowIcon from '@/Shared/Icons/ArrowIcon.vue';
import { HandCoins } from 'lucide-vue-next';
const props = defineProps({
layoutData: Object,
data: Object,
});
const nameInput = useTemplateRef('nameInput');
const loadingState = ref('');
const createLoanModalShown = ref(false);
const localLoans = ref(props.data.loans);
const localCurrencies = ref([]);
const editedLoanId = ref(0);
const warning = ref('');
const form = useForm({
type: 'object',
name: '',
description: '',
loaned_at: props.data.current_date,
amount_lent: '',
currency_id: '',
loaners: [],
loanees: [],
errors: [],
});
const masks = ref({
modelValue: 'YYYY-MM-DD',
});
const showCreateLoanModal = () => {
getCurrencies();
form.errors = [];
form.type = 'object';
form.name = '';
form.description = '';
form.amount_lent = '';
form.currency_id = '';
createLoanModalShown.value = true;
nextTick().then(() => nameInput.value.focus());
};
const showEditLoanModal = (loan) => {
getCurrencies();
form.errors = [];
form.type = loan.amount_lent_input ? 'monetary' : 'object';
form.name = loan.name;
form.description = loan.description;
form.loaned_at = loan.loaned_at;
form.amount_lent = loan.amount_lent_input;
form.currency_id = loan.currency_id;
form.loaners = loan.loaners;
form.loanees = loan.loanees;
editedLoanId.value = loan.id;
};
const getCurrencies = () => {
if (localCurrencies.value.length === 0) {
axios.get(props.data.url.currencies, form).then((response) => {
localCurrencies.value = response.data.data;
});
}
};
const submit = () => {
if (form.loaners.length === 0 || form.loanees.length === 0) {
warning.value = trans('Please indicate the contacts');
return;
}
loadingState.value = 'loading';
axios
.post(props.data.url.store, form)
.then((response) => {
flash(trans('The loan has been created'), 'success');
localLoans.value.unshift(response.data.data);
loadingState.value = '';
createLoanModalShown.value = false;
})
.catch((error) => {
loadingState.value = '';
form.errors = error.response.data;
});
};
const update = (loan) => {
loadingState.value = 'loading';
axios
.put(loan.url.update, form)
.then((response) => {
loadingState.value = '';
flash(trans('The loan has been updated'), 'success');
localLoans.value[localLoans.value.findIndex((x) => x.id === loan.id)] = response.data.data;
editedLoanId.value = 0;
})
.catch((error) => {
loadingState.value = '';
form.errors = error.response.data;
});
};
const destroy = (loan) => {
if (confirm(trans('Are you sure? This action cannot be undone.'))) {
axios
.delete(loan.url.destroy)
.then(() => {
flash(trans('The loan has been deleted'), 'success');
let id = localLoans.value.findIndex((x) => x.id === loan.id);
localLoans.value.splice(id, 1);
})
.catch((error) => {
loadingState.value = null;
form.errors = error.response.data;
});
}
};
const toggle = (loan) => {
axios
.put(loan.url.toggle, form)
.then((response) => {
flash(trans('The loan has been settled'), 'success');
localLoans.value[localLoans.value.findIndex((x) => x.id === loan.id)] = response.data.data;
})
.catch((error) => {
form.errors = error.response.data;
});
};
</script>
<template>
<div class="mb-10">
<!-- title + cta -->
<div class="mb-3 items-center justify-between border-b border-gray-200 pb-2 dark:border-gray-700 sm:flex">
<div class="mb-2 sm:mb-0 flex items-center gap-2">
<HandCoins class="h-4 w-4 text-gray-600" />
<span class="font-semibold"> {{ $t('Loans') }} </span>
</div>
<pretty-button
:text="$t('Record a loan')"
:icon="'plus'"
:class="'w-full sm:w-fit'"
@click="showCreateLoanModal" />
</div>
<div>
<!-- add a loan modal -->
<form
v-if="createLoanModalShown"
class="mb-6 rounded-lg border border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-900"
@submit.prevent="submit()">
<div class="border-b border-gray-200 dark:border-gray-700">
<!-- loan options -->
<div class="border-b border-gray-200 px-5 pb-3 pt-5 dark:border-gray-700">
<ul>
<li class="me-5 inline-block">
<div class="flex items-center">
<input
id="object"
v-model="form.type"
value="object"
name="name-order"
type="radio"
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
<label
for="object"
class="ms-3 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
{{ $t('The loan is an object') }}
</label>
</div>
</li>
<li class="me-5 inline-block">
<div class="flex items-center">
<input
id="monetary"
v-model="form.type"
value="monetary"
name="name-order"
type="radio"
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
<label
for="monetary"
class="ms-3 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
{{ $t('The loan is monetary') }}
</label>
</div>
</li>
</ul>
</div>
<!-- name -->
<div class="border-b border-gray-200 p-5 dark:border-gray-700">
<text-input
ref="nameInput"
v-model="form.name"
:label="$t('What is the loan?')"
:type="'text'"
:autofocus="true"
:input-class="'block w-full'"
:required="true"
:autocomplete="false"
:maxlength="255"
@esc-key-pressed="createLoanModalShown = false" />
</div>
<!-- amount + currency -->
<div v-if="form.type === 'monetary'" class="flex border-b border-gray-200 p-5 dark:border-gray-700">
<text-input
ref="label"
v-model="form.amount_lent"
:label="$t('How much money was lent?')"
:help="$t('Write the amount with a dot if you need decimals, like 100.50')"
:type="'number'"
:autofocus="true"
:input-class="'w-full'"
:required="false"
:min="0"
:max="10000000"
:autocomplete="false"
@esc-key-pressed="createLoanModalShown = false" />
<dropdown
v-model="form.currency_id"
:data="localCurrencies"
:required="false"
:class="'mb-5 ms-3'"
:placeholder="$t('Choose a value')"
:dropdown-class="'block'"
:label="$t('Currency')" />
</div>
<!-- loaned at -->
<div class="border-b border-gray-200 p-5 dark:border-gray-700">
<p class="mb-2 block text-sm">{{ $t('When was the loan made?') }}</p>
<DatePicker
v-model.string="form.loaned_at"
class="inline-block h-full"
:masks="masks"
:locale="$page.props.auth.user?.locale_ietf"
:is-dark="isDark()">
<template #default="{ inputValue, inputEvents }">
<input
class="rounded-xs border bg-white px-2 py-1 dark:bg-gray-900"
:value="inputValue"
v-on="inputEvents" />
</template>
</DatePicker>
</div>
<!-- loaned by or to -->
<div class="flex items-center items-stretch border-b border-gray-200 dark:border-gray-700">
<contact-selector
v-model="form.loaners"
:search-url="layoutData.vault.url.search_contacts_only"
:most-consulted-contacts-url="layoutData.vault.url.get_most_consulted_contacts"
:display-most-consulted-contacts="false"
:label="$t('Who makes the loan?')"
:add-multiple-contacts="true"
:required="true"
:class="'flex-1 border-e border-gray-200 p-5 dark:border-gray-700'" />
<contact-selector
v-model="form.loanees"
:search-url="layoutData.vault.url.search_contacts_only"
:most-consulted-contacts-url="layoutData.vault.url.get_most_consulted_contacts"
:display-most-consulted-contacts="true"
:label="$t('Who the loan is for?')"
:add-multiple-contacts="true"
:required="true"
:class="'flex-1 p-5'" />
</div>
<!-- description -->
<div class="p-5">
<text-area
v-model="form.description"
:label="$t('Description')"
:maxlength="255"
:textarea-class="'block w-full'" />
</div>
</div>
<div v-if="form.errors.length > 0" class="p-5">
<errors :errors="form.errors" />
</div>
<div v-if="warning !== ''" class="border-b p-3"> {{ warning }}</div>
<div class="flex justify-between p-5">
<pretty-span :text="$t('Cancel')" :class="'me-3'" @click="createLoanModalShown = false" />
<pretty-button :text="$t('Add loan')" :state="loadingState" :icon="'plus'" :class="'save'" />
</div>
</form>
<!-- list of loans -->
<div v-for="loan in localLoans" :key="loan.id" class="mb-5 flex">
<div v-if="editedLoanId !== loan.id" class="me-3 flex items-center">
<div class="flex -space-x-2 overflow-hidden">
<div v-for="loaner in loan.loaners" :key="loaner.id">
<contact-card :contact="loaner" :avatar-classes="'h-7 w-7 rounded-full me-2'" :display-name="false" />
</div>
</div>
<ArrowIcon :type="'right'" :size="'big'" />
<div v-for="loanee in loan.loanees" :key="loanee.id">
<contact-card :contact="loanee" :avatar-classes="'h-7 w-7 rounded-full me-2'" :display-name="false" />
</div>
</div>
<div
v-if="editedLoanId !== loan.id"
class="item-list w-full rounded-lg border border-gray-200 bg-white hover:bg-slate-50 dark:border-gray-700 dark:bg-gray-900 dark:hover:bg-slate-800">
<div class="border-b border-gray-200 px-3 py-2 dark:border-gray-700">
<div class="flex items-center justify-between">
<div>
<span class="me-2 block">
<span v-if="loan.amount_full" class="me-2">
{{ loan.amount_full }}
<span class="ms-2"> </span>
</span>
{{ loan.name }}
</span>
<span v-if="loan.description">
{{ loan.description }}
</span>
</div>
<span v-if="loan.loaned_at_human_format" class="me-2 text-sm text-gray-500">{{
loan.loaned_at_human_format
}}</span>
</div>
</div>
<!-- actions -->
<div class="flex items-center justify-between px-3 py-2">
<ul class="text-sm">
<!-- settle -->
<li
v-if="!loan.settled"
class="me-4 inline cursor-pointer text-blue-500 hover:underline"
@click="toggle(loan)">
{{ $t('Settle') }}
</li>
<li v-else class="me-4 inline cursor-pointer text-blue-500 hover:underline" @click="toggle(loan)">
{{ $t('Revert') }}
</li>
<!-- edit -->
<li class="me-4 inline cursor-pointer text-blue-500 hover:underline" @click="showEditLoanModal(loan)">
{{ $t('Edit') }}
</li>
<!-- delete -->
<li class="inline cursor-pointer text-red-500 hover:text-red-900" @click="destroy(loan)">
{{ $t('Delete') }}
</li>
</ul>
</div>
</div>
<!-- edit loan modal -->
<form
v-if="editedLoanId === loan.id"
class="mb-6 w-full rounded-lg border border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-900"
@submit.prevent="update(loan)">
<div class="border-b border-gray-200 dark:border-gray-700">
<!-- loan options -->
<div class="border-b border-gray-200 px-5 pb-3 pt-5 dark:border-gray-700">
<ul>
<li class="me-5 inline-block">
<div class="flex items-center">
<input
id="object"
v-model="form.type"
value="object"
name="name-order"
type="radio"
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
<label
for="object"
class="ms-3 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
{{ $t('The loan is an object') }}
</label>
</div>
</li>
<li class="me-5 inline-block">
<div class="flex items-center">
<input
id="monetary"
v-model="form.type"
value="monetary"
name="name-order"
type="radio"
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
<label
for="monetary"
class="ms-3 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
{{ $t('The loan is monetary') }}
</label>
</div>
</li>
</ul>
</div>
<!-- name -->
<div class="border-b border-gray-200 p-5 dark:border-gray-700">
<text-input
ref="name"
v-model="form.name"
:label="$t('What is the loan?')"
:type="'text'"
:autofocus="true"
:input-class="'block w-full'"
:required="true"
:autocomplete="false"
:maxlength="255"
@esc-key-pressed="createLoanModalShown = false" />
</div>
<!-- amount + currency -->
<div v-if="form.type === 'monetary'" class="flex border-b border-gray-200 p-5 dark:border-gray-700">
<text-input
ref="label"
v-model="form.amount_lent"
:label="$t('How much was lent?')"
:help="$t('Write the amount with a dot if you need decimals, like 100.50')"
:type="'number'"
:autofocus="true"
:input-class="'w-full'"
:required="false"
:min="0"
:max="10000000"
:autocomplete="false"
@esc-key-pressed="createLoanModalShown = false" />
<dropdown
v-model="form.currency_id"
:data="localCurrencies"
:required="false"
:class="'mb-5 ms-3'"
:placeholder="$t('Choose a value')"
:dropdown-class="'block'"
:label="$t('Currency')" />
</div>
<!-- loaned at -->
<div class="border-b border-gray-200 p-5 dark:border-gray-700">
<p class="mb-2 block text-sm">{{ $t('When was the loan made?') }}</p>
<DatePicker
v-model.string="form.loaned_at"
class="inline-block h-full"
:masks="masks"
:locale="$page.props.auth.user?.locale_ietf"
:is-dark="isDark()">
<template #default="{ inputValue, inputEvents }">
<input
class="rounded-xs border bg-white px-2 py-1 dark:bg-gray-900"
:value="inputValue"
v-on="inputEvents" />
</template>
</DatePicker>
</div>
<!-- loaned by or to -->
<div class="flex items-center items-stretch border-b border-gray-200 dark:border-gray-700">
<contact-selector
v-model="form.loaners"
:search-url="layoutData.vault.url.search_contacts_only"
:most-consulted-contacts-url="layoutData.vault.url.get_most_consulted_contacts"
:display-most-consulted-contacts="false"
:label="$t('Who makes the loan?')"
:add-multiple-contacts="true"
:required="true"
:class="'flex-1 border-e border-gray-200 p-5 dark:border-gray-700'" />
<contact-selector
v-model="form.loanees"
:search-url="layoutData.vault.url.search_contacts_only"
:most-consulted-contacts-url="layoutData.vault.url.get_most_consulted_contacts"
:display-most-consulted-contacts="true"
:label="$t('Who the loan is for?')"
:add-multiple-contacts="true"
:required="true"
:class="'flex-1 p-5'" />
</div>
<!-- description -->
<div class="p-5">
<text-area
v-model="form.description"
:label="$t('Description')"
:maxlength="255"
:textarea-class="'block w-full'" />
</div>
</div>
<div v-if="form.errors.length > 0" class="p-5">
<errors :errors="form.errors" />
</div>
<div v-if="warning !== ''" class="border-b p-3"> {{ warning }}</div>
<div class="flex justify-between p-5">
<pretty-span :text="$t('Cancel')" :class="'me-3'" @click="editedLoanId = 0" />
<pretty-button :text="$t('Save')" :state="loadingState" :icon="'plus'" :class="'save'" />
</div>
</form>
</div>
</div>
<!-- blank state -->
<div
v-if="localLoans.length === 0"
class="mb-6 rounded-lg border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900">
<img src="/img/contact_blank_loan.svg" :alt="$t('Loans')" class="mx-auto mt-4 h-14 w-14" />
<p class="px-5 pb-5 pt-2 text-center">{{ $t('There are no loans yet.') }}</p>
</div>
</div>
</template>
<style lang="scss" scoped>
.icon-sidebar {
color: #737e8d;
top: -2px;
}
.item-list {
&:hover {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
}
</style>