1985 lines
105 KiB
PHP
1985 lines
105 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Control Panel</title>
|
|
<!-- Google Fonts: Outfit & Inter -->
|
|
<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@400;600;700;800&display=swap" rel="stylesheet">
|
|
|
|
<!-- SweetAlert2 -->
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
|
|
<!-- jQuery (required for Select2) -->
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
<!-- Select2 CSS -->
|
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
|
<!-- Select2 JS -->
|
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
|
|
|
<style>
|
|
:root {
|
|
--bg-color: #0b0f19;
|
|
--card-bg: rgba(15, 23, 42, 0.55); /* Darker translucent glass base */
|
|
--card-border: rgba(255, 255, 255, 0.22); /* Crisp gloss border */
|
|
--accent-primary: #6366f1;
|
|
--accent-secondary: #0078d4;
|
|
--text-main: #ffffff;
|
|
--text-muted: #d1d5db;
|
|
--success-color: #10b981;
|
|
--error-color: #ef4444;
|
|
--info-color: #06b6d4;
|
|
}
|
|
|
|
/* Select2 Premium Theme Overrides */
|
|
.select2-container--default .select2-selection--multiple {
|
|
background-color: rgba(255, 255, 255, 0.03) !important;
|
|
border: 1px solid var(--card-border) !important;
|
|
border-radius: 8px !important;
|
|
min-height: 42px !important;
|
|
padding: 2px 6px !important;
|
|
transition: all 0.2s ease !important;
|
|
}
|
|
|
|
.select2-container--default.select2-container--focus .select2-selection--multiple {
|
|
border-color: var(--accent-primary) !important;
|
|
background-color: rgba(255, 255, 255, 0.06) !important;
|
|
box-shadow: 0 0 10px rgba(99, 102, 241, 0.25) !important;
|
|
}
|
|
|
|
.select2-container--default .select2-selection--multiple .select2-selection__choice {
|
|
background-color: var(--accent-primary) !important;
|
|
border: none !important;
|
|
color: white !important;
|
|
border-radius: 6px !important;
|
|
padding: 2px 10px !important;
|
|
font-size: 0.8rem !important;
|
|
font-weight: 600 !important;
|
|
font-family: 'Outfit', sans-serif !important;
|
|
margin-top: 6px !important;
|
|
}
|
|
|
|
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
|
|
color: rgba(255, 255, 255, 0.8) !important;
|
|
margin-right: 6px !important;
|
|
border: none !important;
|
|
background: none !important;
|
|
}
|
|
|
|
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
|
|
color: white !important;
|
|
background: none !important;
|
|
}
|
|
|
|
.select2-container--default .select2-search--inline .select2-search__field {
|
|
color: var(--text-main) !important;
|
|
font-family: 'Inter', sans-serif !important;
|
|
font-size: 0.85rem !important;
|
|
margin-top: 8px !important;
|
|
}
|
|
|
|
.select2-dropdown {
|
|
background-color: #151b2c !important;
|
|
border: 1px solid var(--card-border) !important;
|
|
border-radius: 8px !important;
|
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5) !important;
|
|
backdrop-filter: blur(20px) saturate(120%) !important;
|
|
-webkit-backdrop-filter: blur(20px) saturate(120%) !important;
|
|
z-index: 9999 !important;
|
|
}
|
|
|
|
.select2-container--default .select2-results__option {
|
|
padding: 8px 12px !important;
|
|
font-size: 0.85rem !important;
|
|
color: var(--text-main) !important;
|
|
}
|
|
|
|
.select2-container--default .select2-results__option--highlighted[aria-selected] {
|
|
background-color: var(--accent-primary) !important;
|
|
color: white !important;
|
|
}
|
|
|
|
.select2-container--default .select2-results__option[aria-selected=true] {
|
|
background-color: rgba(99, 102, 241, 0.2) !important;
|
|
color: var(--text-main) !important;
|
|
}
|
|
|
|
.select2-container--default .select2-search--dropdown .select2-search__field {
|
|
background-color: rgba(0, 0, 0, 0.2) !important;
|
|
border: 1px solid var(--card-border) !important;
|
|
border-radius: 6px !important;
|
|
color: var(--text-main) !important;
|
|
padding: 6px 10px !important;
|
|
outline: none !important;
|
|
}
|
|
|
|
/* Modern Ultra-Thin White Custom Scrollbars */
|
|
html, body, *, ::-webkit-scrollbar {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: rgba(255, 255, 255, 0.4) rgba(15, 23, 42, 0.35);
|
|
}
|
|
|
|
::-webkit-scrollbar {
|
|
width: 5px;
|
|
height: 5px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: rgba(15, 23, 42, 0.35);
|
|
border-radius: 10px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: rgba(255, 255, 255, 0.4);
|
|
border-radius: 10px;
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(255, 255, 255, 0.85);
|
|
box-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
|
|
}
|
|
|
|
::-webkit-scrollbar-corner {
|
|
background: transparent;
|
|
}
|
|
|
|
.select2-results__options {
|
|
max-height: 220px !important;
|
|
overflow-y: auto !important;
|
|
scrollbar-width: thin !important;
|
|
scrollbar-color: rgba(255, 255, 255, 0.4) rgba(15, 23, 42, 0.35) !important;
|
|
}
|
|
|
|
.select2-results__options::-webkit-scrollbar {
|
|
width: 4px !important;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
background-color: var(--bg-color);
|
|
color: var(--text-main);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow-x: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
/* Glowing background circles */
|
|
body::before {
|
|
content: '';
|
|
position: absolute;
|
|
width: 500px;
|
|
height: 500px;
|
|
border-radius: 50%;
|
|
background: radial-gradient(circle, rgba(99, 102, 241, 0.12) 0%, rgba(0, 0, 0, 0) 70%);
|
|
top: -200px;
|
|
left: -100px;
|
|
z-index: -1;
|
|
}
|
|
|
|
body::after {
|
|
content: '';
|
|
position: absolute;
|
|
width: 400px;
|
|
height: 400px;
|
|
border-radius: 50%;
|
|
background: radial-gradient(circle, rgba(0, 120, 212, 0.1) 0%, rgba(0, 0, 0, 0) 70%);
|
|
bottom: -100px;
|
|
right: -100px;
|
|
z-index: -1;
|
|
}
|
|
|
|
/* Navigation Bar */
|
|
.navbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 20px 5%;
|
|
background: rgba(17, 24, 39, 0.4);
|
|
border-bottom: 1px solid var(--card-border);
|
|
backdrop-filter: blur(12px);
|
|
-webkit-backdrop-filter: blur(12px);
|
|
z-index: 100;
|
|
}
|
|
|
|
.nav-logo {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.5rem;
|
|
font-weight: 800;
|
|
color: #ffffff;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.nav-logo svg {
|
|
width: 24px;
|
|
height: 24px;
|
|
fill: #ffffff;
|
|
}
|
|
|
|
.nav-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.btn-nav {
|
|
color: #e5e7eb;
|
|
text-decoration: none;
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
padding: 8px 16px;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--card-border);
|
|
transition: all 0.25s ease;
|
|
}
|
|
|
|
.btn-nav:hover {
|
|
color: var(--text-main);
|
|
border-color: rgba(255, 255, 255, 0.3);
|
|
background-color: rgba(255, 255, 255, 0.08);
|
|
}
|
|
|
|
.btn-logout {
|
|
color: #ef4444;
|
|
border-color: rgba(239, 68, 68, 0.25);
|
|
}
|
|
|
|
.btn-logout:hover {
|
|
color: white;
|
|
background-color: var(--error-color);
|
|
border-color: transparent;
|
|
}
|
|
|
|
/* Dashboard Wrapper */
|
|
.dashboard-container {
|
|
flex: 1;
|
|
padding: 30px 4%;
|
|
max-width: 1400px;
|
|
width: 100%;
|
|
margin: 0 auto;
|
|
z-index: 10;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
/* Management Grid - 2 cards per row on desktop, 3rd card shifts to lower row */
|
|
.management-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 30px;
|
|
margin-bottom: 50px;
|
|
width: 100%;
|
|
align-items: start;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.management-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
/* Welcome Section */
|
|
.admin-header {
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.admin-header h1 {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
color: #ffffff;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.admin-header p {
|
|
color: #e5e7eb;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
/* Statistics Cards Grid */
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 22px;
|
|
margin-bottom: 40px;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.stat-card {
|
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.02) 100%), var(--card-bg);
|
|
border: 2px solid var(--card-border);
|
|
border-radius: 18px;
|
|
padding: 26px;
|
|
backdrop-filter: blur(25px) saturate(130%);
|
|
-webkit-backdrop-filter: blur(25px) saturate(130%);
|
|
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3), inset 0 1px 2px rgba(255, 255, 255, 0.18), inset 0 -1px 1px rgba(255, 255, 255, 0.05);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
|
|
.stat-card:hover {
|
|
transform: translateY(-2px);
|
|
border-color: rgba(255, 255, 255, 0.35);
|
|
box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.4), inset 0 1px 2px rgba(255, 255, 255, 0.28);
|
|
}
|
|
|
|
.admin-card {
|
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.02) 100%), var(--card-bg);
|
|
border: 2px solid var(--card-border);
|
|
border-radius: 24px;
|
|
padding: 34px;
|
|
backdrop-filter: blur(25px) saturate(130%);
|
|
-webkit-backdrop-filter: blur(25px) saturate(130%);
|
|
box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.35), inset 0 1px 2px rgba(255, 255, 255, 0.18);
|
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: flex-start;
|
|
min-height: 450px;
|
|
}
|
|
|
|
.admin-card:hover {
|
|
border-color: rgba(255, 255, 255, 0.35);
|
|
box-shadow: 0 16px 48px 0 rgba(0, 0, 0, 0.45), inset 0 1px 2px rgba(255, 255, 255, 0.28);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 0.8rem;
|
|
color: #d1d5db;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-bottom: 6px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.stat-value {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.8rem;
|
|
font-weight: 800;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.stat-icon {
|
|
font-size: 2rem;
|
|
opacity: 0.85;
|
|
}
|
|
|
|
/* Users Management Card */
|
|
.users-card {
|
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.02) 100%), var(--card-bg);
|
|
border: 2px solid var(--card-border);
|
|
border-radius: 24px;
|
|
padding: 34px;
|
|
backdrop-filter: blur(25px) saturate(130%);
|
|
-webkit-backdrop-filter: blur(25px) saturate(130%);
|
|
box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.35), inset 0 1px 2px rgba(255, 255, 255, 0.18);
|
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
|
|
.users-card:hover {
|
|
border-color: rgba(255, 255, 255, 0.35);
|
|
box-shadow: 0 16px 48px 0 rgba(0, 0, 0, 0.45), inset 0 1px 2px rgba(255, 255, 255, 0.28);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
/* Users Table styling */
|
|
.table-responsive {
|
|
width: 100%;
|
|
overflow-x: auto;
|
|
scrollbar-width: thin;
|
|
}
|
|
|
|
/* Modern Industry-Standard Role Cell Hover & Quick Edit */
|
|
.role-cell-container {
|
|
position: relative;
|
|
padding: 8px 12px;
|
|
border-radius: 10px;
|
|
transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
border: 1px solid transparent;
|
|
cursor: pointer;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.role-cell-container:hover {
|
|
background-color: rgba(99, 102, 241, 0.08);
|
|
border-color: rgba(99, 102, 241, 0.3);
|
|
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.15);
|
|
}
|
|
|
|
.role-quick-edit-btn {
|
|
position: absolute;
|
|
top: 6px;
|
|
right: 6px;
|
|
opacity: 0;
|
|
transform: scale(0.85);
|
|
background: linear-gradient(135deg, #6366f1 0%, #38bdf8 100%);
|
|
color: white;
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 50%;
|
|
width: 24px;
|
|
height: 24px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
z-index: 5;
|
|
}
|
|
|
|
.role-cell-container:hover .role-quick-edit-btn {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
|
|
.role-quick-edit-btn:hover {
|
|
transform: scale(1.18) rotate(6deg);
|
|
box-shadow: 0 6px 18px rgba(99, 102, 241, 0.6);
|
|
}
|
|
|
|
/* Alert Styling */
|
|
.alert {
|
|
padding: 14px 20px;
|
|
border-radius: 12px;
|
|
font-size: 0.9rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
backdrop-filter: blur(16px);
|
|
-webkit-backdrop-filter: blur(16px);
|
|
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.alert-danger {
|
|
background-color: rgba(239, 68, 68, 0.1);
|
|
border: 1px solid rgba(239, 68, 68, 0.25);
|
|
color: #f87171;
|
|
}
|
|
|
|
.alert-success {
|
|
background-color: rgba(16, 185, 129, 0.1);
|
|
border: 1px solid rgba(16, 185, 129, 0.25);
|
|
color: #34d399;
|
|
}
|
|
|
|
.alert-icon {
|
|
font-size: 1.1rem;
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 24px;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
}
|
|
|
|
.card-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.3rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Users Table styling */
|
|
.table-responsive {
|
|
overflow-x: auto;
|
|
width: 100%;
|
|
}
|
|
|
|
.users-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
text-align: left;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.users-table th {
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--card-border);
|
|
color: var(--text-muted);
|
|
font-weight: 600;
|
|
font-size: 0.8rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.users-table td {
|
|
padding: 16px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.users-table tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.user-cell {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.user-avatar {
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
border: 1px solid var(--card-border);
|
|
}
|
|
|
|
.user-avatar-fallback {
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 600;
|
|
color: white;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.user-name {
|
|
font-weight: 500;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.user-email {
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Badges */
|
|
.badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 4px;
|
|
padding: 4px 8px;
|
|
border-radius: 9999px;
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.badge-admin {
|
|
background-color: rgba(99, 102, 241, 0.15);
|
|
color: #818cf8;
|
|
border: 1px solid rgba(99, 102, 241, 0.3);
|
|
}
|
|
|
|
.badge-user {
|
|
background-color: rgba(6, 182, 212, 0.15);
|
|
color: var(--info-color);
|
|
border: 1px solid rgba(6, 182, 212, 0.3);
|
|
}
|
|
|
|
.badge-microsoft {
|
|
background-color: rgba(16, 185, 129, 0.12);
|
|
color: var(--success-color);
|
|
border: 1px solid rgba(16, 185, 129, 0.25);
|
|
gap: 4px;
|
|
}
|
|
|
|
.badge-password {
|
|
background-color: rgba(245, 158, 11, 0.12);
|
|
color: #fbbf24;
|
|
border: 1px solid rgba(245, 158, 11, 0.25);
|
|
}
|
|
|
|
.action-btn {
|
|
background-color: transparent;
|
|
border: 1px solid var(--card-border);
|
|
color: var(--text-muted);
|
|
padding: 6px 12px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 0.8rem;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.action-btn:hover {
|
|
color: var(--text-main);
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
background-color: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
/* Universal Edit & Delete Buttons */
|
|
.action-btn-edit {
|
|
color: var(--info-color) !important;
|
|
border-color: rgba(6, 182, 212, 0.25) !important;
|
|
background-color: rgba(6, 182, 212, 0.05) !important;
|
|
padding: 6px !important;
|
|
display: inline-flex !important;
|
|
align-items: center !important;
|
|
justify-content: center !important;
|
|
border-radius: 6px !important;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease !important;
|
|
}
|
|
|
|
.action-btn-edit:hover {
|
|
color: #38bdf8 !important;
|
|
border-color: rgba(56, 189, 248, 0.5) !important;
|
|
background-color: rgba(6, 182, 212, 0.2) !important;
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 2px 8px rgba(6, 182, 212, 0.3) !important;
|
|
}
|
|
|
|
.action-btn-delete {
|
|
color: #ef4444 !important;
|
|
border-color: rgba(239, 68, 68, 0.25) !important;
|
|
background-color: rgba(239, 68, 68, 0.05) !important;
|
|
padding: 6px !important;
|
|
display: inline-flex !important;
|
|
align-items: center !important;
|
|
justify-content: center !important;
|
|
border-radius: 6px !important;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease !important;
|
|
}
|
|
|
|
.action-btn-delete:hover {
|
|
color: #f87171 !important;
|
|
border-color: rgba(239, 68, 68, 0.5) !important;
|
|
background-color: rgba(239, 68, 68, 0.2) !important;
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3) !important;
|
|
}
|
|
|
|
.microsoft-icon-tiny {
|
|
display: inline-grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 1px;
|
|
width: 10px;
|
|
height: 10px;
|
|
}
|
|
|
|
.tiny-rect {
|
|
width: 4px;
|
|
height: 4px;
|
|
}
|
|
.tr1 { background-color: #f25022; }
|
|
.tr2 { background-color: #7fba00; }
|
|
.tr3 { background-color: #00a4ef; }
|
|
.tr4 { background-color: #ffb900; }
|
|
|
|
@media (max-width: 768px) {
|
|
.users-card {
|
|
padding: 20px 15px;
|
|
}
|
|
}
|
|
|
|
/* Modal Backdrop */
|
|
.admin-modal {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(11, 15, 25, 0.85);
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
.admin-modal.active {
|
|
opacity: 1;
|
|
pointer-events: all;
|
|
}
|
|
.admin-modal-content {
|
|
background-color: rgba(25, 30, 45, 0.96);
|
|
border: 2px solid var(--card-border);
|
|
border-radius: 22px;
|
|
padding: 32px;
|
|
width: 90%;
|
|
max-width: 500px;
|
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
|
|
transform: scale(0.9);
|
|
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.1);
|
|
}
|
|
.admin-modal.active .admin-modal-content {
|
|
transform: scale(1);
|
|
}
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
border-bottom: 1px solid var(--card-border);
|
|
padding-bottom: 12px;
|
|
}
|
|
.modal-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.25rem;
|
|
font-weight: 700;
|
|
color: #ffffff;
|
|
}
|
|
.modal-close {
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--text-muted);
|
|
font-size: 1.5rem;
|
|
cursor: pointer;
|
|
line-height: 1;
|
|
}
|
|
.modal-close:hover {
|
|
color: white;
|
|
}
|
|
.modal-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 12px;
|
|
margin-top: 24px;
|
|
border-top: 1px solid var(--card-border);
|
|
padding-top: 16px;
|
|
}
|
|
.btn-cancel {
|
|
background: transparent;
|
|
border: 1px solid var(--card-border);
|
|
color: var(--text-muted);
|
|
padding: 8px 16px;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
font-size: 0.85rem;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.btn-cancel:hover {
|
|
color: white;
|
|
border-color: rgba(255,255,255,0.2);
|
|
}
|
|
.checkbox-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
|
gap: 10px;
|
|
max-height: 150px;
|
|
overflow-y: auto;
|
|
background: rgba(0,0,0,0.2);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
margin-top: 6px;
|
|
}
|
|
.checkbox-item {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
font-size: 0.85rem;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
.checkbox-item input {
|
|
margin-top: 3px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Nav -->
|
|
<header class="navbar">
|
|
<div class="nav-logo">
|
|
<svg viewBox="0 0 24 24" style="margin-right: 8px;">
|
|
<path d="M12,1L3,5V11C3,16.55 6.84,21.74 12,23C17.16,21.74 21,16.55 21,11V5L12,1M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,13C14.67,13 20,14.33 20,17V18H4V17C4,14.33 9.33,13 12,13Z" />
|
|
</svg>
|
|
Control Panel
|
|
</div>
|
|
<div class="nav-actions">
|
|
<!-- Allow Admin to view the User portal dashboard -->
|
|
<a href="{{ route('client-calls.index') }}" class="btn-nav" style="border-color: #22d3ee; color: #22d3ee; text-decoration: none;">
|
|
📞 Client Calls Hub
|
|
</a>
|
|
<a href="{{ route('interview.index') }}" class="btn-nav" style="border-color: #34d399; color: #34d399; text-decoration: none;">
|
|
⚡ Assessment Hub
|
|
</a>
|
|
<a href="{{ route('onboarding.index') }}" class="btn-nav" style="border-color: #818cf8; color: #818cf8; text-decoration: none;">
|
|
🚀 Onboarding Hub
|
|
</a>
|
|
<a href="{{ route('dashboard') }}" class="btn-nav">
|
|
User Dashboard
|
|
</a>
|
|
<form action="{{ route('logout') }}" method="POST" id="logout-form" style="display: none;">
|
|
@csrf
|
|
</form>
|
|
<a href="#" onclick="event.preventDefault(); document.getElementById('logout-form').submit();" class="btn-nav btn-logout">
|
|
Sign Out
|
|
</a>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Container -->
|
|
<main class="dashboard-container">
|
|
|
|
<!-- Header -->
|
|
<section class="admin-header">
|
|
<h1>System Overview</h1>
|
|
<p>Welcome, <strong>{{ $admin->name }}</strong>. Monitoring active directories, credentials, and SSO logins.</p>
|
|
</section>
|
|
|
|
<!-- Stats Grid -->
|
|
<section class="stats-grid">
|
|
<div class="stat-card">
|
|
<div>
|
|
<div class="stat-label">Total Registrations</div>
|
|
<div class="stat-value">{{ $stats['total_users'] }}</div>
|
|
</div>
|
|
<div class="stat-icon" style="color: var(--accent-primary);">👥</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div>
|
|
<div class="stat-label">Administrators</div>
|
|
<div class="stat-value">{{ $stats['admins_count'] }}</div>
|
|
</div>
|
|
<div class="stat-icon" style="color: #818cf8;">🛡️</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div>
|
|
<div class="stat-label">Blocked Accounts</div>
|
|
<div class="stat-value">{{ $stats['blocked_count'] }}</div>
|
|
</div>
|
|
<div class="stat-icon" style="color: var(--error-color);">🚫</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div>
|
|
<div class="stat-label">Registered Apps</div>
|
|
<div class="stat-value">{{ $stats['apps_count'] }}</div>
|
|
</div>
|
|
<div class="stat-icon" style="color: #fbbf24;">📦</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div>
|
|
<div class="stat-label">System Roles</div>
|
|
<div class="stat-value">{{ $stats['roles_count'] }}</div>
|
|
</div>
|
|
<div class="stat-icon" style="color: #a5b4fc;">🏷️</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div>
|
|
<div class="stat-label">Client Projects</div>
|
|
<div class="stat-value">{{ $stats['clients_count'] ?? 0 }}</div>
|
|
</div>
|
|
<div class="stat-icon" style="color: #22d3ee;">📁</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div>
|
|
<div class="stat-label">Client Alerts</div>
|
|
<div class="stat-value" style="{{ ($stats['angry_clients_count'] ?? 0) > 0 ? 'color: #f87171;' : '' }}">{{ $stats['angry_clients_count'] ?? 0 }}</div>
|
|
</div>
|
|
<div class="stat-icon" style="color: {{ ($stats['angry_clients_count'] ?? 0) > 0 ? '#f87171' : '#34d399' }};">{{ ($stats['angry_clients_count'] ?? 0) > 0 ? '⚠️' : '✨' }}</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Alert messages in dashboard -->
|
|
@if($errors->any())
|
|
<div class="alert alert-danger" style="margin-bottom: 24px;">
|
|
<span class="alert-icon">⚠️</span>
|
|
<div>
|
|
<ul style="margin: 0; padding-left: 16px; font-size: 0.85rem; line-height: 1.5; list-style-type: disc;">
|
|
@foreach($errors->all() as $error)
|
|
<li>{{ $error }}</li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@if(session('error'))
|
|
<div class="alert alert-danger" style="margin-bottom: 24px;">
|
|
<span class="alert-icon">⚠️</span>
|
|
<div>{{ session('error') }}</div>
|
|
</div>
|
|
@endif
|
|
|
|
@if(session('success'))
|
|
<div class="alert alert-success" style="margin-bottom: 24px;">
|
|
<div>{{ session('success') }}</div>
|
|
</div>
|
|
@endif
|
|
|
|
|
|
|
|
<!-- Client Projects & Calls Directory -->
|
|
<section class="users-card" style="margin-bottom: 50px;">
|
|
<div class="card-header" style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;">
|
|
<div>
|
|
<div class="card-title" style="font-family: 'Outfit', sans-serif; font-size: 1.25rem; font-weight: 700; color: #ffffff;">Client Projects & Meetings Hub</div>
|
|
<div style="font-size: 0.8rem; color: var(--text-muted);">
|
|
Real-time client collaboration, AI transcription, MoM records, and emotion detection
|
|
</div>
|
|
</div>
|
|
<div style="display: flex; gap: 10px;">
|
|
<a href="{{ route('client-calls.index') }}" class="action-btn" style="color: #22d3ee; border-color: rgba(34, 211, 238, 0.4); text-decoration: none; font-size: 0.8rem; padding: 6px 14px; font-weight: 600;">
|
|
Open Full Client Calls Hub ↗
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="users-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Project & Client</th>
|
|
<th>Responsible Lead</th>
|
|
<th>Total Calls</th>
|
|
<th>Client Expression</th>
|
|
<th style="text-align: right;">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($clients as $cl)
|
|
@php
|
|
$sent = strtolower($cl->latest_sentiment ?? 'neutral');
|
|
$badgeStyle = match($sent) {
|
|
'happy' => 'background: rgba(16, 185, 129, 0.15); color: #34d399; border: 1px solid rgba(16, 185, 129, 0.35);',
|
|
'sad' => 'background: rgba(245, 158, 11, 0.15); color: #fbbf24; border: 1px solid rgba(245, 158, 11, 0.35);',
|
|
'angry' => 'background: rgba(239, 68, 68, 0.2); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.45); font-weight: 700;',
|
|
default => 'background: rgba(59, 130, 246, 0.15); color: #60a5fa; border: 1px solid rgba(59, 130, 246, 0.35);',
|
|
};
|
|
$emoji = match($sent) {
|
|
'happy' => '😊 Happy',
|
|
'sad' => '😟 Concerned',
|
|
'angry' => '😡 Frustrated',
|
|
default => '😐 Neutral',
|
|
};
|
|
$latestNote = $cl->callNotes->first();
|
|
@endphp
|
|
<tr>
|
|
<td>
|
|
<div style="font-weight: 700; color: #f3f4f6; font-size: 0.95rem;">{{ $cl->project_name }}</div>
|
|
<div style="font-size: 0.8rem; color: var(--text-muted); display: flex; align-items: center; gap: 6px; margin-top: 2px;">
|
|
<span>👤 {{ $cl->client_name }}</span>
|
|
@if($cl->company_name)<span>• {{ $cl->company_name }}</span>@endif
|
|
<span>• ✉️ {{ $cl->client_email }}</span>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge" style="background: rgba(99, 102, 241, 0.15); color: #818cf8; border: 1px solid rgba(99, 102, 241, 0.3);">
|
|
{{ $cl->responsibleUser->name ?? 'Unassigned' }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span style="font-weight: 600; font-size: 0.85rem; color: #cbd5e1;">{{ $cl->meetings->count() }} meetings</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge" style="{{ $badgeStyle }} font-size: 0.75rem; padding: 4px 10px; border-radius: 999px;">
|
|
{{ $emoji }}
|
|
</span>
|
|
@if($sent === 'angry' && $latestNote)
|
|
<button onclick="openAdminWhyAngryModal({{ $latestNote->id }})" style="background: #ef4444; color: white; border: none; font-size: 0.7rem; padding: 3px 8px; border-radius: 4px; font-weight: 700; cursor: pointer; margin-left: 6px;">
|
|
Why angry?
|
|
</button>
|
|
@endif
|
|
</td>
|
|
<td style="text-align: right;">
|
|
<a href="{{ route('client-calls.show', $cl->id) }}" target="_blank" class="action-btn" style="color: #818cf8; border-color: rgba(99, 102, 241, 0.4); text-decoration: none; font-size: 0.75rem; padding: 5px 12px; font-weight: 600;">
|
|
Open Client Hub ↗
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="5" style="text-align: center; color: var(--text-muted); padding: 24px;">
|
|
No client projects registered yet. <a href="{{ route('client-calls.index') }}" style="color: #818cf8;">Create first client project</a>
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Users Table Card -->
|
|
<section class="users-card" style="margin-bottom: 50px;">
|
|
<div class="card-header" style="margin-bottom: 20px;">
|
|
<div class="card-title" style="font-family: 'Outfit', sans-serif; font-size: 1.25rem; font-weight: 700; color: #ffffff;">User Accounts Directory</div>
|
|
<div style="font-size: 0.8rem; color: var(--text-muted);">
|
|
Showing registered system entities and block controls
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="users-table">
|
|
<thead>
|
|
<tr>
|
|
<th>User Account</th>
|
|
<th>Assigned Role</th>
|
|
<th>Authentication Type</th>
|
|
<th>Linked On</th>
|
|
<th style="text-align: right;">Operations</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($users as $usr)
|
|
<tr>
|
|
<td>
|
|
<div class="user-cell">
|
|
@if($usr->avatar)
|
|
<img src="{{ $usr->avatar }}" alt="{{ $usr->name }}" class="user-avatar">
|
|
@else
|
|
<div class="user-avatar-fallback">
|
|
{{ strtoupper(substr($usr->name, 0, 1)) }}
|
|
</div>
|
|
@endif
|
|
<div>
|
|
<div class="user-name" style="display:flex; align-items:center; gap:8px;">
|
|
{{ $usr->name }}
|
|
@if($usr->is_blocked)
|
|
<span class="badge" style="background-color:rgba(239,68,68,0.15); color:#fca5a5; border:1px solid rgba(239,68,68,0.3); font-size:0.65rem; padding:2px 6px;">Blocked</span>
|
|
@else
|
|
<span class="badge" style="background-color:rgba(16,185,129,0.12); color:#a7f3d0; border:1px solid rgba(16,185,129,0.25); font-size:0.65rem; padding:2px 6px;">Active</span>
|
|
@endif
|
|
</div>
|
|
<div class="user-email">{{ $usr->email }}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="role-cell-container" onclick="openAssignRolesModal({{ $usr->id }}, '{{ addslashes($usr->name) }}', {{ json_encode($usr->roles->pluck('id')) }})" title="Click to modify roles for {{ $usr->name }}">
|
|
<!-- Quick Edit Floating Button on Hover -->
|
|
@if($usr->id !== $admin->id)
|
|
<button class="role-quick-edit-btn" onclick="event.stopPropagation(); openAssignRolesModal({{ $usr->id }}, '{{ addslashes($usr->name) }}', {{ json_encode($usr->roles->pluck('id')) }})" title="Edit User Roles">
|
|
<svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor">
|
|
<path d="M14.06,9L15,9.94L5.92,19H5V18.06L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z" />
|
|
</svg>
|
|
</button>
|
|
@endif
|
|
|
|
<div>
|
|
@if($usr->role === 'admin')
|
|
<span class="badge badge-admin">Admin</span>
|
|
@else
|
|
<span class="badge badge-user">User Portal</span>
|
|
@endif
|
|
</div>
|
|
|
|
@if($usr->roles->isNotEmpty())
|
|
<div style="display:flex; flex-wrap:wrap; gap:4px; margin-top:2px;">
|
|
@foreach($usr->roles as $role)
|
|
@if(strtolower($role->name) === 'admin')
|
|
@continue
|
|
@endif
|
|
<span class="badge" style="background-color:rgba(99,102,241,0.1); color:#818cf8; border:1px solid rgba(99,102,241,0.25); font-size:0.65rem;">{{ $role->name }}</span>
|
|
@endforeach
|
|
</div>
|
|
@elseif($usr->role !== 'admin')
|
|
<div style="display:flex; flex-wrap:wrap; gap:4px; margin-top:2px;">
|
|
<span class="badge" style="background-color:rgba(99,102,241,0.1); color:#818cf8; border:1px solid rgba(99,102,241,0.25); font-size:0.65rem;">{{ $defaultRoleName }}</span>
|
|
</div>
|
|
@endif
|
|
|
|
@if($usr->overrides->isNotEmpty())
|
|
<div style="display:flex; flex-wrap:wrap; gap:4px; margin-top:4px;">
|
|
@foreach($usr->overrides as $ov)
|
|
@if($ov->type === 'allow')
|
|
<span class="badge" style="background-color:rgba(16,185,129,0.15); color:#34d399; border:1px solid rgba(16,185,129,0.3); font-size:0.65rem; display:inline-flex; align-items:center; gap:4px;" title="Explicitly Allowed Service">
|
|
+ {{ $ov->app->name ?? 'App' }}
|
|
<form action="{{ route('admin.overrides.destroy', $ov->id) }}" method="POST" onsubmit="return confirm('Remove override for {{ addslashes($ov->app->name ?? 'App') }}?')" style="display:inline; margin:0;" onclick="event.stopPropagation();">
|
|
@csrf
|
|
@method('DELETE')
|
|
<button type="submit" style="background:none; border:none; color:#34d399; cursor:pointer; padding:0 2px; line-height:1; font-weight:bold; font-size:0.75rem;" title="Remove Override">×</button>
|
|
</form>
|
|
</span>
|
|
@else
|
|
<span class="badge" style="background-color:rgba(239,68,68,0.15); color:#fca5a5; border:1px solid rgba(239,68,68,0.3); font-size:0.65rem; display:inline-flex; align-items:center; gap:4px;" title="Explicitly Revoked Service">
|
|
− {{ $ov->app->name ?? 'App' }}
|
|
<form action="{{ route('admin.overrides.destroy', $ov->id) }}" method="POST" onsubmit="return confirm('Remove revoked override for {{ addslashes($ov->app->name ?? 'App') }}?')" style="display:inline; margin:0;" onclick="event.stopPropagation();">
|
|
@csrf
|
|
@method('DELETE')
|
|
<button type="submit" style="background:none; border:none; color:#fca5a5; cursor:pointer; padding:0 2px; line-height:1; font-weight:bold; font-size:0.75rem;" title="Remove Revoked Override">×</button>
|
|
</form>
|
|
</span>
|
|
@endif
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</td>
|
|
<td>
|
|
@if($usr->microsoft_id)
|
|
<span class="badge badge-microsoft">
|
|
<div class="microsoft-icon-tiny">
|
|
<div class="tiny-rect tr1"></div>
|
|
<div class="tiny-rect tr2"></div>
|
|
<div class="tiny-rect tr3"></div>
|
|
<div class="tiny-rect tr4"></div>
|
|
</div>
|
|
Microsoft OAuth
|
|
</span>
|
|
@else
|
|
<span class="badge badge-password">Local Password</span>
|
|
@endif
|
|
</td>
|
|
<td>
|
|
<span style="font-size: 0.85rem; color: var(--text-muted);">
|
|
{{ $usr->created_at ? $usr->created_at->format('Y-m-d H:i') : 'N/A' }}
|
|
</span>
|
|
</td>
|
|
<td style="text-align: right; white-space: nowrap;">
|
|
@if($usr->id !== $admin->id)
|
|
<button class="action-btn" onclick="openAssignRolesModal({{ $usr->id }}, '{{ addslashes($usr->name) }}', {{ json_encode($usr->roles->pluck('id')) }})" style="color:var(--accent-primary); border-color:rgba(99,102,241,0.2); font-size:0.75rem; padding:6px 12px; margin-right:4px;">Assign Roles</button>
|
|
<button class="action-btn" onclick="openCreateOverrideModalForUser({{ $usr->id }})" style="color:#06b6d4; border-color:rgba(6,182,212,0.25); background-color:rgba(6,182,212,0.05); font-size:0.75rem; padding:6px 10px; margin-right:4px; font-weight:600;" title="Manage Service Overrides">
|
|
<svg viewBox="0 0 24 24" style="width: 13px; height: 13px; fill: currentColor; vertical-align: text-bottom; margin-right: 2px;">
|
|
<path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M11,7V11H7V13H11V17H13V13H17V11H13V7H11Z"/>
|
|
</svg>
|
|
Overrides
|
|
</button>
|
|
|
|
@if($usr->isAdmin())
|
|
<button class="action-btn" onclick="confirmAdminToggle({{ $usr->id }}, '{{ addslashes($usr->name) }}', 'revoke')"
|
|
style="color:#ef4444; border-color:rgba(239,68,68,0.3); background-color:rgba(239,68,68,0.05); font-size:0.75rem; padding:6px 12px; margin-right:4px; font-weight:600;">
|
|
Revoke Admin
|
|
</button>
|
|
@else
|
|
<button class="action-btn" onclick="confirmAdminToggle({{ $usr->id }}, '{{ addslashes($usr->name) }}', 'grant')"
|
|
style="color:#f59e0b; border-color:rgba(245,158,11,0.3); background-color:rgba(245,158,11,0.05); font-size:0.75rem; padding:6px 12px; margin-right:4px; font-weight:600; box-shadow: 0 0 10px rgba(245,158,11,0.15);">
|
|
Give Admin Privilege
|
|
</button>
|
|
@endif
|
|
|
|
<form action="{{ route('admin.users.toggle-block', $usr->id) }}" method="POST" style="display:inline;">
|
|
@csrf
|
|
<button type="submit" class="action-btn"
|
|
style="font-size:0.75rem; padding:6px 12px; border-radius:6px; font-weight:600; cursor:pointer; transition:all 0.2s ease;
|
|
{{ $usr->is_blocked ? 'color:#10b981; border-color:rgba(16,185,129,0.2); background-color:rgba(16,185,129,0.05);' : 'color:#ef4444; border-color:rgba(239,68,68,0.2); background-color:rgba(239,68,68,0.05);' }}">
|
|
{{ $usr->is_blocked ? 'Unblock' : 'Block' }}
|
|
</button>
|
|
</form>
|
|
@else
|
|
<span style="font-size:0.75rem; color:var(--text-muted); font-style:italic;">You (Admin)</span>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="5" style="text-align: center; color: var(--text-muted); padding: 30px;">
|
|
No registered accounts found in the database.
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- App Management, Roles Management, & User Overrides Grid -->
|
|
<section class="management-grid">
|
|
|
|
<!-- App Management Card -->
|
|
<div class="admin-card">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
|
<h3 style="font-family: 'Outfit', sans-serif; font-size: 1.25rem; font-weight: 700; color: #ffffff; margin: 0;">Manage System Services</h3>
|
|
<button onclick="openModal('add-service-modal')" class="action-btn" style="color:var(--accent-primary); border-color:var(--accent-primary); font-size:0.75rem; padding:6px 12px; font-weight:600; cursor:pointer;">+ Add Service</button>
|
|
</div>
|
|
|
|
<!-- Registered Apps List -->
|
|
<div class="table-responsive" style="max-height: 380px; overflow-y: auto;">
|
|
<table class="users-table" style="font-size:0.8rem;">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 45%;">Service</th>
|
|
<th style="width: 35%;">Tag</th>
|
|
<th style="width: 20%; text-align: right;">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($apps as $app)
|
|
<tr>
|
|
<td>
|
|
<div style="display:flex; align-items:center; gap:8px;">
|
|
@if($app->logo)
|
|
<img src="{{ asset($app->logo) }}" alt="{{ $app->name }}" style="width:20px; height:20px; object-fit:contain; border-radius:4px;">
|
|
@else
|
|
<div style="width:20px; height:20px; border-radius:4px; background-color:{{ $app->color }}; display:flex; align-items:center; justify-content:center; color:white; font-size:10px; font-weight:bold;">
|
|
{{ strtoupper(substr($app->name, 0, 1)) }}
|
|
</div>
|
|
@endif
|
|
<strong>{{ $app->name }}</strong>
|
|
</div>
|
|
</td>
|
|
<td><span class="badge" style="background-color:rgba(255,255,255,0.05); color:#cbd5e1; border:1px solid rgba(255,255,255,0.1);">{{ $app->tag }}</span></td>
|
|
<td style="text-align: right;">
|
|
<div style="display: inline-flex; gap: 6px; justify-content: flex-end; align-items: center;">
|
|
<button class="action-btn action-btn-edit" onclick="openEditAppModal({{ $app->id }}, {{ json_encode($app->name) }}, {{ json_encode($app->url) }}, {{ json_encode($app->tag) }}, {{ json_encode($app->icon_svg) }}, {{ json_encode($app->color) }}, {{ json_encode($app->desc) }}, {{ json_encode($app->logo) }})" title="Edit Service">
|
|
<svg viewBox="0 0 24 24" style="width: 14px; height: 14px; fill: currentColor;">
|
|
<path d="M14.06,9L15,9.94L5.92,19H5V18.06L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z" />
|
|
</svg>
|
|
</button>
|
|
<form action="{{ route('admin.apps.destroy', $app->id) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this service?')" style="margin: 0; display: inline;">
|
|
@csrf
|
|
@method('DELETE')
|
|
<button type="submit" class="action-btn action-btn-delete" title="Delete Service">
|
|
<svg viewBox="0 0 24 24" style="width: 14px; height: 14px; fill: currentColor;">
|
|
<path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" />
|
|
</svg>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="3" style="text-align: center; color: var(--text-muted); padding: 20px;">No registered service apps.</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Role Management Card -->
|
|
<div class="admin-card">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
|
<h3 style="font-family: 'Outfit', sans-serif; font-size: 1.25rem; font-weight: 700; color: #ffffff; margin: 0;">Manage Roles & Services</h3>
|
|
<button onclick="openModal('create-role-modal')" class="action-btn" style="color:var(--accent-primary); border-color:var(--accent-primary); font-size:0.75rem; padding:6px 12px; font-weight:600; cursor:pointer;">+ Add Role</button>
|
|
</div>
|
|
|
|
<!-- Default Role Configuration Form (compact inline) -->
|
|
<form action="{{ route('admin.settings.default-role') }}" method="POST" style="margin-bottom: 16px; padding-bottom: 15px; border-bottom: 1px solid rgba(255, 255, 255, 0.1);">
|
|
@csrf
|
|
<div style="margin-bottom: 0;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Default Role for New Users</label>
|
|
<div style="display: flex; gap: 8px;">
|
|
<select name="default_role" required class="form-input" style="flex: 1; padding:8px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white; outline: none; font-size: 0.8rem;">
|
|
@foreach($roles as $role)
|
|
<option value="{{ $role->name }}" {{ $defaultRoleName === $role->name ? 'selected' : '' }} style="background-color: #1e1e38; color: white;">
|
|
{{ $role->name }}
|
|
</option>
|
|
@endforeach
|
|
</select>
|
|
<button type="submit" class="btn" style="background-color: var(--accent-primary); color: white; font-size:0.75rem; border-radius:8px; padding:8px 12px; border:none; cursor:pointer; font-weight:600; white-space: nowrap;">Save Default</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Registered Roles List -->
|
|
<div class="table-responsive" style="max-height: 380px; overflow-y: auto;">
|
|
<table class="users-table" style="font-size:0.8rem;">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 35%;">Role Name</th>
|
|
<th style="width: 45%;">Assigned Services</th>
|
|
<th style="width: 20%; text-align: right;">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($roles as $role)
|
|
<tr>
|
|
<td>
|
|
<div>
|
|
<strong>{{ $role->name }}</strong>
|
|
@if($role->description)
|
|
<div style="font-size:0.7rem; color:var(--text-muted); margin-top:2px;">{{ $role->description }}</div>
|
|
@endif
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div style="display:flex; flex-wrap:wrap; gap:4px;">
|
|
@forelse($role->apps as $app)
|
|
<span class="badge" style="background-color:{{ $app->color }}20; color:{{ $app->color }}; border:1px solid {{ $app->color }}40; font-size:0.65rem;">{{ $app->name }}</span>
|
|
@empty
|
|
<span style="color:var(--text-muted); font-size:0.7rem;">None</span>
|
|
@endforelse
|
|
</div>
|
|
</td>
|
|
<td style="text-align: right; white-space: nowrap;">
|
|
<div style="display: inline-flex; gap: 6px; justify-content: flex-end; align-items: center;">
|
|
<button class="action-btn action-btn-edit" onclick="openEditRoleModal({{ $role->id }}, '{{ addslashes($role->name) }}', '{{ addslashes($role->description) }}', {{ json_encode($role->apps->pluck('id')) }}, {{ $role->can_manage_onboarding ? 'true' : 'false' }})" title="Edit Role">
|
|
<svg viewBox="0 0 24 24" style="width: 14px; height: 14px; fill: currentColor;">
|
|
<path d="M14.06,9L15,9.94L5.92,19H5V18.06L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z" />
|
|
</svg>
|
|
</button>
|
|
<form action="{{ route('admin.roles.destroy', $role->id) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this role?')" style="margin: 0; display: inline;">
|
|
@csrf
|
|
@method('DELETE')
|
|
<button type="submit" class="action-btn action-btn-delete" title="Delete Role">
|
|
<svg viewBox="0 0 24 24" style="width: 14px; height: 14px; fill: currentColor;">
|
|
<path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" />
|
|
</svg>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="3" style="text-align: center; color: var(--text-muted); padding: 20px;">No roles defined.</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
</section>
|
|
|
|
</main>
|
|
|
|
<!-- Add Service Modal -->
|
|
<div id="add-service-modal" class="admin-modal">
|
|
<div class="admin-modal-content" style="max-height: 90vh; overflow-y: auto;">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Register New Service App</h3>
|
|
<button class="modal-close" onclick="closeModal('add-service-modal')">×</button>
|
|
</div>
|
|
<form action="{{ route('admin.apps.store') }}" method="POST" enctype="multipart/form-data" class="validated-form">
|
|
@csrf
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service Name <span style="color: #ef4444; font-weight: bold;">*</span></label>
|
|
<input type="text" name="name" required placeholder="e.g. Keka HR" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<div class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">This field is required.</div>
|
|
</div>
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">SSO URL <span style="color: #ef4444; font-weight: bold;">*</span></label>
|
|
<input type="url" name="url" required placeholder="https://..." class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<div class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">Please enter a valid URL.</div>
|
|
</div>
|
|
</div>
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Brand Accent Color (Hex)</label>
|
|
<input type="text" name="color" placeholder="#f27059" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Portal Tag</label>
|
|
<input type="text" name="tag" placeholder="HR Portal" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Icon SVG Code or Preset Name (outlook/teams/keka)</label>
|
|
<textarea name="icon_svg" rows="2" placeholder="e.g. keka, outlook, teams, or raw <svg>...</svg>" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white; font-family:monospace; font-size:0.8rem;"></textarea>
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Upload Service Logo (Optional - Max 5MB)</label>
|
|
<input type="file" name="logo" accept=".jpg,.jpeg,.png,.webp,.svg" class="form-input logo-upload-5mb" style="width:100%; padding:8px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<div class="file-size-error" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">File size exceeds maximum limit of 5MB.</div>
|
|
</div>
|
|
<div style="margin-bottom: 20px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service Description</label>
|
|
<input type="text" name="desc" placeholder="Brief description of the service..." class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn-cancel" onclick="closeModal('add-service-modal')">Cancel</button>
|
|
<button type="submit" class="action-btn" style="background-color: var(--accent-primary); color: white; border-color: transparent; font-weight:600; padding:8px 16px; border-radius:8px; cursor:pointer;">+ Register Service</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Role Modal -->
|
|
<div id="create-role-modal" class="admin-modal">
|
|
<div class="admin-modal-content" style="max-height: 90vh; overflow-y: auto;">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Create Application Role</h3>
|
|
<button class="modal-close" onclick="closeModal('create-role-modal')">×</button>
|
|
</div>
|
|
<form action="{{ route('admin.roles.store') }}" method="POST" class="validated-form">
|
|
@csrf
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Role Name <span style="color: #ef4444; font-weight: bold;">*</span></label>
|
|
<input type="text" name="name" required placeholder="e.g. Developer, HR" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<div class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">This field is required.</div>
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Role Description</label>
|
|
<input type="text" name="description" placeholder="Description of this role..." class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Assign Services Access</label>
|
|
<select name="apps[]" id="create-role-apps-select" multiple="multiple" class="form-input select2-multiple" style="width: 100%;">
|
|
@foreach($apps as $app)
|
|
<option value="{{ $app->id }}">{{ $app->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div style="margin-bottom: 20px; background: rgba(99,102,241,0.08); padding: 10px 14px; border-radius: 8px; border: 1px solid rgba(99,102,241,0.25);">
|
|
<label style="display:flex; align-items:center; gap:8px; font-size:0.75rem; color:white; cursor:pointer; font-weight:600;">
|
|
<input type="checkbox" name="can_manage_onboarding" value="1" style="accent-color:#6366f1; width:16px; height:16px;">
|
|
🚀 Grant Permission: Manage Onboarding & Offboarding
|
|
</label>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn-cancel" onclick="closeModal('create-role-modal')">Cancel</button>
|
|
<button type="submit" class="action-btn" style="background-color: var(--accent-primary); color: white; border-color: transparent; font-weight:600; padding:8px 16px; border-radius:8px; cursor:pointer;">+ Create Role</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Override Modal -->
|
|
<div id="create-override-modal" class="admin-modal">
|
|
<div class="admin-modal-content" style="max-height: 90vh; overflow-y: auto;">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Create User Service Override</h3>
|
|
<button class="modal-close" onclick="closeModal('create-override-modal')">×</button>
|
|
</div>
|
|
<form action="{{ route('admin.overrides.store') }}" method="POST" class="validated-form">
|
|
@csrf
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Select User Account <span style="color: #ef4444; font-weight: bold;">*</span></label>
|
|
<select name="user_id" required class="form-input" style="width:100%; padding:10px; background:rgba(11,15,25,0.9); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<option value="">-- Choose User --</option>
|
|
@foreach($users as $usr)
|
|
@if(!$usr->isAdmin())
|
|
<option value="{{ $usr->id }}">{{ $usr->name }} ({{ $usr->email }})</option>
|
|
@endif
|
|
@endforeach
|
|
</select>
|
|
<div class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">Please select a user account.</div>
|
|
</div>
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px;">
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service App <span style="color: #ef4444; font-weight: bold;">*</span></label>
|
|
<select name="app_id" required class="form-input" style="width:100%; padding:10px; background:rgba(11,15,25,0.9); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<option value="">-- Choose App --</option>
|
|
@foreach($apps as $app)
|
|
<option value="{{ $app->id }}">{{ $app->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
<div class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">Please select a service app.</div>
|
|
</div>
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Override Mode <span style="color: #ef4444; font-weight: bold;">*</span></label>
|
|
<select name="type" required class="form-input" style="width:100%; padding:10px; background:rgba(11,15,25,0.9); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<option value="allow">ALLOW (Add extra service)</option>
|
|
<option value="deny">DENY (Revoke service)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn-cancel" onclick="closeModal('create-override-modal')">Cancel</button>
|
|
<button type="submit" class="action-btn" style="background-color: var(--accent-primary); color: white; border-color: transparent; font-weight:600; padding:8px 16px; border-radius:8px; cursor:pointer;">Apply Override</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Role Modal -->
|
|
<div id="edit-role-modal" class="admin-modal">
|
|
<div class="admin-modal-content">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Edit Application Role</h3>
|
|
<button class="modal-close" onclick="closeModal('edit-role-modal')">×</button>
|
|
</div>
|
|
<form id="edit-role-form" method="POST" class="validated-form">
|
|
@csrf
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Role Name <span style="color: #ef4444; font-weight: bold;">*</span></label>
|
|
<input type="text" id="edit-role-name" name="name" required class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<div class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">This field is required.</div>
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Description</label>
|
|
<input type="text" id="edit-role-description" name="description" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Services Access</label>
|
|
<select name="apps[]" id="edit-role-apps-select" multiple="multiple" class="form-input select2-multiple" style="width: 100%;">
|
|
@foreach($apps as $app)
|
|
<option value="{{ $app->id }}">{{ $app->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div style="margin-bottom: 20px; background: rgba(99,102,241,0.08); padding: 10px 14px; border-radius: 8px; border: 1px solid rgba(99,102,241,0.25);">
|
|
<label style="display:flex; align-items:center; gap:8px; font-size:0.75rem; color:white; cursor:pointer; font-weight:600;">
|
|
<input type="checkbox" name="can_manage_onboarding" id="edit-role-onboarding" value="1" style="accent-color:#6366f1; width:16px; height:16px;">
|
|
🚀 Grant Permission: Manage Onboarding & Offboarding
|
|
</label>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn-cancel" onclick="closeModal('edit-role-modal')">Cancel</button>
|
|
<button type="submit" class="action-btn" style="background-color:var(--accent-primary); color:white; border-color:transparent; font-weight:600; padding:8px 16px; border-radius:8px; cursor:pointer;">Save Changes</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit App Modal -->
|
|
<div id="edit-app-modal" class="admin-modal">
|
|
<div class="admin-modal-content" style="max-height: 90vh; overflow-y: auto;">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Edit Service App</h3>
|
|
<button class="modal-close" onclick="closeModal('edit-app-modal')">×</button>
|
|
</div>
|
|
<form id="edit-app-form" method="POST" enctype="multipart/form-data" class="validated-form">
|
|
@csrf
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service Name <span style="color: #ef4444; font-weight: bold;">*</span></label>
|
|
<input type="text" id="edit-app-name" name="name" required class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<div class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">This field is required.</div>
|
|
</div>
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service URL <span style="color: #ef4444; font-weight: bold;">*</span></label>
|
|
<input type="url" id="edit-app-url" name="url" required class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<div class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">Please enter a valid URL.</div>
|
|
</div>
|
|
</div>
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Brand Accent Color (Hex)</label>
|
|
<input type="text" id="edit-app-color" name="color" placeholder="#4f46e5" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Portal Tag</label>
|
|
<input type="text" id="edit-app-tag" name="tag" placeholder="HR Portal" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Icon SVG Code or Preset Name (outlook/teams/keka)</label>
|
|
<textarea id="edit-app-icon_svg" name="icon_svg" rows="2" placeholder="e.g. keka, outlook, teams, or raw <svg>...</svg>" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white; font-family:monospace; font-size:0.8rem;"></textarea>
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Upload Service Logo (Optional - Max 5MB)</label>
|
|
<input type="file" name="logo" accept=".jpg,.jpeg,.png,.webp,.svg" class="form-input logo-upload-5mb" style="width:100%; padding:8px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<div class="file-size-error" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">File size exceeds maximum limit of 5MB.</div>
|
|
<div style="font-size:0.7rem; color:var(--text-muted); margin-top:4px;">Currently: <span id="edit-app-logo-status" style="font-weight:600; color:#38bdf8;">None</span></div>
|
|
</div>
|
|
<div style="margin-bottom: 20px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service Description</label>
|
|
<input type="text" id="edit-app-desc" name="desc" placeholder="Brief description of the service..." class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn-cancel" onclick="closeModal('edit-app-modal')">Cancel</button>
|
|
<button type="submit" class="action-btn" style="background-color:var(--accent-primary); color:white; border-color:transparent; font-weight:600; padding:8px 16px; border-radius:8px; cursor:pointer;">Save Changes</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Assign User Roles & Service Overrides Modal -->
|
|
<div id="assign-roles-modal" class="admin-modal">
|
|
<div class="admin-modal-content" style="max-width: 580px; max-height: 90vh; overflow-y: auto;">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Manage Access for <span id="assign-user-name" style="color:var(--accent-primary);">User</span></h3>
|
|
<button class="modal-close" onclick="closeModal('assign-roles-modal')">×</button>
|
|
</div>
|
|
|
|
<!-- Section 1: Assigned Application Roles -->
|
|
<form id="assign-roles-form" method="POST" style="margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid var(--card-border);">
|
|
@csrf
|
|
<div style="margin-bottom: 12px;">
|
|
<label style="display:block; font-size:0.85rem; color:#ffffff; font-weight:600; margin-bottom:8px;">
|
|
1. Application Roles (Group Access)
|
|
</label>
|
|
<select name="roles[]" id="assign-roles-select" multiple="multiple" class="form-input select2-multiple" style="width: 100%;">
|
|
@foreach($roles as $role)
|
|
@if(strtolower($role->name) === 'admin')
|
|
@continue
|
|
@endif
|
|
<option value="{{ $role->id }}">{{ $role->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div style="display:flex; justify-content:flex-end;">
|
|
<button type="submit" class="action-btn" style="background-color:var(--accent-primary); color:white; border-color:transparent; font-weight:600; padding:6px 14px; border-radius:8px; cursor:pointer; font-size:0.8rem;">Update Roles</button>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Section 2: Specific Service Overrides (Grant or Revoke Specific Apps) -->
|
|
<div>
|
|
<label style="display:block; font-size:0.85rem; color:#ffffff; font-weight:600; margin-bottom:10px;">
|
|
2. Specific Service Overrides (Add / Revoke Individual Services)
|
|
</label>
|
|
|
|
<!-- Active Overrides List -->
|
|
<div id="modal-active-overrides-container" style="margin-bottom: 16px;">
|
|
<!-- Dynamically populated via JS -->
|
|
</div>
|
|
|
|
<!-- Add New Override Sub-Form -->
|
|
<form action="{{ route('admin.overrides.store') }}" method="POST" class="validated-form" style="background: rgba(255,255,255,0.03); border: 1px dashed var(--card-border); border-radius: 12px; padding: 14px;">
|
|
@csrf
|
|
<input type="hidden" name="user_id" id="override-form-user-id" value="">
|
|
<div style="font-size: 0.75rem; color: var(--text-muted); font-weight: 600; margin-bottom: 10px;">+ Add New Service Override for User</div>
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px;">
|
|
<div>
|
|
<select name="app_id" required class="form-input" style="width:100%; padding:8px; background:rgba(11,15,25,0.9); border:1px solid var(--card-border); border-radius:8px; color:white; font-size:0.75rem;">
|
|
<option value="">-- Select Service App --</option>
|
|
@foreach($apps as $app)
|
|
<option value="{{ $app->id }}">{{ $app->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<select name="type" required class="form-input" style="width:100%; padding:8px; background:rgba(11,15,25,0.9); border:1px solid var(--card-border); border-radius:8px; color:white; font-size:0.75rem;">
|
|
<option value="allow">ALLOW (Grant Service Access)</option>
|
|
<option value="deny">DENY (Revoke Service Access)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div style="display:flex; justify-content:flex-end;">
|
|
<button type="submit" class="action-btn" style="background-color:#06b6d4; color:white; border-color:transparent; font-weight:600; padding:6px 14px; border-radius:6px; cursor:pointer; font-size:0.75rem;">Apply Override</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="modal-footer" style="margin-top: 20px;">
|
|
<button type="button" class="btn-cancel" onclick="closeModal('assign-roles-modal')">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function openModal(modalId) {
|
|
if (modalId === 'create-role-modal') {
|
|
$('#create-role-apps-select').val([]).trigger('change');
|
|
}
|
|
document.getElementById(modalId).classList.add('active');
|
|
}
|
|
|
|
function closeModal(modalId) {
|
|
document.getElementById(modalId).classList.remove('active');
|
|
}
|
|
|
|
function openEditRoleModal(id, name, description, appIds, canManageOnboarding) {
|
|
const form = document.getElementById('edit-role-form');
|
|
form.action = "{{ route('admin.roles.update', ':id') }}".replace(':id', id);
|
|
|
|
document.getElementById('edit-role-name').value = name;
|
|
document.getElementById('edit-role-description').value = description || '';
|
|
const onbCheckbox = document.getElementById('edit-role-onboarding');
|
|
if (onbCheckbox) onbCheckbox.checked = !!canManageOnboarding;
|
|
|
|
// Set select values and trigger Select2 update
|
|
$('#edit-role-apps-select').val(appIds).trigger('change');
|
|
|
|
openModal('edit-role-modal');
|
|
}
|
|
|
|
const allUserOverridesData = {
|
|
@foreach($users as $usr)
|
|
{{ $usr->id }}: [
|
|
@foreach($usr->overrides as $ov)
|
|
{
|
|
id: {{ $ov->id }},
|
|
app_name: "{{ addslashes($ov->app->name ?? 'App') }}",
|
|
type: "{{ $ov->type }}"
|
|
},
|
|
@endforeach
|
|
],
|
|
@endforeach
|
|
};
|
|
|
|
function openAssignRolesModal(userId, name, roleIds) {
|
|
const form = document.getElementById('assign-roles-form');
|
|
form.action = "{{ route('admin.users.roles.update', ':id') }}".replace(':id', userId);
|
|
|
|
document.getElementById('assign-user-name').innerText = name;
|
|
const userIdInput = document.getElementById('override-form-user-id');
|
|
if (userIdInput) userIdInput.value = userId;
|
|
|
|
// Set select values and trigger Select2 update
|
|
$('#assign-roles-select').val(roleIds).trigger('change');
|
|
|
|
// Populate active overrides list for this user inside the modal
|
|
const container = document.getElementById('modal-active-overrides-container');
|
|
const overrides = allUserOverridesData[userId] || [];
|
|
|
|
if (overrides.length === 0) {
|
|
container.innerHTML = `<div style="font-size:0.75rem; color:var(--text-muted); padding:6px 0; font-style:italic;">No custom service overrides active for this user.</div>`;
|
|
} else {
|
|
let html = `<div style="display:flex; flex-direction:column; gap:6px;">`;
|
|
overrides.forEach(ov => {
|
|
const isAllow = ov.type === 'allow';
|
|
const bg = isAllow ? 'rgba(16,185,129,0.12)' : 'rgba(239,68,68,0.12)';
|
|
const color = isAllow ? '#34d399' : '#fca5a5';
|
|
const border = isAllow ? 'rgba(16,185,129,0.3)' : 'rgba(239,68,68,0.3)';
|
|
const label = isAllow ? '+ ALLOWED (Granted Access)' : '− REVOKED (Access Denied)';
|
|
const destroyUrl = "{{ route('admin.overrides.destroy', ':id') }}".replace(':id', ov.id);
|
|
|
|
html += `
|
|
<div style="display:flex; justify-content:space-between; align-items:center; background:${bg}; border:1px solid ${border}; padding:6px 12px; border-radius:8px; font-size:0.75rem;">
|
|
<span style="color:${color}; font-weight:600;">
|
|
<strong>${ov.app_name}</strong> • <span style="font-size:0.7rem;">${label}</span>
|
|
</span>
|
|
<form action="${destroyUrl}" method="POST" onsubmit="return confirm('Remove override for ${ov.app_name}?')" style="margin:0;">
|
|
@csrf
|
|
@method('DELETE')
|
|
<button type="submit" class="action-btn action-btn-delete" style="padding:2px 8px; font-size:0.7rem;" title="Delete Override">
|
|
Remove
|
|
</button>
|
|
</form>
|
|
</div>
|
|
`;
|
|
});
|
|
html += `</div>`;
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
openModal('assign-roles-modal');
|
|
}
|
|
|
|
function openCreateOverrideModalForUser(userId) {
|
|
const usr = {!! json_encode($users->map(fn($u) => ['id' => $u->id, 'name' => $u->name, 'roles' => $u->roles->pluck('id')])) !!}.find(u => u.id === userId);
|
|
if (usr) {
|
|
openAssignRolesModal(usr.id, usr.name, usr.roles);
|
|
}
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
$('#assign-roles-select').select2({
|
|
placeholder: "Select roles...",
|
|
allowClear: true,
|
|
width: '100%',
|
|
dropdownParent: $('#assign-roles-modal')
|
|
});
|
|
|
|
$('#create-role-apps-select').select2({
|
|
placeholder: "Select services access...",
|
|
allowClear: true,
|
|
width: '100%',
|
|
dropdownParent: $('#create-role-modal')
|
|
});
|
|
|
|
$('#edit-role-apps-select').select2({
|
|
placeholder: "Select services access...",
|
|
allowClear: true,
|
|
width: '100%',
|
|
dropdownParent: $('#edit-role-modal')
|
|
});
|
|
|
|
// Client-side file format and size validation (JPG, PNG, WEBP, SVG, AVIF, GIF, BMP | 5MB max)
|
|
const allowedFormats = ['jpg', 'jpeg', 'png', 'webp', 'svg', 'avif', 'gif', 'bmp', 'ico'];
|
|
$('.logo-upload-5mb').on('change', function() {
|
|
const file = this.files[0];
|
|
const errorDiv = $(this).siblings('.file-size-error');
|
|
if (file) {
|
|
const ext = file.name.split('.').pop().toLowerCase();
|
|
if (!allowedFormats.includes(ext)) {
|
|
errorDiv.text('Invalid file format ".' + ext + '". Only PNG, JPG, WEBP, SVG, AVIF, GIF, and BMP image formats are allowed.').show();
|
|
this.value = '';
|
|
return;
|
|
}
|
|
if (file.size > 5 * 1024 * 1024) {
|
|
errorDiv.text('File size (' + (file.size / (1024*1024)).toFixed(2) + 'MB) exceeds maximum limit of 5MB. Please upload an image under 5MB.').show();
|
|
this.value = '';
|
|
return;
|
|
}
|
|
errorDiv.hide();
|
|
}
|
|
});
|
|
|
|
// Client-side required field validation
|
|
$('form.validated-form').on('submit', function(e) {
|
|
let isValid = true;
|
|
$(this).find('[required]').each(function() {
|
|
const input = $(this);
|
|
const val = input.val();
|
|
const errorDiv = input.siblings('.field-error-msg');
|
|
if (!val || (Array.isArray(val) && val.length === 0)) {
|
|
isValid = false;
|
|
input.css('border-color', '#ef4444');
|
|
if (errorDiv.length) errorDiv.show();
|
|
} else {
|
|
input.css('border-color', 'var(--card-border)');
|
|
if (errorDiv.length) errorDiv.hide();
|
|
}
|
|
});
|
|
|
|
if (!isValid) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
|
|
$('form.validated-form [required]').on('input change', function() {
|
|
const input = $(this);
|
|
const val = input.val();
|
|
const errorDiv = input.siblings('.field-error-msg');
|
|
if (val && (!Array.isArray(val) || val.length > 0)) {
|
|
input.css('border-color', 'rgba(255, 255, 255, 0.18)');
|
|
if (errorDiv.length) errorDiv.hide();
|
|
}
|
|
});
|
|
});
|
|
|
|
function openEditAppModal(id, name, url, tag, iconSvg, color, desc, logoPath) {
|
|
const form = document.getElementById('edit-app-form');
|
|
form.action = "{{ route('admin.apps.update', ':id') }}".replace(':id', id);
|
|
|
|
document.getElementById('edit-app-name').value = name;
|
|
document.getElementById('edit-app-url').value = url;
|
|
document.getElementById('edit-app-color').value = color || '';
|
|
document.getElementById('edit-app-tag').value = tag || '';
|
|
document.getElementById('edit-app-icon_svg').value = iconSvg || '';
|
|
document.getElementById('edit-app-desc').value = desc || '';
|
|
|
|
const logoStatus = document.getElementById('edit-app-logo-status');
|
|
if (logoPath) {
|
|
logoStatus.innerText = 'Has custom logo uploaded';
|
|
logoStatus.style.color = '#10b981';
|
|
} else {
|
|
logoStatus.innerText = 'No custom logo uploaded (using preset SVG/initials)';
|
|
logoStatus.style.color = '#9ca3af';
|
|
}
|
|
|
|
openModal('edit-app-modal');
|
|
}
|
|
|
|
function confirmAdminToggle(userId, userName, actionType) {
|
|
const isGrant = actionType === 'grant';
|
|
const title = isGrant ? 'Grant Admin Privilege?' : 'Revoke Admin Privilege?';
|
|
const text = isGrant
|
|
? `Are you sure you want to grant admin privilege to ${userName}? They will be able to access the admin control panel.`
|
|
: `Are you sure you want to revoke admin privilege from ${userName}? They will no longer have administrative access.`;
|
|
const confirmButtonText = isGrant ? 'Yes, grant it!' : 'Yes, revoke it!';
|
|
const confirmButtonColor = isGrant ? '#f59e0b' : '#ef4444';
|
|
|
|
Swal.fire({
|
|
title: title,
|
|
text: text,
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: confirmButtonColor,
|
|
cancelButtonColor: '#374151',
|
|
confirmButtonText: confirmButtonText,
|
|
background: '#1f2937',
|
|
color: '#f3f4f6'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
const form = document.createElement('form');
|
|
form.method = 'POST';
|
|
form.action = "{{ route('admin.users.toggle-admin', ':id') }}".replace(':id', userId);
|
|
|
|
const csrfInput = document.createElement('input');
|
|
csrfInput.type = 'hidden';
|
|
csrfInput.name = '_token';
|
|
csrfInput.value = '{{ csrf_token() }}';
|
|
form.appendChild(csrfInput);
|
|
|
|
document.body.appendChild(form);
|
|
form.submit();
|
|
}
|
|
});
|
|
}
|
|
|
|
async function openAdminWhyAngryModal(noteId) {
|
|
const modal = document.getElementById('adminWhyAngryModal');
|
|
const content = document.getElementById('adminWhyAngryContent');
|
|
modal.classList.add('active');
|
|
content.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--text-muted);">Analyzing transcript and emotion triggers...</div>';
|
|
|
|
try {
|
|
const res = await fetch(`/client-calls/notes/${noteId}/angry-analysis`, {
|
|
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
|
|
});
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
content.innerHTML = `<div style="color:#f87171;padding:1rem;background:rgba(239,68,68,0.1);border-radius:8px;">${data.error || 'Access Denied.'}</div>`;
|
|
return;
|
|
}
|
|
|
|
const analysis = data.angry_analysis || {};
|
|
const triggers = analysis.trigger_words || [];
|
|
const grievances = analysis.grievances || [];
|
|
|
|
content.innerHTML = `
|
|
<div style="margin-bottom: 1.25rem;">
|
|
<div style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 4px;">Client & Project</div>
|
|
<div style="font-weight: 700; font-size: 1.1rem; color: #ffffff;">${data.client_name} • ${data.project_name}</div>
|
|
</div>
|
|
|
|
<div style="background: rgba(239,68,68,0.15); border-left: 4px solid #ef4444; padding: 14px; border-radius: 0 8px 8px 0; margin-bottom: 1.25rem;">
|
|
<div style="font-weight: 700; font-size: 0.85rem; color: #fca5a5; margin-bottom: 6px; text-transform: uppercase;">AI Executive Breakdown</div>
|
|
<p style="font-size: 0.9rem; color: #fee2e2; line-height: 1.5;">${analysis.summary_why_angry || 'Client showed frustration during the meeting.'}</p>
|
|
</div>
|
|
|
|
${triggers.length > 0 ? `
|
|
<div style="margin-bottom: 1.25rem;">
|
|
<div style="font-weight: 600; font-size: 0.85rem; color: var(--text-muted); margin-bottom: 6px;">Identified Frustrated / Angry Keywords:</div>
|
|
<div style="display: flex; flex-wrap: wrap; gap: 6px;">
|
|
${triggers.map(t => `<span style="background: rgba(239,68,68,0.25); color: #fca5a5; font-size: 0.75rem; padding: 3px 8px; border-radius: 4px; border: 1px solid rgba(239,68,68,0.4); font-weight: 600;">"${t}"</span>`).join('')}
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
|
|
${grievances.length > 0 ? `
|
|
<div style="margin-bottom: 1.25rem;">
|
|
<div style="font-weight: 600; font-size: 0.85rem; color: var(--text-muted); margin-bottom: 6px;">Key Complaints / Pain Points:</div>
|
|
<ul style="padding-left: 20px; font-size: 0.85rem; color: #cbd5e1; line-height: 1.6;">
|
|
${grievances.map(g => `<li>${g}</li>`).join('')}
|
|
</ul>
|
|
</div>
|
|
` : ''}
|
|
|
|
${analysis.suggested_remedy ? `
|
|
<div style="background: rgba(16,185,129,0.15); border: 1px solid rgba(16,185,129,0.35); border-radius: 8px; padding: 12px;">
|
|
<div style="font-weight: 700; font-size: 0.8rem; color: #34d399; margin-bottom: 4px; text-transform: uppercase;">💡 Recommended Action for PM & Director:</div>
|
|
<p style="font-size: 0.85rem; color: #d1fae5; line-height: 1.5;">${analysis.suggested_remedy}</p>
|
|
</div>
|
|
` : ''}
|
|
`;
|
|
} catch (err) {
|
|
content.innerHTML = `<div style="color:#f87171;padding:1rem;">Failed to load analysis.</div>`;
|
|
}
|
|
}
|
|
|
|
function closeAdminWhyAngryModal() {
|
|
document.getElementById('adminWhyAngryModal').classList.remove('active');
|
|
}
|
|
</script>
|
|
|
|
<!-- Admin Why Angry Modal -->
|
|
<div id="adminWhyAngryModal" class="admin-modal">
|
|
<div class="admin-modal-content" style="max-width: 600px; border-color: rgba(239, 68, 68, 0.5);">
|
|
<div class="modal-header">
|
|
<div>
|
|
<h3 class="modal-title" style="color: #f87171; display: flex; align-items: center; gap: 8px;">
|
|
<span>😡</span> AI Root-Cause: Why is the client angry?
|
|
</h3>
|
|
<div style="font-size: 0.75rem; color: #fca5a5; margin-top: 4px;">Confidential to Administrator & Assigned Lead</div>
|
|
</div>
|
|
<button class="modal-close" onclick="closeAdminWhyAngryModal()">×</button>
|
|
</div>
|
|
<div id="adminWhyAngryContent" style="padding: 10px 0;">
|
|
<!-- Dynamically populated -->
|
|
</div>
|
|
<div class="modal-footer" style="margin-top: 15px; border-top: 1px solid var(--card-border); padding-top: 15px; text-align: right;">
|
|
<button type="button" class="btn-secondary" onclick="closeAdminWhyAngryModal()">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<footer style="text-align: center; padding: 40px 0; font-size: 0.75rem; color: var(--text-muted); z-index: 10;">
|
|
© 2026 Systems. Authorized Access Only.
|
|
</footer>
|
|
</body>
|
|
</html>
|