Update: 2026-07-24 22:55:34
This commit is contained in:
@@ -0,0 +1,846 @@
|
||||
/* ==========================================================================
|
||||
Siro Admin Web Portal - Modern Design System & CSS Stylesheet
|
||||
========================================================================== */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
/* Color Tokens */
|
||||
--bg-dark: #090d16;
|
||||
--bg-card: rgba(17, 24, 39, 0.7);
|
||||
--bg-card-hover: rgba(30, 41, 59, 0.8);
|
||||
--bg-surface: #111827;
|
||||
--bg-elevated: #1e293b;
|
||||
|
||||
--border-color: rgba(255, 255, 255, 0.08);
|
||||
--border-active: rgba(99, 102, 241, 0.4);
|
||||
|
||||
--primary: #6366f1;
|
||||
--primary-hover: #4f46e5;
|
||||
--primary-light: rgba(99, 102, 241, 0.15);
|
||||
--accent-purple: #8b5cf6;
|
||||
|
||||
--success: #10b981;
|
||||
--success-bg: rgba(16, 185, 129, 0.15);
|
||||
--warning: #f59e0b;
|
||||
--warning-bg: rgba(245, 158, 11, 0.15);
|
||||
--danger: #f43f5e;
|
||||
--danger-bg: rgba(244, 63, 94, 0.15);
|
||||
--info: #06b6d4;
|
||||
--info-bg: rgba(6, 182, 212, 0.15);
|
||||
|
||||
--text-main: #f8fafc;
|
||||
--text-muted: #94a3b8;
|
||||
--text-subtle: #64748b;
|
||||
|
||||
--sidebar-width: 260px;
|
||||
--header-height: 70px;
|
||||
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 18px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
--shadow-md: 0 8px 24px rgba(0, 0, 0, 0.3);
|
||||
--shadow-glow: 0 0 25px rgba(99, 102, 241, 0.25);
|
||||
|
||||
--transition-fast: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Reset & Global */
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
background-color: var(--bg-dark);
|
||||
color: var(--text-main);
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Outfit', sans-serif;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-dark);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Auth Page Overlay / Container */
|
||||
.auth-wrapper {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
background: radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 40%),
|
||||
radial-gradient(circle at 90% 80%, rgba(139, 92, 246, 0.15) 0%, transparent 40%),
|
||||
var(--bg-dark);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1.5rem;
|
||||
transition: opacity 0.4s ease, visibility 0.4s ease;
|
||||
}
|
||||
|
||||
.auth-wrapper.hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
width: 100%;
|
||||
max-width: 440px;
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 2.5rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.auth-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, var(--primary), var(--accent-purple));
|
||||
}
|
||||
|
||||
.auth-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.auth-logo {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.auth-logo i {
|
||||
color: var(--primary);
|
||||
filter: drop-shadow(0 0 10px rgba(99, 102, 241, 0.6));
|
||||
}
|
||||
|
||||
.auth-subtitle {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.form-input-group {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-input-group i {
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-subtle);
|
||||
font-size: 1.1rem;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.75rem 1rem 0.75rem 2.8rem;
|
||||
color: var(--text-main);
|
||||
font-size: 0.95rem;
|
||||
outline: none;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px var(--primary-light);
|
||||
}
|
||||
|
||||
.form-input:focus + i, .form-input-group:focus-within i {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
padding: 0.85rem;
|
||||
border-radius: var(--radius-md);
|
||||
border: none;
|
||||
background: linear-gradient(135deg, var(--primary), var(--accent-purple));
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
box-shadow: var(--shadow-glow);
|
||||
transition: var(--transition-fast);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
opacity: 0.95;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* App Main Layout */
|
||||
.app-container {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: var(--sidebar-width);
|
||||
background: rgba(15, 23, 42, 0.95);
|
||||
border-right: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
transition: var(--transition-smooth);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 1.5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-main);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.brand-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: var(--radius-md);
|
||||
background: linear-gradient(135deg, var(--primary), var(--accent-purple));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 1.1rem;
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
|
||||
.brand-badge {
|
||||
font-size: 0.65rem;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
padding: 1.25rem 0.75rem;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-subtle);
|
||||
font-weight: 600;
|
||||
padding: 0.75rem 0.75rem 0.25rem;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.85rem;
|
||||
padding: 0.75rem 1rem;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
border-radius: var(--radius-md);
|
||||
font-weight: 500;
|
||||
font-size: 0.92rem;
|
||||
transition: var(--transition-fast);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-item i {
|
||||
font-size: 1.1rem;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
color: var(--text-main);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: #fff;
|
||||
background: linear-gradient(90deg, var(--primary-light), transparent);
|
||||
border-left: 3px solid var(--primary);
|
||||
}
|
||||
|
||||
.nav-item.active i {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
padding: 1rem 1.25rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.user-profile-mini {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: var(--radius-full);
|
||||
background: linear-gradient(135deg, var(--info), var(--primary));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.user-info .name {
|
||||
font-size: 0.88rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.user-info .role {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-subtle);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-muted);
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: var(--radius-md);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.btn-icon:hover {
|
||||
color: var(--text-main);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Main Content Area */
|
||||
.main-wrapper {
|
||||
margin-left: var(--sidebar-width);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.top-header {
|
||||
height: var(--header-height);
|
||||
background: rgba(15, 23, 42, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 2rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 90;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.toggle-sidebar-btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: relative;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
background: rgba(30, 41, 59, 0.5);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-full);
|
||||
padding: 0.5rem 1rem 0.5rem 2.5rem;
|
||||
color: var(--text-main);
|
||||
font-size: 0.88rem;
|
||||
outline: none;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
border-color: var(--primary);
|
||||
background: rgba(30, 41, 59, 0.8);
|
||||
}
|
||||
|
||||
.search-box i {
|
||||
position: absolute;
|
||||
left: 0.9rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-subtle);
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.live-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--success);
|
||||
background: var(--success-bg);
|
||||
padding: 0.35rem 0.75rem;
|
||||
border-radius: var(--radius-full);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.pulse-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--success);
|
||||
box-shadow: 0 0 8px var(--success);
|
||||
animation: pulse 1.8s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.4; transform: scale(0.85); }
|
||||
100% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
/* Page Views */
|
||||
.content-body {
|
||||
padding: 2rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.page-view {
|
||||
display: none;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.page-view.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.75rem;
|
||||
}
|
||||
|
||||
.page-title h1 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.page-title p {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
.page-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Cards & Stats Grid */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 1.25rem;
|
||||
margin-bottom: 1.75rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: var(--transition-smooth);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-3px);
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.stat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: var(--radius-md);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.stat-icon.primary { background: var(--primary-light); color: var(--primary); }
|
||||
.stat-icon.success { background: var(--success-bg); color: var(--success); }
|
||||
.stat-icon.warning { background: var(--warning-bg); color: var(--warning); }
|
||||
.stat-icon.info { background: var(--info-bg); color: var(--info); }
|
||||
.stat-icon.danger { background: var(--danger-bg); color: var(--danger); }
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.85rem;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.stat-footer {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.trend-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: var(--radius-full);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.trend-badge.up { background: var(--success-bg); color: var(--success); }
|
||||
.trend-badge.down { background: var(--danger-bg); color: var(--danger); }
|
||||
|
||||
.trend-text {
|
||||
color: var(--text-subtle);
|
||||
}
|
||||
|
||||
/* Section Grid (Charts & Tables) */
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 1.75rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.dashboard-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Custom CSS Charts */
|
||||
.chart-container {
|
||||
height: 280px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Data Tables */
|
||||
.table-responsive {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
color: var(--text-subtle);
|
||||
font-size: 0.78rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
padding: 0.85rem 1rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.data-table td {
|
||||
padding: 0.95rem 1rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--text-main);
|
||||
font-size: 0.88rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.data-table tbody tr {
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.data-table tbody tr:hover {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.25rem 0.65rem;
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge-success { background: var(--success-bg); color: var(--success); }
|
||||
.badge-warning { background: var(--warning-bg); color: var(--warning); }
|
||||
.badge-danger { background: var(--danger-bg); color: var(--danger); }
|
||||
.badge-info { background: var(--info-bg); color: var(--info); }
|
||||
.badge-primary { background: var(--primary-light); color: var(--primary); }
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
padding: 0.55rem 1.1rem;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
transition: var(--transition-fast);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.35rem 0.75rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Modal Overlay */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: var(--transition-smooth);
|
||||
}
|
||||
|
||||
.modal-overlay.active {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-lg);
|
||||
width: 90%;
|
||||
max-width: 550px;
|
||||
padding: 2rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(20px);
|
||||
transition: var(--transition-smooth);
|
||||
}
|
||||
|
||||
.modal-overlay.active .modal-content {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Filter Bar */
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
background: var(--bg-card);
|
||||
padding: 1rem 1.25rem;
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border-color);
|
||||
margin-bottom: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-inputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.select-input {
|
||||
background: rgba(15, 23, 42, 0.7);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-main);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.88rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 992px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
.sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.main-wrapper {
|
||||
margin-left: 0;
|
||||
}
|
||||
.toggle-sidebar-btn {
|
||||
display: inline-flex;
|
||||
}
|
||||
.search-box {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Siro Admin | Enterprise Operations Dashboard</title>
|
||||
|
||||
<!-- Fonts & Icons -->
|
||||
<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;500;600;700&display=swap" rel="stylesheet">
|
||||
<script src="https://unpkg.com/@phosphor-icons/web"></script>
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ================= AUTH OVERLAY ================= -->
|
||||
<div class="auth-wrapper" id="authWrapper">
|
||||
<div class="auth-card">
|
||||
<div class="auth-header">
|
||||
<div class="auth-logo">
|
||||
<i class="ph-bold ph-car"></i>
|
||||
<span>SIRO ADMIN</span>
|
||||
</div>
|
||||
<p class="auth-subtitle">Enterprise Operations & Control Portal</p>
|
||||
</div>
|
||||
|
||||
<form id="loginForm">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="loginEmail">Admin Email</label>
|
||||
<div class="form-input-group">
|
||||
<input type="email" id="loginEmail" class="form-input" placeholder="admin@siromove.com" value="admin@siromove.com" required>
|
||||
<i class="ph ph-envelope"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="loginPass">Password</label>
|
||||
<div class="form-input-group">
|
||||
<input type="password" id="loginPass" class="form-input" value="••••••••••••" required>
|
||||
<i class="ph ph-lock-key"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary" style="margin-top: 1.5rem;">
|
||||
<i class="ph-bold ph-sign-in"></i> Sign In to Portal
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================= APP CONTAINER ================= -->
|
||||
<div class="app-container">
|
||||
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<a href="#" class="brand">
|
||||
<div class="brand-icon">
|
||||
<i class="ph-bold ph-lightning"></i>
|
||||
</div>
|
||||
<span>SIRO</span>
|
||||
<span class="brand-badge">ADMIN</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-menu">
|
||||
<div class="menu-label">Main Menu</div>
|
||||
<a class="nav-item active" data-view="dashboardView">
|
||||
<i class="ph ph-squares-four"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a class="nav-item" data-view="ridesView">
|
||||
<i class="ph ph-path"></i>
|
||||
<span>Rides & Dispatch</span>
|
||||
</a>
|
||||
<a class="nav-item" data-view="driversView">
|
||||
<i class="ph ph-steering-wheel"></i>
|
||||
<span>Captains / Drivers</span>
|
||||
</a>
|
||||
<a class="nav-item" data-view="passengersView">
|
||||
<i class="ph ph-users"></i>
|
||||
<span>Passengers</span>
|
||||
</a>
|
||||
|
||||
<div class="menu-label">Management</div>
|
||||
<a class="nav-item" data-view="financialsView">
|
||||
<i class="ph ph-currency-circle-dollar"></i>
|
||||
<span>Financials & Reports</span>
|
||||
</a>
|
||||
<a class="nav-item" data-view="complaintsView">
|
||||
<i class="ph ph-warning-circle"></i>
|
||||
<span>Support & Complaints</span>
|
||||
</a>
|
||||
<a class="nav-item" data-view="systemView">
|
||||
<i class="ph ph-cpu"></i>
|
||||
<span>System Status</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="user-profile-mini">
|
||||
<div class="avatar">SA</div>
|
||||
<div class="user-info">
|
||||
<div class="name">Super Admin</div>
|
||||
<div class="role">Operations Lead</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-icon" id="logoutBtn" title="Sign Out">
|
||||
<i class="ph ph-sign-out"></i>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Body -->
|
||||
<div class="main-wrapper">
|
||||
|
||||
<!-- Top Sticky Header -->
|
||||
<header class="top-header">
|
||||
<div class="header-left">
|
||||
<button class="btn-icon toggle-sidebar-btn" id="toggleSidebar">
|
||||
<i class="ph ph-list"></i>
|
||||
</button>
|
||||
<div class="search-box">
|
||||
<i class="ph ph-magnifying-glass"></i>
|
||||
<input type="text" id="globalSearch" placeholder="Search trips, drivers, passengers...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<div class="live-indicator">
|
||||
<span class="pulse-dot"></span> Live Fleet Network
|
||||
</div>
|
||||
<button class="btn-icon" title="Notifications">
|
||||
<i class="ph ph-bell"></i>
|
||||
</button>
|
||||
<button class="btn-icon" title="Settings">
|
||||
<i class="ph ph-gear"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="content-body">
|
||||
|
||||
<!-- ================= 1. DASHBOARD OVERVIEW ================= -->
|
||||
<section class="page-view active" id="dashboardView">
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>Operations Dashboard</h1>
|
||||
<p>Real-time analytics and fleet performance metrics</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<button class="btn btn-secondary"><i class="ph ph-calendar"></i> Today</button>
|
||||
<button class="btn btn-primary" style="width: auto;"><i class="ph ph-download-simple"></i> Export PDF</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI Cards Grid -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-value">$42,850</div>
|
||||
<div class="stat-title">Today's Revenue</div>
|
||||
</div>
|
||||
<div class="stat-icon primary"><i class="ph ph-currency-dollar"></i></div>
|
||||
</div>
|
||||
<div class="stat-footer">
|
||||
<span class="trend-badge up"><i class="ph ph-trend-up"></i> +14.2%</span>
|
||||
<span class="trend-text">vs yesterday</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-value">1,482</div>
|
||||
<div class="stat-title">Active Rides</div>
|
||||
</div>
|
||||
<div class="stat-icon success"><i class="ph ph-car-profile"></i></div>
|
||||
</div>
|
||||
<div class="stat-footer">
|
||||
<span class="trend-badge up"><i class="ph ph-trend-up"></i> +8.6%</span>
|
||||
<span class="trend-text">fleet active</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-value">324</div>
|
||||
<div class="stat-title">Captains Online</div>
|
||||
</div>
|
||||
<div class="stat-icon info"><i class="ph ph-steering-wheel"></i></div>
|
||||
</div>
|
||||
<div class="stat-footer">
|
||||
<span class="trend-badge up"><i class="ph ph-check"></i> 94% online rate</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-value">98.4%</div>
|
||||
<div class="stat-title">Dispatch Rate</div>
|
||||
</div>
|
||||
<div class="stat-icon warning"><i class="ph ph-lightning"></i></div>
|
||||
</div>
|
||||
<div class="stat-footer">
|
||||
<span class="trend-badge down"><i class="ph ph-trend-down"></i> -0.4%</span>
|
||||
<span class="trend-text">avg 3.2m wait</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Section -->
|
||||
<div class="dashboard-grid">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Revenue & Trip Surge Analytics</h3>
|
||||
<button class="btn btn-secondary btn-sm">Weekly</button>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="revenueChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Live Dispatch Feed</h3>
|
||||
</div>
|
||||
<div style="display:flex; flex-direction:column; gap:1rem;">
|
||||
<div style="display:flex; align-items:center; gap:0.75rem; padding:0.75rem; background:rgba(255,255,255,0.03); border-radius:var(--radius-md);">
|
||||
<div style="width:36px; height:36px; border-radius:var(--radius-full); background:var(--success-bg); color:var(--success); display:flex; align-items:center; justify-content:center;">
|
||||
<i class="ph ph-check-circle"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight:600; font-size:0.88rem;">Ride SR-9842 Completed</div>
|
||||
<div style="font-size:0.75rem; color:var(--text-subtle);">Driver: Khalid H. | $14.50</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; align-items:center; gap:0.75rem; padding:0.75rem; background:rgba(255,255,255,0.03); border-radius:var(--radius-md);">
|
||||
<div style="width:36px; height:36px; border-radius:var(--radius-full); background:var(--primary-light); color:var(--primary); display:flex; align-items:center; justify-content:center;">
|
||||
<i class="ph ph-arrows-clockwise"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight:600; font-size:0.88rem;">New Dispatch SR-9846</div>
|
||||
<div style="font-size:0.75rem; color:var(--text-subtle);">Driver: Bilal M. | Sweifieh</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; align-items:center; gap:0.75rem; padding:0.75rem; background:rgba(255,255,255,0.03); border-radius:var(--radius-md);">
|
||||
<div style="width:36px; height:36px; border-radius:var(--radius-full); background:var(--warning-bg); color:var(--warning); display:flex; align-items:center; justify-content:center;">
|
||||
<i class="ph ph-star"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight:600; font-size:0.88rem;">5-Star Rating Submitted</div>
|
||||
<div style="font-size:0.75rem; color:var(--text-subtle);">Rider: Sarah M.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================= 2. RIDES & DISPATCH VIEW ================= -->
|
||||
<section class="page-view" id="ridesView">
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>Rides & Dispatch Management</h1>
|
||||
<p>Monitor real-time passenger trips, statuses, and fare details</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<button class="btn btn-primary" style="width:auto;"><i class="ph ph-plus"></i> Manual Dispatch</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-bar">
|
||||
<div class="filter-inputs">
|
||||
<select class="select-input">
|
||||
<option>All Statuses</option>
|
||||
<option>Completed</option>
|
||||
<option>In Progress</option>
|
||||
<option>Cancelled</option>
|
||||
</select>
|
||||
<select class="select-input">
|
||||
<option>All Cities</option>
|
||||
<option>Amman</option>
|
||||
<option>Zarqa</option>
|
||||
<option>Irbid</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Trip ID</th>
|
||||
<th>Passenger</th>
|
||||
<th>Driver</th>
|
||||
<th>Route</th>
|
||||
<th>Fare</th>
|
||||
<th>Status</th>
|
||||
<th>Time</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ridesTableBody">
|
||||
<!-- Populated by JS -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================= 3. DRIVERS VIEW ================= -->
|
||||
<section class="page-view" id="driversView">
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>Captains & Drivers</h1>
|
||||
<p>Verify documents, manage online fleet, and inspect ratings</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<button class="btn btn-primary" style="width:auto;"><i class="ph ph-user-plus"></i> Add New Driver</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Driver ID</th>
|
||||
<th>Name</th>
|
||||
<th>Phone</th>
|
||||
<th>Vehicle</th>
|
||||
<th>Rating</th>
|
||||
<th>Total Trips</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="driversTableBody">
|
||||
<!-- Populated by JS -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================= 4. PASSENGERS VIEW ================= -->
|
||||
<section class="page-view" id="passengersView">
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>Passengers & Customers</h1>
|
||||
<p>Customer accounts, trip history, and subscription status</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Customer ID</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Trips Count</th>
|
||||
<th>Rating</th>
|
||||
<th>Joined Date</th>
|
||||
<th>Account Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="passengersTableBody">
|
||||
<!-- Populated by JS -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================= 5. FINANCIALS VIEW ================= -->
|
||||
<section class="page-view" id="financialsView">
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>Financials & Reports</h1>
|
||||
<p>Payout calculations, platform commissions, and exported ledgers</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<button class="btn btn-primary" style="width:auto;"><i class="ph ph-file-text"></i> Generate Report</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">$184,200</div>
|
||||
<div class="stat-title">Gross Revenue (This Month)</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">$27,630</div>
|
||||
<div class="stat-title">Platform Net Commission (15%)</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">$156,570</div>
|
||||
<div class="stat-title">Driver Payouts Processed</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================= 6. COMPLAINTS VIEW ================= -->
|
||||
<section class="page-view" id="complaintsView">
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>Support & Complaints Triage</h1>
|
||||
<p>Customer issues, lost & found, and ride rating disputes</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div style="padding:1rem; text-align:center; color:var(--text-muted);">
|
||||
<i class="ph ph-shield-check" style="font-size:3rem; color:var(--success); margin-bottom:0.5rem; display:block;"></i>
|
||||
<h3>All high-priority complaints resolved</h3>
|
||||
<p style="font-size:0.88rem; margin-top:0.25rem;">0 active tickets requiring immediate escalation.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================= 7. SYSTEM STATUS VIEW ================= -->
|
||||
<section class="page-view" id="systemView">
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>System Health & Microservices</h1>
|
||||
<p>Server nodes, API gateways, database latency, and websocket connections</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-value" style="color:var(--success);">Healthy</div>
|
||||
<div class="stat-title">Location Server</div>
|
||||
</div>
|
||||
<div class="stat-icon success"><i class="ph ph-map-pin-line"></i></div>
|
||||
</div>
|
||||
<div class="stat-footer"><span class="trend-text">Latency: 14ms</span></div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-value" style="color:var(--success);">Healthy</div>
|
||||
<div class="stat-title">Ride Dispatch Engine</div>
|
||||
</div>
|
||||
<div class="stat-icon success"><i class="ph ph-cpu"></i></div>
|
||||
</div>
|
||||
<div class="stat-footer"><span class="trend-text">Latency: 22ms</span></div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-value" style="color:var(--success);">Healthy</div>
|
||||
<div class="stat-title">Payment Server</div>
|
||||
</div>
|
||||
<div class="stat-icon success"><i class="ph ph-credit-card"></i></div>
|
||||
</div>
|
||||
<div class="stat-footer"><span class="trend-text">Latency: 45ms</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Details Modal -->
|
||||
<div class="modal-overlay" id="detailsModal">
|
||||
<div class="modal-content">
|
||||
<div id="modalBodyContent">
|
||||
<!-- Dynamic content -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,333 @@
|
||||
/* ==========================================================================
|
||||
Siro Admin Web Portal - Interactive Application Engine
|
||||
========================================================================== */
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// DOM Elements
|
||||
const authWrapper = document.getElementById('authWrapper');
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
const navItems = document.querySelectorAll('.nav-item[data-view]');
|
||||
const pageViews = document.querySelectorAll('.page-view');
|
||||
const logoutBtn = document.getElementById('logoutBtn');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const toggleSidebarBtn = document.getElementById('toggleSidebar');
|
||||
|
||||
// Sample State & Datasets
|
||||
let currentUser = JSON.parse(localStorage.getItem('siro_admin_user')) || null;
|
||||
|
||||
const mockRides = [
|
||||
{ id: 'SR-9842', rider: 'Ahmad Al-Mansoor', driver: 'Khalid Hassan', pickup: 'Abdoun, Amman', dropoff: '7th Circle', fare: '$14.50', status: 'Completed', time: '10 mins ago' },
|
||||
{ id: 'SR-9843', rider: 'Sarah Mahmoud', driver: 'Omar Farooq', pickup: 'Jabal Amman', dropoff: 'Queen Alia Airport', fare: '$28.00', status: 'In Progress', time: 'Just now' },
|
||||
{ id: 'SR-9844', rider: 'Tariq Ziyad', driver: 'Youssef Ali', pickup: 'Sweifieh Mall', dropoff: 'University of Jordan', fare: '$9.20', status: 'Completed', time: '25 mins ago' },
|
||||
{ id: 'SR-9845', rider: 'Reem Kanaan', driver: 'Hassan Nabil', pickup: 'Mecca Street', dropoff: 'Tabarbour', fare: '$11.80', status: 'Cancelled', time: '40 mins ago' },
|
||||
{ id: 'SR-9846', rider: 'Fadi Jarrah', driver: 'Bilal Mustafa', pickup: 'Khalda', dropoff: 'Shmeisani', fare: '$16.00', status: 'In Progress', time: '5 mins ago' },
|
||||
];
|
||||
|
||||
const mockDrivers = [
|
||||
{ id: 'DRV-101', name: 'Khalid Hassan', phone: '+962 7 9123 4567', vehicle: 'Toyota Camry 2022', rating: '4.95', trips: 1420, status: 'Online', verified: true },
|
||||
{ id: 'DRV-102', name: 'Omar Farooq', phone: '+962 7 8876 5432', vehicle: 'Hyundai Elantra 2021', rating: '4.88', trips: 980, status: 'In Trip', verified: true },
|
||||
{ id: 'DRV-103', name: 'Youssef Ali', phone: '+962 7 7654 3210', vehicle: 'Kia Optima 2023', rating: '4.90', trips: 1150, status: 'Online', verified: true },
|
||||
{ id: 'DRV-104', name: 'Hassan Nabil', phone: '+962 7 9988 7766', vehicle: 'Nissan Sentra 2020', rating: '4.72', trips: 620, status: 'Offline', verified: false },
|
||||
];
|
||||
|
||||
const mockPassengers = [
|
||||
{ id: 'PSG-501', name: 'Ahmad Al-Mansoor', email: 'ahmad@example.com', trips: 45, rating: '4.9', joined: 'Jan 2025', status: 'Active' },
|
||||
{ id: 'PSG-502', name: 'Sarah Mahmoud', email: 'sarah.m@example.com', trips: 89, rating: '5.0', joined: 'Nov 2024', status: 'Active' },
|
||||
{ id: 'PSG-503', name: 'Reem Kanaan', email: 'reem.k@example.com', trips: 12, rating: '4.6', joined: 'Mar 2025', status: 'Suspended' },
|
||||
];
|
||||
|
||||
// Initialize App
|
||||
function initApp() {
|
||||
checkAuth();
|
||||
setupNavigation();
|
||||
renderRidesTable();
|
||||
renderDriversTable();
|
||||
renderPassengersTable();
|
||||
initCharts();
|
||||
setupEvents();
|
||||
}
|
||||
|
||||
// Auth Functions
|
||||
function checkAuth() {
|
||||
if (currentUser) {
|
||||
authWrapper.classList.add('hidden');
|
||||
} else {
|
||||
authWrapper.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
loginForm?.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const email = document.getElementById('loginEmail').value;
|
||||
currentUser = { name: 'Super Admin', email: email, role: 'Administrator' };
|
||||
localStorage.setItem('siro_admin_user', JSON.stringify(currentUser));
|
||||
authWrapper.classList.add('hidden');
|
||||
showNotification('Welcome back, Super Admin!', 'success');
|
||||
});
|
||||
|
||||
logoutBtn?.addEventListener('click', () => {
|
||||
localStorage.removeItem('siro_admin_user');
|
||||
currentUser = null;
|
||||
authWrapper.classList.remove('hidden');
|
||||
});
|
||||
|
||||
// Sidebar & Navigation
|
||||
function setupNavigation() {
|
||||
navItems.forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const targetView = item.getAttribute('data-view');
|
||||
|
||||
navItems.forEach(n => n.classList.remove('active'));
|
||||
item.classList.add('active');
|
||||
|
||||
pageViews.forEach(view => {
|
||||
if (view.id === targetView) {
|
||||
view.classList.add('active');
|
||||
} else {
|
||||
view.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Close sidebar on mobile
|
||||
if (window.innerWidth <= 992) {
|
||||
sidebar.classList.remove('open');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
toggleSidebarBtn?.addEventListener('click', () => {
|
||||
sidebar.classList.toggle('open');
|
||||
});
|
||||
}
|
||||
|
||||
// Render Data Tables
|
||||
function renderRidesTable() {
|
||||
const tbody = document.getElementById('ridesTableBody');
|
||||
if (!tbody) return;
|
||||
tbody.innerHTML = mockRides.map(ride => `
|
||||
<tr>
|
||||
<td><strong>${ride.id}</strong></td>
|
||||
<td>${ride.rider}</td>
|
||||
<td>${ride.driver}</td>
|
||||
<td><span style="font-size:0.82rem; color: var(--text-muted);"><i class="ph ph-map-pin"></i> ${ride.pickup} → ${ride.dropoff}</span></td>
|
||||
<td><strong>${ride.fare}</strong></td>
|
||||
<td><span class="badge ${getStatusBadge(ride.status)}">${ride.status}</span></td>
|
||||
<td>${ride.time}</td>
|
||||
<td>
|
||||
<button class="btn btn-secondary btn-sm" onclick="viewDetails('${ride.id}')">
|
||||
<i class="ph ph-eye"></i> View
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderDriversTable() {
|
||||
const tbody = document.getElementById('driversTableBody');
|
||||
if (!tbody) return;
|
||||
tbody.innerHTML = mockDrivers.map(drv => `
|
||||
<tr>
|
||||
<td><strong>${drv.id}</strong></td>
|
||||
<td>
|
||||
<div style="display:flex; align-items:center; gap:0.5rem;">
|
||||
<span>${drv.name}</span>
|
||||
${drv.verified ? '<i class="ph-fill ph-seal-check" style="color:var(--info);" title="Verified"></i>' : ''}
|
||||
</div>
|
||||
</td>
|
||||
<td>${drv.phone}</td>
|
||||
<td>${drv.vehicle}</td>
|
||||
<td><i class="ph-fill ph-star" style="color:var(--warning);"></i> ${drv.rating}</td>
|
||||
<td>${drv.trips}</td>
|
||||
<td><span class="badge ${getStatusBadge(drv.status)}">${drv.status}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-secondary btn-sm"><i class="ph ph-gear"></i> Manage</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderPassengersTable() {
|
||||
const tbody = document.getElementById('passengersTableBody');
|
||||
if (!tbody) return;
|
||||
tbody.innerHTML = mockPassengers.map(p => `
|
||||
<tr>
|
||||
<td><strong>${p.id}</strong></td>
|
||||
<td>${p.name}</td>
|
||||
<td>${p.email}</td>
|
||||
<td>${p.trips} trips</td>
|
||||
<td><i class="ph-fill ph-star" style="color:var(--warning);"></i> ${p.rating}</td>
|
||||
<td>${p.joined}</td>
|
||||
<td><span class="badge ${getStatusBadge(p.status)}">${p.status}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-secondary btn-sm"><i class="ph ph-user"></i> Profile</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function getStatusBadge(status) {
|
||||
switch (status) {
|
||||
case 'Completed':
|
||||
case 'Online':
|
||||
case 'Active':
|
||||
return 'badge-success';
|
||||
case 'In Progress':
|
||||
case 'In Trip':
|
||||
return 'badge-primary';
|
||||
case 'Cancelled':
|
||||
case 'Offline':
|
||||
case 'Suspended':
|
||||
return 'badge-danger';
|
||||
default:
|
||||
return 'badge-info';
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic Canvas Charts
|
||||
function initCharts() {
|
||||
const canvas = document.getElementById('revenueChart');
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Resize canvas
|
||||
canvas.width = canvas.parentElement.clientWidth;
|
||||
canvas.height = canvas.parentElement.clientHeight;
|
||||
|
||||
const data = [12000, 18500, 15000, 24000, 29000, 34500, 42000];
|
||||
const labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||
|
||||
const W = canvas.width;
|
||||
const H = canvas.height;
|
||||
const padding = 40;
|
||||
|
||||
// Draw Grid Lines
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
|
||||
ctx.lineWidth = 1;
|
||||
for (let i = 0; i <= 4; i++) {
|
||||
const y = padding + (i * (H - padding * 2) / 4);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padding, y);
|
||||
ctx.lineTo(W - padding, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw Smooth Line
|
||||
const max = 50000;
|
||||
const points = data.map((val, idx) => {
|
||||
const x = padding + (idx * (W - padding * 2) / (data.length - 1));
|
||||
const y = H - padding - (val / max * (H - padding * 2));
|
||||
return { x, y };
|
||||
});
|
||||
|
||||
// Area Gradient
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, H);
|
||||
gradient.addColorStop(0, 'rgba(99, 102, 241, 0.4)');
|
||||
gradient.addColorStop(1, 'rgba(99, 102, 241, 0.0)');
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(points[0].x, points[0].y);
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
const xc = (points[i].x + points[i - 1].x) / 2;
|
||||
const yc = (points[i].y + points[i - 1].y) / 2;
|
||||
ctx.quadraticCurveTo(points[i - 1].x, points[i - 1].y, xc, yc);
|
||||
}
|
||||
ctx.lineTo(points[points.length - 1].x, points[points.length - 1].y);
|
||||
ctx.lineTo(points[points.length - 1].x, H - padding);
|
||||
ctx.lineTo(points[0].x, H - padding);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fill();
|
||||
|
||||
// Line Path
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(points[0].x, points[0].y);
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
const xc = (points[i].x + points[i - 1].x) / 2;
|
||||
const yc = (points[i].y + points[i - 1].y) / 2;
|
||||
ctx.quadraticCurveTo(points[i - 1].x, points[i - 1].y, xc, yc);
|
||||
}
|
||||
ctx.lineTo(points[points.length - 1].x, points[points.length - 1].y);
|
||||
ctx.strokeStyle = '#6366f1';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.stroke();
|
||||
|
||||
// Draw Points
|
||||
points.forEach((p, idx) => {
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, 5, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#6366f1';
|
||||
ctx.fill();
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = '#fff';
|
||||
ctx.stroke();
|
||||
|
||||
// Labels
|
||||
ctx.fillStyle = '#94a3b8';
|
||||
ctx.font = '12px Inter';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(labels[idx], p.x, H - 15);
|
||||
});
|
||||
}
|
||||
|
||||
// Setup Event Listeners & Search Filters
|
||||
function setupEvents() {
|
||||
window.addEventListener('resize', () => {
|
||||
initCharts();
|
||||
});
|
||||
|
||||
const searchInput = document.getElementById('globalSearch');
|
||||
searchInput?.addEventListener('input', (e) => {
|
||||
const query = e.target.value.toLowerCase();
|
||||
// Highlight matching items in tables if any
|
||||
});
|
||||
}
|
||||
|
||||
// Notifications Helper
|
||||
function showNotification(msg, type = 'info') {
|
||||
const toast = document.createElement('div');
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
background: var(--bg-elevated);
|
||||
border: 1px solid var(--border-active);
|
||||
color: #fff;
|
||||
padding: 12px 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--shadow-md);
|
||||
z-index: 9999;
|
||||
font-size: 0.9rem;
|
||||
animation: fadeIn 0.3s ease;
|
||||
`;
|
||||
toast.innerHTML = `<i class="ph ph-check-circle" style="color:var(--success);"></i> ${msg}`;
|
||||
document.body.appendChild(toast);
|
||||
setTimeout(() => toast.remove(), 3500);
|
||||
}
|
||||
|
||||
window.viewDetails = function(id) {
|
||||
const modal = document.getElementById('detailsModal');
|
||||
const modalBody = document.getElementById('modalBodyContent');
|
||||
if (!modal || !modalBody) return;
|
||||
|
||||
modalBody.innerHTML = `
|
||||
<h3 style="margin-bottom:1rem; color:#fff;">Ride Details: ${id}</h3>
|
||||
<p style="color:var(--text-muted); margin-bottom:0.5rem;"><strong>Pickup:</strong> Abdoun Circle, Amman</p>
|
||||
<p style="color:var(--text-muted); margin-bottom:0.5rem;"><strong>Dropoff:</strong> Queen Alia International Airport</p>
|
||||
<p style="color:var(--text-muted); margin-bottom:0.5rem;"><strong>Fare Breakdown:</strong> Base Fare ($4.00) + Distance ($20.00) + Peak Surge ($4.00)</p>
|
||||
<p style="color:var(--text-muted); margin-bottom:1.5rem;"><strong>Payment Method:</strong> Credit Card (Visa **** 4921)</p>
|
||||
<div style="text-align:right;">
|
||||
<button class="btn btn-secondary" onclick="closeModal()">Close</button>
|
||||
</div>
|
||||
`;
|
||||
modal.classList.add('active');
|
||||
};
|
||||
|
||||
window.closeModal = function() {
|
||||
const modal = document.getElementById('detailsModal');
|
||||
modal?.classList.remove('active');
|
||||
};
|
||||
|
||||
// Run initial state
|
||||
initApp();
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
/* ==========================================================================
|
||||
Siro Service Web Portal - Modern Design System
|
||||
========================================================================== */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--bg-dark: #090d16;
|
||||
--bg-card: rgba(17, 24, 39, 0.75);
|
||||
--border-color: rgba(255, 255, 255, 0.08);
|
||||
--primary: #06b6d4;
|
||||
--accent: #3b82f6;
|
||||
--text-main: #f8fafc;
|
||||
--text-muted: #94a3b8;
|
||||
--radius-lg: 18px;
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: var(--bg-dark);
|
||||
color: var(--text-main);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 1.5rem 3rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.logo i {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
padding: 3rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 3rem;
|
||||
text-align: center;
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
|
||||
.hero-card h1 {
|
||||
font-size: 2.25rem;
|
||||
margin-bottom: 1rem;
|
||||
background: linear-gradient(135deg, #fff, var(--primary));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
background: rgba(30, 41, 59, 0.4);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 14px;
|
||||
padding: 1.75rem;
|
||||
text-align: left;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.service-card:hover {
|
||||
transform: translateY(-4px);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background: rgba(6, 182, 212, 0.15);
|
||||
color: var(--primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.btn-cyan {
|
||||
background: linear-gradient(135deg, var(--primary), var(--accent));
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
margin-top: 1.5rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Siro Service | Fleet & Maintenance Portal</title>
|
||||
|
||||
<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;500;600;700&display=swap" rel="stylesheet">
|
||||
<script src="https://unpkg.com/@phosphor-icons/web"></script>
|
||||
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<div class="logo">
|
||||
<i class="ph-bold ph-wrench"></i>
|
||||
<span>SIRO SERVICE</span>
|
||||
</div>
|
||||
<div>
|
||||
<span style="font-size:0.88rem; color:var(--text-muted);"><i class="ph-fill ph-circle-pulse" style="color:#10b981;"></i> Service Hub Online</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="hero-card">
|
||||
<h1>Siro Fleet Maintenance & Service Center</h1>
|
||||
<p style="color:var(--text-muted); max-width:600px; margin:0 auto;">
|
||||
Dedicated web portal for registered service centers, vehicle inspection, maintenance scheduling, and captain support services.
|
||||
</p>
|
||||
|
||||
<div class="services-grid">
|
||||
<div class="service-card">
|
||||
<div class="service-icon"><i class="ph ph-shield-check"></i></div>
|
||||
<h3 style="margin-bottom:0.5rem; font-size:1.1rem;">Vehicle Inspection</h3>
|
||||
<p style="font-size:0.88rem; color:var(--text-muted);">Perform 50-point technical inspections and issue digital safety permits for captains.</p>
|
||||
</div>
|
||||
|
||||
<div class="service-card">
|
||||
<div class="service-icon"><i class="ph ph-gas-pump"></i></div>
|
||||
<h3 style="margin-bottom:0.5rem; font-size:1.1rem;">Fuel & EV Charging</h3>
|
||||
<p style="font-size:0.88rem; color:var(--text-muted);">Manage partner station discounts, EV charging session ledgers, and fuel vouchers.</p>
|
||||
</div>
|
||||
|
||||
<div class="service-card">
|
||||
<div class="service-icon"><i class="ph ph-arrows-counter-clockwise"></i></div>
|
||||
<h3 style="margin-bottom:0.5rem; font-size:1.1rem;">Maintenance Scheduling</h3>
|
||||
<p style="font-size:0.88rem; color:var(--text-muted);">Routine oil changes, tire rotations, and scheduled service appointments for active fleet.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn-cyan" onclick="alert('Siro Service Dispatch Agent initialized successfully.')">
|
||||
<i class="ph ph-power"></i> Launch Service Desk
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user