Dateien nach "public/ticketview" hochladen

This commit is contained in:
Honey committed 2025-11-15 13:48:56 +01:00
1 parent 58ceca14b3
commit 784b660402
5 files changed
+1085

No files matched your search

+122
View File
@@ -0,0 +1,122 @@
/* ==== ADMIN COMMENT STYLING ==== */
.comment.admin .comment-content {
background-color: var(--color-admin-highlight);
border-left: 4px solid var(--color-primary);
}
.comment.admin .comment-author::after {
content: " (Admin)";
color: var(--color-primary);
font-weight: normal;
margin-left: 4px;
}
/* ==== ACTION BUTTONS (nur Admin hat mehrere Buttons) ==== */
.ticket-actions-bar {
margin-top: 20px;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn-secondary {
padding: 10px 16px;
border: none;
border-radius: var(--radius);
cursor: pointer;
font-weight: 600;
transition: background 0.2s ease;
}
.btn-secondary {
background-color: var(--color-accent);
color: white;
}
.btn-secondary:hover {
background-color: #1976d2;
}
/* ==== CUSTOM ADMIN LAYOUT FIX ==== */
.ticket-meta-block {
display: flex;
flex-direction: column;
gap: 20px;
}
@media (min-width: 600px) {
.ticket-info-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* GRID ADMIN SIDE ORDER */
.ticket-info-grid {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3 equal columns */
grid-template-rows: auto auto auto; /* 3 rows */
gap: 15px;
}
/* Requester: span all 3 columns on row 1 */
.info-item.requester {
grid-column: 1 / 4;
grid-row: 1 / 2;
}
/* Description: span all 3 columns on row 2 */
.info-item.description {
grid-column: 1 / 4;
grid-row: 2 / 3;
}
/* Created, Updated, Time: row 3, columns 1-3 */
.info-item.created {
grid-column: 1 / 2;
grid-row: 3 / 4;
}
.info-item.updated {
grid-column: 2 / 3;
grid-row: 3 / 4;
}
/* Time div isn’t .info-item, so select it directly by nth-child */
.ticket-info-grid > div:nth-child(5) {
grid-column: 3 / 4;
grid-row: 3 / 4;
display: flex;
align-items: center;
gap: 10px;
}
/* Status, Priority, Category: row 4, columns 1-3 */
.info-item.status {
grid-column: 1 / 2;
grid-row: 4 / 5;
}
.info-item.priority {
grid-column: 2 / 3;
grid-row: 4 / 5;
}
.info-item.category {
grid-column: 3 / 4;
grid-row: 4 / 5;
}
.forms-row {
display: flex;
gap: 20px;
align-items: flex-start;
}
.compact-form {
flex: 1;
display: grid;
grid-template-columns: 1fr;
gap: 10px;
}
.forms-row input,
.forms-row textarea {
width: 100%;
}
@media (max-width: 700px) {
.forms-row {
flex-direction: column;
}
}
+311
View File
@@ -0,0 +1,311 @@
import {
fetchTicket, fetchComments, fetchHistory, createComment, logTime,
formatDateTime, formatMinutes, getHistoryIcon
} from './ticket-shared.js';
document.addEventListener('DOMContentLoaded', async () => {
const ticketId = new URLSearchParams(window.location.search).get('id');
if (!ticketId) return window.location.href = '../admin.html';
const statusSelect = document.getElementById('status-select');
const priorityInput = document.getElementById('priority-text');
const categorySelect = document.getElementById('category-select');
const commentForm = document.getElementById('comment-form');
const titleText = document.getElementById('ticket-title-text');
const titleInput = document.getElementById('ticket-title-input');
const descText = document.getElementById('ticket-description-text');
const descInput = document.getElementById('ticket-description-input');
const commentAuthorInput = document.getElementById('comment-author');
const totalTimeDisplay = document.getElementById('total-time');
const timeForm = document.getElementById('time-booking-form');
let webhookConfig = null;
// Fetch Webhook Configuration
async function loadWebhookConfig() {
try {
const response = await fetch('/api/webhooks');
if (response.ok) {
webhookConfig = await response.json();
console.log('Webhook configuration loaded:', webhookConfig.configured);
} else {
console.warn('Failed to load webhook configuration');
webhookConfig = { admin: null, user: null, configured: { admin: false, user: false } };
}
} catch (error) {
console.error('Error loading webhook configuration:', error);
webhookConfig = { admin: null, user: null, configured: { admin: false, user: false } };
}
}
// Send Webhook Notification
async function sendWebhook(message) {
if (!webhookConfig?.user) {
console.log('User webhook not configured, skipping notification');
return;
}
try {
const res = await fetch(webhookConfig.user, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: message })
});
if (!res.ok) {
console.error('User webhook failed:', res.statusText);
}
} catch (err) {
console.error('Error sending user webhook:', err);
}
}
let originalStatus = '', originalPriority = '', originalTitle = '', originalDesc = '', originalCategory = '';
async function loadPage() {
loadWebhookConfig().then(() => {
loadTicketDetails();
loadComments();
loadHistory();
});
}
async function loadTicketDetails() {
const ticket = await fetchTicket(ticketId);
if (!ticket) return alert('Ticket not found');
titleText.textContent = ticket.title;
descText.textContent = ticket.description;
priorityInput.value = ticket.priority;
statusSelect.value = ticket.status;
categorySelect.value = ticket.category || '';
document.getElementById('ticket-id-val').textContent = ticket.id;
document.getElementById('ticket-requester').textContent = ticket.requester;
document.getElementById('ticket-created').textContent = formatDateTime(ticket.created_at);
document.getElementById('ticket-updated').textContent = formatDateTime(ticket.updated_at);
const totalMins = typeof ticket.total_time === 'number' ? ticket.total_time : 0;
totalTimeDisplay.textContent = formatMinutes(totalMins);
statusSelect.className = 'form-control status-' + (ticket.status || '').toLowerCase().replace(/\s+/g, '-');
originalStatus = ticket.status;
originalPriority = ticket.priority;
originalTitle = ticket.title;
originalDesc = ticket.description;
originalCategory = ticket.category || '';
}
async function updateTicket({ status, priority, title, description, category, actor = 'Admin' }) {
try {
const ticket = await fetchTicket(ticketId); // Fetch ticket to get the requester
const requester = ticket.requester || ''; // Ensure requester is included in the update request
const res = await fetch(`/api/tickets/${ticketId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status, priority, title, description, category, actor, requester }) // Add requester
});
if (!res.ok) throw new Error('Failed to update');
alert('Ticket updated successfully');
if (status !== originalStatus) {
await sendWebhook(`Ticket #${ticketId} status changed from "${originalStatus}" to "${status}"`);
originalStatus = status;
}
if (priority !== originalPriority) {
await sendWebhook(`Ticket #${ticketId} priority changed from "${originalPriority}" to "${priority}"`);
originalPriority = priority;
}
if (title !== originalTitle) {
await sendWebhook(`Ticket #${ticketId} title changed by ${actor}`);
originalTitle = title;
}
if (description !== originalDesc) {
await sendWebhook(`Ticket #${ticketId} description changed by ${actor}`);
originalDesc = description;
}
if (category !== originalCategory) {
await sendWebhook(`Ticket #${ticketId} category changed from "${originalCategory}" to "${category}"`);
originalCategory = category;
}
titleText.textContent = title;
descText.textContent = description;
await loadPage();
} catch (err) {
console.error('Update failed:', err);
alert('Error updating ticket');
}
}
document.getElementById('update-ticket-btn').addEventListener('click', async () => {
const isEditingTitle = titleInput.style.display === 'block';
const isEditingDesc = descInput.style.display === 'block';
const newTitle = isEditingTitle ? titleInput.value.trim() : titleText.textContent;
const newDesc = isEditingDesc ? descInput.value.trim() : descText.textContent;
const newStatus = statusSelect.value;
const newPriority = priorityInput.value;
const newCategory = categorySelect.value;
const confirmed = confirm('Möchten Sie die Änderungen wirklich speichern?');
if (!confirmed) return;
await updateTicket({ status: newStatus, priority: newPriority, title: newTitle, description: newDesc, category: newCategory });
titleInput.style.display = 'none';
titleText.style.display = 'block';
descInput.style.display = 'none';
descText.style.display = 'block';
});
document.getElementById('close-ticket-btn').addEventListener('click', async () => {
const confirmed = confirm('Möchtest du das Ticket wirklich schließen?');
if (!confirmed) return;
const newStatus = 'Closed';
const newPriority = priorityInput.value;
const newTitle = titleText.textContent;
const newDesc = descText.textContent;
const newCategory = categorySelect.value;
await updateTicket({ status: newStatus, priority: newPriority, title: newTitle, description: newDesc, category: newCategory });
});
titleText.addEventListener('dblclick', () => {
titleInput.value = titleText.textContent;
titleText.style.display = 'none';
titleInput.style.display = 'block';
titleInput.focus();
});
descText.addEventListener('dblclick', () => {
descInput.value = descText.textContent;
descText.style.display = 'none';
descInput.style.display = 'block';
descInput.focus();
});
const savedAuthor = localStorage.getItem('commentAuthor');
if (savedAuthor) commentAuthorInput.value = savedAuthor;
async function loadComments() {
const comments = await fetchComments(ticketId);
const container = document.getElementById('comments-container');
container.innerHTML = comments.map(comment => {
const role = comment.role || 'user';
const isAdmin = role === 'admin';
const avatarUrl = isAdmin ? '/assets/admin.jpg' : '/assets/user.jpg';
return `
<div class="comment ${isAdmin ? 'comment-admin' : ''}">
<img class="comment-avatar" src="${avatarUrl}" alt="${isAdmin ? 'Admin' : 'User'} Avatar">
<div class="comment-content">
<div class="comment-header">
<span class="comment-author">${comment.author}</span>
${isAdmin ? '<span class="comment-role-badge">Admin</span>' : ''}
<span class="comment-date">${formatDateTime(comment.date)}</span>
</div>
<div class="comment-text">${comment.content}</div>
</div>
</div>`;
}).join('');
}
async function loadHistory() {
const history = await fetchHistory(ticketId);
const container = document.getElementById('history-container');
container.innerHTML = history.map(h => `
<div class="history-item">
<div class="history-icon"><i class="fas fa-${getHistoryIcon(h.action)}"></i></div>
<div class="history-content">
<div class="history-action">${h.action}</div>
<div class="history-details">${h.message || ''}</div>
<div class="history-meta">
<span>By ${h.author || h.actor || 'unknown'}</span>
<span>${formatDateTime(h.date || h.timestamp)}</span>
</div>
</div>
</div>`).join('');
}
commentForm.addEventListener('submit', async (e) => {
e.preventDefault();
const author = commentAuthorInput.value.trim();
if (!author) return alert('Bitte gib deinen Namen ein');
const content = document.getElementById('comment-content').value.trim();
if (!content) return alert('Comment required');
const role = 'admin';
const timestamp = new Date().toISOString();
try {
await createComment(ticketId, { author, role, content, timestamp });
await sendWebhook(`New comment added by ${author} (${role}): "${content}" on ticket #${ticketId}`);
localStorage.setItem('commentAuthor', author);
document.getElementById('comment-content').value = '';
await loadComments();
await loadHistory();
} catch (err) {
console.error('Failed to add comment:', err);
alert('Failed to add comment');
}
});
timeForm.addEventListener('submit', async (e) => {
e.preventDefault();
const minutes = parseInt(document.getElementById('time-minutes').value);
const comment = document.getElementById('time-comment').value.trim();
const actor = commentAuthorInput.value || 'Admin';
if (isNaN(minutes) || minutes <= 0) return alert('Gültige Zeit in Minuten eingeben');
try {
// Save the time log
await logTime(ticketId, { minutes, comment, author: actor });
// Also add it as a comment
const timeComment = `⏱ Zeit gebucht: ${formatMinutes(minutes)}${comment ? ` – ${comment}` : ''}`;
await createComment(ticketId, {
author: actor,
role: 'admin',
content: timeComment,
timestamp: new Date().toISOString()
});
// Webhook notification
await sendWebhook(`Time booked: ${formatMinutes(minutes)} by ${actor} on ticket #${ticketId}${comment ? ` – "${comment}"` : ''}`);
// Clear inputs and reload
document.getElementById('time-minutes').value = '';
document.getElementById('time-comment').value = '';
await loadPage();
} catch (err) {
console.error('Fehler beim Speichern der Zeit:', err);
alert('Fehler beim Speichern');
}
});
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
const tab = btn.dataset.tab;
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
document.getElementById(`${tab}-tab`).classList.add('active');
});
});
await loadPage();
});
+153
View File
@@ -0,0 +1,153 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ticket Details (Admin) - Ticket System</title>
<link rel="icon" type="image/x-icon" href="/assets/favicon_ticket.ico">
<link rel="stylesheet" href="ticket-view.css" />
<link rel="stylesheet" href="admin-view.css" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
</head>
<body>
<div class="container">
<header class="header">
<h1><i class="fas fa-ticket-alt"></i> Ticket Details (Admin)</h1>
<div class="user-actions">
<a href="../admin.html" class="back-link">
<i class="fas fa-arrow-left"></i> Back to Admin
</a>
</div>
</header>
<main class="main-content">
<div class="ticket-details-layout">
<!-- Ticket Info Section -->
<section class="ticket-details">
<div class="ticket-header">
<div id="ticket-title-view">
<h2 id="ticket-title-text">Loading...</h2>
<input id="ticket-title-input" class="form-control" style="display: none" />
</div>
<div class="ticket-meta">
<span class="ticket-id">#<span id="ticket-id-val">-</span></span>
</div>
</div>
<div class="ticket-info-grid">
<div class="info-item requester">
<h4>Requester</h4>
<p id="ticket-requester">-</p>
</div>
<div class="info-item description">
<h3>Description</h3>
<p id="ticket-description-text">Loading...</p>
<textarea id="ticket-description-input" class="form-control" style="display: none"></textarea>
</div>
<div class="info-item created">
<h4>Created</h4>
<p id="ticket-created">-</p>
</div>
<div class="info-item updated">
<h4>Last Updated</h4>
<p id="ticket-updated">-</p>
</div>
<div class="info-item">
<h4>Time</h4>
<p><span id="total-time">0</span></p>
</div>
<div class="info-item status">
<h4><label for="status-select">Status</label></h4>
<select id="status-select" class="form-control">
<option value="Open">Open</option>
<option value="In Progress">In Progress</option>
<option value="On Hold">On Hold</option>
<option value="Closed">Closed</option>
</select>
</div>
<div class="info-item priority">
<h4><label for="priority-text">Priority</label></h4>
<select id="priority-text" class="form-control">
<option value="Low">Low</option>
<option value="Medium">Medium</option>
<option value="High">High</option>
<option value="Urgent">Urgent</option>
</select>
</div>
<div class="info-item category">
<h4><label for="category-select">Category</label></h4>
<select id="category-select" class="form-control">
<option value="HELP">HELP</option>
<option value="HELP MEE NOOOW">HELP MEE NOOOW</option>
<option value="IT-Support">IT-Support</option>
<option value="Suggestion...">Suggestion...</option>
<option value="Add Book pls">Add Book pls</option>
<option value="Honeyycloud!">Honeyycloud!</option>
</select>
</div>
</div>
<div class="ticket-actions-bar">
<button class="btn-primary" id="close-ticket-btn">
<i class="fas fa-check-circle"></i> Close Ticket
</button>
<button class="btn-secondary" id="update-ticket-btn" title="Update">
<i class="fas fa-sync-alt"></i>
</button>
</div>
</section>
<!-- Ticket Activity Section -->
<section class="ticket-activity">
<div class="tabs">
<button class="tab-btn active" data-tab="comments">Comments</button>
<button class="tab-btn" data-tab="history">History</button>
</div>
<div class="tab-content active" id="comments-tab">
<div class="forms-row">
<form id="comment-form" class="comment-form compact-form">
<input type="text" id="comment-author" placeholder="Your name" required />
<textarea id="comment-content" rows="2" placeholder="Add a comment..." required></textarea>
<button class="btn-primary" type="submit">Post Comment</button>
</form>
<form id="time-booking-form" class="comment-form compact-form">
<input type="number" id="time-minutes" name="time-minutes" min="0" placeholder="Minutes" required />
<textarea id="time-comment" name="time-comment" rows="2" placeholder="Optional comment"></textarea>
<button class="btn-primary" type="submit">Add Time</button>
</form>
</div>
<!-- Comment Output -->
<div id="comments-container"></div>
<!-- Time Logs -->
<section id="time-logs" class="time-logs"></section>
</div>
<div class="tab-content" id="history-tab">
<div id="history-container"></div>
</div>
</section>
</div>
</main>
<footer class="footer">
<p>Ticket System by Honey v1.1 © 2025</p>
</footer>
</div>
<script type="module" src="ticket-admin.js"></script>
</body>
</html>
+119
View File
@@ -0,0 +1,119 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>My Ticket - Ticket System</title>
<link rel="icon" type="image/x-icon" href="/assets/favicon_ticket.ico">
<link rel="stylesheet" href="ticket-view.css" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
</head>
<body>
<div class="container user-view">
<header class="header">
<h1><i class="fas fa-ticket-alt"></i> My Ticket</h1>
<div class="user-actions">
<a href="../index.html" class="back-link"
><i class="fas fa-arrow-left"></i> Back to Dashboard</a
>
</div>
</header>
<main class="main-content">
<div class="ticket-details-layout">
<section class="ticket-details">
<div class="ticket-header">
<h2 id="ticket-title-text">Loading...</h2>
<div class="ticket-meta">
<span class="ticket-id"
>#<span id="ticket-id-val">-</span></span
>
</div>
</div>
<div class="ticket-body">
<!-- Requester first -->
<div class="info-item requester">
<h4>Requester</h4>
<p id="ticket-requester">-</p>
</div>
<!-- Then description -->
<h3>Description</h3>
<p id="ticket-description-text">Loading...</p>
<!-- Then grid info -->
<div class="ticket-info-grid">
<div class="info-item">
<h4>Created</h4>
<p id="ticket-created">-</p>
</div>
<div class="info-item">
<h4>Last Updated</h4>
<p id="ticket-updated">-</p>
</div>
<div class="info-item">
<h4>Time</h4>
<p><span id="total-time">0</span></p>
</div>
<div class="info-item">
<h4>Status</h4>
<span id="ticket-status-badge" class="ticket-status">Open</span>
</div>
<div class="info-item">
<h4>Priority</h4>
<span id="priority-text" class="ticket-priority">-</span>
</div>
<div class="info-item">
<h4>Category</h4>
<span id="ticket-category" class="ticket-category">-</span>
</div>
</div>
</div>
</section>
<section class="ticket-activity">
<div class="tabs">
<button class="tab-btn active" data-tab="comments">Comments</button>
<button class="tab-btn" data-tab="history">History</button>
</div>
<div class="tab-content active" id="comments-tab">
<form id="comment-form" class="comment-form">
<input
type="text"
id="comment-author"
class="form-control"
placeholder="Your name"
required
/>
<textarea
id="comment-content"
rows="3"
class="form-control"
placeholder="Add a comment..."
required
></textarea>
<button class="btn-primary" type="submit">Post Comment</button>
</form>
<div id="comments-container"></div>
</div>
<div class="tab-content" id="history-tab">
<div id="history-container"></div>
</div>
</section>
</div>
</main>
<footer class="footer">
<p>Ticket System by Honey v1.1 © 2025</p>
</footer>
</div>
<script type="module" src="ticket-user.js"></script>
</body>
</html>
+380
View File
@@ -0,0 +1,380 @@
:root {
--color-bg: #2e2e2e;
--color-surface: #3c3c3c;
--color-primary: #4caf50;
--color-primary-hover: #45a049;
--color-accent: #2196f3;
--color-text: #f5f5f5;
--color-muted: #b0b0b0;
--color-border: #4f4f4f;
--color-danger: #e53935;
--color-admin-highlight: #424242;
--radius: 8px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--color-bg);
color: var(--color-text);
line-height: 1.6;
padding: 20px;
}
a {
color: var(--color-accent);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: var(--color-surface);
border-radius: var(--radius);
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid var(--color-border);
}
.header h1 {
font-size: 1.8rem;
display: flex;
align-items: center;
gap: 10px;
}
.back-link {
font-size: 1rem;
}
.back-link i {
margin-right: 5px;
}
.main-content {
padding: 20px;
}
.ticket-details-layout {
display: flex;
flex-direction: column;
gap: 20px;
}
@media (min-width: 900px) {
.ticket-details-layout {
flex-direction: row;
}
}
.ticket-details,
.ticket-activity {
background-color: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--radius);
padding: 20px;
flex: 1;
min-width: 0;
}
.ticket-activity {
display: flex;
flex-direction: column;
height: 600px;
}
.tab-content {
display: none;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
height: 100%;
}
.tab-content.active {
display: flex;
}
#comments-container,
#history-container {
flex-grow: 1;
overflow-y: auto;
max-height: 100%;
padding-right: 8px;
scrollbar-width: thin;
scrollbar-color: var(--color-primary) transparent;
}
#comments-container::-webkit-scrollbar,
#history-container::-webkit-scrollbar {
width: 8px;
}
#comments-container::-webkit-scrollbar-thumb,
#history-container::-webkit-scrollbar-thumb {
background-color: var(--color-primary);
border-radius: 4px;
}
.ticket-header {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
margin-bottom: 20px;
}
.ticket-header-left {
display: flex;
flex-direction: column;
gap: 10px;
max-width: 100%;
}
.ticket-header-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8px;
}
.ticket-header h2 {
font-size: 1.4rem;
font-weight: bold;
}
.ticket-status-priority-requester {
display: flex;
gap: 15px;
align-items: center;
margin-top: 5px;
flex-wrap: wrap;
}
.ticket-status,
.ticket-priority,
.ticket-requester,
.ticket-category {
padding: 6px 14px;
border-radius: 20px;
font-weight: bold;
font-size: 0.9rem;
white-space: nowrap;
display: inline-block;
overflow: visible;
max-width: 100%;
box-sizing: border-box;
}
.ticket-status {
background-color: var(--color-primary);
color: white;
}
.ticket-priority {
background-color: var(--color-accent);
color: white;
}
.ticket-requester,
.ticket-category {
background-color: var(--color-muted);
color: var(--color-bg);
}
.ticket-meta {
margin-top: 10px;
font-size: 0.9rem;
color: var(--color-muted);
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.ticket-body h3 {
margin: 20px 0 10px;
font-size: 1.1rem;
}
#ticket-description-text {
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: 100%;
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
padding-right: 10px;
margin-bottom: 20px;
}
@media (min-width: 600px) {
.ticket-info-grid {
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
}
.info-item h4 {
margin-bottom: 4px;
font-size: 1rem;
font-weight: 600;
}
.form-control {
padding: 10px 12px;
border-radius: 6px;
border: 1px solid var(--color-border);
background-color: var(--color-surface);
color: var(--color-text);
}
.form-control:focus {
outline: none;
border-color: var(--color-primary);
}
.ticket-info-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr; /* 3 columns */
gap: 15px;
grid-auto-rows: auto;
}
.ticket-info-grid .info-item {
min-width: 0;
overflow: hidden;
}
.info-item .form-control {
width: 100%;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tabs {
display: flex;
margin-bottom: 15px;
gap: 10px;
}
.tab-btn {
flex: 1;
padding: 10px;
border: none;
border-radius: var(--radius);
background-color: var(--color-surface);
color: var(--color-text);
cursor: pointer;
transition: background 0.2s ease;
}
.tab-btn:hover {
background-color: var(--color-border);
}
.tab-btn.active {
background-color: var(--color-primary);
}
.comment {
display: flex;
align-items: flex-start;
gap: 16px;
margin-bottom: 20px;
max-width: 800px;
}
.comment-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
border: 2px solid var(--color-border);
}
.comment-content {
background-color: var(--color-surface);
padding: 12px 16px;
border-radius: var(--radius);
width: 100%;
}
.comment-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
font-size: 0.85rem;
}
.comment-author {
font-weight: bold;
}
.comment-date {
color: var(--color-muted);
font-size: 0.8rem;
}
.comment-text {
font-size: 0.95rem;
line-height: 1.5;
word-wrap: break-word;
}
.comment-form {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 20px;
}
.comment-form input,
.comment-form textarea {
background-color: var(--color-surface);
color: var(--color-text);
border: 1px solid var(--color-border);
border-radius: var(--radius);
padding: 10px;
font-size: 1rem;
}
.comment-form input:focus,
.comment-form textarea:focus {
outline: none;
border-color: var(--color-primary);
}
.history-item {
display: flex;
gap: 12px;
margin-bottom: 15px;
}
.history-icon {
font-size: 1.2rem;
color: var(--color-primary);
}
.history-content {
background-color: var(--color-surface);
padding: 10px 15px;
border-radius: var(--radius);
width: 100%;
}
.history-action {
font-weight: bold;
margin-bottom: 4px;
}
.history-meta {
display: flex;
justify-content: space-between;
font-size: 0.8rem;
color: var(--color-muted);
}
/* Post Comment Button Styling */
.btn-primary {
padding: 10px 16px;
background-color: var(--color-primary);
color: white;
border: none;
border-radius: var(--radius);
cursor: pointer;
font-weight: 600;
transition: background 0.2s ease;
align-self: flex-start; /* damit er nicht breit wird */
}
.btn-primaryt-btn:hover {
background-color: var(--color-primary-hover);
}
.footer {
padding: 15px;
text-align: center;
font-size: 0.85rem;
color: var(--color-muted);
border-top: 1px solid var(--color-border);
}