diff --git a/public/admin-script.js b/public/admin-script.js new file mode 100644 index 0000000..0f6b212 --- /dev/null +++ b/public/admin-script.js @@ -0,0 +1,150 @@ +document.addEventListener('DOMContentLoaded', () => { + const ticketsBody = document.getElementById('tickets-body'); + const closedTicketsBody = document.getElementById('closed-tickets-body'); + const searchInput = document.getElementById('search-tickets'); + const statusFilter = document.getElementById('status-filter'); + const priorityFilter = document.getElementById('priority-filter'); + const categoryFilter = document.getElementById('category-filter'); + const statTotal = document.getElementById('total-tickets'); + const statOpen = document.getElementById('open-tickets'); + const statInProgress = document.getElementById('in-progress-tickets'); + const statClosed = document.getElementById('closed-tickets'); + const statTotalTime = document.getElementById('time-tickets'); // Neu: Gesamtzeit Anzeige + + searchInput.addEventListener('input', refreshTickets); + statusFilter.addEventListener('change', refreshTickets); + priorityFilter.addEventListener('change', refreshTickets); + categoryFilter.addEventListener('change', refreshTickets); + + // Helper: Status/Priority zu "Title-Case-With-Dash" + function toTitleCaseWithDash(str) { + return str + .split(' ') + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join('-'); + } + + async function fetchTickets() { + try { + const res = await fetch('/api/tickets'); + return await res.json(); + } catch (err) { + console.error('Error fetching tickets:', err); + return []; + } + } + + async function deleteTicket(id) { + try { + const res = await fetch(`/api/tickets/${id}`, { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ actor: 'Admin' }) + }); + return await res.json(); + } catch (err) { + console.error('Error deleting ticket:', err); + } + } + + async function refreshTickets() { + const tickets = await fetchTickets(); + renderTickets(tickets); + updateStats(tickets); + } + + function renderTickets(tickets) { + const status = statusFilter.value; + const priority = priorityFilter.value; + const category = categoryFilter.value; + const searchTerm = searchInput.value.toLowerCase(); + + const filtered = tickets.filter(t => + (status === 'all' || t.status === status) && + (priority === 'all' || t.priority === priority) && + (category === 'all' || t.category === category) && + (t.title.toLowerCase().includes(searchTerm) || + t.description.toLowerCase().includes(searchTerm) || + t.requester.toLowerCase().includes(searchTerm)) + ).sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + + const openRows = []; + const closedRows = []; + + filtered.forEach(ticket => { + const shortTitle = ticket.title.length > 50 ? ticket.title.slice(0, 50) + '...' : ticket.title; + + // Wandle Status und Priority korrekt in Klassen um + const statusClass = toTitleCaseWithDash(ticket.status || 'Unknown'); + const priorityClass = toTitleCaseWithDash(ticket.priority || 'None'); + + const rowHTML = ` + + ${ticket.id} + ${shortTitle} + ${ticket.requester} + ${ticket.category || '-'} + ${ticket.status || 'Unknown'} + ${ticket.priority || 'None'} + ${formatDate(ticket.created_at)} + ${ticket.total_time} min + + + + + `; + + if (ticket.status === 'Closed') { + closedRows.push(rowHTML); + } else { + openRows.push(rowHTML); + } + }); + + ticketsBody.innerHTML = openRows.length + ? openRows.join('') + : `No open tickets found`; + + if (closedTicketsBody) { + closedTicketsBody.innerHTML = closedRows.length + ? closedRows.join('') + : `No closed tickets`; + } + + // Event listeners an Buttons hängen + [...document.querySelectorAll('.view-btn')].forEach(btn => + btn.addEventListener('click', () => { + window.location.href = `/ticketview/ticket-view-admin.html?id=${btn.dataset.id}`; + }) + ); + + [...document.querySelectorAll('.delete-btn')].forEach(btn => + btn.addEventListener('click', () => { + if (confirm('Are you sure you want to delete this ticket?')) { + deleteTicket(btn.dataset.id).then(refreshTickets); + } + }) + ); + } + + function updateStats(tickets) { + statTotal.textContent = tickets.length; + statOpen.textContent = tickets.filter(t => t.status === 'Open').length; + statInProgress.textContent = tickets.filter(t => t.status === 'In Progress').length; + statClosed.textContent = tickets.filter(t => t.status === 'Closed').length; + + // Gesamtzeit aller Tickets summieren (total_time in Minuten) + const totalTime = tickets.reduce((sum, ticket) => sum + (ticket.total_time || 0), 0); + statTotalTime.textContent = `${totalTime} min`; + } + + function formatDate(date) { + return new Date(date).toLocaleDateString(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric' + }); + } + + refreshTickets(); +}); \ No newline at end of file diff --git a/public/admin.css b/public/admin.css new file mode 100644 index 0000000..5cd08a0 --- /dev/null +++ b/public/admin.css @@ -0,0 +1,256 @@ +/* admin.css - Dark Mode Only */ + +/* ===== GLOBAL STYLES & VARIABLES ===== */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); + +:root { + --primary-color: #4A90E2; + --primary-hover-color: #357ABD; + --secondary-color: #50E3C2; + --accent-color: #F5A623; + --success-color: #7ED321; + --danger-color: #D0021B; + --neutral-dark: #f0f0f0; + --neutral-medium: #cccccc; + --neutral-light: #999999; + --bg-color: #121212; + --card-bg-color: #1e1e1e; + --border-color: #2a2a2a; + --shadow-color: rgba(0, 0, 0, 0.5); + --text-color: #f5f5f5; + --link-color: var(--primary-color); + --font-family: 'Inter', sans-serif; + --border-radius: 8px; + --card-padding: 25px; + --input-padding: 12px 15px; + --transition-speed: 0.2s ease-in-out; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: var(--font-family); + background-color: var(--bg-color); + color: var(--text-color); + font-size: 16px; + line-height: 1.6; +} + +a { + color: var(--link-color); + text-decoration: none; + transition: color var(--transition-speed); +} +a:hover { + color: var(--primary-hover-color); + text-decoration: underline; +} + +/* ===== UTILITY CLASSES ===== */ +.container { + max-width: 1400px; + margin: auto; + padding: 20px 30px; +} + +/* ===== HEADER ===== */ +.header { + background: var(--card-bg-color); + padding: 20px var(--card-padding); + border-radius: var(--border-radius); + box-shadow: 0 2px 10px var(--shadow-color); + margin-bottom: 30px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 20px; +} + +.header h1 { + color: var(--primary-color); + font-size: 1.7rem; + font-weight: 700; + display: flex; + align-items: center; +} +.header h1 i { + margin-right: 12px; +} + +/* ===== SECTION HEADER ===== */ +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 25px; + flex-wrap: wrap; + gap: 15px; +} +.section-header h2 { + color: var(--neutral-dark); + font-size: 1.6rem; + font-weight: 700; +} +.section-header h2 i { + margin-right: 10px; + color: var(--primary-color); +} + +/* ===== STATS CARDS ===== */ +.admin-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + background: var(--card-bg-color); + padding: var(--card-padding); + border-radius: var(--border-radius); + margin-bottom: 30px; + box-shadow: 0 4px 15px var(--shadow-color); +} +.stat-card { + text-align: center; +} +.stat-value { + font-size: 2.5rem; + color: var(--primary-color); + font-weight: bold; +} +.stat-label { + font-size: 1rem; + color: var(--neutral-medium); +} + +/* ===== FILTERS ===== */ +.admin-filters { + display: flex; + flex-wrap: wrap; + gap: 15px; +} +.admin-filters input, +.admin-filters select { + padding: var(--input-padding); + background: #1c1c1c; + border: 1px solid var(--border-color); + color: var(--text-color); + border-radius: var(--border-radius); + font-size: 0.95rem; +} + +/* ===== TICKET TABLE STYLES (OPEN + CLOSED) ===== */ +.admin-tickets-list { + background: var(--card-bg-color); + padding: var(--card-padding); + border-radius: var(--border-radius); +} +table { + width: 100%; + border-collapse: collapse; + font-size: 0.95rem; +} +table th, +table td { + padding: 12px 15px; + text-align: left; + border-bottom: 1px solid var(--border-color); + color: var(--text-color); +} +table th { + background: #1a1a1a; + color: var(--neutral-medium); + font-weight: 600; + text-transform: uppercase; + font-size: 0.8rem; +} + +/* ===== TABLE ACTION BUTTONS ===== */ +.table-actions { + display: flex; + gap: 8px; + justify-content: center; +} +.table-btn { + padding: 6px 10px; + border-radius: var(--border-radius); + color: white; + font-size: 0.85rem; + display: flex; + align-items: center; + gap: 5px; + cursor: pointer; +} +.view-btn { + background-color: var(--primary-color); +} +.view-btn:hover { + background-color: var(--primary-hover-color); +} +.delete-btn { + background-color: var(--danger-color); +} +.delete-btn:hover { + background-color: #a60014; +} + +/* ===== BADGES ===== */ +.status-Open { background-color: rgba(74, 144, 226, 0.15); color: #4A90E2; } +.status-In-Progress { background-color: rgba(245, 166, 35, 0.15); color: #F5A623; } +.status-On-Hold { background-color: rgba(155, 155, 155, 0.15); color: #9B9B9B; } +.status-Closed { background-color: rgba(126, 211, 33, 0.15); color: #7ED321; } + +.priority-Low { background-color: rgba(80, 227, 194, 0.2); color: #50E3C2; } +.priority-Medium { background-color: rgba(74, 144, 226, 0.2); color: #4A90E2; } +.priority-High { background-color: rgba(245, 166, 35, 0.2); color: #F5A623; } +.priority-Urgent { background-color: rgba(208, 2, 27, 0.15); color: #D0021B; } + +.status-badge, +.priority-badge { + padding: 0.25em 0.6em; + border-radius: 9999px; +} + +/* ===== CLOSED TICKETS SECTION ===== */ +.closed-tickets-section { + margin-top: 30px; +} +.closed-tickets-section .section-header h2 { + color: var(--success-color); +} + +/* ===== RESPONSIVE ===== */ +@media (max-width: 768px) { + .container { + padding: 15px 20px; + } + .header { + flex-direction: column; + align-items: flex-start; + } + .section-header { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } +} +@media (max-width: 480px) { + .header h1 { + font-size: 1.3rem; + } + .stat-value { + font-size: 2rem; + } + .btn-primary, + .btn-small { + width: 100%; + } + table { + font-size: 0.85rem; + } + table th, + table td { + padding: 8px 10px; + } +} diff --git a/public/admin.html b/public/admin.html new file mode 100644 index 0000000..de3792b --- /dev/null +++ b/public/admin.html @@ -0,0 +1,127 @@ + + + + + + Admin Dashboard - Ticket System + + + + + + +
+
+

Admin Dashboard

+
+ Welcome, Admin! + User View +
+
+ +
+
+
+
0
+
Total Tickets
+
+
+
0
+
Open Tickets
+
+
+
0
+
In Progress
+
+
+
0
+
Closed Tickets
+
+
+
0
+
Total Time
+
+
+ +
+
+

All Tickets

+
+ + + + +
+
+
+ + + + + + + + + + + + + + + +
IDTitleRequesterCategoryStatusPriorityCreatedTotal TimeActions
+
+
+
+ +
+
+

Closed Tickets

+
+
+ + + + + + + + + + + + + + + +
IDTitleRequesterCategoryStatusPriorityCreatedTotal TimeActions
+
+
+ + + +
+ + + + \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..c591f9e --- /dev/null +++ b/public/index.html @@ -0,0 +1,142 @@ + + + + + + Laila Dashboard - Ticket System! + + + + + +
+
+

My Tickets

+ +
+ + + + + + + +
+
+
+

My Tickets

+
+ + + +
+
+ + +
+

Active Tickets

+
+
+ + +
+

Closed Tickets

+
+
+
+
+ + +
+ + + + \ No newline at end of file diff --git a/public/notes b/public/notes new file mode 100644 index 0000000..cd166fd --- /dev/null +++ b/public/notes @@ -0,0 +1,3 @@ +minify the css and javascript maybe + +Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned \ No newline at end of file