Merge branch 'notification-ui' of https://gitlab.com/CIEFWorldwideSdnBhd/shipping-portal into development

# Conflicts:
#	routes/web.php
This commit is contained in:
edmondlang
2022-04-17 23:38:10 +08:00
13 changed files with 373 additions and 28 deletions
@@ -0,0 +1,21 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
class TargetId implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('target_type', User::class)->where('target_id', $value);
}
}
@@ -14,6 +14,8 @@ use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Documents\Services\FetchesDocument;
use App\Classes\Modules\Documents\Services\ApprovesDocument;
use App\Classes\Modules\Notifications\DataTransferObjects\NotificationObject;
Use App\Classes\Modules\Notifications\Processors\CreateNotificationProcessor;
use App\Models\Document;
use Illuminate\Http\JsonResponse;
@@ -50,6 +52,9 @@ class ApproveIdentificationDocumentLogic extends AbstractControllerLogic
/** @var UpdatesOrdersStatus */
private $updatesOrdersStatus;
/** @var CreateNotificationProcessor */
private $createNotificationProcessor;
/**
* ApproveIdentificationDocumentLogic constructor.
* @param CanApproveDocument $canApproveDocument
@@ -58,8 +63,9 @@ class ApproveIdentificationDocumentLogic extends AbstractControllerLogic
* @param FetchesDocument $fetchesDocument
* @param UpdatesCompanyStatus $updatesCompanyStatus
* @param UpdatesOrdersStatus $updatesOrdersStatus
* @param CreateNotificationProcessor $createNotificationProcessor
*/
public function __construct(CanApproveDocument $canApproveDocument, ApprovesDocument $approvesDocument, RejectsDocument $rejectsDocument, FetchesDocument $fetchesDocument, UpdatesCompanyStatus $updatesCompanyStatus, UpdatesOrdersStatus $updatesOrdersStatus)
public function __construct(CanApproveDocument $canApproveDocument, ApprovesDocument $approvesDocument, RejectsDocument $rejectsDocument, FetchesDocument $fetchesDocument, UpdatesCompanyStatus $updatesCompanyStatus, UpdatesOrdersStatus $updatesOrdersStatus, CreateNotificationProcessor $createNotificationProcessor)
{
$this->canApproveDocument = $canApproveDocument;
$this->approvesDocument = $approvesDocument;
@@ -67,6 +73,7 @@ class ApproveIdentificationDocumentLogic extends AbstractControllerLogic
$this->fetchesDocument = $fetchesDocument;
$this->updatesCompanyStatus = $updatesCompanyStatus;
$this->updatesOrdersStatus = $updatesOrdersStatus;
$this->createNotificationProcessor = $createNotificationProcessor;
}
/**
@@ -90,6 +97,16 @@ class ApproveIdentificationDocumentLogic extends AbstractControllerLogic
$this->updatesCompanyStatus->execute($document->owner, $status === 'approve' ? ApprovalStatus::APPROVED : ApprovalStatus::REJECTED);
$object = new NotificationObject(
'ID Verification ' . ( $status === 'approve' ? 'Approved' : 'Rejected' ),
( $status === 'approve' ? 'Dear user, congratulations that your ' : 'Dear user, we are sorry to inform you that your ' ) . ( $document->type === 'IDENTITY_CARD' ? 'IC' : 'SSM' ) . ( $status === 'approve' ? ' has been approved. Start your first order now!' : ' has been rejected due to ' . ( $request->input('rejectRemark') ?? '' ) . ', please resubmit it for further action.' ),
$document->owner,
$document->owner->companyModules()->first()->employees()->first(),
$document,
);
$this->createNotificationProcessor->execute($object);
if($status === 'approve'){
$orders = $this->updatesOrdersStatus->execute($document->owner, ApprovalStatus::APPROVED);
}
@@ -0,0 +1,61 @@
<?php
namespace App\Classes\Modules\Notifications\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Notifications\Services\ListsNotification;
use App\Http\Resources\NotificationResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ListNotificationsLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Retrieve Notifications',
'message' => 'You have successfully retrieved a list of Notifications'
];
}
/** @var ListsNotification */
private $listsNotification;
/**
* ListNotificationsLogic constructor.
* @param ListsNotification $listsNotification
*/
public function __construct(
ListsNotification $listsNotification
)
{
$this->listsNotification = $listsNotification;
}
/**
* @param Request $request
* @return JsonResponse
* @throws \App\Classes\Exceptions\AccessForbiddenException
* @throws \App\Classes\Exceptions\MalformedRequestException
* @throws \App\Classes\Exceptions\RequestValidationException
*/
public function logic(Request $request) : JsonResponse
{
$filters = [
// 'target_id'=>auth()->user()->id,
// 'per_page'=>$request->route('per_page')
];
$notifications = $this->listsNotification->execute($filters);
return $this->collectionResponse(NotificationResource::collection($notifications));
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Classes\Modules\Notifications\Services;
use App\Classes\General\Eloquent\AbstractListRecord;
use Illuminate\Database\Eloquent\Builder;
use App\Models\Notification;
class ListsNotification extends AbstractListRecord
{
/** @var Bank */
private $repository;
/**
* ListsBank constructor.
* @param Notification $repository
*/
public function __construct(Notification $repository)
{
$this->repository = $repository;
}
/**
* @return Builder
*/
function getRepository(): Builder
{
return $this->repository->newQuery();
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Notifications;
use App\Classes\Modules\Notifications\ControllersLogic\ListNotificationsLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ListNotificationsController
{
/**
* @param Request $request
* @param ListNotificationsLogic $logic
* @return JsonResponse
*/
public function list(Request $request, ListNotificationsLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Http\Resources;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\BusinessType;
use App\Classes\ValueObjects\Constants\DocumentType;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Crypt;
class NotificationResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'description' => $this->description,
'long_ago' => $this->created_at->diffForHumans(),
'created_at' => $this->created_at->format('d-m-Y')
];
}
}
+6
View File
@@ -317,6 +317,12 @@ hr{
background-color: $color-primary-lighter !important;
}
.bg-primary-lighter-hover {
&:hover {
background-color: $color-primary-lighter !important;
}
}
/* Complete
------------------------------------
*/
@@ -137,28 +137,7 @@
</button>
</div>
<modal-component small type="rejectDocument">
<div class="row">
<div class="col text-center">
<div class="row">
<div class="col text-center">
<div class="row m-b-20">
<div class="col">
<h5 class="all-caps">Reject Document</h5>
<div class="fs-11">Are you sure you want to reject this customer's identification?</div>
</div>
</div>
<div class="row">
<div class="col p-r-5">
<div data-dismiss="modal" class="btn btn-sm btn-default bg-master-lighter btn-block b-rad-none">Cancel</div>
</div>
<div class="col p-l-5">
<div data-dismiss="modal" class="btn btn-sm btn-danger btn-block b-rad-none" @click="approveDocument('reject')">Reject</div>
</div>
</div>
</div>
</div>
</div>
</div>
<reject-identification-verification-form-component :section="section" :data="item"></reject-identification-verification-form-component>
</modal-component>
<div class="col no-padding ml-auto">
<button class="btn btn-md btn-block btn-success b-rad-none p-t-10 p-b-10 requestModal" data-type="approveDocument">
@@ -0,0 +1,82 @@
<template>
<div class="row">
<div class="col">
<div class="row justify-content-center">
<div class="col-auto text-center">
<div class="row">
<div class="col text-center">
<div class="row">
<div class="col">
<h5 class="all-caps">Reject Document</h5>
<div class="fs-11">Are you sure you want to reject this customer's identification?</div>
</div>
</div>
<div class="row text-left margin-auto m-t-10 m-b-10">
<div class="col">
<span class="text-danger fs-9">{{ error }}</span>
<div class="fs-11">Reason: </div>
<div class="row">
<div class="col fs-11">
<div class="b-a padding-5 w-100 m-b-5 pointer b-grey muted" :class="{'b-primary': parameters.rejectRemark === rejectRemarkItem, 'text-primary': parameters.rejectRemark === rejectRemarkItem}" v-for="rejectRemarkItem in rejectRemarkArray" @click="chooseRejectRemark(rejectRemarkItem)">{{ rejectRemarkItem }}</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col p-r-5">
<div data-dismiss="modal" class="btn btn-sm btn-default bg-master-lighter btn-block b-rad-none">Cancel</div>
</div>
<div class="col p-l-5">
<div class="btn btn-sm btn-danger btn-block b-rad-none" @click="approveDocument('reject')">Reject</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import componentHandler from '../../../general/mixins/componentHandler';
import modalFormHandler from '../../../general/mixins/modalFormHandler';
import { required } from "vuelidate/lib/validators";
export default {
data(){
return {
parameters: {
rejectRemark: null,
},
rejectRemarkArray: null,
documentType: this.data.document_type === 'IDENTITY_CARD' ? 'IC' : 'SSM',
error: null,
}
},
validations: {
parameters: {
rejectRemark: { required },
}
},
created(){
this.rejectRemarkArray = [
this.documentType + ' not clear',
this.documentType + ' name different with registration name',
'Wrong Document uploaded',
'Non-Malaysian ' + this.documentType + ' Uploaded',
this.documentType + ' not Genuine'
];
},
methods: {
approveDocument(status){
this.parameters.rejectRemark === null ? this.error = 'Please choose a remark.' : null;
this.isLoading = true;
this.submit(this.route('api.company.identification.approval', this.item.owner.id, this.item.id, status), 'put', 'identificationVerificationSection', true, true);
},
chooseRejectRemark(remark) {
this.parameters.rejectRemark = remark;
}
},
mixins: [componentHandler, modalFormHandler]
}
</script>
@@ -11,7 +11,6 @@
<on-boarding-section-component v-if="!company.last_order"></on-boarding-section-component>
<div class="row" v-if="company.last_order">
<div class="col">
<!-- new swction -->
<div class="row tabsContainer">
<div class="col no-padding">
<div class="row m-l-0 m-r-0">
@@ -127,7 +126,6 @@
</div>
</div>
</div>
<!-- end new swction -->
</div>
</div>
</div>
@@ -0,0 +1,98 @@
<template>
<div class="row">
<div class="col">
<div class="btn-group">
<i class="fa fa-bell fs-18 m-t-5 muted pointer" :class="{'text-primary' : isClicked}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" @click="openNotification()"></i>
<div class="b-rad-md dropdown-menu dropdown-menu-right p-l-15 p-b-15 p-r-15 p-t-0" style="width: 100vw; height: 100vh; background: transparent !important;" @click="openNotification()">
<div class="row shadow" style="width: 320px; position: absolute; top: 50px; right: 50px; background: white!important;">
<div class="col">
<div class="row">
<div class="bg-master-lighter col p-l-10 p-l-10 p-t-10 bg-white text-center">
<p>Notification Center</p>
</div>
</div>
<loading-component style="height: 200px; top: 0;" key="1" color="primary" v-show="isLoading"></loading-component>
<div class="row" :class="{'h-100' : notificationsLength >= 5}" v-show="!isLoading" style="max-height: 400px; ">
<div class="col page-container overflow-hidden">
<div class="row b-b b-grey bg-primary-lighter-hover pointer w-100 m-l-0 m-r-0" v-for="(notification, index) in notifications">
<div class="col-auto justify-content-center align-items-center d-flex hide">
<div>
<i class="fa fa-check-circle fs-20 p-l-5 text-success"></i>
</div>
</div>
<div class="col padding-10 p-l-15 p-r-15">
<p class="bold m-b-5 lh-16">{{ notification.title }}</p>
<p class="fs-9 m-b-0 lh-10">{{ notification.description }}</p>
<p class="fs-9 m-b-0 m-t-10 lh-10">{{ notification.long_ago }}</p>
</div>
<div class="col-auto justify-content-center align-items-center d-none" :class="{'d-flex' : index === 0}">
<div>
<i class="fa fa-circle fs-10 text-primary"></i>
</div>
</div>
</div>
</div>
</div>
<div class="row text-center m-t-50 m-b-50" v-if="notificationsLength === 0" v-show="!isLoading">
<div class="col">
<div class="row align-items-center justify-content-center hint-text">
<div class="col-4 hint-text"><img src="/images/not-found-illustration.png" class="w-100 hint-text"/></div>
</div>
<div class="row text-center">
<div class="col">
<div class="row m-t-20">
<div class="col">
<p class="all-caps no-margin fs-11" style="letter-spacing: 2px;">Nothing To Show Here</p>
</div>
</div>
<div class="row m-t-5 align-items-center justify-content-center hide">
<div class="col">
<small class="fs-9 muted all-caps font-lato" style="letter-spacing: 2px">There is no results found.</small>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row b-t muted">
<div class="col p-l-10 p-l-10 p-t-10 m-b-10 bg-white text-center">
<a href="#">View All Notifications</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import ModalFormHandler from '../../../general/mixins/modalFormHandler';
export default {
data(){
return {
isLoading: true,
error: '',
notifications: null,
isClicked: false,
notificationsLength: 0,
}
},
methods: {
openNotification(){
this.isClicked === false ? this.fetchNotification() : '';
this.isClicked = !this.isClicked;
},
fetchNotification(){
this.isLoading = true;
this.submit(route('notifications.list'), 'get', this.section, false, false)
},
successHandler(response){
this.isLoading = false;
this.notifications = response.payload.data;
this.notificationsLength = response.payload.data.length;
},
},
mixins: [ModalFormHandler]
}
</script>
@@ -10,6 +10,9 @@
</div>
</div>
</div>
<div class="col-auto">
<notification-section-component section="section"></notification-section-component>
</div>
<div class="col-auto p-r-20 d-md-none">
<div class="row d-md-none">
<div class="col padding-15 pointer" @click="$store.dispatch('toggleSection', {name: 'sideMenu', status: true})">
+1 -3
View File
@@ -368,6 +368,4 @@ Route::get('/customers/active/{active_start}/{active_end}/{inactive_start?}/{ina
Route::get('/online_payment/redirect', 'Billplz\CallbackBillplzController@callback')->name('online_payment.redirect');
Route::get('/order/{id}', function ($id) {
return view('pages.orders.profile', ['id' => $id]);
})->name('order.details');
Route::get('/notifications/list', 'Notifications\ListNotificationsController@list')->name('notifications.list');