229 lines
6.9 KiB
Svelte
229 lines
6.9 KiB
Svelte
|
|
<script lang="ts">
|
|||
|
|
import { onMount } from 'svelte';
|
|||
|
|
import { page } from '$app/stores';
|
|||
|
|
import { api, type Employee, type MonthlyReport, type EmployeeReportLine } from '$lib/api/client';
|
|||
|
|
|
|||
|
|
let token = '';
|
|||
|
|
let employees: Employee[] = [];
|
|||
|
|
let report: MonthlyReport | null = null;
|
|||
|
|
let selectedEmployee: EmployeeReportLine | null = null;
|
|||
|
|
let loading = true;
|
|||
|
|
let selectedMonth = new Date().toISOString().slice(0, 7);
|
|||
|
|
|
|||
|
|
// Modal state
|
|||
|
|
let showModal = false;
|
|||
|
|
let editId: number | null = null;
|
|||
|
|
let formFirstName = '';
|
|||
|
|
let formLastName = '';
|
|||
|
|
|
|||
|
|
$: token = $page.url.searchParams.get('token') ?? '';
|
|||
|
|
|
|||
|
|
onMount(async () => {
|
|||
|
|
if (!token) return;
|
|||
|
|
await loadData();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
async function loadData() {
|
|||
|
|
loading = true;
|
|||
|
|
[employees, report] = await Promise.all([
|
|||
|
|
api.companyAdmin.getEmployees(token),
|
|||
|
|
api.companyAdmin.getReport(token, selectedMonth)
|
|||
|
|
]);
|
|||
|
|
loading = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function changeMonth() {
|
|||
|
|
report = await api.companyAdmin.getReport(token, selectedMonth);
|
|||
|
|
selectedEmployee = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function openCreate() {
|
|||
|
|
editId = null;
|
|||
|
|
formFirstName = '';
|
|||
|
|
formLastName = '';
|
|||
|
|
showModal = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function openEdit(emp: Employee) {
|
|||
|
|
editId = emp.id;
|
|||
|
|
formFirstName = emp.firstName;
|
|||
|
|
formLastName = emp.lastName;
|
|||
|
|
showModal = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function saveEmployee() {
|
|||
|
|
if (editId) {
|
|||
|
|
await api.companyAdmin.updateEmployee(token, editId, formFirstName, formLastName);
|
|||
|
|
} else {
|
|||
|
|
await api.companyAdmin.createEmployee(token, formFirstName, formLastName);
|
|||
|
|
}
|
|||
|
|
showModal = false;
|
|||
|
|
await loadData();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function toggleEmployee(id: number) {
|
|||
|
|
await api.companyAdmin.toggleEmployee(token, id);
|
|||
|
|
await loadData();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function showEmployeeDetail(employeeId: number) {
|
|||
|
|
selectedEmployee = await api.companyAdmin.getEmployeeReport(token, employeeId, selectedMonth);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function formatPrice(cents: number): string {
|
|||
|
|
return (cents / 100).toFixed(2).replace('.', ',') + ' €';
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<svelte:head>
|
|||
|
|
<title>Firmen-Admin - Strichliste</title>
|
|||
|
|
</svelte:head>
|
|||
|
|
|
|||
|
|
<div class="admin-container">
|
|||
|
|
<div class="admin-header">
|
|||
|
|
<h1>Firmen-Administration</h1>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{#if !token}
|
|||
|
|
<p>Kein Zugangstoken angegeben.</p>
|
|||
|
|
{:else if loading}
|
|||
|
|
<p>Laden...</p>
|
|||
|
|
{:else}
|
|||
|
|
<!-- Mitarbeiterverwaltung -->
|
|||
|
|
<section style="margin-bottom: 40px;">
|
|||
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
|||
|
|
<h2>Mitarbeiter</h2>
|
|||
|
|
<button class="btn btn-primary" on:click={openCreate}>+ Mitarbeiter anlegen</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<table class="admin-table">
|
|||
|
|
<thead>
|
|||
|
|
<tr>
|
|||
|
|
<th>Name</th>
|
|||
|
|
<th>Status</th>
|
|||
|
|
<th>Aktionen</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
{#each employees as emp}
|
|||
|
|
<tr>
|
|||
|
|
<td>{emp.firstName} {emp.lastName}</td>
|
|||
|
|
<td>
|
|||
|
|
<span class="badge" class:badge-active={emp.active} class:badge-inactive={!emp.active}>
|
|||
|
|
{emp.active ? 'Aktiv' : 'Inaktiv'}
|
|||
|
|
</span>
|
|||
|
|
</td>
|
|||
|
|
<td>
|
|||
|
|
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem;" on:click={() => openEdit(emp)}>Bearbeiten</button>
|
|||
|
|
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem; margin-left: 8px;" on:click={() => toggleEmployee(emp.id)}>
|
|||
|
|
{emp.active ? 'Deaktivieren' : 'Aktivieren'}
|
|||
|
|
</button>
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
{/each}
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<!-- Monatsauswertung -->
|
|||
|
|
<section>
|
|||
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
|||
|
|
<h2>Monatsauswertung</h2>
|
|||
|
|
<div class="month-selector">
|
|||
|
|
<input type="month" bind:value={selectedMonth} on:change={changeMonth} />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{#if report}
|
|||
|
|
<table class="admin-table">
|
|||
|
|
<thead>
|
|||
|
|
<tr>
|
|||
|
|
<th>Mitarbeiter</th>
|
|||
|
|
<th style="text-align: right;">Anzahl</th>
|
|||
|
|
<th style="text-align: right;">Summe</th>
|
|||
|
|
<th>Detail</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
{#each report.employees as line}
|
|||
|
|
<tr>
|
|||
|
|
<td>{line.firstName} {line.lastName}</td>
|
|||
|
|
<td style="text-align: right;">{line.totalCount}×</td>
|
|||
|
|
<td style="text-align: right;" class="price">{formatPrice(line.totalCents)}</td>
|
|||
|
|
<td>
|
|||
|
|
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem;" on:click={() => showEmployeeDetail(line.employeeId)}>Details</button>
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
{/each}
|
|||
|
|
{#if report.employees.length > 0}
|
|||
|
|
<tr style="font-weight: 700;">
|
|||
|
|
<td>Gesamt</td>
|
|||
|
|
<td></td>
|
|||
|
|
<td style="text-align: right;" class="price">{formatPrice(report.totalCents)}</td>
|
|||
|
|
<td></td>
|
|||
|
|
</tr>
|
|||
|
|
{:else}
|
|||
|
|
<tr><td colspan="4" style="text-align: center; color: var(--color-text-muted);">Keine Einträge in diesem Monat</td></tr>
|
|||
|
|
{/if}
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
{/if}
|
|||
|
|
|
|||
|
|
{#if selectedEmployee}
|
|||
|
|
<div style="margin-top: 24px; background: var(--color-bg-secondary); padding: 20px; border-radius: var(--radius);">
|
|||
|
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|||
|
|
<h3>Detail: {selectedEmployee.firstName} {selectedEmployee.lastName}</h3>
|
|||
|
|
<button class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem;" on:click={() => selectedEmployee = null}>Schließen</button>
|
|||
|
|
</div>
|
|||
|
|
<table class="admin-table" style="margin-top: 12px;">
|
|||
|
|
<thead>
|
|||
|
|
<tr>
|
|||
|
|
<th>Produkt</th>
|
|||
|
|
<th style="text-align: right;">Einzelpreis</th>
|
|||
|
|
<th style="text-align: right;">Anzahl</th>
|
|||
|
|
<th style="text-align: right;">Summe</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
{#each selectedEmployee.products as prod}
|
|||
|
|
<tr>
|
|||
|
|
<td>{prod.productName}</td>
|
|||
|
|
<td style="text-align: right;" class="price">{formatPrice(prod.priceCents)}</td>
|
|||
|
|
<td style="text-align: right;">{prod.count}×</td>
|
|||
|
|
<td style="text-align: right;" class="price">{formatPrice(prod.totalCents)}</td>
|
|||
|
|
</tr>
|
|||
|
|
{/each}
|
|||
|
|
<tr style="font-weight: 700;">
|
|||
|
|
<td>Gesamt</td>
|
|||
|
|
<td></td>
|
|||
|
|
<td style="text-align: right;">{selectedEmployee.totalCount}×</td>
|
|||
|
|
<td style="text-align: right;" class="price">{formatPrice(selectedEmployee.totalCents)}</td>
|
|||
|
|
</tr>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
{/if}
|
|||
|
|
</section>
|
|||
|
|
{/if}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{#if showModal}
|
|||
|
|
<div class="modal-overlay" on:click|self={() => showModal = false}>
|
|||
|
|
<div class="modal">
|
|||
|
|
<h2>{editId ? 'Mitarbeiter bearbeiten' : 'Neuer Mitarbeiter'}</h2>
|
|||
|
|
<div class="form-group">
|
|||
|
|
<label for="firstName">Vorname</label>
|
|||
|
|
<input id="firstName" type="text" bind:value={formFirstName} />
|
|||
|
|
</div>
|
|||
|
|
<div class="form-group">
|
|||
|
|
<label for="lastName">Nachname</label>
|
|||
|
|
<input id="lastName" type="text" bind:value={formLastName} />
|
|||
|
|
</div>
|
|||
|
|
<div class="modal-actions">
|
|||
|
|
<button class="btn btn-secondary" on:click={() => showModal = false}>Abbrechen</button>
|
|||
|
|
<button class="btn btn-primary" on:click={saveEmployee}>Speichern</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
{/if}
|