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('Notify Icon')},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()) +
+
+
+
+
{{ trans('general.export') }}
+
+ +
+ +
+
+
+ +
+ + + @foreach ($notifications as $notification) + + + + + + @endforeach + +
+ @if (empty($notification->message)) + {!! trans('notifications.messages.export', [ + 'type' => $notification->translation, + 'file_name' => $notification->file_name, + 'url' => $notification->download_url + ]) !!} + @else + {!! $notification->message !!} + @endif + + +
+
+ + @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()) +
+
+
+
+
{{ trans('general.export') }}
+
+ +
+ +
+
+
+ +
+ + + @foreach ($notifications as $notification) + + + + + + @endforeach + +
+ @if (empty($notification->message)) + {!! trans('notifications.messages.export', [ + 'type' => $notification->translation, + 'file_name' => $notification->file_name, + 'url' => $notification->download_url + ]) !!} + @else + {!! $notification->message !!} + @endif + + +
+
+ + @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()) +
+
+
+
+
Card title
+
+ +
+ Action +
+
+
+ +
+ + + + @stack('document_number_th_start') + @if (!$hideDocumentNumber) + + @endif + @stack('document_number_th_end') + + @stack('contact_name_th_start') + @if (!$hideContactName) + + @endif + @stack('contact_name_th_end') + + @stack('amount_th_start') + @if (!$hideAmount) + + @endif + @stack('amount_th_end') + + @stack('issued_at_th_start') + @if (!$hideIssuedAt) + + @endif + @stack('issued_at_th_end') + + @stack('due_at_th_start') + @if (!$hideDueAt) + + @endif + @stack('due_at_th_end') + + @stack('status_th_start') + @if (!$hideStatus) + + @endif + @stack('status_th_end') + + @if (!$hideActions) + + @endif + + + + + @foreach($documents as $item) + + @stack('document_number_td_start') + @if (!$hideDocumentNumber) + + @endif + @stack('document_number_td_end') + + @stack('contact_name_td_start') + @if (!$hideContactName) + + @endif + @stack('contact_name_td_end') + + @stack('amount_td_start') + @if (!$hideAmount) + + @endif + @stack('amount_td_end') + + @stack('issued_at_td_start') + @if (!$hideIssuedAt) + + @endif + @stack('issued_at_td_end') + + @stack('due_at_td_start') + @if (!$hideDueAt) + + @endif + @stack('due_at_td_end') + + @stack('status_td_start') + @if (!$hideStatus) + + @endif + @stack('status_td_end') + + @if (!$hideActions) + + @endif + + @endforeach + +
+ @stack('document_number_th_inside_start') + + {{ trans_choice($textDocumentNumber, 1) }} + + @stack('document_number_th_inside_end') + + @stack('contact_name_th_inside_start') + + {{ trans_choice($textContactName, 1) }} + + @stack('contact_name_th_inside_end') + + @stack('amount_th_inside_start') + + {{ trans('general.amount') }} + + @stack('amount_th_inside_end') + + @stack('issued_at_th_inside_start') + + {{ trans($textIssuedAt) }} + + @stack('issued_at_th_inside_end') + + @stack('due_at_th_inside_start') + + {{ trans($textDueAt) }} + + @stack('due_at_th_inside_end') + + @stack('status_th_inside_start') + + {{ trans_choice('general.statuses', 1) }} + + @stack('status_th_inside_end') + + {{ trans('general.actions') }} +
+ @stack('document_number_td_inside_start') + + {{ $item->document_number }} + + @stack('document_number_td_inside_end') + + @stack('contact_name_td_inside_start') + + {{ $item->contact_name }} + + @stack('contact_name_td_inside_end') + + @stack('amount_td_inside_start') + + @money($item->amount, $item->currency_code, true) + + @stack('amount_td_inside_end') + + @stack('issued_at_td_inside_start') + + @date($item->issued_at) + + @stack('issued_at_td_inside_end') + + @stack('due_at_td_inside_start') + + @date($item->due_at) + + @stack('due_at_td_inside_end') + + @stack('status_td_inside_start') + + {{ trans($textDocumentStatus . $item->status) }} + + @stack('status_td_inside_end') + + +
+
+ + @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()) +
+
+
+
+
{{ trans($textTitle) }}
+
+ +
+ +
+
+
+ +
+ + + + @stack('document_number_th_start') + @if (!$hideDocumentNumber) + + @endif + @stack('document_number_th_end') + + @stack('contact_name_th_start') + @if (!$hideContactName) + + @endif + @stack('contact_name_th_end') + + @stack('amount_th_start') + @if (!$hideAmount) + + @endif + @stack('amount_th_end') + + @stack('issued_at_th_start') + @if (!$hideIssuedAt) + + @endif + @stack('issued_at_th_end') + + @stack('due_at_th_start') + @if (!$hideDueAt) + + @endif + @stack('due_at_th_end') + + @stack('status_th_start') + @if (!$hideStatus) + + @endif + @stack('status_th_end') + + @if (!$hideActions) + + @endif + + + + + @foreach($documents as $item) + + @stack('document_number_td_start') + @if (!$hideDocumentNumber) + + @endif + @stack('document_number_td_end') + + @stack('contact_name_td_start') + @if (!$hideContactName) + + @endif + @stack('contact_name_td_end') + + @stack('amount_td_start') + @if (!$hideAmount) + + @endif + @stack('amount_td_end') + + @stack('issued_at_td_start') + @if (!$hideIssuedAt) + + @endif + @stack('issued_at_td_end') + + @stack('due_at_td_start') + @if (!$hideDueAt) + + @endif + @stack('due_at_td_end') + + @stack('status_td_start') + @if (!$hideStatus) + + @endif + @stack('status_td_end') + + @if (!$hideActions) + + @endif + + @endforeach + +
+ @stack('document_number_th_inside_start') + + {{ trans_choice($textDocumentNumber, 1) }} + + @stack('document_number_th_inside_end') + + @stack('contact_name_th_inside_start') + + {{ trans_choice($textContactName, 1) }} + + @stack('contact_name_th_inside_end') + + @stack('amount_th_inside_start') + + {{ trans('general.amount') }} + + @stack('amount_th_inside_end') + + @stack('issued_at_th_inside_start') + + {{ trans($textIssuedAt) }} + + @stack('issued_at_th_inside_end') + + @stack('due_at_th_inside_start') + + {{ trans($textDueAt) }} + + @stack('due_at_th_inside_end') + + @stack('status_th_inside_start') + + {{ trans_choice('general.statuses', 1) }} + + @stack('status_th_inside_end') + + {{ trans_choice('notifications.reads', 1) }} +
+ @stack('document_number_td_inside_start') + + {{ $item->document_number }} + + @stack('document_number_td_inside_end') + + @stack('contact_name_td_inside_start') + + {{ $item->contact_name }} + + @stack('contact_name_td_inside_end') + + @stack('amount_td_inside_start') + + @money($item->amount, $item->currency_code, true) + + @stack('amount_td_inside_end') + + @stack('issued_at_td_inside_start') + + @date($item->issued_at) + + @stack('issued_at_td_inside_end') + + @stack('due_at_td_inside_start') + + @date($item->due_at) + + @stack('due_at_td_inside_end') + + @stack('status_td_inside_start') + + {{ trans($textDocumentStatus . $item->status) }} + + @stack('status_td_inside_end') + + +
+
+ + @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
+ @stack('notification_exports_completed_start') + + @if (!empty($exports['completed']) && count($exports['completed'])) + +
+
+ +
+
+
+

{{ trans_choice('header.notifications.exports.completed', count($exports['completed']), ['count' => count($exports['completed'])]) }}

+
+
+
+
+ @endif + + @stack('notification_exports_completed_end') + + @stack('notification_exports_failed_start') + + @if (!empty($exports['failed']) && count($exports['failed'])) + +
+
+ +
+
+
+

{{ trans_choice('header.notifications.exports.failed', count($exports['failed']), ['count' => count($exports['failed'])]) }}

+
+
+
+
+ @endif + + @stack('notification_exports_failed_end') + + @stack('notification_imports_completed_start') + + @if (!empty($imports['completed']) && count($imports['completed'])) + +
+
+ +
+
+
+

{{ trans_choice('header.notifications.imports.completed', count($imports['completed']), ['count' => count($imports['completed'])]) }}

+
+
+
+
+ @endif + + @stack('notification_imports_completed_end') + + @stack('notification_imports_failed_start') + + @if (!empty($imports['failed']) && count($imports['failed'])) + +
+
+ +
+
+
+

{{ trans_choice('header.notifications.imports.failed', count($imports['failed']), ['count' => count($imports['failed'])]) }}

+
+
+
+
+ @endif + + @stack('notification_imports_failed_end') + @stack('notification_bills_start') @can('read-purchases-bills') @if (count($bills)) - +
@@ -152,7 +228,7 @@ @can('read-sales-invoices') @if (count($invoices)) - +
@@ -171,7 +247,7 @@
@if ($notifications) -
{{ trans('header.notifications.view_all') }} + {{ trans('header.notifications.view_all') }} @else {{ trans_choice('header.notifications.counter', $notifications, ['count' => $notifications]) }} @endif diff --git a/resources/views/partials/admin/scripts.blade.php b/resources/views/partials/admin/scripts.blade.php index 963c6b250..ef9d46298 100644 --- a/resources/views/partials/admin/scripts.blade.php +++ b/resources/views/partials/admin/scripts.blade.php @@ -124,6 +124,12 @@ } }) })(); + + $(document).ready(function () { + if ($("[data-toggle=tooltip]").length) { + $("[data-toggle=tooltip]").tooltip(); + } + }); @stack('body_css') diff --git a/resources/views/vendor/livewire/bootstrap.blade.php b/resources/views/vendor/livewire/bootstrap.blade.php new file mode 100644 index 000000000..7de9c18e1 --- /dev/null +++ b/resources/views/vendor/livewire/bootstrap.blade.php @@ -0,0 +1,48 @@ +
+ @if ($paginator->hasPages()) + + @endif +
diff --git a/resources/views/vendor/livewire/default.blade.php b/resources/views/vendor/livewire/default.blade.php new file mode 100644 index 000000000..367596590 --- /dev/null +++ b/resources/views/vendor/livewire/default.blade.php @@ -0,0 +1,48 @@ +
    + {{-- Previous Page Link --}} + @if ($paginator->onFirstPage()) +
  • + + + +
  • + @else +
  • + +
  • + @endif + + {{-- Pagination Elements --}} + @foreach ($elements as $element) + {{-- "Three Dots" Separator --}} + @if (is_string($element)) +
  • {{ $element }}
  • + @endif + + {{-- Array Of Links --}} + @if (is_array($element)) + @foreach ($element as $page => $url) + @if ($page == $paginator->currentPage()) +
  • {{ $page }}
  • + @else +
  • + @endif + @endforeach + @endif + @endforeach + + {{-- Next Page Link --}} + @if ($paginator->hasMorePages()) +
  • + +
  • + @else +
  • + +
  • + @endif +
diff --git a/resources/views/vendor/livewire/simple-bootstrap.blade.php b/resources/views/vendor/livewire/simple-bootstrap.blade.php new file mode 100644 index 000000000..dbc374434 --- /dev/null +++ b/resources/views/vendor/livewire/simple-bootstrap.blade.php @@ -0,0 +1,29 @@ +
+ @if ($paginator->hasPages()) + + @endif +
diff --git a/resources/views/vendor/livewire/simple-tailwind.blade.php b/resources/views/vendor/livewire/simple-tailwind.blade.php new file mode 100644 index 000000000..55b334aac --- /dev/null +++ b/resources/views/vendor/livewire/simple-tailwind.blade.php @@ -0,0 +1,31 @@ +
+ @if ($paginator->hasPages()) + + @endif +
diff --git a/resources/views/vendor/livewire/tailwind.blade.php b/resources/views/vendor/livewire/tailwind.blade.php new file mode 100644 index 000000000..6c5cf8611 --- /dev/null +++ b/resources/views/vendor/livewire/tailwind.blade.php @@ -0,0 +1,114 @@ +
+ @if ($paginator->hasPages()) + + @endif +
diff --git a/routes/admin.php b/routes/admin.php index 3134e5a97..a60fb0288 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -40,6 +40,8 @@ Route::group(['prefix' => 'common'], function () { Route::resource('items', 'Common\Items', ['middleware' => ['money', 'dropzone']]); Route::post('notifications/disable', 'Common\Notifications@disable')->name('notifications.disable'); + Route::get('notifications/readAll', 'Common\Notifications@readAll')->name('notifications.read-all'); + Route::resource('notifications', 'Common\Notifications'); Route::post('bulk-actions/{group}/{type}', 'Common\BulkActions@action')->name('bulk-actions.action');