575 lines
15 KiB
Vue
575 lines
15 KiB
Vue
<template>
|
|
<section class="hero is-fullheight account-item" v-if="account">
|
|
<account-encrypted v-if="isEncrypted" />
|
|
<div class="account-item-container">
|
|
<h2 class="title is-2 account-title">
|
|
<router-link
|
|
v-if="isAdmin"
|
|
:to="{ name: 'account-setting', params: { id } }"
|
|
:style="colorStyle()"
|
|
>
|
|
{{ account.name }}
|
|
</router-link>
|
|
<span v-else :style="colorStyle()">{{ account.name }}</span>
|
|
</h2>
|
|
<div class="columns is-centered" v-if="showShare">
|
|
<div class="column is-one-third">
|
|
<account-share :account="account" />
|
|
</div>
|
|
</div>
|
|
<article
|
|
class="message is-warning"
|
|
v-if="transactionWithoutExchange.length"
|
|
>
|
|
<div class="message-header">
|
|
<p>Attention</p>
|
|
</div>
|
|
<div class="message-body">
|
|
Certaines dépenses ne sont pas comptées car en attente de récupérer
|
|
les taux pratiqués à leur date respectives.
|
|
<ul>
|
|
<li v-for="(transaction, k) in transactionWithoutExchange" :key="k">
|
|
-
|
|
<router-link
|
|
:to="{ name: 'transaction', params: { id: transaction._id } }"
|
|
>{{ transaction.name }}</router-link
|
|
>
|
|
</li>
|
|
</ul>
|
|
<online-view @online="retrieveExchange">
|
|
<button
|
|
@click="retrieveExchange"
|
|
class="button is-success is-large is-rounded"
|
|
:class="{ 'is-loading': retrievingExchange }"
|
|
>
|
|
Récupérer les devises
|
|
</button>
|
|
</online-view>
|
|
</div>
|
|
</article>
|
|
<div class="tabs is-toggle is-fullwidth" v-if="transactions.length">
|
|
<ul>
|
|
<li>
|
|
<a
|
|
:style="colorStyle(activeTab === 'transaction')"
|
|
href="#"
|
|
@click.prevent="toggleTab('transaction')"
|
|
>
|
|
<awe-icon icon="dollar-sign" />
|
|
</a>
|
|
</li>
|
|
<li v-if="tagCount > 1">
|
|
<a
|
|
:style="colorStyle(activeTab === 'tag')"
|
|
href="#"
|
|
@click.prevent="toggleTab('tag')"
|
|
>
|
|
<awe-icon icon="tag" />
|
|
</a>
|
|
</li>
|
|
<li v-if="isMultiUser">
|
|
<a
|
|
:style="colorStyle(activeTab === 'balance')"
|
|
href="#"
|
|
@click.prevent="toggleTab('balance')"
|
|
>
|
|
<awe-icon icon="balance-scale" />
|
|
</a>
|
|
</li>
|
|
<li v-if="isMultiUser">
|
|
<a
|
|
:style="colorStyle(activeTab === 'refund')"
|
|
href="#"
|
|
@click.prevent="toggleTab('refund')"
|
|
>
|
|
<awe-icon icon="exchange-alt" />
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<transition-group
|
|
:name="transitionName"
|
|
tag="div"
|
|
mode="out-in"
|
|
v-if="transactions.length"
|
|
>
|
|
<div
|
|
key="transaction"
|
|
class="transaction-panel tab-content columns is-centered"
|
|
v-if="activeTab === 'transaction'"
|
|
>
|
|
<div class="column column-transaction-panel">
|
|
<account-transaction-list
|
|
:account="account"
|
|
:transactions="transactions"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
key="tag"
|
|
class="tag-panel tab-content columns is-centered"
|
|
v-if="activeTab === 'tag'"
|
|
>
|
|
<div class="column">
|
|
<tag-list
|
|
:transactions="transactionWithoutRefund"
|
|
:account="account"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
key="balance"
|
|
class="balance-panel tab-content columns is-centered"
|
|
v-if="activeTab === 'balance'"
|
|
>
|
|
<div class="column">
|
|
<chart-balance
|
|
:account="account"
|
|
:stats="userStats"
|
|
:currency="account.mainCurrency"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
key="refund"
|
|
class="refund-panel tab-content columns is-centered"
|
|
v-if="activeTab === 'refund'"
|
|
>
|
|
<div class="column">
|
|
<div
|
|
v-if="userRefunds.length"
|
|
class="columns is-centered is-multiline"
|
|
>
|
|
<div
|
|
class="column is-one-fifth"
|
|
v-for="(refund, k) in userRefunds"
|
|
:key="`${refund.from.alias}-${refund.to.alias}`"
|
|
>
|
|
<refund-transaction
|
|
:refund="userRefunds[k]"
|
|
:account="account"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div v-else class="equilibrium">Le compte est à l'équilibre !</div>
|
|
</div>
|
|
</div>
|
|
</transition-group>
|
|
</div>
|
|
<fab-button
|
|
v-if="!account.archive"
|
|
:to="{ name: 'transaction-new', params: { id: account._id } }"
|
|
:button-style="backgroundColor"
|
|
:margin="true"
|
|
>
|
|
<awe-icon icon="money-bill-wave" />
|
|
<span slot="fulltext">dépense</span>
|
|
</fab-button>
|
|
</section>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Watch } from 'vue-property-decorator'
|
|
import { Action, Getter } from 'vuex-class'
|
|
import BaseAccount from '@/base-components/BaseAccount'
|
|
import bus, { SYNC } from '@/utils/bus-event'
|
|
import notif from '@/utils/notif'
|
|
import TransactionType from '@/enums/TransactionType'
|
|
import accountService from '@/services/AccountService'
|
|
import chartService from '@/services/ChartService'
|
|
import balanceService from '@/services/BalanceService'
|
|
import transactionService from '@/services/TransactionService'
|
|
import exchangeService from '@/services/ExchangeService'
|
|
import IAccount from '@/models/IAccount'
|
|
import IBalance from '@/models/IBalance'
|
|
import ICurrency from '@/models/ICurrency'
|
|
import IRefund from '@/models/IRefund'
|
|
import IStat from '@/models/IStat'
|
|
import ITransaction from '@/models/ITransaction'
|
|
import IUser from '@/models/IUser'
|
|
import { primary, main, findContrastColor, findDarkValue } from '@/utils'
|
|
|
|
@Component({
|
|
components: {
|
|
'account-share': () => import('@/components/AccountShare.vue'),
|
|
'account-encrypted': () => import('@/components/AccountEncrypted.vue'),
|
|
'account-transaction-list': () =>
|
|
import('@/components/AccountTransactionList.vue'),
|
|
'tag-list': () => import('@/components/TagList.vue'),
|
|
'chart-balance': () => import('@/components/ChartBalance.vue'),
|
|
'online-view': () => import('@/components/OnlineView.vue'),
|
|
'refund-transaction': () => import('@/components/RefundTransaction.vue'),
|
|
'fab-button': () => import('@/components/FabButton.vue')
|
|
}
|
|
})
|
|
export default class AccountItem extends BaseAccount {
|
|
@Action
|
|
public setTitle!: any
|
|
public retrieveTransactions: boolean = false
|
|
public transactions: ITransaction[] = []
|
|
public activeTab: string = 'transaction'
|
|
public retrievingExchange: boolean = false
|
|
public transitionName: string = ''
|
|
|
|
public async getData(docIds?: string[]): Promise<void> {
|
|
if (docIds && !docIds.find((i: string) => i === this.id)) {
|
|
return
|
|
}
|
|
try {
|
|
if (this.id) {
|
|
this.account = await accountService.get(this.id)
|
|
if (!this.account) {
|
|
notif.error(`le compte ${this.id} n'existe pas`)
|
|
this.$router.push({ name: 'home' })
|
|
return
|
|
}
|
|
this.transactions = await transactionService.getAllByAccountId(this.id)
|
|
this.retrieveTransactions = true
|
|
if (document && document.documentElement) {
|
|
if (this.account.color) {
|
|
document.documentElement.style.setProperty(
|
|
'--primary-color',
|
|
this.account.color
|
|
)
|
|
document.documentElement.style.setProperty(
|
|
'--primary-font-color',
|
|
findDarkValue(this.account.color) || main
|
|
)
|
|
} else {
|
|
document.documentElement.style.setProperty(
|
|
'--primary-color',
|
|
primary
|
|
)
|
|
document.documentElement.style.setProperty(
|
|
'--primary-font-color',
|
|
main
|
|
)
|
|
}
|
|
}
|
|
this.setTitle(this.account.name)
|
|
}
|
|
} catch (error) {
|
|
// tslint:disable-next-line
|
|
console.error({ error })
|
|
this.$router.push({ name: 'home' })
|
|
return
|
|
}
|
|
}
|
|
|
|
public toggleTab(tab: string): void {
|
|
this.activeTab = tab
|
|
}
|
|
|
|
public async retrieveExchange(): Promise<void> {
|
|
if (!this.transactionWithoutExchange.length) {
|
|
return
|
|
}
|
|
this.retrievingExchange = true
|
|
try {
|
|
const transactions: ITransaction[] = [...this.transactionWithoutExchange]
|
|
for (const transaction of transactions) {
|
|
transaction.exchange = await exchangeService.get(
|
|
transaction.mainCurrency.code,
|
|
transaction.date
|
|
)
|
|
}
|
|
await transactionService.multipleSave(transactions)
|
|
notif.success('Dépenses mises à jour.')
|
|
} catch (error) {
|
|
// tslint:disable-next-line
|
|
console.warn({ error })
|
|
}
|
|
this.retrievingExchange = false
|
|
}
|
|
|
|
public totalCostByUserAlias(alias: string): number {
|
|
if (!this.account) {
|
|
return 0
|
|
}
|
|
const transactions: ITransaction[] = this.transactionWithExchange
|
|
return transactionService.getTotalCost(
|
|
transactions,
|
|
this.account.mainCurrency.code,
|
|
alias
|
|
)
|
|
}
|
|
|
|
public get totalUserCost(): Array<{ alias: string; total: number }> {
|
|
if (!this.account) {
|
|
return []
|
|
}
|
|
return this.account.users
|
|
.map((user: IUser) => ({
|
|
alias: user.alias || '',
|
|
total: this.totalCostByUserAlias(user.alias || '')
|
|
}))
|
|
.filter((user) => user.total > 0)
|
|
}
|
|
|
|
public get showShare(): boolean {
|
|
return !!(
|
|
this.account &&
|
|
this.account.users.length > 1 &&
|
|
this.retrieveTransactions &&
|
|
!this.transactions.length
|
|
)
|
|
}
|
|
|
|
public get transactionWithoutRefund(): ITransaction[] {
|
|
if (!this.transactions) {
|
|
return []
|
|
}
|
|
return this.transactions.filter(
|
|
(transaction: ITransaction) =>
|
|
transaction.transactionType === TransactionType.normal
|
|
)
|
|
}
|
|
|
|
public get tagCount(): number {
|
|
const tags = new Set(
|
|
this.transactionWithoutRefund.map(
|
|
(transaction: ITransaction) => transaction.tag
|
|
)
|
|
)
|
|
return tags.size
|
|
}
|
|
|
|
public get transactionWithExchange(): ITransaction[] {
|
|
if (!this.transactions) {
|
|
return []
|
|
}
|
|
return this.transactions.filter(
|
|
(transaction: ITransaction) => !!transaction.exchange
|
|
)
|
|
}
|
|
|
|
public get transactionWithoutExchange(): ITransaction[] {
|
|
if (!this.transactions) {
|
|
return []
|
|
}
|
|
return this.transactions.filter(
|
|
(transaction: ITransaction) => !transaction.exchange
|
|
)
|
|
}
|
|
|
|
public get userStats(): IStat[] {
|
|
const totalCost: number = this.totalCost
|
|
if (!this.account || totalCost === 0) {
|
|
return []
|
|
}
|
|
const account: IAccount = this.account
|
|
const transactions: ITransaction[] = this.transactionWithExchange
|
|
const mainCurrency: ICurrency = account.mainCurrency
|
|
return this.account.users.map((user: IUser) => {
|
|
const userCost: number = transactionService.getTotalCost(
|
|
transactions,
|
|
account.mainCurrency.code,
|
|
user.alias
|
|
)
|
|
const percent: number = Math.round((userCost / totalCost) * 100)
|
|
return {
|
|
label: user.alias || '',
|
|
value: userCost,
|
|
percent,
|
|
balance: balanceService.getBalanceByUser(
|
|
user,
|
|
mainCurrency,
|
|
transactions
|
|
)
|
|
}
|
|
})
|
|
}
|
|
|
|
public get myTotalCost(): number {
|
|
if (!this.account) {
|
|
return 0
|
|
}
|
|
const transactions: ITransaction[] = this.transactionWithExchange
|
|
return transactionService.getTotalCost(
|
|
transactions,
|
|
this.account.mainCurrency.code,
|
|
this.userAlias
|
|
)
|
|
}
|
|
|
|
public get totalCost(): number {
|
|
if (!this.account) {
|
|
return 0
|
|
}
|
|
const transactions: ITransaction[] = this.transactionWithExchange
|
|
return transactionService.getTotalCost(
|
|
transactions,
|
|
this.account.mainCurrency.code
|
|
)
|
|
}
|
|
|
|
public get userBalance(): IBalance[] {
|
|
if (!this.account || !this.account.users) {
|
|
return []
|
|
}
|
|
const mainCurrency: ICurrency = this.account.mainCurrency
|
|
const transactions: ITransaction[] = this.transactionWithExchange
|
|
return this.account.users.map((user: IUser) =>
|
|
balanceService.getBalanceByUser(user, mainCurrency, transactions)
|
|
)
|
|
}
|
|
|
|
public get userRefunds(): IRefund[] {
|
|
if (!this.account) {
|
|
return []
|
|
}
|
|
return balanceService.getRefund(this.userBalance)
|
|
}
|
|
|
|
public get isAdmin(): boolean {
|
|
if (!this.account || !this.account.admin || !this.user) {
|
|
return true
|
|
}
|
|
return this.account.admin.slugEmail === this.user.slugEmail
|
|
}
|
|
|
|
public get userAlias(): string {
|
|
if (!this.account || !this.user) {
|
|
return ''
|
|
}
|
|
const mainUser = this.user
|
|
const user = this.account.users.find(
|
|
(u: IUser) => u.slugEmail === mainUser.slugEmail
|
|
)
|
|
return (user && user.alias) || ''
|
|
}
|
|
|
|
public get isEncrypted(): boolean {
|
|
return !!(this.account && this.account.name.length > 30)
|
|
}
|
|
|
|
public get backgroundColor(): any | null {
|
|
if (!this.account || !this.account.color) {
|
|
return null
|
|
}
|
|
return {
|
|
backgroundColor: this.account.color,
|
|
color: findContrastColor(this.account.color) || 'black'
|
|
}
|
|
}
|
|
|
|
public get isMultiUser(): boolean {
|
|
return !!this.account && this.account.users.length > 1
|
|
}
|
|
|
|
@Watch('activeTab')
|
|
public onActiveTabChange(newTab: string, oldTab: string): void {
|
|
const tabs: string[] = ['transaction', 'tag', 'balance', 'refund']
|
|
this.transitionName =
|
|
tabs.indexOf(newTab) > tabs.indexOf(oldTab) ? 'slide-left' : 'slide-right'
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
@import '../../styles/variables';
|
|
|
|
.account-item {
|
|
font-size: 14pt;
|
|
|
|
h2.title,
|
|
a {
|
|
color: var(--primary-font-color);
|
|
}
|
|
.account-title {
|
|
margin: 10px 0;
|
|
span,
|
|
a {
|
|
padding: 0 10px;
|
|
margin: 0 10px;
|
|
}
|
|
}
|
|
|
|
.tabs.is-toggle {
|
|
max-width: 400pt;
|
|
margin: auto;
|
|
}
|
|
|
|
.tabs.is-toggle {
|
|
a {
|
|
height: 35pt;
|
|
width: 35pt;
|
|
border-radius: 50%;
|
|
margin: auto;
|
|
border-width: 2px;
|
|
font-size: 14pt;
|
|
}
|
|
li:first-child,
|
|
li:last-child {
|
|
a {
|
|
border-radius: 50%;
|
|
}
|
|
}
|
|
}
|
|
&.hero {
|
|
&.is-fullheight {
|
|
min-height: calc(100vh - 52px);
|
|
}
|
|
}
|
|
.equilibrium {
|
|
font-size: 25pt;
|
|
color: var(--primary-font-color);
|
|
}
|
|
table.table {
|
|
margin: auto;
|
|
}
|
|
th,
|
|
.pay-by {
|
|
text-align: center;
|
|
}
|
|
.numeric {
|
|
text-align: right;
|
|
}
|
|
.table-container {
|
|
overflow-x: auto;
|
|
}
|
|
.total-panel {
|
|
font-size: 16pt;
|
|
}
|
|
}
|
|
.no-transaction {
|
|
font-size: 16pt;
|
|
}
|
|
.tab-content {
|
|
position: absolute;
|
|
width: calc(100% - 30px);
|
|
margin: auto;
|
|
transition: opacity 0.5s cubic-bezier(0.55, 0, 0.1, 1),
|
|
transform 0.5s cubic-bezier(0.55, 0, 0.1, 1);
|
|
&.columns {
|
|
&:last-child {
|
|
margin-bottom: 60px;
|
|
}
|
|
}
|
|
}
|
|
.slide-left-enter,
|
|
.slide-right-leave-active {
|
|
opacity: 0;
|
|
transform: translate(15px, 0);
|
|
}
|
|
.slide-left-leave-active,
|
|
.slide-right-enter {
|
|
opacity: 0;
|
|
transform: translate(-15px, 0);
|
|
}
|
|
.table {
|
|
th,
|
|
td {
|
|
vertical-align: middle;
|
|
}
|
|
}
|
|
.account-item-container {
|
|
flex: 1;
|
|
}
|
|
|
|
.column-transaction-panel {
|
|
margin-right: -1.9rem;
|
|
margin-left: -1.5rem;
|
|
}
|
|
</style>
|