feat: replaced CSS with TailwindCSS for DetailView and ListView
This commit is contained in:
@@ -119,6 +119,7 @@ USE_TZ = True
|
|||||||
# https://docs.djangoproject.com/en/5.2/howto/static-files/
|
# https://docs.djangoproject.com/en/5.2/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = "static/"
|
STATIC_URL = "static/"
|
||||||
|
STATICFILES_DIRS = [BASE_DIR / "static"]
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||||
|
|||||||
1
ticketsystem/static/css/style.css
Normal file
1
ticketsystem/static/css/style.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
|
{% load static %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{% block title %}TicketSystem{% endblock %}</title>
|
<title>{% block title %}TicketSystem{% endblock %}</title>
|
||||||
|
<link href="{% static 'css/tailwind.css' %}" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -1,300 +1,149 @@
|
|||||||
{% extends "ticketsystem/base.html" %}
|
{% extends "ticketsystem/base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
<!-- Messages -->
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<div style="max-width: 700px; margin: 1rem auto;">
|
<div class="max-w-4xl mx-auto px-4 pt-4">
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div style="padding: 1rem; border-radius: 5px; margin-bottom: 1rem;
|
<div class="mb-4 p-3 rounded {% if message.tags == 'error' %}bg-red-100 text-red-700{% elif message.tags == 'success' %}bg-green-100 text-green-700{% else %}bg-yellow-100 text-yellow-700{% endif %}">
|
||||||
background-color: {% if message.tags == 'error' %}#f8d7da
|
|
||||||
{% elif message.tags == 'success' %}#d4edda
|
|
||||||
{% else %}#fff3cd{% endif %};
|
|
||||||
color: #333;">
|
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<style>
|
<div class="max-w-4xl mx-auto px-4 py-6">
|
||||||
.ticket-container {
|
<!-- Ticket Bearbeitung -->
|
||||||
max-width: 700px;
|
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
||||||
margin: 2rem auto;
|
<h1 class="text-3xl font-bold mb-4">🎫 Ticket #{{ ticket.id }} – {{ ticket.title }}</h1>
|
||||||
padding: 2rem;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: #fafafa;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
<div class="text-sm text-gray-500 mb-6">
|
||||||
margin-bottom: 1rem;
|
🕒 Erstellt: {{ ticket.created_at|date:"d.m.Y H:i" }} |
|
||||||
}
|
🔄 Aktualisiert: {{ ticket.updated_at|date:"d.m.Y H:i" }}
|
||||||
|
</div>
|
||||||
|
|
||||||
.form-group label {
|
<form method="post" class="space-y-4">
|
||||||
display: block;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 0.3rem;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input, .form-group select, .form-group textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group textarea {
|
|
||||||
min-height: 100px;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
background: #007bff;
|
|
||||||
color: white;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
background: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-meta {
|
|
||||||
margin-top: 2rem;
|
|
||||||
padding-top: 1rem;
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment {
|
|
||||||
margin: 1rem 0;
|
|
||||||
padding: 1rem;
|
|
||||||
border-left: 4px solid #007bff;
|
|
||||||
background: #f0f4ff;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-meta {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-text {
|
|
||||||
color: #333;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-form {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-form h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-form textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.75rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
min-height: 80px;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-form button {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
background: #28a745;
|
|
||||||
color: white;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-form button:hover {
|
|
||||||
background: #218838;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-entry {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
border-left: 4px solid #6c757d;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-user {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-field {
|
|
||||||
background: #e9ecef;
|
|
||||||
padding: 0.2rem 0.4rem;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-change {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-old {
|
|
||||||
color: #dc3545;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-new {
|
|
||||||
color: #28a745;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-date {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-container {
|
|
||||||
max-width: 700px;
|
|
||||||
margin: 1rem auto;
|
|
||||||
padding: 2rem;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: #fafafa;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-container h2 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
color: #333;
|
|
||||||
border-bottom: 2px solid #007bff;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-content {
|
|
||||||
color: #666;
|
|
||||||
font-style: italic;
|
|
||||||
text-align: center;
|
|
||||||
padding: 2rem;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px dashed #dee2e6;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!-- Ticket Bearbeitung -->
|
|
||||||
<div class="ticket-container">
|
|
||||||
<h1>🎫 Ticket #{{ ticket.id }} – {{ ticket.title }}</h1>
|
|
||||||
|
|
||||||
<form method="post" style="margin-top: 1rem;">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.non_field_errors }}
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div>
|
||||||
<label for="id_title">Titel:</label>
|
<label class="block text-sm font-medium mb-1">Titel:</label>
|
||||||
{{ form.title }}
|
<input type="text" name="title" value="{{ ticket.title }}"
|
||||||
|
{% if not view.can_edit %}disabled{% endif %}
|
||||||
|
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 {% if not view.can_edit %}bg-gray-100{% endif %}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div>
|
||||||
<label for="id_description">Beschreibung:</label>
|
<label class="block text-sm font-medium mb-1">Beschreibung:</label>
|
||||||
{{ form.description }}
|
<textarea name="description" rows="4"
|
||||||
|
{% if not view.can_edit %}disabled{% endif %}
|
||||||
|
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 {% if not view.can_edit %}bg-gray-100{% endif %}">{{ ticket.description }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<label for="id_status">Status:</label>
|
<div>
|
||||||
{{ form.status }}
|
<label class="block text-sm font-medium mb-1">Status:</label>
|
||||||
|
<select name="status" {% if not view.can_edit %}disabled{% endif %}
|
||||||
|
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 {% if not view.can_edit %}bg-gray-100{% endif %}">
|
||||||
|
<option value="open" {% if ticket.status == 'open' %}selected{% endif %}>Offen</option>
|
||||||
|
<option value="in_progress" {% if ticket.status == 'in_progress' %}selected{% endif %}>In Bearbeitung</option>
|
||||||
|
<option value="closed" {% if ticket.status == 'closed' %}selected{% endif %}>Geschlossen</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Priorität:</label>
|
||||||
|
<select name="priority" {% if not view.can_edit %}disabled{% endif %}
|
||||||
|
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 {% if not view.can_edit %}bg-gray-100{% endif %}">
|
||||||
|
<option value="low" {% if ticket.priority == 'low' %}selected{% endif %}>Niedrig</option>
|
||||||
|
<option value="medium" {% if ticket.priority == 'medium' %}selected{% endif %}>Normal</option>
|
||||||
|
<option value="high" {% if ticket.priority == 'high' %}selected{% endif %}>Hoch</option>
|
||||||
|
<option value="urgent" {% if ticket.priority == 'urgent' %}selected{% endif %}>Dringend</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div>
|
||||||
<label for="id_priority">Priorität:</label>
|
<label class="block text-sm font-medium mb-1">Zugewiesen an:</label>
|
||||||
{{ form.priority }}
|
<select name="assigned_to" {% if not view.can_edit %}disabled{% endif %}
|
||||||
</div>
|
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 {% if not view.can_edit %}bg-gray-100{% endif %}">
|
||||||
|
<option value="">Nicht zugewiesen</option>
|
||||||
<div class="form-group">
|
{% for user in form.assigned_to.field.queryset %}
|
||||||
<label for="id_assigned_to">Zugewiesen an:</label>
|
<option value="{{ user.pk }}" {% if ticket.assigned_to == user %}selected{% endif %}>
|
||||||
{{ form.assigned_to }}
|
{{ user.username }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if view.can_edit %}
|
{% if view.can_edit %}
|
||||||
<button type="submit" class="btn">💾 Änderungen speichern</button>
|
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">
|
||||||
|
💾 Speichern
|
||||||
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p style="color: #999; margin-top: 1rem;">Du darfst dieses Ticket nicht bearbeiten.</p>
|
<p class="text-gray-500 italic">Du kannst dieses Ticket nicht bearbeiten.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="ticket-meta">
|
|
||||||
🕒 Erstellt am: {{ ticket.created_at|date:"d.m.Y H:i" }}<br>
|
|
||||||
🔄 Aktualisiert: {{ ticket.updated_at|date:"d.m.Y H:i" }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Kommentare Sektion -->
|
<!-- Kommentare -->
|
||||||
<div class="section-container">
|
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
||||||
<h2>💬 Kommentare</h2>
|
<h2 class="text-xl font-bold mb-4">💬 Kommentare ({{ ticket.comments.count }})</h2>
|
||||||
|
|
||||||
{% if ticket.comments.exists %}
|
{% if ticket.comments.exists %}
|
||||||
{% for comment in ticket.comments.all %}
|
{% for comment in ticket.comments.all %}
|
||||||
<div class="comment">
|
<div class="border-l-4 border-blue-400 bg-blue-50 p-4 mb-4">
|
||||||
<div class="comment-meta">
|
<div class="text-sm text-gray-600 mb-2">
|
||||||
<strong>{{ comment.author.username }}</strong> am {{ comment.created_at|date:"d.m.Y H:i" }}
|
<strong>{{ comment.author.username }}</strong> - {{ comment.created_at|date:"d.m.Y H:i" }}
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-text">{{ comment.text }}</div>
|
<div>{{ comment.text|linebreaks }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="no-content">
|
<p class="text-gray-500 italic">Keine Kommentare vorhanden.</p>
|
||||||
Keine Kommentare vorhanden.
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<div class="comment-form">
|
<div class="bg-gray-50 p-4 rounded mt-4">
|
||||||
<h3>📝 Neuen Kommentar schreiben</h3>
|
<h3 class="font-medium mb-2">📝 Neuer Kommentar</h3>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ comment_form.as_p }}
|
<textarea name="text" rows="3" placeholder="Kommentar schreiben..."
|
||||||
<button type="submit" name="comment_submit">Kommentar absenden</button>
|
class="w-full p-2 border border-gray-300 rounded shadow-sm mb-2 focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500"></textarea>
|
||||||
|
<button type="submit" name="comment_submit"
|
||||||
|
class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded">
|
||||||
|
Absenden
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bearbeitungshistorie Sektion -->
|
<!-- Historie -->
|
||||||
<div class="section-container">
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
<h2>🕓 Bearbeitungshistorie</h2>
|
<h2 class="text-xl font-bold mb-4">🕓 Änderungen ({{ ticket.history.count }})</h2>
|
||||||
|
|
||||||
{% if ticket.history.exists %}
|
{% if ticket.history.exists %}
|
||||||
{% for entry in ticket.history.all %}
|
{% for entry in ticket.history.all %}
|
||||||
<div class="history-entry">
|
<div class="border-l-4 border-gray-300 bg-gray-50 p-4 mb-4">
|
||||||
<div class="history-user">{{ entry.changed_by.username }}</div>
|
<div class="text-sm">
|
||||||
<div class="history-change">
|
<strong>{{ entry.changed_by.username }}</strong> hat
|
||||||
hat <span class="history-field">{{ entry.field }}</span> geändert:
|
<code class="bg-gray-200 px-1 rounded">{{ entry.field }}</code> geändert
|
||||||
</div>
|
</div>
|
||||||
<div class="history-change">
|
<div class="text-sm mt-1">
|
||||||
<span class="history-old">{{ entry.old_value }}</span> → <span class="history-new">{{ entry.new_value }}</span>
|
<span class="text-red-600">{{ entry.old_value }}</span> →
|
||||||
|
<span class="text-green-600">{{ entry.new_value }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="history-date">am {{ entry.changed_at|date:"d.m.Y H:i" }}</div>
|
<div class="text-xs text-gray-500 mt-1">{{ entry.changed_at|date:"d.m.Y H:i" }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="no-content">
|
<p class="text-gray-500 italic">Keine Änderungen bisher.</p>
|
||||||
Keine Änderungen bisher.
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<div class="mt-6">
|
||||||
|
<a href="{% url 'ticket-list' %}" class="text-blue-500 hover:text-blue-600">← Zurück zur Übersicht</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,90 +1,196 @@
|
|||||||
{% extends "ticketsystem/base.html" %}
|
{% extends "ticketsystem/base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<style>
|
|
||||||
.ticket-list-container {
|
|
||||||
max-width: 700px;
|
|
||||||
margin: 2rem auto;
|
|
||||||
padding: 2rem;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: #fafafa;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-list-container h1 {
|
<!-- Messages -->
|
||||||
margin-bottom: 1.5rem;
|
{% if messages %}
|
||||||
font-size: 1.8rem;
|
<div class="max-w-6xl mx-auto px-4 pt-4">
|
||||||
color: #333;
|
{% for message in messages %}
|
||||||
}
|
<div class="mb-4 p-3 rounded {% if message.tags == 'error' %}bg-red-100 text-red-700{% elif message.tags == 'success' %}bg-green-100 text-green-700{% else %}bg-yellow-100 text-yellow-700{% endif %}">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
.ticket-item {
|
<div class="max-w-6xl mx-auto px-4 py-6">
|
||||||
padding: 1rem;
|
<!-- Header -->
|
||||||
border-bottom: 1px solid #ccc;
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center mb-6">
|
||||||
}
|
<div>
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 mb-2">🎫 Ticket-Übersicht</h1>
|
||||||
|
<p class="text-gray-600">Verwalte und verfolge alle deine Tickets</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 sm:mt-0">
|
||||||
|
<a href="{% url 'create' %}"
|
||||||
|
class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded font-medium">
|
||||||
|
➕ Neues Ticket erstellen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
.ticket-item:last-child {
|
<!-- Filter und Suche -->
|
||||||
border-bottom: none;
|
<div class="bg-white rounded-lg shadow p-4 mb-6">
|
||||||
}
|
<div class="flex flex-col lg:flex-row gap-4">
|
||||||
|
<!-- Status Filter -->
|
||||||
.ticket-item a {
|
<div class="flex-1">
|
||||||
text-decoration: none;
|
<form method="get">
|
||||||
color: #007bff;
|
<label class="block text-sm font-medium mb-1">Status:</label>
|
||||||
font-weight: bold;
|
<select name="status" onchange="this.form.submit()"
|
||||||
}
|
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<option value="">Alle Status</option>
|
||||||
.ticket-item a:hover {
|
<option value="open" {% if selected_status == "open" %}selected{% endif %}>🔴 Offen</option>
|
||||||
text-decoration: underline;
|
<option value="in_progress" {% if selected_status == "in_progress" %}selected{% endif %}>🟡 In Bearbeitung</option>
|
||||||
}
|
<option value="closed" {% if selected_status == "closed" %}selected{% endif %}>🟢 Erledigt</option>
|
||||||
|
|
||||||
.ticket-meta {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #666;
|
|
||||||
margin-top: 0.3rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="ticket-list-container">
|
|
||||||
<h1>🎫 Alle Tickets</h1>
|
|
||||||
|
|
||||||
<p style="text-align: right;">
|
|
||||||
<a href="{% url 'create' %}" style="text-decoration: none; font-weight: bold;">➕ Neues Ticket</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<form method="get" style="margin-bottom: 1rem;">
|
|
||||||
<label for="status">🔍 Filtern nach Status:</label>
|
|
||||||
<select name="status" id="status" onchange="this.form.submit()">
|
|
||||||
<option value="">Alle</option>
|
|
||||||
<option value="open" {% if selected_status == "open" %}selected{% endif %}>Offen</option>
|
|
||||||
<option value="in_progress" {% if selected_status == "in_progress" %}selected{% endif %}>In Bearbeitung</option>
|
|
||||||
<option value="closed" {% if selected_status == "closed" %}selected{% endif %}>Erledigt</option>
|
|
||||||
</select>
|
</select>
|
||||||
</form>
|
</form>
|
||||||
<form method="get" style="margin-bottom: 1.5rem;">
|
</div>
|
||||||
<input type="text" name="q" placeholder="🔍 Suche nach Titel oder Beschreibung"
|
|
||||||
value="{{ search_query }}" style="padding: 0.4rem; width: 60%; max-width: 300px;">
|
<!-- Suche -->
|
||||||
|
<div class="flex-1 lg:flex-2">
|
||||||
|
<form method="get">
|
||||||
|
<label class="block text-sm font-medium mb-1">Suche:</label>
|
||||||
|
<div class="flex">
|
||||||
|
<input type="text" name="q" value="{{ search_query }}"
|
||||||
|
placeholder="Titel oder Beschreibung durchsuchen..."
|
||||||
|
class="flex-1 p-2 border border-gray-300 rounded-l shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
{% if selected_status %}
|
{% if selected_status %}
|
||||||
<input type="hidden" name="status" value="{{ selected_status }}">
|
<input type="hidden" name="status" value="{{ selected_status }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button type="submit">Suchen</button>
|
<button type="submit"
|
||||||
|
class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-r">
|
||||||
|
🔍
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Suchergebnisse Info -->
|
||||||
{% if search_query %}
|
{% if search_query %}
|
||||||
<p>Ergebnisse für „<strong>{{ search_query }}</strong>“:</p>
|
<div class="mb-4 p-3 bg-blue-100 border-l-4 border-blue-400 rounded">
|
||||||
{% endif %}
|
<span class="font-medium">Suchergebnisse für:</span> „{{ search_query }}"
|
||||||
|
<a href="?" class="ml-4 text-blue-600 hover:text-blue-800">Suche zurücksetzen</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Ticket Tabelle -->
|
||||||
|
{% if tickets %}
|
||||||
|
<div class="bg-white rounded-lg shadow overflow-hidden">
|
||||||
|
<table class="w-full">
|
||||||
|
<thead class="bg-blue-600 text-white">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3 text-center text-sm font-bold">#</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-bold">Titel</th>
|
||||||
|
<th class="px-4 py-3 text-center text-sm font-bold">Status</th>
|
||||||
|
<th class="px-4 py-3 text-center text-sm font-bold">Priorität</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-bold">Zugewiesen an</th>
|
||||||
|
<th class="px-4 py-3 text-center text-sm font-bold">Erstellt</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200">
|
||||||
{% for ticket in tickets %}
|
{% for ticket in tickets %}
|
||||||
<div class="ticket-item">
|
<tr class="hover:bg-gray-50">
|
||||||
<a href="{% url 'detail' ticket.pk %}">
|
<td class="px-4 py-3 text-center text-sm font-bold text-gray-500">
|
||||||
#{{ ticket.id }} – {{ ticket.title }}
|
#{{ ticket.id }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<a href="{% url 'detail' ticket.pk %}"
|
||||||
|
class="font-bold text-blue-600 hover:text-blue-800 hover:underline">
|
||||||
|
{{ ticket.title }}
|
||||||
</a>
|
</a>
|
||||||
<div class="ticket-meta">
|
</td>
|
||||||
Status: {{ ticket.get_status_display }} |
|
<td class="px-4 py-3 text-center">
|
||||||
Priorität: {{ ticket.get_priority_display }} |
|
{% if ticket.status == 'open' %}
|
||||||
Angelegt am {{ ticket.created_at|date:"d.m.Y H:i" }}
|
<span class="px-2 py-1 rounded-full text-xs font-bold bg-red-500 text-white">
|
||||||
</div>
|
{{ ticket.get_status_display }}
|
||||||
</div>
|
</span>
|
||||||
{% empty %}
|
{% elif ticket.status == 'in_progress' %}
|
||||||
<p>Es sind derzeit keine Tickets vorhanden.</p>
|
<span class="px-2 py-1 rounded-full text-xs font-bold bg-yellow-400 text-gray-900">
|
||||||
|
{{ ticket.get_status_display }}
|
||||||
|
</span>
|
||||||
|
{% elif ticket.status == 'closed' %}
|
||||||
|
<span class="px-2 py-1 rounded-full text-xs font-bold bg-green-500 text-white">
|
||||||
|
{{ ticket.get_status_display }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-center">
|
||||||
|
{% if ticket.priority == 'low' %}
|
||||||
|
<span class="px-2 py-1 rounded-full text-xs font-bold bg-blue-500 text-white">
|
||||||
|
{{ ticket.get_priority_display }}
|
||||||
|
</span>
|
||||||
|
{% elif ticket.priority == 'medium' %}
|
||||||
|
<span class="px-2 py-1 rounded-full text-xs font-bold bg-yellow-400 text-gray-900">
|
||||||
|
{{ ticket.get_priority_display }}
|
||||||
|
</span>
|
||||||
|
{% elif ticket.priority == 'high' %}
|
||||||
|
<span class="px-2 py-1 rounded-full text-xs font-bold bg-orange-500 text-white">
|
||||||
|
{{ ticket.get_priority_display }}
|
||||||
|
</span>
|
||||||
|
{% elif ticket.priority == 'urgent' %}
|
||||||
|
<span class="px-2 py-1 rounded-full text-xs font-bold bg-red-500 text-white">
|
||||||
|
{{ ticket.get_priority_display }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-600">
|
||||||
|
{% if ticket.assigned_to %}
|
||||||
|
{{ ticket.assigned_to.username }}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-gray-400 italic">Nicht zugewiesen</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-center text-sm text-gray-500">
|
||||||
|
<div>{{ ticket.created_at|date:"d.m.Y" }}</div>
|
||||||
|
<div class="text-xs text-gray-400">{{ ticket.created_at|date:"H:i" }}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
{% if is_paginated %}
|
||||||
|
<div class="mt-6 flex justify-between items-center">
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
Seite {{ page_obj.number }} von {{ page_obj.paginator.num_pages }}
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<a href="?page={{ page_obj.previous_page_number }}{% if selected_status %}&status={{ selected_status }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}"
|
||||||
|
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-50">Zurück</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<a href="?page={{ page_obj.next_page_number }}{% if selected_status %}&status={{ selected_status }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}"
|
||||||
|
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-50">Weiter</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<!-- Keine Tickets -->
|
||||||
|
<div class="text-center py-12 bg-white rounded-lg shadow">
|
||||||
|
<div class="text-gray-400 text-6xl mb-4">📋</div>
|
||||||
|
{% if search_query %}
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 mb-2">Keine Tickets gefunden</h3>
|
||||||
|
<p class="text-gray-500 mb-4">
|
||||||
|
Keine Tickets gefunden für die Suche „<strong>{{ search_query }}</strong>"
|
||||||
|
</p>
|
||||||
|
<a href="?" class="text-blue-600 hover:text-blue-800 font-medium">
|
||||||
|
Alle Tickets anzeigen
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 mb-2">Noch keine Tickets vorhanden</h3>
|
||||||
|
<p class="text-gray-500 mb-4">Erstelle dein erstes Ticket um loszulegen.</p>
|
||||||
|
<a href="{% url 'create' %}"
|
||||||
|
class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded font-medium">
|
||||||
|
Erstes Ticket erstellen
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -53,7 +53,7 @@ class TicketDetailUpdateView(UpdateView):
|
|||||||
comment_form_class = CommentForm
|
comment_form_class = CommentForm
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse('detail-update', kwargs={'pk': self.object.pk})
|
return reverse('detail', kwargs={'pk': self.object.pk})
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.ticket = self.get_object()
|
self.ticket = self.get_object()
|
||||||
|
|||||||
Reference in New Issue
Block a user