DONE: add notification table on kaunter, notifies pc and operasi for pembiayaan online; WIP: notification for gadaian above 10000
This commit is contained in:
+3
-1
@@ -51,4 +51,6 @@ MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
MYKAD_READER_PORT=8085
|
||||
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
|
||||
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
|
||||
|
||||
INTERNAL_NOTIFICATION_SECRET=erahn-local-notification-secret
|
||||
@@ -48,3 +48,6 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
MYKAD_READER_PORT=17321
|
||||
MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED
|
||||
|
||||
# Shared with api_arrahn so the API can write staff inbox rows
|
||||
INTERNAL_NOTIFICATION_SECRET=
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Events\SerahanPenilai;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\CustomerService;
|
||||
use App\Services\NotificationService;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -50,6 +51,37 @@ class GadaianController extends Controller
|
||||
return $this->isFeatureEnabled($kodcaw, 'pembiayaan_online');
|
||||
}
|
||||
|
||||
/**
|
||||
* Inbox row for PC at this branch. Failures must not block gadai.
|
||||
*/
|
||||
protected function notifyPcPembiayaanOnlinePending($auth_user, Request $request, $pinjamanAmt)
|
||||
{
|
||||
try {
|
||||
app(NotificationService::class)->notifyRoleAtCawangan('pc', $auth_user['cawangan'], [
|
||||
'type' => NotificationService::TYPE_PEMBIAYAAN_PENDING_PC,
|
||||
'title' => 'Pembiayaan online menunggu semakan',
|
||||
'body' => sprintf(
|
||||
'%s — %s (RM %s)',
|
||||
$request->nama_pelanggan,
|
||||
$request->norujukan_gadai,
|
||||
number_format((float) $pinjamanAmt, 2)
|
||||
),
|
||||
'url' => route('pembiayaan.online.pc'),
|
||||
'data' => [
|
||||
'norujukan' => $request->norujukan_gadai,
|
||||
'nama_pelanggan' => $request->nama_pelanggan,
|
||||
'pinjaman' => $pinjamanAmt,
|
||||
'created_by' => $auth_user['name'],
|
||||
],
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Pembiayaan online PC notification failed: '.$e->getMessage(), [
|
||||
'norujukan' => $request->norujukan_gadai,
|
||||
'cawangan' => $auth_user['cawangan'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseMoney($value)
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
@@ -278,6 +310,8 @@ class GadaianController extends Controller
|
||||
: 'Gagal daftar pembiayaan online. Sila cuba lagi.';
|
||||
return redirect()->back()->with('error', $msg);
|
||||
}
|
||||
|
||||
$this->notifyPcPembiayaanOnlinePending($auth_user, $request, $pinjamanAmt);
|
||||
} else {
|
||||
$this->AliranTunaiController->alirantunaiGadai($request, $auth_user['name']);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ namespace App\Http\Controllers;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use App\Services\CustomerService;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class HomeController extends Controller
|
||||
@@ -41,17 +39,6 @@ class HomeController extends Controller
|
||||
|
||||
|
||||
|
||||
|
||||
if(session()->has('iklan') && session()->get('iklan') == 0)
|
||||
{
|
||||
session()->put('iklan', 1);
|
||||
}else{
|
||||
if (Hash::check(config('app.reset_password'), $auth_user['password']) || Hash::check($auth_user['no_kp'], $auth_user['password'])) {
|
||||
session()->put('iklan', 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($auth_user['roles'] == 'teller'){
|
||||
if($auth_user['role_harian'] == 'teller'){
|
||||
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
|
||||
@@ -117,16 +104,6 @@ class HomeController extends Controller
|
||||
|
||||
]);
|
||||
|
||||
if(session()->has('iklan') && session()->get('iklan') == 0)
|
||||
{
|
||||
session()->put('iklan', 1);
|
||||
}else{
|
||||
if (Hash::check(config('app.reset_password'), $auth_user['password']) || Hash::check($auth_user['no_kp'], $auth_user['password'])) {
|
||||
session()->put('iklan', 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($auth_user['roles'] == 'teller'){
|
||||
if($auth_user['role_harian'] == 'teller'){
|
||||
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\NotificationService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class InternalNotificationController extends Controller
|
||||
{
|
||||
public function store(Request $request, NotificationService $notifications)
|
||||
{
|
||||
$expected = (string) config('notifications.internal_secret');
|
||||
$provided = (string) $request->header('X-Internal-Token', '');
|
||||
|
||||
if ($expected === '' || !hash_equals($expected, $provided)) {
|
||||
return response()->json(['message' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$audience = $request->input('audience', []);
|
||||
$role = is_array($audience) && !empty($audience['role']) ? $audience['role'] : null;
|
||||
|
||||
if (!$role) {
|
||||
return response()->json(['message' => 'audience.role diperlukan'], 422);
|
||||
}
|
||||
|
||||
$created = $notifications->dispatch($request->all());
|
||||
|
||||
return response()->json([
|
||||
'created' => $created->count(),
|
||||
], 201);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\NotificationService;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class NotificationController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
public function index(NotificationService $notifications)
|
||||
{
|
||||
return response()->json($notifications->inboxPayload(Auth::user()));
|
||||
}
|
||||
|
||||
public function markAsRead(NotificationService $notifications, $id)
|
||||
{
|
||||
$notification = $notifications->markAsRead(Auth::user(), $id);
|
||||
|
||||
if (!$notification) {
|
||||
return response()->json(['message' => 'Notifikasi tidak dijumpai.'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'notification' => $notification->toInboxArray(),
|
||||
'unread_count' => $notifications->unreadCount(Auth::user()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function markAllAsRead(NotificationService $notifications)
|
||||
{
|
||||
$updated = $notifications->markAllAsRead(Auth::user());
|
||||
|
||||
return response()->json([
|
||||
'updated' => $updated,
|
||||
'unread_count' => 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,6 @@ class VerifyCsrfToken extends Middleware
|
||||
* @var array
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
'internal/notifications',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\StaffNotification;
|
||||
use App\User;
|
||||
|
||||
class NotificationService
|
||||
{
|
||||
const TYPE_PEMBIAYAAN_PENDING_PC = 'pembiayaan_online.pending_pc';
|
||||
const TYPE_MODAL_PENDING_OPERASI = 'modal_online.pending_operasi';
|
||||
const TYPE_PEMBIAYAAN_BATCH_OPERASI = 'pembiayaan_online.batch_operasi';
|
||||
const TYPE_PEMBIAYAAN_REJECTED_PC = 'pembiayaan_online.rejected_pc';
|
||||
const TYPE_PEMBIAYAAN_PAID_PC = 'pembiayaan_online.paid_pc';
|
||||
|
||||
/**
|
||||
* Create one inbox row per staff matching role + branch.
|
||||
*
|
||||
* @param string $role
|
||||
* @param string $kodcaw
|
||||
* @param array $payload
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function notifyRoleAtCawangan($role, $kodcaw, array $payload)
|
||||
{
|
||||
$users = User::query()
|
||||
->where('roles', $role)
|
||||
->where('cawangan', $kodcaw)
|
||||
->get();
|
||||
|
||||
$created = collect();
|
||||
foreach ($users as $user) {
|
||||
$created->push($this->notifyUser($user, $kodcaw, $role, $payload));
|
||||
}
|
||||
|
||||
return $created;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create one inbox row per staff matching role (any branch). For HQ like operasi.
|
||||
*
|
||||
* @param string $role
|
||||
* @param array $payload
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function notifyRole($role, array $payload)
|
||||
{
|
||||
$kodcaw = isset($payload['cawangan']) ? $payload['cawangan'] : null;
|
||||
$users = User::query()->where('roles', $role)->get();
|
||||
|
||||
$created = collect();
|
||||
foreach ($users as $user) {
|
||||
$created->push($this->notifyUser($user, $kodcaw, $role, $payload));
|
||||
}
|
||||
|
||||
return $created;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for api_arrahn (and local callers). Builds title/url from type when omitted.
|
||||
*
|
||||
* @param array $input
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function dispatch(array $input)
|
||||
{
|
||||
$type = isset($input['type']) ? $input['type'] : '';
|
||||
$kodcaw = isset($input['cawangan']) ? $input['cawangan'] : null;
|
||||
$audience = isset($input['audience']) && is_array($input['audience']) ? $input['audience'] : [];
|
||||
$role = isset($audience['role']) ? $audience['role'] : null;
|
||||
$data = isset($input['data']) && is_array($input['data']) ? $input['data'] : [];
|
||||
|
||||
$payload = $this->payloadForType($type, $kodcaw, $data);
|
||||
|
||||
if (!empty($input['title'])) {
|
||||
$payload['title'] = $input['title'];
|
||||
}
|
||||
if (array_key_exists('body', $input) && $input['body'] !== null && $input['body'] !== '') {
|
||||
$payload['body'] = $input['body'];
|
||||
}
|
||||
if (!empty($input['url'])) {
|
||||
$payload['url'] = $input['url'];
|
||||
}
|
||||
|
||||
if ($role === 'operasi') {
|
||||
return $this->notifyRole('operasi', $payload);
|
||||
}
|
||||
|
||||
if ($role && $kodcaw) {
|
||||
return $this->notifyRoleAtCawangan($role, $kodcaw, $payload);
|
||||
}
|
||||
|
||||
return collect();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param string|null $kodcaw
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function payloadForType($type, $kodcaw, array $data)
|
||||
{
|
||||
if ($type === self::TYPE_MODAL_PENDING_OPERASI) {
|
||||
$jenis = isset($data['jenis']) ? strtoupper((string) $data['jenis']) : 'HAD';
|
||||
$label = $jenis === 'TOPUP' ? 'tambah modal' : 'had modal';
|
||||
$nama = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw;
|
||||
$amaun = number_format(isset($data['amaun']) ? (float) $data['amaun'] : 0, 2);
|
||||
$oleh = isset($data['dimohon_oleh']) ? trim((string) $data['dimohon_oleh']) : '';
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'cawangan' => $kodcaw,
|
||||
'title' => 'Permohonan modal online',
|
||||
'body' => sprintf(
|
||||
'%s mohon %s RM %s%s',
|
||||
$nama,
|
||||
$label,
|
||||
$amaun,
|
||||
$oleh !== '' ? ' — '.$oleh : ''
|
||||
),
|
||||
'url' => '/pembiayaan-online/operasi/topup',
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
if ($type === self::TYPE_PEMBIAYAAN_BATCH_OPERASI) {
|
||||
$nama = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw;
|
||||
$batchNo = !empty($data['batch_no']) ? $data['batch_no'] : 'batch';
|
||||
$batchId = isset($data['batch_id']) ? $data['batch_id'] : null;
|
||||
$submitted = isset($data['submitted_count']) ? (int) $data['submitted_count'] : 0;
|
||||
$jumlah = number_format(isset($data['jumlah_keseluruhan']) ? (float) $data['jumlah_keseluruhan'] : 0, 2);
|
||||
$oleh = isset($data['pc_submitted_by']) ? trim((string) $data['pc_submitted_by']) : '';
|
||||
$appended = !empty($data['appended']);
|
||||
|
||||
if ($appended) {
|
||||
$body = sprintf(
|
||||
'%s tambah %d rekod ke %s (RM %s)%s',
|
||||
$nama,
|
||||
$submitted,
|
||||
$batchNo,
|
||||
$jumlah,
|
||||
$oleh !== '' ? ' — '.$oleh : ''
|
||||
);
|
||||
} else {
|
||||
$body = sprintf(
|
||||
'%s hantar %s — %d rekod (RM %s)%s',
|
||||
$nama,
|
||||
$batchNo,
|
||||
$submitted,
|
||||
$jumlah,
|
||||
$oleh !== '' ? ' — '.$oleh : ''
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'cawangan' => $kodcaw,
|
||||
'title' => 'Batch pembiayaan online',
|
||||
'body' => $body,
|
||||
'url' => ($kodcaw && $batchId)
|
||||
? '/pembiayaan-online/operasi/'.$kodcaw.'/batches/'.$batchId
|
||||
: '/pembiayaan-online/operasi',
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
if ($type === self::TYPE_PEMBIAYAAN_REJECTED_PC) {
|
||||
$norujukan = !empty($data['norujukan']) ? $data['norujukan'] : 'rekod';
|
||||
$reason = isset($data['reject_reason']) ? trim((string) $data['reject_reason']) : '';
|
||||
$amaun = number_format(isset($data['pinjaman']) ? (float) $data['pinjaman'] : 0, 2);
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'cawangan' => $kodcaw,
|
||||
'title' => 'Pembiayaan online ditolak',
|
||||
'body' => sprintf(
|
||||
'%s (RM %s) ditolak%s',
|
||||
$norujukan,
|
||||
$amaun,
|
||||
$reason !== '' ? ' — '.$reason : ''
|
||||
),
|
||||
'url' => '/pembiayaan-online/pc',
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
if ($type === self::TYPE_PEMBIAYAAN_PAID_PC) {
|
||||
$paidCount = isset($data['paid_count']) ? (int) $data['paid_count'] : 1;
|
||||
$batchNo = !empty($data['batch_no']) ? $data['batch_no'] : '';
|
||||
$norujukan = !empty($data['norujukan']) ? $data['norujukan'] : '';
|
||||
$nama = isset($data['nama_pelanggan']) ? trim((string) $data['nama_pelanggan']) : '';
|
||||
$amaun = number_format(
|
||||
isset($data['pinjaman'])
|
||||
? (float) $data['pinjaman']
|
||||
: (isset($data['jumlah_keseluruhan']) ? (float) $data['jumlah_keseluruhan'] : 0),
|
||||
2
|
||||
);
|
||||
|
||||
if ($paidCount > 1 || ($batchNo !== '' && $norujukan === '')) {
|
||||
$body = sprintf(
|
||||
'%d rekod telah dibayar%s (RM %s)',
|
||||
$paidCount > 0 ? $paidCount : 0,
|
||||
$batchNo !== '' ? ' dalam '.$batchNo : '',
|
||||
$amaun
|
||||
);
|
||||
} else {
|
||||
$body = sprintf(
|
||||
'%s telah dibayar (RM %s)%s',
|
||||
$norujukan !== '' ? $norujukan : 'Rekod',
|
||||
$amaun,
|
||||
$nama !== '' ? ' — '.$nama : ''
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'cawangan' => $kodcaw,
|
||||
'title' => 'Pembiayaan online telah dibayar',
|
||||
'body' => $body,
|
||||
'url' => '/pembiayaan-online/pc/paid',
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'cawangan' => $kodcaw,
|
||||
'title' => isset($data['title']) ? $data['title'] : $type,
|
||||
'body' => isset($data['body']) ? $data['body'] : null,
|
||||
'url' => isset($data['url']) ? $data['url'] : null,
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create one inbox row for a specific user.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param string|null $kodcaw
|
||||
* @param string|null $role
|
||||
* @param array $payload
|
||||
* @return \App\StaffNotification
|
||||
*/
|
||||
public function notifyUser(User $user, $kodcaw, $role, array $payload)
|
||||
{
|
||||
return StaffNotification::create([
|
||||
'user_id' => $user->id,
|
||||
'cawangan' => $kodcaw,
|
||||
'role' => $role ?: $user->roles,
|
||||
'type' => $payload['type'],
|
||||
'title' => $payload['title'],
|
||||
'body' => isset($payload['body']) ? $payload['body'] : null,
|
||||
'url' => isset($payload['url']) ? $payload['url'] : null,
|
||||
'data' => isset($payload['data']) ? $payload['data'] : null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \App\User $user
|
||||
* @param int $limit
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function inboxFor(User $user, $limit = 20)
|
||||
{
|
||||
return StaffNotification::query()
|
||||
->where('user_id', $user->id)
|
||||
->orderByDesc('id')
|
||||
->limit($limit)
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \App\User $user
|
||||
* @return int
|
||||
*/
|
||||
public function unreadCount(User $user)
|
||||
{
|
||||
return StaffNotification::query()
|
||||
->where('user_id', $user->id)
|
||||
->unread()
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \App\User $user
|
||||
* @param int $id
|
||||
* @return \App\StaffNotification|null
|
||||
*/
|
||||
public function markAsRead(User $user, $id)
|
||||
{
|
||||
$notification = StaffNotification::query()
|
||||
->where('user_id', $user->id)
|
||||
->where('id', $id)
|
||||
->first();
|
||||
|
||||
if (!$notification) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($notification->read_at === null) {
|
||||
$notification->read_at = now();
|
||||
$notification->save();
|
||||
}
|
||||
|
||||
return $notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \App\User $user
|
||||
* @return int
|
||||
*/
|
||||
public function markAllAsRead(User $user)
|
||||
{
|
||||
return StaffNotification::query()
|
||||
->where('user_id', $user->id)
|
||||
->unread()
|
||||
->update(['read_at' => now()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \App\User $user
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function inboxPayload(User $user, $limit = 20)
|
||||
{
|
||||
return [
|
||||
'unread_count' => $this->unreadCount($user),
|
||||
'notifications' => $this->inboxFor($user, $limit)->map(function (StaffNotification $notification) {
|
||||
return $notification->toInboxArray();
|
||||
})->values(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class StaffNotification extends Model
|
||||
{
|
||||
protected $table = 'staff_notifications';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'cawangan',
|
||||
'role',
|
||||
'type',
|
||||
'title',
|
||||
'body',
|
||||
'url',
|
||||
'data',
|
||||
'read_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'data' => 'array',
|
||||
'read_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function isRead()
|
||||
{
|
||||
return $this->read_at !== null;
|
||||
}
|
||||
|
||||
public function scopeUnread($query)
|
||||
{
|
||||
return $query->whereNull('read_at');
|
||||
}
|
||||
|
||||
public function toInboxArray()
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'type' => $this->type,
|
||||
'title' => $this->title,
|
||||
'body' => $this->body,
|
||||
'url' => $this->url,
|
||||
'read_at' => $this->read_at ? $this->read_at->toIso8601String() : null,
|
||||
'created_at' => $this->created_at ? $this->created_at->toIso8601String() : null,
|
||||
'created_at_human' => $this->created_at ? $this->created_at->diffForHumans() : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,11 @@ class User extends Authenticatable
|
||||
return $this->hasOne(Cawangan::class, 'kodcaw', 'cawangan');
|
||||
}
|
||||
|
||||
public function staffNotifications()
|
||||
{
|
||||
return $this->hasMany(StaffNotification::class);
|
||||
}
|
||||
|
||||
public function canImpersonate(): bool
|
||||
{
|
||||
return in_array($this->roles ?? '', ['admin'], true);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'internal_secret' => env('INTERNAL_NOTIFICATION_SECRET', ''),
|
||||
];
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateStaffNotificationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('staff_notifications', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->string('cawangan', 20)->nullable();
|
||||
$table->string('role', 30)->nullable();
|
||||
$table->string('type', 80);
|
||||
$table->string('title');
|
||||
$table->text('body')->nullable();
|
||||
$table->string('url', 500)->nullable();
|
||||
$table->text('data')->nullable();
|
||||
$table->timestamp('read_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['user_id', 'read_at']);
|
||||
$table->index(['cawangan', 'type']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('staff_notifications');
|
||||
}
|
||||
}
|
||||
@@ -219,78 +219,70 @@
|
||||
<div class="card-body text-center">
|
||||
|
||||
<div class="row form-group justify-content-center">
|
||||
<div class="col-3 mx-1">
|
||||
<div class="row">
|
||||
<a href="{{ url('dashboard') }}" disabled
|
||||
class="btn btn-info btn-lg btn-block mb-3">Dashboard</a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="{{ url('kakitangan') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Kakitangan</a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="{{ url('kelulusan') }}" id="btnkelulusan"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Kelulusan</a>
|
||||
</div>
|
||||
{{-- <div class="row">
|
||||
<a href="{{ url('permohonan') }}" class="btn btn-danger btn-lg btn-block mb-3">Permohonan</a>
|
||||
</div> --}}
|
||||
<div class="row">
|
||||
<a href="#" data-target="#denominasiShowModal" data-backdrop="static" data-keyboard="false"
|
||||
data-toggle="modal" class="btn btn-info btn-lg btn-block mb-3">Denominasi</a>
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ url('dashboard') }}" disabled
|
||||
class="btn btn-info btn-lg btn-block mb-3">Dashboard</a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="{{ route('khazanah.harga_emas') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Harga Emas</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-3 mx-1">
|
||||
<div class="row">
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ route('khazanah.pelanggan') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Pelanggan</a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ route('lelong') }}" class="btn btn-info btn-lg btn-block mb-3">Lelong</a>
|
||||
</div>
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ url('kakitangan') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Kakitangan</a>
|
||||
</div>
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ route('laporan') }}" class="btn btn-info btn-lg btn-block mb-3">Laporan</a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ url('panjar') }}" class="btn btn-info btn-lg btn-block mb-3">Perbelanjaan</a>
|
||||
</div>
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ url('kelulusan') }}" id="btnkelulusan"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Kelulusan</a>
|
||||
</div>
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ url('menu') }}"
|
||||
id="btn-khazanah"
|
||||
class="btn {{ $countkhazanah > 0 ? 'btn-danger' : 'btn-info' }} btn-lg btn-block mb-3">Khazanah</a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ url('alirantunai') }}" class="btn btn-info btn-lg btn-block mb-3">Aliran
|
||||
Tunai</a>
|
||||
</div>
|
||||
<div class="col-4 px-2">
|
||||
<a href="#" data-target="#denominasiShowModal" data-backdrop="static" data-keyboard="false"
|
||||
data-toggle="modal" class="btn btn-info btn-lg btn-block mb-3">Denominasi</a>
|
||||
</div>
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ url('pendahuluanserahan') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Pendahuluan - Serahan</a>
|
||||
</div>
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ route('khazanah.bajet') }}" class="btn btn-info btn-lg btn-block mb-3">Bajet</a>
|
||||
</div>
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ route('khazanah.harga_emas') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Harga Emas</a>
|
||||
</div>
|
||||
@if(!empty(($cawangan_info['features'] ?? [])['pembiayaan_online']))
|
||||
<div class="row">
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ route('pembiayaan.online.pc') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Pembiayaan Online</a>
|
||||
</div>
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ route('pembiayaan.online.pc.topup') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Had / Modal Online</a>
|
||||
</div>
|
||||
@endif
|
||||
<div class="row">
|
||||
<div class="col-4 px-2">
|
||||
<a href="{{ route('khazanah.indexwakil') }}"
|
||||
class="btn btn-info btn-lg btn-block mb-3">Surat Wakil</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3 mx-1">
|
||||
{{-- <div class="row">
|
||||
<a href="#" class="btn btn-danger btn-lg btn-block mb-3">Capaian</a>
|
||||
</div> --}}
|
||||
<div class="row">
|
||||
<a href="{{ route('lelong') }}" class="btn btn-info btn-lg btn-block mb-3">Lelong</a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="{{ url('panjar') }}" class="btn btn-info btn-lg btn-block mb-3">Perbelanjaan</a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="{{ url('alirantunai') }}" class="btn btn-info btn-lg btn-block mb-3">Aliran
|
||||
Tunai</a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="{{ route('khazanah.bajet') }}" class="btn btn-info btn-lg btn-block mb-3">Bajet</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -214,14 +214,16 @@
|
||||
v-pre
|
||||
style="color: {{ isset($cawangan_info['details_caw']) && $cawangan_info['details_caw'] == 'Putatan 2' ? 'black' : '#1abc9c' }};">
|
||||
<i id="bell" class="fa fa-bell"></i>
|
||||
<span id="staff-notification-badge" class="staff-notification-badge"></span>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
|
||||
<div id="divbiasa">
|
||||
<div class="dropdown-menu dropdown-menu-right staff-notification-menu" aria-labelledby="navbarDropdown">
|
||||
<div id="divbiasa" class="d-none">
|
||||
<a class="dropdown-item" id="linkz" href="#">
|
||||
Tiada Data
|
||||
</a>
|
||||
</div>
|
||||
@include('layouts.partials.notification_inbox')
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -448,28 +450,6 @@
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if(session()->has('iklan') && session()->get('iklan') == 0)
|
||||
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" style="position: absolute; top: 1rem; right: 1rem; width:100%;z-index:1;" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
|
||||
<i class="text-danger fa-2x fa fa-exclamation-triangle mr-2"></i>
|
||||
<strong class="mr-auto text-danger fa-2x">Amaran</strong>
|
||||
{{-- <small>11 mins ago</small> --}}
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
<b style="font-size:18px;">sila tukar kata laluan asal dengan segera!</b>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$('.toast').toast('show');
|
||||
</script>
|
||||
@endif
|
||||
|
||||
|
||||
<main class="py-4">
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
@@ -164,29 +164,6 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@if(session()->has('iklan') && session()->get('iklan') == 0)
|
||||
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" style="position: absolute; top: 1rem; right: 1rem; width:100%;z-index:1;" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
|
||||
<i class="text-danger fa-2x fa fa-exclamation-triangle mr-2"></i>
|
||||
<strong class="mr-auto text-danger fa-2x">Amaran</strong>
|
||||
{{-- <small>11 mins ago</small> --}}
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
<b style="font-size:18px;">sila tukar kata laluan asal dengan segera!</b>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$('.toast').toast('show');
|
||||
</script>
|
||||
@endif
|
||||
|
||||
|
||||
|
||||
<main class="py-4">
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
@@ -111,14 +111,17 @@
|
||||
<li class="nav-item dropdown" style="margin-right:10px;">
|
||||
<a id="navbarDropdown" class="nav-link " href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
|
||||
<i id="bell" class="fa fa-bell"></i>
|
||||
<span id="staff-notification-badge" class="staff-notification-badge"></span>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
|
||||
<div id="divbiasa">
|
||||
<div class="dropdown-menu dropdown-menu-right staff-notification-menu" aria-labelledby="navbarDropdown">
|
||||
<div id="divbiasa" class="d-none">
|
||||
<a class="dropdown-item" id="gadailinkz" href="#">
|
||||
Tiada Notifikasi
|
||||
</a>
|
||||
<a class="dropdown-item" id="linkz" href="#"></a>
|
||||
</div>
|
||||
@include('layouts.partials.notification_inbox')
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -254,29 +257,6 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@if(session()->has('iklan') && session()->get('iklan') == 0)
|
||||
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" style="position: absolute; top: 1rem; right: 1rem; width:100%;z-index:1;" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
|
||||
<i class="text-danger fa-2x fa fa-exclamation-triangle mr-2"></i>
|
||||
<strong class="mr-auto text-danger fa-2x">Amaran</strong>
|
||||
{{-- <small>11 mins ago</small> --}}
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
<b style="font-size:18px;">sila tukar kata laluan asal dengan segera!</b>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$('.toast').toast('show');
|
||||
</script>
|
||||
@endif
|
||||
|
||||
|
||||
|
||||
<main class="py-4">
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
<style>
|
||||
.staff-notification-menu {
|
||||
min-width: 340px;
|
||||
max-width: 380px;
|
||||
padding: 0;
|
||||
}
|
||||
.staff-notification-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
.staff-notification-header button {
|
||||
border: 0;
|
||||
background: none;
|
||||
color: #1abc9c;
|
||||
font-size: 12px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.staff-notification-header button:disabled {
|
||||
color: #bbb;
|
||||
cursor: default;
|
||||
}
|
||||
.staff-notification-list {
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.staff-notification-item {
|
||||
display: block;
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid #f3f3f3;
|
||||
white-space: normal;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
.staff-notification-item:hover {
|
||||
background: #f8f9fa;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
}
|
||||
.staff-notification-item.is-unread {
|
||||
background: #eafaf6;
|
||||
}
|
||||
.staff-notification-item .sn-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.staff-notification-item.is-unread .sn-title {
|
||||
font-weight: 700;
|
||||
}
|
||||
.staff-notification-item .sn-body,
|
||||
.staff-notification-item .sn-time {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
.staff-notification-empty {
|
||||
padding: 18px 14px;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
font-size: 13px;
|
||||
}
|
||||
.staff-notification-badge {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 0;
|
||||
font-size: 10px;
|
||||
min-width: 16px;
|
||||
padding: 2px 5px;
|
||||
border-radius: 10px;
|
||||
background: #e74c3c;
|
||||
color: #fff;
|
||||
line-height: 1.2;
|
||||
display: none;
|
||||
}
|
||||
.staff-notification-toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
a.nav-link #staff-notification-badge {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: -4px;
|
||||
}
|
||||
li.nav-item.dropdown > a.nav-link {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="staff-notification-inbox">
|
||||
<div class="staff-notification-header">
|
||||
<span>Notifikasi</span>
|
||||
<button type="button" id="staff-notification-mark-all" disabled>Tanda semua dibaca</button>
|
||||
</div>
|
||||
<div id="staff-notification-list" class="staff-notification-list">
|
||||
<div class="staff-notification-empty">Memuatkan...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var listUrl = @json(route('notifications.index'));
|
||||
var markAllUrl = @json(route('notifications.read.all'));
|
||||
var markOneUrlTemplate = @json(url('/notifications/__ID__/read'));
|
||||
var csrf = document.querySelector('meta[name="csrf-token"]');
|
||||
var csrfToken = csrf ? csrf.getAttribute('content') : '';
|
||||
|
||||
function markOneUrl(id) {
|
||||
return markOneUrlTemplate.replace('__ID__', id);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value == null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function setBadge(count) {
|
||||
var badge = document.getElementById('staff-notification-badge');
|
||||
var bell = document.getElementById('bell');
|
||||
if (!badge) {
|
||||
return;
|
||||
}
|
||||
if (count > 0) {
|
||||
badge.style.display = 'inline-block';
|
||||
badge.textContent = count > 99 ? '99+' : String(count);
|
||||
if (bell) {
|
||||
bell.style.color = '#e74c3c';
|
||||
}
|
||||
} else {
|
||||
badge.style.display = 'none';
|
||||
badge.textContent = '';
|
||||
if (bell) {
|
||||
bell.style.color = '';
|
||||
}
|
||||
}
|
||||
var markAll = document.getElementById('staff-notification-mark-all');
|
||||
if (markAll) {
|
||||
markAll.disabled = count < 1;
|
||||
}
|
||||
}
|
||||
|
||||
function render(payload) {
|
||||
var wrap = document.getElementById('staff-notification-list');
|
||||
if (!wrap) {
|
||||
return;
|
||||
}
|
||||
var items = payload.notifications || [];
|
||||
setBadge(payload.unread_count || 0);
|
||||
|
||||
if (!items.length) {
|
||||
wrap.innerHTML = '<div class="staff-notification-empty">Tiada notifikasi</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
wrap.innerHTML = items.map(function (item) {
|
||||
var unread = !item.read_at;
|
||||
return (
|
||||
'<a class="dropdown-item staff-notification-item' + (unread ? ' is-unread' : '') + '" href="' + escapeHtml(item.url || '#') + '" data-id="' + item.id + '">' +
|
||||
'<div class="sn-title">' + escapeHtml(item.title) + '</div>' +
|
||||
(item.body ? '<p class="sn-body">' + escapeHtml(item.body) + '</p>' : '') +
|
||||
'<p class="sn-time">' + escapeHtml(item.created_at_human || '') + '</p>' +
|
||||
'</a>'
|
||||
);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function loadInbox() {
|
||||
return fetch(listUrl, {
|
||||
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
|
||||
credentials: 'same-origin'
|
||||
}).then(function (res) {
|
||||
if (!res.ok) {
|
||||
throw new Error('Gagal muat notifikasi');
|
||||
}
|
||||
return res.json();
|
||||
}).then(render).catch(function () {
|
||||
var wrap = document.getElementById('staff-notification-list');
|
||||
if (wrap) {
|
||||
wrap.innerHTML = '<div class="staff-notification-empty">Tidak dapat memuatkan notifikasi</div>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function postJson(url) {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-CSRF-TOKEN': csrfToken
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: '{}'
|
||||
}).then(function (res) {
|
||||
if (!res.ok) {
|
||||
throw new Error('Gagal kemaskini notifikasi');
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
var target = event.target;
|
||||
if (target && target.nodeType !== 1) {
|
||||
target = target.parentElement;
|
||||
}
|
||||
if (!target || !target.closest) {
|
||||
return;
|
||||
}
|
||||
var item = target.closest('.staff-notification-item');
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
var id = item.getAttribute('data-id');
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
postJson(markOneUrl(id)).catch(function () {});
|
||||
});
|
||||
|
||||
var markAll = document.getElementById('staff-notification-mark-all');
|
||||
if (markAll) {
|
||||
markAll.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
postJson(markAllUrl).then(function () {
|
||||
loadInbox();
|
||||
}).catch(function () {});
|
||||
});
|
||||
}
|
||||
|
||||
loadInbox();
|
||||
setInterval(loadInbox, 30000);
|
||||
})();
|
||||
</script>
|
||||
@@ -62,7 +62,7 @@
|
||||
@endif
|
||||
@if($approvedCount > 0)
|
||||
<button type="button" class="btn btn-dark btn-sm" data-toggle="modal" data-target="#paidBatchModal">
|
||||
Tanda PAID Batch ({{ $approvedCount }})
|
||||
Tanda semua sebagai dibayar ({{ $approvedCount }})
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
@@ -133,7 +133,7 @@
|
||||
class="btn btn-dark btn-sm btn-paid"
|
||||
data-id="{{ $line['id'] }}"
|
||||
data-norujukan="{{ $line['norujukan'] ?? '' }}">
|
||||
PAID + Bukti
|
||||
Dibayar + Bukti
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-outline-danger btn-sm btn-reject"
|
||||
@@ -211,7 +211,7 @@
|
||||
<input type="hidden" name="batch_id" value="{{ $batch['id'] }}">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Tanda PAID — <span id="paidNorujukan"></span></h5>
|
||||
<h5 class="modal-title">Tanda sebagai dibayar — <span id="paidNorujukan"></span></h5>
|
||||
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -231,7 +231,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-dark">Simpan PAID</button>
|
||||
<button type="submit" class="btn btn-dark">Simpan sebagai dibayar</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -247,7 +247,7 @@
|
||||
@csrf
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Tanda PAID Semua APPROVED ({{ $approvedCount }})</h5>
|
||||
<h5 class="modal-title">Tanda semua sebagai dibayar ({{ $approvedCount }})</h5>
|
||||
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -267,7 +267,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-dark">Simpan PAID Batch</button>
|
||||
<button type="submit" class="btn btn-dark">Simpan semua sebagai dibayar</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<div class="card-header d-flex justify-content-between align-items-center flex-wrap">
|
||||
<h5 class="mb-0">Senarai Pembiayaan Online — PAID</h5>
|
||||
<button type="button" class="btn btn-success btn-sm mt-2 mt-md-0" id="btnMarkBlast" disabled>
|
||||
Sahkan Tally / Selesai
|
||||
Sahkan semua sebagai dibayar
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@@ -168,7 +168,7 @@
|
||||
function syncBlastButton() {
|
||||
var n = $('.row-check:checked').length;
|
||||
$('#btnMarkBlast').prop('disabled', n === 0);
|
||||
$('#btnMarkBlast').text(n ? ('Sahkan Tally / Selesai (' + n + ')') : 'Sahkan Tally / Selesai');
|
||||
$('#btnMarkBlast').text(n ? ('Sahkan semua sebagai dibayar (' + n + ')') : 'Sahkan semua sebagai dibayar');
|
||||
}
|
||||
|
||||
$('#checkAll').on('change', function () {
|
||||
@@ -180,7 +180,7 @@
|
||||
|
||||
$('#btnMarkBlast').on('click', function () {
|
||||
if ($('.row-check:checked').length === 0) return;
|
||||
if (!confirm('Sahkan tally untuk rekod dipilih? Rekod akan ditanda selesai dan WhatsApp notis akan dihantar.')) return;
|
||||
if (!confirm('Sahkan semua sebagai dibayar untuk rekod dipilih? Rekod akan ditanda sebagai dibayar dan WhatsApp notis akan dihantar.')) return;
|
||||
$('#formMarkBlast').submit();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -15,6 +15,8 @@ use Illuminate\Support\Facades\Storage;
|
||||
|
|
||||
*/
|
||||
|
||||
Route::post('/internal/notifications', ['as' => 'internal.notifications.store', 'uses' => 'InternalNotificationController@store']);
|
||||
|
||||
Route::get('/health', function () {
|
||||
return response()->json(['status' => 'ok']);
|
||||
});
|
||||
@@ -190,6 +192,10 @@ Route::get('/penebusankhazanah/{norujukan}',['as'=>'penebusan.khazanah','uses' =
|
||||
Route::get('/lelongankhazanah/{norujukan}',['as'=>'lelongan.khazanah','uses' => 'KhazanahController@lelongan'])->middleware('auth','khazanah');
|
||||
Route::get('/permohonan',['as'=>'permohonan.index','uses' => 'KhazanahController@PaparanPermohonan'])->middleware('auth','khazanah');
|
||||
|
||||
Route::get('/notifications',['as'=>'notifications.index','uses'=>'NotificationController@index'])->middleware('auth');
|
||||
Route::post('/notifications/read-all',['as'=>'notifications.read.all','uses'=>'NotificationController@markAllAsRead'])->middleware('auth');
|
||||
Route::post('/notifications/{id}/read',['as'=>'notifications.read','uses'=>'NotificationController@markAsRead'])->middleware('auth');
|
||||
|
||||
// Pembiayaan Online — PC
|
||||
Route::get('/pembiayaan-online/pc',['as'=>'pembiayaan.online.pc','uses'=>'PembiayaanOnlineController@index'])->middleware('auth','khazanah');
|
||||
Route::put('/pembiayaan-online/pc/lines/{id}',['as'=>'pembiayaan.online.pc.update','uses'=>'PembiayaanOnlineController@updateLine'])->middleware('auth','khazanah');
|
||||
|
||||
Reference in New Issue
Block a user