htmx single page application + work
This commit is contained in:
parent
7a70163e76
commit
73e019de9a
10 changed files with 150 additions and 58 deletions
|
@ -1,4 +1,7 @@
|
||||||
<a href="/report/{{ report.id }}" class="{{ style.card }}">
|
<button class="{{ style.card }}"
|
||||||
|
|
||||||
|
hx-get="/report/{{ report.id }}" hx-target="#main_content" hx-push-url="true" hx-swap="innerHTML"
|
||||||
|
>
|
||||||
{% if report.is_approved %}
|
{% if report.is_approved %}
|
||||||
<div
|
<div
|
||||||
class="absolute top-0 right-0 translate-y-3 -translate-x-3 bg-green-500 text-white rounded-full w-6 h-6 flex items-center justify-center scale-[1.5]"
|
class="absolute top-0 right-0 translate-y-3 -translate-x-3 bg-green-500 text-white rounded-full w-6 h-6 flex items-center justify-center scale-[1.5]"
|
||||||
|
@ -6,4 +9,4 @@
|
||||||
✓
|
✓
|
||||||
</div>
|
</div>
|
||||||
{% endif %} {{ report }}
|
{% endif %} {{ report }}
|
||||||
</a>
|
</button>
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<header class="w-full bg-red-600 text-white py-4 shadow-md max-h-20 h-full max-h-20">
|
|
||||||
<div class="max-w-4xl {% if center is None or center %}mx-auto{% endif %} px-4">
|
|
||||||
<h1 class="text-2xl font-bold {% if center is None or center %}{% else %}ml-5{% endif %}">{{ title }}</h1>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
|
@ -1,13 +1,4 @@
|
||||||
<html>
|
{{ title|safe }}
|
||||||
<head>
|
|
||||||
<title>Azube</title>
|
|
||||||
{% include 'head.html' %}
|
|
||||||
</head>
|
|
||||||
<body class="bg-gray-100 min-h-screen">
|
|
||||||
|
|
||||||
{% include 'header.html' with title="Deine Berichtshefte" center=False %}
|
|
||||||
|
|
||||||
<div class="bg-gray-100 min-h-screen">
|
|
||||||
|
|
||||||
{% if late_reports > 1 %}
|
{% if late_reports > 1 %}
|
||||||
|
|
||||||
|
@ -33,9 +24,11 @@
|
||||||
{% for report in reports %}
|
{% for report in reports %}
|
||||||
|
|
||||||
{% if forloop.first %} {% if report.week != week_now %}
|
{% if forloop.first %} {% if report.week != week_now %}
|
||||||
<a href="/write" class="{{ style.card }} flex items-center justify-center" title="Neues Berichtsheft">
|
<button class="{{ style.card }} flex items-center justify-center" title="Neues Berichtsheft"
|
||||||
|
hx-get="/write" hx-target="#main_content" hx-push-url="true" hx-swap="innerHTML"
|
||||||
|
>
|
||||||
<span class="text-9xl font-bold"> + </span>
|
<span class="text-9xl font-bold"> + </span>
|
||||||
</a>
|
</button>
|
||||||
{% endif %} {% endif %}
|
{% endif %} {% endif %}
|
||||||
|
|
||||||
{% include 'component/report.html' with report=report %}
|
{% include 'component/report.html' with report=report %}
|
||||||
|
@ -50,5 +43,3 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -1,13 +1,4 @@
|
||||||
<html>
|
{{ title|safe }}
|
||||||
<head>
|
|
||||||
<title> Berichtsheft {{ report.num }} </title>
|
|
||||||
{% include 'head.html' %}
|
|
||||||
</head>
|
|
||||||
<body class="bg-gray-50 text-gray-800 font-sans min-h-screen flex flex-col items-center">
|
|
||||||
|
|
||||||
{% with title="Berichtsheft "|add:report.num %}
|
|
||||||
{% include 'header.html' with title="Berichtsheft" %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
<main class="flex-grow w-full max-w-4xl mx-auto p-6 bg-white shadow-lg rounded-lg mt-6">
|
<main class="flex-grow w-full max-w-4xl mx-auto p-6 bg-white shadow-lg rounded-lg mt-6">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
|
@ -25,14 +16,13 @@
|
||||||
<div class="border-t border-gray-200 mt-4 pt-4">
|
<div class="border-t border-gray-200 mt-4 pt-4">
|
||||||
<h2 class="text-lg font-medium text-gray-800 mb-2">Content:</h2>
|
<h2 class="text-lg font-medium text-gray-800 mb-2">Content:</h2>
|
||||||
<dl class="space-y-2">
|
<dl class="space-y-2">
|
||||||
|
{% load markdown %}
|
||||||
{% for key, value in report.content.items %}
|
{% for key, value in report.content.items %}
|
||||||
<div class="flex justify-between items-start">
|
<div class="justify-between items-start">
|
||||||
<dt class="text-sm font-medium text-gray-600">{{ key }}</dt>
|
<h4 class="text-sm font-medium text-gray-600">{{ key }}</h4>
|
||||||
<dd class="text-sm text-gray-800">{{ value }}</dd>
|
<span class="text-sm text-gray-800 mb-4">{{ value|markdown|safe }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
67
core/templates/shell.html
Normal file
67
core/templates/shell.html
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title id="page_title">{{ title }}</title>
|
||||||
|
{% include 'head.html' %}
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-50 text-gray-800 min-h-screen">
|
||||||
|
|
||||||
|
<!-- Main wrapper that will hold the header, sidebar, and content -->
|
||||||
|
<div class="flex flex-col min-h-screen">
|
||||||
|
<header class="w-full bg-red-600 text-white flex py-4 shadow-md max-h-20 h-full max-h-20">
|
||||||
|
<button id="menu-toggle" class="text-2xl focus:outline-none ml-5">
|
||||||
|
☰
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<h1 class="text-2xl font-bold {% if center is not None and center %} text-center {% else %}ml-5{% endif %} flex-1" id="page_banner_title">{{ title }}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main content container with sidebar and main content -->
|
||||||
|
<div class="flex flex-1">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div id="sidepanel" class="w-0 transition-all duration-250 ease-in-out bg-red-600 shadow-md text-white">
|
||||||
|
<ul class="space-y-2 font-medium">
|
||||||
|
<li>
|
||||||
|
<button href="#" onclick="toggleSidepanel()" class="rounded-lg lg:bg-transparent flex w-full items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-red-100 dark:hover:bg-red-700 group"
|
||||||
|
hx-get="/" hx-target="#main_content" hx-push-url="true" hx-swap="innerHTML"
|
||||||
|
>
|
||||||
|
<span class="ms-3">Home</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main content -->
|
||||||
|
<div class="flex-1 p-4" id="main_content">
|
||||||
|
{{ main_content|safe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Script to toggle the sidebar -->
|
||||||
|
<script>
|
||||||
|
const menuToggle = document.getElementById('menu-toggle');
|
||||||
|
const sidepanel = document.getElementById('sidepanel');
|
||||||
|
|
||||||
|
function toggleSidepanel() {
|
||||||
|
if (sidepanel.classList.contains('w-0')) {
|
||||||
|
sidepanel.classList.remove('w-0');
|
||||||
|
sidepanel.classList.add('w-60');
|
||||||
|
sidepanel.classList.add('p-4');
|
||||||
|
sidepanel.querySelectorAll('button').forEach(button => {
|
||||||
|
button.classList.add('bg-red-700');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sidepanel.classList.add('w-0');
|
||||||
|
sidepanel.classList.remove('w-60');
|
||||||
|
sidepanel.classList.remove('p-4');
|
||||||
|
sidepanel.querySelectorAll('button').forEach(button => {
|
||||||
|
button.classList.remove('bg-red-700');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menuToggle.addEventListener('click', () => toggleSidepanel());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,13 +1,7 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Neues Berichtsheft</title>
|
|
||||||
{% include 'head.html' %}
|
|
||||||
</head>
|
|
||||||
<body class="bg-gray-50 text-gray-800 font-sans min-h-screen flex flex-col items-center">
|
|
||||||
|
|
||||||
{% include 'header.html' with title="Neues Berichtsheft" %}
|
|
||||||
|
|
||||||
<main class="flex-grow w-full max-w-4xl mx-auto p-6 bg-white shadow-lg rounded-lg mt-6">
|
<main class="flex-grow w-full max-w-4xl mx-auto p-6 bg-white shadow-lg rounded-lg mt-6">
|
||||||
|
|
||||||
|
{{ title|safe }}
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<p class="text-lg font-medium">{{ user.display_name }}</p>
|
<p class="text-lg font-medium">{{ user.display_name }}</p>
|
||||||
<p class="text-gray-600">
|
<p class="text-gray-600">
|
||||||
|
@ -20,8 +14,7 @@
|
||||||
|
|
||||||
<form method="post" class="space-y-4">
|
<form method="post" class="space-y-4">
|
||||||
|
|
||||||
{% load set_content %}
|
{% load access set_content %}
|
||||||
{% load access %}
|
|
||||||
|
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
|
@ -45,6 +38,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="w-full bg-red-600 text-white py-2 px-4 rounded-lg shadow hover:bg-red-700 focus:ring focus:ring-red-400">
|
class="w-full bg-red-600 text-white py-2 px-4 rounded-lg shadow hover:bg-red-700 focus:ring focus:ring-red-400">
|
||||||
|
@ -127,5 +121,3 @@ setupListeners();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
10
core/templatetags/markdown.py
Normal file
10
core/templatetags/markdown.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
from django import template
|
||||||
|
import markdown
|
||||||
|
import bleach
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="markdown")
|
||||||
|
def markdown_tag(value):
|
||||||
|
return markdown.markdown(bleach.clean(value, tags=[], attributes=[]))
|
36
core/util.py
36
core/util.py
|
@ -1,5 +1,9 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
|
||||||
def next_date(year: int, week: int) -> (int, int):
|
def next_date(year: int, week: int) -> (int, int):
|
||||||
if week >= 52:
|
if week >= 52:
|
||||||
|
@ -30,3 +34,35 @@ def get_week_range(p_year, p_week):
|
||||||
).date()
|
).date()
|
||||||
lastdayofweek = firstdayofweek + datetime.timedelta(days=6.9)
|
lastdayofweek = firstdayofweek + datetime.timedelta(days=6.9)
|
||||||
return firstdayofweek, lastdayofweek
|
return firstdayofweek, lastdayofweek
|
||||||
|
|
||||||
|
|
||||||
|
def is_htmx_request(request) -> bool:
|
||||||
|
return request.headers.get("HX-Request") is not None
|
||||||
|
|
||||||
|
|
||||||
|
def title(t):
|
||||||
|
return f"""
|
||||||
|
<script>
|
||||||
|
document.getElementById('page_title').innerHTML = '{t}';
|
||||||
|
document.getElementById('page_banner_title').innerHTML = '{t}';
|
||||||
|
</script>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def htmx_request(request, content_template, vars, page_title):
|
||||||
|
vars["title"] = title(page_title)
|
||||||
|
vars["htmx"] = is_htmx_request(request)
|
||||||
|
|
||||||
|
main_content = render_to_string(content_template, vars, request=request)
|
||||||
|
|
||||||
|
if is_htmx_request(request):
|
||||||
|
return HttpResponse(main_content, content_type="text/html")
|
||||||
|
else:
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"shell.html",
|
||||||
|
{
|
||||||
|
"title": page_title,
|
||||||
|
"main_content": main_content,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
|
@ -5,7 +5,9 @@ from core.util import get_week_range, next_date
|
||||||
from .azure_auth import AzureUser
|
from .azure_auth import AzureUser
|
||||||
from .models import Berichtsheft
|
from .models import Berichtsheft
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
from django.template.loader import render_to_string
|
||||||
from core.styles import STYLE
|
from core.styles import STYLE
|
||||||
|
from .util import is_htmx_request, title, htmx_request
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
@ -91,7 +93,7 @@ def write_new_report_get(request):
|
||||||
|
|
||||||
form = user.get_report_kind_form()
|
form = user.get_report_kind_form()
|
||||||
|
|
||||||
return render(
|
return htmx_request(
|
||||||
request,
|
request,
|
||||||
"write.html",
|
"write.html",
|
||||||
{
|
{
|
||||||
|
@ -105,6 +107,7 @@ def write_new_report_get(request):
|
||||||
"form": form,
|
"form": form,
|
||||||
"draft": draft,
|
"draft": draft,
|
||||||
},
|
},
|
||||||
|
"Neues Berichtsheft",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,7 +119,7 @@ def index(request):
|
||||||
|
|
||||||
year_now, week_now, _ = datetime.datetime.today().isocalendar()
|
year_now, week_now, _ = datetime.datetime.today().isocalendar()
|
||||||
|
|
||||||
return render(
|
return htmx_request(
|
||||||
request,
|
request,
|
||||||
"index.html",
|
"index.html",
|
||||||
{
|
{
|
||||||
|
@ -126,6 +129,7 @@ def index(request):
|
||||||
"late_reports": user.late_reports(),
|
"late_reports": user.late_reports(),
|
||||||
"style": STYLE,
|
"style": STYLE,
|
||||||
},
|
},
|
||||||
|
"Berichtshefte",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -159,7 +163,9 @@ def report_detail_page(request, report_id):
|
||||||
if report.user != user.id:
|
if report.user != user.id:
|
||||||
return HttpResponse("Nah", status=401)
|
return HttpResponse("Nah", status=401)
|
||||||
|
|
||||||
return render(request, "report.html", {"report": report})
|
return htmx_request(
|
||||||
|
request, "report.html", {"report": report}, f"Berichtsheft {report.num}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
Django~=4.2.11
|
Django~=4.2.11
|
||||||
psycopg2
|
psycopg2
|
||||||
|
markdown
|
||||||
|
bleach
|
Loading…
Add table
Reference in a new issue