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:
agencetwogether
2024-06-05 12:07:46 +02:00
committed by GitHub
parent 3259173066
commit 3b61440e1f
89 changed files with 925 additions and 213 deletions

View File

@@ -88,7 +88,7 @@ class CustomerStatsController extends Controller
($receiptTotals[$i] - $expenseTotals[$i]) ($receiptTotals[$i] - $expenseTotals[$i])
); );
$i++; $i++;
array_push($months, $start->format('M')); array_push($months, $start->translatedFormat('M'));
$monthCounter++; $monthCounter++;
$end->startOfMonth(); $end->startOfMonth();
$start->addMonth()->startOfMonth(); $start->addMonth()->startOfMonth();

View File

@@ -91,7 +91,7 @@ class DashboardController extends Controller
($receipt_totals[$i] - $expense_totals[$i]) ($receipt_totals[$i] - $expense_totals[$i])
); );
$i++; $i++;
array_push($months, $start->format('M')); array_push($months, $start->translatedFormat('M'));
$monthCounter++; $monthCounter++;
$end->startOfMonth(); $end->startOfMonth();
$start->addMonth()->startOfMonth(); $start->addMonth()->startOfMonth();

View File

@@ -54,8 +54,8 @@ class CustomerSalesReportController extends Controller
} }
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat); $from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->translatedFormat($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat); $to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->translatedFormat($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id)); $currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [ $colors = [

View File

@@ -41,8 +41,8 @@ class ExpensesReportController extends Controller
} }
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat); $from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->translatedFormat($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat); $to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->translatedFormat($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id)); $currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [ $colors = [

View File

@@ -41,8 +41,8 @@ class ItemSalesReportController extends Controller
} }
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat); $from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->translatedFormat($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat); $to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->translatedFormat($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id)); $currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [ $colors = [

View File

@@ -47,8 +47,8 @@ class ProfitLossReportController extends Controller
} }
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat); $from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->translatedFormat($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat); $to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->translatedFormat($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id)); $currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [ $colors = [

View File

@@ -42,8 +42,8 @@ class TaxSummaryReportController extends Controller
} }
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat); $from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->translatedFormat($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat); $to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->translatedFormat($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id)); $currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [ $colors = [

View File

@@ -34,6 +34,7 @@ class BootstrapController extends Controller
'menu' => $menu, 'menu' => $menu,
'current_customer_currency' => Currency::find($customer->currency_id), 'current_customer_currency' => Currency::find($customer->currency_id),
'modules' => Module::where('enabled', true)->pluck('name'), 'modules' => Module::where('enabled', true)->pluck('name'),
'current_company_language' => CompanySetting::getSetting('language', $customer->company_id),
]]); ]]);
} }
} }

View File

@@ -0,0 +1,21 @@
<?php
namespace InvoiceShelf\Http\Controllers\V1\Installation;
use Illuminate\Http\JsonResponse;
use InvoiceShelf\Http\Controllers\Controller;
class LanguagesController extends Controller
{
/**
* Display the languages page.
*
* @return JsonResponse
*/
public function languages()
{
return response()->json([
'languages' => config('invoiceshelf.languages'),
]);
}
}

View File

@@ -19,11 +19,13 @@ class OnboardingWizardController extends Controller
if (! InstallUtils::dbMarkerExists()) { if (! InstallUtils::dbMarkerExists()) {
return response()->json([ return response()->json([
'profile_complete' => 0, 'profile_complete' => 0,
'profile_language' => 'en',
]); ]);
} }
return response()->json([ return response()->json([
'profile_complete' => Setting::getSetting('profile_complete'), 'profile_complete' => Setting::getSetting('profile_complete'),
'profile_language' => Setting::getSetting('profile_language'),
]); ]);
} }
@@ -43,4 +45,13 @@ class OnboardingWizardController extends Controller
'profile_complete' => Setting::getSetting('profile_complete'), 'profile_complete' => Setting::getSetting('profile_complete'),
]); ]);
} }
public function saveLanguage(Request $request)
{
Setting::setSetting('profile_language', $request->profile_language);
return response()->json([
'profile_language' => Setting::getSetting('profile_language'),
]);
}
} }

View File

@@ -29,6 +29,6 @@ class RoleResource extends JsonResource
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->scope); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->scope);
return Carbon::parse($this->created_at)->format($dateFormat); return Carbon::parse($this->created_at)->translatedFormat($dateFormat);
} }
} }

View File

@@ -31,6 +31,7 @@ class EstimateViewedMail extends Mailable
public function build() public function build()
{ {
return $this->from(config('mail.from.address'), config('mail.from.name')) return $this->from(config('mail.from.address'), config('mail.from.name'))
->subject(__('notification_view_estimate'))
->markdown('emails.viewed.estimate', ['data', $this->data]); ->markdown('emails.viewed.estimate', ['data', $this->data]);
} }
} }

View File

@@ -31,6 +31,7 @@ class InvoiceViewedMail extends Mailable
public function build() public function build()
{ {
return $this->from(config('mail.from.address'), config('mail.from.name')) return $this->from(config('mail.from.address'), config('mail.from.name'))
->subject(__('notification_view_invoice'))
->markdown('emails.viewed.invoice', ['data', $this->data]); ->markdown('emails.viewed.invoice', ['data', $this->data]);
} }
} }

View File

@@ -54,7 +54,7 @@ class Customer extends Authenticatable implements HasMedia
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->created_at)->format($dateFormat); return Carbon::parse($this->created_at)->translatedFormat($dateFormat);
} }
public function setPasswordAttribute($value) public function setPasswordAttribute($value)

View File

@@ -112,14 +112,14 @@ class Estimate extends Model implements HasMedia
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->expiry_date)->format($dateFormat); return Carbon::parse($this->expiry_date)->translatedFormat($dateFormat);
} }
public function getFormattedEstimateDateAttribute($value) public function getFormattedEstimateDateAttribute($value)
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->estimate_date)->format($dateFormat); return Carbon::parse($this->estimate_date)->translatedFormat($dateFormat);
} }
public function scopeEstimatesBetween($query, $start, $end) public function scopeEstimatesBetween($query, $start, $end)

View File

@@ -72,14 +72,14 @@ class Expense extends Model implements HasMedia
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->expense_date)->format($dateFormat); return Carbon::parse($this->expense_date)->translatedFormat($dateFormat);
} }
public function getFormattedCreatedAtAttribute($value) public function getFormattedCreatedAtAttribute($value)
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->created_at)->format($dateFormat); return Carbon::parse($this->created_at)->translatedFormat($dateFormat);
} }
public function getReceiptUrlAttribute($value) public function getReceiptUrlAttribute($value)

View File

@@ -189,14 +189,14 @@ class Invoice extends Model implements HasMedia
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->due_date)->format($dateFormat); return Carbon::parse($this->due_date)->translatedFormat($dateFormat);
} }
public function getFormattedInvoiceDateAttribute($value) public function getFormattedInvoiceDateAttribute($value)
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->invoice_date)->format($dateFormat); return Carbon::parse($this->invoice_date)->translatedFormat($dateFormat);
} }
public function scopeWhereStatus($query, $status) public function scopeWhereStatus($query, $status)

View File

@@ -111,7 +111,7 @@ class Item extends Model
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', request()->header('company')); $dateFormat = CompanySetting::getSetting('carbon_date_format', request()->header('company'));
return Carbon::parse($this->created_at)->format($dateFormat); return Carbon::parse($this->created_at)->translatedFormat($dateFormat);
} }
public function taxes(): HasMany public function taxes(): HasMany

View File

@@ -74,14 +74,14 @@ class Payment extends Model implements HasMedia
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->created_at)->format($dateFormat); return Carbon::parse($this->created_at)->translatedFormat($dateFormat);
} }
public function getFormattedPaymentDateAttribute($value) public function getFormattedPaymentDateAttribute($value)
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->payment_date)->format($dateFormat); return Carbon::parse($this->payment_date)->translatedFormat($dateFormat);
} }
public function getPaymentPdfUrlAttribute() public function getPaymentPdfUrlAttribute()

View File

@@ -57,14 +57,14 @@ class RecurringInvoice extends Model
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->starts_at)->format($dateFormat); return Carbon::parse($this->starts_at)->translatedFormat($dateFormat);
} }
public function getFormattedNextInvoiceAtAttribute() public function getFormattedNextInvoiceAtAttribute()
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->next_invoice_at)->format($dateFormat); return Carbon::parse($this->next_invoice_at)->translatedFormat($dateFormat);
} }
public function getFormattedLimitDateAttribute() public function getFormattedLimitDateAttribute()

View File

@@ -47,7 +47,7 @@ class DateFormatter
foreach (static::$formats as $format) { foreach (static::$formats as $format) {
$new[] = [ $new[] = [
'display_date' => Carbon::now()->format($format['carbon_format']), 'display_date' => Carbon::now()->translatedFormat($format['carbon_format']),
'carbon_format_value' => $format['carbon_format'], 'carbon_format_value' => $format['carbon_format'],
'moment_format_value' => $format['moment_format'], 'moment_format_value' => $format['moment_format'],
]; ];

View File

@@ -103,9 +103,9 @@ class EnvironmentManager
*/ */
public function saveDatabaseVariables(DatabaseEnvironmentRequest $request) public function saveDatabaseVariables(DatabaseEnvironmentRequest $request)
{ {
$dbEnv = [ $dbEnv = [
'APP_URL' => $request->get('app_url'), 'APP_URL' => $request->get('app_url'),
'APP_LOCALE' => $request->get('app_locale'),
'DB_CONNECTION' => $request->get('database_connection'), 'DB_CONNECTION' => $request->get('database_connection'),
'SANCTUM_STATEFUL_DOMAINS' => $request->get('app_domain'), 'SANCTUM_STATEFUL_DOMAINS' => $request->get('app_domain'),
'SESSION_DOMAIN' => explode(':', $request->get('app_domain'))[0], 'SESSION_DOMAIN' => explode(':', $request->get('app_domain'))[0],

View File

@@ -11,4 +11,4 @@ return [
'Redis' => Illuminate\Support\Facades\Redis::class, 'Redis' => Illuminate\Support\Facades\Redis::class,
])->toArray(), ])->toArray(),
]; ];

View File

@@ -78,18 +78,18 @@ return [
* List of Fiscal Years * List of Fiscal Years
*/ */
'fiscal_years' => [ 'fiscal_years' => [
['key' => 'january-december', 'value' => '1-12'], ['key' => 'settings.preferences.fiscal_years.january_december', 'value' => '1-12'],
['key' => 'february-january', 'value' => '2-1'], ['key' => 'settings.preferences.fiscal_years.february_january', 'value' => '2-1'],
['key' => 'march-february', 'value' => '3-2'], ['key' => 'settings.preferences.fiscal_years.march_february', 'value' => '3-2'],
['key' => 'april-march', 'value' => '4-3'], ['key' => 'settings.preferences.fiscal_years.april_march', 'value' => '4-3'],
['key' => 'may-april', 'value' => '5-4'], ['key' => 'settings.preferences.fiscal_years.may_april', 'value' => '5-4'],
['key' => 'june-may', 'value' => '6-5'], ['key' => 'settings.preferences.fiscal_years.june_may', 'value' => '6-5'],
['key' => 'july-june', 'value' => '7-6'], ['key' => 'settings.preferences.fiscal_years.july_june', 'value' => '7-6'],
['key' => 'august-july', 'value' => '8-7'], ['key' => 'settings.preferences.fiscal_years.august_july', 'value' => '8-7'],
['key' => 'september-august', 'value' => '9-8'], ['key' => 'settings.preferences.fiscal_years.september_august', 'value' => '9-8'],
['key' => 'october-september', 'value' => '10-9'], ['key' => 'settings.preferences.fiscal_years.october_september', 'value' => '10-9'],
['key' => 'november-october', 'value' => '11-10'], ['key' => 'settings.preferences.fiscal_years.november_october', 'value' => '11-10'],
['key' => 'december-november', 'value' => '12-11'], ['key' => 'settings.preferences.fiscal_years.december_november', 'value' => '12-11'],
], ],
/* /*
@@ -408,7 +408,7 @@ return [
*/ */
'customer_menu' => [ 'customer_menu' => [
[ [
'title' => 'Dashboard', 'title' => 'navigation.dashboard',
'link' => '/customer/dashboard', 'link' => '/customer/dashboard',
'icon' => '', 'icon' => '',
'name' => '', 'name' => '',
@@ -418,7 +418,7 @@ return [
'model' => '', 'model' => '',
], ],
[ [
'title' => 'Invoices', 'title' => 'navigation.invoices',
'link' => '/customer/invoices', 'link' => '/customer/invoices',
'icon' => '', 'icon' => '',
'name' => '', 'name' => '',
@@ -428,7 +428,7 @@ return [
'model' => '', 'model' => '',
], ],
[ [
'title' => 'Estimates', 'title' => 'navigation.estimates',
'link' => '/customer/estimates', 'link' => '/customer/estimates',
'icon' => '', 'icon' => '',
'name' => '', 'name' => '',
@@ -438,7 +438,7 @@ return [
'model' => '', 'model' => '',
], ],
[ [
'title' => 'Payments', 'title' => 'navigation.payments',
'link' => '/customer/payments', 'link' => '/customer/payments',
'icon' => '', 'icon' => '',
'name' => '', 'name' => '',
@@ -448,7 +448,7 @@ return [
'model' => '', 'model' => '',
], ],
[ [
'title' => 'Settings', 'title' => 'navigation.settings',
'link' => '/customer/settings', 'link' => '/customer/settings',
'icon' => '', 'icon' => '',
'name' => '', 'name' => '',

View File

@@ -100,16 +100,35 @@
"pay_invoice": "Pay Invoice", "pay_invoice": "Pay Invoice",
"login_successfully": "Logged in successfully!", "login_successfully": "Logged in successfully!",
"logged_out_successfully": "Logged out successfully", "logged_out_successfully": "Logged out successfully",
"mark_as_default": "Mark as default" "mark_as_default": "Mark as default",
"no_data_found": "No data found",
"pagination": {
"previous": "Previous",
"next": "Next",
"showing": "Showing",
"to": "to",
"of": "of",
"results": "results"
},
"file_upload": {
"drag_a_file": "Drag a file here or",
"browse": "browse",
"to_choose": "to choose a file"
},
"multiselect": {
"the_list_is_empty": "The list is empty",
"no_results_found": "No results found"
},
"copy_to_clipboard": "Copy to Clipboard"
}, },
"dashboard": { "dashboard": {
"select_year": "Select year", "select_year": "Select year",
"cards": { "cards": {
"due_amount": "Amount Due", "due_amount": "Amount Due",
"customers": "Customers", "customers": "Customer | Customers",
"invoices": "Invoices", "invoices": "Invoice | Invoices",
"estimates": "Estimates", "estimates": "Estimate | Estimates",
"payments": "Payments" "payments": "Payment | Payments"
}, },
"chart_info": { "chart_info": {
"total_sales": "Sales", "total_sales": "Sales",
@@ -548,7 +567,18 @@
"hour": "Hour", "hour": "Hour",
"day_month": "Day of month", "day_month": "Day of month",
"month": "Month", "month": "Month",
"day_week": "Day of week" "day_week": "Day of week",
"every_minute": "Every Minute",
"every_30_minute": "Every 30 Minute",
"every_hour": "Every Hour",
"every_2_hour": "Every 2 Hour",
"every_day_at_midnight": "Every day at midnight",
"every_week": "Every Week",
"every_15_days_at_midnight": "Every 15 days at midnight",
"on_the_first_day_of_every_month_at_midnight": "On the first day of every month at 00:00",
"every_6_month": "Every 6 Month",
"every_year_on_the_first_day_of_january_at_midnight": "Every year on the first day of january at 00:00",
"custom": "Custom"
}, },
"confirm_delete": "You will not be able to recover this Invoice | You will not be able to recover these Invoices", "confirm_delete": "You will not be able to recover this Invoice | You will not be able to recover these Invoices",
"created_message": "Recurring Invoice created successfully", "created_message": "Recurring Invoice created successfully",
@@ -557,7 +587,12 @@
"marked_as_sent_message": "Recurring Invoice marked as sent successfully", "marked_as_sent_message": "Recurring Invoice marked as sent successfully",
"user_email_does_not_exist": "User email does not exist", "user_email_does_not_exist": "User email does not exist",
"something_went_wrong": "something went wrong", "something_went_wrong": "something went wrong",
"invalid_due_amount_message": "Total Recurring Invoice amount cannot be less than total paid amount for this Recurring Invoice. Please update the invoice or delete the associated payments to continue." "invalid_due_amount_message": "Total Recurring Invoice amount cannot be less than total paid amount for this Recurring Invoice. Please update the invoice or delete the associated payments to continue.",
"limit": {
"none": "None",
"date": "Date",
"count": "Count"
}
}, },
"payments": { "payments": {
"title": "Payments", "title": "Payments",
@@ -595,7 +630,8 @@
"created_message": "Payment created successfully", "created_message": "Payment created successfully",
"updated_message": "Payment updated successfully", "updated_message": "Payment updated successfully",
"deleted_message": "Payment deleted successfully | Payments deleted successfully", "deleted_message": "Payment deleted successfully | Payments deleted successfully",
"invalid_amount_message": "Payment amount is invalid" "invalid_amount_message": "Payment amount is invalid",
"amount_due": "Due Amount"
}, },
"expenses": { "expenses": {
"title": "Expenses", "title": "Expenses",
@@ -700,7 +736,8 @@
"installed": "Installed", "installed": "Installed",
"no_modules_installed": "No Modules Installed Yet!", "no_modules_installed": "No Modules Installed Yet!",
"disable_warning": "All the settings for this particular will be reverted.", "disable_warning": "All the settings for this particular will be reverted.",
"what_you_get": "What you get" "what_you_get": "What you get",
"sign_up_and_get_token": "Sign up & Get Token"
}, },
"users": { "users": {
"title": "Users", "title": "Users",
@@ -752,7 +789,11 @@
"date_range": "Select Date Range", "date_range": "Select Date Range",
"to_date": "To Date", "to_date": "To Date",
"from_date": "From Date", "from_date": "From Date",
"report_type": "Report Type" "report_type": "Report Type",
"sort": {
"by_customer": "By Customer",
"by_item": "By Item"
}
}, },
"taxes": { "taxes": {
"taxes": "Taxes", "taxes": "Taxes",
@@ -920,7 +961,14 @@
"added_message": "Custom Field added successfully", "added_message": "Custom Field added successfully",
"press_enter_to_add": "Press enter to add new option", "press_enter_to_add": "Press enter to add new option",
"model_in_use": "Cannot update model for fields which are already in use.", "model_in_use": "Cannot update model for fields which are already in use.",
"type_in_use": "Cannot update type for fields which are already in use." "type_in_use": "Cannot update type for fields which are already in use.",
"model_type": {
"customer": "Customer",
"invoice": "Invoice",
"estimate": "Estimate",
"expense": "Expense",
"payment": "Payment"
}
}, },
"customization": { "customization": {
"customization": "customization", "customization": "customization",
@@ -1041,7 +1089,12 @@
"note_updated": "Note Updated successfully", "note_updated": "Note Updated successfully",
"note_confirm_delete": "You will not be able to recover this Note", "note_confirm_delete": "You will not be able to recover this Note",
"already_in_use": "Note is already in use", "already_in_use": "Note is already in use",
"deleted_message": "Note deleted successfully" "deleted_message": "Note deleted successfully",
"types": {
"estimate": "Estimate",
"invoice": "Invoice",
"payment": "Payment"
}
} }
}, },
"account_settings": { "account_settings": {
@@ -1196,7 +1249,21 @@
"on_hold": "On Hold", "on_hold": "On Hold",
"update_status": "Update Status", "update_status": "Update Status",
"completed": "Completed", "completed": "Completed",
"company_currency_unchangeable": "Company currency cannot be changed" "company_currency_unchangeable": "Company currency cannot be changed",
"fiscal_years": {
"january_december": "January - December",
"february_january": "February - January",
"march_february": "March - February",
"april_march": "April - March",
"may_april": "May - April",
"june_may": "June - May",
"july_june": "July - June",
"august_july": "August - July",
"september_august": "September - August",
"october_september": "October - September",
"november_october": "November - October",
"december_november": "December - November"
}
}, },
"update_app": { "update_app": {
"title": "Update App", "title": "Update App",
@@ -1355,6 +1422,10 @@
"next": "Next", "next": "Next",
"continue": "Continue", "continue": "Continue",
"skip": "Skip", "skip": "Skip",
"install_language": {
"title": "Choose your language",
"description": "Select language wizard to install InvoiceShelf"
},
"database": { "database": {
"database": "Site URL & Database", "database": "Site URL & Database",
"connection": "Database Connection", "connection": "Database Connection",
@@ -1381,7 +1452,14 @@
"verify_now": "Verify Now", "verify_now": "Verify Now",
"success": "Domain Verify Successfully.", "success": "Domain Verify Successfully.",
"failed": "Domain verification failed. Please enter valid domain name.", "failed": "Domain verification failed. Please enter valid domain name.",
"verify_and_continue": "Verify And Continue" "verify_and_continue": "Verify And Continue",
"notes": {
"notes" : "Notes:",
"not_contain" : "App domain should not contain",
"or" : "or",
"in_front": "in front of the domain.",
"if_you": "If you're accessing the website on a different port, please mention the port. For example:"
}
}, },
"mail": { "mail": {
"host": "Mail Host", "host": "Mail Host",
@@ -1532,5 +1610,14 @@
"pdf_received_from": "Received from:", "pdf_received_from": "Received from:",
"pdf_tax_label": "Tax", "pdf_tax_label": "Tax",
"pdf_tax_id": "Tax-ID", "pdf_tax_id": "Tax-ID",
"pdf_vat_id": "VAT-ID" "pdf_vat_id": "VAT-ID",
"mail_thanks": "Thanks",
"mail_view_estimate":"View Estimate",
"mail_viewed_estimate": ":name viewed this Estimate.",
"mail_view_invoice": "View Invoice",
"mail_viewed_invoice": ":name viewed this Invoice.",
"mail_view_payment": "View Payment",
"notification_view_estimate": "[Notification] Estimate viewed",
"notification_view_invoice": "[Notification] Invoice viewed",
"You have received a new invoice from <b>{COMPANY_NAME}</b>.</br> Please download using the button below:": "You have received a new invoice from <b>{COMPANY_NAME}</b>.</br> Please download using the button below:"
} }

View File

@@ -100,16 +100,35 @@
"pay_invoice": "Payer facture", "pay_invoice": "Payer facture",
"login_successfully": "Identifié avec succès!", "login_successfully": "Identifié avec succès!",
"logged_out_successfully": "Déconnecté avec succès", "logged_out_successfully": "Déconnecté avec succès",
"mark_as_default": "Marquer par défaut" "mark_as_default": "Marquer par défaut",
"no_data_found": "Aucune donnée pour le moment",
"pagination": {
"previous": "Précédent",
"next": "Suivant",
"showing": "Affichage de",
"to": "à",
"of": "sur",
"results": "résultats"
},
"file_upload": {
"drag_a_file": "Déposez un fichier ici ou",
"browse": "parcourez",
"to_choose": "pour choisir un fichier"
},
"multiselect": {
"the_list_is_empty": "La liste est vide",
"no_results_found": "Aucun résultat"
},
"copy_to_clipboard": "Copier dans le presse-papier"
}, },
"dashboard": { "dashboard": {
"select_year": "Sélectionnez l'année", "select_year": "Sélectionnez l'année",
"cards": { "cards": {
"due_amount": "Encours clients", "due_amount": "Encours clients",
"customers": "Clients", "customers": "Client | Clients",
"invoices": "Factures", "invoices": "Facture | Factures",
"estimates": "Devis", "estimates": "Devis | Devis",
"payments": "Paiements" "payments": "Paiement | Paiements"
}, },
"chart_info": { "chart_info": {
"total_sales": "Ventes", "total_sales": "Ventes",
@@ -548,7 +567,18 @@
"hour": "Heure", "hour": "Heure",
"day_month": "Jour du mois", "day_month": "Jour du mois",
"month": "Mois", "month": "Mois",
"day_week": "Jour de la semaine" "day_week": "Jour de la semaine",
"every_minute": "Toutes les minutes",
"every_30_minute": "Toutes les 30 minutes",
"every_hour": "Toutes les heures",
"every_2_hour": "Toutes les 2 heures",
"every_day_at_midnight": "Tous les jours à minuit",
"every_week": "Toutes les semaines",
"every_15_days_at_midnight": "Tous les 15 jours à minuit",
"on_the_first_day_of_every_month_at_midnight": "Au premier jour du mois à minuit",
"every_6_month": "Tous les 6 mois",
"every_year_on_the_first_day_of_january_at_midnight": "Tous les ans, au premier janvier à minuit",
"custom": "Personnalisée"
}, },
"confirm_delete": "Vous ne pourrez pas récupérer cette facture | Vous ne pourrez pas récupérer ces factures", "confirm_delete": "Vous ne pourrez pas récupérer cette facture | Vous ne pourrez pas récupérer ces factures",
"created_message": "Facture récurrente créée", "created_message": "Facture récurrente créée",
@@ -557,7 +587,12 @@
"marked_as_sent_message": "Facture récurrente envoyée", "marked_as_sent_message": "Facture récurrente envoyée",
"user_email_does_not_exist": "L'email de l'utilisateur n'existe pas", "user_email_does_not_exist": "L'email de l'utilisateur n'existe pas",
"something_went_wrong": "une erreur sest produite", "something_went_wrong": "une erreur sest produite",
"invalid_due_amount_message": "Le montant total de la facture récurrente ne peut pas être inférieur au montant total payé pour cette facture récurrente. Veuillez mettre à jour la facture ou supprimer les paiements associés pour continuer." "invalid_due_amount_message": "Le montant total de la facture récurrente ne peut pas être inférieur au montant total payé pour cette facture récurrente. Veuillez mettre à jour la facture ou supprimer les paiements associés pour continuer.",
"limit": {
"none": "Aucun",
"date": "Date",
"count": "Nombre"
}
}, },
"payments": { "payments": {
"title": "Paiements", "title": "Paiements",
@@ -595,7 +630,8 @@
"created_message": "Paiement créé", "created_message": "Paiement créé",
"updated_message": "Paiement mis à jour", "updated_message": "Paiement mis à jour",
"deleted_message": "Paiement supprimé | Paiements supprimés", "deleted_message": "Paiement supprimé | Paiements supprimés",
"invalid_amount_message": "Le montant du paiement est invalide" "invalid_amount_message": "Le montant du paiement est invalide",
"amount_due": "Montant dû"
}, },
"expenses": { "expenses": {
"title": "Dépenses", "title": "Dépenses",
@@ -700,7 +736,8 @@
"installed": "Installé", "installed": "Installé",
"no_modules_installed": "Aucun module installé !", "no_modules_installed": "Aucun module installé !",
"disable_warning": "Tous les paramètres de ce module seront réinitialisés.", "disable_warning": "Tous les paramètres de ce module seront réinitialisés.",
"what_you_get": "Ce que vous obtenez" "what_you_get": "Ce que vous obtenez",
"sign_up_and_get_token": "Inscrivez-vous et obtenez votre Jeton"
}, },
"users": { "users": {
"title": "Utilisateurs", "title": "Utilisateurs",
@@ -752,7 +789,11 @@
"date_range": "Période", "date_range": "Période",
"to_date": "Au", "to_date": "Au",
"from_date": "Du", "from_date": "Du",
"report_type": "Trier" "report_type": "Trier",
"sort": {
"by_customer": "Par Client",
"by_item": "Par Article"
}
}, },
"taxes": { "taxes": {
"taxes": "Taxes", "taxes": "Taxes",
@@ -863,6 +904,8 @@
"company_info": { "company_info": {
"company_info": "Coordonnées de la société", "company_info": "Coordonnées de la société",
"company_name": "Nom", "company_name": "Nom",
"tax_id": "Numéro d'identification fiscale",
"vat_id": "Numéro d'identification TVA",
"company_logo": "Logo", "company_logo": "Logo",
"section_description": "Saisissez ici les coordonnées de votre entreprise qui s'afficheront sur tous vos documents.", "section_description": "Saisissez ici les coordonnées de votre entreprise qui s'afficheront sur tous vos documents.",
"phone": "Téléphone", "phone": "Téléphone",
@@ -918,7 +961,14 @@
"added_message": "Champ personnalisé ajouté", "added_message": "Champ personnalisé ajouté",
"press_enter_to_add": "Appuyez sur Entrée pour ajouter une nouvelle option", "press_enter_to_add": "Appuyez sur Entrée pour ajouter une nouvelle option",
"model_in_use": "Impossible de mettre à jour le modèle pour les champs qui sont déjà utilisés.", "model_in_use": "Impossible de mettre à jour le modèle pour les champs qui sont déjà utilisés.",
"type_in_use": "Impossible de mettre à jour le type des champs déjà utilisés." "type_in_use": "Impossible de mettre à jour le type des champs déjà utilisés.",
"model_type": {
"customer": "Client",
"invoice": "Facture",
"estimate": "Devis",
"expense": "Dépense",
"payment": "Paiement"
}
}, },
"customization": { "customization": {
"customization": "Personnalisation", "customization": "Personnalisation",
@@ -1039,7 +1089,12 @@
"note_updated": "Note de bas de page mise à jour", "note_updated": "Note de bas de page mise à jour",
"note_confirm_delete": "Vous ne pourrez pas récupérer cette note de bas de page", "note_confirm_delete": "Vous ne pourrez pas récupérer cette note de bas de page",
"already_in_use": "La note de bas de page est déjà utilisée", "already_in_use": "La note de bas de page est déjà utilisée",
"deleted_message": "Note de bas de page supprimée" "deleted_message": "Note de bas de page supprimée",
"types": {
"estimate": "Devis",
"invoice": "Facture",
"payment": "Paiement"
}
} }
}, },
"account_settings": { "account_settings": {
@@ -1194,7 +1249,21 @@
"on_hold": "En attente", "on_hold": "En attente",
"update_status": "Mettre à jour le statut", "update_status": "Mettre à jour le statut",
"completed": "Terminé", "completed": "Terminé",
"company_currency_unchangeable": "La devise de la société ne peut pas être modifiée" "company_currency_unchangeable": "La devise de la société ne peut pas être modifiée",
"fiscal_years": {
"january_december": "Janvier - Décembre",
"february_january": "Février - Janvier",
"march_february": "Mars - Février",
"april_march": "Avril - Mars",
"may_april": "Mai - Avril",
"june_may": "Juin - Mai",
"july_june": "Juillet - Juin",
"august_july": "Aout - Juillet",
"september_august": "Septembre - Aout",
"october_september": "Octobre - Septembre",
"november_october": "Novembre - Octobre",
"december_november": "Décembre - Novembre"
}
}, },
"update_app": { "update_app": {
"title": "Mise à jour", "title": "Mise à jour",
@@ -1267,6 +1336,12 @@
"aws_region": "Région AWS", "aws_region": "Région AWS",
"aws_bucket": "Bucket", "aws_bucket": "Bucket",
"aws_root": "Répertoire", "aws_root": "Répertoire",
"s3_endpoint": "S3 Endpoint",
"s3_key": "S3 Key",
"s3_secret": "S3 Secret",
"s3_region": "S3 Region",
"s3_bucket": "S3 Bucket",
"s3_root": "S3 Root",
"do_spaces_type": "Type", "do_spaces_type": "Type",
"do_spaces_key": "Key", "do_spaces_key": "Key",
"do_spaces_secret": "Secret", "do_spaces_secret": "Secret",
@@ -1347,6 +1422,10 @@
"next": "Suivant", "next": "Suivant",
"continue": "Poursuivre", "continue": "Poursuivre",
"skip": "Ignorer", "skip": "Ignorer",
"install_language": {
"title": "Choix de la langue",
"description": "Sélectionner la langue de l'assistant pour installer InvoiceShelf"
},
"database": { "database": {
"database": "URL du site et base de données", "database": "URL du site et base de données",
"connection": "Connexion à la base de données", "connection": "Connexion à la base de données",
@@ -1373,7 +1452,14 @@
"verify_now": "Vérifier maintenant", "verify_now": "Vérifier maintenant",
"success": "Vérification du domaine réussie.", "success": "Vérification du domaine réussie.",
"failed": "La vérification du domaine a échoué. Veuillez entrer un nom de domaine valide.", "failed": "La vérification du domaine a échoué. Veuillez entrer un nom de domaine valide.",
"verify_and_continue": "Vérifier et continuer" "verify_and_continue": "Vérifier et continuer",
"notes": {
"notes" : "Notes :",
"not_contain" : "Le domaine de l'application ne doit pas contenir",
"or" : "ou",
"in_front": "devant le domaine.",
"if_you": "Si vous accédez au site Web sur un autre port, veuillez mentionner le port. Par exemple :"
}
}, },
"mail": { "mail": {
"host": "Serveur email", "host": "Serveur email",
@@ -1522,5 +1608,16 @@
"pdf_bill_to": "Facturer à", "pdf_bill_to": "Facturer à",
"pdf_ship_to": "Expédier à", "pdf_ship_to": "Expédier à",
"pdf_received_from": "Reçu de :", "pdf_received_from": "Reçu de :",
"pdf_tax_label": "Taxe" "pdf_tax_label": "Taxe",
"pdf_tax_id": "Tax-ID",
"pdf_vat_id": "VAT-ID",
"mail_thanks": "Merci",
"mail_view_estimate":"Voir le Devis",
"mail_viewed_estimate": ":name a consulté ce Devis.",
"mail_view_invoice": "Voir la Facture",
"mail_viewed_invoice": ":name a consulté cette Facture.",
"mail_view_payment": "Voir le Paiement",
"notification_view_estimate": "[Notification] Un Devis a été consulté",
"notification_view_invoice": "[Notification] Une Facture a été consultée",
"You have received a new invoice from <b>{COMPANY_NAME}</b>.</br> Please download using the button below:": "Vous avez reçu une nouvelle facture de <b>{COMPANY_NAME}</b>.</br> Vous pouvez la télécharger en cliquant sur le bouton :"
} }

View File

@@ -26,7 +26,7 @@
{{ token }} {{ token }}
</span> </span>
<svg <svg
v-tooltip="{ content: 'Copy to Clipboard' }" v-tooltip="{ content: $t('general.copy_to_clipboard') }"
class=" class="
absolute absolute
right-0 right-0

View File

@@ -178,7 +178,7 @@
v$.confirm_password.$errors[0].$message v$.confirm_password.$errors[0].$message
" "
:content-loading="isFetchingInitialData" :content-loading="isFetchingInitialData"
label="Confirm Password" :label="$t('customers.confirm_password')"
> >
<BaseInput <BaseInput
v-model.trim="customerStore.currentCustomer.confirm_password" v-model.trim="customerStore.currentCustomer.confirm_password"

View File

@@ -41,7 +41,7 @@
<BaseMultiselect <BaseMultiselect
v-model="noteStore.currentNote.type" v-model="noteStore.currentNote.type"
:options="types" :options="types"
value-prop="type" value-prop="value"
class="mt-2" class="mt-2"
/> />
</BaseInputGroup> </BaseInputGroup>
@@ -122,7 +122,11 @@ const route = useRoute()
const { t } = useI18n() const { t } = useI18n()
let isSaving = ref(false) 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']) let fields = ref(['customer', 'customerCustom'])
const modalActive = computed(() => { const modalActive = computed(() => {
@@ -164,7 +168,7 @@ watch(
onMounted(() => { onMounted(() => {
if (route.name === 'estimates.create') { if (route.name === 'estimates.create') {
noteStore.currentNote.type = 'Estimate' 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' noteStore.currentNote.type = 'Invoice'
} else { } else {
noteStore.currentNote.type = 'Payment' noteStore.currentNote.type = 'Payment'

View File

@@ -96,7 +96,7 @@
@click="cancelPreview" @click="cancelPreview"
> >
<BaseIcon name="PencilIcon" class="h-5 mr-2" /> <BaseIcon name="PencilIcon" class="h-5 mr-2" />
Edit {{ $t('general.edit') }}
</BaseButton> </BaseButton>
<iframe <iframe
:src="templateUrl" :src="templateUrl"
@@ -166,7 +166,7 @@ let estimateMailForm = reactive({
id: null, id: null,
from: null, from: null,
to: null, to: null,
subject: 'New Estimate', subject: t('estimates.new_estimate'),
body: null, body: null,
}) })

View File

@@ -104,7 +104,7 @@
@click="cancelPreview" @click="cancelPreview"
> >
<BaseIcon name="PencilIcon" class="h-5 mr-2" /> <BaseIcon name="PencilIcon" class="h-5 mr-2" />
Edit {{ $t('general.edit') }}
</BaseButton> </BaseButton>
<iframe <iframe
@@ -181,7 +181,7 @@ const invoiceMailForm = reactive({
id: null, id: null,
from: null, from: null,
to: null, to: null,
subject: 'New Invoice', subject: t('invoices.new_invoice'),
body: null, body: null,
}) })

View File

@@ -104,7 +104,7 @@
@click="cancelPreview" @click="cancelPreview"
> >
<BaseIcon name="PencilIcon" class="h-5 mr-2" /> <BaseIcon name="PencilIcon" class="h-5 mr-2" />
Edit {{ $t('general.edit') }}
</BaseButton> </BaseButton>
<iframe <iframe
@@ -181,7 +181,7 @@ const paymentMailForm = reactive({
id: null, id: null,
from: null, from: null,
to: null, to: null,
subject: 'New Payment', subject: t('payments.new_payment'),
body: null, body: null,
}) })

View File

@@ -48,6 +48,7 @@
<BaseMultiselect <BaseMultiselect
v-model="customFieldStore.currentCustomField.model_type" v-model="customFieldStore.currentCustomField.model_type"
:options="modelTypes" :options="modelTypes"
value-prop="value"
:can-deselect="false" :can-deselect="false"
:invalid="v$.currentCustomField.model_type.$error" :invalid="v$.currentCustomField.model_type.$error"
:searchable="true" :searchable="true"
@@ -229,11 +230,11 @@ const { t } = useI18n()
let isSaving = ref(false) let isSaving = ref(false)
const modelTypes = reactive([ const modelTypes = reactive([
'Customer', {label: t('settings.custom_fields.model_type.customer'), value: 'Customer'},
'Invoice', {label: t('settings.custom_fields.model_type.invoice'), value: 'Invoice'},
'Estimate', {label: t('settings.custom_fields.model_type.estimate'), value: 'Estimate'},
'Expense', {label: t('settings.custom_fields.model_type.expense'), value: 'Expense'},
'Payment', {label: t('settings.custom_fields.model_type.payment'), value: 'Payment'}
]) ])
const dataTypes = reactive([ const dataTypes = reactive([
@@ -286,9 +287,6 @@ const isRequiredField = computed({
const rules = computed(() => { const rules = computed(() => {
return { return {
currentCustomField: { currentCustomField: {
type: {
required: helpers.withMessage(t('validation.required'), required),
},
name: { name: {
required: helpers.withMessage(t('validation.required'), required), required: helpers.withMessage(t('validation.required'), required),
}, },

View File

@@ -20,10 +20,25 @@ export const useInstallationStore = (useWindow = false) => {
database_username: null, database_username: null,
database_password: null, database_password: null,
app_url: window.location.origin, app_url: window.location.origin,
app_locale: null
}, },
}), }),
actions: { actions: {
fetchInstallationLanguages() {
return new Promise((resolve, reject) => {
axios
.get(`/api/v1/installation/languages`)
.then((response) => {
resolve(response)
})
.catch((err) => {
handleError(err)
reject(err)
})
})
},
fetchInstallationRequirements() { fetchInstallationRequirements() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
axios 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() { fetchInstallationPermissions() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
axios axios

View File

@@ -36,17 +36,17 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
}, },
frequencies: [ frequencies: [
{ label: 'Every Minute', value: '* * * * *' }, { label: global.t('recurring_invoices.frequency.every_minute'), value: '* * * * *' },
{ label: 'Every 30 Minute', value: '*/30 * * * *' }, { label: global.t('recurring_invoices.frequency.every_30_minute'), value: '*/30 * * * *' },
{ label: 'Every Hour', value: '0 * * * *' }, { label: global.t('recurring_invoices.frequency.every_hour'), value: '0 * * * *' },
{ label: 'Every 2 Hour', value: '0 */2 * * *' }, { label: global.t('recurring_invoices.frequency.every_2_hour'), value: '0 */2 * * *' },
{ label: 'Every day at midnight ', value: '0 0 * * *' }, { label: global.t('recurring_invoices.frequency.every_day_at_midnight'), value: '0 0 * * *' },
{ label: 'Every Week', value: '0 0 * * 0' }, { label: global.t('recurring_invoices.frequency.every_week'), value: '0 0 * * 0' },
{ label: 'Every 15 days at midnight', value: '0 5 */15 * *' }, { label: global.t('recurring_invoices.frequency.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: global.t('recurring_invoices.frequency.on_the_first_day_of_every_month_at_midnight'), value: '0 0 1 * *' },
{ label: 'Every 6 Month', value: '0 0 1 */6 *' }, { label: global.t('recurring_invoices.frequency.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: global.t('recurring_invoices.frequency.every_year_on_the_first_day_of_january_at_midnight'), value: '0 0 1 1 *' },
{ label: 'Custom', value: 'CUSTOM' }, { label: global.t('recurring_invoices.frequency.custom'), value: 'CUSTOM' },
], ],
}), }),

View File

@@ -1,8 +1,10 @@
import Guid from 'guid' import Guid from 'guid'
import recurringInvoiceItemStub from './recurring-invoice-item' import recurringInvoiceItemStub from './recurring-invoice-item'
import taxStub from './tax' import taxStub from './tax'
import { useI18n } from 'vue-i18n'
export default function () { export default function () {
const { t } = useI18n()
return { return {
currency: null, currency: null,
customer: null, customer: null,
@@ -42,7 +44,7 @@ export default function () {
fields: [], fields: [],
invoices: [], invoices: [],
selectedNote: null, selectedNote: null,
selectedFrequency: { label: 'Every Week', value: '0 0 * * 0' }, selectedFrequency: { label: t('recurring_invoices.frequency.every_week'), value: '0 0 * * 0' },
selectedInvoice: null, selectedInvoice: null,
} }
} }

View File

@@ -231,7 +231,7 @@
v$.currentCustomer.confirm_password.$errors[0].$message v$.currentCustomer.confirm_password.$errors[0].$message
" "
:content-loading="isFetchingInitialData" :content-loading="isFetchingInitialData"
label="Confirm Password" :label="$t('customers.confirm_password')"
> >
<BaseInput <BaseInput
v-model.trim="customerStore.currentCustomer.confirm_password" v-model.trim="customerStore.currentCustomer.confirm_password"

View File

@@ -128,17 +128,19 @@ import { useCustomerStore } from '@/scripts/admin/stores/customer'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useCompanyStore } from '@/scripts/admin/stores/company' import { useCompanyStore } from '@/scripts/admin/stores/company'
import ChartPlaceholder from './CustomerChartPlaceholder.vue' import ChartPlaceholder from './CustomerChartPlaceholder.vue'
import { useI18n } from 'vue-i18n'
const companyStore = useCompanyStore() const companyStore = useCompanyStore()
const customerStore = useCustomerStore() const customerStore = useCustomerStore()
const utils = inject('utils') const utils = inject('utils')
const { t } = useI18n()
const route = useRoute() const route = useRoute()
let isLoading = ref(false) let isLoading = ref(false)
let chartData = reactive({}) let chartData = reactive({})
let data = 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') let selectedYear = ref('This year')
const getChartExpenses = computed(() => { const getChartExpenses = computed(() => {

View File

@@ -7,17 +7,20 @@
<BaseDescriptionList> <BaseDescriptionList>
<BaseDescriptionListItem <BaseDescriptionListItem
v-if="selectedViewCustomer.name"
:content-loading="contentLoading" :content-loading="contentLoading"
:label="$t('customers.display_name')" :label="$t('customers.display_name')"
:value="selectedViewCustomer?.name" :value="selectedViewCustomer?.name"
/> />
<BaseDescriptionListItem <BaseDescriptionListItem
v-if="selectedViewCustomer.contact_name"
:content-loading="contentLoading" :content-loading="contentLoading"
:label="$t('customers.primary_contact_name')" :label="$t('customers.primary_contact_name')"
:value="selectedViewCustomer?.contact_name" :value="selectedViewCustomer?.contact_name"
/> />
<BaseDescriptionListItem <BaseDescriptionListItem
v-if="selectedViewCustomer.email"
:content-loading="contentLoading" :content-loading="contentLoading"
:label="$t('customers.email')" :label="$t('customers.email')"
:value="selectedViewCustomer?.email" :value="selectedViewCustomer?.email"
@@ -36,11 +39,13 @@
/> />
<BaseDescriptionListItem <BaseDescriptionListItem
v-if="selectedViewCustomer.phone"
:content-loading="contentLoading" :content-loading="contentLoading"
:label="$t('customers.phone_number')" :label="$t('customers.phone_number')"
:value="selectedViewCustomer?.phone" :value="selectedViewCustomer?.phone"
/> />
<BaseDescriptionListItem <BaseDescriptionListItem
v-if="selectedViewCustomer.website"
:content-loading="contentLoading" :content-loading="contentLoading"
:label="$t('customers.website')" :label="$t('customers.website')"
:value="selectedViewCustomer?.website" :value="selectedViewCustomer?.website"
@@ -89,8 +94,8 @@
v-if="field.type === 'Switch'" v-if="field.type === 'Switch'"
class="text-sm font-bold leading-5 text-black non-italic" class="text-sm font-bold leading-5 text-black non-italic"
> >
<span v-if="field.default_answer === 1"> Yes </span> <span v-if="field.default_answer === 1"> {{ $t('general.yes') }} </span>
<span v-else> No </span> <span v-else> {{ $t('general.no') }} </span>
</p> </p>
<p v-else class="text-sm font-bold leading-5 text-black non-italic"> <p v-else class="text-sm font-bold leading-5 text-black non-italic">
{{ field.default_answer }} {{ field.default_answer }}

View File

@@ -156,13 +156,16 @@ import LineChart from '@/scripts/admin/components/charts/LineChart.vue'
import ChartPlaceholder from './DashboardChartPlaceholder.vue' import ChartPlaceholder from './DashboardChartPlaceholder.vue'
import abilities from '@/scripts/admin/stub/abilities' import abilities from '@/scripts/admin/stub/abilities'
import { useUserStore } from '@/scripts/admin/stores/user' import { useUserStore } from '@/scripts/admin/stores/user'
import { useI18n } from 'vue-i18n'
const dashboardStore = useDashboardStore() const dashboardStore = useDashboardStore()
const companyStore = useCompanyStore() const companyStore = useCompanyStore()
const { t } = useI18n()
const utils = inject('utils') const utils = inject('utils')
const userStore = useUserStore() 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') const selectedYear = ref('This year')
watch( watch(

View File

@@ -21,7 +21,7 @@
:icon-component="CustomerIcon" :icon-component="CustomerIcon"
:loading="!dashboardStore.isDashboardDataLoaded" :loading="!dashboardStore.isDashboardDataLoaded"
route="/admin/customers" 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 }} {{ dashboardStore.stats.totalCustomerCount }}
</DashboardStatsItem> </DashboardStatsItem>
@@ -32,7 +32,7 @@
:icon-component="InvoiceIcon" :icon-component="InvoiceIcon"
:loading="!dashboardStore.isDashboardDataLoaded" :loading="!dashboardStore.isDashboardDataLoaded"
route="/admin/invoices" 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 }} {{ dashboardStore.stats.totalInvoiceCount }}
</DashboardStatsItem> </DashboardStatsItem>
@@ -43,7 +43,7 @@
:icon-component="EstimateIcon" :icon-component="EstimateIcon"
:loading="!dashboardStore.isDashboardDataLoaded" :loading="!dashboardStore.isDashboardDataLoaded"
route="/admin/estimates" 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 }} {{ dashboardStore.stats.totalEstimateCount }}
</DashboardStatsItem> </DashboardStatsItem>

View File

@@ -95,7 +95,7 @@
v-if="hasAtleastOneEstimateAbility()" v-if="hasAtleastOneEstimateAbility()"
#cell-actions="{ row }" #cell-actions="{ row }"
> >
<EstimateDropdown :row="row" :table="estimateTableComponent" /> <EstimateDropdown :row="row.data" :table="estimateTableComponent" />
</template> </template>
</BaseTable> </BaseTable>
</div> </div>

View File

@@ -216,7 +216,7 @@
<template #cell-status="{ row }"> <template #cell-status="{ row }">
<BaseEstimateStatusBadge :status="row.data.status" class="px-3 py-1"> <BaseEstimateStatusBadge :status="row.data.status" class="px-3 py-1">
{{ row.data.status }} <BaseEstimateStatusLabel :status="row.data.status"/>
</BaseEstimateStatusBadge> </BaseEstimateStatusBadge>
</template> </template>
@@ -249,6 +249,7 @@ import abilities from '@/scripts/admin/stub/abilities'
import ObservatoryIcon from '@/scripts/components/icons/empty/ObservatoryIcon.vue' import ObservatoryIcon from '@/scripts/components/icons/empty/ObservatoryIcon.vue'
import EstimateDropDown from '@/scripts/admin/components/dropdowns/EstimateIndexDropdown.vue' import EstimateDropDown from '@/scripts/admin/components/dropdowns/EstimateIndexDropdown.vue'
import SendEstimateModal from '@/scripts/admin/components/modal-components/SendEstimateModal.vue' import SendEstimateModal from '@/scripts/admin/components/modal-components/SendEstimateModal.vue'
import BaseEstimateStatusLabel from "@/scripts/components/base/BaseEstimateStatusLabel.vue";
const estimateStore = useEstimateStore() const estimateStore = useEstimateStore()
const dialogStore = useDialogStore() const dialogStore = useDialogStore()
@@ -258,12 +259,12 @@ const tableComponent = ref(null)
const { t } = useI18n() const { t } = useI18n()
const showFilters = ref(false) const showFilters = ref(false)
const status = ref([ const status = ref([
'DRAFT', {label: t('estimates.draft'), value: 'DRAFT'},
'SENT', {label: t('estimates.sent'), value: 'SENT'},
'VIEWED', {label: t('estimates.viewed'), value: 'VIEWED'},
'EXPIRED', {label: t('estimates.expired'), value: 'EXPIRED'},
'ACCEPTED', {label: t('estimates.accepted'), value: 'ACCEPTED'},
'REJECTED', {label: t('estimates.rejected'), value: 'REJECTED'},
]) ])
const isRequestOngoing = ref(true) const isRequestOngoing = ref(true)

View File

@@ -212,7 +212,7 @@
:status="estimate.status" :status="estimate.status"
class="px-1 text-xs" class="px-1 text-xs"
> >
{{ estimate.status }} <BaseEstimateStatusLabel :status="estimate.status" />
</BaseEstimateStatusBadge> </BaseEstimateStatusBadge>
</div> </div>

View File

@@ -278,19 +278,19 @@ const expenseColumns = computed(() => {
}, },
{ {
key: 'expense_date', key: 'expense_date',
label: 'Date', label: t('expenses.date'),
thClass: 'extra', thClass: 'extra',
tdClass: 'font-medium text-gray-900', tdClass: 'font-medium text-gray-900',
}, },
{ {
key: 'name', key: 'name',
label: 'Category', label: t('expenses.category'),
thClass: 'extra', thClass: 'extra',
tdClass: 'cursor-pointer font-medium text-primary-500', tdClass: 'cursor-pointer font-medium text-primary-500',
}, },
{ key: 'user_name', label: 'Customer' }, { key: 'user_name', label: t('expenses.customer') },
{ key: 'notes', label: 'Note' }, { key: 'notes', label: t('expenses.note') },
{ key: 'amount', label: 'Amount' }, { key: 'amount', label: t('expenses.amount') },
{ {
key: 'actions', key: 'actions',
sortable: false, sortable: false,

View File

@@ -9,7 +9,7 @@
/> />
<BaseWizard <BaseWizard
:steps="7" :steps="8"
:current-step="currentStepNumber" :current-step="currentStepNumber"
@click="onNavClick" @click="onNavClick"
> >
@@ -20,6 +20,7 @@
<script> <script>
import { ref } from 'vue' import { ref } from 'vue'
import Step0SetLanguage from './Step0SetLanguage.vue'
import Step1RequirementsCheck from './Step1RequirementsCheck.vue' import Step1RequirementsCheck from './Step1RequirementsCheck.vue'
import Step2PermissionCheck from './Step2PermissionCheck.vue' import Step2PermissionCheck from './Step2PermissionCheck.vue'
import Step3DatabaseConfig from './Step3DatabaseConfig.vue' import Step3DatabaseConfig from './Step3DatabaseConfig.vue'
@@ -34,6 +35,7 @@ import { useRouter } from 'vue-router'
export default { export default {
components: { components: {
step_0: Step0SetLanguage,
step_1: Step1RequirementsCheck, step_1: Step1RequirementsCheck,
step_2: Step2PermissionCheck, step_2: Step2PermissionCheck,
step_3: Step3DatabaseConfig, step_3: Step3DatabaseConfig,
@@ -45,11 +47,12 @@ export default {
}, },
setup() { setup() {
let stepComponent = ref('step_1') let stepComponent = ref('step_0')
let currentStepNumber = ref(1) let currentStepNumber = ref(1)
const router = useRouter() const router = useRouter()
const installationStore = useInstallationStore() const installationStore = useInstallationStore()
const { global } = window.i18n
checkCurrentProgress() checkCurrentProgress()
@@ -61,6 +64,10 @@ export default {
return return
} }
if(typeof res.data.profile_language === 'string') {
global.locale.value = res.data.profile_language
}
let dbstep = parseInt(res.data.profile_complete) let dbstep = parseInt(res.data.profile_complete)
if (dbstep) { if (dbstep) {
@@ -93,7 +100,7 @@ export default {
currentStepNumber.value++ currentStepNumber.value++
if (currentStepNumber.value <= 8) { if (currentStepNumber.value <= 9) {
stepComponent.value = 'step_' + currentStepNumber.value stepComponent.value = 'step_' + currentStepNumber.value
} }
} }

View File

@@ -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>

View File

@@ -36,11 +36,13 @@ export default {
const database_connection = ref('mysql') const database_connection = ref('mysql')
const isSaving = ref(false) const isSaving = ref(false)
const { t } = useI18n() const { t } = useI18n()
const { global } = window.i18n
const notificationStore = useNotificationStore() const notificationStore = useNotificationStore()
const installationStore = useInstallationStore() const installationStore = useInstallationStore()
const databaseData = computed(() => { const databaseData = computed(() => {
installationStore.currentDataBaseData.app_locale = global.locale.value
return installationStore.currentDataBaseData return installationStore.currentDataBaseData
}) })
@@ -75,6 +77,12 @@ export default {
emit('next', 3) emit('next', 3)
let language = {
profile_language: global.locale.value,
}
await installationStore.addInstallationLanguage(language)
notificationStore.showNotification({ notificationStore.showNotification({
type: 'success', type: 'success',
message: t('wizard.success.' + res.data.success), message: t('wizard.success.' + res.data.success),

View File

@@ -18,17 +18,15 @@
</BaseInputGroup> </BaseInputGroup>
</div> </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"> <ul class="w-full text-gray-600 list-disc list-inside">
<li class="text-sm leading-8"> <li class="text-sm leading-8">
App domain should not contain {{ $t('wizard.verify_domain.notes.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">https://</b> {{ $t('wizard.verify_domain.notes.or') }}
<b class="inline-block px-1 bg-gray-100 rounded-sm">http</b> in front of <b class="inline-block px-1 bg-gray-100 rounded-sm">http</b> {{ $t('wizard.verify_domain.notes.in_front') }}
the domain.
</li> </li>
<li class="text-sm leading-8"> <li class="text-sm leading-8">
If you're accessing the website on a different port, please mention the {{ $t('wizard.verify_domain.notes.if_you') }}
port. For example:
<b class="inline-block px-1 bg-gray-100">localhost:8080</b> <b class="inline-block px-1 bg-gray-100">localhost:8080</b>
</li> </li>
</ul> </ul>

View File

@@ -115,7 +115,7 @@
<BaseMultiselect <BaseMultiselect
v-model="currentPreferences.fiscal_year" v-model="currentPreferences.fiscal_year"
:content-loading="isFetchingInitialData" :content-loading="isFetchingInitialData"
:options="globalStore.fiscalYears" :options="fiscalYearsList"
label="key" label="key"
value-prop="value" value-prop="value"
:placeholder="$t('settings.preferences.select_financial_year')" :placeholder="$t('settings.preferences.select_financial_year')"
@@ -174,6 +174,14 @@ const router = useRouter()
isFetchingInitialData.value = true isFetchingInitialData.value = true
const fiscalYearsList = computed(() => {
return globalStore.fiscalYears.map((item) => {
return Object.assign({}, item, {
key: t(item.key),
})
})
})
const options = reactive([ const options = reactive([
{ {
title: tm('settings.customization.invoices.allow'), title: tm('settings.customization.invoices.allow'),

View File

@@ -225,7 +225,7 @@
<!-- Invoice status --> <!-- Invoice status -->
<template #cell-status="{ row }"> <template #cell-status="{ row }">
<BaseInvoiceStatusBadge :status="row.data.status" class="px-3 py-1"> <BaseInvoiceStatusBadge :status="row.data.status" class="px-3 py-1">
{{ row.data.status }} <BaseInvoiceStatusLabel :status="row.data.status" />
</BaseInvoiceStatusBadge> </BaseInvoiceStatusBadge>
</template> </template>
@@ -249,7 +249,7 @@
:status="row.data.paid_status" :status="row.data.paid_status"
class="px-1 py-0.5 ml-2" class="px-1 py-0.5 ml-2"
> >
{{ row.data.paid_status }} <BaseInvoiceStatusLabel :status="row.data.paid_status" />
</BasePaidStatusBadge> </BasePaidStatusBadge>
</div> </div>
</template> </template>
@@ -277,6 +277,7 @@ import { debouncedWatch } from '@vueuse/core'
import MoonwalkerIcon from '@/scripts/components/icons/empty/MoonwalkerIcon.vue' import MoonwalkerIcon from '@/scripts/components/icons/empty/MoonwalkerIcon.vue'
import InvoiceDropdown from '@/scripts/admin/components/dropdowns/InvoiceIndexDropdown.vue' import InvoiceDropdown from '@/scripts/admin/components/dropdowns/InvoiceIndexDropdown.vue'
import SendInvoiceModal from '@/scripts/admin/components/modal-components/SendInvoiceModal.vue' import SendInvoiceModal from '@/scripts/admin/components/modal-components/SendInvoiceModal.vue'
import BaseInvoiceStatusLabel from "@/scripts/components/base/BaseInvoiceStatusLabel.vue";
// Stores // Stores
const invoiceStore = useInvoiceStore() const invoiceStore = useInvoiceStore()
const dialogStore = useDialogStore() const dialogStore = useDialogStore()
@@ -291,12 +292,21 @@ const showFilters = ref(false)
const status = ref([ const status = ref([
{ {
label: 'Status', label: t('invoices.status'),
options: ['DRAFT', 'DUE', 'SENT', 'VIEWED', 'COMPLETED'], 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', label: t('invoices.paid_status'),
options: ['UNPAID', 'PAID', 'PARTIALLY_PAID'], options: [
{label: t('invoices.unpaid'), value: 'UNPAID'},
{label: t('invoices.paid'), value: 'PAID'},
{label: t('invoices.partially_paid'), value: 'PARTIALLY_PAID'}],
}, },
, ,
]) ])

View File

@@ -454,7 +454,7 @@ onSearched = debounce(onSearched, 500)
:status="invoice.status" :status="invoice.status"
class="px-1 text-xs" class="px-1 text-xs"
> >
{{ invoice.status }} <BaseInvoiceStatusLabel :status="invoice.status" />
</BaseEstimateStatusBadge> </BaseEstimateStatusBadge>
</div> </div>

View File

@@ -101,7 +101,7 @@
target="_blank" target="_blank"
> >
<BaseButton variant="primary-outline" type="button"> <BaseButton variant="primary-outline" type="button">
Sign up & Get Token {{ $t('modules.sign_up_and_get_token') }}
</BaseButton> </BaseButton>
</a> </a>
</div> </div>

View File

@@ -99,7 +99,7 @@
:label="$t('payments.invoice')" :label="$t('payments.invoice')"
:help-text=" :help-text="
selectedInvoice selectedInvoice
? `Due Amount: ${ ? `${t('payments.amount_due')}: ${
paymentStore.currentPayment.maxPayableAmount / 100 paymentStore.currentPayment.maxPayableAmount / 100
}` }`
: '' : ''

View File

@@ -251,7 +251,7 @@ const paymentColumns = computed(() => {
{ key: 'payment_number', label: t('payments.payment_number') }, { key: 'payment_number', label: t('payments.payment_number') },
{ key: 'name', label: t('payments.customer') }, { key: 'name', label: t('payments.customer') },
{ key: 'payment_mode', label: t('payments.payment_mode') }, { 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: 'amount', label: t('payments.amount') },
{ {
key: 'actions', key: 'actions',

View File

@@ -230,7 +230,7 @@
:status="row.data.status" :status="row.data.status"
class="px-3 py-1" class="px-3 py-1"
> >
{{ row.data.status }} <BaseRecurringInvoiceStatusLabel :status="row.data.status" />
</BaseRecurringInvoiceStatusBadge> </BaseRecurringInvoiceStatusBadge>
</template> </template>
@@ -276,7 +276,11 @@ const userStore = useUserStore()
const table = ref(null) const table = ref(null)
const { t } = useI18n() const { t } = useI18n()
const showFilters = ref(false) 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 isRequestOngoing = ref(true)
const activeTab = ref('recurring-invoices.all') const activeTab = ref('recurring-invoices.all')
const router = useRouter() const router = useRouter()

View File

@@ -129,7 +129,7 @@
:invalid="v.status.$error" :invalid="v.status.$error"
:placeholder="$t('recurring_invoices.select_a_status')" :placeholder="$t('recurring_invoices.select_a_status')"
value-prop="value" value-prop="value"
label="value" label="key"
/> />
</BaseInputGroup> </BaseInputGroup>
@@ -186,6 +186,7 @@ import { useDebounceFn } from '@vueuse/core'
import { useRecurringInvoiceStore } from '@/scripts/admin/stores/recurring-invoice' import { useRecurringInvoiceStore } from '@/scripts/admin/stores/recurring-invoice'
import { computed, onMounted, reactive, ref, watch } from 'vue' import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import ExchangeRateConverter from '@/scripts/admin/components/estimate-invoice-common/ExchangeRateConverter.vue' import ExchangeRateConverter from '@/scripts/admin/components/estimate-invoice-common/ExchangeRateConverter.vue'
@@ -207,13 +208,14 @@ const props = defineProps({
const route = useRoute() const route = useRoute()
const recurringInvoiceStore = useRecurringInvoiceStore() const recurringInvoiceStore = useRecurringInvoiceStore()
const globalStore = useGlobalStore() const globalStore = useGlobalStore()
const { t } = useI18n()
const isLoadingNextDate = ref(false) const isLoadingNextDate = ref(false)
const limits = reactive([ const limits = reactive([
{ label: 'None', value: 'NONE' }, { label: t('recurring_invoices.limit.none'), value: 'NONE' },
{ label: 'Date', value: 'DATE' }, { label: t('recurring_invoices.limit.date'), value: 'DATE' },
{ label: 'Count', value: 'COUNT' }, { label: t('recurring_invoices.limit.count'), value: 'COUNT' },
]) ])
const isCustomFrequency = computed(() => { const isCustomFrequency = computed(() => {
@@ -226,9 +228,17 @@ const isCustomFrequency = computed(() => {
const getStatusOptions = computed(() => { const getStatusOptions = computed(() => {
if (props.isEdit) { 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( watch(

View File

@@ -27,10 +27,15 @@
/> />
</template> </template>
<!-- Invoice date -->
<template #cell-invoice_date="{ row }">
{{ row.data.formatted_invoice_date }}
</template>
<!-- Invoice status --> <!-- Invoice status -->
<template #cell-status="{ row }"> <template #cell-status="{ row }">
<BaseInvoiceStatusBadge :status="row.data.status" class="px-3 py-1"> <BaseInvoiceStatusBadge :status="row.data.status" class="px-3 py-1">
{{ row.data.status }} <BaseInvoiceStatusLabel :status="row.data.status" />
</BaseInvoiceStatusBadge> </BaseInvoiceStatusBadge>
</template> </template>

View File

@@ -293,7 +293,7 @@ onSearched = debounce(onSearched, 500)
:status="invoice.status" :status="invoice.status"
class="px-1 text-xs" class="px-1 text-xs"
> >
{{ invoice.status }} <BaseRecurringInvoiceStatusLabel :status="invoice.status" />
</BaseRecurringInvoiceStatusBadge> </BaseRecurringInvoiceStatusBadge>
</div> </div>

View File

@@ -156,7 +156,16 @@ const dateRange = reactive([
]) ])
const selectedRange = ref(dateRange[2]) 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') const selectedType = ref('By Customer')
let range = ref(new Date()) let range = ref(new Date())
let url = ref(null) let url = ref(null)

View File

@@ -30,6 +30,10 @@
<span class="text-xs text-gray-500"> ({{ row.data.slug }})</span> <span class="text-xs text-gray-500"> ({{ row.data.slug }})</span>
</template> </template>
<template #cell-model_type="{ row }">
{{ getModelType(row.data.model_type) }}
</template>
<template #cell-is_required="{ row }"> <template #cell-is_required="{ row }">
<BaseBadge <BaseBadge
:bg-color=" :bg-color="
@@ -147,4 +151,21 @@ function addCustomField() {
async function refreshTable() { async function refreshTable() {
table.value && table.value.refresh() 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> </script>

View File

@@ -31,7 +31,7 @@
.color .color
" "
> >
{{ row.data.set_as_default ? 'Yes' : 'No'.replace('_', ' ') }} {{ row.data.set_as_default ? $t('general.yes') : $t('general.no').replace('_', ' ') }}
</BaseBadge> </BaseBadge>
</template> </template>

View File

@@ -31,6 +31,9 @@
:load-data="refreshTable" :load-data="refreshTable"
/> />
</template> </template>
<template #cell-type="{ row }">
{{ getLabelNote(row.data.type) }}
</template>
</BaseTable> </BaseTable>
</BaseSettingCard> </BaseSettingCard>
</template> </template>
@@ -113,4 +116,17 @@ async function openNoteSelectModal() {
async function refreshTable() { async function refreshTable() {
table.value && table.value.refresh() 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> </script>

View File

@@ -95,7 +95,7 @@
<BaseMultiselect <BaseMultiselect
v-model="settingsForm.fiscal_year" v-model="settingsForm.fiscal_year"
:content-loading="isFetchingInitialData" :content-loading="isFetchingInitialData"
:options="globalStore.config.fiscal_years" :options="fiscalYearsList"
label="key" label="key"
value-prop="value" value-prop="value"
:invalid="v$.fiscal_year.$error" :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( watch(
() => settingsForm.carbon_date_format, () => settingsForm.carbon_date_format,
(val) => { (val) => {

View File

@@ -43,7 +43,7 @@
border-t border-b border-gray-200 border-solid border-t border-b border-gray-200 border-solid
" "
> >
Component {{ $t('settings.customization.component') }}
</th> </th>
<th <th
class=" class="
@@ -57,7 +57,7 @@
border-t border-b border-gray-200 border-solid border-t border-b border-gray-200 border-solid
" "
> >
Parameter {{ $t('settings.customization.Parameter') }}
</th> </th>
<th <th
class=" class="
@@ -126,7 +126,7 @@
variant="white" variant="white"
@click.prevent="removeComponent(element)" @click.prevent="removeComponent(element)"
> >
Remove {{ $t('general.remove') }}
<template #left="slotProps"> <template #left="slotProps">
<BaseIcon <BaseIcon
name="XIcon" name="XIcon"

View File

@@ -3,7 +3,7 @@
<div> <div>
<BaseInput <BaseInput
v-model="name" v-model="name"
placeholder="Search..." :placeholder="$t('global_search.search')"
container-class="!rounded" container-class="!rounded"
class="h-8 md:h-9 !rounded" class="h-8 md:h-9 !rounded"
@input="onSearch" @input="onSearch"

View File

@@ -32,7 +32,7 @@
:status="invoice.paid_status" :status="invoice.paid_status"
class="px-3 py-1" class="px-3 py-1"
> >
{{ invoice.paid_status }} <BaseInvoiceStatusLabel :status="invoice.paid_status" />
</BaseInvoiceStatusBadge> </BaseInvoiceStatusBadge>
</dd> </dd>
</div> </div>

View File

@@ -181,11 +181,11 @@
</ul> </ul>
<slot v-if="noOptions" name="nooptions"> <slot v-if="noOptions" name="nooptions">
<div :class="classList.noOptions" v-html="noOptionsText"></div> <div :class="classList.noOptions" v-html="$t('general.multiselect.the_list_is_empty')"></div>
</slot> </slot>
<slot v-if="noResults" name="noresults"> <slot v-if="noResults" name="noresults">
<div :class="classList.noResults" v-html="noResultsText"></div> <div :class="classList.noResults" v-html="$t('general.multiselect.no_results_found')"></div>
</slot> </slot>
<slot name="afterlist" :options="fo"> </slot> <slot name="afterlist" :options="fo"> </slot>

View File

@@ -49,8 +49,39 @@
<script type="text/babel" setup> <script type="text/babel" setup>
import FlatPickr from 'vue-flatpickr-component' import FlatPickr from 'vue-flatpickr-component'
import 'flatpickr/dist/flatpickr.css' import 'flatpickr/dist/flatpickr.css'
import { Arabic } from 'flatpickr/dist/l10n/ar.js'
import { Czech } from 'flatpickr/dist/l10n/cs.js'
import { German } from 'flatpickr/dist/l10n/de.js'
import { Greek } from 'flatpickr/dist/l10n/gr.js'
import { english } from 'flatpickr/dist/l10n/default.js'
import { Spanish } from 'flatpickr/dist/l10n/es.js'
import { Persian } from 'flatpickr/dist/l10n/fa.js'
import { Finnish } from 'flatpickr/dist/l10n/fi.js'
import { French } from 'flatpickr/dist/l10n/fr.js'
import { Hindi } from 'flatpickr/dist/l10n/hi.js'
import { Croatian } from 'flatpickr/dist/l10n/hr.js'
import { Indonesian } from 'flatpickr/dist/l10n/id.js'
import { Italian } from 'flatpickr/dist/l10n/it.js'
import { Japanese } from 'flatpickr/dist/l10n/ja.js'
import { Korean } from 'flatpickr/dist/l10n/ko.js'
import { Lithuanian } from 'flatpickr/dist/l10n/lt.js'
import { Latvian } from 'flatpickr/dist/l10n/lv.js'
import { Dutch } from 'flatpickr/dist/l10n/nl.js'
import { Polish } from 'flatpickr/dist/l10n/pl.js'
import { Portuguese } from 'flatpickr/dist/l10n/pt.js'
import { Romanian } from 'flatpickr/dist/l10n/ro.js'
import { Russian } from 'flatpickr/dist/l10n/ru.js'
import { Slovak } from 'flatpickr/dist/l10n/sk.js'
import { Slovenian } from 'flatpickr/dist/l10n/sl.js'
import { Serbian } from 'flatpickr/dist/l10n/sr.js'
import { Swedish } from 'flatpickr/dist/l10n/sv.js'
import { Thai } from 'flatpickr/dist/l10n/th.js'
import { Turkish } from 'flatpickr/dist/l10n/tr.js'
import { Vietnamese } from 'flatpickr/dist/l10n/vn.js'
import { Mandarin } from 'flatpickr/dist/l10n/zh.js'
import { computed, reactive, watch, ref, useSlots } from 'vue' import { computed, reactive, watch, ref, useSlots } from 'vue'
import { useCompanyStore } from '@/scripts/admin/stores/company' import { useCompanyStore } from '@/scripts/admin/stores/company'
import { useUserStore } from '@/scripts/admin/stores/user'
const dp = ref(null) const dp = ref(null)
@@ -104,10 +135,112 @@ const slots = useSlots()
const companyStore = useCompanyStore() const companyStore = useCompanyStore()
const userStore = useUserStore()
//Localize Flatpicker stuff
const lang = userStore.currentUserSettings.language
let fpLocale = null
switch(lang){
case 'ar':
fpLocale = Arabic;
break;
case 'cs':
fpLocale = Czech;
break;
case 'de':
fpLocale = German;
break;
case 'el':
fpLocale = Greek;
break;
case 'en':
fpLocale = english;
break;
case 'es':
fpLocale = Spanish;
break;
case 'fa':
fpLocale = Persian;
break;
case 'fi':
fpLocale = Finnish;
break;
case 'fr':
fpLocale = French;
break;
case 'hi':
fpLocale = Hindi;
break;
case 'hr':
fpLocale = Croatian;
break;
case 'id':
fpLocale = Indonesian;
break;
case 'it':
fpLocale = Italian;
break;
case 'ja':
fpLocale = Japanese;
break;
case 'ko':
fpLocale = Korean;
break;
case 'lt':
fpLocale = Lithuanian;
break;
case 'lv':
fpLocale = Latvian;
break;
case 'nl':
fpLocale = Dutch;
break;
case 'pl':
fpLocale = Polish;
break;
case 'pt':
case 'pt_BR':
fpLocale = Portuguese;
break;
case 'ro':
fpLocale = Romanian;
break;
case 'ru':
fpLocale = Russian;
break;
case 'sk':
fpLocale = Slovak;
break;
case 'sl':
fpLocale = Slovenian;
break;
case 'sr':
fpLocale = Serbian;
break;
case 'sv':
fpLocale = Swedish;
break;
case 'th':
fpLocale = Thai;
break;
case 'tr':
fpLocale = Turkish;
break;
case 'vi':
fpLocale = Vietnamese;
break;
case 'zh':
fpLocale = Mandarin;
break;
default:
fpLocale = english;
}
let config = reactive({ let config = reactive({
altInput: true, altInput: true,
enableTime: props.enableTime, enableTime: props.enableTime,
time_24hr: props.time24hr, time_24hr: props.time24hr,
locale: fpLocale
}) })
const date = computed({ const date = computed({

View File

@@ -0,0 +1,39 @@
<template>
{{ labelStatus }}
</template>
<script setup>
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps({
status: {
type: String,
required: false,
default: '',
},
})
const labelStatus = computed(() => {
switch (props.status) {
case 'DRAFT':
return t('estimates.draft')
case 'SENT':
return t('estimates.sent')
case 'VIEWED':
return t('estimates.viewed')
case 'EXPIRED':
return t('estimates.expired')
case 'ACCEPTED':
return t('estimates.accepted')
case 'REJECTED':
return t('estimates.rejected')
case 'DECLINED':
return t('estimates.declined')
default:
return props.status
}
})
</script>

View File

@@ -71,7 +71,7 @@
class="h-6 mb-2 text-xl leading-6 text-gray-400" class="h-6 mb-2 text-xl leading-6 text-gray-400"
/> />
<p class="text-xs leading-4 text-center text-gray-400"> <p class="text-xs leading-4 text-center text-gray-400">
Drag a file here or {{ $t('general.file_upload.drag_a_file') }}
<a <a
class=" class="
cursor-pointer cursor-pointer
@@ -83,9 +83,9 @@
href="#" href="#"
@click.prevent.stop="onBrowse" @click.prevent.stop="onBrowse"
> >
browse {{ $t('general.file_upload.browse') }}
</a> </a>
to choose a file {{ $t('general.file_upload.to_choose') }}
</p> </p>
<p class="text-xs leading-4 text-center text-gray-400 mt-2"> <p class="text-xs leading-4 text-center text-gray-400 mt-2">
{{ recommendedText }} {{ recommendedText }}

View File

@@ -0,0 +1,43 @@
<template>
{{ labelStatus }}
</template>
<script setup>
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps({
status: {
type: String,
required: false,
default: '',
},
})
const labelStatus = computed(() => {
switch (props.status) {
case 'DRAFT':
return t('general.draft')
case 'SENT':
return t('general.sent')
case 'VIEWED':
return t('invoices.viewed')
case 'COMPLETED':
return t('invoices.completed')
case 'DUE':
return t('general.due')
case 'OVERDUE':
return t('invoices.overdue')
case 'UNPAID':
return t('invoices.unpaid')
case 'PARTIALLY_PAID':
return t('invoices.partially_paid')
case 'PAID':
return t('invoices.paid')
default:
return props.status
}
})
</script>

View File

@@ -0,0 +1,31 @@
<template>
{{ labelStatus }}
</template>
<script setup>
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps({
status: {
type: String,
required: false,
default: '',
},
})
const labelStatus = computed(() => {
switch (props.status) {
case 'COMPLETED':
return t('recurring_invoices.complete')
case 'ON_HOLD':
return t('recurring_invoices.on_hold')
case 'ACTIVE':
return t('recurring_invoices.active')
default:
return props.status
}
})
</script>

View File

@@ -128,7 +128,7 @@
class="w-6 h-6 text-gray-400" class="w-6 h-6 text-gray-400"
/> />
<span class="block mt-1">{{ noResultsMessage }}</span> <span class="block mt-1">{{ $t('general.no_data_found') }}</span>
</div> </div>
<BaseTablePagination <BaseTablePagination

View File

@@ -35,7 +35,7 @@
" "
@click="pageClicked(pagination.currentPage - 1)" @click="pageClicked(pagination.currentPage - 1)"
> >
Previous {{ $t('general.pagination.previous') }}
</a> </a>
<a <a
href="#" href="#"
@@ -60,13 +60,13 @@
" "
@click="pageClicked(pagination.currentPage + 1)" @click="pageClicked(pagination.currentPage + 1)"
> >
Next {{ $t('general.pagination.next') }}
</a> </a>
</div> </div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"> <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div> <div>
<p class="text-sm text-gray-700"> <p class="text-sm text-gray-700">
Showing {{ $t('general.pagination.showing') }}
{{ ' ' }} {{ ' ' }}
<span <span
v-if="pagination.limit && pagination.currentPage" v-if="pagination.limit && pagination.currentPage"
@@ -77,7 +77,7 @@
}} }}
</span> </span>
{{ ' ' }} {{ ' ' }}
to {{ $t('general.pagination.to') }}
{{ ' ' }} {{ ' ' }}
<span <span
v-if="pagination.limit && pagination.currentPage" v-if="pagination.limit && pagination.currentPage"
@@ -96,13 +96,13 @@
</span> </span>
</span> </span>
{{ ' ' }} {{ ' ' }}
of {{ $t('general.pagination.of') }}
{{ ' ' }} {{ ' ' }}
<span v-if="pagination.totalCount" class="font-medium"> <span v-if="pagination.totalCount" class="font-medium">
{{ pagination.totalCount }} {{ pagination.totalCount }}
</span> </span>
{{ ' ' }} {{ ' ' }}
results {{ $t('general.pagination.results') }}
</p> </p>
</div> </div>
<div> <div>

View File

@@ -38,7 +38,7 @@
'inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium', 'inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium',
]" ]"
> >
{{ item.title }} {{ $t(item.title) }}
</router-link> </router-link>
</div> </div>
</div> </div>
@@ -141,7 +141,7 @@
'block pl-3 pr-4 py-2 border-l-4 text-base font-medium', 'block pl-3 pr-4 py-2 border-l-4 text-base font-medium',
]" ]"
:aria-current="item.current ? 'page' : undefined" :aria-current="item.current ? 'page' : undefined"
>{{ item.title }} >{{ $t(item.title) }}
</router-link> </router-link>
</div> </div>
<div class="pt-4 pb-3 border-t border-gray-200"> <div class="pt-4 pb-3 border-t border-gray-200">
@@ -185,7 +185,7 @@
: 'border-transparent text-gray-600 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-800', : 'border-transparent text-gray-600 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-800',
'block pl-3 pr-4 py-2 border-l-4 text-base font-medium', 'block pl-3 pr-4 py-2 border-l-4 text-base font-medium',
]" ]"
>{{ item.title }}</router-link >{{ $t(item.title) }}</router-link
> >
</div> </div>
</div> </div>

View File

@@ -2,6 +2,7 @@ import { handleError } from '@/scripts/customer/helpers/error-handling'
import { useUserStore } from './user' import { useUserStore } from './user'
const { defineStore } = window.pinia const { defineStore } = window.pinia
import axios from 'axios' import axios from 'axios'
const { global } = window.i18n
export const useGlobalStore = defineStore({ export const useGlobalStore = defineStore({
id: 'CustomerPortalGlobalStore', id: 'CustomerPortalGlobalStore',
state: () => ({ state: () => ({
@@ -29,7 +30,12 @@ export const useGlobalStore = defineStore({
this.currency = response.data.data.currency this.currency = response.data.data.currency
this.enabledModules = response.data.meta.modules this.enabledModules = response.data.meta.modules
Object.assign(userStore.userForm, response.data.data) Object.assign(userStore.userForm, response.data.data)
window.i18n.locale = response.data.default_language
if(typeof global.locale !== 'string') {
global.locale.value =
response.data.meta.current_company_language || 'en'
}
this.isAppLoaded = true this.isAppLoaded = true
resolve(response) resolve(response)
}) })

View File

@@ -19,7 +19,7 @@
:icon-component="InvoiceIcon" :icon-component="InvoiceIcon"
:loading="!globalStore.getDashboardDataLoaded" :loading="!globalStore.getDashboardDataLoaded"
:route="{ name: 'invoices.dashboard' }" :route="{ name: 'invoices.dashboard' }"
:label="$t('dashboard.cards.invoices')" :label="(dashboardStore.invoiceCount <= 1 ? $t('dashboard.cards.invoices', 1) : $t('dashboard.cards.invoices', 2))"
> >
{{ dashboardStore.invoiceCount }} {{ dashboardStore.invoiceCount }}
</DashboardStatsItem> </DashboardStatsItem>
@@ -29,7 +29,7 @@
:icon-component="EstimateIcon" :icon-component="EstimateIcon"
:loading="!globalStore.getDashboardDataLoaded" :loading="!globalStore.getDashboardDataLoaded"
:route="{ name: 'estimates.dashboard' }" :route="{ name: 'estimates.dashboard' }"
:label="$t('dashboard.cards.estimates')" :label="(dashboardStore.estimateCount <= 1 ? $t('dashboard.cards.estimates', 1) : $t('dashboard.cards.estimates', 2))"
> >
{{ dashboardStore.estimateCount }} {{ dashboardStore.estimateCount }}
</DashboardStatsItem> </DashboardStatsItem>
@@ -40,7 +40,7 @@
:icon-component="PaymentIcon" :icon-component="PaymentIcon"
:loading="!globalStore.getDashboardDataLoaded" :loading="!globalStore.getDashboardDataLoaded"
:route="{ name: 'payments.dashboard' }" :route="{ name: 'payments.dashboard' }"
:label="$t('dashboard.cards.payments')" :label="(dashboardStore.paymentCount <= 1 ? $t('dashboard.cards.payments', 1 ) : $t('dashboard.cards.payments', 2))"
> >
{{ dashboardStore.paymentCount }} {{ dashboardStore.paymentCount }}
</DashboardStatsItem> </DashboardStatsItem>

View File

@@ -34,7 +34,7 @@
<template #cell-paid_status="{ row }"> <template #cell-paid_status="{ row }">
<BasePaidStatusBadge :status="row.data.paid_status"> <BasePaidStatusBadge :status="row.data.paid_status">
{{ row.data.paid_status }} <BaseInvoiceStatusLabel :status="row.data.paid_status" />
</BasePaidStatusBadge> </BasePaidStatusBadge>
</template> </template>
@@ -80,7 +80,7 @@
</template> </template>
<template #cell-status="{ row }"> <template #cell-status="{ row }">
<BaseEstimateStatusBadge :status="row.data.status" class="px-3 py-1"> <BaseEstimateStatusBadge :status="row.data.status" class="px-3 py-1">
{{ row.data.status }} <BaseEstimateStatusLabel :status="row.data.status" />
</BaseEstimateStatusBadge> </BaseEstimateStatusBadge>
</template> </template>
<template #cell-total="{ row }"> <template #cell-total="{ row }">

View File

@@ -108,7 +108,7 @@
<template #cell-status="{ row }"> <template #cell-status="{ row }">
<BaseEstimateStatusBadge :status="row.data.status" class="px-3 py-1"> <BaseEstimateStatusBadge :status="row.data.status" class="px-3 py-1">
{{ row.data.status }} <BaseEstimateStatusLabel :status="row.data.status" />
</BaseEstimateStatusBadge> </BaseEstimateStatusBadge>
</template> </template>
@@ -156,12 +156,12 @@ let showFilters = ref(false)
let isFetchingInitialData = ref(true) let isFetchingInitialData = ref(true)
const status = ref([ const status = ref([
'DRAFT', {label: t('estimates.draft'), value: 'DRAFT'},
'SENT', {label: t('estimates.sent'), value: 'SENT'},
'VIEWED', {label: t('estimates.viewed'), value: 'VIEWED'},
'EXPIRED', {label: t('estimates.expired'), value: 'EXPIRED'},
'ACCEPTED', {label: t('estimates.accepted'), value: 'ACCEPTED'},
'REJECTED', {label: t('estimates.rejected'), value: 'REJECTED'},
]) ])
const filters = reactive({ const filters = reactive({
status: '', status: '',
@@ -240,7 +240,7 @@ function toggleFilter() {
async function fetchData({ page, sort }) { async function fetchData({ page, sort }) {
let data = { let data = {
status: filters.status, status: filters.status.value,
estimate_number: filters.estimate_number, estimate_number: filters.estimate_number,
from_date: filters.from_date, from_date: filters.from_date,
to_date: filters.to_date, to_date: filters.to_date,

View File

@@ -170,7 +170,7 @@
</div> </div>
<BaseEstimateStatusBadge :status="estimate.status"> <BaseEstimateStatusBadge :status="estimate.status">
{{ estimate.status }} <BaseEstimateStatusLabel :status="estimate.status" />
</BaseEstimateStatusBadge> </BaseEstimateStatusBadge>
</div> </div>

View File

@@ -110,7 +110,7 @@
<template #cell-status="{ row }"> <template #cell-status="{ row }">
<BaseInvoiceStatusBadge :status="row.data.status" class="px-3 py-1"> <BaseInvoiceStatusBadge :status="row.data.status" class="px-3 py-1">
{{ row.data.status }} <BaseInvoiceStatusLabel :status="row.data.status" />
</BaseInvoiceStatusBadge> </BaseInvoiceStatusBadge>
</template> </template>
@@ -119,7 +119,7 @@
:status="row.data.paid_status" :status="row.data.paid_status"
class="px-3 py-1" class="px-3 py-1"
> >
{{ row.data.paid_status }} <BaseInvoiceStatusLabel :status="row.data.paid_status" />
</BaseInvoiceStatusBadge> </BaseInvoiceStatusBadge>
</template> </template>
@@ -160,7 +160,13 @@ const route = useRoute()
const table = ref(null) const table = ref(null)
let isFetchingInitialData = ref(true) let isFetchingInitialData = ref(true)
let showFilters = ref(false) let showFilters = ref(false)
const status = ref(['DRAFT', 'DUE', 'SENT', 'VIEWED', 'COMPLETED']) const status = ref([
{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'}
])
const filters = reactive({ const filters = reactive({
status: '', status: '',
from_date: '', from_date: '',
@@ -247,7 +253,7 @@ function toggleFilter() {
async function fetchData({ page, sort }) { async function fetchData({ page, sort }) {
let data = { let data = {
status: filters.status, status: filters.status.value,
invoice_number: filters.invoice_number, invoice_number: filters.invoice_number,
from_date: filters.from_date, from_date: filters.from_date,
to_date: filters.to_date, to_date: filters.to_date,

View File

@@ -175,7 +175,7 @@
{{ invoice.invoice_number }} {{ invoice.invoice_number }}
</div> </div>
<BaseInvoiceStatusBadge :status="invoice.status"> <BaseInvoiceStatusBadge :status="invoice.status">
{{ invoice.status }} <BaseInvoiceStatusLabel :status="invoice.status" />
</BaseInvoiceStatusBadge> </BaseInvoiceStatusBadge>
</div> </div>

View File

@@ -19,7 +19,7 @@
{!! $data['body'] !!} {!! $data['body'] !!}
@if(!$data['attach']['data']) @if(!$data['attach']['data'])
@component('mail::button', ['url' => $data['url']]) @component('mail::button', ['url' => $data['url']])
View Estimate @lang('mail_view_estimate')
@endcomponent @endcomponent
@endif @endif
@endcomponent @endcomponent

View File

@@ -19,7 +19,7 @@
{!! $data['body'] !!} {!! $data['body'] !!}
@if(!$data['attach']['data']) @if(!$data['attach']['data'])
@component('mail::button', ['url' => $data['url']]) @component('mail::button', ['url' => $data['url']])
View Invoice @lang('mail_view_invoice')
@endcomponent @endcomponent
@endif @endif
@endcomponent @endcomponent

View File

@@ -19,7 +19,7 @@
{!! $data['body'] !!} {!! $data['body'] !!}
@if(!$data['attach']['data']) @if(!$data['attach']['data'])
@component('mail::button', ['url' => $data['url']]) @component('mail::button', ['url' => $data['url']])
View Payment @lang('mail_view_payment')
@endcomponent @endcomponent
@endif @endif
@endcomponent @endcomponent

View File

@@ -1,10 +1,9 @@
@component('mail::message') @component('mail::message')
{{ $data['user']['name'] }} viewed this Estimate. @lang('mail_viewed_estimate', ['name' => $data['user']['name']])
@component('mail::button', ['url' => url('/admin/estimates/'.$data['estimate']['id'].'/view')]) @component('mail::button', ['url' => url('/admin/estimates/'.$data['estimate']['id'].'/view')])@lang('mail_view_estimate')
View Estimate
@endcomponent @endcomponent
Thanks,<br> @lang('mail_thanks'),<br>
{{ config('app.name') }} {{ config('app.name') }}
@endcomponent @endcomponent

View File

@@ -1,10 +1,9 @@
@component('mail::message') @component('mail::message')
{{ $data['user']['name'] }} viewed this Invoice. @lang('mail_viewed_invoice', ['name' => $data['user']['name']])
@component('mail::button', ['url' => url('/admin/invoices/'.$data['invoice']['id'].'/view')]) @component('mail::button', ['url' => url('/admin/invoices/'.$data['invoice']['id'].'/view')])@lang('mail_view_invoice')
View Invoice
@endcomponent @endcomponent
Thanks,<br> @lang('mail_thanks'),<br>
{{ config('app.name') }} {{ config('app.name') }}
@endcomponent @endcomponent

View File

@@ -164,6 +164,10 @@ Route::prefix('/v1')->group(function () {
Route::post('/wizard-step', [OnboardingWizardController::class, 'updateStep']); Route::post('/wizard-step', [OnboardingWizardController::class, 'updateStep']);
Route::post('/wizard-language', [OnboardingWizardController::class, 'saveLanguage']);
Route::get('/languages', [LanguagesController::class, 'languages']);
Route::get('/requirements', [RequirementsController::class, 'requirements']); Route::get('/requirements', [RequirementsController::class, 'requirements']);
Route::get('/permissions', [FilePermissionsController::class, 'permissions']); Route::get('/permissions', [FilePermissionsController::class, 'permissions']);