Initial commit

This commit is contained in:
Felix Baumann
2026-06-27 17:07:34 +00:00
commit fb47c53d52
256 changed files with 21270 additions and 0 deletions
+342
View File
@@ -0,0 +1,342 @@
{% load static tailwind_tags %}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
{% block title %}
Projektverwaltung
{% endblock %}
</title>
<script>
if (localStorage.getItem("theme") === "dark") {
document.documentElement.classList.add("dark");
}
</script>
{% tailwind_css %}
<style>
:root {
--company-color: {{ company_settings.color }};
}
</style>
</head>
<body class="min-h-screen bg-slate-100 dark:bg-slate-900 text-slate-900 dark:text-white transition-colors">
<div class="flex min-h-screen">
{% if user.is_authenticated %}
<div id="overlay"
class="fixed inset-0 bg-black/40 hidden z-30 lg:hidden"
onclick="toggleSidebar()"></div>
<!-- Sidebar -->
<aside id="sidebar" class="fixed top-0 z-40 w-72 h-screen bg-slate-800 text-white transform -translate-x-full lg:translate-x-0 transition-transform duration-300 flex flex-col border-r border-white/20">
<div class="p-6 border-b border-white/20 " style="background-color: {{ company_settings.color }};">
<h2 class="text-xl font-bold">
{{ company_settings.companyname|default:"Projektverwaltung" }}
</h2>
</div>
<nav class="flex-1 p-4 space-y-2">
<div class="w-full rounded-lg flex items-center justify-between px-4 py-3 hover:bg-slate-700">
<a href="{% url 'core:dashboard' %}">
Dashboard
</a>
</div>
<div class="space-y-2">
<!-- Projekt wechseln -->
<div class="rounded-lg overflow-hidden">
<button
onclick="toggleProjects()"
class="w-full flex items-center justify-between px-4 py-3 hover:bg-slate-700">
<span>{{ current_project.name|default:"Projekt wählen" }}</span>
<svg id="projectArrow"
class="w-4 h-4 transition-transform"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div id="projectMenu" class="hidden border-t border-slate-700">
{% for p in user_projects %}
<a href="{% url 'projects:overview' p.id %}"
class="block px-8 py-3 hover:bg-slate-700">
{{ p.name }}
</a>
{% endfor %}
</div>
</div>
<!-- Rechnungswesen -->
<div class="rounded-lg overflow-hidden">
<button
type="button"
onclick="toggleInvoicesMenu()"
class="w-full flex items-center justify-between px-4 py-3 hover:bg-slate-700 transition">
<span>
Rechnungswesen
</span>
<svg id="invoiceArrow"
class="w-4 h-4 transition-transform duration-200"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div id="invoiceMenu" class="hidden border-t border-slate-700">
<a href="{% url 'invoices:overview' %}"
class="block px-8 py-3 hover:bg-slate-700 transition">
Übersicht
</a>
<a href="{% url 'invoices:invoices_offers_overview' %}"
class="block px-8 py-3 hover:bg-slate-700 transition">
Angebote
</a>
<a href="{% url 'invoices:invoices_invoices_overview' %}"
class="block px-8 py-3 hover:bg-slate-700 transition">
Rechnungen
</a>
<a href="{% url 'invoices:invoices_reminders_overview' %}"
class="block px-8 py-3 hover:bg-slate-700 transition">
Mahnungen
</a>
<a href="{% url 'invoices:customers_overview' %}"
class="block px-8 py-3 hover:bg-slate-700 transition">
Kunden
</a>
<a href="{% url 'invoices:items_overview' %}"
class="block px-8 py-3 hover:bg-slate-700 transition">
Posten
</a>
</div>
</div>
</div>
<!--
<div class="w-full rounded-lg flex items-center justify-between px-4 py-3 hover:bg-slate-700">
<a href="{% url 'core:dashboard' %}">
Finanzübersicht
</a>
</div>
<div class="w-full rounded-lg flex items-center justify-between px-4 py-3 hover:bg-slate-700">
<a href="{% url 'core:dashboard' %}">
Arbeitszeiten
</a>
</div>
-->
<div class="w-full rounded-lg flex items-center justify-between px-4 py-3 hover:bg-slate-700">
<a href="{% url 'inventory_global:overview' %}">
Inventar
</a>
</div>
<!--
<div class="w-full rounded-lg flex items-center justify-between px-4 py-3 hover:bg-slate-700">
<a href="{% url 'core:dashboard' %}">
Investitionen
</a>
</div>
-->
<div class="w-full rounded-lg flex items-center justify-between px-4 py-3 hover:bg-slate-700">
<a href="{% url 'accounts:overview' %}">
Mitarbeiter
</a>
</div>
<div class="w-full rounded-lg flex items-center justify-between px-4 py-3 hover:bg-slate-700">
<a href="{% url 'masterdata:overview' %}">
Einstellungen
</a>
</div>
</nav>
<div class="p-4 border-t border-white/20">
{% if user.is_superuser %}
<div class="w-full rounded-xl flex items-center justify-between px-4 py-3 hover:bg-slate-700 mb-4 border-white/20 border">
<a href="{% url 'admin:index' %}">Admin</a>
</div>
{% endif %}
<div class="flex gap-2 w-full">
<button id="theme-toggle" class="rounded-xl px-4 py-3 hover:bg-slate-700 border-white/20 border">🌙</button>
<form method="post" action="{% url 'accounts:logout' %}" class="w-full">
{% csrf_token %}
<button class="w-full rounded-xl px-4 py-3 border-white/20 border" style="background-color: {{ company_settings.color }};">Abmelden</button>
</form>
</div>
</div>
</aside>
{% endif %}
<!-- Hauptbereich -->
{% if user.is_authenticated %}
<div class="flex-1 flex flex-col lg:ml-72">
{% else %}
<div class="flex-1 flex flex-col">
{% endif %}
<header class="lg:hidden bg-white shadow h-16 flex items-center px-4 dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
<button onclick="toggleSidebar()" class="text-3xl">☰</button>
<span class="ml-4 font-bold">{{ company_settings.companyname|default:"Projektverwaltung" }}</span>
</header>
<main class="flex-1 p-6 overflow-x-auto w-screen lg:w-auto">
<div class="p-6 rounded-xl shadow mb-8 bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
<h1 class="text-3xl font-bold">
{% block header_title %}
{% endblock %}
</h1>
<p class="text-slate-400 mt-1">
{% block header_description %}
{% endblock %}
</p>
</div>
{% block content %}
{% endblock %}
</main>
</div>
</div>
{% if messages %}
<div class="fixed top-5 right-5 z-50 space-y-3">
{% for message in messages %}
<div
class="rounded-xl shadow-lg px-5 py-4 flex items-center gap-4
{% if message.tags == 'success' %}
bg-green-100 border border-green-300 text-green-800
{% elif message.tags == 'error' %}
bg-red-100 border border-red-300 text-red-800
{% elif message.tags == 'warning' %}
bg-yellow-100 border border-yellow-300 text-yellow-800
{% else %}
bg-blue-100 border border-blue-300 text-blue-800
{% endif %}
"
>
<div>
{{ message }}
</div>
</div>
{% endfor %}
</div>
<script>
setTimeout(() => {
document
.querySelectorAll(".fixed.top-5.right-5 > div")
.forEach(el => {
el.style.opacity = "0";
setTimeout(() => {
el.remove();
}, 500);
});
}, 3000);
</script>
{% endif %}
<script>
function toggleProjects() {
const menu = document.getElementById("projectMenu");
const arrow = document.getElementById("projectArrow");
menu.classList.toggle("hidden");
arrow.classList.toggle("rotate-180");
}
function toggleInvoicesMenu() {
const menu = document.getElementById("invoiceMenu");
const arrow = document.getElementById("invoiceArrow");
menu.classList.toggle("hidden");
arrow.classList.toggle("rotate-180");
}
function toggleSidebar() {
const sidebar = document.getElementById("sidebar");
const overlay = document.getElementById("overlay");
sidebar.classList.toggle("-translate-x-full");
overlay.classList.toggle("hidden");
}
</script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const btn = document.getElementById("theme-toggle");
const html = document.documentElement;
// Gespeichertes Theme anwenden
const saved = localStorage.getItem("theme");
if (saved === "dark") {
html.classList.add("dark");
} else {
html.classList.remove("dark");
}
// Toggle
btn.addEventListener("click", () => {
html.classList.toggle("dark");
localStorage.setItem("theme", html.classList.contains("dark") ? "dark" : "light");
btn.textContent = html.classList.contains("dark") ? "☀️" : "🌙";
});
// Icon initial setzen
btn.textContent = html.classList.contains("dark") ? "☀️" : "🌙";
});
</script>
</body>
</html>
+114
View File
@@ -0,0 +1,114 @@
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block header_title %}
Dashboard
{% endblock %}
{% block header_description %}
Willkommen {{ user.username }}
{% endblock %}
{% block content %}
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-4 mb-8">
<a href="{% url 'invoices:overview' %}" class="border border-white/20 p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
<div class="flex items-center gap-3 justify-center">
<h2 class="text-xl font-bold">Rechnungswesen</h2>
</div>
</a>
<!--
<a href="" class="border border-white/20 p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
<div class="flex items-center gap-3 justify-center">
<h2 class="text-xl font-bold">Finanzübersicht</h2>
</div>
</a>
<a href="" class="border border-white/20 p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
<div class="flex items-center gap-3 justify-center">
<h2 class="text-xl font-bold">Arbeitszeiten</h2>
</div>
</a> -->
<a href="{% url 'inventory_global:overview' %}" class="border border-white/20 p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
<div class="flex items-center gap-3 justify-center">
<h2 class="text-xl font-bold">Inventar</h2>
</div>
</a>
<!--
<a href="" class="border border-white/20 p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
<div class="flex items-center gap-3 justify-center">
<h2 class="text-xl font-bold">Investitionen</h2>
</div>
</a>
-->
<a href="{% url 'accounts:overview' %}" class="border border-white/20 p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
<div class="flex items-center gap-3 justify-center">
<h2 class="text-xl font-bold">Mitarbeiter</h2>
</div>
</a>
<a href="{% url 'masterdata:overview' %}" class="border border-white/20 p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
<div class="flex items-center gap-3 justify-center">
<h2 class="text-xl font-bold">Einstellungen</h2>
</div>
</a>
</div>
<div class="mb-8">
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
<div>
<h2 class="text-xl font-bold">Aktionen</h2>
</div>
<div class="flex flex-col sm:flex-row gap-2">
<a href="{% url 'projects:add' %}" class="btn btn-add">+ Projekt</a>
</div>
</div>
</div>
</div>
{% if memberships %}
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
{% for m in memberships %}
<a href="{% url 'projects:overview' m.project.id %}"
class="p-6 rounded-xl shadow hover:shadow-lg transition block text-white"
style="background: linear-gradient(135deg, {{ m.project.color }}, {{ m.project.color }}ff);">
<h2 class="text-xl font-bold">
{{ m.project.name }}
</h2>
<p class="text-sm mt-2">
Rolle: {{ m.get_role_display }}
</p>
</a>
{% endfor %}
</div>
{% else %}
<div class="bg-white dark:bg-slate-800 border border-orange-500 rounded-xl p-6 text-orange-500">
Du bist noch keinem aktiven Projekt zugewiesen.
</div>
{% endif %}
{% if archived_count %}
<a href="{% url 'projects:archived' %}" class="text-sm text-slate-400 hover:text-slate-500">
{{ archived_count }} archivierte
{% if archived_count == 1 %}
Projekt anzeigen
{% else %}
Projekte anzeigen
{% endif %}
</a>
{% endif %}
{% endblock %}
+47
View File
@@ -0,0 +1,47 @@
{% load django_tables2 %}
<table {% render_attrs table.attrs %}>
<thead class="bg-slate-100 dark:bg-slate-700 text-left">
<tr>
{% for column in table.columns %}
<th class="p-3">
{{ column.header }}
</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in table.paginated_rows %}
<tr class="border-t">
{% for column, cell in row.items %}
<td class="p-3">
{{ cell }}
</td>
{% endfor %}
</tr>
{% empty %}
<tr class="border-t">
<td colspan="{{ table.columns|length }}"
class="p-6 text-center text-slate-400">
Keine Daten vorhanden
</td>
</tr>
{% endfor %}
</tbody>
</table>
@@ -0,0 +1,95 @@
{% load django_tables2 %}
<div class="overflow-x-auto rounded-xl">
<table {% render_attrs table.attrs %}>
<thead class="bg-slate-100 dark:bg-slate-700 text-left">
<tr>
<th class="p-3"></th>
{% for column in table.columns %}
<th class="p-3">
{{ column.header }}
</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in table.paginated_rows %}
<tr class="border-t">
<td class="p-3">
<button
type="button"
onclick="toggleItem('{{ table.toggle_prefix }}-{{ row.record.id }}')"
class="font-bold">
▼
</button>
</td>
{% for column, cell in row.items %}
<td class="p-3">
{{ cell }}
</td>
{% endfor %}
</tr>
{% if table.detail_template %}
<tr id="{{ table.toggle_prefix }}-{{ row.record.id }}"
class="hidden border-t">
<td colspan="{{ table.columns|length|add:'1' }}"
class="p-6">
{% include table.detail_template with item=row.record categories=table.categories locations=table.locations users=table.users %}
</td>
</tr>
{% endif %}
{% empty %}
<tr>
<td colspan="{{ table.columns|length|add:1 }}"
class="p-6 text-center text-slate-400">
Keine Daten vorhanden
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script>
function toggleItem(id) {
const element = document.getElementById(id);
if (!element) {
console.log("Nicht gefunden:", id);
return;
}
element.classList.toggle("hidden");
}
</script>