2374 lines
104 KiB
PHP
2374 lines
104 KiB
PHP
{{--
|
|
/**
|
|
* ============================================================
|
|
* @project biiproject
|
|
* @author Andika Debi Putra
|
|
* @email andikadebiputra@gmail.com
|
|
* @website https://biiproject.com
|
|
* @copyright Copyright (c) 2026 Andika Debi Putra
|
|
* @license Proprietary - All Rights Reserved
|
|
* @version 1.0.0
|
|
* @created 2026-05-01
|
|
* ============================================================
|
|
*
|
|
* Unauthorized copying, modification, distribution, or use
|
|
* of this file is strictly prohibited without prior written
|
|
* permission from the author.
|
|
* ============================================================
|
|
*/
|
|
--}}
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
{{-- meta --}}
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
<meta name="user-id" content="{{ auth()->id() }}">
|
|
<title>{{ $app_name ?? config('app.name', 'Laravel') }}</title>
|
|
<link id="dynamic-favicon" rel="icon" type="image/png"
|
|
href="{{ asset($app_favicon ?? 'assets/img/favicon.png') }}?v={{ file_exists(public_path($app_favicon ?? '')) ? filemtime(public_path($app_favicon ?? '')) : time() }}">
|
|
|
|
{{-- font --}}
|
|
@vite(['resources/js/app.js'])
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link
|
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@300;400;500;600;700;800;900&display=swap"
|
|
rel="stylesheet">
|
|
|
|
{{-- dynamic theme logic --}}
|
|
@php
|
|
$mobileConfig = app(\App\Services\MobileConfig\MobileConfigService::class)->all();
|
|
$primaryHex = $mobileConfig['branding']['theme_color_primary'] ?? '#1e1e1e';
|
|
|
|
// Helper to convert hex to rgb
|
|
$hex = str_replace('#', '', $primaryHex);
|
|
if(strlen($hex) == 3) {
|
|
$r = hexdec(substr($hex,0,1).substr($hex,0,1));
|
|
$g = hexdec(substr($hex,1,1).substr($hex,1,1));
|
|
$b = hexdec(substr($hex,2,1).substr($hex,2,1));
|
|
} else {
|
|
$r = hexdec(substr($hex,0,2));
|
|
$g = hexdec(substr($hex,2,2));
|
|
$b = hexdec(substr($hex,4,2));
|
|
}
|
|
|
|
// Helper to convert rgb to hsl
|
|
$r_prime = $r / 255;
|
|
$g_prime = $g / 255;
|
|
$b_prime = $b / 255;
|
|
$max = max($r_prime, $g_prime, $b_prime);
|
|
$min = min($r_prime, $g_prime, $b_prime);
|
|
$l = ($max + $min) / 2;
|
|
$h = $s = 0;
|
|
if ($max != $min) {
|
|
$d = $max - $min;
|
|
$s = $l > 0.5 ? $d / (2 - $max - $min) : $d / ($max + $min);
|
|
switch ($max) {
|
|
case $r_prime: $h = ($g_prime - $b_prime) / $d + ($g_prime < $b_prime ? 6 : 0); break;
|
|
case $g_prime: $h = ($b_prime - $r_prime) / $d + 2; break;
|
|
case $b_prime: $h = ($r_prime - $g_prime) / $d + 4; break;
|
|
}
|
|
$h /= 6;
|
|
}
|
|
$h = round($h * 360);
|
|
$s = round($s * 100);
|
|
$l = round($l * 100);
|
|
@endphp
|
|
|
|
{{-- custom root --}}
|
|
<style>
|
|
:root {
|
|
--adminuiux-content-font: "Inter", sans-serif;
|
|
--adminuiux-title-font: "Outfit", sans-serif;
|
|
|
|
/* Dynamic Theme Variables */
|
|
--adminuiux-theme-1-h: {{ $h }};
|
|
--adminuiux-theme-1-s: {{ $s }}%;
|
|
--adminuiux-theme-1-l: {{ $l }}%;
|
|
--adminuiux-theme-1: hsl(var(--adminuiux-theme-1-h), var(--adminuiux-theme-1-s), var(--adminuiux-theme-1-l));
|
|
--adminuiux-theme-1-rgb: {{ $r }}, {{ $g }}, {{ $b }};
|
|
|
|
/* Theme variations */
|
|
--adminuiux-theme-1-subtle: hsla(var(--adminuiux-theme-1-h), var(--adminuiux-theme-1-s), var(--adminuiux-theme-1-l), 0.1);
|
|
--adminuiux-theme-1-deep: hsl(var(--adminuiux-theme-1-h), var(--adminuiux-theme-1-s), calc(var(--adminuiux-theme-1-l) - 10%));
|
|
}
|
|
|
|
.notification-clickable {
|
|
cursor: pointer;
|
|
transition: transform 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease;
|
|
}
|
|
.notification-clickable:hover {
|
|
background-color: rgba(var(--adminuiux-theme-1-rgb), 0.03) !important;
|
|
transform: translateY(-1px);
|
|
}
|
|
.notification-clickable:active {
|
|
transform: translateY(0);
|
|
}
|
|
.notification-clickable .btn, .notification-clickable a {
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
|
|
/* DATA TABLES MODERNIZATION */
|
|
.dataTables_filter, .dt-search {
|
|
display: none !important;
|
|
}
|
|
table.dataTable {
|
|
border-collapse: separate !important;
|
|
border-spacing: 0 !important;
|
|
margin-top: 1rem !important;
|
|
margin-bottom: 1rem !important;
|
|
border: 0 !important;
|
|
}
|
|
table.dataTable.no-footer {
|
|
border-bottom: 0 !important;
|
|
}
|
|
table.dataTable thead th {
|
|
background-color: #fff !important;
|
|
color: #64748b !important;
|
|
font-size: 0.75rem !important;
|
|
text-transform: uppercase !important;
|
|
letter-spacing: 0.025em !important;
|
|
font-weight: 700 !important;
|
|
padding: 0.75rem 0.75rem !important;
|
|
border-bottom: 1px solid #f1f5f9 !important;
|
|
border-top: 0 !important;
|
|
}
|
|
table.dataTable thead th:first-child,
|
|
table.dataTable tbody td:first-child {
|
|
padding-left: 2rem !important;
|
|
}
|
|
table.dataTable thead th:last-child,
|
|
table.dataTable tbody td:last-child {
|
|
padding-right: 2rem !important;
|
|
}
|
|
table.dataTable thead tr.filter-row th {
|
|
padding: 0.4rem 0.75rem 0.8rem 0.75rem !important;
|
|
background-color: #fff !important;
|
|
border-bottom: 1px solid #f1f5f9 !important;
|
|
}
|
|
table.dataTable tbody td {
|
|
padding: 0.6rem 0.75rem !important;
|
|
border-bottom: 1px solid #f8fafc !important;
|
|
color: #1e293b !important;
|
|
font-size: 0.875rem !important;
|
|
}
|
|
table.dataTable tbody tr:hover {
|
|
background-color: #fcfcfc !important;
|
|
}
|
|
.dataTables_info {
|
|
font-size: 0.8125rem !important;
|
|
color: #64748b !important;
|
|
padding-top: 1.5rem !important;
|
|
}
|
|
.dataTables_length label {
|
|
font-size: 0.8125rem !important;
|
|
color: #64748b !important;
|
|
}
|
|
.dataTables_length select {
|
|
border-radius: 20px !important;
|
|
padding: 0.25rem 0.75rem !important;
|
|
border: 1px solid #e2e8f0 !important;
|
|
margin: 0 0.5rem !important;
|
|
}
|
|
/* Premium Seamless Button Group */
|
|
.dt-buttons {
|
|
display: inline-flex !important;
|
|
gap: 0 !important;
|
|
margin-bottom: 1rem !important;
|
|
}
|
|
.dt-buttons .btn {
|
|
padding: 0.4rem 1.25rem !important;
|
|
font-size: 0.8125rem !important;
|
|
font-weight: 500 !important;
|
|
border: 1px solid #e2e8f0 !important;
|
|
background: #fff !important;
|
|
color: #475569 !important;
|
|
transition: all 0.2s ease;
|
|
margin: 0 !important;
|
|
margin-left: -1px !important;
|
|
border-radius: 0 !important;
|
|
position: relative;
|
|
display: inline-flex !important;
|
|
align-items: center !important;
|
|
justify-content: center !important;
|
|
line-height: 1 !important;
|
|
}
|
|
.dt-buttons .btn:hover {
|
|
background: #f8fafc !important;
|
|
color: #0f172a !important;
|
|
border-color: #cbd5e1 !important;
|
|
z-index: 5 !important;
|
|
}
|
|
.dt-buttons .btn:first-child,
|
|
.dt-buttons .btn-group:first-child .btn:first-child {
|
|
border-top-left-radius: 100px !important;
|
|
border-bottom-left-radius: 100px !important;
|
|
border-top-right-radius: 0 !important;
|
|
border-bottom-right-radius: 0 !important;
|
|
margin-left: 0 !important;
|
|
padding-left: 2rem !important;
|
|
}
|
|
.dt-buttons .btn:last-child,
|
|
.dt-buttons .btn-group:last-child .btn:last-child {
|
|
border-top-right-radius: 100px !important;
|
|
border-bottom-right-radius: 100px !important;
|
|
border-top-left-radius: 0 !important;
|
|
border-bottom-left-radius: 0 !important;
|
|
padding-right: 2rem !important;
|
|
}
|
|
/* Final Aggressive Fix for ColVis dropdown visibility */
|
|
.dt-button-collection {
|
|
z-index: 9999 !important;
|
|
background-color: #ffffff !important;
|
|
border: 1px solid #e2e8f0 !important;
|
|
border-radius: 12px !important;
|
|
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1) !important;
|
|
padding: 10px !important;
|
|
min-width: 200px !important;
|
|
}
|
|
.dt-button-collection .dt-button,
|
|
.dt-button-collection .dropdown-item {
|
|
color: #1e293b !important;
|
|
background: transparent !important;
|
|
padding: 8px 15px !important;
|
|
font-size: 0.8125rem !important;
|
|
border-radius: 6px !important;
|
|
border: none !important;
|
|
display: block !important;
|
|
width: 100% !important;
|
|
text-align: left !important;
|
|
position: relative !important;
|
|
}
|
|
.dt-button-collection .dt-button span {
|
|
color: inherit !important;
|
|
}
|
|
.dt-button-collection .dt-button:hover,
|
|
.dt-button-collection .dropdown-item:hover {
|
|
background-color: #f1f5f9 !important;
|
|
color: #0f172a !important;
|
|
}
|
|
.dt-button-collection .dt-button.active,
|
|
.dt-button-collection .dropdown-item.active {
|
|
background-color: #f1f5f9 !important;
|
|
color: #1e1e1e !important;
|
|
font-weight: 700 !important;
|
|
}
|
|
/* Ensure the text is visible */
|
|
.dt-button-collection .dt-button-text,
|
|
.buttons-columnVisibility .dt-button-text {
|
|
color: #1e293b !important;
|
|
display: inline !important;
|
|
}
|
|
.dataTables_paginate {
|
|
padding-top: 1.5rem !important;
|
|
}
|
|
.dataTables_paginate .paginate_button {
|
|
border: 0 !important;
|
|
background: transparent !important;
|
|
color: #64748b !important;
|
|
font-size: 0.8125rem !important;
|
|
font-weight: 600 !important;
|
|
margin: 0 2px !important;
|
|
border-radius: 50% !important;
|
|
width: 32px !important;
|
|
height: 32px !important;
|
|
padding: 0 !important;
|
|
display: inline-flex !important;
|
|
align-items: center !important;
|
|
justify-content: center !important;
|
|
}
|
|
.dataTables_paginate .paginate_button:hover {
|
|
background: #f1f5f9 !important;
|
|
color: #1e293b !important;
|
|
}
|
|
.dataTables_paginate .paginate_button.current {
|
|
background: #1e1e1e !important;
|
|
color: #fff !important;
|
|
}
|
|
/* Custom Checkbox/Toggle styling in table */
|
|
table.dataTable .form-check-input:checked {
|
|
background-color: #1e1e1e;
|
|
border-color: #1e1e1e;
|
|
}
|
|
|
|
body {
|
|
font-family: var(--adminuiux-content-font);
|
|
color: #1e293b;
|
|
background-color: #f8fafc;
|
|
}
|
|
|
|
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
|
|
font-family: var(--adminuiux-title-font);
|
|
font-weight: 700;
|
|
letter-spacing: -0.02em;
|
|
margin-bottom: 0.5rem;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.card, .adminuiux-card {
|
|
background-color: rgba(255, 255, 255, 0.8) !important;
|
|
backdrop-filter: blur(12px) saturate(180%) !important;
|
|
-webkit-backdrop-filter: blur(12px) saturate(180%) !important;
|
|
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
|
border-radius: 20px !important;
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.02) !important;
|
|
}
|
|
|
|
.adminuiux-sidebar {
|
|
background: rgba(255, 255, 255, 0.7) !important;
|
|
backdrop-filter: blur(25px) saturate(200%) !important;
|
|
-webkit-backdrop-filter: blur(25px) saturate(200%) !important;
|
|
border-right: 1px solid rgba(0, 0, 0, 0.05) !important;
|
|
}
|
|
|
|
.adminuiux-sidebar .nav-link {
|
|
border-radius: 12px !important;
|
|
margin: 2px 15px !important;
|
|
padding: 10px 15px !important;
|
|
transition: all 0.2s ease !important;
|
|
color: #64748b !important;
|
|
font-weight: 500 !important;
|
|
}
|
|
|
|
.adminuiux-sidebar .nav-link:hover {
|
|
background: rgba(0, 0, 0, 0.03) !important;
|
|
color: #1e293b !important;
|
|
transform: translateX(4px) !important;
|
|
}
|
|
|
|
.adminuiux-sidebar .nav-link.active {
|
|
background: var(--adminuiux-theme-1) !important;
|
|
color: {{ $l > 60 ? '#1a1a1a' : '#ffffff' }} !important;
|
|
box-shadow: 0 8px 15px -5px hsla(var(--adminuiux-theme-1-h), var(--adminuiux-theme-1-s), var(--adminuiux-theme-1-l), 0.4) !important;
|
|
}
|
|
|
|
.adminuiux-sidebar .nav-link.active .menu-icon {
|
|
color: inherit !important;
|
|
}
|
|
|
|
.swal2-popup {
|
|
border-radius: 30px !important;
|
|
padding: 2.2rem !important;
|
|
font-family: var(--adminuiux-content-font) !important;
|
|
}
|
|
|
|
.swal2-title {
|
|
font-family: var(--adminuiux-title-font) !important;
|
|
font-weight: 600 !important;
|
|
color: #1e1e1e !important;
|
|
}
|
|
|
|
.swal2-actions {
|
|
gap: 15px !important;
|
|
margin-top: 1.5rem !important;
|
|
}
|
|
|
|
/* Standardized Modern Pill Buttons */
|
|
.btn-theme-1 {
|
|
background-color: var(--adminuiux-theme-1) !important;
|
|
color: {{ $l > 60 ? '#1a1a1a' : '#ffffff' }} !important;
|
|
border: 2px solid var(--adminuiux-theme-1) !important;
|
|
border-radius: 50px !important;
|
|
padding: 10px 28px !important;
|
|
font-weight: 600 !important;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
|
}
|
|
|
|
.btn-theme-1:hover {
|
|
background-color: var(--adminuiux-theme-1-deep) !important;
|
|
border-color: var(--adminuiux-theme-1-deep) !important;
|
|
transform: translateY(-2px) !important;
|
|
box-shadow: 0 10px 20px -5px hsla(var(--adminuiux-theme-1-h), var(--adminuiux-theme-1-s), var(--adminuiux-theme-1-l), 0.4) !important;
|
|
}
|
|
|
|
.btn-pill-primary {
|
|
background-color: #111827 !important;
|
|
color: #ffffff !important;
|
|
border: 1.5px solid #111827 !important;
|
|
border-radius: 50px !important;
|
|
padding: 12px 32px !important;
|
|
font-weight: 600 !important;
|
|
font-family: var(--adminuiux-title-font) !important;
|
|
transition: all 0.2s ease !important;
|
|
min-width: 120px !important;
|
|
font-size: 15px !important;
|
|
margin: 0 !important;
|
|
}
|
|
|
|
.btn-pill-primary:hover {
|
|
background-color: #000000 !important;
|
|
border-color: #000000 !important;
|
|
transform: translateY(-1px) !important;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
|
}
|
|
|
|
.btn-pill-danger {
|
|
background-color: #dc3545 !important;
|
|
color: #ffffff !important;
|
|
border: 2px solid #dc3545 !important;
|
|
border-radius: 50px !important;
|
|
padding: 12px 32px !important;
|
|
font-weight: 600 !important;
|
|
font-family: var(--adminuiux-title-font) !important;
|
|
transition: all 0.2s ease !important;
|
|
min-width: 120px !important;
|
|
font-size: 15px !important;
|
|
margin: 0 !important;
|
|
}
|
|
|
|
.btn-pill-danger:hover {
|
|
background-color: #a71d2a !important;
|
|
border-color: #a71d2a !important;
|
|
transform: translateY(-1px) !important;
|
|
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.2) !important;
|
|
}
|
|
|
|
.btn-pill-cancel {
|
|
background-color: #ffffff !important;
|
|
color: #1e1e1e !important;
|
|
border: 2px solid #1e1e1e !important;
|
|
border-radius: 50px !important;
|
|
padding: 12px 32px !important;
|
|
font-weight: 600 !important;
|
|
font-family: var(--adminuiux-title-font) !important;
|
|
transition: all 0.2s ease !important;
|
|
min-width: 120px !important;
|
|
font-size: 15px !important;
|
|
margin: 0 !important;
|
|
}
|
|
|
|
.btn-pill-cancel:hover {
|
|
background-color: #f8f9fa !important;
|
|
color: #000000 !important;
|
|
border-color: #000000 !important;
|
|
}
|
|
|
|
/* Target specific SWAL classes to override defaults */
|
|
.swal2-confirm.btn-pill-primary,
|
|
.swal2-confirm.btn-pill-danger {
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
.swal2-cancel.btn-pill-cancel {
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
/* GLOBAL FIX: Choices.js UI - Professional Pill Design */
|
|
.choices {
|
|
margin-bottom: 0 !important;
|
|
overflow: visible !important;
|
|
}
|
|
.choices__inner {
|
|
background-color: #ffffff !important;
|
|
border: 1px solid #ced4da !important;
|
|
border-radius: 50rem !important; /* Full pill shape */
|
|
padding: 8px 25px !important; /* Spacious padding */
|
|
min-height: 48px !important;
|
|
display: flex !important;
|
|
align-items: center !important;
|
|
width: 100% !important;
|
|
box-sizing: border-box !important;
|
|
transition: all 0.2s ease !important;
|
|
}
|
|
.choices.is-focused .choices__inner {
|
|
border-color: #1e1e1e !important; /* Professional dark focus */
|
|
box-shadow: 0 0 0 0.2rem rgba(0, 0, 0, 0.05) !important;
|
|
}
|
|
.choices__list--multiple .choices__item {
|
|
background-color: #f1f1f1 !important;
|
|
border: 1px solid #e2e2e2 !important;
|
|
color: #1e1e1e !important;
|
|
border-radius: 50rem !important;
|
|
padding: 2px 14px !important;
|
|
font-weight: 600 !important;
|
|
font-size: 0.8rem !important;
|
|
margin-top: 2px !important;
|
|
margin-bottom: 2px !important;
|
|
}
|
|
.choices__list--multiple .choices__item .choices__button {
|
|
border-left: 1px solid #d1d1d1 !important;
|
|
margin-left: 10px !important;
|
|
filter: grayscale(1);
|
|
}
|
|
.choices__placeholder {
|
|
opacity: 0.6 !important;
|
|
color: #6c757d !important;
|
|
}
|
|
.choices__list--dropdown {
|
|
z-index: 1060 !important;
|
|
border-radius: 1.2rem !important;
|
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.2) !important;
|
|
border: 1px solid #f1f1f1 !important;
|
|
background-color: #ffffff !important;
|
|
}
|
|
.choices__list--dropdown .choices__item--selectable.is-highlighted {
|
|
background-color: #1e1e1e !important;
|
|
color: #ffffff !important;
|
|
}
|
|
.choices__list--multiple .choices__item {
|
|
background-color: #1e1e1e !important;
|
|
border: 1px solid #1e1e1e !important;
|
|
color: #ffffff !important;
|
|
border-radius: 50px !important;
|
|
padding: 4px 14px !important;
|
|
font-weight: 600 !important;
|
|
font-size: 0.85rem !important;
|
|
margin-top: 2px !important;
|
|
margin-bottom: 2px !important;
|
|
}
|
|
.choices__list--multiple .choices__item .choices__button {
|
|
border-left: 1px solid rgba(255, 255, 255, 0.3) !important;
|
|
margin-left: 8px !important;
|
|
opacity: 0.8 !important;
|
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E") !important;
|
|
filter: none !important;
|
|
}
|
|
|
|
.btn-primary {
|
|
--bs-btn-color: #fff !important;
|
|
--bs-btn-bg: #1e1e1e !important;
|
|
--bs-btn-border-color: #1e1e1e !important;
|
|
--bs-btn-hover-color: #fff !important;
|
|
--bs-btn-hover-bg: #000 !important;
|
|
--bs-btn-hover-border-color: #000 !important;
|
|
--bs-btn-focus-shadow-rgb: 30, 30, 30 !important;
|
|
--bs-btn-active-color: #fff !important;
|
|
--bs-btn-active-bg: #000 !important;
|
|
--bs-btn-active-border-color: #000 !important;
|
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, .125) !important;
|
|
--bs-btn-disabled-color: #fff !important;
|
|
--bs-btn-disabled-bg: #1e1e1e !important;
|
|
--bs-btn-disabled-border-color: #1e1e1e !important;
|
|
}
|
|
|
|
/* Soft High-Density Action Buttons */
|
|
.btn-square {
|
|
width: 34px !important;
|
|
height: 34px !important;
|
|
padding: 0 !important;
|
|
display: inline-flex !important;
|
|
align-items: center !important;
|
|
justify-content: center !important;
|
|
border-radius: 10px !important;
|
|
border: none !important;
|
|
}
|
|
|
|
.btn-light-primary {
|
|
background-color: #f0f0f0 !important;
|
|
color: #1e1e1e !important;
|
|
}
|
|
|
|
.btn-light-primary:hover {
|
|
background-color: #1e1e1e !important;
|
|
color: #ffffff !important;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) !important;
|
|
}
|
|
|
|
.btn-light-danger {
|
|
background-color: #fff5f5 !important;
|
|
color: #dc3545 !important;
|
|
}
|
|
|
|
.btn-light-danger:hover {
|
|
background-color: #dc3545 !important;
|
|
color: #ffffff !important;
|
|
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.15) !important;
|
|
}
|
|
|
|
.btn-light-warning {
|
|
background-color: #fff9e6 !important;
|
|
color: #ffc107 !important;
|
|
}
|
|
|
|
.btn-light-warning:hover {
|
|
background-color: #ffc107 !important;
|
|
color: #ffffff !important;
|
|
box-shadow: 0 4px 12px rgba(255, 193, 7, 0.15) !important;
|
|
}
|
|
|
|
/* New Specific Styles to Match Reference Image */
|
|
.btn-pill-standard-primary {
|
|
background-color: #ffffff !important;
|
|
color: #212529 !important;
|
|
border: 1px solid #212529 !important;
|
|
border-radius: 50rem !important;
|
|
padding: 8px 24px !important;
|
|
font-weight: 500 !important;
|
|
font-family: var(--adminuiux-title-font) !important;
|
|
min-width: 110px !important;
|
|
transition: all 0.2s ease !important;
|
|
}
|
|
|
|
.btn-pill-standard-primary:hover {
|
|
background-color: #f8f9fa !important;
|
|
border-color: #000000 !important;
|
|
}
|
|
|
|
.btn-pill-standard-secondary {
|
|
background-color: #ffffff !important;
|
|
color: #212529 !important;
|
|
border: 1px solid #212529 !important;
|
|
border-radius: 50rem !important;
|
|
padding: 8px 24px !important;
|
|
font-weight: 500 !important;
|
|
font-family: var(--adminuiux-title-font) !important;
|
|
min-width: 110px !important;
|
|
transition: all 0.2s ease !important;
|
|
}
|
|
|
|
.btn-pill-standard-secondary:hover {
|
|
background-color: #f8f9fa !important;
|
|
}
|
|
|
|
.text-primary {
|
|
color: var(--adminuiux-theme-1) !important;
|
|
}
|
|
|
|
.bg-primary {
|
|
background-color: var(--adminuiux-theme-1) !important;
|
|
}
|
|
|
|
.border-primary {
|
|
border-color: var(--adminuiux-theme-1) !important;
|
|
}
|
|
|
|
.btn-outline-primary {
|
|
--bs-btn-color: var(--adminuiux-theme-1) !important;
|
|
--bs-btn-border-color: var(--adminuiux-theme-1) !important;
|
|
--bs-btn-hover-color: {{ $l > 60 ? '#1a1a1a' : '#ffffff' }} !important;
|
|
--bs-btn-hover-bg: var(--adminuiux-theme-1) !important;
|
|
--bs-btn-hover-border-color: var(--adminuiux-theme-1) !important;
|
|
}
|
|
|
|
.dt-buttons {
|
|
float: right !important;
|
|
}
|
|
|
|
div.dt-button-collection {
|
|
border-radius: 12px;
|
|
padding: 6px;
|
|
}
|
|
|
|
div.dt-button-collection button.dt-button {
|
|
font-size: 14px;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.permission-clamp {
|
|
display: -webkit-box;
|
|
-webkit-box-orient: vertical;
|
|
-webkit-line-clamp: 1;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.permission-clamp .badge {
|
|
margin-right: 4px;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.badge-slate-pill {
|
|
background-color: #5f6d7a !important;
|
|
color: #ffffff !important;
|
|
border-radius: 50rem !important;
|
|
padding: 0.35em 0.85em !important;
|
|
font-weight: 500 !important;
|
|
font-size: 0.75rem !important;
|
|
display: inline-block;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
/* html {
|
|
zoom: 0.8;
|
|
} */
|
|
|
|
/* Fixed Full Screen Overlays (Loaders, Backdrops, etc.) must cover 100% of viewport */
|
|
/* .pageloader,
|
|
.modal-backdrop,
|
|
.offcanvas-backdrop,
|
|
.swal2-container,
|
|
.select2-container--open,
|
|
.choices__list--dropdown {
|
|
zoom: 1.25;
|
|
} */
|
|
|
|
/* Global 100% Fade-In Policy - Without Exception */
|
|
.adminuiux-content,
|
|
.card,
|
|
.modal-content,
|
|
.offcanvas,
|
|
.dropdown-menu,
|
|
.adminuiux-card,
|
|
.table-responsive,
|
|
.list-group-item {
|
|
animation: fadeIn 0.6s ease-out both !important;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
|
|
/* Ensure Animate.css compatibility for these elements if they use classes */
|
|
.animate__fadeIn {
|
|
animation-name: fadeIn !important;
|
|
}
|
|
|
|
/* Professional Fixed Footer */
|
|
.adminuiux-footer {
|
|
position: fixed !important;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
width: 100%;
|
|
background: rgba(255, 255, 255, 0.7) !important;
|
|
backdrop-filter: blur(20px) saturate(180%) !important;
|
|
-webkit-backdrop-filter: blur(20px) saturate(180%) !important;
|
|
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
|
z-index: 1000;
|
|
padding: 12px 0;
|
|
}
|
|
|
|
/* .adminuiux-content {
|
|
padding-bottom: 80px !important;
|
|
} */
|
|
|
|
/* Unified Global Modal Policy - Majestic UI */
|
|
.modal {
|
|
z-index: 1060 !important;
|
|
/* Level parity with SweetAlert2 */
|
|
}
|
|
|
|
.modal-backdrop {
|
|
z-index: 1050 !important;
|
|
}
|
|
|
|
.modal-backdrop.show,
|
|
.swal2-container.swal2-backdrop-show {
|
|
opacity: 1 !important;
|
|
background-color: rgba(0, 0, 0, 0.10) !important;
|
|
backdrop-filter: blur(3px) !important;
|
|
-webkit-backdrop-filter: blur(3px) !important;
|
|
}
|
|
|
|
/* CRITICAL: Fix for modal trapped behind backdrop due to stacking context */
|
|
body.modal-open .adminuiux-content,
|
|
body.modal-open .adminuiux-content>.container-fluid>.row>div {
|
|
animation: none !important;
|
|
/* Stop animation to break stacking context */
|
|
transform: none !important;
|
|
opacity: 1 !important;
|
|
filter: none !important;
|
|
isolation: auto !important;
|
|
will-change: auto !important;
|
|
}
|
|
|
|
|
|
|
|
.modal-dialog {
|
|
display: flex !important;
|
|
align-items: flex-start !important;
|
|
justify-content: center !important;
|
|
margin-top: 4rem !important;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
width: 850px;
|
|
max-width: 94% !important;
|
|
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) !important;
|
|
}
|
|
|
|
@media (min-width: 992px) {
|
|
.modal-dialog {
|
|
width: 850px !important;
|
|
}
|
|
}
|
|
|
|
/* Large modal override — applies globally to any .modal-xl */
|
|
.modal-dialog.modal-xl,
|
|
.modal-dialog.modal-permission {
|
|
width: calc(70vw - 40px) !important;
|
|
max-width: calc(70vw - 40px) !important;
|
|
margin-top: 20px !important;
|
|
}
|
|
|
|
@media (min-width: 992px) {
|
|
.modal-dialog.modal-xl,
|
|
.modal-dialog.modal-permission {
|
|
width: calc(70vw - 40px) !important;
|
|
max-width: calc(70vw - 40px) !important;
|
|
}
|
|
}
|
|
|
|
.modal-content {
|
|
border: none !important;
|
|
border-radius: 20px !important;
|
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.35) !important;
|
|
background: #ffffff;
|
|
overflow: clip !important;
|
|
}
|
|
.modal-content .modal-header {
|
|
border-radius: 20px 20px 0 0 !important;
|
|
}
|
|
.modal-content .modal-footer {
|
|
border-radius: 0 0 20px 20px !important;
|
|
}
|
|
|
|
.modal.fade .modal-dialog {
|
|
transform: translateY(-20px) !important;
|
|
}
|
|
|
|
.modal.show .modal-dialog {
|
|
transform: translateY(0) !important;
|
|
}
|
|
|
|
/* Floating Top Impersonation Pill */
|
|
.impersonate-floating-pill {
|
|
position: fixed;
|
|
top: 12px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: rgba(255, 193, 7, 0.9);
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
color: #000;
|
|
z-index: 3000;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 6px 12px 6px 18px;
|
|
border-radius: 50px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
font-size: 0.7rem;
|
|
letter-spacing: 0.5px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.impersonate-floating-pill .btn-end {
|
|
background: #000;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 50px;
|
|
padding: 4px 12px;
|
|
font-size: 0.65rem;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.impersonate-floating-pill .btn-end:hover {
|
|
background: #222;
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
@media (max-width: 576px) {
|
|
.impersonate-floating-pill {
|
|
top: 4px;
|
|
padding: 4px 8px 4px 12px;
|
|
font-size: 0.6rem;
|
|
}
|
|
}
|
|
|
|
/* Target User Security Notice (Centered Red Pill) */
|
|
.security-awareness-pill {
|
|
position: fixed;
|
|
top: 12px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: rgba(220, 53, 69, 0.9);
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
color: #fff;
|
|
z-index: 3000;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 8px 24px;
|
|
border-radius: 50px;
|
|
box-shadow: 0 4px 20px rgba(220, 53, 69, 0.3);
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
font-size: 0.75rem;
|
|
letter-spacing: 1px;
|
|
white-space: nowrap;
|
|
/* animation: pulse-danger-majestic 2s infinite; */
|
|
|
|
}
|
|
|
|
@keyframes pulse-danger-majestic {
|
|
0% {
|
|
transform: translate(-50%, 0) scale(1);
|
|
box-shadow: 0 4px 20px rgba(220, 53, 69, 0.3);
|
|
}
|
|
|
|
50% {
|
|
transform: translate(-50%, 0) scale(1.03);
|
|
box-shadow: 0 10px 40px rgba(220, 53, 69, 0.5);
|
|
}
|
|
|
|
100% {
|
|
transform: translate(-50%, 0) scale(1);
|
|
box-shadow: 0 4px 20px rgba(220, 53, 69, 0.3);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 576px) {
|
|
.security-awareness-pill {
|
|
top: 4px;
|
|
padding: 6px 16px;
|
|
font-size: 0.6rem;
|
|
width: 90%;
|
|
justify-content: center;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
{{-- apps css --}}
|
|
<link rel="stylesheet" href="{{ asset('assets/css/app.css') }}">
|
|
|
|
{{-- markdown --}}
|
|
<link rel="stylesheet"
|
|
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown.min.css">
|
|
|
|
<link rel="stylesheet" href="https://cdn.datatables.net/2.3.8/css/dataTables.bootstrap5.min.css">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
|
|
<style>
|
|
/* 🔍 SMART SEARCH (CMD+K) STYLES */
|
|
#global-search-modal .modal-content {
|
|
background: rgba(255, 255, 255, 0.9) !important;
|
|
backdrop-filter: blur(20px) saturate(180%) !important;
|
|
-webkit-backdrop-filter: blur(20px) saturate(180%) !important;
|
|
border: 1px solid rgba(255, 255, 255, 0.5) !important;
|
|
border-radius: 24px !important;
|
|
}
|
|
.search-input-wrapper {
|
|
position: relative;
|
|
padding: 20px;
|
|
border-bottom: 1px solid rgba(0,0,0,0.05);
|
|
}
|
|
#global-search-input {
|
|
border: none !important;
|
|
background: transparent !important;
|
|
font-size: 1.25rem !important;
|
|
font-weight: 500 !important;
|
|
box-shadow: none !important;
|
|
padding-left: 40px !important;
|
|
}
|
|
.search-icon-fixed {
|
|
position: absolute;
|
|
left: 32px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
font-size: 1.2rem;
|
|
color: #adb5bd;
|
|
}
|
|
.search-results-list {
|
|
max-height: 450px;
|
|
overflow-y: auto;
|
|
padding: 10px;
|
|
}
|
|
.search-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 12px 16px;
|
|
border-radius: 14px;
|
|
text-decoration: none;
|
|
color: inherit;
|
|
transition: all 0.2s;
|
|
margin-bottom: 4px;
|
|
}
|
|
.search-item:hover, .search-item.active {
|
|
background: rgba(0,0,0,0.04);
|
|
transform: translateX(5px);
|
|
}
|
|
.search-item .icon-box {
|
|
width: 40px;
|
|
height: 40px;
|
|
background: #fff;
|
|
border-radius: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-right: 15px;
|
|
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);
|
|
}
|
|
.search-item .category-tag {
|
|
font-size: 0.65rem;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
padding: 2px 8px;
|
|
border-radius: 50px;
|
|
background: #f1f5f9;
|
|
color: #64748b;
|
|
margin-left: auto;
|
|
}
|
|
.search-shortcut-hint {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
right: 20px;
|
|
background: rgba(255,255,255,0.8);
|
|
backdrop-filter: blur(10px);
|
|
padding: 8px 16px;
|
|
border-radius: 50px;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
|
|
border: 1px solid rgba(0,0,0,0.05);
|
|
z-index: 999;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
.search-shortcut-hint:hover { transform: scale(1.05); background: #fff; }
|
|
.kbd-capsule {
|
|
background: #f1f5f9;
|
|
border: 1px solid #cbd5e1;
|
|
border-radius: 4px;
|
|
padding: 1px 6px;
|
|
font-family: monospace;
|
|
font-size: 0.7rem;
|
|
box-shadow: 0 2px 0 #cbd5e1;
|
|
margin: 0 2px;
|
|
}
|
|
</style>
|
|
@stack('styles')
|
|
<style>[x-cloak]{display:none!important}</style>
|
|
<style>
|
|
.adminuiux-sidebar .nav-link .bi-chevron-down {
|
|
transition: transform 0.2s ease;
|
|
}
|
|
.adminuiux-sidebar .nav-link:not(.collapsed) .bi-chevron-down {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
/*
|
|
* Align content card top edge with sidebar card top edge.
|
|
* Navbar (fixed-top): py-2 (16px) + logo 32px + boxed outer .5rem (8px) = ~56px.
|
|
* Sidebar-inner: margin-top .5rem = 8px → first visible pixel of sidebar card.
|
|
* Total = 56px + 8px = 64px.
|
|
*/
|
|
main.adminuiux-content {
|
|
padding-top: 64px !important;
|
|
}
|
|
main.adminuiux-content > .container-fluid {
|
|
margin-top: 20px !important;
|
|
padding-top: 0 !important;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body
|
|
class="main-bg main-bg-opac roundedui adminuiux-header-boxed adminuiux-header-transparent adminuiux-sidebar-fill-white adminuiux-sidebar-boxed theme-black bg-gradient-1 scrollup {{ session()->has('impersonator_id') ? 'is-impersonating' : '' }}"
|
|
data-theme="theme-black" data-sidebarfill="adminuiux-sidebar-fill-white" data-bs-spy="scroll"
|
|
data-bs-target="#list-example" data-bs-smooth-scroll="true" tabindex="0"
|
|
data-sidebarlayout="adminuiux-sidebar-boxed" data-headerlayout="adminuiux-header-standard"
|
|
data-headerfill="adminuiux-header-transparent" data-bggradient="bg-gradient-1">
|
|
|
|
{{-- page loader --}}
|
|
<div class="pageloader">
|
|
<div class="container h-100">
|
|
<div class="row justify-content-center align-items-center text-center h-100">
|
|
<div class="col-12 mb-auto pt-4"></div>
|
|
<div class="col-auto">
|
|
<div class="loader5 mb-2 mx-auto"></div>
|
|
</div>
|
|
<div class="col-12 mt-auto pb-4">
|
|
<p class="text-secondary">Please wait for awesome things...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{{-- header --}}
|
|
<header class="adminuiux-header">
|
|
<nav class="navbar navbar-expand-lg fixed-top">
|
|
<div class="container-fluid py-2">
|
|
{{-- sidebar toggle --}}
|
|
<button class="btn btn-link btn-square sidebar-toggler text-dark" type="button" onclick="initSidebar()">
|
|
<i class="sidebar-svg" data-feather="menu"></i>
|
|
</button>
|
|
{{-- logo --}}
|
|
<a class="navbar-brand d-flex align-items-center" href="{{ url('/') }}">
|
|
<div id="header-logo-container" class="me-2">
|
|
<img id="header-logo"
|
|
src="{{ asset($app_logo) }}?v={{ file_exists(public_path($app_logo ?? '')) ? filemtime(public_path($app_logo ?? '')) : time() }}"
|
|
alt="Logo" class="rounded" style="height: 32px; width: auto; object-fit: contain;"
|
|
onerror="this.parentElement.style.display='none';">
|
|
</div>
|
|
<div class="d-block ps-1">
|
|
<h6 class="mb-0 app-name-text">{{ $app_name ?? config('app.name', 'Laravel') }}</h6>
|
|
@if(!empty($app_tagline))
|
|
<p class="company-tagline small mb-0">{{ $app_tagline }}</p>
|
|
@endif
|
|
</div>
|
|
</a>
|
|
{{-- right icons --}}
|
|
<div class="ms-auto">
|
|
<button class="btn btn-link btn-square btnsunmoon btn-link-header d-none"
|
|
id="btn-layout-modes-dark-page">
|
|
<i class="sun mx-auto" data-feather="sun"></i>
|
|
<i class="moon mx-auto" data-feather="moon"></i>
|
|
</button>
|
|
<div class="dropdown d-none">
|
|
<button class="btn btn-link btn-square btn-icon btn-link-header dropdown-toggle no-caret"
|
|
type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
<i class="bi bi-translate"></i>
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-end">
|
|
<li><a class="dropdown-item active" data-value="EN">EN - English</a></li>
|
|
<li><a class="dropdown-item" data-value="ID">ID - Indonesia</a></li>
|
|
</ul>
|
|
</div>
|
|
<button class="btn btn-link btn-square btn-icon btn-link-header position-relative"
|
|
data-bs-toggle="offcanvas" data-bs-target="#view-notification" id="header-bell-btn">
|
|
<i data-feather="bell"></i>
|
|
<span class="position-absolute top-0 end-0 badge rounded-pill bg-danger p-1 d-none"
|
|
id="header-bell-badge">
|
|
<small id="header-badge-count">0</small>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
|
|
@include('layouts.navigation')
|
|
|
|
{{-- page content — inside adminuiux-wrap so theme sidebar/content selectors apply --}}
|
|
<main class="adminuiux-content has-sidebar animate__animated animate__fadeIn" onclick="contentClick()">
|
|
{{ $slot }}
|
|
</main>
|
|
</div>{{-- /adminuiux-wrap --}}
|
|
|
|
{{-- impersonate alert (Floating Top Pill) --}}
|
|
@if(session()->has('impersonator_id'))
|
|
<div class="impersonate-floating-pill animate__animated animate__fadeIn">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<i class="bi bi-incognito fs-6"></i>
|
|
<span><span class="d-none d-sm-inline">{{ __('Acting as') }}:</span> {{ auth()->user()->email }}</span>
|
|
</div>
|
|
<form action="{{ route('impersonate.stop') }}" method="POST" class="m-0">
|
|
@csrf
|
|
<button type="submit" class="btn-end fw-bold">
|
|
{{ __('Stop') }}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- security awareness (For the user BEING impersonated - ONLY show in non-admin session) --}}
|
|
@if(Auth::check() && !session()->has('impersonator_id'))
|
|
<div id="security-awareness-alert"
|
|
class="security-awareness-pill animate__animated animate__fadeIn {{ \Illuminate\Support\Facades\Cache::has('is_being_impersonated:' . Auth::id()) ? '' : 'd-none' }}">
|
|
<i class="bi bi-shield-lock-fill fs-6 animate__animated animate__flash animate__infinite animate__slow"></i>
|
|
<span>{{ __('Security Alert: Admin Access is Active') }}</span>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- notification drawer --}}
|
|
<div class="offcanvas offcanvas-end shadow border-0 maxwidth-300 pt-ios" tabindex="-1" id="view-notification"
|
|
data-bs-scroll="true">
|
|
<div class="offcanvas-header border-bottom">
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-0">{{ __('Notifications') }}</h6>
|
|
<p class="small text-secondary" id="notification-stat-text">{{ __('0 unread updates') }}</p>
|
|
</div>
|
|
<button class="btn btn-sm btn-link text-success text-decoration-none me-2 d-none" id="header-mark-all-read">
|
|
{{ __('Mark all as read') }}
|
|
</button>
|
|
@hasanyrole('Developer|Administrator')
|
|
<button class="btn btn-sm btn-link text-danger text-decoration-none me-2 d-none" id="header-clear-read"
|
|
title="{{ __('Clear all read') }}">
|
|
<i class="bi bi-trash3"></i>
|
|
</button>
|
|
@endhasanyrole
|
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
</div>
|
|
<div class="offcanvas-body px-0 pb-ios" id="header-notification-container">
|
|
<div class="text-center py-5">
|
|
<div class="spinner-border text-secondary spinner-border-sm" role="status"></div>
|
|
<p class="small text-secondary mt-2">{{ __('Loading...') }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="offcanvas-footer p-3 border-top text-center">
|
|
<a href="{{ route('notification-center.index') }}"
|
|
class="btn btn-sm btn-outline-dark fw-semibold rounded-pill px-3">
|
|
{{ __('View Notification Center') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- page footer --}}
|
|
{{-- footer --}}
|
|
<footer class="adminuiux-footer has-adminuiux-sidebar mt-auto d-none">
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12 col-md col-lg py-3">
|
|
<span class="small">
|
|
{{ $footer_text }}
|
|
</span>
|
|
</div>
|
|
<div class="col-12 col-md-auto col-lg-auto align-self-center d-none">
|
|
<ul class="nav small">
|
|
<li class="nav-item"><a class="nav-link" href="#">Help</a></li>
|
|
<li class="nav-item">|</li>
|
|
<li class="nav-item"><a class="nav-link" href="#">Terms of Use</a></li>
|
|
<li class="nav-item">|</li>
|
|
<li class="nav-item"><a class="nav-link" href="#">Privacy Policy</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<div class="position-fixed bottom-0 end-0 m-3 z-index-5 d-none" id="fixedbuttons">
|
|
<button class="btn btn-square btn-theme shadow rounded-circle" type="button" data-bs-toggle="offcanvas"
|
|
data-bs-target="#theming" aria-controls="theming">
|
|
<i class="bi bi-palette"></i>
|
|
</button>
|
|
<br />
|
|
<button class="btn btn-theme btn-square shadow mt-2 d-none rounded-circle" id="backtotop">
|
|
<i class="bi bi-arrow-up"></i>
|
|
</button>
|
|
</div>
|
|
{{-- theming offcanvas --}}
|
|
<div class="offcanvas offcanvas-end shadow border-0" tabindex="-1" id="theming" data-bs-scroll="true"
|
|
data-bs-backdrop="false" aria-labelledby="theminglabel">
|
|
<div class="offcanvas-header border-bottom">
|
|
<div>
|
|
<h5 class="offcanvas-title" id="theminglabel">Personalize</h5>
|
|
<p class="text-secondary small">Make it more like your own</p>
|
|
</div>
|
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
</div>
|
|
<div class="offcanvas-body">
|
|
<h6 class="offcanvas-title">Colors</h6>
|
|
<p class="text-secondary small mb-4">Change colors of templates</p>
|
|
|
|
<div class="row gx-3 mb-4 theme-select">
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-default">
|
|
<i class="bi bi-arrow-clockwise"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-blue">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-blue"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-indigo">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-indigo"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-purple">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-purple"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-pink">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-pink"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-red">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-red"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-orange">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-orange"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-yellow">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-yellow"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-green">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-green"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-teal">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-teal"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-pista">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-pista"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-cyan">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-cyan"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-grey">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-grey"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-brown">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-brown"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-chocolate">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-chocolate"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="theme-black">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-dark"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h6 class="offcanvas-title">Backgrounds</h6>
|
|
<p class="text-secondary small mb-4">Change color for background</p>
|
|
<div class="row gx-3 mb-4 theme-background">
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-default">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-default"><i
|
|
class="bi bi-arrow-clockwise"></i></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-white">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-white"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-r-gradient">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-r-gradient"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-gradient-1">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-gradient-1"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-gradient-2">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-gradient-2"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-gradient-3">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-gradient-3"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-gradient-4">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-gradient-4"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-gradient-5">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-gradient-5"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-gradient-6">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-gradient-6"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-gradient-7">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-gradient-7"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-gradient-8">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-gradient-8"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-gradient-9">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-gradient-9"></span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="gradient-box text-center mb-2" data-title="bg-gradient-10">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-gradient-10"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h6 class="offcanvas-title">Sidebar Layout</h6>
|
|
<p class="text-secondary small mb-4">Change sidebar layout style</p>
|
|
|
|
<div class="row gx-3 mb-4 sidebar-layout">
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="adminuiux-sidebar-standard"
|
|
data-bs-toggle="tooltip" title="None">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-default">
|
|
<i class="bi bi-arrow-clockwise"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="adminuiux-sidebar-iconic"
|
|
data-bs-toggle="tooltip" title="Iconic">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-default">
|
|
<i class="bi bi-bezier h4"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2" data-title="adminuiux-sidebar-boxed"
|
|
data-bs-toggle="tooltip" title="Boxed">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-default">
|
|
<i class="bi bi-box h5"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="select-box text-center mb-2"
|
|
data-title="adminuiux-sidebar-boxed adminuiux-sidebar-iconic" data-bs-toggle="tooltip"
|
|
title="Iconic+Boxed">
|
|
<span class="avatar avatar-40 rounded-circle mb-2 bg-default">
|
|
<i class="bi bi-bounding-box h5"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-center mb-4">
|
|
<a href="mobileux-personalization.html" class="btn btn-sm btn-outline-theme">More options <i
|
|
class="bi bi-arrow-right-short"></i></a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- jQuery --}}
|
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js" crossorigin="anonymous"></script>
|
|
|
|
{{-- DataTables Core --}}
|
|
<script src="https://cdn.datatables.net/2.3.8/js/dataTables.min.js" crossorigin="anonymous"></script>
|
|
<script src="https://cdn.datatables.net/2.3.8/js/dataTables.bootstrap5.min.js" crossorigin="anonymous"></script>
|
|
|
|
{{-- SweetAlert --}}
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11" crossorigin="anonymous"></script>
|
|
|
|
{{-- Markdown JS --}}
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" crossorigin="anonymous"></script>
|
|
|
|
{{-- app js (Modern Vite handled) --}}
|
|
<script
|
|
src="{{ asset('assets/js/app.js') }}?v={{ file_exists(public_path('assets/js/app.js')) ? filemtime(public_path('assets/js/app.js')) : '1' }}"></script>
|
|
<script
|
|
src="{{ asset('assets/js/ajax-form-handler.js') }}?v={{ file_exists(public_path('assets/js/ajax-form-handler.js')) ? filemtime(public_path('assets/js/ajax-form-handler.js')) : '1' }}"></script>
|
|
<script src="{{ asset('assets/js/password-toggle.js') }}"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js" crossorigin="anonymous"></script>
|
|
|
|
{{-- datatables --}}
|
|
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/3.1.2/css/buttons.bootstrap5.min.css">
|
|
<script src="https://cdn.datatables.net/buttons/3.1.2/js/dataTables.buttons.min.js" crossorigin="anonymous"></script>
|
|
<script src="https://cdn.datatables.net/buttons/3.1.2/js/buttons.bootstrap5.min.js" crossorigin="anonymous"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js" crossorigin="anonymous"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js" crossorigin="anonymous"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js" crossorigin="anonymous"></script>
|
|
<script src="https://cdn.datatables.net/buttons/3.1.2/js/buttons.html5.min.js" crossorigin="anonymous"></script>
|
|
<script src="https://cdn.datatables.net/buttons/3.1.2/js/buttons.print.min.js" crossorigin="anonymous"></script>
|
|
<script src="https://cdn.datatables.net/buttons/3.1.2/js/buttons.colVis.min.js" crossorigin="anonymous"></script>
|
|
|
|
{{-- datatables filter --}}
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
const tableElement = document.getElementById('datatables');
|
|
|
|
if (!tableElement) {
|
|
return;
|
|
}
|
|
|
|
const headerCells = Array.from(tableElement.querySelectorAll('thead tr:first-child th'));
|
|
const hiddenTargets = [];
|
|
const nonOrderableTargets = [];
|
|
const nonSearchableTargets = [];
|
|
|
|
headerCells.forEach((cell, index) => {
|
|
if (cell.dataset.hide === 'audit') {
|
|
hiddenTargets.push(index);
|
|
}
|
|
|
|
if (cell.dataset.orderable === 'false') {
|
|
nonOrderableTargets.push(index);
|
|
}
|
|
|
|
if (cell.dataset.searchable === 'false') {
|
|
nonSearchableTargets.push(index);
|
|
}
|
|
});
|
|
|
|
const realTitles = Array.from(tableElement.querySelectorAll('thead tr:first-child th')).map(th => {
|
|
const $clone = $(th).clone();
|
|
$clone.find('input, select, .dt-column-order, .dataTables_sizing').remove();
|
|
let text = $clone.text().trim();
|
|
if (!text && th.querySelector('input[type="checkbox"]')) return 'Checkbox';
|
|
return text;
|
|
});
|
|
|
|
const isServerSide = tableElement.dataset.serverSide === 'true';
|
|
const buttons = [];
|
|
|
|
buttons.push({
|
|
extend: 'colvis',
|
|
text: 'Columns',
|
|
className: 'btn btn-outline-dark btn-sm px-3',
|
|
columnText: function ( dt, idx, title ) {
|
|
return realTitles[idx] || title;
|
|
}
|
|
}, {
|
|
extend: 'copy',
|
|
text: 'Copy',
|
|
className: 'btn btn-outline-dark btn-sm px-3'
|
|
}, {
|
|
extend: 'excel',
|
|
text: 'Excel',
|
|
className: 'btn btn-outline-dark btn-sm px-3',
|
|
filename: 'datatable_export_' + new Date().toISOString().slice(0, 10)
|
|
}, {
|
|
extend: 'print',
|
|
text: 'Print',
|
|
className: 'btn btn-outline-dark btn-sm px-3'
|
|
});
|
|
|
|
const columnDefs = [];
|
|
|
|
if (hiddenTargets.length) {
|
|
columnDefs.push({
|
|
targets: hiddenTargets,
|
|
visible: false,
|
|
});
|
|
}
|
|
|
|
if (nonOrderableTargets.length) {
|
|
columnDefs.push({
|
|
targets: nonOrderableTargets,
|
|
orderable: false,
|
|
});
|
|
}
|
|
|
|
if (nonSearchableTargets.length) {
|
|
columnDefs.push({
|
|
targets: nonSearchableTargets,
|
|
searchable: false,
|
|
});
|
|
}
|
|
|
|
const table = $('#datatables').DataTable({
|
|
destroy: true,
|
|
responsive: true,
|
|
fixedHeader: true,
|
|
autoWidth: false,
|
|
stateSave: false,
|
|
processing: isServerSide,
|
|
serverSide: isServerSide,
|
|
pageLength: 25,
|
|
lengthMenu: [10, 25, 50, 100, 250, 500],
|
|
ajax: isServerSide ? {
|
|
url: tableElement.dataset.ajaxUrl || window.location.href,
|
|
type: 'GET',
|
|
data: function(d) {
|
|
// Dynamically add search values from any .filter-row inputs not already handled
|
|
// Or just pick up specific extra filters
|
|
document.querySelectorAll('.filter-extra').forEach(el => {
|
|
d[el.name || el.id] = el.value;
|
|
});
|
|
return d;
|
|
}
|
|
} : undefined,
|
|
layout: {
|
|
topStart: 'pageLength',
|
|
topEnd: 'buttons',
|
|
bottomStart: 'info',
|
|
bottomEnd: 'paging'
|
|
},
|
|
buttons,
|
|
columnDefs,
|
|
order: tableElement.dataset.order ? JSON.parse(tableElement.dataset.order) : [],
|
|
language: {
|
|
emptyTable: "{{ __('No records found.') }}",
|
|
zeroRecords: "{{ __('No matching records found.') }}"
|
|
},
|
|
createdRow: row => row.classList.add('align-middle'),
|
|
drawCallback: function() {
|
|
window.dispatchEvent(new Event('dataTableReloaded'));
|
|
}
|
|
});
|
|
|
|
window.appDataTable = table;
|
|
window.reloadDataTable = () => {
|
|
if (window.appDataTable?.ajax) {
|
|
window.appDataTable.ajax.reload(null, false);
|
|
return;
|
|
}
|
|
|
|
window.location.reload();
|
|
};
|
|
|
|
$('#datatables thead tr.filter-row th').each(function (i) {
|
|
$(this).find('input,select').on('keyup change', function () {
|
|
table.column(i).search(this.value).draw();
|
|
});
|
|
});
|
|
});
|
|
|
|
// prevent sorting when clicking inside search inputs (header filter)
|
|
$('#datatables thead').on('click', 'input, select', function (e) {
|
|
e.stopPropagation();
|
|
});
|
|
</script>
|
|
|
|
|
|
<script>
|
|
// Global Standardized SweetAlert Mixin
|
|
window.StandardSwal = Swal.mixin({
|
|
buttonsStyling: false,
|
|
padding: '2.2rem',
|
|
customClass: {
|
|
popup: 'swal2-popup',
|
|
title: 'swal2-title',
|
|
confirmButton: 'btn-pill-primary',
|
|
cancelButton: 'btn-pill-cancel'
|
|
}
|
|
});
|
|
|
|
// Fail-safe to hide pageloader if legacy scripts fail
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
setTimeout(function() {
|
|
const loader = document.querySelector('.pageloader');
|
|
if (loader && loader.style.display !== 'none') {
|
|
console.warn('Pageloader still visible after 3s, hiding manually.');
|
|
$(loader).fadeOut('slow');
|
|
}
|
|
}, 3000);
|
|
});
|
|
</script>
|
|
|
|
{{-- SweetAlert for success / common error --}}
|
|
@if (session('success') || session('error'))
|
|
<script>
|
|
StandardSwal.fire({
|
|
icon: "{{ session('success') ? 'success' : 'error' }}",
|
|
title: "{{ session('success') ? 'Success' : 'Error' }}",
|
|
text: "{{ session('success') ?? session('error') }}",
|
|
confirmButtonText: "{{ __('OK') }}",
|
|
timer: "{{ session('success') ? 2000 : null }}",
|
|
timerProgressBar: true
|
|
});
|
|
</script>
|
|
@endif
|
|
|
|
{{-- SweetAlert for validation errors (e.g., unique) --}}
|
|
|
|
@if ($errors->any())
|
|
<script>
|
|
StandardSwal.fire({
|
|
icon: "error",
|
|
title: "Error Validation",
|
|
html: `
|
|
<ul style="text-align:left; font-size: 14px; line-height: 1.6;">
|
|
@foreach ($errors->all() as $error)
|
|
<li>{{ $error }}</li>
|
|
@endforeach
|
|
</ul>
|
|
`,
|
|
confirmButtonText: "OK"
|
|
});
|
|
</script>
|
|
@endif
|
|
|
|
{{-- Real-time & Dynamic Notification Handler --}}
|
|
<script>
|
|
// Shared Render Helper (Moved outside DOMContentLoaded to avoid race condition)
|
|
window.renderNotificationCard = function (n, context = 'default') {
|
|
const isUnread = !!n.is_unread;
|
|
const isSidebar = context === 'sidebar';
|
|
const avatarClass = n.type === 'warning' ? 'bg-warning text-white' : 'bg-info text-white';
|
|
const iconClass = n.type === 'warning' ? 'bi-exclamation-triangle' : 'bi-info-circle';
|
|
|
|
// Robust escaping for JS Template Literals and HTML Attributes
|
|
const escapeJS = (str) => (str || '').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
const escapeAttr = (str) => (str || '').replace(/"/g, '"').replace(/'/g, ''');
|
|
|
|
const safeTitle = escapeJS(n.title);
|
|
const safeMessage = escapeJS(n.message);
|
|
|
|
return `
|
|
<div class="px-3 ${isSidebar ? 'mb-2' : 'mb-3'} animate__animated animate__fadeIn">
|
|
<div class="card adminuiux-card border-0 shadow-none rounded-3 mb-0 notification-clickable ${isUnread ? 'bg-primary-subtle' : (isSidebar ? 'bg-light border-bottom' : 'bg-white border-bottom')}"
|
|
data-title="${escapeAttr(n.title)}"
|
|
data-message="${escapeAttr(n.message)}"
|
|
data-type="${n.type}"
|
|
data-time="${n.time_ago}"
|
|
data-recipient="${n.recipient}">
|
|
<div class="card-body p-3">
|
|
<div class="d-flex align-items-start">
|
|
<div class="avatar avatar-40 rounded-circle ${avatarClass} d-flex align-items-center justify-content-center me-3 flex-shrink-0">
|
|
<i class="bi ${iconClass} fw-bold" style="font-size: 16px;"></i>
|
|
</div>
|
|
<div class="flex-grow-1 overflow-hidden" style="min-width: 0;">
|
|
<div class="d-flex justify-content-between align-items-start mb-1">
|
|
<p class="${isSidebar ? '' : 'h6'} fw-bold text-dark mb-0 text-truncate" title="${escapeAttr(n.title)}">${safeTitle}</p>
|
|
<span class="text-secondary flex-shrink-0 ms-2" style="font-size: 11px;">${n.time_ago}</span>
|
|
</div>
|
|
<div class="text-secondary text-truncate small" style="max-width: 100%;">${safeMessage}</div>
|
|
<div class="d-flex justify-content-end mt-2 gap-2">
|
|
${isUnread ? `
|
|
<button class="btn btn-sm btn-link text-primary p-0 btn-mark-read" data-url="${n.read_url}" style="font-size: 12px; text-decoration: none;">
|
|
<i class="bi bi-check2 me-1"></i>{{ __('Mark Read') }}
|
|
</button>
|
|
` : `
|
|
<span class="text-success fw-medium" style="font-size: 12px;"><i class="bi bi-check2-circle me-1"></i>{{ __('Read') }}</span>
|
|
`}
|
|
${(!isSidebar && n.delete_url) ? `
|
|
<button class="btn btn-sm btn-link text-danger p-0 btn-delete" data-url="${n.delete_url}" style="font-size: 12px; text-decoration: none;">
|
|
<i class="bi bi-trash3 me-1"></i>{{ __('Delete') }}
|
|
</button>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
};
|
|
|
|
window.reloadNotificationUI = function () {
|
|
if (typeof window.updateNotificationUI === 'function') {
|
|
window.updateNotificationUI(false);
|
|
}
|
|
if (typeof window.reloadFeed === 'function') window.reloadFeed();
|
|
};
|
|
|
|
window.showNotificationToast = function (icon, title) {
|
|
StandardSwal.fire({
|
|
icon: icon,
|
|
title: title,
|
|
timer: 2000,
|
|
showConfirmButton: false,
|
|
position: 'center'
|
|
});
|
|
};
|
|
|
|
// Optimistic UI Helper for Read Status
|
|
window.updateNotificationCardToRead = function ($card) {
|
|
if (!$card || !$card.length) return;
|
|
|
|
// 1. Remove unread background
|
|
$card.removeClass('bg-primary-subtle');
|
|
|
|
// 2. Add appropriate "read" background
|
|
const isSidebar = $card.closest('#offcanvasNotification').length > 0;
|
|
if (isSidebar) {
|
|
$card.addClass('bg-light border-bottom');
|
|
} else {
|
|
$card.addClass('bg-white border-bottom');
|
|
}
|
|
|
|
// 3. Update Mark Read button to "Read" badge
|
|
const $actionArea = $card.find('.d-flex.justify-content-end');
|
|
if ($actionArea.length) {
|
|
const deleteBtn = $card.find('.btn-delete');
|
|
const deleteBtnUrl = deleteBtn.length ? deleteBtn.data('url') : '';
|
|
const deleteHtml = (!isSidebar && deleteBtnUrl) ? `
|
|
<button class="btn btn-sm btn-link text-danger p-0 btn-delete" data-url="${deleteBtnUrl}" style="font-size: 13px; text-decoration: none;">
|
|
<i class="bi bi-trash3 me-1"></i>Delete
|
|
</button>
|
|
` : '';
|
|
|
|
$actionArea.html(`
|
|
<span class="text-success fw-medium" style="font-size: 12px;"><i class="bi bi-check2-circle me-1"></i>{{ __('Read') }}</span>
|
|
${deleteHtml}
|
|
`);
|
|
}
|
|
|
|
// 4. Update the unread count badge in header (using correct IDs)
|
|
const $badgeCount = $('#header-badge-count');
|
|
const $bellBadge = $('#header-bell-badge');
|
|
|
|
if ($badgeCount.length) {
|
|
let currentCount = parseInt($badgeCount.text()) || 0;
|
|
if (currentCount > 0) {
|
|
currentCount--;
|
|
if (currentCount <= 0) {
|
|
$badgeCount.text('0');
|
|
if ($bellBadge.length) $bellBadge.addClass('d-none');
|
|
} else {
|
|
$badgeCount.text(currentCount > 9 ? '9+' : currentCount);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const bellBadge = document.getElementById('header-bell-badge');
|
|
const badgeCount = document.getElementById('header-badge-count');
|
|
const offcanvasBody = document.getElementById('header-notification-container');
|
|
const statText = document.getElementById('notification-stat-text');
|
|
const headerMarkAllRead = document.getElementById('header-mark-all-read');
|
|
|
|
let notificationOffset = 0;
|
|
const notificationLimit = 10;
|
|
|
|
// =====================================================
|
|
// NOTIFICATION SYSTEM (Resilient)
|
|
// =====================================================
|
|
try {
|
|
window.updateNotificationUI = function (isLoadMore = false) {
|
|
if (!isLoadMore) notificationOffset = 0;
|
|
|
|
fetch(`{{ route('notification-center.api.recent') }}?offset=${notificationOffset}&limit=${notificationLimit}`, {
|
|
headers: { 'Accept': 'application/json' }
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
try {
|
|
if (!data.success) throw new Error('API reported failure');
|
|
|
|
// Update Badge
|
|
if (bellBadge && badgeCount) {
|
|
if (data.unread_count > 0) {
|
|
bellBadge.classList.remove('d-none');
|
|
badgeCount.innerText = data.unread_count > 9 ? '9+' : data.unread_count;
|
|
} else {
|
|
bellBadge.classList.add('d-none');
|
|
badgeCount.innerText = '0';
|
|
}
|
|
}
|
|
|
|
// Update Stat Text
|
|
if (statText) statText.innerText = `${data.unread_count} unread updates`;
|
|
|
|
// Show/Hide Mark All Read
|
|
if (headerMarkAllRead) {
|
|
if (data.unread_count > 0) headerMarkAllRead.classList.remove('d-none');
|
|
else headerMarkAllRead.classList.add('d-none');
|
|
}
|
|
|
|
// Show/Hide Clear Read (Admin only)
|
|
const headerClearRead = document.getElementById('header-clear-read');
|
|
if (headerClearRead) {
|
|
if (data.notifications && data.notifications.length > 0) headerClearRead.classList.remove('d-none');
|
|
else headerClearRead.classList.add('d-none');
|
|
}
|
|
|
|
// Populate Drawer
|
|
if (offcanvasBody) {
|
|
const notifications = Array.isArray(data.notifications) ? data.notifications : [];
|
|
|
|
if (notifications.length === 0 && !isLoadMore) {
|
|
offcanvasBody.innerHTML = `
|
|
<div class="text-center py-5 opacity-50">
|
|
<i class="bi bi-bell-slash h1 display-1"></i>
|
|
<p class="small mt-2">${"{{ __('No notifications yet') }}"}</p>
|
|
</div>`;
|
|
} else {
|
|
let cardsHtml = '';
|
|
notifications.forEach(n => {
|
|
cardsHtml += window.renderNotificationCard(n, 'sidebar');
|
|
});
|
|
|
|
if (!isLoadMore) {
|
|
offcanvasBody.innerHTML = '<div id="drawer-list-group">' + cardsHtml + '</div>';
|
|
} else {
|
|
$('#drawer-list-group').append(cardsHtml);
|
|
}
|
|
|
|
// Load More Button
|
|
if (data.has_more) {
|
|
if (!$('#drawer-load-more').length) {
|
|
$(offcanvasBody).append(`
|
|
<div class="text-center py-3" id="drawer-load-more-container">
|
|
<button class="btn btn-xs btn-outline-secondary rounded-pill px-4" id="drawer-load-more">Load more</button>
|
|
</div>
|
|
`);
|
|
}
|
|
} else {
|
|
$('#drawer-load-more-container').remove();
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('Notification UI Update Error:', err);
|
|
}
|
|
}).catch(err => console.warn("Notification API currently unavailable."));
|
|
}
|
|
|
|
// Initial Load
|
|
window.updateNotificationUI();
|
|
|
|
// Intercept Offcanvas Opening
|
|
const offcanvasEl = document.getElementById('view-notification');
|
|
if (offcanvasEl) {
|
|
offcanvasEl.addEventListener('show.bs.offcanvas', () => window.updateNotificationUI(false));
|
|
}
|
|
|
|
// Load More Click
|
|
$(document).on('click', '#drawer-load-more', function () {
|
|
notificationOffset += notificationLimit;
|
|
window.updateNotificationUI(true);
|
|
});
|
|
|
|
// Mark All as Read logic
|
|
if (headerMarkAllRead) {
|
|
headerMarkAllRead.addEventListener('click', function () {
|
|
fetch("{{ route('notification-center.read-all') }}", {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
|
'Accept': 'application/json'
|
|
}
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
window.reloadNotificationUI();
|
|
window.showNotificationToast('success', data.message || 'Success');
|
|
}
|
|
})
|
|
.catch(err => window.showNotificationToast('error', 'Failed to process request'));
|
|
});
|
|
}
|
|
|
|
// Mark as Read Action (Shared)
|
|
$(document).on('click', '.btn-mark-read', function (e, extraData) {
|
|
e.preventDefault();
|
|
const $btn = $(this);
|
|
const $card = $btn.closest('.notification-clickable');
|
|
const isSilent = extraData && extraData.silent;
|
|
const url = $btn.data('url');
|
|
|
|
// Optimistic UI Update
|
|
if ($card.length) window.updateNotificationCardToRead($card);
|
|
|
|
fetch(url, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
|
'Accept': 'application/json'
|
|
}
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
window.reloadNotificationUI();
|
|
if (!isSilent) window.showNotificationToast('success', data.message || 'Marked as read');
|
|
}
|
|
})
|
|
.catch(err => {
|
|
if (!isSilent) window.showNotificationToast('error', 'Failed to mark as read');
|
|
});
|
|
});
|
|
|
|
// Header Clear Read
|
|
$(document).on('click', '#header-clear-read', function () {
|
|
StandardSwal.fire({
|
|
title: "Clear all read notifications?",
|
|
text: "This will permanently remove all read updates from your notification center.",
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonText: "Yes, Clear All",
|
|
cancelButtonText: "Cancel",
|
|
}).then(result => {
|
|
if (result.isConfirmed) {
|
|
fetch("{{ route('notification-center.clear-read') }}", {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
|
'Accept': 'application/json'
|
|
}
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
window.reloadNotificationUI();
|
|
window.showNotificationToast('success', data.message || 'Read notifications cleared');
|
|
}
|
|
})
|
|
.catch(err => window.showNotificationToast('error', 'Failed to clear notifications'));
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Real-time Listen (Laravel Reverb)
|
|
if (window.Echo) {
|
|
const currentUserId = document.querySelector('meta[name="user-id"]')?.content;
|
|
const handleNotification = (e) => {
|
|
window.reloadNotificationUI();
|
|
if (currentUserId && e.notification.created_by == currentUserId) return;
|
|
|
|
try {
|
|
const audio = new Audio('data:audio/mp3;base64,//uQxAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAAYAAA4EAAFBwcOCAgRERIWFhkZHBwfHx8jIyInJycrKzAwMTIyNjc3Ozs7Pz9DR0dJSUtLTU1PUFJTVVdbW11dXV9fY2NjZ2dra21tb19fYmJjY2NlZWVnZ2dpZGVmZmVlZWRkY2RiYmJiYmJiYmIAAAADTEFNRTMuOTlyAwAAAAAlAAsAABRCAE8SAAAAAQEAAAcIAAAAHwAAAAAAsK5vSwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/uQxAAAD+oHIAAAAFGZ9fOswABu8H5/uXIAAAAAAAAAAAAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAPWfA8N9oH9vP5/uXIAAAAAAAAAAAAAAAAD/uQxAAAD/LzOABUAAH/KCcACoAA/+5DEAQAA//uQxAoAA//uQxAsAA//uQxAwAA//uQxBAAA//uQxBEAA//uQxBIAA//uQxBMAA//uQxBQAA//uQxBUAA/8A');
|
|
audio.volume = 0.8;
|
|
audio.play().catch(() => { });
|
|
} catch (err) { }
|
|
|
|
if (window.showNotificationToast) {
|
|
window.showNotificationToast(e.notification.type || 'info', e.notification.title || 'New Notification');
|
|
}
|
|
};
|
|
|
|
window.Echo.channel('notifications').listen('.notification.sent', handleNotification);
|
|
@auth
|
|
window.Echo.private('App.Models.User.' + {{ auth()->id() }}).listen('.notification.sent', handleNotification);
|
|
|
|
// Hierarchical Role Channels
|
|
@php
|
|
$userRoles = auth()->user()->getRoleNames()->toArray();
|
|
$listenTo = array_unique($userRoles);
|
|
if (in_array('Developer', $userRoles)) {
|
|
$listenTo = array_unique(array_merge($listenTo, ['Administrator', 'User']));
|
|
} elseif (in_array('Administrator', $userRoles)) {
|
|
$listenTo = array_unique(array_merge($listenTo, ['User']));
|
|
}
|
|
@endphp
|
|
@foreach($listenTo as $roleChannel)
|
|
window.Echo.private('roles.{{ $roleChannel }}').listen('.notification.sent', handleNotification);
|
|
@endforeach
|
|
@endauth
|
|
}
|
|
} catch (globalError) {
|
|
console.error("Critical Failure in Notification System:", globalError);
|
|
}
|
|
|
|
// Majestic UI: Global Modal Re-anchoring to Body
|
|
// Prevents modals from being trapped in animated stacking contexts
|
|
const relocateModals = () => {
|
|
document.querySelectorAll('.modal:not(.relocated)').forEach(modal => {
|
|
document.body.appendChild(modal);
|
|
modal.classList.add('relocated');
|
|
});
|
|
};
|
|
relocateModals();
|
|
// Also watch for dynamically added modals
|
|
const observer = new MutationObserver(relocateModals);
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
});
|
|
|
|
// Sticky Notification Fix: Hide alerts when navigating back to a cached page
|
|
window.addEventListener('pageshow', function(event) {
|
|
if (event.persisted || (window.performance && window.performance.navigation.type === 2)) {
|
|
// Hide common alert/toast elements
|
|
const alerts = document.querySelectorAll('.alert-success, .alert-info, .swal2-container');
|
|
alerts.forEach(el => {
|
|
if (el.classList.contains('swal2-container')) {
|
|
if (window.Swal) Swal.close();
|
|
} else {
|
|
el.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{{-- 🔍 SMART SEARCH (CMD+K) MODAL --}}
|
|
<div class="modal fade" id="global-search-modal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content overflow-hidden">
|
|
<div class="search-input-wrapper">
|
|
<i class="bi bi-search search-icon-fixed"></i>
|
|
<input type="text" id="global-search-input" class="form-control" placeholder="Search users, settings, pages..." autocomplete="off">
|
|
</div>
|
|
<div class="search-results-list" id="search-results">
|
|
<div class="p-5 text-center text-muted">
|
|
<i class="bi bi-keyboard fs-1 mb-3 d-block opacity-25"></i>
|
|
<p>Type something to search across the system...</p>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer bg-light border-0 py-2 px-3 d-flex justify-content-between">
|
|
<div class="small text-muted d-none d-sm-block">
|
|
<span class="kbd-capsule">ESC</span> to close
|
|
<span class="kbd-capsule">↑↓</span> to navigate
|
|
<span class="kbd-capsule">ENTER</span> to select
|
|
</div>
|
|
<div class="small fw-bold text-theme-1">
|
|
<i class="bi bi-lightning-charge-fill me-1"></i> SMART SEARCH
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Shortcut Hint --}}
|
|
<div class="search-shortcut-hint" onclick="$('#global-search-modal').modal('show')">
|
|
<span class="kbd-capsule">⌘</span> <span class="kbd-capsule">K</span> SEARCH
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const searchModal = new bootstrap.Modal('#global-search-modal');
|
|
const searchInput = $('#global-search-input');
|
|
const searchResults = $('#search-results');
|
|
let searchTimeout = null;
|
|
let activeIndex = -1;
|
|
|
|
// Shortcut listener
|
|
$(document).on('keydown', function(e) {
|
|
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
e.preventDefault();
|
|
searchModal.show();
|
|
}
|
|
});
|
|
|
|
$('#global-search-modal').on('shown.bs.modal', function () {
|
|
searchInput.focus();
|
|
});
|
|
|
|
// Clear input & reset state when modal closes
|
|
$('#global-search-modal').on('hidden.bs.modal', function () {
|
|
searchInput.val('');
|
|
searchResults.html('<div class="p-5 text-center text-muted"><i class="bi bi-keyboard fs-1 mb-3 d-block opacity-25"></i><p>Type something to search across the system...</p></div>');
|
|
activeIndex = -1;
|
|
clearTimeout(searchTimeout);
|
|
});
|
|
|
|
// Live Search
|
|
searchInput.on('input', function() {
|
|
const q = $(this).val();
|
|
clearTimeout(searchTimeout);
|
|
|
|
if (q.length < 2) {
|
|
searchResults.html('<div class="p-5 text-center text-muted"><p>Type at least 2 characters...</p></div>');
|
|
activeIndex = -1;
|
|
return;
|
|
}
|
|
|
|
searchResults.html('<div class="p-5 text-center"><div class="spinner-border text-primary" role="status"></div></div>');
|
|
|
|
searchTimeout = setTimeout(() => {
|
|
$.get('{{ route("global-search") }}', { q: q })
|
|
.done(function(data) {
|
|
if (data.length === 0) {
|
|
searchResults.html('<div class="p-5 text-center text-muted"><i class="bi bi-search fs-1 mb-3 d-block opacity-25"></i><p>No results found for "<strong>' + $('<span>').text(q).html() + '</strong>"</p></div>');
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
data.forEach((item, index) => {
|
|
const safeTitle = $('<span>').text(item.title).html();
|
|
const safeSubtitle = $('<span>').text(item.subtitle).html();
|
|
const safeCategory = $('<span>').text(item.category).html();
|
|
const safeIcon = $('<span>').text(item.icon).html();
|
|
const safeUrl = item.url === '#ask-ai' ? '#ask-ai' : $('<span>').text(item.url).html();
|
|
html += `
|
|
<a href="${safeUrl}" class="search-item${item.is_ai ? ' search-item-ai' : ''}" data-index="${index}" data-is-ai="${item.is_ai ? '1' : '0'}">
|
|
<div class="icon-box">
|
|
<i class="bi ${safeIcon} fs-5 ${item.is_ai ? 'text-warning' : 'text-theme-1'}"></i>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<div class="fw-bold text-dark">${safeTitle}</div>
|
|
<div class="small text-muted">${safeSubtitle}</div>
|
|
</div>
|
|
<span class="category-tag${item.is_ai ? ' bg-warning text-dark' : ''}">${safeCategory}</span>
|
|
</a>
|
|
`;
|
|
});
|
|
searchResults.html(html);
|
|
activeIndex = -1;
|
|
})
|
|
.fail(function() {
|
|
searchResults.html('<div class="p-5 text-center text-danger"><i class="bi bi-wifi-off fs-1 mb-3 d-block opacity-50"></i><p>Search unavailable. Please try again.</p></div>');
|
|
});
|
|
}, 300);
|
|
});
|
|
|
|
// Keyboard Navigation
|
|
searchInput.on('keydown', function(e) {
|
|
const items = searchResults.find('.search-item');
|
|
if (items.length === 0) return;
|
|
|
|
if (e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
activeIndex = (activeIndex + 1) % items.length;
|
|
updateActiveItem(items);
|
|
} else if (e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
activeIndex = (activeIndex - 1 + items.length) % items.length;
|
|
updateActiveItem(items);
|
|
} else if (e.key === 'Enter') {
|
|
if (activeIndex > -1) {
|
|
e.preventDefault();
|
|
const activeItem = $(items[activeIndex]);
|
|
if (activeItem.data('is-ai') === 1 || activeItem.attr('href') === '#ask-ai') {
|
|
askAiAssistant(searchInput.val());
|
|
} else {
|
|
window.location.href = activeItem.attr('href');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
$(document).on('click', '.search-item', function(e) {
|
|
if ($(this).data('is-ai') === 1 || $(this).attr('href') === '#ask-ai') {
|
|
e.preventDefault();
|
|
askAiAssistant(searchInput.val());
|
|
}
|
|
});
|
|
|
|
function askAiAssistant(question) {
|
|
searchModal.hide();
|
|
StandardSwal.fire({
|
|
title: '<i class="bi bi-robot text-theme-1 me-2"></i>AI Assistant',
|
|
html: '<div class="text-start py-3"><div class="spinner-border text-primary spinner-border-sm me-2"></div> Thinking...</div>',
|
|
showConfirmButton: false,
|
|
allowOutsideClick: false,
|
|
didOpen: () => {
|
|
$.post('{{ route("ai.assistant.ask") }}', {
|
|
_token: '{{ csrf_token() }}',
|
|
question: question
|
|
}, function(d) {
|
|
StandardSwal.update({
|
|
html: `<div class="text-start py-2" style="line-height: 1.6;">${d.answer.replace(/\n/g, '<br>')}</div>`,
|
|
showConfirmButton: true,
|
|
confirmButtonText: 'Got it!'
|
|
});
|
|
}).fail(function() {
|
|
StandardSwal.fire('Error', 'AI Assistant is currently unavailable.', 'error');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateActiveItem(items) {
|
|
items.removeClass('active');
|
|
if (activeIndex > -1) {
|
|
const active = $(items[activeIndex]);
|
|
active.addClass('active');
|
|
active[0].scrollIntoView({ block: 'nearest' });
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
<!-- Notification Detail Modal -->
|
|
<div class="modal fade" id="notificationDetailModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content border-0 shadow-lg rounded-4 overflow-hidden">
|
|
<div class="modal-header border-0 pb-0">
|
|
<h5 class="modal-title fw-bold" id="notif-detail-title"></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body pt-3">
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div id="notif-detail-avatar" class="avatar avatar-40 rounded-circle d-flex align-items-center justify-content-center me-3">
|
|
<i id="notif-detail-icon" class="bi fw-bold"></i>
|
|
</div>
|
|
<div>
|
|
<div class="small text-secondary" id="notif-detail-time"></div>
|
|
<div class="small fw-bold text-dark" id="notif-detail-sender"></div>
|
|
</div>
|
|
</div>
|
|
<div class="notification-content-wrapper p-3 bg-light rounded-3 mb-3">
|
|
<div id="notif-detail-message" style="line-height: 1.6;"></div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 pt-0">
|
|
<button type="button" class="btn btn-outline-dark rounded-pill px-4" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@stack('scripts')
|
|
<script>
|
|
$(document).on('click', '.notification-clickable', function(e) {
|
|
// Prevent modal if clicking on actions
|
|
if ($(e.target).closest('.header-mark-read-single, .btn-delete, .btn-mark-read').length) {
|
|
return;
|
|
}
|
|
|
|
const $card = $(this);
|
|
const title = $card.data('title');
|
|
let message = $card.data('message');
|
|
const type = $card.data('type');
|
|
const time = $card.data('time');
|
|
const recipient = $card.data('recipient');
|
|
|
|
// Escape raw HTML before markdown-lite conversion to prevent XSS.
|
|
const escapeHtml = (str) => (str || '')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
|
|
if (message) {
|
|
message = escapeHtml(message)
|
|
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
.replace(/\n/g, '<br>');
|
|
}
|
|
|
|
const avatarClass = type === 'warning' ? 'bg-warning text-white' : 'bg-info text-white';
|
|
const iconClass = type === 'warning' ? 'bi-exclamation-triangle' : 'bi-info-circle';
|
|
|
|
$('#notif-detail-title').text(title);
|
|
$('#notif-detail-message').html(message);
|
|
$('#notif-detail-time').text(time);
|
|
$('#notif-detail-sender').text(recipient);
|
|
$('#notif-detail-avatar').removeClass('bg-warning bg-info text-white').addClass(avatarClass);
|
|
$('#notif-detail-icon').removeClass('bi-exclamation-triangle bi-info-circle').addClass(iconClass);
|
|
|
|
new bootstrap.Modal('#notificationDetailModal').show();
|
|
|
|
// Autoread: If unread, mark as read automatically
|
|
const $markReadBtn = $card.find('.btn-mark-read');
|
|
if ($markReadBtn.length && $card.hasClass('bg-primary-subtle')) {
|
|
// Trigger the mark-read logic (this also calls window.updateNotificationCardToRead)
|
|
$markReadBtn.first().trigger('click', [{silent: true}]);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
{{-- Cookie Consent Banner --}}
|
|
@include('layouts.partials.cookie-banner')
|
|
|
|
{{-- Real-time Security Awareness Listener --}}
|
|
@auth
|
|
@if(!session()->has('impersonator_id'))
|
|
<script type="module">
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (window.Echo) {
|
|
window.Echo.private(`App.Models.User.{{ auth()->id() }}`)
|
|
.listen('.impersonation.updated', (e) => {
|
|
const alertEl = document.getElementById('security-awareness-alert');
|
|
if (alertEl) {
|
|
if (e.isImpersonated) {
|
|
alertEl.classList.remove('d-none');
|
|
alertEl.classList.add('animate__fadeIn');
|
|
alertEl.classList.remove('animate__fadeOutUp');
|
|
} else {
|
|
alertEl.classList.add('animate__fadeOutUp');
|
|
setTimeout(() => {
|
|
alertEl.classList.add('d-none');
|
|
}, 800);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
@endif
|
|
@endauth
|
|
</body>
|
|
|
|
</html>
|