frontend fixed and good
This commit is contained in:
1
static/css/main.css
Normal file
1
static/css/main.css
Normal file
File diff suppressed because one or more lines are too long
131
static/js/fetch-wrapper.js
Normal file
131
static/js/fetch-wrapper.js
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
/**
|
||||||
|
* Lightweight fetch wrapper that:
|
||||||
|
* - Adds an X-Correlation-ID header to every request
|
||||||
|
* - Exposes helpers to parse the standard error envelope
|
||||||
|
* - Provides helpers to build user-facing error messages with correlation ID
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
// Ensure global app object exists
|
||||||
|
window.app = window.app || {};
|
||||||
|
|
||||||
|
const CORRELATION_HEADER = 'X-Correlation-ID';
|
||||||
|
|
||||||
|
function generateCorrelationId() {
|
||||||
|
try {
|
||||||
|
if (window.crypto && typeof window.crypto.randomUUID === 'function') {
|
||||||
|
return window.crypto.randomUUID();
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
// Fallback RFC4122-ish UUID
|
||||||
|
const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).slice(1);
|
||||||
|
return (
|
||||||
|
s4() + s4() + '-' + s4() + '-' + s4() + '-' +
|
||||||
|
s4() + '-' + s4() + s4() + s4()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHeaders(input) {
|
||||||
|
if (!input) return new Headers();
|
||||||
|
return input instanceof Headers ? input : new Headers(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalFetch = window.fetch.bind(window);
|
||||||
|
|
||||||
|
async function wrappedFetch(resource, options = {}) {
|
||||||
|
const url = typeof resource === 'string' ? resource : (resource && resource.url) || '';
|
||||||
|
const headers = normalizeHeaders(options.headers);
|
||||||
|
|
||||||
|
// Inject correlation id if not present
|
||||||
|
let outgoingCid = headers.get(CORRELATION_HEADER);
|
||||||
|
if (!outgoingCid) {
|
||||||
|
outgoingCid = generateCorrelationId();
|
||||||
|
headers.set(CORRELATION_HEADER, outgoingCid);
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestInit = { ...options, headers };
|
||||||
|
|
||||||
|
const response = await originalFetch(resource, requestInit);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const incomingCid = (response && response.headers && response.headers.get(CORRELATION_HEADER)) || null;
|
||||||
|
// Track last seen correlation id for diagnostics/UI messaging
|
||||||
|
window.app.lastCorrelationId = incomingCid || outgoingCid || null;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the standardized error envelope from a failed response.
|
||||||
|
* Returns a normalized object with message, correlationId, code, details.
|
||||||
|
*/
|
||||||
|
async function parseErrorEnvelope(response) {
|
||||||
|
let body = null;
|
||||||
|
try {
|
||||||
|
body = await response.clone().json();
|
||||||
|
} catch (_) {
|
||||||
|
// not JSON; try text as last resort
|
||||||
|
try {
|
||||||
|
const txt = await response.clone().text();
|
||||||
|
body = { detail: txt };
|
||||||
|
} catch (_) {
|
||||||
|
body = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerCid = response && response.headers ? response.headers.get(CORRELATION_HEADER) : null;
|
||||||
|
const envelopeCid = body && (body.correlation_id || (body.error && body.error.correlation_id));
|
||||||
|
const correlationId = headerCid || envelopeCid || window.app.lastCorrelationId || null;
|
||||||
|
|
||||||
|
const message = body && body.error && body.error.message
|
||||||
|
? String(body.error.message)
|
||||||
|
: body && typeof body.detail === 'string' && body.detail.trim()
|
||||||
|
? String(body.detail)
|
||||||
|
: `HTTP ${response.status}`;
|
||||||
|
|
||||||
|
const code = body && body.error ? (body.error.code || null) : null;
|
||||||
|
const details = body && body.error ? (body.error.details || null) : null;
|
||||||
|
|
||||||
|
return { message, correlationId, code, details, raw: body };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an Error populated from a failed Response and optional fallback message.
|
||||||
|
*/
|
||||||
|
async function toError(response, fallbackMessage = null) {
|
||||||
|
const parsed = await parseErrorEnvelope(response);
|
||||||
|
const msg = parsed && parsed.message ? parsed.message : (fallbackMessage || `HTTP ${response.status}`);
|
||||||
|
const err = new Error(msg);
|
||||||
|
err.status = response.status;
|
||||||
|
err.correlationId = parsed.correlationId || null;
|
||||||
|
if (parsed && parsed.code) err.code = parsed.code;
|
||||||
|
if (parsed && parsed.details) err.details = parsed.details;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a message suitable for UI alerts with correlation id reference.
|
||||||
|
* Example: formatAlert(err, 'Error saving customer')
|
||||||
|
*/
|
||||||
|
function formatAlert(errorOrMessage, prefix = null) {
|
||||||
|
const isError = errorOrMessage instanceof Error;
|
||||||
|
const message = isError ? errorOrMessage.message : String(errorOrMessage || '');
|
||||||
|
const correlationId = isError && errorOrMessage.correlationId
|
||||||
|
? errorOrMessage.correlationId
|
||||||
|
: (window.app && window.app.lastCorrelationId) || null;
|
||||||
|
const base = prefix ? `${prefix}: ${message}` : message;
|
||||||
|
return correlationId ? `${base} (Ref: ${correlationId})` : base;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose helpers
|
||||||
|
window.http = {
|
||||||
|
parseErrorEnvelope,
|
||||||
|
toError,
|
||||||
|
formatAlert,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Install wrapper
|
||||||
|
window.fetch = wrappedFetch;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
@@ -39,27 +39,51 @@ module.exports = {
|
|||||||
// Semantic colors
|
// Semantic colors
|
||||||
success: {
|
success: {
|
||||||
50: '#f0fdf4',
|
50: '#f0fdf4',
|
||||||
|
100: '#dcfce7',
|
||||||
|
200: '#bbf7d0',
|
||||||
|
300: '#86efac',
|
||||||
|
400: '#4ade80',
|
||||||
500: '#22c55e',
|
500: '#22c55e',
|
||||||
600: '#16a34a',
|
600: '#16a34a',
|
||||||
700: '#15803d',
|
700: '#15803d',
|
||||||
|
800: '#166534',
|
||||||
|
900: '#14532d',
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
50: '#fffbeb',
|
50: '#fffbeb',
|
||||||
|
100: '#fef3c7',
|
||||||
|
200: '#fde68a',
|
||||||
|
300: '#fcd34d',
|
||||||
|
400: '#fbbf24',
|
||||||
500: '#f59e0b',
|
500: '#f59e0b',
|
||||||
600: '#d97706',
|
600: '#d97706',
|
||||||
700: '#b45309',
|
700: '#b45309',
|
||||||
|
800: '#92400e',
|
||||||
|
900: '#78350f',
|
||||||
},
|
},
|
||||||
danger: {
|
danger: {
|
||||||
50: '#fef2f2',
|
50: '#fef2f2',
|
||||||
|
100: '#fee2e2',
|
||||||
|
200: '#fecaca',
|
||||||
|
300: '#fca5a5',
|
||||||
|
400: '#f87171',
|
||||||
500: '#ef4444',
|
500: '#ef4444',
|
||||||
600: '#dc2626',
|
600: '#dc2626',
|
||||||
700: '#b91c1c',
|
700: '#b91c1c',
|
||||||
|
800: '#991b1b',
|
||||||
|
900: '#7f1d1d',
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
50: '#f0f9ff',
|
50: '#f0f9ff',
|
||||||
|
100: '#e0f2fe',
|
||||||
|
200: '#bae6fd',
|
||||||
|
300: '#7dd3fc',
|
||||||
|
400: '#38bdf8',
|
||||||
500: '#06b6d4',
|
500: '#06b6d4',
|
||||||
600: '#0891b2',
|
600: '#0891b2',
|
||||||
700: '#0e7490',
|
700: '#0e7490',
|
||||||
|
800: '#155e75',
|
||||||
|
900: '#164e63',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
|
|||||||
@@ -1,208 +1,420 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}System Administration{% endblock %}
|
{% block title %}System Administration - Delphi Database{% endblock %}
|
||||||
|
|
||||||
{% block bridge_css %}{% endblock %}
|
{% block bridge_css %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="max-w-full px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
<div class="flex flex-wrap -mx-4 mb-6">
|
<!-- Page Header -->
|
||||||
<div class="w-full md:w-1/4 px-4 mb-4">
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
|
||||||
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow border border-primary-200 dark:border-primary-700">
|
<div class="flex items-center gap-3">
|
||||||
<div class="p-4 text-center">
|
<div class="flex items-center justify-center w-12 h-12 bg-primary-100 dark:bg-primary-800 text-primary-600 dark:text-primary-400 rounded-xl">
|
||||||
<div id="system-status" class="text-4xl mb-2">
|
<i class="fa-solid fa-shield-halved text-xl"></i>
|
||||||
<i class="fas fa-circle text-success"></i>
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-bold text-neutral-900 dark:text-neutral-100">System Administration</h1>
|
||||||
|
<p class="text-neutral-600 dark:text-neutral-400">Manage users, settings, and system operations</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button id="refreshStatsBtn" class="flex items-center gap-2 px-4 py-2 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-200 dark:hover:bg-neutral-600 rounded-lg transition-colors duration-200">
|
||||||
|
<i class="fa-solid fa-rotate-right"></i>
|
||||||
|
<span>Refresh</span>
|
||||||
|
</button>
|
||||||
|
<div class="flex items-center gap-2 px-3 py-2 bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 rounded-lg border border-green-200 dark:border-green-800">
|
||||||
|
<div class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||||
|
<span class="text-sm font-medium">System Online</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Enhanced Stats Cards -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||||
|
<!-- System Status Card -->
|
||||||
|
<div class="bg-white dark:bg-neutral-900 rounded-xl shadow-sm dark:shadow-none border border-neutral-200 dark:border-neutral-700 hover:shadow-md dark:hover:shadow-lg dark:hover:shadow-black/20 transition-all duration-200">
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<div class="flex items-center justify-center w-12 h-12 bg-green-100 dark:bg-green-800 text-green-600 dark:text-green-300 rounded-lg">
|
||||||
|
<i class="fa-solid fa-heart-pulse text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div id="system-status" class="text-2xl">
|
||||||
|
<i class="fas fa-circle text-green-500"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-medium text-neutral-600 dark:text-neutral-300 uppercase tracking-wide">System Status</h3>
|
||||||
|
<p class="text-2xl font-bold text-neutral-900 dark:text-white mt-1" id="system-status-text">Healthy</p>
|
||||||
|
<div class="mt-2 flex items-center text-xs text-green-600 dark:text-green-400">
|
||||||
|
<i class="fa-solid fa-arrow-up mr-1"></i>
|
||||||
|
<span>All systems operational</span>
|
||||||
</div>
|
</div>
|
||||||
<h6 class="text-sm font-semibold mb-0">System Status</h6>
|
|
||||||
<p class="text-xs" id="system-status-text">Healthy</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/4 px-4 mb-4">
|
|
||||||
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow border border-info-300 dark:border-info-700">
|
<!-- Users Card -->
|
||||||
<div class="p-4 text-center">
|
<div class="bg-white dark:bg-neutral-900 rounded-xl shadow-sm dark:shadow-none border border-neutral-200 dark:border-neutral-700 hover:shadow-md dark:hover:shadow-lg dark:hover:shadow-black/20 transition-all duration-200">
|
||||||
<div class="text-4xl mb-2">
|
<div class="p-6">
|
||||||
<i class="fas fa-users"></i>
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<div class="flex items-center justify-center w-12 h-12 bg-blue-100 dark:bg-blue-800 text-blue-600 dark:text-blue-300 rounded-lg">
|
||||||
|
<i class="fa-solid fa-users text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<button class="text-neutral-400 hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300" onclick="openTab(event, 'users')">
|
||||||
|
<i class="fa-solid fa-external-link"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-medium text-neutral-600 dark:text-neutral-300 uppercase tracking-wide">Total Users</h3>
|
||||||
|
<p class="text-3xl font-bold text-neutral-900 dark:text-white mt-1" id="total-users">0</p>
|
||||||
|
<div class="mt-2 flex items-center text-xs text-blue-600 dark:text-blue-400">
|
||||||
|
<span id="active-users-count">0 active</span>
|
||||||
</div>
|
</div>
|
||||||
<h6 class="text-sm font-semibold">Total Users</h6>
|
|
||||||
<p class="text-2xl font-semibold" id="total-users">0</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/4 px-4 mb-4">
|
|
||||||
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow border border-warning-300 dark:border-warning-700">
|
<!-- Database Card -->
|
||||||
<div class="p-4 text-center">
|
<div class="bg-white dark:bg-neutral-900 rounded-xl shadow-sm dark:shadow-none border border-neutral-200 dark:border-neutral-700 hover:shadow-md dark:hover:shadow-lg dark:hover:shadow-black/20 transition-all duration-200">
|
||||||
<div class="text-4xl mb-2">
|
<div class="p-6">
|
||||||
<i class="fas fa-database"></i>
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<div class="flex items-center justify-center w-12 h-12 bg-purple-100 dark:bg-purple-800 text-purple-600 dark:text-purple-300 rounded-lg">
|
||||||
|
<i class="fa-solid fa-database text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<button class="text-neutral-400 hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300" onclick="openTab(event, 'maintenance')">
|
||||||
|
<i class="fa-solid fa-external-link"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-medium text-neutral-600 dark:text-neutral-300 uppercase tracking-wide">Database Size</h3>
|
||||||
|
<p class="text-2xl font-bold text-neutral-900 dark:text-white mt-1" id="db-size">0 MB</p>
|
||||||
|
<div class="mt-2 flex items-center text-xs text-purple-600 dark:text-purple-400">
|
||||||
|
<span id="db-growth">+0% this week</span>
|
||||||
</div>
|
</div>
|
||||||
<h6 class="text-sm font-semibold">Database Size</h6>
|
|
||||||
<p class="text-base" id="db-size">0 MB</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/4 px-4 mb-4">
|
|
||||||
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow border border-success-300 dark:border-success-700">
|
<!-- Uptime Card -->
|
||||||
<div class="p-4 text-center">
|
<div class="bg-white dark:bg-neutral-900 rounded-xl shadow-sm dark:shadow-none border border-neutral-200 dark:border-neutral-700 hover:shadow-md dark:hover:shadow-lg dark:hover:shadow-black/20 transition-all duration-200">
|
||||||
<div class="text-4xl mb-2">
|
<div class="p-6">
|
||||||
<i class="fas fa-clock"></i>
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<div class="flex items-center justify-center w-12 h-12 bg-amber-100 dark:bg-amber-800 text-amber-600 dark:text-amber-300 rounded-lg">
|
||||||
|
<i class="fa-solid fa-clock text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-neutral-500 dark:text-neutral-400">
|
||||||
|
99.9% uptime
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-medium text-neutral-600 dark:text-neutral-300 uppercase tracking-wide">System Uptime</h3>
|
||||||
|
<p class="text-xl font-bold text-neutral-900 dark:text-white mt-1" id="system-uptime">Unknown</p>
|
||||||
|
<div class="mt-2 flex items-center text-xs text-amber-600 dark:text-amber-400">
|
||||||
|
<span>Last restart: Never</span>
|
||||||
</div>
|
</div>
|
||||||
<h6 class="text-sm font-semibold">System Uptime</h6>
|
|
||||||
<p class="text-xs" id="system-uptime">Unknown</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Navigation Tabs -->
|
<!-- Modern Navigation Tabs -->
|
||||||
<ul id="adminTabs" class="flex border-b border-neutral-200 dark:border-neutral-700 mb-6" role="tablist">
|
<div class="bg-white dark:bg-neutral-900 rounded-xl shadow-sm dark:shadow-none border border-neutral-200 dark:border-neutral-700 mb-8">
|
||||||
<li class="px-4 py-2 -mb-px border-b-2 border-transparent hover:border-primary-600 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors active" id="overview-tab" data-tab-target="#overview" type="button" role="tab">
|
<nav class="flex flex-wrap border-b border-neutral-200 dark:border-neutral-700" role="tablist">
|
||||||
<i class="fas fa-tachometer-alt"></i> Overview
|
<button class="flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 border-primary-600 text-primary-600 dark:text-primary-400 bg-primary-50 dark:bg-primary-900/20 transition-all duration-200 rounded-t-xl active" id="overview-tab" data-tab-target="#overview" type="button" role="tab">
|
||||||
</li>
|
<i class="fa-solid fa-chart-line"></i>
|
||||||
<li class="px-4 py-2 -mb-px border-b-2 border-transparent hover:border-primary-600 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" id="users-tab" data-tab-target="#users" type="button" role="tab">
|
<span>Overview</span>
|
||||||
<i class="fas fa-users"></i> User Management
|
</button>
|
||||||
</li>
|
<button class="flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 border-transparent hover:border-primary-300 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-all duration-200" id="users-tab" data-tab-target="#users" type="button" role="tab">
|
||||||
<li class="px-4 py-2 -mb-px border-b-2 border-transparent hover:border-primary-600 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" id="settings-tab" data-tab-target="#settings" type="button" role="tab">
|
<i class="fa-solid fa-users-gear"></i>
|
||||||
<i class="fas fa-sliders-h"></i> System Settings
|
<span>Users</span>
|
||||||
</li>
|
<span class="ml-1 px-2 py-0.5 bg-blue-100 dark:bg-blue-800 text-blue-700 dark:text-blue-400 text-xs rounded-full" id="users-badge">0</span>
|
||||||
<li class="px-4 py-2 -mb-px border-b-2 border-transparent hover:border-primary-600 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" id="maintenance-tab" data-tab-target="#maintenance" type="button" role="tab">
|
</button>
|
||||||
<i class="fas fa-tools"></i> Maintenance
|
<button class="flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 border-transparent hover:border-primary-300 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-all duration-200" id="settings-tab" data-tab-target="#settings" type="button" role="tab">
|
||||||
</li>
|
<i class="fa-solid fa-sliders"></i>
|
||||||
<li class="px-4 py-2 -mb-px border-b-2 border-transparent hover:border-primary-600 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" id="issues-tab" data-tab-target="#issues" type="button" role="tab">
|
<span>Settings</span>
|
||||||
<i class="fas fa-bug"></i> Issue Tracking
|
</button>
|
||||||
</li>
|
<button class="flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 border-transparent hover:border-primary-300 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-all duration-200" id="maintenance-tab" data-tab-target="#maintenance" type="button" role="tab">
|
||||||
<li class="px-4 py-2 -mb-px border-b-2 border-transparent hover:border-primary-600 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" id="import-tab" data-tab-target="#import" type="button" role="tab">
|
<i class="fa-solid fa-wrench"></i>
|
||||||
<i class="fa-solid fa-upload"></i> Data Import
|
<span>Maintenance</span>
|
||||||
</li>
|
</button>
|
||||||
<li class="px-4 py-2 -mb-px border-b-2 border-transparent hover:border-primary-600 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" id="backup-tab" data-tab-target="#backup" type="button" role="tab">
|
<button class="flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 border-transparent hover:border-primary-300 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-all duration-200" id="import-tab" data-tab-target="#import" type="button" role="tab">
|
||||||
<i class="fas fa-hdd"></i> Backup & Restore
|
<i class="fa-solid fa-file-import"></i>
|
||||||
</li>
|
<span>Import</span>
|
||||||
</ul>
|
</button>
|
||||||
|
<button class="flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 border-transparent hover:border-primary-300 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-all duration-200" id="backup-tab" data-tab-target="#backup" type="button" role="tab">
|
||||||
|
<i class="fa-solid fa-shield-halved"></i>
|
||||||
|
<span>Backup</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 border-transparent hover:border-primary-300 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-all duration-200" id="issues-tab" data-tab-target="#issues" type="button" role="tab">
|
||||||
|
<i class="fa-solid fa-bug"></i>
|
||||||
|
<span>Issues</span>
|
||||||
|
<span class="ml-1 px-2 py-0.5 bg-red-100 dark:bg-red-800 text-red-700 dark:text-red-400 text-xs rounded-full hidden" id="issues-badge">0</span>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<!-- Tab Content -->
|
<!-- Tab Content -->
|
||||||
<div id="adminTabContent">
|
<div id="adminTabContent" class="space-y-6">
|
||||||
<!-- Overview Tab -->
|
<!-- Overview Tab -->
|
||||||
<div id="overview" role="tabpanel" class="">
|
<div id="overview" role="tabpanel" class="space-y-6">
|
||||||
<div class="flex flex-wrap -mx-4">
|
<!-- Quick Stats Overview -->
|
||||||
<div class="w-full md:w-2/3 px-4">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow">
|
<!-- System Statistics -->
|
||||||
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700">
|
<div class="lg:col-span-2">
|
||||||
<h5 class="m-0 font-semibold"><i class="fas fa-chart-bar"></i> System Statistics</h5>
|
<div class="bg-white dark:bg-neutral-900 rounded-xl shadow-sm dark:shadow-none border border-neutral-200 dark:border-neutral-700">
|
||||||
</div>
|
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
|
||||||
<div class="p-4">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex flex-wrap -mx-3">
|
<h3 class="text-lg font-semibold text-neutral-900 dark:text-white flex items-center gap-2">
|
||||||
<div class="w-full sm:w-1/2 px-3 mb-3">
|
<i class="fa-solid fa-chart-bar text-primary-600 dark:text-primary-400"></i>
|
||||||
<strong>Total Customers:</strong>
|
System Statistics
|
||||||
<span id="stat-customers" class="float-right">0</span>
|
</h3>
|
||||||
|
<button class="text-neutral-400 hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300" onclick="loadSystemStats()">
|
||||||
|
<i class="fa-solid fa-rotate-right"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full sm:w-1/2 px-3 mb-3">
|
</div>
|
||||||
<strong>Total Files:</strong>
|
<div class="p-6">
|
||||||
<span id="stat-files" class="float-right">0</span>
|
<div class="grid grid-cols-2 gap-6">
|
||||||
|
<div class="flex items-center justify-between p-4 bg-neutral-50 dark:bg-neutral-800/50 rounded-lg">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-neutral-600 dark:text-neutral-300">Total Customers</p>
|
||||||
|
<p class="text-2xl font-bold text-neutral-900 dark:text-white" id="stat-customers">0</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-10 h-10 bg-blue-100 dark:bg-blue-800 text-blue-600 dark:text-blue-300 rounded-lg flex items-center justify-center">
|
||||||
|
<i class="fa-solid fa-users"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between p-4 bg-neutral-50 dark:bg-neutral-800/50 rounded-lg">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-neutral-600 dark:text-neutral-300">Total Files</p>
|
||||||
|
<p class="text-2xl font-bold text-neutral-900 dark:text-white" id="stat-files">0</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-10 h-10 bg-green-100 dark:bg-green-800 text-green-600 dark:text-green-300 rounded-lg flex items-center justify-center">
|
||||||
|
<i class="fa-solid fa-folder"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between p-4 bg-neutral-50 dark:bg-neutral-800/50 rounded-lg">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-neutral-600 dark:text-neutral-300">Transactions</p>
|
||||||
|
<p class="text-2xl font-bold text-neutral-900 dark:text-white" id="stat-transactions">0</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-10 h-10 bg-purple-100 dark:bg-purple-800 text-purple-600 dark:text-purple-300 rounded-lg flex items-center justify-center">
|
||||||
|
<i class="fa-solid fa-receipt"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between p-4 bg-neutral-50 dark:bg-neutral-800/50 rounded-lg">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-neutral-600 dark:text-neutral-300">QDROs</p>
|
||||||
|
<p class="text-2xl font-bold text-neutral-900 dark:text-white" id="stat-qdros">0</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-10 h-10 bg-amber-100 dark:bg-amber-800 text-amber-600 dark:text-amber-300 rounded-lg flex items-center justify-center">
|
||||||
|
<i class="fa-solid fa-file-contract"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full sm:w-1/2 px-3 mb-3">
|
<div class="mt-6 pt-6 border-t border-neutral-200 dark:border-neutral-700">
|
||||||
<strong>Total Transactions:</strong>
|
<div class="flex items-center justify-between">
|
||||||
<span id="stat-transactions" class="float-right">0</span>
|
<div class="flex items-center gap-4">
|
||||||
</div>
|
<div class="text-center">
|
||||||
<div class="w-full sm:w-1/2 px-3 mb-3">
|
<p class="text-lg font-bold text-neutral-900 dark:text-white" id="stat-active-users">0</p>
|
||||||
<strong>Total QDROs:</strong>
|
<p class="text-xs text-neutral-600 dark:text-neutral-300">Active Users</p>
|
||||||
<span id="stat-qdros" class="float-right">0</span>
|
</div>
|
||||||
</div>
|
<div class="text-center">
|
||||||
<div class="w-full sm:w-1/2 px-3 mb-3">
|
<p class="text-lg font-bold text-neutral-900 dark:text-white" id="stat-admins">0</p>
|
||||||
<strong>Active Users:</strong>
|
<p class="text-xs text-neutral-600 dark:text-neutral-300">Admin Users</p>
|
||||||
<span id="stat-active-users" class="float-right">0</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full sm:w-1/2 px-3 mb-3">
|
<div class="text-right">
|
||||||
<strong>Admin Users:</strong>
|
<p class="text-sm text-neutral-600 dark:text-neutral-300">Last updated</p>
|
||||||
<span id="stat-admins" class="float-right">0</span>
|
<p class="text-sm font-medium" id="stats-updated">Loading...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="w-full md:w-1/3 px-4 mt-4 md:mt-0">
|
<!-- System Alerts -->
|
||||||
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow">
|
<div class="lg:col-span-1">
|
||||||
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700">
|
<div class="bg-white dark:bg-neutral-900 rounded-xl shadow-sm dark:shadow-none border border-neutral-200 dark:border-neutral-700">
|
||||||
<h5 class="m-0 font-semibold"><i class="fas fa-exclamation-triangle"></i> System Alerts</h5>
|
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
|
||||||
|
<h3 class="text-lg font-semibold text-neutral-900 dark:text-white flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-bell text-amber-500"></i>
|
||||||
|
System Alerts
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="p-6">
|
||||||
|
<div id="system-alerts" class="space-y-3">
|
||||||
|
<div class="flex items-center gap-3 p-3 bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 rounded-lg">
|
||||||
|
<i class="fa-solid fa-check-circle"></i>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium">All Systems Normal</p>
|
||||||
|
<p class="text-xs opacity-75">No issues detected</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4">
|
|
||||||
<div id="system-alerts">
|
<!-- Quick Actions -->
|
||||||
<p class="text-neutral-500 dark:text-neutral-400">No alerts</p>
|
<div class="mt-6 bg-white dark:bg-neutral-900 rounded-xl shadow-sm dark:shadow-none border border-neutral-200 dark:border-neutral-700">
|
||||||
|
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
|
||||||
|
<h3 class="text-lg font-semibold text-neutral-900 dark:text-white flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-bolt text-primary-600 dark:text-primary-400"></i>
|
||||||
|
Quick Actions
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 space-y-3">
|
||||||
|
<button class="w-full flex items-center gap-3 p-3 text-left hover:bg-neutral-50 dark:hover:bg-neutral-800/50 rounded-lg transition-colors" onclick="openTab(event, 'users')">
|
||||||
|
<div class="w-8 h-8 bg-blue-100 dark:bg-blue-800 text-blue-600 dark:text-blue-300 rounded-lg flex items-center justify-center">
|
||||||
|
<i class="fa-solid fa-user-plus text-sm"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-neutral-900 dark:text-white">Add User</p>
|
||||||
|
<p class="text-xs text-neutral-600 dark:text-neutral-300">Create new user account</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button class="w-full flex items-center gap-3 p-3 text-left hover:bg-neutral-50 dark:hover:bg-neutral-800/50 rounded-lg transition-colors" onclick="openTab(event, 'backup')">
|
||||||
|
<div class="w-8 h-8 bg-green-100 dark:bg-green-800 text-green-600 dark:text-green-300 rounded-lg flex items-center justify-center">
|
||||||
|
<i class="fa-solid fa-download text-sm"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-neutral-900 dark:text-white">Backup Now</p>
|
||||||
|
<p class="text-xs text-neutral-600 dark:text-neutral-300">Create system backup</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button class="w-full flex items-center gap-3 p-3 text-left hover:bg-neutral-50 dark:hover:bg-neutral-800/50 rounded-lg transition-colors" onclick="openTab(event, 'settings')">
|
||||||
|
<div class="w-8 h-8 bg-purple-100 dark:bg-purple-800 text-purple-600 dark:text-purple-300 rounded-lg flex items-center justify-center">
|
||||||
|
<i class="fa-solid fa-cog text-sm"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-neutral-900 dark:text-white">System Settings</p>
|
||||||
|
<p class="text-xs text-neutral-600 dark:text-neutral-300">Configure system options</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
<!-- Recent Activity -->
|
||||||
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow">
|
<div class="bg-white dark:bg-neutral-900 rounded-xl shadow-sm dark:shadow-none border border-neutral-200 dark:border-neutral-700">
|
||||||
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700">
|
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
|
||||||
<h5 class="m-0 font-semibold"><i class="fas fa-history"></i> Recent Activity</h5>
|
<div class="flex items-center justify-between">
|
||||||
</div>
|
<h3 class="text-lg font-semibold text-neutral-900 dark:text-white flex items-center gap-2">
|
||||||
<div class="p-4">
|
<i class="fa-solid fa-clock-rotate-left text-primary-600 dark:text-primary-400"></i>
|
||||||
<div id="recent-activity">
|
Recent Activity
|
||||||
<p class="text-neutral-500 dark:text-neutral-400">Loading recent activity...</p>
|
</h3>
|
||||||
</div>
|
<button class="text-neutral-400 hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300 text-sm">
|
||||||
</div>
|
View All
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Users Tab -->
|
|
||||||
<div id="users" role="tabpanel" class="hidden">
|
|
||||||
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow">
|
|
||||||
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700 flex items-center justify-between">
|
|
||||||
<h5 class="m-0 font-semibold"><i class="fas fa-users"></i> User Management</h5>
|
|
||||||
<button type="button" class="px-3 py-1.5 bg-primary-600 hover:bg-primary-700 text-white rounded text-sm" onclick="showCreateUserModal()">
|
|
||||||
<i class="fas fa-plus"></i> Add User
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="p-4">
|
|
||||||
<div class="flex flex-wrap -mx-3 mb-3">
|
|
||||||
<div class="w-full md:w-1/2 px-3 mb-3 md:mb-0">
|
|
||||||
<input type="text" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent" id="user-search"
|
|
||||||
placeholder="Search users..." onkeyup="searchUsers()">
|
|
||||||
</div>
|
|
||||||
<div class="w-full md:w-1/4 px-3 mb-3 md:mb-0">
|
|
||||||
<select class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent" id="user-filter" onchange="filterUsers()">
|
|
||||||
<option value="all">All Users</option>
|
|
||||||
<option value="active">Active Only</option>
|
|
||||||
<option value="admin">Admins Only</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="w-full md:w-1/4 px-3">
|
|
||||||
<button type="button" class="w-full md:w-auto px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg hover:bg-neutral-50 dark:hover:bg-neutral-700" onclick="loadUsers()">
|
|
||||||
<i class="fas fa-sync"></i> Refresh
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-6">
|
||||||
|
<div id="recent-activity" class="space-y-4">
|
||||||
|
<div class="flex items-center gap-4 p-4 bg-neutral-50 dark:bg-neutral-800/50 rounded-lg">
|
||||||
|
<div class="w-10 h-10 bg-blue-100 dark:bg-blue-800 text-blue-600 dark:text-blue-300 rounded-full flex items-center justify-center">
|
||||||
|
<i class="fa-solid fa-user text-sm"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-sm font-medium text-neutral-900 dark:text-white">Loading recent activity...</p>
|
||||||
|
<p class="text-xs text-neutral-600 dark:text-neutral-300">Please wait...</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-neutral-500 dark:text-neutral-400">
|
||||||
|
--
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="overflow-x-auto rounded-lg border border-neutral-200 dark:border-neutral-700">
|
<!-- Users Tab -->
|
||||||
<table class="min-w-full divide-y divide-neutral-200 dark:divide-neutral-700">
|
<div id="users" role="tabpanel" class="hidden space-y-6">
|
||||||
<thead class="bg-primary-600 text-white">
|
<div class="bg-white dark:bg-neutral-900 rounded-xl shadow-sm dark:shadow-none border border-neutral-200 dark:border-neutral-700">
|
||||||
<tr>
|
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider">Username</th>
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider">Email</th>
|
<div>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider">Name</th>
|
<h3 class="text-lg font-semibold text-neutral-900 dark:text-white flex items-center gap-2">
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider">Status</th>
|
<i class="fa-solid fa-users-gear text-primary-600 dark:text-primary-400"></i>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider">Role</th>
|
User Management
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider">Last Login</th>
|
</h3>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider">Actions</th>
|
<p class="text-sm text-neutral-600 dark:text-neutral-300 mt-1">Manage user accounts and permissions</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="flex items-center gap-2 px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white rounded-lg transition-colors duration-200" onclick="showCreateUserModal()">
|
||||||
|
<i class="fa-solid fa-user-plus"></i>
|
||||||
|
<span>Add User</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search and Filter Controls -->
|
||||||
|
<div class="px-6 py-4 bg-neutral-50 dark:bg-neutral-800/50 border-b border-neutral-200 dark:border-neutral-700">
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
|
<i class="fa-solid fa-magnifying-glass text-neutral-400"></i>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="w-full pl-10 pr-4 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent placeholder-neutral-500"
|
||||||
|
id="user-search" placeholder="Search users by name, email, or username..." onkeyup="searchUsers()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<select class="px-4 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent" id="user-filter" onchange="filterUsers()">
|
||||||
|
<option value="all">All Users</option>
|
||||||
|
<option value="active">Active Only</option>
|
||||||
|
<option value="inactive">Inactive Only</option>
|
||||||
|
<option value="admin">Admins Only</option>
|
||||||
|
</select>
|
||||||
|
<button type="button" class="px-4 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg hover:bg-neutral-50 dark:hover:bg-neutral-700 transition-colors" onclick="loadUsers()">
|
||||||
|
<i class="fa-solid fa-rotate-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Users Table -->
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="border-b border-neutral-200 dark:border-neutral-700">
|
||||||
|
<th class="px-6 py-4 text-left text-xs font-medium text-neutral-600 dark:text-neutral-400 uppercase tracking-wider">User</th>
|
||||||
|
<th class="px-6 py-4 text-left text-xs font-medium text-neutral-600 dark:text-neutral-400 uppercase tracking-wider">Status</th>
|
||||||
|
<th class="px-6 py-4 text-left text-xs font-medium text-neutral-600 dark:text-neutral-400 uppercase tracking-wider">Role</th>
|
||||||
|
<th class="px-6 py-4 text-left text-xs font-medium text-neutral-600 dark:text-neutral-400 uppercase tracking-wider">Last Login</th>
|
||||||
|
<th class="px-6 py-4 text-right text-xs font-medium text-neutral-600 dark:text-neutral-400 uppercase tracking-wider">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="users-table-body" class="bg-white dark:bg-neutral-800 divide-y divide-neutral-200 dark:divide-neutral-700">
|
<tbody id="users-table-body" class="divide-y divide-neutral-200 dark:divide-neutral-700">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="text-center px-4 py-4 text-neutral-500">Loading users...</td>
|
<td colspan="5" class="px-6 py-12 text-center">
|
||||||
|
<div class="flex flex-col items-center gap-3">
|
||||||
|
<div class="w-12 h-12 bg-neutral-100 dark:bg-neutral-700 rounded-full flex items-center justify-center">
|
||||||
|
<i class="fa-solid fa-spinner fa-spin text-neutral-400"></i>
|
||||||
|
</div>
|
||||||
|
<p class="text-neutral-600 dark:text-neutral-400">Loading users...</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="mt-3">
|
<!-- Pagination -->
|
||||||
<ul class="flex items-center justify-center gap-2" id="users-pagination">
|
<div class="px-6 py-4 border-t border-neutral-200 dark:border-neutral-700">
|
||||||
</ul>
|
<nav class="flex items-center justify-between">
|
||||||
</nav>
|
<div class="flex items-center gap-2">
|
||||||
|
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||||
|
<span id="users-count-display">0 users total</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2" id="users-pagination">
|
||||||
|
<!-- Pagination will be inserted here -->
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Settings Tab -->
|
<!-- Settings Tab -->
|
||||||
<div id="settings" role="tabpanel" class="hidden">
|
<div id="settings" role="tabpanel" class="hidden">
|
||||||
@@ -311,7 +523,7 @@
|
|||||||
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow mb-4">
|
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow mb-4">
|
||||||
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700 flex items-center justify-between">
|
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700 flex items-center justify-between">
|
||||||
<h5 class="m-0 font-semibold"><i class="fa-solid fa-circle-info"></i> Current Database Status</h5>
|
<h5 class="m-0 font-semibold"><i class="fa-solid fa-circle-info"></i> Current Database Status</h5>
|
||||||
<button class="px-3 py-1.5 border border-info-600 text-info-700 dark:text-info-300 rounded text-sm hover:bg-info-50 dark:hover:bg-info-900/20" onclick="loadImportStatus()">
|
<button class="px-3 py-1.5 border border-info-600 text-info-700 dark:text-info-200 rounded text-sm hover:bg-info-50 dark:hover:bg-info-900/20" onclick="loadImportStatus()">
|
||||||
<i class="fa-solid fa-rotate-right"></i> Refresh
|
<i class="fa-solid fa-rotate-right"></i> Refresh
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -423,7 +635,7 @@
|
|||||||
<div class="w-full md:w-1/2 px-4 mt-4 md:mt-0">
|
<div class="w-full md:w-1/2 px-4 mt-4 md:mt-0">
|
||||||
<h6 class="font-semibold">Quick Actions</h6>
|
<h6 class="font-semibold">Quick Actions</h6>
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<button class="px-3 py-2 border border-info-600 text-info-700 dark:text-info-300 rounded hover:bg-info-50 dark:hover:bg-info-900/20" onclick="viewImportLogs()">
|
<button class="px-3 py-2 border border-info-600 text-info-700 dark:text-info-200 rounded hover:bg-info-50 dark:hover:bg-info-900/20" onclick="viewImportLogs()">
|
||||||
<i class="fa-regular fa-file-lines"></i> View Import Logs
|
<i class="fa-regular fa-file-lines"></i> View Import Logs
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -521,7 +733,7 @@
|
|||||||
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700 flex items-center justify-between">
|
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700 flex items-center justify-between">
|
||||||
<h5 class="m-0 font-semibold"><i class="fas fa-bug"></i> Internal Issues & Bugs</h5>
|
<h5 class="m-0 font-semibold"><i class="fas fa-bug"></i> Internal Issues & Bugs</h5>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button type="button" class="px-3 py-1.5 border border-primary-600 text-primary-700 dark:text-primary-300 rounded text-sm hover:bg-primary-50 dark:hover:bg-primary-900/20" onclick="loadIssues()">
|
<button type="button" class="px-3 py-1.5 border border-primary-600 text-primary-700 dark:text-primary-200 rounded text-sm hover:bg-primary-50 dark:hover:bg-primary-900/20" onclick="loadIssues()">
|
||||||
<i class="fas fa-sync"></i> Refresh
|
<i class="fas fa-sync"></i> Refresh
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="px-3 py-1.5 bg-primary-600 hover:bg-primary-700 text-white rounded text-sm" onclick="openSupportModal()">
|
<button type="button" class="px-3 py-1.5 bg-primary-600 hover:bg-primary-700 text-white rounded text-sm" onclick="openSupportModal()">
|
||||||
@@ -624,7 +836,7 @@
|
|||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="px-2 py-1 rounded bg-primary-600 text-white text-xs" id="issueDetailNumber">ST-2025-XXXX</span>
|
<span class="px-2 py-1 rounded bg-primary-600 text-white text-xs" id="issueDetailNumber">ST-2025-XXXX</span>
|
||||||
<span class="px-2 py-1 rounded bg-primary-100 text-primary-700 dark:bg-primary-900/30 dark:text-primary-300 text-xs" id="issueDetailCategory">category</span>
|
<span class="px-2 py-1 rounded bg-primary-100 text-primary-700 dark:bg-primary-800 dark:text-primary-200 text-xs" id="issueDetailCategory">category</span>
|
||||||
<span class="px-2 py-1 rounded bg-neutral-200 dark:bg-neutral-700 text-xs" id="issueDetailPriority">priority</span>
|
<span class="px-2 py-1 rounded bg-neutral-200 dark:bg-neutral-700 text-xs" id="issueDetailPriority">priority</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="px-2 py-1 rounded text-xs bg-neutral-200 dark:bg-neutral-700" id="issueDetailStatus">status</span>
|
<span class="px-2 py-1 rounded text-xs bg-neutral-200 dark:bg-neutral-700" id="issueDetailStatus">status</span>
|
||||||
@@ -889,6 +1101,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Close main admin content container and tab content -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -947,24 +1163,45 @@ function closeModal(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initializeTabs() {
|
function initializeTabs() {
|
||||||
const tabs = document.querySelectorAll('#adminTabs > li');
|
const tabs = document.querySelectorAll('nav[role="tablist"] button[role="tab"]');
|
||||||
const panes = document.querySelectorAll('#adminTabContent > div[role="tabpanel"]');
|
const panes = document.querySelectorAll('#adminTabContent > div[role="tabpanel"]');
|
||||||
|
|
||||||
tabs.forEach(tab => {
|
tabs.forEach(tab => {
|
||||||
tab.addEventListener('click', () => {
|
tab.addEventListener('click', (event) => {
|
||||||
const target = tab.getAttribute('data-tab-target');
|
openTab(event, tab.getAttribute('data-tab-target')?.replace('#', ''));
|
||||||
if (!target) return;
|
|
||||||
// deactivate
|
|
||||||
tabs.forEach(t => t.classList.remove('active'));
|
|
||||||
panes.forEach(p => p.classList.add('hidden'));
|
|
||||||
// activate
|
|
||||||
tab.classList.add('active');
|
|
||||||
const pane = document.querySelector(target);
|
|
||||||
if (pane) pane.classList.remove('hidden');
|
|
||||||
onTabShown((target || '').replace('#', ''));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openTab(event, tabName) {
|
||||||
|
const tabs = document.querySelectorAll('nav[role="tablist"] button[role="tab"]');
|
||||||
|
const panes = document.querySelectorAll('#adminTabContent > div[role="tabpanel"]');
|
||||||
|
|
||||||
|
// Remove active states
|
||||||
|
tabs.forEach(t => {
|
||||||
|
t.classList.remove('border-primary-600', 'text-primary-600', 'bg-primary-50', 'dark:bg-primary-900/20', 'active');
|
||||||
|
t.classList.add('border-transparent', 'text-neutral-600', 'dark:text-neutral-400');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hide all panes
|
||||||
|
panes.forEach(p => p.classList.add('hidden'));
|
||||||
|
|
||||||
|
// Activate clicked tab
|
||||||
|
if (event && event.currentTarget) {
|
||||||
|
event.currentTarget.classList.remove('border-transparent', 'text-neutral-600', 'dark:text-neutral-400');
|
||||||
|
event.currentTarget.classList.add('border-primary-600', 'text-primary-600', 'dark:text-primary-400', 'bg-primary-50', 'dark:bg-primary-900/20', 'active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show target pane
|
||||||
|
const targetPane = document.getElementById(tabName);
|
||||||
|
if (targetPane) {
|
||||||
|
targetPane.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load tab-specific content
|
||||||
|
onTabShown(tabName);
|
||||||
|
}
|
||||||
|
|
||||||
function onTabShown(tabName) {
|
function onTabShown(tabName) {
|
||||||
if (tabName === 'issues') {
|
if (tabName === 'issues') {
|
||||||
loadIssues();
|
loadIssues();
|
||||||
@@ -1137,21 +1374,21 @@ function renderUsersTable(users) {
|
|||||||
<td>${user.email}</td>
|
<td>${user.email}</td>
|
||||||
<td>${(user.first_name || '') + ' ' + (user.last_name || '')}</td>
|
<td>${(user.first_name || '') + ' ' + (user.last_name || '')}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${user.is_active ? 'bg-success-100 text-success-800 dark:bg-success-900/30 dark:text-success-300' : 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'}">${user.is_active ? 'Active' : 'Inactive'}</span>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${user.is_active ? 'bg-success-100 text-success-800 dark:bg-success-800 dark:text-success-200' : 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'}">${user.is_active ? 'Active' : 'Inactive'}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${user.is_admin ? 'bg-primary-100 text-primary-800 dark:bg-primary-900/30 dark:text-primary-300' : 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'}">${user.is_admin ? 'Admin' : 'User'}</span>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${user.is_admin ? 'bg-primary-100 text-primary-800 dark:bg-primary-800 dark:text-primary-200' : 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'}">${user.is_admin ? 'Admin' : 'User'}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>${user.last_login ? new Date(user.last_login).toLocaleDateString() : 'Never'}</td>
|
<td>${user.last_login ? new Date(user.last_login).toLocaleDateString() : 'Never'}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button class="px-2 py-1 border border-primary-600 text-primary-700 dark:text-primary-300 rounded text-xs hover:bg-primary-50 dark:hover:bg-primary-900/20" onclick="editUser(${user.id})" title="Edit">
|
<button class="px-2 py-1 border border-primary-600 text-primary-700 dark:text-primary-200 rounded text-xs hover:bg-primary-50 dark:hover:bg-primary-900/20" onclick="editUser(${user.id})" title="Edit">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="px-2 py-1 border border-warning-600 text-warning-700 dark:text-warning-300 rounded text-xs hover:bg-warning-50 dark:hover:bg-warning-900/20" onclick="showPasswordModal(${user.id})" title="Reset Password">
|
<button class="px-2 py-1 border border-warning-600 text-warning-700 dark:text-warning-200 rounded text-xs hover:bg-warning-50 dark:hover:bg-warning-900/20" onclick="showPasswordModal(${user.id})" title="Reset Password">
|
||||||
<i class="fas fa-key"></i>
|
<i class="fas fa-key"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="px-2 py-1 border border-danger-600 text-danger-700 dark:text-danger-300 rounded text-xs hover:bg-danger-50 dark:hover:bg-danger-900/20" onclick="deactivateUser(${user.id})" title="Deactivate">
|
<button class="px-2 py-1 border border-danger-600 text-danger-700 dark:text-danger-200 rounded text-xs hover:bg-danger-50 dark:hover:bg-danger-900/20" onclick="deactivateUser(${user.id})" title="Deactivate">
|
||||||
<i class="fas fa-user-times"></i>
|
<i class="fas fa-user-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1334,10 +1571,10 @@ function renderSettingsTable(settings) {
|
|||||||
<td>${setting.description || '-'}</td>
|
<td>${setting.description || '-'}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button class="px-2 py-1 border border-primary-600 text-primary-700 dark:text-primary-300 rounded text-xs hover:bg-primary-50 dark:hover:bg-primary-900/20" onclick="editSetting('${setting.setting_key}')" title="Edit">
|
<button class="px-2 py-1 border border-primary-600 text-primary-700 dark:text-primary-200 rounded text-xs hover:bg-primary-50 dark:hover:bg-primary-900/20" onclick="editSetting('${setting.setting_key}')" title="Edit">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="px-2 py-1 border border-danger-600 text-danger-700 dark:text-danger-300 rounded text-xs hover:bg-danger-50 dark:hover:bg-danger-900/20" onclick="deleteSetting('${setting.setting_key}')" title="Delete">
|
<button class="px-2 py-1 border border-danger-600 text-danger-700 dark:text-danger-200 rounded text-xs hover:bg-danger-50 dark:hover:bg-danger-900/20" onclick="deleteSetting('${setting.setting_key}')" title="Delete">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1505,7 +1742,7 @@ async function loadLookupTables() {
|
|||||||
<strong>${table.display_name}</strong><br>
|
<strong>${table.display_name}</strong><br>
|
||||||
<small class="text-neutral-500 dark:text-neutral-400">${table.description}</small>
|
<small class="text-neutral-500 dark:text-neutral-400">${table.description}</small>
|
||||||
</div>
|
</div>
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-info-100 text-info-800 dark:bg-info-900/30 dark:text-info-300">${table.record_count} records</span>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-info-100 text-info-800 dark:bg-info-800 dark:text-info-200">${table.record_count} records</span>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
@@ -1599,10 +1836,10 @@ async function loadBackups() {
|
|||||||
<td>${backup.size}</td>
|
<td>${backup.size}</td>
|
||||||
<td>${new Date(backup.created_at).toLocaleString()}</td>
|
<td>${new Date(backup.created_at).toLocaleString()}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${backup.backup_type === 'manual' ? 'bg-primary-100 text-primary-800 dark:bg-primary-900/30 dark:text-primary-300' : 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'}">${backup.backup_type}</span>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${backup.backup_type === 'manual' ? 'bg-primary-100 text-primary-800 dark:bg-primary-800 dark:text-primary-200' : 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'}">${backup.backup_type}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="px-2 py-1 border border-success-600 text-success-700 dark:text-success-300 rounded text-xs hover:bg-success-50 dark:hover:bg-success-900/20" onclick="downloadBackup('${backup.filename}')" title="Download">
|
<button class="px-2 py-1 border border-success-600 text-success-700 dark:text-success-200 rounded text-xs hover:bg-success-50 dark:hover:bg-success-900/20" onclick="downloadBackup('${backup.filename}')" title="Download">
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@@ -1783,16 +2020,16 @@ function renderIssuesTable(issues) {
|
|||||||
|
|
||||||
tbody.innerHTML = issues.map(issue => {
|
tbody.innerHTML = issues.map(issue => {
|
||||||
const priorityClass = {
|
const priorityClass = {
|
||||||
'urgent': 'bg-danger-100 text-danger-800 dark:bg-danger-900/30 dark:text-danger-300',
|
'urgent': 'bg-danger-100 text-danger-800 dark:bg-danger-800 dark:text-danger-200',
|
||||||
'high': 'bg-warning-100 text-warning-800 dark:bg-warning-900/30 dark:text-warning-300',
|
'high': 'bg-warning-100 text-warning-800 dark:bg-warning-800 dark:text-warning-200',
|
||||||
'medium': 'bg-info-100 text-info-800 dark:bg-info-900/30 dark:text-info-300',
|
'medium': 'bg-info-100 text-info-800 dark:bg-info-800 dark:text-info-200',
|
||||||
'low': 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'
|
'low': 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'
|
||||||
}[issue.priority] || 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300';
|
}[issue.priority] || 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300';
|
||||||
|
|
||||||
const statusClass = {
|
const statusClass = {
|
||||||
'open': 'bg-danger-100 text-danger-800 dark:bg-danger-900/30 dark:text-danger-300',
|
'open': 'bg-danger-100 text-danger-800 dark:bg-danger-800 dark:text-danger-200',
|
||||||
'in_progress': 'bg-warning-100 text-warning-800 dark:bg-warning-900/30 dark:text-warning-300',
|
'in_progress': 'bg-warning-100 text-warning-800 dark:bg-warning-800 dark:text-warning-200',
|
||||||
'resolved': 'bg-success-100 text-success-800 dark:bg-success-900/30 dark:text-success-300',
|
'resolved': 'bg-success-100 text-success-800 dark:bg-success-800 dark:text-success-200',
|
||||||
'closed': 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'
|
'closed': 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'
|
||||||
}[issue.status] || 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300';
|
}[issue.status] || 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300';
|
||||||
|
|
||||||
@@ -1813,7 +2050,7 @@ function renderIssuesTable(issues) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td><strong>${issue.ticket_number}</strong></td>
|
<td><strong>${issue.ticket_number}</strong></td>
|
||||||
<td>
|
<td>
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-primary-100 text-primary-800 dark:bg-primary-900/30 dark:text-primary-300">${categoryDisplay}</span>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-primary-100 text-primary-800 dark:bg-primary-800 dark:text-primary-200">${categoryDisplay}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${priorityClass}">${issue.priority.toUpperCase()}</span>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${priorityClass}">${issue.priority.toUpperCase()}</span>
|
||||||
@@ -1830,7 +2067,7 @@ function renderIssuesTable(issues) {
|
|||||||
<td>${issue.assigned_admin_name || 'Unassigned'}</td>
|
<td>${issue.assigned_admin_name || 'Unassigned'}</td>
|
||||||
<td>${new Date(issue.created_at).toLocaleDateString()}</td>
|
<td>${new Date(issue.created_at).toLocaleDateString()}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="px-2 py-1 border border-primary-600 text-primary-700 dark:text-primary-300 rounded text-xs hover:bg-primary-50 dark:hover:bg-primary-900/20" onclick="viewIssue(${issue.id})" title="View Details">
|
<button class="px-2 py-1 border border-primary-600 text-primary-700 dark:text-primary-200 rounded text-xs hover:bg-primary-50 dark:hover:bg-primary-900/20" onclick="viewIssue(${issue.id})" title="View Details">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@@ -1875,21 +2112,21 @@ async function viewIssue(issueId) {
|
|||||||
}[issue.category] || issue.category;
|
}[issue.category] || issue.category;
|
||||||
|
|
||||||
document.getElementById('issueDetailCategory').textContent = categoryDisplay;
|
document.getElementById('issueDetailCategory').textContent = categoryDisplay;
|
||||||
document.getElementById('issueDetailCategory').className = 'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-primary-100 text-primary-800 dark:bg-primary-900/30 dark:text-primary-300';
|
document.getElementById('issueDetailCategory').className = 'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-primary-100 text-primary-800 dark:bg-primary-800 dark:text-primary-200';
|
||||||
|
|
||||||
document.getElementById('issueDetailPriority').textContent = issue.priority.toUpperCase();
|
document.getElementById('issueDetailPriority').textContent = issue.priority.toUpperCase();
|
||||||
document.getElementById('issueDetailPriority').className = 'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ml-2 ' + ({
|
document.getElementById('issueDetailPriority').className = 'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ml-2 ' + ({
|
||||||
'urgent': 'bg-danger-100 text-danger-800 dark:bg-danger-900/30 dark:text-danger-300',
|
'urgent': 'bg-danger-100 text-danger-800 dark:bg-danger-800 dark:text-danger-200',
|
||||||
'high': 'bg-warning-100 text-warning-800 dark:bg-warning-900/30 dark:text-warning-300',
|
'high': 'bg-warning-100 text-warning-800 dark:bg-warning-800 dark:text-warning-200',
|
||||||
'medium': 'bg-info-100 text-info-800 dark:bg-info-900/30 dark:text-info-300',
|
'medium': 'bg-info-100 text-info-800 dark:bg-info-800 dark:text-info-200',
|
||||||
'low': 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'
|
'low': 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'
|
||||||
}[issue.priority] || 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300');
|
}[issue.priority] || 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300');
|
||||||
|
|
||||||
document.getElementById('issueDetailStatus').textContent = issue.status.replace('_', ' ').toUpperCase();
|
document.getElementById('issueDetailStatus').textContent = issue.status.replace('_', ' ').toUpperCase();
|
||||||
document.getElementById('issueDetailStatus').className = 'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ' + ({
|
document.getElementById('issueDetailStatus').className = 'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ' + ({
|
||||||
'open': 'bg-danger-100 text-danger-800 dark:bg-danger-900/30 dark:text-danger-300',
|
'open': 'bg-danger-100 text-danger-800 dark:bg-danger-800 dark:text-danger-200',
|
||||||
'in_progress': 'bg-warning-100 text-warning-800 dark:bg-warning-900/30 dark:text-warning-300',
|
'in_progress': 'bg-warning-100 text-warning-800 dark:bg-warning-800 dark:text-warning-200',
|
||||||
'resolved': 'bg-success-100 text-success-800 dark:bg-success-900/30 dark:text-success-300',
|
'resolved': 'bg-success-100 text-success-800 dark:bg-success-800 dark:text-success-200',
|
||||||
'closed': 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'
|
'closed': 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300'
|
||||||
}[issue.status] || 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300');
|
}[issue.status] || 'bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300');
|
||||||
|
|
||||||
@@ -1966,8 +2203,8 @@ function displayIssueResponses(responses) {
|
|||||||
container.innerHTML = responses.map(response => {
|
container.innerHTML = responses.map(response => {
|
||||||
const isInternal = response.is_internal;
|
const isInternal = response.is_internal;
|
||||||
const badgeClass = isInternal
|
const badgeClass = isInternal
|
||||||
? 'bg-warning-100 text-warning-800 dark:bg-warning-900/30 dark:text-warning-300'
|
? 'bg-warning-100 text-warning-800 dark:bg-warning-800 dark:text-warning-200'
|
||||||
: 'bg-primary-100 text-primary-800 dark:bg-primary-900/30 dark:text-primary-300';
|
: 'bg-primary-100 text-primary-800 dark:bg-primary-800 dark:text-primary-200';
|
||||||
const badgeText = isInternal ? 'Internal' : 'Public';
|
const badgeText = isInternal ? 'Internal' : 'Public';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<!-- Tailwind CSS -->
|
<!-- Tailwind CSS -->
|
||||||
<!-- Custom Tailwind CSS -->
|
<!-- Custom Tailwind CSS -->
|
||||||
<link href="/static/css/tailwind.css" rel="stylesheet">
|
<link href="/static/css/main.css" rel="stylesheet">
|
||||||
|
|
||||||
{% block bridge_css %}{% endblock %}
|
{% block bridge_css %}{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% block title %}Customers (Rolodex) - Delphi Database{% endblock %}
|
{% block title %}Customers (Rolodex) - Delphi Database{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="space-y-6">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 space-y-6">
|
||||||
<!-- Page Header -->
|
<!-- Page Header -->
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
|||||||
@@ -57,8 +57,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="md:col-span-1">
|
<div class="md:col-span-1">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input type="text" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg" id="templateSearch" placeholder="Search templates...">
|
<input type="text" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 placeholder-neutral-400 dark:placeholder-neutral-500 transition-all duration-200" id="templateSearch" placeholder="Search templates...">
|
||||||
<select class="px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg" id="categoryFilter">
|
<select class="px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 transition-all duration-200" id="categoryFilter">
|
||||||
<option value="">All Categories</option>
|
<option value="">All Categories</option>
|
||||||
</select>
|
</select>
|
||||||
<button class="px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg hover:bg-neutral-100 dark:hover:bg-neutral-700" id="refreshTemplatesBtn"><i class="fa-solid fa-rotate-right"></i></button>
|
<button class="px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg hover:bg-neutral-100 dark:hover:bg-neutral-700" id="refreshTemplatesBtn"><i class="fa-solid fa-rotate-right"></i></button>
|
||||||
@@ -95,8 +95,8 @@
|
|||||||
<div><h5 class="mb-0 font-semibold"><i class="fa-regular fa-file-lines"></i> QDRO Documents</h5></div>
|
<div><h5 class="mb-0 font-semibold"><i class="fa-regular fa-file-lines"></i> QDRO Documents</h5></div>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input type="text" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg" id="qdroSearch" placeholder="Search QDROs...">
|
<input type="text" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 placeholder-neutral-400 dark:placeholder-neutral-500 transition-all duration-200" id="qdroSearch" placeholder="Search QDROs...">
|
||||||
<select class="px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg" id="qdroStatusFilter">
|
<select class="px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 transition-all duration-200" id="qdroStatusFilter">
|
||||||
<option value="">All Status</option>
|
<option value="">All Status</option>
|
||||||
<option value="DRAFT">Draft</option>
|
<option value="DRAFT">Draft</option>
|
||||||
<option value="APPROVED">Approved</option>
|
<option value="APPROVED">Approved</option>
|
||||||
|
|||||||
@@ -34,30 +34,30 @@
|
|||||||
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-6">
|
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-6">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
|
||||||
<div class="md:col-span-3">
|
<div class="md:col-span-3">
|
||||||
<label for="searchInput" class="block text-sm font-medium mb-2">Search Files</label>
|
<label for="searchInput" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Search Files</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input type="text" id="searchInput" class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent" placeholder="File #, Client, Matter...">
|
<input type="text" id="searchInput" class="w-full pl-10 pr-4 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 placeholder-neutral-400 dark:placeholder-neutral-500 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" placeholder="File #, Client, Matter...">
|
||||||
<i class="fa-solid fa-magnifying-glass absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
|
<i class="fa-solid fa-magnifying-glass absolute left-3 top-1/2 transform -translate-y-1/2 text-neutral-400 dark:text-neutral-500"></i>
|
||||||
<button id="searchBtn" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-primary-500">
|
<button id="searchBtn" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-neutral-400 hover:text-primary-600 dark:text-neutral-500 dark:hover:text-primary-400 transition-colors">
|
||||||
<i class="fa-solid fa-arrow-right"></i>
|
<i class="fa-solid fa-arrow-right"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="statusFilter" class="block text-sm font-medium mb-2">Status</label>
|
<label for="statusFilter" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Status</label>
|
||||||
<select id="statusFilter" class="w-full py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent">
|
<select id="statusFilter" class="w-full px-3 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200">
|
||||||
<option value="">All Statuses</option>
|
<option value="">All Statuses</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="typeFilter" class="block text-sm font-medium mb-2">File Type</label>
|
<label for="typeFilter" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">File Type</label>
|
||||||
<select id="typeFilter" class="w-full py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent">
|
<select id="typeFilter" class="w-full px-3 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200">
|
||||||
<option value="">All Types</option>
|
<option value="">All Types</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="employeeFilter" class="block text-sm font-medium mb-2">Attorney</label>
|
<label for="employeeFilter" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Attorney</label>
|
||||||
<select id="employeeFilter" class="w-full py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent">
|
<select id="employeeFilter" class="w-full px-3 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200">
|
||||||
<option value="">All Attorneys</option>
|
<option value="">All Attorneys</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,12 +65,12 @@
|
|||||||
<div class="flex justify-between items-center p-4 border-b border-neutral-200 dark:border-neutral-700">
|
<div class="flex justify-between items-center p-4 border-b border-neutral-200 dark:border-neutral-700">
|
||||||
<h2 class="text-lg font-semibold"><i class="fa-solid fa-clock-rotate-left mr-2"></i>Recent Time Entries</h2>
|
<h2 class="text-lg font-semibold"><i class="fa-solid fa-clock-rotate-left mr-2"></i>Recent Time Entries</h2>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<select class="px-3 py-1 border border-neutral-300 dark:border-neutral-600 rounded-lg text-sm" id="recentDaysFilter">
|
<select class="px-3 py-1 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-sm text-neutral-900 dark:text-neutral-100 transition-all duration-200" id="recentDaysFilter">
|
||||||
<option value="7">Last 7 days</option>
|
<option value="7">Last 7 days</option>
|
||||||
<option value="14">Last 14 days</option>
|
<option value="14">Last 14 days</option>
|
||||||
<option value="30">Last 30 days</option>
|
<option value="30">Last 30 days</option>
|
||||||
</select>
|
</select>
|
||||||
<select class="px-3 py-1 border border-neutral-300 dark:border-neutral-600 rounded-lg text-sm" id="employeeFilter">
|
<select class="px-3 py-1 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-sm text-neutral-900 dark:text-neutral-100 transition-all duration-200" id="employeeFilter">
|
||||||
<option value="">All Employees</option>
|
<option value="">All Employees</option>
|
||||||
</select>
|
</select>
|
||||||
<button class="px-3 py-1 bg-gray-200 dark:bg-neutral-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-300 dark:hover:bg-neutral-600 transition-colors" id="refreshRecentBtn">
|
<button class="px-3 py-1 bg-gray-200 dark:bg-neutral-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-300 dark:hover:bg-neutral-600 transition-colors" id="refreshRecentBtn">
|
||||||
|
|||||||
@@ -110,11 +110,11 @@
|
|||||||
<div class="flex flex-wrap -mx-2">
|
<div class="flex flex-wrap -mx-2">
|
||||||
<div class="w-1/2 px-2">
|
<div class="w-1/2 px-2">
|
||||||
<label for="dateFrom" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">From</label>
|
<label for="dateFrom" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">From</label>
|
||||||
<input type="date" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" id="dateFrom">
|
<input type="date" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" id="dateFrom">
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/2 px-2">
|
<div class="w-1/2 px-2">
|
||||||
<label for="dateTo" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">To</label>
|
<label for="dateTo" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">To</label>
|
||||||
<input type="date" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" id="dateTo">
|
<input type="date" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" id="dateTo">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 flex gap-2">
|
<div class="mt-2 flex gap-2">
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
<span class="absolute left-3 top-1/2 -translate-y-1/2 px-1 bg-neutral-200 dark:bg-neutral-700 text-neutral-500 dark:text-neutral-400 rounded-l-md">
|
<span class="absolute left-3 top-1/2 -translate-y-1/2 px-1 bg-neutral-200 dark:bg-neutral-700 text-neutral-500 dark:text-neutral-400 rounded-l-md">
|
||||||
$
|
$
|
||||||
</span>
|
</span>
|
||||||
<input type="number" class="w-full pl-10 pr-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" id="amountMin" step="0.01" min="0">
|
<input type="number" class="w-full pl-10 pr-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" id="amountMin" step="0.01" min="0">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/2 px-2">
|
<div class="w-1/2 px-2">
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
<span class="absolute left-3 top-1/2 -translate-y-1/2 px-1 bg-neutral-200 dark:bg-neutral-700 text-neutral-500 dark:text-neutral-400 rounded-l-md">
|
<span class="absolute left-3 top-1/2 -translate-y-1/2 px-1 bg-neutral-200 dark:bg-neutral-700 text-neutral-500 dark:text-neutral-400 rounded-l-md">
|
||||||
$
|
$
|
||||||
</span>
|
</span>
|
||||||
<input type="number" class="w-full pl-10 pr-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" id="amountMax" step="0.01" min="0">
|
<input type="number" class="w-full pl-10 pr-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" id="amountMax" step="0.01" min="0">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -258,8 +258,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="grid grid-cols-2 gap-3 items-center">
|
<div class="grid grid-cols-2 gap-3 items-center">
|
||||||
<div>
|
<div>
|
||||||
<label for="sortBy" class="block text-sm font-medium mb-1">Sort by:</label>
|
<label for="sortBy" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">Sort by:</label>
|
||||||
<select class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg" id="sortBy">
|
<select class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 transition-all duration-200" id="sortBy">
|
||||||
<option value="relevance">Relevance</option>
|
<option value="relevance">Relevance</option>
|
||||||
<option value="date">Date</option>
|
<option value="date">Date</option>
|
||||||
<option value="amount">Amount</option>
|
<option value="amount">Amount</option>
|
||||||
@@ -267,8 +267,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="sortOrder" class="block text-sm font-medium mb-1">Order:</label>
|
<label for="sortOrder" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">Order:</label>
|
||||||
<select class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg" id="sortOrder">
|
<select class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 transition-all duration-200" id="sortOrder">
|
||||||
<option value="desc">Descending</option>
|
<option value="desc">Descending</option>
|
||||||
<option value="asc">Ascending</option>
|
<option value="asc">Ascending</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
Reference in New Issue
Block a user