diff --git a/requirements.txt b/requirements.txt index eca8ce8..00c4c0f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ Django==5.2 djlint==1.36.4 EditorConfig==0.17.0 gunicorn==23.0.0 +isort==6.0.1 jsbeautifier==1.15.4 json5==0.12.0 mypy_extensions==1.1.0 diff --git a/ticketsystem/static/js/faq.js b/ticketsystem/static/js/faq.js new file mode 100644 index 0000000..5bb1832 --- /dev/null +++ b/ticketsystem/static/js/faq.js @@ -0,0 +1,33 @@ +function toggleFAQ(index) { + const content = document.getElementById(`content-${index}`); + const icon = document.getElementById(`icon-${index}`); + + if (content.classList.contains('hidden')) { + content.classList.remove('hidden'); + icon.classList.add('rotate-180'); + } else { + content.classList.add('hidden'); + icon.classList.remove('rotate-180'); + } +} + +function toggleAllFAQs() { + const button = document.getElementById('toggle-all-btn'); + const allContents = document.querySelectorAll('[id^="content-"]'); + const allIcons = document.querySelectorAll('[id^="icon-"]'); + + // Prüfen ob alle eingeklappt sind + const allCollapsed = Array.from(allContents).every(content => content.classList.contains('hidden')); + + if (allCollapsed) { + // Alle ausklappen + allContents.forEach(content => content.classList.remove('hidden')); + allIcons.forEach(icon => icon.classList.add('rotate-180')); + button.innerHTML = '📁 Alle einklappen'; + } else { + // Alle einklappen + allContents.forEach(content => content.classList.add('hidden')); + allIcons.forEach(icon => icon.classList.remove('rotate-180')); + button.innerHTML = '📂 Alle ausklappen'; + } +} diff --git a/ticketsystem/templates/ticketsystem/faq.html b/ticketsystem/templates/ticketsystem/faq.html index 736139b..bfe5abd 100644 --- a/ticketsystem/templates/ticketsystem/faq.html +++ b/ticketsystem/templates/ticketsystem/faq.html @@ -1,19 +1,46 @@ {% extends "ticketsystem/base.html" %} +{% load static %} {% block content %}

❓ Häufig gestellte Fragen

- 📄 PDF Download +
+ + 📄 PDF Download +
+ {% for faq in faqs %} -
-

{{ forloop.counter }}. {{ faq.question }}

-
{{ faq.answer|linebreaks }}
+
+ + + + +
{% empty %}
@@ -21,4 +48,7 @@
{% endfor %}
-{% endblock %} + + + +{% endblock %} \ No newline at end of file diff --git a/ticketsystem/views.py b/ticketsystem/views.py index 14f33ae..89c42c4 100644 --- a/ticketsystem/views.py +++ b/ticketsystem/views.py @@ -1,22 +1,20 @@ -from django.views.generic import ListView, TemplateView -from django.views.generic.edit import CreateView, UpdateView -from django.urls import reverse_lazy -from django.views.generic.detail import DetailView -from django.views.generic.edit import FormMixin -from .forms import CommentForm, TicketForm -from django.urls import reverse -from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib import messages -from django.shortcuts import redirect, render +from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Q from django.http import HttpResponse +from django.shortcuts import redirect, render +from django.urls import reverse, reverse_lazy +from django.utils import timezone +from django.views.generic import ListView, TemplateView +from django.views.generic.edit import CreateView, UpdateView +from reportlab.lib.enums import TA_CENTER, TA_LEFT from reportlab.lib.pagesizes import A4 -from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet from reportlab.lib.units import cm -from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer -from reportlab.lib.enums import TA_LEFT, TA_CENTER +from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer -from .models import Ticket, TicketHistory, FAQ, Course +from .forms import CommentForm, TicketForm +from .models import FAQ, Course, Ticket, TicketHistory class HomeView(TemplateView): @@ -92,9 +90,9 @@ class TicketDetailUpdateView(UpdateView): # Bearbeitungsrechte abhängig vom Status if is_superuser: self.can_edit = True - elif self.ticket.status == 'resolved' and is_creator: + elif self.ticket.status == "resolved" and is_creator: self.can_edit = True - elif self.ticket.status == 'closed': + elif self.ticket.status == "closed": self.can_edit = False else: self.can_edit = is_assigned_tutor @@ -108,16 +106,16 @@ class TicketDetailUpdateView(UpdateView): def get_form_kwargs(self): """Übergibt zusätzliche kwargs ans Form""" kwargs = super().get_form_kwargs() - kwargs['user'] = self.request.user - kwargs['ticket'] = self.object + kwargs["user"] = self.request.user + kwargs["ticket"] = self.object return kwargs def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Füge Berechtigungs-Infos zum Context hinzu - context['is_creator'] = self.is_creator - context['is_tutor'] = self.is_tutor - context['can_edit'] = self.can_edit + context["is_creator"] = self.is_creator + context["is_tutor"] = self.is_tutor + context["can_edit"] = self.can_edit # Kommentarformular hinzufügen if "comment_form" not in context: @@ -134,7 +132,15 @@ class TicketDetailUpdateView(UpdateView): response = super().form_valid(form) # Speichert das Ticket # History tracking für geänderte Felder - tracked_fields = ["title", "description", "status", "mistake", "course", "answer", "material"] + tracked_fields = [ + "title", + "description", + "status", + "mistake", + "course", + "answer", + "material", + ] for field in tracked_fields: if field in form.changed_data: old_value = getattr(original, field) @@ -152,11 +158,15 @@ class TicketDetailUpdateView(UpdateView): new_value = str(new_value) elif field == "answer": if old_value: - old_value = old_value[:50] + "..." if len(old_value) > 50 else old_value + old_value = ( + old_value[:50] + "..." if len(old_value) > 50 else old_value + ) else: old_value = "Keine Antwort" if new_value: - new_value = new_value[:50] + "..." if len(new_value) > 50 else new_value + new_value = ( + new_value[:50] + "..." if len(new_value) > 50 else new_value + ) else: new_value = "Keine Antwort" @@ -286,41 +296,54 @@ class TicketUpdateView(LoginRequiredMixin, UpdateView): def faq_list(request): """Zeigt alle aktiven FAQs an""" faqs = FAQ.objects.filter(is_active=True) - return render(request, 'ticketsystem/faq.html', {'faqs': faqs}) + return render(request, "ticketsystem/faq.html", {"faqs": faqs}) def faq_pdf_download(request): """Generiert PDF mit allen FAQs""" - # Response Setup - response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = 'attachment; filename="FAQ_Ticketsystem.pdf"' + + current_date = timezone.now() + date_string = current_date.strftime("%Y-%m-%d") + date_display = current_date.strftime("%d.%m.%Y") + + # Response Setup mit Datum im Dateinamen + response = HttpResponse(content_type="application/pdf") + response["Content-Disposition"] = ( + f'attachment; filename="FAQ_Ticketsystem_{date_string}.pdf"' + ) # PDF erstellen - doc = SimpleDocTemplate(response, pagesize=A4, topMargin=2 * cm, bottomMargin=2 * cm) + doc = SimpleDocTemplate( + response, pagesize=A4, topMargin=2 * cm, bottomMargin=2 * cm + ) # Styles styles = getSampleStyleSheet() title_style = ParagraphStyle( - 'CustomTitle', - parent=styles['Heading1'], + "CustomTitle", + parent=styles["Heading1"], fontSize=24, - textColor='#1a1a1a', + textColor="#1a1a1a", + spaceAfter=20, + alignment=TA_CENTER, + ) + subtitle_style = ParagraphStyle( + "Subtitle", + parent=styles["Normal"], + fontSize=12, + textColor="#666666", spaceAfter=30, - alignment=TA_CENTER + alignment=TA_CENTER, ) question_style = ParagraphStyle( - 'Question', - parent=styles['Heading2'], + "Question", + parent=styles["Heading2"], fontSize=14, - textColor='#2563eb', - spaceAfter=10 + textColor="#2563eb", + spaceAfter=10, ) answer_style = ParagraphStyle( - 'Answer', - parent=styles['Normal'], - fontSize=11, - spaceAfter=20, - alignment=TA_LEFT + "Answer", parent=styles["Normal"], fontSize=11, spaceAfter=20, alignment=TA_LEFT ) # Content @@ -328,23 +351,29 @@ def faq_pdf_download(request): # Titel elements.append(Paragraph("Häufig gestellte Fragen (FAQ)", title_style)) + + elements.append(Paragraph(f"Stand: {date_display}", subtitle_style)) + elements.append(Spacer(1, 20)) # FAQs holen faqs = FAQ.objects.filter(is_active=True) - # FAQs hinzufügen - for i, faq in enumerate(faqs, 1): - # Frage - elements.append(Paragraph(f"{i}. {faq.question}", question_style)) + if faqs.exists(): + # FAQs hinzufügen + for i, faq in enumerate(faqs, 1): + # Frage + elements.append(Paragraph(f"{i}. {faq.question}", question_style)) - # Antwort - elements.append(Paragraph(faq.answer.replace('\n', '
'), answer_style)) + # Antwort + elements.append(Paragraph(faq.answer.replace("\n", "
"), answer_style)) - # Abstand zwischen FAQs - elements.append(Spacer(1, 10)) + # Abstand zwischen FAQs + elements.append(Spacer(1, 10)) + else: + elements.append(Paragraph("Derzeit sind keine FAQs verfügbar.", answer_style)) # PDF generieren doc.build(elements) - return response \ No newline at end of file + return response