Abstract footer to separate component. Abstract logo to separate component. Be able to edit team in modal. Add custom fields to team. Removed vat and reg no from team (replaced by custom fields). Custom fields are also stored on the invoice. When creating invoice add team custom fields to invoice as well. Be able to set default invoice due date, late fee, vat rate and currency.

This commit is contained in:
HenriT
2021-04-13 12:07:08 +03:00
parent dce73b5603
commit 580fd9aa5a
19 changed files with 523 additions and 137 deletions

View File

@ -0,0 +1,64 @@
<template>
<footer class="col-12 d-flex justify-content-between align-items-center text-secondary px-0 mt-3 d-print-none">
<button class="btn btn-sm text-secondary" @click="toggleTheme">
Lights {{ theme === 'dark' ? 'on' : 'off' }}
<i class="material-icons material-icons-round md-14 align-text-bottom ml-1">
{{ theme === 'dark' ? 'wb_sunny' : 'brightness_2' }}
</i>
</button>
<div>
<small v-b-tooltip.hover
title="All your data is saved in your browser and not on any server.
This application is truly serverless and only you have access to your data."
class="pointer">
What about my data?
</small>
<small class="pl-2">
Made with
<i class="material-icons material-icons-round md-14 align-text-bottom">favorite</i>
by
<a href="https://mokuapp.io/" class="text-secondary" target="_blank">Moku</a>.
</small>
<a href="https://github.com/mokuappio/serverless-invoices"
class="btn btn-sm btn--icon ml-2"
target="_blank">
<img src="@/assets/img/github.png"
alt="Serverless Invoices Github"
v-if="theme === 'dark'">
<img src="@/assets/img/github-dark.png"
alt="Serverless Invoices Github"
v-else>
</a>
<a href="https://app.mokuapp.io/"
class="btn btn-sm btn-primary ml-2"
target="_blank">Upgrade</a>
</div>
</footer>
</template>
<script>
import { mapState } from 'vuex';
import { VBTooltip } from 'bootstrap-vue';
export default {
directives: {
'b-tooltip': VBTooltip,
},
computed: {
...mapState({
theme: state => state.themes.theme,
}),
},
methods: {
toggleTheme() {
if (this.theme === 'light') {
this.$store.commit('themes/theme', 'dark');
} else {
this.$store.commit('themes/theme', 'light');
}
localStorage.setItem('theme', this.theme);
document.documentElement.setAttribute('data-theme', this.theme);
},
},
};
</script>

View File

@ -1,12 +1,12 @@
<template>
<div>
<strong>
<strong class="break-line">
<AppEditable :value="invoice.from_name"
:errors="errors"
field="from_name"
placeholder="Your company name"
class="break-line"
@change="updateProp({ from_name: $event })"/>
<i class="material-icons md-18 ml-2 pointer d-print-none" @click="editTeam">edit</i>
</strong>
<AppEditable :value="invoice.from_address"
suffix=", "
@ -35,20 +35,8 @@
<AppError :errors="errors" field="from_county"/>
<AppError :errors="errors" field="from_country"/>
<span :class="{'d-print-none': !invoice.from_reg_no }">Reg no: </span>
<AppEditable :value="invoice.from_reg_no"
:errors="errors"
field="from_reg_no"
placeholder="Enter reg no"
class="break-line"
@change="updateProp({ from_reg_no: $event })"/>
<span :class="{'d-print-none': !invoice.from_vat_no }">VAT no: </span>
<AppEditable :value="invoice.from_vat_no"
:errors="errors"
field="from_vat_no"
placeholder="Enter vat no"
class="break-line"
@change="updateProp({ from_vat_no: $event })"/>
<InvoiceTeamFields :invoice="invoice"/>
<AppEditable :value="invoice.from_email"
:errors="errors"
field="from_email"
@ -58,6 +46,7 @@
</template>
<script>
import AppError from '@/components/form/AppError';
import InvoiceTeamFields from '@/components/invoices/InvoiceTeamFields';
import AppEditable from '../form/AppEditable';
export default {
@ -65,11 +54,15 @@ export default {
components: {
AppEditable,
AppError,
InvoiceTeamFields,
},
methods: {
updateProp(props) {
this.$emit('update', props);
},
editTeam() {
this.$store.commit('teams/isModalOpen', true);
},
},
};
</script>

View File

@ -2,15 +2,7 @@
<div class="card bg-base dp--02 invoice-box" v-if="invoice">
<div class="card-body">
<div class="row mb-5">
<div class="col-4">
<img v-if="team.logo_url"
v-b-modal.team_logo_url
:src="team.logo_url" style="width:100%; max-width:200px;">
<button class="btn btn-sm" v-b-modal.team_logo_url v-else>
<i class="material-icons material-icons-round md-36">file_upload</i>
</button>
<AppError :errors="errors" field="logo_url"/>
</div>
<TeamLogo class="col-sm-4" :errors="errors"/>
<InvoiceHeader :invoice="invoice" :errors="errors" @update="updateProp"
class="col-8 text-right mb-2"/>
</div>
@ -60,23 +52,6 @@
class="col-4 text-right"/>
</div>
</div>
<BModal id="team_logo_url"
centered
title="Choose logo"
hide-footer
size="sm"
content-class="bg-base dp--24 text-center">
<AppFileInput accept="image/*" class="mb-4" @selected="logoSelected"
button-text="Select from files" output-type="base64"/>
or
<AppInput :value="team.logo_url"
class="mt-4"
@change="updateTeam({ logo_url: $event })"
label="Insert web url"
field="logo_url"
:errors="errors"
type="url"/>
</BModal>
</div>
</template>
<script>
@ -90,16 +65,11 @@ import InvoiceHeader from '@/components/invoices/InvoiceHeader';
import InvoiceTotals from '@/components/invoices/InvoiceTotals';
import AppEditable from '@/components/form/AppEditable';
import AppError from '@/components/form/AppError';
import { BModal, VBModal } from 'bootstrap-vue';
import AppInput from '@/components/form/AppInput';
import AppFileInput from '@/components/form/AppFileInput';
import TeamLogo from '@/components/team/TeamLogo';
export default {
directives: {
'b-modal': VBModal,
},
components: {
AppFileInput,
TeamLogo,
InvoiceTotals,
InvoiceHeader,
InvoiceContactDetails,
@ -109,15 +79,12 @@ export default {
InvoiceClientDetails,
AppEditable,
AppError,
AppInput,
BModal,
},
computed: {
...mapState({
errors: state => state.invoices.errors,
}),
...mapGetters({
team: 'teams/team',
invoice: 'invoices/invoice',
}),
},
@ -139,16 +106,6 @@ export default {
addRow() {
this.$store.dispatch('invoiceRows/addRow', this.invoice.id);
},
updateTeam(props) {
this.$store.dispatch('teams/updateTeam', props);
},
logoSelected(payload) {
this.errors.clear();
if (payload.size / 1000 > 512) {
return this.errors.set({ logo_url: ['Logo has to be under 512kb.'] });
}
this.updateTeam({ logo_url: payload.content });
},
},
};
</script>

View File

@ -0,0 +1,29 @@
<template>
<div>
<div v-for="field in invoice.team_fields">
<span :class="{'d-print-none': !field.value }">{{ field.label }}: </span>
<AppEditable :value="field.value"
:placeholder="field.label"
class="break-line"
@change="updateProp({ value: $event }, field)"/>
</div>
</div>
</template>
<script>
import AppEditable from '@/components/form/AppEditable';
export default {
props: ['invoice'],
components: {
AppEditable,
},
methods: {
updateProp(props, field) {
this.$store.dispatch('invoiceTeamFields/updateInvoiceTeamField', {
props,
fieldId: field.id,
});
},
},
};
</script>

View File

@ -0,0 +1,70 @@
<template>
<div>
<div v-for="field in team.fields" :key="field.id" class="col-sm-6">
<AppEditable :value="field.label"
placeholder="Label"
@change="updateFieldProp({ label: $event }, field)"/>
<i class="material-icons md-18 float-right pointer" @click="removeField(field)">close</i>
<AppInput :value="field.value" @change="updateFieldProp({ value: $event }, field)"
:placeholder="field.label"/>
</div>
<div class="col-12">
<button class="btn btn-sm btn-secondary" @click="addNewField">
<i class="material-icons md-18">add</i>
Field
</button>
</div>
</div>
</template>
<script>
import NotificationService from '@/services/notification.service';
import AppInput from '@/components/form/AppInput';
import AppEditable from '@/components/form/AppEditable';
export default {
props: ['team'],
components: {
AppEditable,
AppInput,
},
computed: {
isNew() {
return this.team && this.team.$isNew;
},
},
methods: {
addNewField() {
this.$store.dispatch('teamFields/addNewField', this.team.id);
},
async removeField(field) {
const confirmed = await this.$bvModal.msgBoxConfirm(`Delete field ${field.label}?`, {
okTitle: 'Delete',
okVariant: 'danger',
cancelTitle: 'Dismiss',
cancelVariant: 'btn-link',
contentClass: 'bg-base dp--24',
});
if (confirmed) {
await this.$store.dispatch('teamFields/deleteTeamField', field.id);
try {
NotificationService.success('Deleted');
} catch (err) {
NotificationService.error(err.message);
}
}
},
updateFieldProp(props, field) {
if (this.isNew) {
return this.$store.dispatch('teamFields/teamFieldProps', {
props,
fieldId: field.id,
});
}
this.$store.dispatch('teamFields/updateTeamField', {
props,
fieldId: field.id,
});
},
},
};
</script>

View File

@ -0,0 +1,111 @@
<template>
<div>
<div class="row">
<div class="col-12 d-flex justify-content-between">
<h4>Team</h4>
<div v-if="team">
<button class="btn btn-sm btn-primary"
@click="$emit('done')">Done
</button>
</div>
</div>
</div>
<b-tabs v-if="team" nav-class="nav-tabs--simple mb-4" active-tab-class="active" class="row">
<b-tab title="General" class="col-12">
<div class="row">
<TeamLogo :errors="errors" class="col-sm-4"/>
</div>
<div class="row">
<AppInput :value="team.company_name" @change="updateProp({ company_name: $event })"
label="Company Name" field="company_name" :errors="errors" class="col-12"/>
<AppInput :value="team.contact_email" @change="updateProp({ contact_email: $event })"
label="Email" field="contact_email" :errors="errors" class="col-sm-7"/>
<AppInput :value="team.contact_phone" @change="updateProp({ contact_phone: $event })"
label="Phone" field="contact_phone" :errors="errors" class="col-sm-7"/>
<AppInput :value="team.website" @change="updateProp({ website: $event })"
label="Website" field="website" :errors="errors" class="col-sm-7"/>
</div>
<TeamFields class="row" :team="team"/>
</b-tab>
<b-tab title="Invoicing" class="col-12">
<div class="row">
<AppInput :value="team.vat_rate" @change="updateProp({ vat_rate: $event })" type="number"
label="VAT rate" field="vat_rate" :errors="errors" class="col-sm-4"/>
<AppInput :value="team.invoice_late_fee" @change="updateProp({ invoice_late_fee: $event })" type="number"
label="Late fee (%)" field="invoice_late_fee" :errors="errors" class="col-sm-4"/>
<AppInput :value="team.invoice_due_days" @change="updateProp({ invoice_due_days: $event })" type="number"
label="Payment terms, days" field="invoice_due_days" :errors="errors" class="col-sm-4"/>
<AppInput :value="team.currency" @change="updateProp({ currency: $event })"
label="Default currency" field="currency" :errors="errors" class="col-sm-4"/>
</div>
</b-tab>
<b-tab title="Address" class="col-12">
<div class="row">
<AppInput :value="team.company_address" @change="updateProp({ company_address: $event })"
label="Company Address" field="company_address" :errors="errors"
class="col-12"/>
<AppInput :value="team.company_postal_code"
@change="updateProp({ company_postal_code: $event })"
label="Postal code" field="company_postal_code" :errors="errors"
class="col-sm-5"/>
<AppInput :value="team.company_city" @change="updateProp({ company_city: $event })"
label="City" field="company_city" :errors="errors" class="col-sm-7"/>
<AppInput :value="team.company_county" @change="updateProp({ company_county: $event })"
label="County/State" field="company_county" :errors="errors" class="col-sm-6"/>
<AppInput :value="team.company_country" @change="updateProp({ company_country: $event })"
label="Country" field="company_country" :errors="errors" class="col-sm-6"/>
</div>
</b-tab>
</b-tabs>
<div v-if="!team">Loading</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import {
BTab, BTabs,
} from 'bootstrap-vue';
import NotificationService from '@/services/notification.service';
import AppInput from '@/components/form/AppInput';
import Errors from '@/utils/errors';
import TeamFields from '@/components/team/TeamFields';
import TeamLogo from '@/components/team/TeamLogo';
export default {
components: {
TeamLogo,
TeamFields,
AppInput,
BTab,
BTabs,
},
data() {
return {
errors: new Errors(),
loading: false,
};
},
computed: {
...mapGetters({
team: 'teams/team',
}),
},
methods: {
updateProp(props) {
this.errors.clear();
this.$store.dispatch('teams/updateTeam', props)
.then(() => {
NotificationService.success('Updated');
})
.catch(err => this.errors.set(err.errors));
},
},
};
</script>

View File

@ -0,0 +1,71 @@
<template>
<div>
<img v-if="team.logo_url"
class="pointer"
@click="openModal"
:src="team.logo_url" style="width:100%; max-width:200px;">
<button class="btn btn-sm" @click="openModal" v-else>
<i class="material-icons material-icons-round md-36">file_upload</i>
</button>
<AppError :errors="errors" field="logo_url"/>
<BModal v-model="isModalOpen"
centered
title="Choose logo"
hide-footer
size="sm"
content-class="bg-base dp--24 text-center">
<AppFileInput accept="image/*" class="mb-4" @selected="logoSelected"
button-text="Select from files" output-type="base64"/>
or
<AppInput :value="team.logo_url"
class="mt-4"
@change="updateTeam({ logo_url: $event })"
label="Insert web url"
field="logo_url"
:errors="errors"
type="url"/>
</BModal>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import AppError from '@/components/form/AppError';
import { BModal } from 'bootstrap-vue';
import AppInput from '@/components/form/AppInput';
import AppFileInput from '@/components/form/AppFileInput';
export default {
props: ['errors'],
components: {
AppFileInput,
AppError,
AppInput,
BModal,
},
data() {
return {
isModalOpen: false,
};
},
computed: {
...mapGetters({
team: 'teams/team',
}),
},
methods: {
updateTeam(props) {
this.$store.dispatch('teams/updateTeam', props);
},
logoSelected(payload) {
this.errors.clear();
if (payload.size / 1000 > 512) {
return this.errors.set({ logo_url: ['Logo has to be under 512kb.'] });
}
this.updateTeam({ logo_url: payload.content });
},
openModal() {
this.isModalOpen = true;
},
},
};
</script>

View File

@ -0,0 +1,46 @@
<template>
<BModal v-model="isOpen"
centered
hide-footer
hide-header
content-class="bg-base dp--24">
<TeamForm @done="close()"/>
</BModal>
</template>
<script>
import { mapGetters } from 'vuex';
import { BModal } from 'bootstrap-vue';
import TeamForm from '@/components/team/TeamForm';
export default {
components: {
TeamForm,
BModal,
},
computed: {
isOpen: {
get() {
return this.$store.state.teams.isModalOpen;
},
set(val) {
this.$store.commit('teams/isModalOpen', val);
},
},
...mapGetters({
team: 'teams/team',
}),
},
mounted() {
this.getTeam();
},
methods: {
getTeam() {
this.$store.dispatch('teams/getTeam');
},
close() {
this.isOpen = false;
},
},
};
</script>

View File

@ -1,6 +1,8 @@
import storage from 'localforage';
import TeamService from '@/services/team.service';
import { validate, generateInvoiceNumber, removeVuexORMFlags } from '@/utils/helpers';
import {
validate, generateInvoiceNumber, removeVuexORMFlags, uuidv4,
} from '@/utils/helpers';
import dayjs from 'dayjs';
class InvoiceService {
@ -22,26 +24,33 @@ class InvoiceService {
invoice.issued_at = dayjs()
.format('YYYY-MM-DD');
invoice.due_at = dayjs()
.add(14, 'days')
.add(team.invoice_due_days || 14, 'days')
.format('YYYY-MM-DD');
invoice.number = generateInvoiceNumber(invoices);
invoice.late_fee = 0.5;
invoice.late_fee = team.invoice_late_fee || 0.5;
invoice.from_name = team.company_name;
invoice.from_address = team.company_address;
invoice.from_postal_code = team.company_postal_code;
invoice.from_city = team.company_city;
invoice.from_country = team.company_country;
invoice.from_county = team.company_county;
invoice.from_reg_no = team.company_reg_no;
invoice.from_vat_no = team.company_vat_no;
invoice.from_website = team.website;
invoice.from_email = team.contact_email;
invoice.from_phone = team.contact_phone;
invoice.vat_rate = 0;
invoice.currency = 'USD';
invoice.vat_rate = team.vat_rate || 0;
invoice.currency = team.currency || 'USD';
// Add custom fields
invoice.team_fields = team.fields.map(field => ({
id: uuidv4(),
label: field.label,
value: field.value,
invoice_id: invoice.id,
}));
delete invoice.client;
return this.saveInvoice(invoice);
}

View File

@ -11,8 +11,6 @@ class TeamService {
company_country: null,
company_county: null,
company_city: null,
company_reg_no: null,
company_vat_no: null,
website: null,
contact_email: null,
contact_phone: null,

View File

@ -0,0 +1,31 @@
import InvoiceTeamField from '@/store/models/invoice-team-field';
export default {
namespaced: true,
state: {},
mutations: {},
actions: {
init() {},
terminate() {},
invoiceTeamFieldProps(store, payload) {
return InvoiceTeamField.update({
where: payload.fieldId,
data: payload.props,
});
},
async updateInvoiceTeamField({ dispatch }, payload) {
await dispatch('invoiceTeamFieldProps', payload);
return dispatch('invoices/updateInvoice', null, { root: true });
},
async addInvoiceTeamField(store, payload) {
const field = await InvoiceTeamField.createNew();
await field.$update({
...payload.props,
invoice_id: payload.invoiceId,
});
},
async removeInvoiceTeamField(store, fieldId) {
await InvoiceTeamField.delete(fieldId);
},
},
};

View File

@ -86,8 +86,6 @@ export default {
from_city: 'company_city',
from_country: 'company_country',
from_county: 'company_county',
from_reg_no: 'company_reg_no',
from_vat_no: 'company_vat_no',
from_website: 'website',
from_email: 'contact_email',
from_phone: 'contact_phone',
@ -139,7 +137,7 @@ export default {
getters: {
invoice(state) {
return Invoice.query()
.with(['client', 'client_fields'])
.with(['client', 'client_fields', 'team_fields'])
.with('rows', query => query.orderBy('order', 'asc'))
.find(state.invoiceId);
},

View File

@ -0,0 +1,16 @@
import { Model } from '@vuex-orm/core';
import { uuidv4 } from '@/utils/helpers';
export default class InvoiceTeamField extends Model {
// This is the name used as module name of the Vuex Store.
static entity = 'invoice_team_fields';
static fields() {
return {
id: this.attr(() => uuidv4()),
invoice_id: this.attr(null),
label: this.attr(''),
value: this.attr(''),
};
}
}

View File

@ -3,6 +3,7 @@ import { uuidv4 } from '@/utils/helpers';
import Client from '@/store/models/client';
import InvoiceRow from '@/store/models/invoice-row';
import InvoiceClientField from '@/store/models/invoice-client-field';
import InvoiceTeamField from '@/store/models/invoice-team-field';
export default class Invoice extends Model {
// This is the name used as module name of the Vuex Store.
@ -24,8 +25,6 @@ export default class Invoice extends Model {
from_city: this.attr(''),
from_country: this.attr(''),
from_county: this.attr(''),
from_reg_no: this.attr(''),
from_vat_no: this.attr(''),
from_website: this.attr(''),
from_email: this.attr(''),
from_phone: this.attr(''),
@ -46,6 +45,7 @@ export default class Invoice extends Model {
created_at: this.attr(''),
total: this.attr(null), // Only used in lists.
client_fields: this.hasMany(InvoiceClientField, 'invoice_id'),
team_fields: this.hasMany(InvoiceTeamField, 'invoice_id'),
};
}

View File

@ -15,12 +15,11 @@ export default class Team extends Model {
company_country: this.attr(''),
company_county: this.attr(''),
company_city: this.attr(''),
company_reg_no: this.attr(''),
company_vat_no: this.attr(''),
website: this.attr(''),
contact_email: this.attr(''),
contact_phone: this.attr(''),
vat_rate: this.attr(null),
currency: this.attr(null),
invoice_late_fee: this.attr(null),
invoice_due_days: this.attr(null),
fields: this.hasMany(TeamField, 'team_id'),

View File

@ -13,12 +13,15 @@ import clientFields from '@/store/client-fields';
import invoices from '@/store/invoices';
import invoiceRows from '@/store/invoice-rows';
import invoiceClientFields from '@/store/invoice-client-fields';
import invoiceTeamFields from '@/store/invoice-team-fields';
import teams from '@/store/teams';
import teamFields from '@/store/team-fields';
import themes from '@/store/themes';
import data from '@/store/data';
import ClientField from '@/store/models/client-field';
import TeamField from '@/store/models/team-field';
import InvoiceClientField from '@/store/models/invoice-client-field';
import InvoiceTeamField from '@/store/models/invoice-team-field';
Vue.use(Vuex);
@ -31,6 +34,7 @@ database.register(Client);
database.register(ClientField);
database.register(Invoice);
database.register(InvoiceClientField);
database.register(InvoiceTeamField);
database.register(InvoiceRow);
database.register(BankAccount);
@ -43,7 +47,9 @@ export default new Vuex.Store({
invoices,
invoiceRows,
invoiceClientFields,
invoiceTeamFields,
teams,
teamFields,
themes,
data,
},

31
src/store/team-fields.js Normal file
View File

@ -0,0 +1,31 @@
import TeamField from '@/store/models/team-field';
export default {
namespaced: true,
state: {},
mutations: {},
actions: {
init() {},
terminate() {},
async teamFieldProps(store, payload) {
return TeamField.update({
where: payload.fieldId,
data: payload.props,
});
},
async updateTeamField({ dispatch }, payload) {
await dispatch('teamFieldProps', payload);
return dispatch('teams/updateTeam', null, { root: true });
},
async addNewField(store, teamId) {
const field = await TeamField.createNew();
field.$update({
team_id: teamId,
});
},
async deleteTeamField({ dispatch }, fieldId) {
await TeamField.delete(fieldId);
return dispatch('teams/updateTeam', null, { root: true });
},
},
};

View File

@ -3,8 +3,14 @@ import Team from '@/store/models/team';
export default {
namespaced: true,
state: {},
mutations: {},
state: {
isModalOpen: false,
},
mutations: {
isModalOpen(state, isOpen) {
state.isModalOpen = isOpen;
},
},
actions: {
async init({ dispatch }) {
await Promise.all([
@ -39,12 +45,7 @@ export default {
},
getters: {
team() {
return Team.query().first();
},
all() {
return Team.query()
.where('$isNew', false)
.get();
return Team.query().with(['fields']).first();
},
},
};

View File

@ -6,63 +6,33 @@
<transition name="fade" mode="out-in">
<router-view/>
</transition>
<footer class="col-12 d-flex justify-content-between align-items-center text-secondary px-0 mt-3 d-print-none">
<button class="btn btn-sm text-secondary" @click="toggleTheme">
Lights {{ theme === 'dark' ? 'on' : 'off' }}
<i class="material-icons material-icons-round md-14 align-text-bottom ml-1">
{{ theme === 'dark' ? 'wb_sunny' : 'brightness_2' }}
</i>
</button>
<div>
<small v-b-tooltip.hover
title="All your data is saved in your browser and not on any server.
This application is truly serverless and only you have access to your data."
class="pointer">
What about my data?
</small>
<small class="pl-2">
Made with
<i class="material-icons material-icons-round md-14 align-text-bottom">favorite</i>
by
<a href="https://mokuapp.io/" class="text-secondary" target="_blank">Moku</a>.
</small>
<a href="https://github.com/mokuappio/serverless-invoices"
class="btn btn-sm btn--icon ml-2"
target="_blank">
<img src="@/assets/img/github.png"
alt="Serverless Invoices Github"
v-if="theme === 'dark'">
<img src="@/assets/img/github-dark.png"
alt="Serverless Invoices Github"
v-else>
</a>
<a href="https://app.mokuapp.io/"
class="btn btn-sm btn-primary ml-2"
target="_blank">Upgrade</a>
</div>
</footer>
<TheFooter/>
</div>
</div>
</div>
<ClientModal v-if="team"/>
<TeamModal v-if="team"/>
<BankAccountModal v-if="team"/>
<ImportModal/>
</div>
</template>
<script>
import { mapGetters, mapState } from 'vuex';
import { mapGetters } from 'vuex';
import ClientModal from '@/components/clients/ClientModal';
import BankAccountModal from '@/components/bank-accounts/BankAccountModal';
import { VBTooltip } from 'bootstrap-vue';
import TeamModal from '@/components/team/TeamModal';
import TheFooter from '@/components/TheFooter';
import ImportModal from '../../components/ImportModal';
export default {
directives: {
'b-tooltip': VBTooltip,
},
components: {
TheFooter,
TeamModal,
ImportModal,
BankAccountModal,
ClientModal,
@ -71,20 +41,6 @@ export default {
...mapGetters({
team: 'teams/team',
}),
...mapState({
theme: state => state.themes.theme,
}),
},
methods: {
toggleTheme() {
if (this.theme === 'light') {
this.$store.commit('themes/theme', 'dark');
} else {
this.$store.commit('themes/theme', 'light');
}
localStorage.setItem('theme', this.theme);
document.documentElement.setAttribute('data-theme', this.theme);
},
},
};
</script>