mirror of
https://github.com/mokuappio/serverless-invoices.git
synced 2025-10-28 00:11:08 -04:00
CustomizationsModal.vue - add a way for users to customize css of their invoices and store it per team,
fix print styles for invoices, add keys to translations with default English values
This commit is contained in:
5
public/locales/bn/customizations-modal.json
Normal file
5
public/locales/bn/customizations-modal.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "Customize your invoices",
|
||||||
|
"textarea_label": "Insert your custom CSS",
|
||||||
|
"updated": "Updated"
|
||||||
|
}
|
||||||
@ -1,6 +1,10 @@
|
|||||||
{
|
{
|
||||||
"back": "ব্যাক",
|
"back": "ব্যাক",
|
||||||
"book": "বুক",
|
"book": "বুক",
|
||||||
|
"design_and_layout": "Design & layout",
|
||||||
|
"compact": "Compact",
|
||||||
|
"comfortable": "Comfortable",
|
||||||
|
"customize": "Customize",
|
||||||
"download_pdf": "ডাউনলোড পিডিএফ",
|
"download_pdf": "ডাউনলোড পিডিএফ",
|
||||||
"delete": "ডিলিট",
|
"delete": "ডিলিট",
|
||||||
"delete_modal": {
|
"delete_modal": {
|
||||||
|
|||||||
5
public/locales/en/customizations-modal.json
Normal file
5
public/locales/en/customizations-modal.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "Customize your invoices",
|
||||||
|
"textarea_label": "Insert your custom CSS",
|
||||||
|
"updated": "Updated"
|
||||||
|
}
|
||||||
@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"book": "Book",
|
"book": "Book",
|
||||||
"density": "Density",
|
"design_and_layout": "Design & layout",
|
||||||
"compact": "Compact",
|
"compact": "Compact",
|
||||||
"comfortable": "Comfortable",
|
"comfortable": "Comfortable",
|
||||||
|
"customize": "Customize",
|
||||||
"download_pdf": "Download PDF",
|
"download_pdf": "Download PDF",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"delete_modal": {
|
"delete_modal": {
|
||||||
|
|||||||
5
public/locales/es/customizations-modal.json
Normal file
5
public/locales/es/customizations-modal.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "Customize your invoices",
|
||||||
|
"textarea_label": "Insert your custom CSS",
|
||||||
|
"updated": "Updated"
|
||||||
|
}
|
||||||
@ -1,6 +1,10 @@
|
|||||||
{
|
{
|
||||||
"back": "Atrás",
|
"back": "Atrás",
|
||||||
"book": "Libro",
|
"book": "Libro",
|
||||||
|
"design_and_layout": "Design & layout",
|
||||||
|
"compact": "Compact",
|
||||||
|
"comfortable": "Comfortable",
|
||||||
|
"customize": "Customize",
|
||||||
"download_pdf": "Descargar PDF",
|
"download_pdf": "Descargar PDF",
|
||||||
"delete": "Eliminar",
|
"delete": "Eliminar",
|
||||||
"delete_modal": {
|
"delete_modal": {
|
||||||
|
|||||||
5
public/locales/et/customizations-modal.json
Normal file
5
public/locales/et/customizations-modal.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "Customize your invoices",
|
||||||
|
"textarea_label": "Insert your custom CSS",
|
||||||
|
"updated": "Updated"
|
||||||
|
}
|
||||||
@ -1,6 +1,10 @@
|
|||||||
{
|
{
|
||||||
"back": "Tagasi",
|
"back": "Tagasi",
|
||||||
"book": "Kinnita",
|
"book": "Kinnita",
|
||||||
|
"design_and_layout": "Design & layout",
|
||||||
|
"compact": "Compact",
|
||||||
|
"comfortable": "Comfortable",
|
||||||
|
"customize": "Customize",
|
||||||
"download_pdf": "Lae alla PDF",
|
"download_pdf": "Lae alla PDF",
|
||||||
"delete": "Kustuta",
|
"delete": "Kustuta",
|
||||||
"delete_modal": {
|
"delete_modal": {
|
||||||
|
|||||||
5
public/locales/fa/customizations-modal.json
Normal file
5
public/locales/fa/customizations-modal.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "Customize your invoices",
|
||||||
|
"textarea_label": "Insert your custom CSS",
|
||||||
|
"updated": "Updated"
|
||||||
|
}
|
||||||
@ -1,6 +1,10 @@
|
|||||||
{
|
{
|
||||||
"back": "بازگشت",
|
"back": "بازگشت",
|
||||||
"book": "کتاب",
|
"book": "کتاب",
|
||||||
|
"design_and_layout": "Design & layout",
|
||||||
|
"compact": "Compact",
|
||||||
|
"comfortable": "Comfortable",
|
||||||
|
"customize": "Customize",
|
||||||
"download_pdf": "بارگیری PDF",
|
"download_pdf": "بارگیری PDF",
|
||||||
"delete": "حذف",
|
"delete": "حذف",
|
||||||
"delete_modal": {
|
"delete_modal": {
|
||||||
|
|||||||
@ -10,7 +10,6 @@
|
|||||||
box-shadow: $box-shadow-light-1;
|
box-shadow: $box-shadow-light-1;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__row {
|
&__row {
|
||||||
@ -36,18 +35,26 @@
|
|||||||
@page {
|
@page {
|
||||||
size: A4;
|
size: A4;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
//-webkit-print-color-adjust: exact;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
html, body {
|
html, body {
|
||||||
width: 210mm;
|
width: 210mm;
|
||||||
height: 297mm;
|
height: 297mm;
|
||||||
color: $dark;
|
//color: $dark;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
}
|
}
|
||||||
.table {
|
.table {
|
||||||
color: $dark;
|
//color: $dark;
|
||||||
|
td, th {
|
||||||
|
background-color: inherit !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.invoice-box {
|
.invoice-box {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
85
src/components/invoices/CustomizationsModal.vue
Normal file
85
src/components/invoices/CustomizationsModal.vue
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<BModal v-model="isOpen"
|
||||||
|
centered
|
||||||
|
hide-footer
|
||||||
|
:title="$t('title')"
|
||||||
|
size="lg"
|
||||||
|
scrollable
|
||||||
|
content-class="bg-base dp--24 text-center">
|
||||||
|
<AppTextarea :value="team.custom_css"
|
||||||
|
@change="updateProp({ custom_css: $event })"
|
||||||
|
:label="$t('textarea_label')"
|
||||||
|
field="custom_css"
|
||||||
|
:errors="errors"
|
||||||
|
input-classes="min-vh-50 text-monospace"
|
||||||
|
class="text-left"/>
|
||||||
|
</BModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { BModal } from 'bootstrap-vue';
|
||||||
|
import AppTextarea from '@/components/form/AppTextarea';
|
||||||
|
import Errors from '@/utils/errors';
|
||||||
|
import NotificationService from '@/services/notification.service';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
i18nOptions: {
|
||||||
|
namespaces: 'customizations-modal',
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
errors: new Errors(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
BModal,
|
||||||
|
AppTextarea,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isOpen: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.invoices.isCustomizationsModalOpen;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$store.commit('invoices/isCustomizationsModalOpen', val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...mapGetters({
|
||||||
|
team: 'teams/team',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
team() {
|
||||||
|
this.updateStyleEl(this.team.custom_css);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.initStyleEl();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initStyleEl() {
|
||||||
|
const styleEl = document.createElement('style');
|
||||||
|
styleEl.setAttribute('id', 'custom-styles');
|
||||||
|
styleEl.setAttribute('type', 'text/css');
|
||||||
|
document.head.appendChild(styleEl);
|
||||||
|
},
|
||||||
|
updateStyleEl(styles) {
|
||||||
|
const styleEl = document.getElementById('custom-styles');
|
||||||
|
styleEl.innerHTML = styles;
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.isOpen = false;
|
||||||
|
},
|
||||||
|
updateProp(props) {
|
||||||
|
this.errors.clear();
|
||||||
|
this.$store.dispatch('teams/updateTeam', props)
|
||||||
|
.then(() => {
|
||||||
|
NotificationService.success(this.$t('updated'));
|
||||||
|
this.updateStyleEl(props.custom_css);
|
||||||
|
})
|
||||||
|
.catch(err => this.errors.set(err.errors));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -20,10 +20,13 @@
|
|||||||
<template slot="button-content">
|
<template slot="button-content">
|
||||||
<i class="material-icons">more_vert</i>
|
<i class="material-icons">more_vert</i>
|
||||||
</template>
|
</template>
|
||||||
<b-dropdown-group :header="$t('density')">
|
<b-dropdown-group :header="$t('design_and_layout')">
|
||||||
<b-dropdown-item-button @click="toggleCompact">
|
<b-dropdown-item-button @click="toggleCompact">
|
||||||
{{ invoice.is_compact ? $t('comfortable') : $t('compact') }}
|
{{ invoice.is_compact ? $t('comfortable') : $t('compact') }}
|
||||||
</b-dropdown-item-button>
|
</b-dropdown-item-button>
|
||||||
|
<b-dropdown-item-button @click="openCustomizationsModal">
|
||||||
|
{{ $t('customize') }}
|
||||||
|
</b-dropdown-item-button>
|
||||||
</b-dropdown-group>
|
</b-dropdown-group>
|
||||||
<b-dropdown-divider/>
|
<b-dropdown-divider/>
|
||||||
<b-dropdown-item-button @click="print">{{ $t('download_pdf') }}</b-dropdown-item-button>
|
<b-dropdown-item-button @click="print">{{ $t('download_pdf') }}</b-dropdown-item-button>
|
||||||
@ -37,11 +40,18 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import NotificationService from '@/services/notification.service';
|
import NotificationService from '@/services/notification.service';
|
||||||
import { BDropdown, BDropdownDivider, BDropdownGroup, BDropdownItemButton } from 'bootstrap-vue';
|
import {
|
||||||
|
BDropdown,
|
||||||
|
BDropdownDivider,
|
||||||
|
BDropdownGroup,
|
||||||
|
BDropdownItemButton,
|
||||||
|
} from 'bootstrap-vue';
|
||||||
import AppSelect from '@/components/form/AppSelect';
|
import AppSelect from '@/components/form/AppSelect';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
i18nOptions: { namespaces: ['invoice-controls', 'statuses'] },
|
i18nOptions: {
|
||||||
|
namespaces: ['invoice-controls', 'statuses'],
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
BDropdown,
|
BDropdown,
|
||||||
BDropdownDivider,
|
BDropdownDivider,
|
||||||
@ -106,6 +116,9 @@ export default {
|
|||||||
toggleCompact() {
|
toggleCompact() {
|
||||||
this.updateProp({ is_compact: !this.invoice.is_compact });
|
this.updateProp({ is_compact: !this.invoice.is_compact });
|
||||||
},
|
},
|
||||||
|
openCustomizationsModal() {
|
||||||
|
this.$store.commit('invoices/isCustomizationsModalOpen', true);
|
||||||
|
},
|
||||||
print() {
|
print() {
|
||||||
window.print();
|
window.print();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -16,14 +16,14 @@ export default {
|
|||||||
state: {
|
state: {
|
||||||
errors: new Errors(),
|
errors: new Errors(),
|
||||||
invoiceId: null,
|
invoiceId: null,
|
||||||
isSendModalOpen: false,
|
isCustomizationsModalOpen: false,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
invoiceId(state, invoiceId) {
|
invoiceId(state, invoiceId) {
|
||||||
state.invoiceId = invoiceId;
|
state.invoiceId = invoiceId;
|
||||||
},
|
},
|
||||||
isSendModalOpen(state, isSendModalOpen) {
|
isCustomizationsModalOpen(state, isCustomizationsModalOpen) {
|
||||||
state.isSendModalOpen = isSendModalOpen;
|
state.isCustomizationsModalOpen = isCustomizationsModalOpen;
|
||||||
},
|
},
|
||||||
setErrors(state, errors) {
|
setErrors(state, errors) {
|
||||||
state.errors.set(errors);
|
state.errors.set(errors);
|
||||||
|
|||||||
@ -2,6 +2,18 @@ import { Model } from '@vuex-orm/core';
|
|||||||
import { uuidv4 } from '@/utils/helpers';
|
import { uuidv4 } from '@/utils/helpers';
|
||||||
import TeamField from '@/store/models/team-field';
|
import TeamField from '@/store/models/team-field';
|
||||||
|
|
||||||
|
const customCSS = `
|
||||||
|
/* @import url('https://fonts.googleapis.com/css2?family=Work+Sans:wght@300&display=swap');
|
||||||
|
|
||||||
|
.invoice-box:after {
|
||||||
|
background: linear-gradient(to bottom, #1C7CE0, #150051);
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-box {
|
||||||
|
font-family: 'Work Sans', sans-serif;
|
||||||
|
} */
|
||||||
|
`;
|
||||||
|
|
||||||
export default class Team extends Model {
|
export default class Team extends Model {
|
||||||
// This is the name used as module name of the Vuex Store.
|
// This is the name used as module name of the Vuex Store.
|
||||||
static entity = 'teams';
|
static entity = 'teams';
|
||||||
@ -25,6 +37,7 @@ export default class Team extends Model {
|
|||||||
updated_at: this.attr(''),
|
updated_at: this.attr(''),
|
||||||
created_at: this.attr(''),
|
created_at: this.attr(''),
|
||||||
logo_url: this.attr(''),
|
logo_url: this.attr(''),
|
||||||
|
custom_css: this.attr(customCSS),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<CustomizationsModal/>
|
||||||
<ClientModal v-if="team"/>
|
<ClientModal v-if="team"/>
|
||||||
<TeamModal v-if="team"/>
|
<TeamModal v-if="team"/>
|
||||||
<BankAccountModal v-if="team"/>
|
<BankAccountModal v-if="team"/>
|
||||||
@ -24,6 +25,7 @@ import BankAccountModal from '@/components/bank-accounts/BankAccountModal';
|
|||||||
import { VBTooltip } from 'bootstrap-vue';
|
import { VBTooltip } from 'bootstrap-vue';
|
||||||
import TeamModal from '@/components/team/TeamModal';
|
import TeamModal from '@/components/team/TeamModal';
|
||||||
import TheFooter from '@/components/TheFooter';
|
import TheFooter from '@/components/TheFooter';
|
||||||
|
import CustomizationsModal from '@/components/invoices/CustomizationsModal';
|
||||||
import ImportModal from '../../components/ImportModal';
|
import ImportModal from '../../components/ImportModal';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -36,6 +38,7 @@ export default {
|
|||||||
ImportModal,
|
ImportModal,
|
||||||
BankAccountModal,
|
BankAccountModal,
|
||||||
ClientModal,
|
ClientModal,
|
||||||
|
CustomizationsModal,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
|
|||||||
Reference in New Issue
Block a user