diff --git a/app/Abstracts/Export.php b/app/Abstracts/Export.php
index 5508ebc87..3e4eb5adb 100644
--- a/app/Abstracts/Export.php
+++ b/app/Abstracts/Export.php
@@ -91,6 +91,6 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto
public function failed(\Throwable $exception): void
{
- $this->user->notify(new ExportFailed($exception));
+ $this->user->notify(new ExportFailed($exception->getMessage()));
}
}
diff --git a/app/Abstracts/Livewire/Document.php b/app/Abstracts/Livewire/Document.php
new file mode 100644
index 000000000..481faf6ab
--- /dev/null
+++ b/app/Abstracts/Livewire/Document.php
@@ -0,0 +1,1208 @@
+type = $type;
+ $this->imageEmptyPage = $this->getImageEmptyPage($type, $imageEmptyPage);
+ $this->textEmptyPage = $this->getTextEmptyPage($type, $textEmptyPage);
+ $this->textPage = $this->getTextPage($type, $textPage);
+ $this->textTitle = $this->getTextTitle($type, $textTitle);
+ $this->urlDocsPath = $this->getUrlDocsPath($type, $urlDocsPath);
+ $this->hideEmptyPage = $hideEmptyPage;
+
+ /* -- Top Buttons Start -- */
+ $this->checkPermissionCreate = $checkPermissionCreate;
+
+ $this->createRoute = $this->getCreateRoute($type, $createRoute);
+ $this->importRoute = $this->getImportRoute($importRoute);
+ $this->importRouteParameters = $this->getImportRouteParameters($type, $importRouteParameters);
+ $this->exportRoute = $this->getExportRoute($type, $exportRoute);
+
+ $this->hideCreate = $hideCreate;
+ $this->hideImport = $hideImport;
+ $this->hideExport = $hideExport;
+ /* -- Top Buttons End -- */
+
+ /* -- Card Header Start -- */
+ $this->textBulkAction = $this->getTextBulkAction($type, $textBulkAction);
+ $this->bulkActionClass = $bulkActionClass;
+ $this->bulkActions = $this->getBulkActions($type, $bulkActions, $bulkActionClass);
+
+ $this->bulkActionRouteParameters = $this->getBulkActionRouteParameters($type, $bulkActionRouteParameters);
+
+ $this->formCardHeaderRoute = $this->getRoute($type, $formCardHeaderRoute);
+
+ $this->searchStringModel = $this->getSearchStringModel($type, $searchStringModel);
+
+ $this->hideBulkAction = $hideBulkAction;
+ $this->hideSearchString = $hideSearchString;
+ /* -- Card Header End -- */
+
+ /* -- Card Body Start -- */
+ $this->textDocumentNumber = $this->getTextDocumentNumber($type, $textDocumentNumber);
+ $this->textContactName = $this->getTextContactName($type, $textContactName);
+ $this->textIssuedAt = $this->getTextIssuedAt($type, $textIssuedAt);
+ $this->textDueAt = $this->getTextDueAt($type, $textDueAt);
+ $this->textDocumentStatus = $this->getTextDocumentStatus($type, $textDocumentStatus);
+
+ $this->checkButtonReconciled = $checkButtonReconciled;
+ $this->checkButtonCancelled = $checkButtonCancelled;
+
+ $this->routeButtonShow = $this->getRouteButtonShow($type, $routeButtonShow);
+ $this->routeButtonEdit = $this->getRouteButtonEdit($type, $routeButtonEdit);
+ $this->routeButtonDuplicate = $this->getRouteButtonDuplicate($type, $routeButtonDuplicate);
+ $this->routeButtonCancelled = $this->getRouteButtonCancelled($type, $routeButtonCancelled);
+ $this->routeButtonDelete = $this->getRouteButtonDelete($type, $routeButtonDelete);
+
+ $this->textModalDelete = $this->getTextModalDelete($type, $textModalDelete);
+ $this->valueModalDelete = $valueModalDelete;
+
+ $this->hideBulkAction = $hideBulkAction;
+ $this->hideDocumentNumber = $hideDocumentNumber;
+ $this->hideContactName = $hideContactName;
+ $this->hideAmount = $hideAmount;
+ $this->hideIssuedAt = $hideIssuedAt;
+ $this->hideDueAt = $hideDueAt;
+ $this->hideStatus = $hideStatus;
+ $this->hideActions = $hideActions;
+
+ $this->class_count = 12;
+
+ $this->calculateClass();
+
+ $this->classBulkAction = $this->getClassBulkAction($type, $classBulkAction);
+ $this->classDocumentNumber = $this->getClassDocumentNumber($type, $classDocumentNumber);
+ $this->classContactName = $this->getClassContactName($type, $classContactName);
+ $this->classAmount = $this->getClassAmount($type, $classAmount);
+ $this->classIssuedAt = $this->getClassIssuedAt($type, $classIssuedAt);
+ $this->classDueAt = $this->getClassDueAt($type, $classDueAt);
+ $this->classStatus = $this->getClassStatus($type, $classStatus);
+ $this->classActions = $this->getClassActions($type, $classActions);
+
+ $this->hideButtonShow = $hideButtonShow;
+ $this->hideButtonEdit = $hideButtonEdit;
+ $this->hideButtonDuplicate = $hideButtonDuplicate;
+ $this->hideButtonCancel = $hideButtonCancel;
+ $this->hideButtonDelete = $hideButtonDelete;
+
+ $this->permissionCreate = $this->getPermissionCreate($type, $permissionCreate);
+ $this->permissionUpdate = $this->getPermissionUpdate($type, $permissionUpdate);
+ $this->permissionDelete = $this->getPermissionDelete($type, $permissionDelete);
+ /* -- Card Body End -- */
+
+ $this->limits = ($limits) ? $limits : ['10' => '10', '25' => '25', '50' => '50', '100' => '100'];
+ }
+
+ protected function getImageEmptyPage($type, $imageEmptyPage)
+ {
+ if (!empty($imageEmptyPage)) {
+ return $imageEmptyPage;
+ }
+
+ $image_empty_page = config('type.' . $type . '.image_empty_page');
+
+ if (!empty($image_empty_page)) {
+ return $image_empty_page;
+ }
+
+ $page = str_replace('-', '_', config('type.' . $type . '.route.prefix', 'invoices'));
+ $image_path = 'public/img/empty_pages/' . $page . '.png';
+
+ if ($alias = config('type.' . $type . '.alias')) {
+ $image_path = 'modules/' . Str::studly($alias) . '/Resources/assets/img/empty_pages/' . $page . '.png';
+ }
+
+ return $image_path;
+ }
+
+ protected function getTextEmptyPage($type, $textEmptyPage)
+ {
+ if (!empty($textEmptyPage)) {
+ return $textEmptyPage;
+ }
+
+ $page = str_replace('-', '_', config('type.' . $type . '.route.prefix', 'invoices'));
+
+ $translation = $this->getTextFromConfig($type, 'empty_page', 'empty.' . $page);
+
+ if (!empty($translation)) {
+ return $translation;
+ }
+
+ return 'general.empty.' . $page;
+ }
+
+ protected function getUrlDocsPath($type, $urlDocsPath)
+ {
+ if (!empty($urlDocsPath)) {
+ return $urlDocsPath;
+ }
+
+ $docs_path = config('type.' . $type . '.docs_path');
+
+ if (!empty($docs_path)) {
+ return $docs_path;
+ }
+
+ switch ($type) {
+ case 'bill':
+ case 'expense':
+ case 'purchase':
+ $docsPath = 'purchases/bills';
+ break;
+ default:
+ $docsPath = 'sales/invoices';
+ break;
+ }
+
+ return 'https://akaunting.com/docs/user-manual/' . $docsPath;
+ }
+
+ protected function getTextTitle($type, $textTitle)
+ {
+ if (!empty($textTitle)) {
+ return $textTitle;
+ }
+
+ $page = str_replace('-', '_', config('type.' . $type . '.route.prefix', 'invoices'));
+
+ $translation = $this->getTextFromConfig($type, 'title', $page);
+
+ if (!empty($translation)) {
+ return $translation;
+ }
+
+ return 'general.' . $page;
+ }
+
+ protected function getTextPage($type, $textPage)
+ {
+ if (!empty($textPage)) {
+ return $textPage;
+ }
+
+ $page = str_replace('-', '_', config('type.' . $type . '.route.prefix', 'invoices'));
+
+ $translation = $this->getTextFromConfig($type, 'page', $page);
+
+ if (!empty($translation)) {
+ return $translation;
+ }
+
+ return 'general.' . $page;
+ }
+
+ protected function getCreateRoute($type, $createRoute)
+ {
+ if (!empty($createRoute)) {
+ return $createRoute;
+ }
+
+ $route = $this->getRouteFromConfig($type, 'create');
+
+ if (!empty($route)) {
+ return $route;
+ }
+
+ return 'invoices.create';
+ }
+
+ protected function getImportRoute($importRoute)
+ {
+ if (!empty($importRoute)) {
+ return $importRoute;
+ }
+
+ $route = 'import.create';
+
+ return $route;
+ }
+
+ protected function getImportRouteParameters($type, $importRouteParameters)
+ {
+ if (!empty($importRouteParameters)) {
+ return $importRouteParameters;
+ }
+
+ $route = $this->getRouteFromConfig($type, 'import');
+
+ $alias = config('type.' . $type . '.alias');
+ $group = config('type.' . $type . '.group');
+
+ if (empty($group) && !empty($alias)){
+ $group = $alias;
+ } else if (empty($group) && empty($alias)) {
+ $group = 'sales';
+ }
+
+ $importRouteParameters = [
+ 'group' => $group,
+ 'type' => config('type.' . $type . '.route.prefix'),
+ 'route' => ($route) ? $route : 'invoices.import',
+ ];
+
+ return $importRouteParameters;
+ }
+
+ protected function getExportRoute($type, $exportRoute)
+ {
+ if (!empty($exportRoute)) {
+ return $exportRoute;
+ }
+
+ $route = $this->getRouteFromConfig($type, 'export');
+
+ if (!empty($route)) {
+ return $route;
+ }
+
+ return 'invoices.export';
+ }
+
+ protected function getRoute($type, $formCardHeaderRoute)
+ {
+ if (!empty($formCardHeaderRoute)) {
+ return $formCardHeaderRoute;
+ }
+
+ $route = $this->getRouteFromConfig($type, 'index');
+
+ if (!empty($route)) {
+ return $route;
+ }
+
+ return 'invoices.index';
+ }
+
+ protected function getSearchStringModel($type, $searchStringModel)
+ {
+ if (!empty($searchStringModel)) {
+ return $searchStringModel;
+ }
+
+ $search_string_model = config('type.' . $type . '.search_string_model');
+
+ if (!empty($search_string_model)) {
+ return $search_string_model;
+ }
+
+ if ($group = config('type.' . $type . '.group')) {
+ $group = Str::studly(Str::singular($group)) . '\\';
+ }
+
+ $prefix = Str::studly(Str::singular(config('type.' . $type . '.route.prefix')));
+
+ if ($alias = config('type.' . $type . '.alias')) {
+ $searchStringModel = 'Modules\\' . Str::studly($alias) .'\Models\\' . $group . $prefix;
+ } else {
+ $searchStringModel = 'App\Models\\' . $group . $prefix;
+ }
+
+ return $searchStringModel;
+ }
+
+ protected function getTextBulkAction($type, $textBulkAction)
+ {
+ if (!empty($textBulkAction)) {
+ return $textBulkAction;
+ }
+
+ $default_key = config('type.' . $type . '.translation.prefix');
+
+ $translation = $this->getTextFromConfig($type, 'bulk_action', $default_key, 'trans_choice');
+
+ if (!empty($translation)) {
+ return $translation;
+ }
+
+ return 'general.invoices';
+ }
+
+ protected function getBulkActions($type, $bulkActions, $bulkActionClass)
+ {
+ if (!empty($bulkActions)) {
+ return $bulkActions;
+ }
+
+ $bulk_actions = config('type.' . $type . '.bulk_actions');
+
+ if (!empty($bulk_actions)) {
+ return $bulk_actions;
+ }
+
+ $file_name = '';
+
+ if ($group = config('type.' . $type . '.group')) {
+ $file_name .= Str::studly($group) . '\\';
+ }
+
+ if ($prefix = config('type.' . $type . '.route.prefix')) {
+ $file_name .= Str::studly($prefix);
+ }
+
+ if ($alias = config('type.' . $type . '.alias')) {
+ $module = module($alias);
+
+ if (!$module instanceof Module) {
+ $b = new \stdClass();
+ $b->actions = [];
+
+ event(new BulkActionsAdding($b));
+
+ return $b->actions;
+ }
+
+ $bulkActionClass = 'Modules\\' . $module->getStudlyName() . '\BulkActions\\' . $file_name;
+ } else {
+ $bulkActionClass = 'App\BulkActions\\' . $file_name;
+ }
+
+ if (class_exists($bulkActionClass)) {
+ event(new BulkActionsAdding(app($bulkActionClass)));
+
+ $bulkActions = app($bulkActionClass)->actions;
+ } else {
+ $b = new \stdClass();
+ $b->actions = [];
+
+ event(new BulkActionsAdding($b));
+
+ $bulkActions = $b->actions;
+ }
+
+ return $bulkActions;
+ }
+
+ protected function getBulkActionRouteParameters($type, $bulkActionRouteParameters)
+ {
+ if (!empty($bulkActionRouteParameters)) {
+ return $bulkActionRouteParameters;
+ }
+
+ $group = config('type.' . $type . '.group');
+
+ if (!empty(config('type.' . $type . '.alias'))) {
+ $group = config('type.' . $type . '.alias');
+ }
+
+ $bulkActionRouteParameters = [
+ 'group' => $group,
+ 'type' => config('type.' . $type . '.route.prefix')
+ ];
+
+ return $bulkActionRouteParameters;
+ }
+
+ protected function getClassBulkAction($type, $classBulkAction)
+ {
+ if (!empty($classBulkAction)) {
+ return $classBulkAction;
+ }
+
+ $class = $this->getClassFromConfig($type, 'bulk_action');
+
+ if (!empty($class)) {
+ return $class;
+ }
+
+ return 'col-sm-2 col-md-1 col-lg-1 col-xl-1 d-none d-sm-block';
+ }
+
+ protected function getTextDocumentNumber($type, $textDocumentNumber)
+ {
+ if (!empty($textDocumentNumber)) {
+ return $textDocumentNumber;
+ }
+
+ $translation = $this->getTextFromConfig($type, 'document_number', 'numbers');
+
+ if (!empty($translation)) {
+ return $translation;
+ }
+
+ return 'general.numbers';
+ }
+
+ protected function getClassDocumentNumber($type, $classDocumentNumber)
+ {
+ if (!empty($classDocumentNumber)) {
+ return $classDocumentNumber;
+ }
+
+ if ($classDocumentNumber = $this->getClass('classDocumentNumber')) {
+ return $classDocumentNumber;
+ }
+
+ $class = $this->getClassFromConfig($type, 'document_number');
+
+ if (!empty($class)) {
+ return $class;
+ }
+
+ return 'col-md-3 col-lg-2 col-xl-2 d-none d-md-block';
+ }
+
+ protected function getTextContactName($type, $textContactName)
+ {
+ if (!empty($textContactName)) {
+ return $textContactName;
+ }
+
+ $default_key = Str::plural(config('type.' . $type . '.contact_type'), 2);
+
+ $translation = $this->getTextFromConfig($type, 'contact_name', $default_key, 'trans_choice');
+
+ if (!empty($translation)) {
+ return $translation;
+ }
+
+ return 'general.customers';
+ }
+
+ protected function getClassContactName($type, $classContactName)
+ {
+ if (!empty($classContactName)) {
+ return $classContactName;
+ }
+
+ if ($classContactName = $this->getClass('classContactName')) {
+ return $classContactName;
+ }
+
+ $class = $this->getClassFromConfig($type, 'contact_name');
+
+ if (!empty($class)) {
+ return $class;
+ }
+
+ return 'col-xs-4 col-sm-4 col-md-4 col-lg-2 col-xl-2 text-left';
+ }
+
+ protected function getClassAmount($type, $classAmount)
+ {
+ if (!empty($classAmount)) {
+ return $classAmount;
+ }
+
+ if ($classAmount = $this->getClass('classAmount')) {
+ return $classAmount;
+ }
+
+ $class = $this->getClassFromConfig($type, 'amount');
+
+ if (!empty($class)) {
+ return $class;
+ }
+
+ return 'col-xs-4 col-sm-4 col-md-3 col-lg-2 col-xl-2 text-right';
+ }
+
+ protected function getTextIssuedAt($type, $textIssuedAt)
+ {
+ if (!empty($textIssuedAt)) {
+ return $textIssuedAt;
+ }
+
+ switch ($type) {
+ case 'bill':
+ case 'expense':
+ case 'purchase':
+ $default_key = 'bill_date';
+ break;
+ default:
+ $default_key = 'invoice_date';
+ break;
+ }
+
+ $translation = $this->getTextFromConfig($type, 'issued_at', $default_key);
+
+ if (!empty($translation)) {
+ return $translation;
+ }
+
+ return 'invoices.invoice_date';
+ }
+
+ protected function getClassIssuedAt($type, $classIssuedAt)
+ {
+ if (!empty($classIssuedAt)) {
+ return $classIssuedAt;
+ }
+
+ if ($classIssuedAt = $this->getClass('classIssuedAt')) {
+ return $classIssuedAt;
+ }
+
+ $class = $this->getClassFromConfig($type, 'issued_at');
+
+ if (!empty($class)) {
+ return $class;
+ }
+
+ return 'col-lg-2 col-xl-2 d-none d-lg-block text-left';
+ }
+
+ protected function getTextDueAt($type, $textDueAt)
+ {
+ if (!empty($textDueAt)) {
+ return $textDueAt;
+ }
+
+ $translation = $this->getTextFromConfig($type, 'due_at', 'due_date');
+
+ if (!empty($translation)) {
+ return $translation;
+ }
+
+ return 'invoices.due_date';
+ }
+
+ protected function getClassDueAt($type, $classDueAt)
+ {
+ if (!empty($classDueAt)) {
+ return $classDueAt;
+ }
+
+ $class = $this->getClassFromConfig($type, 'due_at');
+
+ if (!empty($class)) {
+ return $class;
+ }
+
+ if ($classDueAt = $this->getClass('classDueAt')) {
+ return $classDueAt;
+ }
+
+ return 'col-lg-2 col-xl-2 d-none d-lg-block text-left';
+ }
+
+ protected function getTextDocumentStatus($type, $textDocumentStatus)
+ {
+ if (!empty($textDocumentStatus)) {
+ return $textDocumentStatus;
+ }
+
+ $translation = $this->getTextFromConfig($type, 'document_status', 'statuses.');
+
+ if (!empty($translation)) {
+ return $translation;
+ }
+
+ $alias = config('type.' . $type . '.alias');
+
+ if (!empty($alias)) {
+ $translation = $alias . '::' . config('type.' . $type . '.translation.prefix') . '.statuses';
+
+ if (is_array(trans($translation))) {
+ return $translation . '.';
+ }
+ }
+
+ return 'documents.statuses.';
+ }
+
+ protected function getClassStatus($type, $classStatus)
+ {
+ if (!empty($classStatus)) {
+ return $classStatus;
+ }
+
+ if ($classStatus = $this->getClass('classStatus')) {
+ return $classStatus;
+ }
+
+ $class = $this->getClassFromConfig($type, 'status');
+
+ if (!empty($class)) {
+ return $class;
+ }
+
+ return 'col-lg-1 col-xl-1 d-none d-lg-block text-center';
+ }
+
+ protected function getClassActions($type, $classActions)
+ {
+ if (!empty($classActions)) {
+ return $classActions;
+ }
+
+ if ($classActions = $this->getClass('classActions')) {
+ return $classActions;
+ }
+
+ $class = $this->getClassFromConfig($type, 'actions');
+
+ if (!empty($class)) {
+ return $class;
+ }
+
+ return 'col-xs-4 col-sm-2 col-md-2 col-lg-1 col-xl-1 text-center';
+ }
+
+ protected function getRouteButtonShow($type, $routeButtonShow)
+ {
+ if (!empty($routeButtonShow)) {
+ return $routeButtonShow;
+ }
+
+ //example route parameter.
+ $parameter = 1;
+
+ $route = $this->getRouteFromConfig($type, 'show', $parameter);
+
+ if (!empty($route)) {
+ return $route;
+ }
+
+ return 'invoices.show';
+ }
+
+ protected function getRouteButtonEdit($type, $routeButtonEdit)
+ {
+ if (!empty($routeButtonEdit)) {
+ return $routeButtonEdit;
+ }
+
+ //example route parameter.
+ $parameter = 1;
+
+ $route = $this->getRouteFromConfig($type, 'edit', $parameter);
+
+ if (!empty($route)) {
+ return $route;
+ }
+
+ return 'invoices.edit';
+ }
+
+ protected function getRouteButtonDuplicate($type, $routeButtonDuplicate)
+ {
+ if (!empty($routeButtonDuplicate)) {
+ return $routeButtonDuplicate;
+ }
+
+ //example route parameter.
+ $parameter = 1;
+
+ $route = $this->getRouteFromConfig($type, 'duplicate', $parameter);
+
+ if (!empty($route)) {
+ return $route;
+ }
+
+ return 'invoices.duplicate';
+ }
+
+ protected function getRouteButtonCancelled($type, $routeButtonCancelled)
+ {
+ if (!empty($routeButtonCancelled)) {
+ return $routeButtonCancelled;
+ }
+
+ //example route parameter.
+ $parameter = 1;
+
+ $route = $this->getRouteFromConfig($type, 'cancelled', $parameter);
+
+ if (!empty($route)) {
+ return $route;
+ }
+
+ return 'invoices.cancelled';
+ }
+
+ protected function getRouteButtonDelete($type, $routeButtonDelete)
+ {
+ if (!empty($routeButtonDelete)) {
+ return $routeButtonDelete;
+ }
+
+ //example route parameter.
+ $parameter = 1;
+
+ $route = $this->getRouteFromConfig($type, 'destroy', $parameter);
+
+ if (!empty($route)) {
+ return $route;
+ }
+
+ return 'invoices.destroy';
+ }
+
+ protected function getTextModalDelete($type, $textModalDelete)
+ {
+ if (!empty($textModalDelete)) {
+ return $textModalDelete;
+ }
+
+ if ($alias = config('type.' . $type . '.alias')) {
+ return $alias . '::general.' . Str::plural(str_replace('-', '_', $type));
+ }
+
+ return '';
+ }
+
+ protected function getPermissionCreate($type, $permissionCreate)
+ {
+ if (!empty($permissionCreate)) {
+ return $permissionCreate;
+ }
+
+ $permissionCreate = $this->getPermissionFromConfig($type, 'create');
+
+ return $permissionCreate;
+ }
+
+ protected function getPermissionUpdate($type, $permissionUpdate)
+ {
+ if (!empty($permissionUpdate)) {
+ return $permissionUpdate;
+ }
+
+ $permissionUpdate = $this->getPermissionFromConfig($type, 'update');
+
+ return $permissionUpdate;
+ }
+
+ protected function getPermissionDelete($type, $permissionDelete)
+ {
+ if (!empty($permissionDelete)) {
+ return $permissionDelete;
+ }
+
+ $permissionDelete = $this->getPermissionFromConfig($type, 'delete');
+
+ return $permissionDelete;
+ }
+
+ protected function calculateClass()
+ {
+ $hides = [
+ 'BulkAction' => '1',
+ 'DocumentNumber' => '1',
+ 'ContactName' => '2',
+ 'Amount' => '2',
+ 'IssuedAt' => '2',
+ 'DueAt' => '2',
+ 'Status' => '1',
+ 'Actions' => '1',
+ ];
+
+ foreach ($hides as $hide => $count) {
+ if ($this->{'hide'. $hide}) {
+ $this->class_count -= $count;
+ }
+ }
+ }
+
+ protected function getClass($type)
+ {
+ $hide_count = 12 - $this->class_count;
+
+ if (empty($hide_count)) {
+ //return false;
+ }
+
+ $class = false;
+
+ switch($type) {
+ case 'classDocumentNumber':
+ switch ($hide_count) {
+ case 1:
+ $class = 'col-md-3 col-lg-2 col-xl-2 d-none d-md-block';
+ $this->class_count++;
+ break;
+ case 2:
+ $class = 'col-md-4 col-lg-3 col-xl-3 d-none d-md-block';
+ $this->class_count += 2;
+ break;
+ case 3:
+ $class = 'col-md-5 col-lg-4 col-xl-4 d-none d-md-block';
+ $this->class_count += 3;
+ break;
+ }
+ }
+
+ return $class;
+ }
+
+ public function getTextFromConfig($type, $config_key, $default_key = '', $trans_type = 'trans')
+ {
+ $translation = '';
+
+ // if set config translation config_key
+ if ($translation = config('type.' . $type . '.translation.' . $config_key)) {
+ return $translation;
+ }
+
+ $alias = config('type.' . $type . '.alias');
+ $prefix = config('type.' . $type . '.translation.prefix');
+
+ if (!empty($alias)) {
+ $alias .= '::';
+ }
+
+ // This magic trans key..
+ $translations = [
+ 'general' => $alias . 'general.' . $default_key,
+ 'prefix' => $alias . $prefix . '.' . $default_key,
+ 'config_general' => $alias . 'general.' . $config_key,
+ 'config_prefix' => $alias . $prefix . '.' . $config_key,
+ ];
+
+ switch ($trans_type) {
+ case 'trans':
+ foreach ($translations as $trans) {
+ if (trans($trans) !== $trans) {
+ return $trans;
+ }
+ }
+
+ break;
+ case 'trans_choice':
+ foreach ($translations as $trans_choice) {
+ if (trans_choice($trans_choice, 1) !== $trans_choice) {
+ return $trans_choice;
+ }
+ }
+
+ break;
+ }
+
+ return $translation;
+ }
+
+ public function getRouteFromConfig($type, $config_key, $config_parameters = [])
+ {
+ $route = '';
+
+ // if set config trasnlation config_key
+ if ($route = config('type.' . $type . '.route.' . $config_key)) {
+ return $route;
+ }
+
+ $alias = config('type.' . $type . '.alias');
+ $prefix = config('type.' . $type . '.route.prefix');
+
+ // if use module set module alias
+ if (!empty($alias)) {
+ $route .= $alias . '.';
+ }
+
+ if (!empty($prefix)) {
+ $route .= $prefix . '.';
+ }
+
+ $route .= $config_key;
+
+ try {
+ route($route, $config_parameters);
+ } catch (\Exception $e) {
+ try {
+ $route = Str::plural($type, 2) . '.' . $config_key;
+
+ route($route, $config_parameters);
+ } catch (\Exception $e) {
+ $route = '';
+ }
+ }
+
+ return $route;
+ }
+
+ public function getPermissionFromConfig($type, $config_key)
+ {
+ $permission = '';
+
+ // if set config trasnlation config_key
+ if ($permission = config('type.' . $type . '.permission.' . $config_key)) {
+ return $permission;
+ }
+
+ $alias = config('type.' . $type . '.alias');
+ $group = config('type.' . $type . '.group');
+ $prefix = config('type.' . $type . '.permission.prefix');
+
+ $permission = $config_key . '-';
+
+ // if use module set module alias
+ if (!empty($alias)) {
+ $permission .= $alias . '-';
+ }
+
+ // if controller in folder it must
+ if (!empty($group)) {
+ $permission .= $group . '-';
+ }
+
+ $permission .= $prefix;
+
+ return $permission;
+ }
+
+ public function getHideFromConfig($type, $config_key)
+ {
+ $hide = false;
+
+ $hides = config('type.' . $type . '.hide');
+
+ if (!empty($hides) && (in_array($config_key, $hides))) {
+ $hide = true;
+ }
+
+ return $hide;
+ }
+
+ public function getClassFromConfig($type, $config_key)
+ {
+ $class_key = 'type.' . $type . '.class.' . $config_key;
+
+ return config($class_key, '');
+ }
+
+ public function getCategoryFromConfig($type)
+ {
+ $category_type = '';
+
+ // if set config trasnlation config_key
+ if ($category_type = config('type.' . $type . '.category_type')) {
+ return $category_type;
+ }
+
+ switch ($type) {
+ case 'bill':
+ case 'expense':
+ case 'purchase':
+ $category_type = 'expense';
+ break;
+ case 'item':
+ $category_type = 'item';
+ break;
+ case 'other':
+ $category_type = 'other';
+ break;
+ case 'transfer':
+ $category_type = 'transfer';
+ break;
+ default:
+ $category_type = 'income';
+ break;
+ }
+
+ return $category_type;
+ }
+}
diff --git a/app/Http/Controllers/Common/Notifications.php b/app/Http/Controllers/Common/Notifications.php
index d86ee06c3..e19dd8c83 100644
--- a/app/Http/Controllers/Common/Notifications.php
+++ b/app/Http/Controllers/Common/Notifications.php
@@ -18,9 +18,7 @@ class Notifications extends Controller
*/
public function index()
{
- $notifications = setting('notifications');
-
- return view('common.notifications.index', compact('notifications'));
+ return view('common.notifications.index');
}
/**
@@ -28,11 +26,19 @@ class Notifications extends Controller
*
* @return Response
*/
- public function show($path, $id)
+ public function readAll()
{
- $notification = setting('notifications.' . $path . '.' . $id);
+ $notifications = user()->unreadNotifications;
- return view('common.notifications.show', compact('notification'));
+ foreach ($notifications as $notification) {
+ $notification->markAsRead();
+ }
+
+ $message = trans('messages.success.duplicated', ['type' => trans_choice('general.items', 1)]);
+
+ flash($message)->success();
+
+ return redirect()->route('dashboard');
}
/**
diff --git a/app/Http/Livewire/Common/Notifications/Exports.php b/app/Http/Livewire/Common/Notifications/Exports.php
new file mode 100644
index 000000000..7f457c082
--- /dev/null
+++ b/app/Http/Livewire/Common/Notifications/Exports.php
@@ -0,0 +1,87 @@
+ '$notifications',
+ ];
+
+ public function markRead($notification_id)
+ {
+ $notification = DatabaseNotification::find($notification_id);
+ $data = $notification->getAttribute('data');
+
+ $notification->markAsRead();
+
+ $this->dispatchBrowserEvent('mark-read', [
+ 'type' => 'export',
+ 'message' => trans('notifications.messages.mark_read', ['type' => $data['file_name']]),
+ ]);
+ }
+
+ public function markReadAll()
+ {
+ $notifications = $this->getNotifications();
+
+ foreach ($notifications as $notification) {
+ $notification->markAsRead();
+ }
+
+ $this->dispatchBrowserEvent('mark-read-all', [
+ 'type' => 'export',
+ 'message' => trans('notifications.messages.mark_read_all', ['type' => trans('general.export')]),
+ ]);
+ }
+
+ public function render()
+ {
+ $limit = 5;
+
+ $notifications = $this->getNotifications($limit);
+
+ return view('livewire.common.notifications.exports', compact('notifications'));
+ }
+
+ protected function getNotifications($limit = false)
+ {
+ $query = user()->notifications()->unread()
+ ->where('type', 'App\Notifications\Common\ExportCompleted')
+ ->orWhere('type', 'App\Notifications\Common\ExportFailed');
+
+ if ($limit) {
+ $notifications = $query->paginate($limit);
+ } else {
+ $notifications = $query->get();
+ }
+
+ if ($notifications->items()) {
+ $items = [];
+
+ foreach ($notifications->items() as $key => $notification) {
+ $data = (object) $notification->getAttribute('data');
+ $data->notification_id = $notification->getAttribute('id');
+
+ $items[] = $data;
+ }
+
+ $notifications->setCollection(Collection::make($items));
+ }
+
+ return $notifications;
+ }
+
+ public function paginationView()
+ {
+ return 'vendor.livewire.default';
+ }
+}
diff --git a/app/Http/Livewire/Common/Notifications/Imports.php b/app/Http/Livewire/Common/Notifications/Imports.php
new file mode 100644
index 000000000..23f58a906
--- /dev/null
+++ b/app/Http/Livewire/Common/Notifications/Imports.php
@@ -0,0 +1,28 @@
+notifications()->unread()
+ ->where('type', 'App\Notifications\Common\ImportCompleted')
+ ->orWhere('type', 'App\Notifications\Common\ImportFailed')
+ ->paginate($limit);
+
+ return view('livewire.common.notifications.imports', compact('notifications'));
+ }
+
+ public function paginationView()
+ {
+ return 'vendor.livewire.default';
+ }
+}
diff --git a/app/Http/Livewire/Common/Notifications/NewApps.php b/app/Http/Livewire/Common/Notifications/NewApps.php
new file mode 100644
index 000000000..421c4e296
--- /dev/null
+++ b/app/Http/Livewire/Common/Notifications/NewApps.php
@@ -0,0 +1,13 @@
+notifications()->unread()->where('type', 'App\Notifications\Sale\Invoice')->paginate($limit);
+
+ $documents->setCollection(Collection::make([]));
+
+ return view('livewire.common.notifications.recurring', compact('documents'));
+ }
+
+ public function paginationView()
+ {
+ return 'vendor.livewire.default';
+ }
+}
diff --git a/app/Http/Livewire/Common/Notifications/Reminder.php b/app/Http/Livewire/Common/Notifications/Reminder.php
new file mode 100644
index 000000000..62ce4cdc5
--- /dev/null
+++ b/app/Http/Livewire/Common/Notifications/Reminder.php
@@ -0,0 +1,78 @@
+getAttribute('data');
+
+ $notification->markAsRead();
+
+ $this->dispatchBrowserEvent('mark-read', [
+ 'type' => $this->type,
+ 'message' => trans('notifications.messages.mark_read', ['type' => $data[$this->type . '_number']]),
+ ]);
+ }
+
+ public function markReadAll()
+ {
+ $type = config('type.' . $this->type . '.notification.class');
+
+ $notifications = user()->notifications()->unread()
+ ->where('type', $type)
+ ->get();
+
+ foreach ($notifications as $notification) {
+ $notification->markAsRead();
+ }
+
+ $this->dispatchBrowserEvent('mark-read-all', [
+ 'type' => $this->type,
+ 'message' => trans('notifications.messages.mark_read', ['type' => trans_choice('general.' . Str::plural($this->type) , 2)]),
+ ]);
+ }
+
+ public function render()
+ {
+ $limit = 10;
+
+ $type = config('type.' . $this->type . '.notification.class');
+
+ $documents = user()->notifications()->unread()
+ ->where('type', $type)
+ ->paginate($limit);
+
+ $items = [];
+
+ foreach ($documents->items() as $key => $document) {
+ $data = $document->getAttribute('data');
+
+ $item = Document::invoice()->where('id', $data['invoice_id'])->first();
+
+ $item->notification_id = $document->getAttribute('id');
+
+ $items[] = $item;
+ }
+
+ $documents->setCollection(Collection::make($items));
+
+ return view('livewire.common.notifications.reminder', compact('documents'));
+ }
+
+ public function paginationView()
+ {
+ return 'vendor.livewire.default';
+ }
+}
diff --git a/app/Http/ViewComposers/Header.php b/app/Http/ViewComposers/Header.php
index 5c19e1bd6..e52b1c62e 100644
--- a/app/Http/ViewComposers/Header.php
+++ b/app/Http/ViewComposers/Header.php
@@ -20,7 +20,7 @@ class Header
{
$user = user();
- $invoices = $bills = [];
+ $invoices = $bills = $exports = $imports = [];
$updates = $notifications = 0;
$company = null;
@@ -42,6 +42,22 @@ class Header
$data = $unread->getAttribute('data');
switch ($unread->getAttribute('type')) {
+ case 'App\Notifications\Common\ExportCompleted':
+ $exports['completed'][$data['file_name']] = $data['download_url'];
+ $notifications++;
+ break;
+ case 'App\Notifications\Common\ExportFailed':
+ $exports['failed'][] = $data['message'];
+ $notifications++;
+ break;
+ case 'App\Notifications\Common\ImportCompleted':
+ $import_completed[$data['bill_id']] = $data['amount'];
+ $notifications++;
+ break;
+ case 'App\Notifications\Common\ImportFailed':
+ $import_failed[$data['bill_id']] = $data['amount'];
+ $notifications++;
+ break;
case 'App\Notifications\Purchase\Bill':
$bills[$data['bill_id']] = $data['amount'];
$notifications++;
@@ -64,6 +80,8 @@ class Header
$view->with([
'user' => $user,
'notifications' => $notifications,
+ 'exports' => $exports,
+ 'imports' => $imports,
'bills' => $bills,
'invoices' => $invoices,
'company' => $company,
diff --git a/app/Jobs/Common/CreateMediableForExport.php b/app/Jobs/Common/CreateMediableForExport.php
index 0522e2c36..3b509b086 100644
--- a/app/Jobs/Common/CreateMediableForExport.php
+++ b/app/Jobs/Common/CreateMediableForExport.php
@@ -15,16 +15,19 @@ class CreateMediableForExport extends JobShouldQueue
protected $file_name;
+ protected $translation;
+
/**
* Create a new job instance.
*
* @param $user
* @param $file_name
*/
- public function __construct($user, $file_name)
+ public function __construct($user, $file_name, $translation)
{
$this->user = $user;
$this->file_name = $file_name;
+ $this->translation = $translation;
$this->onQueue('jobs');
}
@@ -42,7 +45,7 @@ class CreateMediableForExport extends JobShouldQueue
$download_url = route('uploads.download', ['id' => $media->id, 'company_id' => company_id()]);
- $this->user->notify(new ExportCompleted($download_url));
+ $this->user->notify(new ExportCompleted($this->translation, $this->file_name, $download_url));
}
public function getQueuedMedia()
diff --git a/app/Notifications/Common/ExportCompleted.php b/app/Notifications/Common/ExportCompleted.php
index effeb4a21..5fbaeb9a0 100644
--- a/app/Notifications/Common/ExportCompleted.php
+++ b/app/Notifications/Common/ExportCompleted.php
@@ -11,6 +11,10 @@ class ExportCompleted extends Notification implements ShouldQueue
{
use Queueable;
+ protected $translation;
+
+ protected $file_name;
+
protected $download_url;
/**
@@ -18,8 +22,10 @@ class ExportCompleted extends Notification implements ShouldQueue
*
* @param string $download_url
*/
- public function __construct($download_url)
+ public function __construct($translation, $file_name, $download_url)
{
+ $this->translation = $translation;
+ $this->file_name = $file_name;
$this->download_url = $download_url;
$this->onQueue('notifications');
@@ -33,7 +39,7 @@ class ExportCompleted extends Notification implements ShouldQueue
*/
public function via($notifiable)
{
- return ['mail'];
+ return ['mail', 'database'];
}
/**
@@ -49,4 +55,19 @@ class ExportCompleted extends Notification implements ShouldQueue
->line(trans('notifications.export.completed.description'))
->action(trans('general.download'), $this->download_url);
}
+
+ /**
+ * Get the array representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function toArray($notifiable)
+ {
+ return [
+ 'translation' => $this->translation,
+ 'file_name' => $this->file_name,
+ 'download_url' => $this->download_url,
+ ];
+ }
}
diff --git a/app/Notifications/Common/ExportFailed.php b/app/Notifications/Common/ExportFailed.php
index b44b366db..64f6e2060 100644
--- a/app/Notifications/Common/ExportFailed.php
+++ b/app/Notifications/Common/ExportFailed.php
@@ -14,18 +14,18 @@ class ExportFailed extends Notification implements ShouldQueue
/**
* The error exception.
*
- * @var object
+ * @var string
*/
- public $exception;
+ public $message;
/**
* Create a notification instance.
*
- * @param object $exception
+ * @param string $message
*/
- public function __construct($exception)
+ public function __construct($message)
{
- $this->exception = $exception;
+ $this->message = $message;
$this->onQueue('notifications');
}
@@ -38,7 +38,7 @@ class ExportFailed extends Notification implements ShouldQueue
*/
public function via($notifiable)
{
- return ['mail'];
+ return ['mail', 'database'];
}
/**
@@ -52,6 +52,19 @@ class ExportFailed extends Notification implements ShouldQueue
return (new MailMessage)
->subject(trans('notifications.export.failed.subject'))
->line(trans('notifications.export.failed.description'))
- ->line($this->exception->getMessage());
+ ->line($this->message);
+ }
+
+ /**
+ * Get the array representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function toArray($notifiable)
+ {
+ return [
+ 'message' => $this->message,
+ ];
}
}
diff --git a/app/Notifications/Portal/PaymentReceived.php b/app/Notifications/Portal/PaymentReceived.php
index 3f2f8afb7..cd07d7ff4 100644
--- a/app/Notifications/Portal/PaymentReceived.php
+++ b/app/Notifications/Portal/PaymentReceived.php
@@ -86,8 +86,14 @@ class PaymentReceived extends Notification
public function toArray($notifiable)
{
return [
+ 'template_alias' => $this->template->alias,
'invoice_id' => $this->invoice->id,
+ 'invoice_number' => $this->invoice->document_number,
+ 'customer_name' => $this->invoice->contact_name,
'amount' => $this->invoice->amount,
+ 'invoice_at' => $this->invoice->issued_at,
+ 'due_at' => $this->invoice->due_at,
+ 'status' => $this->invoice->status,
];
}
diff --git a/app/Notifications/Purchase/Bill.php b/app/Notifications/Purchase/Bill.php
index a80296447..c8da2187c 100644
--- a/app/Notifications/Purchase/Bill.php
+++ b/app/Notifications/Purchase/Bill.php
@@ -57,8 +57,14 @@ class Bill extends Notification
public function toArray($notifiable)
{
return [
+ 'template_alias' => $this->template->alias,
'bill_id' => $this->bill->id,
+ 'bill_number' => $this->bill->document_number,
+ 'vendor_name' => $this->bill->contact_name,
'amount' => $this->bill->amount,
+ 'billed_date' => company_date($this->bill->issued_at),
+ 'bill_due_date' => company_date($this->bill->due_at),
+ 'status' => $this->bill->status,
];
}
@@ -68,6 +74,7 @@ class Bill extends Notification
'{bill_number}',
'{bill_total}',
'{bill_amount_due}',
+ '{billed_date}',
'{bill_due_date}',
'{bill_admin_link}',
'{vendor_name}',
diff --git a/app/Notifications/Sale/Invoice.php b/app/Notifications/Sale/Invoice.php
index 48e510c9c..1056f1176 100644
--- a/app/Notifications/Sale/Invoice.php
+++ b/app/Notifications/Sale/Invoice.php
@@ -77,8 +77,14 @@ class Invoice extends Notification
public function toArray($notifiable)
{
return [
+ 'template_alias' => $this->template->alias,
'invoice_id' => $this->invoice->id,
+ 'invoice_number' => $this->invoice->document_number,
+ 'customer_name' => $this->invoice->contact_name,
'amount' => $this->invoice->amount,
+ 'invoiced_date' => company_date($this->invoice->issued_at),
+ 'invoice_due_date' => company_date($this->invoice->due_at),
+ 'status' => $this->invoice->status,
];
}
@@ -88,6 +94,7 @@ class Invoice extends Notification
'{invoice_number}',
'{invoice_total}',
'{invoice_amount_due}',
+ '{invoiced_date}',
'{invoice_due_date}',
'{invoice_guest_link}',
'{invoice_admin_link}',
diff --git a/app/Utilities/Export.php b/app/Utilities/Export.php
index 8d11a89ce..63c44855b 100644
--- a/app/Utilities/Export.php
+++ b/app/Utilities/Export.php
@@ -31,7 +31,7 @@ class Export
}
$class->queue($file_name, $disk)->onQueue('exports')->chain([
- new CreateMediableForExport(user(), $file_name),
+ new CreateMediableForExport(user(), $file_name, $translation),
]);
$message = trans('messages.success.export_queued', ['type' => $translation]);
diff --git a/public/vendor/bootstrap-notify/bootstrap-notify.min.js b/public/vendor/bootstrap-notify/bootstrap-notify.min.js
new file mode 100644
index 000000000..ea70a9f17
--- /dev/null
+++ b/public/vendor/bootstrap-notify/bootstrap-notify.min.js
@@ -0,0 +1 @@
+!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){function b(b,d,e){var d={content:{message:"object"==typeof d?d.message:d,title:d.title?d.title:"",icon:d.icon?d.icon:"",url:d.url?d.url:"#",target:d.target?d.target:"-"}};e=a.extend(!0,{},d,e),this.settings=a.extend(!0,{},c,e),this._defaults=c,"-"==this.settings.content.target&&(this.settings.content.target=this.settings.url_target),this.animations={start:"webkitAnimationStart oanimationstart MSAnimationStart animationstart",end:"webkitAnimationEnd oanimationend MSAnimationEnd animationend"},"number"==typeof this.settings.offset&&(this.settings.offset={x:this.settings.offset,y:this.settings.offset}),this.init()}var c={element:"body",position:null,type:"info",allow_dismiss:!0,newest_on_top:!1,showProgressbar:!1,placement:{from:"top",align:"right"},offset:20,spacing:10,z_index:1031,delay:5e3,timer:1e3,url_target:"_blank",mouse_over:null,animate:{enter:"animated fadeInDown",exit:"animated fadeOutUp"},onShow:null,onShown:null,onClose:null,onClosed:null,icon_type:"class",template:'
'};String.format=function(){for(var a=arguments[0],b=1;b .progress-bar').removeClass("progress-bar-"+a.settings.type),a.settings.type=d[b],this.$ele.addClass("alert-"+d[b]).find('[data-notify="progressbar"] > .progress-bar').addClass("progress-bar-"+d[b]);break;case"icon":var e=this.$ele.find('[data-notify="icon"]');"class"==a.settings.icon_type.toLowerCase()?e.removeClass(a.settings.content.icon).addClass(d[b]):(e.is("img")||e.find("img"),e.attr("src",d[b]));break;case"progress":var f=a.settings.delay-a.settings.delay*(d[b]/100);this.$ele.data("notify-delay",f),this.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",d[b]).css("width",d[b]+"%");break;case"url":this.$ele.find('[data-notify="url"]').attr("href",d[b]);break;case"target":this.$ele.find('[data-notify="url"]').attr("target",d[b]);break;default:this.$ele.find('[data-notify="'+b+'"]').html(d[b])}var g=this.$ele.outerHeight()+parseInt(a.settings.spacing)+parseInt(a.settings.offset.y);a.reposition(g)},close:function(){a.close()}}},buildNotify:function(){var b=this.settings.content;this.$ele=a(String.format(this.settings.template,this.settings.type,b.title,b.message,b.url,b.target)),this.$ele.attr("data-notify-position",this.settings.placement.from+"-"+this.settings.placement.align),this.settings.allow_dismiss||this.$ele.find('[data-notify="dismiss"]').css("display","none"),(this.settings.delay<=0&&!this.settings.showProgressbar||!this.settings.showProgressbar)&&this.$ele.find('[data-notify="progressbar"]').remove()},setIcon:function(){"class"==this.settings.icon_type.toLowerCase()?this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon):this.$ele.find('[data-notify="icon"]').is("img")?this.$ele.find('[data-notify="icon"]').attr("src",this.settings.content.icon):this.$ele.find('[data-notify="icon"]').append(' ')},styleDismiss:function(){this.$ele.find('[data-notify="dismiss"]').css({position:"absolute",right:"10px",top:"5px",zIndex:this.settings.z_index+2})},styleURL:function(){this.$ele.find('[data-notify="url"]').css({backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)",height:"100%",left:"0px",position:"absolute",top:"0px",width:"100%",zIndex:this.settings.z_index+1})},placement:function(){var b=this,c=this.settings.offset.y,d={display:"inline-block",margin:"0px auto",position:this.settings.position?this.settings.position:"body"===this.settings.element?"fixed":"absolute",transition:"all .5s ease-in-out",zIndex:this.settings.z_index},e=!1,f=this.settings;switch(a('[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])').each(function(){return c=Math.max(c,parseInt(a(this).css(f.placement.from))+parseInt(a(this).outerHeight())+parseInt(f.spacing))}),1==this.settings.newest_on_top&&(c=this.settings.offset.y),d[this.settings.placement.from]=c+"px",this.settings.placement.align){case"left":case"right":d[this.settings.placement.align]=this.settings.offset.x+"px";break;case"center":d.left=0,d.right=0}this.$ele.css(d).addClass(this.settings.animate.enter),a.each(Array("webkit","moz","o","ms",""),function(a,c){b.$ele[0].style[c+"AnimationIterationCount"]=1}),a(this.settings.element).append(this.$ele),1==this.settings.newest_on_top&&(c=parseInt(c)+parseInt(this.settings.spacing)+this.$ele.outerHeight(),this.reposition(c)),a.isFunction(b.settings.onShow)&&b.settings.onShow.call(this.$ele),this.$ele.one(this.animations.start,function(a){e=!0}).one(this.animations.end,function(c){a.isFunction(b.settings.onShown)&&b.settings.onShown.call(this)}),setTimeout(function(){e||a.isFunction(b.settings.onShown)&&b.settings.onShown.call(this)},600)},bind:function(){var b=this;if(this.$ele.find('[data-notify="dismiss"]').on("click",function(){b.close()}),this.$ele.mouseover(function(b){a(this).data("data-hover","true")}).mouseout(function(b){a(this).data("data-hover","false")}),this.$ele.data("data-hover","false"),this.settings.delay>0){b.$ele.data("notify-delay",b.settings.delay);var c=setInterval(function(){var a=parseInt(b.$ele.data("notify-delay"))-b.settings.timer;if("false"===b.$ele.data("data-hover")&&"pause"==b.settings.mouse_over||"pause"!=b.settings.mouse_over){var d=(b.settings.delay-a)/b.settings.delay*100;b.$ele.data("notify-delay",a),b.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",d).css("width",d+"%")}a<=-b.settings.timer&&(clearInterval(c),b.close())},b.settings.timer)}},close:function(){var b=this,c=parseInt(this.$ele.css(this.settings.placement.from)),d=!1;this.$ele.data("closing","true").addClass(this.settings.animate.exit),b.reposition(c),a.isFunction(b.settings.onClose)&&b.settings.onClose.call(this.$ele),this.$ele.one(this.animations.start,function(a){d=!0}).one(this.animations.end,function(c){a(this).remove(),a.isFunction(b.settings.onClosed)&&b.settings.onClosed.call(this)}),setTimeout(function(){d||(b.$ele.remove(),b.settings.onClosed&&b.settings.onClosed(b.$ele))},600)},reposition:function(b){var c=this,d='[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])',e=this.$ele.nextAll(d);1==this.settings.newest_on_top&&(e=this.$ele.prevAll(d)),e.each(function(){a(this).css(c.settings.placement.from,b),b=parseInt(b)+parseInt(c.settings.spacing)+a(this).outerHeight()})}}),a.notify=function(a,c){var d=new b(this,a,c);return d.notify},a.notifyDefaults=function(b){return c=a.extend(!0,{},c,b)},a.notifyClose=function(b){"undefined"==typeof b||"all"==b?a("[data-notify]").find('[data-notify="dismiss"]').trigger("click"):a('[data-notify-position="'+b+'"]').find('[data-notify="dismiss"]').trigger("click")}});
\ No newline at end of file
diff --git a/resources/lang/en-GB/general.php b/resources/lang/en-GB/general.php
index b65eb7077..159e41cd1 100644
--- a/resources/lang/en-GB/general.php
+++ b/resources/lang/en-GB/general.php
@@ -51,6 +51,7 @@ return [
'templates' => 'Template|Templates',
'sales' => 'Sale|Sales',
'purchases' => 'Purchase|Purchases',
+ 'notifications' => 'Notification|Notifications',
'welcome' => 'Welcome',
'banking' => 'Banking',
diff --git a/resources/lang/en-GB/header.php b/resources/lang/en-GB/header.php
index 2bc824465..e40d06f08 100644
--- a/resources/lang/en-GB/header.php
+++ b/resources/lang/en-GB/header.php
@@ -4,12 +4,23 @@ return [
'change_language' => 'Change Language',
'last_login' => 'Last login :time',
+
'notifications' => [
'counter' => '{0} You have no notification|{1} You have :count notification|[2,*] You have :count notifications',
'overdue_invoices' => '{1} :count overdue invoice|[2,*] :count overdue invoices',
'upcoming_bills' => '{1} :count upcoming bill|[2,*] :count upcoming bills',
- 'view_all' => 'View All'
+ 'view_all' => 'View All',
+
+ 'exports' => [
+ 'completed' => '{1} :count finished export|[2,*] :count finished exports',
+ 'failed' => '{1} :count failed export|[2,*] :count failed exports',
+ ],
+ 'imports' => [
+ 'completed' => '{1} :count finished import|[2,*] :count finished imports',
+ 'failed' => '{1} :count failed import|[2,*] :count failed imports',
+ ],
],
+
'docs_link' => 'https://akaunting.com/docs',
'support_link' => 'https://akaunting.com/support',
diff --git a/resources/lang/en-GB/notifications.php b/resources/lang/en-GB/notifications.php
index 384577473..ad63ff4e7 100644
--- a/resources/lang/en-GB/notifications.php
+++ b/resources/lang/en-GB/notifications.php
@@ -6,6 +6,13 @@ return [
'hello' => 'Hello!',
'salutation' => 'Regards, :company_name',
'subcopy' => 'If you’re having trouble clicking the ":text" button, copy and paste the URL below into your web browser: [:url](:url)',
+ 'reads' => 'Read|Reads',
+ 'read_all' => 'Read All',
+ 'mark_read' => 'Mark Read',
+ 'mark_read_all' => 'Mark Read All',
+ 'upcoming_bills' => 'Upcoming Bills',
+ 'recurring_invoices' => 'Recurring Invoices',
+ 'recurring_bills' => 'Recurring Bills',
'update' => [
@@ -51,4 +58,9 @@ return [
],
+ 'messages' => [
+ 'mark_read' => ':type is read this notification!',
+ 'mark_read_all' => ':type is read all notification!',
+ 'export' => ':type export is ready! The export file is ready to download from the following :file_name '
+ ],
];
diff --git a/resources/views/common/notifications/index.blade.php b/resources/views/common/notifications/index.blade.php
index 67fd0064c..5e958ec28 100644
--- a/resources/views/common/notifications/index.blade.php
+++ b/resources/views/common/notifications/index.blade.php
@@ -2,23 +2,52 @@
@section('title', trans_choice('general.notifications', 2))
-@section('content')
-
+@section('new_button')
+
+
+
@endsection
+
+@section('content')
+ @stack('new_apps')
+
+
+
+ @stack('exports')
+
+
+
+ @stack('imports')
+
+
+
+ @stack('invoices_recurring')
+
+
+
+ @stack('invoices_reminder')
+
+
+
+ @stack('bills_recurring')
+
+
+
+ @stack('bills_reminder')
+
+
+
+ @stack('end')
+@endsection
+
+@push('body_js')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/livewire/common/notifications/exports.blade.php b/resources/views/livewire/common/notifications/exports.blade.php
new file mode 100644
index 000000000..ff14f26a3
--- /dev/null
+++ b/resources/views/livewire/common/notifications/exports.blade.php
@@ -0,0 +1,105 @@
+@if ($notifications->total())
+
+
+
+
+
+
+ @foreach ($notifications as $notification)
+
+
+ @if (empty($notification->message))
+ {!! trans('notifications.messages.export', [
+ 'type' => $notification->translation,
+ 'file_name' => $notification->file_name,
+ 'url' => $notification->download_url
+ ]) !!}
+ @else
+ {!! $notification->message !!}
+ @endif
+
+
+
+
+
+
+
+
+ @endforeach
+
+
+
+
+ @if ($notifications->total() > 5)
+
+ @endif
+
+@endif
+
+@push('scripts_start')
+
+@endpush
+
+@push('body_js')
+
+@endpush
diff --git a/resources/views/livewire/common/notifications/imports.blade.php b/resources/views/livewire/common/notifications/imports.blade.php
new file mode 100644
index 000000000..dddd3598d
--- /dev/null
+++ b/resources/views/livewire/common/notifications/imports.blade.php
@@ -0,0 +1,105 @@
+@if ($notifications->total())
+
+
+
+
+
+
+ @foreach ($notifications as $notification)
+
+
+ @if (empty($notification->message))
+ {!! trans('notifications.messages.export', [
+ 'type' => $notification->translation,
+ 'file_name' => $notification->file_name,
+ 'url' => $notification->download_url
+ ]) !!}
+ @else
+ {!! $notification->message !!}
+ @endif
+
+
+
+
+
+
+
+
+ @endforeach
+
+
+
+
+ @if ($notifications->total() > 5)
+
+ @endif
+
+@endif
+
+@push('scripts_start')
+
+@endpush
+
+@push('body_js')
+
+@endpush
diff --git a/resources/views/livewire/common/notifications/new-apps.blade.php b/resources/views/livewire/common/notifications/new-apps.blade.php
new file mode 100644
index 000000000..c51622f2b
--- /dev/null
+++ b/resources/views/livewire/common/notifications/new-apps.blade.php
@@ -0,0 +1,7 @@
+
diff --git a/resources/views/livewire/common/notifications/recurring.blade.php b/resources/views/livewire/common/notifications/recurring.blade.php
new file mode 100644
index 000000000..885851db7
--- /dev/null
+++ b/resources/views/livewire/common/notifications/recurring.blade.php
@@ -0,0 +1,213 @@
+@if ($documents->count())
+
+
+
+
+
+
+
+ @stack('document_number_th_start')
+ @if (!$hideDocumentNumber)
+
+ @stack('document_number_th_inside_start')
+
+ {{ trans_choice($textDocumentNumber, 1) }}
+
+ @stack('document_number_th_inside_end')
+
+ @endif
+ @stack('document_number_th_end')
+
+ @stack('contact_name_th_start')
+ @if (!$hideContactName)
+
+ @stack('contact_name_th_inside_start')
+
+ {{ trans_choice($textContactName, 1) }}
+
+ @stack('contact_name_th_inside_end')
+
+ @endif
+ @stack('contact_name_th_end')
+
+ @stack('amount_th_start')
+ @if (!$hideAmount)
+
+ @stack('amount_th_inside_start')
+
+ {{ trans('general.amount') }}
+
+ @stack('amount_th_inside_end')
+
+ @endif
+ @stack('amount_th_end')
+
+ @stack('issued_at_th_start')
+ @if (!$hideIssuedAt)
+
+ @stack('issued_at_th_inside_start')
+
+ {{ trans($textIssuedAt) }}
+
+ @stack('issued_at_th_inside_end')
+
+ @endif
+ @stack('issued_at_th_end')
+
+ @stack('due_at_th_start')
+ @if (!$hideDueAt)
+
+ @stack('due_at_th_inside_start')
+
+ {{ trans($textDueAt) }}
+
+ @stack('due_at_th_inside_end')
+
+ @endif
+ @stack('due_at_th_end')
+
+ @stack('status_th_start')
+ @if (!$hideStatus)
+
+ @stack('status_th_inside_start')
+
+ {{ trans_choice('general.statuses', 1) }}
+
+ @stack('status_th_inside_end')
+
+ @endif
+ @stack('status_th_end')
+
+ @if (!$hideActions)
+
+ {{ trans('general.actions') }}
+
+ @endif
+
+
+
+
+ @foreach($documents as $item)
+
+ @stack('document_number_td_start')
+ @if (!$hideDocumentNumber)
+
+ @stack('document_number_td_inside_start')
+
+ {{ $item->document_number }}
+
+ @stack('document_number_td_inside_end')
+
+ @endif
+ @stack('document_number_td_end')
+
+ @stack('contact_name_td_start')
+ @if (!$hideContactName)
+
+ @stack('contact_name_td_inside_start')
+
+ {{ $item->contact_name }}
+
+ @stack('contact_name_td_inside_end')
+
+ @endif
+ @stack('contact_name_td_end')
+
+ @stack('amount_td_start')
+ @if (!$hideAmount)
+
+ @stack('amount_td_inside_start')
+
+ @money($item->amount, $item->currency_code, true)
+
+ @stack('amount_td_inside_end')
+
+ @endif
+ @stack('amount_td_end')
+
+ @stack('issued_at_td_start')
+ @if (!$hideIssuedAt)
+
+ @stack('issued_at_td_inside_start')
+
+ @date($item->issued_at)
+
+ @stack('issued_at_td_inside_end')
+
+ @endif
+ @stack('issued_at_td_end')
+
+ @stack('due_at_td_start')
+ @if (!$hideDueAt)
+
+ @stack('due_at_td_inside_start')
+
+ @date($item->due_at)
+
+ @stack('due_at_td_inside_end')
+
+ @endif
+ @stack('due_at_td_end')
+
+ @stack('status_td_start')
+ @if (!$hideStatus)
+
+ @stack('status_td_inside_start')
+
+ {{ trans($textDocumentStatus . $item->status) }}
+
+ @stack('status_td_inside_end')
+
+ @endif
+ @stack('status_td_end')
+
+ @if (!$hideActions)
+
+
+
+
+
+ @endif
+
+ @endforeach
+
+
+
+
+ @if ($documents->total() > 5)
+
+ @endif
+
+@endif
\ No newline at end of file
diff --git a/resources/views/livewire/common/notifications/reminder.blade.php b/resources/views/livewire/common/notifications/reminder.blade.php
new file mode 100644
index 000000000..e0b833fb9
--- /dev/null
+++ b/resources/views/livewire/common/notifications/reminder.blade.php
@@ -0,0 +1,249 @@
+@if ($documents->count())
+
+
+
+
+
+
+
+ @stack('document_number_th_start')
+ @if (!$hideDocumentNumber)
+
+ @stack('document_number_th_inside_start')
+
+ {{ trans_choice($textDocumentNumber, 1) }}
+
+ @stack('document_number_th_inside_end')
+
+ @endif
+ @stack('document_number_th_end')
+
+ @stack('contact_name_th_start')
+ @if (!$hideContactName)
+
+ @stack('contact_name_th_inside_start')
+
+ {{ trans_choice($textContactName, 1) }}
+
+ @stack('contact_name_th_inside_end')
+
+ @endif
+ @stack('contact_name_th_end')
+
+ @stack('amount_th_start')
+ @if (!$hideAmount)
+
+ @stack('amount_th_inside_start')
+
+ {{ trans('general.amount') }}
+
+ @stack('amount_th_inside_end')
+
+ @endif
+ @stack('amount_th_end')
+
+ @stack('issued_at_th_start')
+ @if (!$hideIssuedAt)
+
+ @stack('issued_at_th_inside_start')
+
+ {{ trans($textIssuedAt) }}
+
+ @stack('issued_at_th_inside_end')
+
+ @endif
+ @stack('issued_at_th_end')
+
+ @stack('due_at_th_start')
+ @if (!$hideDueAt)
+
+ @stack('due_at_th_inside_start')
+
+ {{ trans($textDueAt) }}
+
+ @stack('due_at_th_inside_end')
+
+ @endif
+ @stack('due_at_th_end')
+
+ @stack('status_th_start')
+ @if (!$hideStatus)
+
+ @stack('status_th_inside_start')
+
+ {{ trans_choice('general.statuses', 1) }}
+
+ @stack('status_th_inside_end')
+
+ @endif
+ @stack('status_th_end')
+
+ @if (!$hideActions)
+
+ {{ trans_choice('notifications.reads', 1) }}
+
+ @endif
+
+
+
+
+ @foreach($documents as $item)
+
+ @stack('document_number_td_start')
+ @if (!$hideDocumentNumber)
+
+ @stack('document_number_td_inside_start')
+
+ {{ $item->document_number }}
+
+ @stack('document_number_td_inside_end')
+
+ @endif
+ @stack('document_number_td_end')
+
+ @stack('contact_name_td_start')
+ @if (!$hideContactName)
+
+ @stack('contact_name_td_inside_start')
+
+ {{ $item->contact_name }}
+
+ @stack('contact_name_td_inside_end')
+
+ @endif
+ @stack('contact_name_td_end')
+
+ @stack('amount_td_start')
+ @if (!$hideAmount)
+
+ @stack('amount_td_inside_start')
+
+ @money($item->amount, $item->currency_code, true)
+
+ @stack('amount_td_inside_end')
+
+ @endif
+ @stack('amount_td_end')
+
+ @stack('issued_at_td_start')
+ @if (!$hideIssuedAt)
+
+ @stack('issued_at_td_inside_start')
+
+ @date($item->issued_at)
+
+ @stack('issued_at_td_inside_end')
+
+ @endif
+ @stack('issued_at_td_end')
+
+ @stack('due_at_td_start')
+ @if (!$hideDueAt)
+
+ @stack('due_at_td_inside_start')
+
+ @date($item->due_at)
+
+ @stack('due_at_td_inside_end')
+
+ @endif
+ @stack('due_at_td_end')
+
+ @stack('status_td_start')
+ @if (!$hideStatus)
+
+ @stack('status_td_inside_start')
+
+ {{ trans($textDocumentStatus . $item->status) }}
+
+ @stack('status_td_inside_end')
+
+ @endif
+ @stack('status_td_end')
+
+ @if (!$hideActions)
+
+
+
+
+
+ @endif
+
+ @endforeach
+
+
+
+
+ @if ($documents->total() > 5)
+
+ @endif
+
+@endif
+
+@push('scripts_start')
+
+@endpush
+
+@push('body_js')
+
+@endpush
diff --git a/resources/views/partials/admin/navbar.blade.php b/resources/views/partials/admin/navbar.blade.php
index 64cc26012..680c058d3 100644
--- a/resources/views/partials/admin/navbar.blade.php
+++ b/resources/views/partials/admin/navbar.blade.php
@@ -127,11 +127,87 @@
@endif