school-timetracker/frontend/src/components/AdminDashboard.svelte

388 lines
16 KiB
Svelte

<script>
import { auth } from "../lib/stores";
import { logout } from "../lib/api";
import AdminScheduleTab from "./admin/AdminScheduleTab.svelte";
import AdminUsersTab from "./admin/AdminUsersTab.svelte";
import AdminTimeEntriesTab from "./admin/AdminTimeEntriesTab.svelte";
import AdminSchoolYearsTab from "./admin/AdminSchoolYearsTab.svelte";
import AdminSettingsTab from "./admin/AdminSettingsTab.svelte";
import AdminSubstitutionsTab from "./admin/AdminSubstitutionsTab.svelte";
let activeTab = "schedule";
const user = $auth.user;
$: pageTitle = getPageTitle(activeTab);
function getPageTitle(tab) {
switch (tab) {
case "schedule":
return "Stundenplan Konfiguration";
case "users":
return "Benutzerverwaltung";
case "timeEntries":
return "Zeiteinträge & Buchungen";
case "schoolYears":
return "Schuljahre & Perioden";
case "settings":
return "Einstellungen";
case "substitutions":
return "Vertretungen";
default:
return "Admin";
}
}
let isDrawerOpen = false;
function closeDrawer() {
isDrawerOpen = false;
}
</script>
<div class="drawer lg:drawer-open">
<input
id="admin-drawer"
type="checkbox"
class="drawer-toggle"
bind:checked={isDrawerOpen}
/>
<div class="drawer-content flex flex-col bg-base-200 min-h-screen">
<div
class="navbar bg-base-100 shadow-sm border-b border-base-200 sticky top-0 z-30 px-4 sm:px-8"
>
<div class="flex-none lg:hidden">
<label for="admin-drawer" class="btn btn-square btn-ghost">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="inline-block w-6 h-6 stroke-current"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
></path></svg
>
</label>
</div>
<div class="flex-1">
<div class="text-sm breadcrumbs hidden sm:block">
<ul>
<li class="opacity-50">Admin</li>
<li class="font-bold text-primary">{pageTitle}</li>
</ul>
</div>
<span class="font-bold text-lg sm:hidden">{pageTitle}</span>
</div>
<div class="flex-none flex items-center gap-4">
<div class="hidden sm:block text-right leading-tight">
<div class="font-bold text-sm">{$auth.user?.username}</div>
<div class="text-xs opacity-50">Administrator</div>
</div>
<div class="dropdown dropdown-end">
<div
tabindex="0"
role="button"
class="btn btn-ghost btn-circle avatar placeholder"
>
<div
class="bg-neutral text-neutral-content rounded-full w-10"
>
<span class="text-xl"
>{user?.username?.charAt(0).toUpperCase()}</span
>
</div>
</div>
<ul
tabindex="0"
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52"
>
<li>
<button
on:click={logout}
class="text-error font-bold">Abmelden</button
>
</li>
</ul>
</div>
</div>
</div>
<div class="p-4 md:p-8 lg:p-10 fade-in">
{#if activeTab === "schedule"}
<AdminScheduleTab />
{:else if activeTab === "users"}
<AdminUsersTab />
{:else if activeTab === "timeEntries"}
<AdminTimeEntriesTab />
{:else if activeTab === "schoolYears"}
<AdminSchoolYearsTab />
{:else if activeTab === "settings"}
<AdminSettingsTab />
{:else if activeTab === "substitutions"}
<AdminSubstitutionsTab />
{/if}
</div>
</div>
<div class="drawer-side z-40">
<label for="admin-drawer" class="drawer-overlay"></label>
<aside
class="bg-base-100 w-80 h-full flex flex-col border-r border-base-300"
>
<div class="p-6 border-b border-base-200">
<div class="flex items-center gap-3">
<div class="w-10 h-10 flex items-center justify-center">
<img
src="/api/logo?t={Date.now()}"
alt="Logo"
class="w-full h-full object-contain"
on:error={(e) => {
e.target.style.display = "none";
e.target.nextElementSibling.style.display =
"flex";
}}
/>
<div
class="hidden w-10 h-10 rounded bg-primary text-primary-content font-bold text-xl items-center justify-center"
>
Z
</div>
</div>
<div class="font-bold text-xl tracking-tight">
Zeiterfassung
</div>
</div>
<div class="text-xs font-mono opacity-50 mt-1 pl-14">
Admin Dashboard
</div>
</div>
<ul class="menu p-4 w-full gap-2 text-base font-medium flex-1">
<li
class="menu-title opacity-50 uppercase text-xs font-bold tracking-wider mt-2 mb-1"
>
Verwaltung
</li>
<li>
<button
class={activeTab === "schedule"
? "active bg-primary/10 text-primary"
: ""}
on:click={() => {
activeTab = "schedule";
closeDrawer();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
><path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5"
/></svg
>
Stundenplan
</button>
</li>
<li>
<button
class={activeTab === "users"
? "active bg-primary/10 text-primary"
: ""}
on:click={() => {
activeTab = "users";
closeDrawer();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
><path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"
/></svg
>
Benutzer
</button>
</li>
<li
class="menu-title opacity-50 uppercase text-xs font-bold tracking-wider mt-4 mb-1"
>
Daten
</li>
<li>
<button
class={activeTab === "timeEntries"
? "active bg-primary/10 text-primary"
: ""}
on:click={() => {
activeTab = "timeEntries";
closeDrawer();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
><path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"
/></svg
>
Zeiteinträge
</button>
</li>
<li>
<button
class={activeTab === "schoolYears"
? "active bg-primary/10 text-primary"
: ""}
on:click={() => {
activeTab = "schoolYears";
closeDrawer();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
><path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5"
/></svg
>
Schuljahre
</button>
</li>
<li>
<button
class={activeTab === "substitutions"
? "active bg-primary/10 text-primary"
: ""}
on:click={() => {
activeTab = "substitutions";
closeDrawer();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
><path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5"
/></svg
>
Vertretungen
</button>
</li>
<li
class="menu-title opacity-50 uppercase text-xs font-bold tracking-wider mt-4 mb-1"
>
System
</li>
<li>
<button
class={activeTab === "settings"
? "active bg-primary/10 text-primary"
: ""}
on:click={() => {
activeTab = "settings";
closeDrawer();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
><path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"
/><path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/></svg
>
Einstellungen
</button>
</li>
</ul>
<div class="p-4 border-t border-base-200">
<button
on:click={logout}
class="btn btn-ghost btn-sm w-full justify-start text-error"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5 mr-2"
><path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9"
/></svg
>
Abmelden
</button>
</div>
</aside>
</div>
</div>
<style>
/* Sanfte Fade-In Animation für Tab-Wechsel */
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>