mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-10 23:14:48 +00:00
Complete dashboard translations & small UI improvements (#69)
* fix dropdown action Estimate Dashboard and fix translating full Dasboard page * Update app.php * fix locale in app.php config * Wizard install with translation, customer portal with translation, and fixing hardcoding strings to get translation * fixes asked to review * fixes pint --------- Co-authored-by: Max <contact@agencetwogether.fr> Co-authored-by: Darko Gjorgjijoski <5760249+gdarko@users.noreply.github.com>
This commit is contained in:
@@ -26,7 +26,7 @@
|
||||
{{ token }}
|
||||
</span>
|
||||
<svg
|
||||
v-tooltip="{ content: 'Copy to Clipboard' }"
|
||||
v-tooltip="{ content: $t('general.copy_to_clipboard') }"
|
||||
class="
|
||||
absolute
|
||||
right-0
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
v$.confirm_password.$errors[0].$message
|
||||
"
|
||||
:content-loading="isFetchingInitialData"
|
||||
label="Confirm Password"
|
||||
:label="$t('customers.confirm_password')"
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="customerStore.currentCustomer.confirm_password"
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<BaseMultiselect
|
||||
v-model="noteStore.currentNote.type"
|
||||
:options="types"
|
||||
value-prop="type"
|
||||
value-prop="value"
|
||||
class="mt-2"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
@@ -122,7 +122,11 @@ const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
|
||||
let isSaving = ref(false)
|
||||
const types = reactive(['Invoice', 'Estimate', 'Payment'])
|
||||
const types = reactive([
|
||||
{label: t('settings.customization.notes.types.invoice'), value: 'Invoice'},
|
||||
{label: t('settings.customization.notes.types.estimate'), value: 'Estimate'},
|
||||
{label: t('settings.customization.notes.types.payment'), value: 'Payment'}
|
||||
])
|
||||
let fields = ref(['customer', 'customerCustom'])
|
||||
|
||||
const modalActive = computed(() => {
|
||||
@@ -164,7 +168,7 @@ watch(
|
||||
onMounted(() => {
|
||||
if (route.name === 'estimates.create') {
|
||||
noteStore.currentNote.type = 'Estimate'
|
||||
} else if (route.name === 'invoices.create') {
|
||||
} else if (route.name === 'invoices.create' || route.name === 'recurring-invoices.create') {
|
||||
noteStore.currentNote.type = 'Invoice'
|
||||
} else {
|
||||
noteStore.currentNote.type = 'Payment'
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
@click="cancelPreview"
|
||||
>
|
||||
<BaseIcon name="PencilIcon" class="h-5 mr-2" />
|
||||
Edit
|
||||
{{ $t('general.edit') }}
|
||||
</BaseButton>
|
||||
<iframe
|
||||
:src="templateUrl"
|
||||
@@ -166,7 +166,7 @@ let estimateMailForm = reactive({
|
||||
id: null,
|
||||
from: null,
|
||||
to: null,
|
||||
subject: 'New Estimate',
|
||||
subject: t('estimates.new_estimate'),
|
||||
body: null,
|
||||
})
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
@click="cancelPreview"
|
||||
>
|
||||
<BaseIcon name="PencilIcon" class="h-5 mr-2" />
|
||||
Edit
|
||||
{{ $t('general.edit') }}
|
||||
</BaseButton>
|
||||
|
||||
<iframe
|
||||
@@ -181,7 +181,7 @@ const invoiceMailForm = reactive({
|
||||
id: null,
|
||||
from: null,
|
||||
to: null,
|
||||
subject: 'New Invoice',
|
||||
subject: t('invoices.new_invoice'),
|
||||
body: null,
|
||||
})
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
@click="cancelPreview"
|
||||
>
|
||||
<BaseIcon name="PencilIcon" class="h-5 mr-2" />
|
||||
Edit
|
||||
{{ $t('general.edit') }}
|
||||
</BaseButton>
|
||||
|
||||
<iframe
|
||||
@@ -181,7 +181,7 @@ const paymentMailForm = reactive({
|
||||
id: null,
|
||||
from: null,
|
||||
to: null,
|
||||
subject: 'New Payment',
|
||||
subject: t('payments.new_payment'),
|
||||
body: null,
|
||||
})
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
<BaseMultiselect
|
||||
v-model="customFieldStore.currentCustomField.model_type"
|
||||
:options="modelTypes"
|
||||
value-prop="value"
|
||||
:can-deselect="false"
|
||||
:invalid="v$.currentCustomField.model_type.$error"
|
||||
:searchable="true"
|
||||
@@ -229,11 +230,11 @@ const { t } = useI18n()
|
||||
let isSaving = ref(false)
|
||||
|
||||
const modelTypes = reactive([
|
||||
'Customer',
|
||||
'Invoice',
|
||||
'Estimate',
|
||||
'Expense',
|
||||
'Payment',
|
||||
{label: t('settings.custom_fields.model_type.customer'), value: 'Customer'},
|
||||
{label: t('settings.custom_fields.model_type.invoice'), value: 'Invoice'},
|
||||
{label: t('settings.custom_fields.model_type.estimate'), value: 'Estimate'},
|
||||
{label: t('settings.custom_fields.model_type.expense'), value: 'Expense'},
|
||||
{label: t('settings.custom_fields.model_type.payment'), value: 'Payment'}
|
||||
])
|
||||
|
||||
const dataTypes = reactive([
|
||||
@@ -286,9 +287,6 @@ const isRequiredField = computed({
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
currentCustomField: {
|
||||
type: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
|
||||
@@ -20,10 +20,25 @@ export const useInstallationStore = (useWindow = false) => {
|
||||
database_username: null,
|
||||
database_password: null,
|
||||
app_url: window.location.origin,
|
||||
app_locale: null
|
||||
},
|
||||
}),
|
||||
|
||||
actions: {
|
||||
fetchInstallationLanguages() {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(`/api/v1/installation/languages`)
|
||||
.then((response) => {
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
fetchInstallationRequirements() {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
@@ -66,6 +81,20 @@ export const useInstallationStore = (useWindow = false) => {
|
||||
})
|
||||
},
|
||||
|
||||
addInstallationLanguage(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(`/api/v1/installation/wizard-language`, data)
|
||||
.then((response) => {
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
fetchInstallationPermissions() {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
|
||||
@@ -36,17 +36,17 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
|
||||
},
|
||||
|
||||
frequencies: [
|
||||
{ label: 'Every Minute', value: '* * * * *' },
|
||||
{ label: 'Every 30 Minute', value: '*/30 * * * *' },
|
||||
{ label: 'Every Hour', value: '0 * * * *' },
|
||||
{ label: 'Every 2 Hour', value: '0 */2 * * *' },
|
||||
{ label: 'Every day at midnight ', value: '0 0 * * *' },
|
||||
{ label: 'Every Week', value: '0 0 * * 0' },
|
||||
{ label: 'Every 15 days at midnight', value: '0 5 */15 * *' },
|
||||
{ label: 'On the first day of every month at 00:00', value: '0 0 1 * *' },
|
||||
{ label: 'Every 6 Month', value: '0 0 1 */6 *' },
|
||||
{ label: 'Every year on the first day of january at 00:00', value: '0 0 1 1 *' },
|
||||
{ label: 'Custom', value: 'CUSTOM' },
|
||||
{ label: global.t('recurring_invoices.frequency.every_minute'), value: '* * * * *' },
|
||||
{ label: global.t('recurring_invoices.frequency.every_30_minute'), value: '*/30 * * * *' },
|
||||
{ label: global.t('recurring_invoices.frequency.every_hour'), value: '0 * * * *' },
|
||||
{ label: global.t('recurring_invoices.frequency.every_2_hour'), value: '0 */2 * * *' },
|
||||
{ label: global.t('recurring_invoices.frequency.every_day_at_midnight'), value: '0 0 * * *' },
|
||||
{ label: global.t('recurring_invoices.frequency.every_week'), value: '0 0 * * 0' },
|
||||
{ label: global.t('recurring_invoices.frequency.every_15_days_at_midnight'), value: '0 5 */15 * *' },
|
||||
{ label: global.t('recurring_invoices.frequency.on_the_first_day_of_every_month_at_midnight'), value: '0 0 1 * *' },
|
||||
{ label: global.t('recurring_invoices.frequency.every_6_month'), value: '0 0 1 */6 *' },
|
||||
{ label: global.t('recurring_invoices.frequency.every_year_on_the_first_day_of_january_at_midnight'), value: '0 0 1 1 *' },
|
||||
{ label: global.t('recurring_invoices.frequency.custom'), value: 'CUSTOM' },
|
||||
],
|
||||
}),
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import Guid from 'guid'
|
||||
import recurringInvoiceItemStub from './recurring-invoice-item'
|
||||
import taxStub from './tax'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export default function () {
|
||||
const { t } = useI18n()
|
||||
return {
|
||||
currency: null,
|
||||
customer: null,
|
||||
@@ -42,7 +44,7 @@ export default function () {
|
||||
fields: [],
|
||||
invoices: [],
|
||||
selectedNote: null,
|
||||
selectedFrequency: { label: 'Every Week', value: '0 0 * * 0' },
|
||||
selectedFrequency: { label: t('recurring_invoices.frequency.every_week'), value: '0 0 * * 0' },
|
||||
selectedInvoice: null,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@
|
||||
v$.currentCustomer.confirm_password.$errors[0].$message
|
||||
"
|
||||
:content-loading="isFetchingInitialData"
|
||||
label="Confirm Password"
|
||||
:label="$t('customers.confirm_password')"
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="customerStore.currentCustomer.confirm_password"
|
||||
|
||||
@@ -128,17 +128,19 @@ import { useCustomerStore } from '@/scripts/admin/stores/customer'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useCompanyStore } from '@/scripts/admin/stores/company'
|
||||
import ChartPlaceholder from './CustomerChartPlaceholder.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const companyStore = useCompanyStore()
|
||||
const customerStore = useCustomerStore()
|
||||
const utils = inject('utils')
|
||||
const { t } = useI18n()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
let isLoading = ref(false)
|
||||
let chartData = reactive({})
|
||||
let data = reactive({})
|
||||
let years = reactive(['This year', 'Previous year'])
|
||||
let years = reactive([{label: t('dateRange.this_year'), value: 'This year'}, {label: t('dateRange.previous_year'), value: 'Previous year'}])
|
||||
let selectedYear = ref('This year')
|
||||
|
||||
const getChartExpenses = computed(() => {
|
||||
|
||||
@@ -7,17 +7,20 @@
|
||||
|
||||
<BaseDescriptionList>
|
||||
<BaseDescriptionListItem
|
||||
v-if="selectedViewCustomer.name"
|
||||
:content-loading="contentLoading"
|
||||
:label="$t('customers.display_name')"
|
||||
:value="selectedViewCustomer?.name"
|
||||
/>
|
||||
|
||||
<BaseDescriptionListItem
|
||||
v-if="selectedViewCustomer.contact_name"
|
||||
:content-loading="contentLoading"
|
||||
:label="$t('customers.primary_contact_name')"
|
||||
:value="selectedViewCustomer?.contact_name"
|
||||
/>
|
||||
<BaseDescriptionListItem
|
||||
v-if="selectedViewCustomer.email"
|
||||
:content-loading="contentLoading"
|
||||
:label="$t('customers.email')"
|
||||
:value="selectedViewCustomer?.email"
|
||||
@@ -36,11 +39,13 @@
|
||||
/>
|
||||
|
||||
<BaseDescriptionListItem
|
||||
v-if="selectedViewCustomer.phone"
|
||||
:content-loading="contentLoading"
|
||||
:label="$t('customers.phone_number')"
|
||||
:value="selectedViewCustomer?.phone"
|
||||
/>
|
||||
<BaseDescriptionListItem
|
||||
v-if="selectedViewCustomer.website"
|
||||
:content-loading="contentLoading"
|
||||
:label="$t('customers.website')"
|
||||
:value="selectedViewCustomer?.website"
|
||||
@@ -89,8 +94,8 @@
|
||||
v-if="field.type === 'Switch'"
|
||||
class="text-sm font-bold leading-5 text-black non-italic"
|
||||
>
|
||||
<span v-if="field.default_answer === 1"> Yes </span>
|
||||
<span v-else> No </span>
|
||||
<span v-if="field.default_answer === 1"> {{ $t('general.yes') }} </span>
|
||||
<span v-else> {{ $t('general.no') }} </span>
|
||||
</p>
|
||||
<p v-else class="text-sm font-bold leading-5 text-black non-italic">
|
||||
{{ field.default_answer }}
|
||||
|
||||
@@ -156,13 +156,16 @@ import LineChart from '@/scripts/admin/components/charts/LineChart.vue'
|
||||
import ChartPlaceholder from './DashboardChartPlaceholder.vue'
|
||||
import abilities from '@/scripts/admin/stub/abilities'
|
||||
import { useUserStore } from '@/scripts/admin/stores/user'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const dashboardStore = useDashboardStore()
|
||||
const companyStore = useCompanyStore()
|
||||
|
||||
const { t } = useI18n()
|
||||
const utils = inject('utils')
|
||||
const userStore = useUserStore()
|
||||
const years = ref(['This year', 'Previous year'])
|
||||
const years = ref( [{label: t('dateRange.this_year'), value: 'This year'}, {label: t( 'dateRange.previous_year'), value:
|
||||
'Previous year'}])
|
||||
const selectedYear = ref('This year')
|
||||
|
||||
watch(
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
:icon-component="CustomerIcon"
|
||||
:loading="!dashboardStore.isDashboardDataLoaded"
|
||||
route="/admin/customers"
|
||||
:label="$t('dashboard.cards.customers')"
|
||||
:label="(dashboardStore.stats.totalCustomerCount <= 1 ? $t('dashboard.cards.customers', 1) : $t('dashboard.cards.customers', 2))"
|
||||
>
|
||||
{{ dashboardStore.stats.totalCustomerCount }}
|
||||
</DashboardStatsItem>
|
||||
@@ -32,7 +32,7 @@
|
||||
:icon-component="InvoiceIcon"
|
||||
:loading="!dashboardStore.isDashboardDataLoaded"
|
||||
route="/admin/invoices"
|
||||
:label="$t('dashboard.cards.invoices')"
|
||||
:label="(dashboardStore.stats.totalInvoiceCount <= 1 ? $t('dashboard.cards.invoices', 1) : $t('dashboard.cards.invoices', 2))"
|
||||
>
|
||||
{{ dashboardStore.stats.totalInvoiceCount }}
|
||||
</DashboardStatsItem>
|
||||
@@ -43,7 +43,7 @@
|
||||
:icon-component="EstimateIcon"
|
||||
:loading="!dashboardStore.isDashboardDataLoaded"
|
||||
route="/admin/estimates"
|
||||
:label="$t('dashboard.cards.estimates')"
|
||||
:label="(dashboardStore.stats.totalEstimateCount <= 1 ? $t( 'dashboard.cards.estimates', 1) : $t('dashboard.cards.estimates', 2))"
|
||||
>
|
||||
{{ dashboardStore.stats.totalEstimateCount }}
|
||||
</DashboardStatsItem>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
v-if="hasAtleastOneEstimateAbility()"
|
||||
#cell-actions="{ row }"
|
||||
>
|
||||
<EstimateDropdown :row="row" :table="estimateTableComponent" />
|
||||
<EstimateDropdown :row="row.data" :table="estimateTableComponent" />
|
||||
</template>
|
||||
</BaseTable>
|
||||
</div>
|
||||
|
||||
@@ -216,7 +216,7 @@
|
||||
|
||||
<template #cell-status="{ row }">
|
||||
<BaseEstimateStatusBadge :status="row.data.status" class="px-3 py-1">
|
||||
{{ row.data.status }}
|
||||
<BaseEstimateStatusLabel :status="row.data.status"/>
|
||||
</BaseEstimateStatusBadge>
|
||||
</template>
|
||||
|
||||
@@ -249,6 +249,7 @@ import abilities from '@/scripts/admin/stub/abilities'
|
||||
import ObservatoryIcon from '@/scripts/components/icons/empty/ObservatoryIcon.vue'
|
||||
import EstimateDropDown from '@/scripts/admin/components/dropdowns/EstimateIndexDropdown.vue'
|
||||
import SendEstimateModal from '@/scripts/admin/components/modal-components/SendEstimateModal.vue'
|
||||
import BaseEstimateStatusLabel from "@/scripts/components/base/BaseEstimateStatusLabel.vue";
|
||||
|
||||
const estimateStore = useEstimateStore()
|
||||
const dialogStore = useDialogStore()
|
||||
@@ -258,12 +259,12 @@ const tableComponent = ref(null)
|
||||
const { t } = useI18n()
|
||||
const showFilters = ref(false)
|
||||
const status = ref([
|
||||
'DRAFT',
|
||||
'SENT',
|
||||
'VIEWED',
|
||||
'EXPIRED',
|
||||
'ACCEPTED',
|
||||
'REJECTED',
|
||||
{label: t('estimates.draft'), value: 'DRAFT'},
|
||||
{label: t('estimates.sent'), value: 'SENT'},
|
||||
{label: t('estimates.viewed'), value: 'VIEWED'},
|
||||
{label: t('estimates.expired'), value: 'EXPIRED'},
|
||||
{label: t('estimates.accepted'), value: 'ACCEPTED'},
|
||||
{label: t('estimates.rejected'), value: 'REJECTED'},
|
||||
])
|
||||
|
||||
const isRequestOngoing = ref(true)
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
:status="estimate.status"
|
||||
class="px-1 text-xs"
|
||||
>
|
||||
{{ estimate.status }}
|
||||
<BaseEstimateStatusLabel :status="estimate.status" />
|
||||
</BaseEstimateStatusBadge>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -278,19 +278,19 @@ const expenseColumns = computed(() => {
|
||||
},
|
||||
{
|
||||
key: 'expense_date',
|
||||
label: 'Date',
|
||||
label: t('expenses.date'),
|
||||
thClass: 'extra',
|
||||
tdClass: 'font-medium text-gray-900',
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
label: 'Category',
|
||||
label: t('expenses.category'),
|
||||
thClass: 'extra',
|
||||
tdClass: 'cursor-pointer font-medium text-primary-500',
|
||||
},
|
||||
{ key: 'user_name', label: 'Customer' },
|
||||
{ key: 'notes', label: 'Note' },
|
||||
{ key: 'amount', label: 'Amount' },
|
||||
{ key: 'user_name', label: t('expenses.customer') },
|
||||
{ key: 'notes', label: t('expenses.note') },
|
||||
{ key: 'amount', label: t('expenses.amount') },
|
||||
{
|
||||
key: 'actions',
|
||||
sortable: false,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
/>
|
||||
|
||||
<BaseWizard
|
||||
:steps="7"
|
||||
:steps="8"
|
||||
:current-step="currentStepNumber"
|
||||
@click="onNavClick"
|
||||
>
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import Step0SetLanguage from './Step0SetLanguage.vue'
|
||||
import Step1RequirementsCheck from './Step1RequirementsCheck.vue'
|
||||
import Step2PermissionCheck from './Step2PermissionCheck.vue'
|
||||
import Step3DatabaseConfig from './Step3DatabaseConfig.vue'
|
||||
@@ -34,6 +35,7 @@ import { useRouter } from 'vue-router'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
step_0: Step0SetLanguage,
|
||||
step_1: Step1RequirementsCheck,
|
||||
step_2: Step2PermissionCheck,
|
||||
step_3: Step3DatabaseConfig,
|
||||
@@ -45,11 +47,12 @@ export default {
|
||||
},
|
||||
|
||||
setup() {
|
||||
let stepComponent = ref('step_1')
|
||||
let stepComponent = ref('step_0')
|
||||
let currentStepNumber = ref(1)
|
||||
|
||||
const router = useRouter()
|
||||
const installationStore = useInstallationStore()
|
||||
const { global } = window.i18n
|
||||
|
||||
checkCurrentProgress()
|
||||
|
||||
@@ -61,6 +64,10 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
if(typeof res.data.profile_language === 'string') {
|
||||
global.locale.value = res.data.profile_language
|
||||
}
|
||||
|
||||
let dbstep = parseInt(res.data.profile_complete)
|
||||
|
||||
if (dbstep) {
|
||||
@@ -93,7 +100,7 @@ export default {
|
||||
|
||||
currentStepNumber.value++
|
||||
|
||||
if (currentStepNumber.value <= 8) {
|
||||
if (currentStepNumber.value <= 9) {
|
||||
stepComponent.value = 'step_' + currentStepNumber.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<BaseWizardStep
|
||||
:title="$t('wizard.install_language.title')"
|
||||
:description="$t('wizard.install_language.description')"
|
||||
>
|
||||
<div class="w-full md:w-2/3">
|
||||
<div class="mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.language')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="currentLanguage"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="languages"
|
||||
label="name"
|
||||
value-prop="code"
|
||||
:placeholder="$t('settings.preferences.select_language')"
|
||||
class="w-full"
|
||||
track-by="name"
|
||||
:searchable="true"
|
||||
@change="changeLanguage"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
v-show="!isFetchingInitialData"
|
||||
@click="next"
|
||||
>
|
||||
{{ $t('wizard.continue') }}
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="ArrowRightIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
</BaseButton>
|
||||
|
||||
</div>
|
||||
</BaseWizardStep>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useInstallationStore } from '@/scripts/admin/stores/installation.js'
|
||||
|
||||
const { global } = window.i18n
|
||||
|
||||
const emit = defineEmits(['next'])
|
||||
|
||||
let isFetchingInitialData = ref(false)
|
||||
let isSaving = ref(false)
|
||||
let languages = ref([])
|
||||
let currentLanguage = 'en'
|
||||
|
||||
const installationStore = useInstallationStore()
|
||||
|
||||
onMounted(() => {
|
||||
getLanguages()
|
||||
})
|
||||
|
||||
async function getLanguages() {
|
||||
isFetchingInitialData.value = true
|
||||
|
||||
const res = await installationStore.fetchInstallationLanguages()
|
||||
|
||||
languages.value = res.data.languages
|
||||
|
||||
isFetchingInitialData.value = false
|
||||
}
|
||||
|
||||
|
||||
function next() {
|
||||
isSaving.value = true
|
||||
emit('next')
|
||||
isSaving.value = false
|
||||
}
|
||||
|
||||
function changeLanguage(event){
|
||||
if(typeof global.locale !== 'string') {
|
||||
global.locale.value = event
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -36,11 +36,13 @@ export default {
|
||||
const database_connection = ref('mysql')
|
||||
const isSaving = ref(false)
|
||||
const { t } = useI18n()
|
||||
const { global } = window.i18n
|
||||
|
||||
const notificationStore = useNotificationStore()
|
||||
const installationStore = useInstallationStore()
|
||||
|
||||
const databaseData = computed(() => {
|
||||
installationStore.currentDataBaseData.app_locale = global.locale.value
|
||||
return installationStore.currentDataBaseData
|
||||
})
|
||||
|
||||
@@ -75,6 +77,12 @@ export default {
|
||||
|
||||
emit('next', 3)
|
||||
|
||||
let language = {
|
||||
profile_language: global.locale.value,
|
||||
}
|
||||
await installationStore.addInstallationLanguage(language)
|
||||
|
||||
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: t('wizard.success.' + res.data.success),
|
||||
|
||||
@@ -18,17 +18,15 @@
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<p class="mt-4 mb-0 text-sm text-gray-600">Notes:</p>
|
||||
<p class="mt-4 mb-0 text-sm text-gray-600">{{ $t('wizard.verify_domain.notes.notes') }}</p>
|
||||
<ul class="w-full text-gray-600 list-disc list-inside">
|
||||
<li class="text-sm leading-8">
|
||||
App domain should not contain
|
||||
<b class="inline-block px-1 bg-gray-100 rounded-sm">https://</b> or
|
||||
<b class="inline-block px-1 bg-gray-100 rounded-sm">http</b> in front of
|
||||
the domain.
|
||||
{{ $t('wizard.verify_domain.notes.not_contain') }}
|
||||
<b class="inline-block px-1 bg-gray-100 rounded-sm">https://</b> {{ $t('wizard.verify_domain.notes.or') }}
|
||||
<b class="inline-block px-1 bg-gray-100 rounded-sm">http</b> {{ $t('wizard.verify_domain.notes.in_front') }}
|
||||
</li>
|
||||
<li class="text-sm leading-8">
|
||||
If you're accessing the website on a different port, please mention the
|
||||
port. For example:
|
||||
{{ $t('wizard.verify_domain.notes.if_you') }}
|
||||
<b class="inline-block px-1 bg-gray-100">localhost:8080</b>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
<BaseMultiselect
|
||||
v-model="currentPreferences.fiscal_year"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="globalStore.fiscalYears"
|
||||
:options="fiscalYearsList"
|
||||
label="key"
|
||||
value-prop="value"
|
||||
:placeholder="$t('settings.preferences.select_financial_year')"
|
||||
@@ -174,6 +174,14 @@ const router = useRouter()
|
||||
|
||||
isFetchingInitialData.value = true
|
||||
|
||||
const fiscalYearsList = computed(() => {
|
||||
return globalStore.fiscalYears.map((item) => {
|
||||
return Object.assign({}, item, {
|
||||
key: t(item.key),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const options = reactive([
|
||||
{
|
||||
title: tm('settings.customization.invoices.allow'),
|
||||
|
||||
@@ -225,7 +225,7 @@
|
||||
<!-- Invoice status -->
|
||||
<template #cell-status="{ row }">
|
||||
<BaseInvoiceStatusBadge :status="row.data.status" class="px-3 py-1">
|
||||
{{ row.data.status }}
|
||||
<BaseInvoiceStatusLabel :status="row.data.status" />
|
||||
</BaseInvoiceStatusBadge>
|
||||
</template>
|
||||
|
||||
@@ -249,7 +249,7 @@
|
||||
:status="row.data.paid_status"
|
||||
class="px-1 py-0.5 ml-2"
|
||||
>
|
||||
{{ row.data.paid_status }}
|
||||
<BaseInvoiceStatusLabel :status="row.data.paid_status" />
|
||||
</BasePaidStatusBadge>
|
||||
</div>
|
||||
</template>
|
||||
@@ -277,6 +277,7 @@ import { debouncedWatch } from '@vueuse/core'
|
||||
import MoonwalkerIcon from '@/scripts/components/icons/empty/MoonwalkerIcon.vue'
|
||||
import InvoiceDropdown from '@/scripts/admin/components/dropdowns/InvoiceIndexDropdown.vue'
|
||||
import SendInvoiceModal from '@/scripts/admin/components/modal-components/SendInvoiceModal.vue'
|
||||
import BaseInvoiceStatusLabel from "@/scripts/components/base/BaseInvoiceStatusLabel.vue";
|
||||
// Stores
|
||||
const invoiceStore = useInvoiceStore()
|
||||
const dialogStore = useDialogStore()
|
||||
@@ -291,12 +292,21 @@ const showFilters = ref(false)
|
||||
|
||||
const status = ref([
|
||||
{
|
||||
label: 'Status',
|
||||
options: ['DRAFT', 'DUE', 'SENT', 'VIEWED', 'COMPLETED'],
|
||||
label: t('invoices.status'),
|
||||
options: [
|
||||
{label: t('general.draft'), value: 'DRAFT'},
|
||||
{label: t('general.due'), value: 'DUE'},
|
||||
{label: t('general.sent'), value: 'SENT'},
|
||||
{label: t('invoices.viewed'), value: 'VIEWED'},
|
||||
{label: t('invoices.completed'), value: 'COMPLETED'}
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Paid Status',
|
||||
options: ['UNPAID', 'PAID', 'PARTIALLY_PAID'],
|
||||
label: t('invoices.paid_status'),
|
||||
options: [
|
||||
{label: t('invoices.unpaid'), value: 'UNPAID'},
|
||||
{label: t('invoices.paid'), value: 'PAID'},
|
||||
{label: t('invoices.partially_paid'), value: 'PARTIALLY_PAID'}],
|
||||
},
|
||||
,
|
||||
])
|
||||
|
||||
@@ -454,7 +454,7 @@ onSearched = debounce(onSearched, 500)
|
||||
:status="invoice.status"
|
||||
class="px-1 text-xs"
|
||||
>
|
||||
{{ invoice.status }}
|
||||
<BaseInvoiceStatusLabel :status="invoice.status" />
|
||||
</BaseEstimateStatusBadge>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
target="_blank"
|
||||
>
|
||||
<BaseButton variant="primary-outline" type="button">
|
||||
Sign up & Get Token
|
||||
{{ $t('modules.sign_up_and_get_token') }}
|
||||
</BaseButton>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
:label="$t('payments.invoice')"
|
||||
:help-text="
|
||||
selectedInvoice
|
||||
? `Due Amount: ${
|
||||
? `${t('payments.amount_due')}: ${
|
||||
paymentStore.currentPayment.maxPayableAmount / 100
|
||||
}`
|
||||
: ''
|
||||
|
||||
@@ -251,7 +251,7 @@ const paymentColumns = computed(() => {
|
||||
{ key: 'payment_number', label: t('payments.payment_number') },
|
||||
{ key: 'name', label: t('payments.customer') },
|
||||
{ key: 'payment_mode', label: t('payments.payment_mode') },
|
||||
{ key: 'invoice_number', label: t('invoices.invoice_number') },
|
||||
{ key: 'invoice_number', label: t('payments.invoice') },
|
||||
{ key: 'amount', label: t('payments.amount') },
|
||||
{
|
||||
key: 'actions',
|
||||
|
||||
@@ -230,7 +230,7 @@
|
||||
:status="row.data.status"
|
||||
class="px-3 py-1"
|
||||
>
|
||||
{{ row.data.status }}
|
||||
<BaseRecurringInvoiceStatusLabel :status="row.data.status" />
|
||||
</BaseRecurringInvoiceStatusBadge>
|
||||
</template>
|
||||
|
||||
@@ -276,7 +276,11 @@ const userStore = useUserStore()
|
||||
const table = ref(null)
|
||||
const { t } = useI18n()
|
||||
const showFilters = ref(false)
|
||||
const statusList = ref(['ACTIVE', 'ON_HOLD', 'ALL'])
|
||||
const statusList = ref([
|
||||
{label: t('recurring_invoices.active'), value: 'ACTIVE'},
|
||||
{label: t('recurring_invoices.on_hold'), value: 'ON_HOLD'},
|
||||
{label: t('recurring_invoices.all'), value: 'ALL'}
|
||||
])
|
||||
const isRequestOngoing = ref(true)
|
||||
const activeTab = ref('recurring-invoices.all')
|
||||
const router = useRouter()
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
:invalid="v.status.$error"
|
||||
:placeholder="$t('recurring_invoices.select_a_status')"
|
||||
value-prop="value"
|
||||
label="value"
|
||||
label="key"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
@@ -186,6 +186,7 @@ import { useDebounceFn } from '@vueuse/core'
|
||||
import { useRecurringInvoiceStore } from '@/scripts/admin/stores/recurring-invoice'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import ExchangeRateConverter from '@/scripts/admin/components/estimate-invoice-common/ExchangeRateConverter.vue'
|
||||
|
||||
@@ -207,13 +208,14 @@ const props = defineProps({
|
||||
const route = useRoute()
|
||||
const recurringInvoiceStore = useRecurringInvoiceStore()
|
||||
const globalStore = useGlobalStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const isLoadingNextDate = ref(false)
|
||||
|
||||
const limits = reactive([
|
||||
{ label: 'None', value: 'NONE' },
|
||||
{ label: 'Date', value: 'DATE' },
|
||||
{ label: 'Count', value: 'COUNT' },
|
||||
{ label: t('recurring_invoices.limit.none'), value: 'NONE' },
|
||||
{ label: t('recurring_invoices.limit.date'), value: 'DATE' },
|
||||
{ label: t('recurring_invoices.limit.count'), value: 'COUNT' },
|
||||
])
|
||||
|
||||
const isCustomFrequency = computed(() => {
|
||||
@@ -226,9 +228,17 @@ const isCustomFrequency = computed(() => {
|
||||
|
||||
const getStatusOptions = computed(() => {
|
||||
if (props.isEdit) {
|
||||
return globalStore.config.recurring_invoice_status.update_status
|
||||
return globalStore.config.recurring_invoice_status.update_status.map((item) => {
|
||||
return Object.assign({}, item, {
|
||||
key: t(item.key),
|
||||
})
|
||||
})
|
||||
}
|
||||
return globalStore.config.recurring_invoice_status.create_status
|
||||
return globalStore.config.recurring_invoice_status.create_status.map((item) => {
|
||||
return Object.assign({}, item, {
|
||||
key: t(item.key),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
watch(
|
||||
|
||||
@@ -27,10 +27,15 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Invoice date -->
|
||||
<template #cell-invoice_date="{ row }">
|
||||
{{ row.data.formatted_invoice_date }}
|
||||
</template>
|
||||
|
||||
<!-- Invoice status -->
|
||||
<template #cell-status="{ row }">
|
||||
<BaseInvoiceStatusBadge :status="row.data.status" class="px-3 py-1">
|
||||
{{ row.data.status }}
|
||||
<BaseInvoiceStatusLabel :status="row.data.status" />
|
||||
</BaseInvoiceStatusBadge>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -293,7 +293,7 @@ onSearched = debounce(onSearched, 500)
|
||||
:status="invoice.status"
|
||||
class="px-1 text-xs"
|
||||
>
|
||||
{{ invoice.status }}
|
||||
<BaseRecurringInvoiceStatusLabel :status="invoice.status" />
|
||||
</BaseRecurringInvoiceStatusBadge>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -156,7 +156,16 @@ const dateRange = reactive([
|
||||
])
|
||||
|
||||
const selectedRange = ref(dateRange[2])
|
||||
const reportTypes = ref(['By Customer', 'By Item'])
|
||||
const reportTypes = ref([
|
||||
{
|
||||
label: t('reports.sales.sort.by_customer'),
|
||||
value: 'By Customer'
|
||||
},
|
||||
{
|
||||
label: t('reports.sales.sort.by_item'),
|
||||
value: 'By Item'
|
||||
}
|
||||
])
|
||||
const selectedType = ref('By Customer')
|
||||
let range = ref(new Date())
|
||||
let url = ref(null)
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
<span class="text-xs text-gray-500"> ({{ row.data.slug }})</span>
|
||||
</template>
|
||||
|
||||
<template #cell-model_type="{ row }">
|
||||
{{ getModelType(row.data.model_type) }}
|
||||
</template>
|
||||
|
||||
<template #cell-is_required="{ row }">
|
||||
<BaseBadge
|
||||
:bg-color="
|
||||
@@ -147,4 +151,21 @@ function addCustomField() {
|
||||
async function refreshTable() {
|
||||
table.value && table.value.refresh()
|
||||
}
|
||||
|
||||
function getModelType(type) {
|
||||
switch (type) {
|
||||
case 'Customer':
|
||||
return t('settings.custom_fields.model_type.customer')
|
||||
case 'Invoice':
|
||||
return t('settings.custom_fields.model_type.invoice')
|
||||
case 'Estimate':
|
||||
return t('settings.custom_fields.model_type.estimate')
|
||||
case 'Expense':
|
||||
return t('settings.custom_fields.model_type.expense')
|
||||
case 'Payment':
|
||||
return t('settings.custom_fields.model_type.payment')
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
.color
|
||||
"
|
||||
>
|
||||
{{ row.data.set_as_default ? 'Yes' : 'No'.replace('_', ' ') }}
|
||||
{{ row.data.set_as_default ? $t('general.yes') : $t('general.no').replace('_', ' ') }}
|
||||
</BaseBadge>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
:load-data="refreshTable"
|
||||
/>
|
||||
</template>
|
||||
<template #cell-type="{ row }">
|
||||
{{ getLabelNote(row.data.type) }}
|
||||
</template>
|
||||
</BaseTable>
|
||||
</BaseSettingCard>
|
||||
</template>
|
||||
@@ -113,4 +116,17 @@ async function openNoteSelectModal() {
|
||||
async function refreshTable() {
|
||||
table.value && table.value.refresh()
|
||||
}
|
||||
|
||||
function getLabelNote(type) {
|
||||
switch (type) {
|
||||
case 'Estimate':
|
||||
return t('settings.customization.notes.types.estimate')
|
||||
case 'Invoice':
|
||||
return t('settings.customization.notes.types.invoice')
|
||||
case 'Payment':
|
||||
return t('settings.customization.notes.types.payment')
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<BaseMultiselect
|
||||
v-model="settingsForm.fiscal_year"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="globalStore.config.fiscal_years"
|
||||
:options="fiscalYearsList"
|
||||
label="key"
|
||||
value-prop="value"
|
||||
:invalid="v$.fiscal_year.$error"
|
||||
@@ -197,6 +197,14 @@ const retrospectiveEditOptions = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const fiscalYearsList = computed(() => {
|
||||
return globalStore.config.fiscal_years.map((item) => {
|
||||
return Object.assign({}, item, {
|
||||
key: t(item.key),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
watch(
|
||||
() => settingsForm.carbon_date_format,
|
||||
(val) => {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
border-t border-b border-gray-200 border-solid
|
||||
"
|
||||
>
|
||||
Component
|
||||
{{ $t('settings.customization.component') }}
|
||||
</th>
|
||||
<th
|
||||
class="
|
||||
@@ -57,7 +57,7 @@
|
||||
border-t border-b border-gray-200 border-solid
|
||||
"
|
||||
>
|
||||
Parameter
|
||||
{{ $t('settings.customization.Parameter') }}
|
||||
</th>
|
||||
<th
|
||||
class="
|
||||
@@ -126,7 +126,7 @@
|
||||
variant="white"
|
||||
@click.prevent="removeComponent(element)"
|
||||
>
|
||||
Remove
|
||||
{{ $t('general.remove') }}
|
||||
<template #left="slotProps">
|
||||
<BaseIcon
|
||||
name="XIcon"
|
||||
|
||||
Reference in New Issue
Block a user