Durchführender: Verfügbarkeit pro Termin, Umbenennung Trainer→Durchführender
This commit is contained in:
parent
59aa3cbcce
commit
c23ac90d35
4 changed files with 213 additions and 117 deletions
|
|
@ -43,6 +43,7 @@ export interface Mitglied {
|
|||
}
|
||||
|
||||
export type Rolle = 'admin' | 'trainer';
|
||||
export type Verfuegbarkeit = 'offen' | 'bestaetigt' | 'abgesagt' | 'vertretung_gesucht';
|
||||
|
||||
export interface Gruppe {
|
||||
id: string;
|
||||
|
|
@ -87,6 +88,8 @@ export interface Termin {
|
|||
ende?: string;
|
||||
ort?: string;
|
||||
gruppe_ids: string[];
|
||||
durchfuehrender_id?: string;
|
||||
verfuegbarkeit?: Verfuegbarkeit;
|
||||
}
|
||||
|
||||
export interface Nachricht {
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@
|
|||
|
||||
{#if isAdmin()}
|
||||
<section>
|
||||
<h2>Trainer</h2>
|
||||
<h2>Durchführende</h2>
|
||||
{#if trainer.length === 0}
|
||||
<p class="sepa-hint">Noch keine Trainer eingeladen.</p>
|
||||
{:else}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { pb } from '$lib/pb';
|
||||
import { onMount } from 'svelte';
|
||||
import type { Termin, Gruppe } from '$lib/types';
|
||||
import type { Termin, Gruppe, Verfuegbarkeit } from '$lib/types';
|
||||
|
||||
const isAdmin = () => !pb.authStore.record?.rolle || pb.authStore.record?.rolle === 'admin';
|
||||
const userId = () => pb.authStore.record?.id as string;
|
||||
|
||||
let termine = $state<Termin[]>([]);
|
||||
let gruppen = $state<Gruppe[]>([]);
|
||||
let alleUser = $state<any[]>([]);
|
||||
let loading = $state(true);
|
||||
|
||||
// Formular
|
||||
|
|
@ -16,9 +20,9 @@
|
|||
let fEnde = $state('');
|
||||
let fOrt = $state('');
|
||||
let fGruppeIds = $state<string[]>([]);
|
||||
let fDurchfuehrenderId = $state('');
|
||||
let saving = $state(false);
|
||||
let formError = $state('');
|
||||
|
||||
let showDelete = $state<string | null>(null);
|
||||
|
||||
const now = new Date();
|
||||
|
|
@ -34,10 +38,19 @@
|
|||
.sort((a, b) => new Date(b.beginn).getTime() - new Date(a.beginn).getTime())
|
||||
);
|
||||
|
||||
// Admin-Warnung: Termine ohne Bestätigung
|
||||
const offene = $derived(
|
||||
upcoming.filter(t => !t.verfuegbarkeit || t.verfuegbarkeit === 'offen' || t.verfuegbarkeit === 'vertretung_gesucht')
|
||||
);
|
||||
|
||||
onMount(async () => {
|
||||
[termine, gruppen] = await Promise.all([
|
||||
const vid = pb.authStore.record?.verein_id as string;
|
||||
[termine, gruppen, alleUser] = await Promise.all([
|
||||
pb.collection('termine').getFullList<Termin>({ sort: 'beginn' }),
|
||||
pb.collection('gruppen').getFullList<Gruppe>({ sort: 'name' }),
|
||||
isAdmin()
|
||||
? pb.collection('users').getFullList({ filter: `verein_id = "${vid}" && rolle = "trainer"` })
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
loading = false;
|
||||
});
|
||||
|
|
@ -45,47 +58,38 @@
|
|||
function toLocal(iso: string | undefined): string {
|
||||
if (!iso) return '';
|
||||
const d = new Date(iso);
|
||||
// datetime-local format: YYYY-MM-DDTHH:MM
|
||||
return new Date(d.getTime() - d.getTimezoneOffset() * 60000).toISOString().slice(0, 16);
|
||||
}
|
||||
|
||||
function fromLocal(local: string): string {
|
||||
return local ? new Date(local).toISOString() : '';
|
||||
}
|
||||
|
||||
function neuerTermin() {
|
||||
editId = null;
|
||||
fTitel = ''; fBeschr = ''; fBeginn = ''; fEnde = ''; fOrt = ''; fGruppeIds = [];
|
||||
editId = null; fTitel = ''; fBeschr = ''; fBeginn = ''; fEnde = '';
|
||||
fOrt = ''; fGruppeIds = []; fDurchfuehrenderId = '';
|
||||
formError = ''; showForm = true;
|
||||
}
|
||||
|
||||
function bearbeiten(t: Termin) {
|
||||
editId = t.id;
|
||||
fTitel = t.titel;
|
||||
fBeschr = t.beschreibung ?? '';
|
||||
fBeginn = toLocal(t.beginn);
|
||||
fEnde = toLocal(t.ende);
|
||||
fOrt = t.ort ?? '';
|
||||
fGruppeIds = t.gruppe_ids ?? [];
|
||||
editId = t.id; fTitel = t.titel; fBeschr = t.beschreibung ?? '';
|
||||
fBeginn = toLocal(t.beginn); fEnde = toLocal(t.ende);
|
||||
fOrt = t.ort ?? ''; fGruppeIds = t.gruppe_ids ?? [];
|
||||
fDurchfuehrenderId = t.durchfuehrender_id ?? '';
|
||||
formError = ''; showForm = true;
|
||||
}
|
||||
|
||||
async function speichern() {
|
||||
if (!fTitel.trim() || !fBeginn) {
|
||||
formError = 'Titel und Beginn sind Pflichtfelder.';
|
||||
return;
|
||||
}
|
||||
if (!fTitel.trim() || !fBeginn) { formError = 'Titel und Beginn sind Pflichtfelder.'; return; }
|
||||
formError = ''; saving = true;
|
||||
try {
|
||||
const vid = pb.authStore.record?.verein_id as string;
|
||||
const data = {
|
||||
verein_id: vid,
|
||||
titel: fTitel.trim(),
|
||||
verein_id: vid, titel: fTitel.trim(),
|
||||
beschreibung: fBeschr.trim() || null,
|
||||
beginn: fromLocal(fBeginn),
|
||||
ende: fEnde ? fromLocal(fEnde) : null,
|
||||
ort: fOrt.trim() || null,
|
||||
gruppe_ids: fGruppeIds,
|
||||
beginn: fromLocal(fBeginn), ende: fEnde ? fromLocal(fEnde) : null,
|
||||
ort: fOrt.trim() || null, gruppe_ids: fGruppeIds,
|
||||
durchfuehrender_id: fDurchfuehrenderId || null,
|
||||
verfuegbarkeit: fDurchfuehrenderId ? 'offen' : null,
|
||||
};
|
||||
if (editId) {
|
||||
const updated = await pb.collection('termine').update<Termin>(editId, data);
|
||||
|
|
@ -108,6 +112,11 @@
|
|||
showDelete = null;
|
||||
}
|
||||
|
||||
async function setVerfuegbarkeit(t: Termin, v: Verfuegbarkeit) {
|
||||
const updated = await pb.collection('termine').update<Termin>(t.id, { verfuegbarkeit: v });
|
||||
termine = termine.map(x => x.id === t.id ? updated : x);
|
||||
}
|
||||
|
||||
function toggleGruppe(id: string) {
|
||||
fGruppeIds = fGruppeIds.includes(id)
|
||||
? fGruppeIds.filter(g => g !== id)
|
||||
|
|
@ -119,14 +128,27 @@
|
|||
weekday: 'short', day: '2-digit', month: 'long', year: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
function formatZeit(iso: string): string {
|
||||
return new Date(iso).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
function gruppenLabel(ids: string[]): string {
|
||||
if (!ids?.length) return '';
|
||||
return ids.map(id => gruppen.find(g => g.id === id)?.name).filter(Boolean).join(', ');
|
||||
return (ids ?? []).map(id => gruppen.find(g => g.id === id)?.name).filter(Boolean).join(', ');
|
||||
}
|
||||
function userName(uid: string | undefined): string {
|
||||
if (!uid) return '';
|
||||
const u = alleUser.find((u: any) => u.id === uid);
|
||||
return u?.name ?? '';
|
||||
}
|
||||
|
||||
const verfuegbarkeitConfig: Record<Verfuegbarkeit, { label: string; farbe: string }> = {
|
||||
offen: { label: 'Offen', farbe: '#f59e0b' },
|
||||
bestaetigt: { label: 'Bestätigt', farbe: '#16a34a' },
|
||||
abgesagt: { label: 'Abgesagt', farbe: '#dc2626' },
|
||||
vertretung_gesucht: { label: 'Vertretung gesucht', farbe: '#7c3aed' },
|
||||
};
|
||||
|
||||
function istMeinTermin(t: Termin): boolean {
|
||||
return t.durchfuehrender_id === userId();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -134,13 +156,21 @@
|
|||
|
||||
<div class="top">
|
||||
<h1>Termine</h1>
|
||||
{#if isAdmin()}
|
||||
<button class="btn-primary" onclick={neuerTermin}>+ Termin</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if isAdmin() && offene.length > 0}
|
||||
<div class="warnung">
|
||||
⚠ {offene.length} {offene.length === 1 ? 'Termin benötigt' : 'Termine benötigen'} eine Bestätigung
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if loading}
|
||||
<p class="hint">Laden…</p>
|
||||
{:else if termine.length === 0}
|
||||
<p class="hint">Noch keine Termine – lege den ersten an!</p>
|
||||
<p class="hint">Noch keine Termine geplant.</p>
|
||||
{:else}
|
||||
{#if upcoming.length > 0}
|
||||
<ul class="liste">
|
||||
|
|
@ -151,19 +181,58 @@
|
|||
<span class="tag-zahl">{new Date(t.beginn).getDate()}</span>
|
||||
<span class="monat">{new Date(t.beginn).toLocaleDateString('de-DE', { month: 'short' })}</span>
|
||||
</div>
|
||||
|
||||
<div class="karte-info">
|
||||
<span class="karte-titel">{t.titel}</span>
|
||||
<span class="karte-meta">
|
||||
{formatZeit(t.beginn)}{t.ende ? ' – ' + formatZeit(t.ende) : ''}{t.ort ? ' · ' + t.ort : ''}
|
||||
</span>
|
||||
{#if t.gruppe_ids?.length}
|
||||
<span class="karte-gruppen">{gruppenLabel(t.gruppe_ids)}</span>
|
||||
<span class="karte-sub">{gruppenLabel(t.gruppe_ids)}</span>
|
||||
{/if}
|
||||
|
||||
{#if t.durchfuehrender_id}
|
||||
<div class="verfueg-zeile">
|
||||
{#if t.verfuegbarkeit && t.verfuegbarkeit !== 'offen'}
|
||||
<span class="verfueg-badge" style="color:{verfuegbarkeitConfig[t.verfuegbarkeit].farbe}">
|
||||
● {verfuegbarkeitConfig[t.verfuegbarkeit].label}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="verfueg-badge" style="color:#f59e0b">● Offen</span>
|
||||
{/if}
|
||||
{#if isAdmin() && userName(t.durchfuehrender_id)}
|
||||
<span class="karte-sub">→ {userName(t.durchfuehrender_id)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if istMeinTermin(t)}
|
||||
<div class="meine-aktionen">
|
||||
<button
|
||||
class="btn-aktion bestaetigen"
|
||||
class:aktiv={t.verfuegbarkeit === 'bestaetigt'}
|
||||
onclick={() => setVerfuegbarkeit(t, 'bestaetigt')}
|
||||
>Ich bin dabei</button>
|
||||
<button
|
||||
class="btn-aktion absagen"
|
||||
class:aktiv={t.verfuegbarkeit === 'abgesagt'}
|
||||
onclick={() => setVerfuegbarkeit(t, 'abgesagt')}
|
||||
>Kann nicht</button>
|
||||
<button
|
||||
class="btn-aktion vertretung"
|
||||
class:aktiv={t.verfuegbarkeit === 'vertretung_gesucht'}
|
||||
onclick={() => setVerfuegbarkeit(t, 'vertretung_gesucht')}
|
||||
>Vertretung gesucht</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if isAdmin()}
|
||||
<div class="karte-aktionen">
|
||||
<button class="btn-icon" onclick={() => bearbeiten(t)} title="Bearbeiten">✎</button>
|
||||
<button class="btn-icon btn-icon-red" onclick={() => showDelete = t.id} title="Löschen">✕</button>
|
||||
</div>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
|
@ -184,9 +253,11 @@
|
|||
<span class="karte-titel">{t.titel}</span>
|
||||
<span class="karte-meta">{formatDatum(t.beginn)}{t.ort ? ' · ' + t.ort : ''}</span>
|
||||
</div>
|
||||
{#if isAdmin()}
|
||||
<div class="karte-aktionen">
|
||||
<button class="btn-icon btn-icon-red" onclick={() => showDelete = t.id} title="Löschen">✕</button>
|
||||
</div>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
|
@ -194,18 +265,16 @@
|
|||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- Termin-Formular -->
|
||||
{#if showForm}
|
||||
<!-- Termin-Formular (nur Admin) -->
|
||||
{#if showForm && isAdmin()}
|
||||
<div class="overlay" role="dialog" aria-modal="true">
|
||||
<div class="sheet">
|
||||
<h2>{editId ? 'Termin bearbeiten' : 'Neuer Termin'}</h2>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); speichern(); }}>
|
||||
<div class="field">
|
||||
<label for="ftitel">Titel *</label>
|
||||
<input id="ftitel" type="text" bind:value={fTitel} placeholder="z. B. Jahreshauptversammlung" required />
|
||||
<input id="ftitel" type="text" bind:value={fTitel} required />
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="field">
|
||||
<label for="fbeginn">Beginn *</label>
|
||||
|
|
@ -216,17 +285,23 @@
|
|||
<input id="fende" type="datetime-local" bind:value={fEnde} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="fort">Ort</label>
|
||||
<input id="fort" type="text" bind:value={fOrt} placeholder="z. B. Vereinsheim" />
|
||||
<input id="fort" type="text" bind:value={fOrt} />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="fbeschr">Beschreibung</label>
|
||||
<textarea id="fbeschr" bind:value={fBeschr} rows="3" placeholder="Optional"></textarea>
|
||||
<textarea id="fbeschr" bind:value={fBeschr} rows="2"></textarea>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="fdurch">Durchführender</label>
|
||||
<select id="fdurch" bind:value={fDurchfuehrenderId}>
|
||||
<option value="">— nicht zugewiesen —</option>
|
||||
{#each alleUser as u (u.id)}
|
||||
<option value={u.id}>{u.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{#if gruppen.length > 0}
|
||||
<div class="field">
|
||||
<span class="field-label">Für Gruppen</span>
|
||||
|
|
@ -240,23 +315,17 @@
|
|||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if formError}
|
||||
<p class="error">{formError}</p>
|
||||
{/if}
|
||||
|
||||
{#if formError}<p class="error">{formError}</p>{/if}
|
||||
<div class="actions">
|
||||
<button type="button" class="btn-ghost" onclick={() => showForm = false}>Abbrechen</button>
|
||||
<button type="submit" class="btn-primary" disabled={saving}>
|
||||
{saving ? 'Speichern…' : 'Speichern'}
|
||||
</button>
|
||||
<button type="submit" class="btn-primary" disabled={saving}>{saving ? 'Speichern…' : 'Speichern'}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Lösch-Bestätigung -->
|
||||
<!-- Lösch-Dialog -->
|
||||
{#if showDelete}
|
||||
<div class="overlay" role="dialog" aria-modal="true">
|
||||
<div class="dialog">
|
||||
|
|
@ -271,62 +340,75 @@
|
|||
{/if}
|
||||
|
||||
<style>
|
||||
.top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.25rem; }
|
||||
.top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
|
||||
h1 { font-size: 1.4rem; font-weight: 700; color: #1e293b; }
|
||||
.hint { color: #94a3b8; font-size: 0.95rem; text-align: center; margin-top: 3rem; }
|
||||
|
||||
.warnung {
|
||||
background: #fffbeb; border: 1px solid #fde68a;
|
||||
border-radius: 8px; padding: 0.65rem 0.9rem;
|
||||
font-size: 0.85rem; color: #92400e; margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.liste { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1rem; }
|
||||
|
||||
.karte {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
padding: 0.85rem 1rem;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
display: flex; align-items: flex-start; gap: 0.75rem;
|
||||
padding: 0.85rem 1rem; background: #fff;
|
||||
border: 1px solid #e2e8f0; border-radius: 10px;
|
||||
}
|
||||
.karte-grau { background: #f8fafc; opacity: 0.75; }
|
||||
|
||||
.karte-datum-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 2.2rem;
|
||||
padding-top: 0.1rem;
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
min-width: 2.2rem; padding-top: 0.1rem;
|
||||
}
|
||||
.tag { font-size: 0.65rem; font-weight: 600; color: #94a3b8; text-transform: uppercase; }
|
||||
.tag-zahl { font-size: 1.4rem; font-weight: 700; color: #1e40af; line-height: 1.1; }
|
||||
.monat { font-size: 0.65rem; color: #94a3b8; text-transform: uppercase; }
|
||||
|
||||
.karte-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.15rem; }
|
||||
.karte-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
|
||||
.karte-titel { font-weight: 600; font-size: 0.95rem; color: #1e293b; }
|
||||
.karte-meta { font-size: 0.78rem; color: #64748b; }
|
||||
.karte-gruppen { font-size: 0.72rem; color: #94a3b8; }
|
||||
.karte-sub { font-size: 0.72rem; color: #94a3b8; }
|
||||
|
||||
.verfueg-zeile { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.2rem; }
|
||||
.verfueg-badge { font-size: 0.75rem; font-weight: 600; }
|
||||
|
||||
.meine-aktionen {
|
||||
display: flex; gap: 0.35rem; flex-wrap: wrap; margin-top: 0.4rem;
|
||||
}
|
||||
.btn-aktion {
|
||||
padding: 0.3rem 0.65rem; border-radius: 20px;
|
||||
font-size: 0.75rem; font-weight: 600; cursor: pointer;
|
||||
border: 1.5px solid #e2e8f0; background: #fff;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.btn-aktion.bestaetigen { color: #16a34a; }
|
||||
.btn-aktion.bestaetigen.aktiv { background: #dcfce7; border-color: #16a34a; }
|
||||
.btn-aktion.absagen { color: #dc2626; }
|
||||
.btn-aktion.absagen.aktiv { background: #fee2e2; border-color: #dc2626; }
|
||||
.btn-aktion.vertretung { color: #7c3aed; }
|
||||
.btn-aktion.vertretung.aktiv { background: #ede9fe; border-color: #7c3aed; }
|
||||
|
||||
.karte-aktionen { display: flex; gap: 0.35rem; flex-shrink: 0; }
|
||||
|
||||
.btn-icon {
|
||||
width: 1.9rem; height: 1.9rem;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: none; border: 1px solid #e2e8f0; border-radius: 6px;
|
||||
color: #64748b; font-size: 0.85rem; cursor: pointer;
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
width: 1.9rem; height: 1.9rem; display: flex; align-items: center;
|
||||
justify-content: center; background: none; border: 1px solid #e2e8f0;
|
||||
border-radius: 6px; color: #64748b; font-size: 0.85rem; cursor: pointer;
|
||||
}
|
||||
.btn-icon:hover { border-color: #94a3b8; color: #1e293b; }
|
||||
.btn-icon-red:hover { border-color: #fca5a5; color: #dc2626; }
|
||||
|
||||
.vergangen { margin-top: 0.5rem; }
|
||||
.vergangen summary {
|
||||
font-size: 0.85rem; color: #94a3b8; cursor: pointer;
|
||||
padding: 0.5rem 0; list-style: none; user-select: none;
|
||||
}
|
||||
.vergangen-liste { margin-top: 0.5rem; }
|
||||
|
||||
/* Sheet & Overlay */
|
||||
/* Sheet */
|
||||
.overlay {
|
||||
position: fixed; inset: 0;
|
||||
background: rgba(0,0,0,0.4);
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.4);
|
||||
display: flex; align-items: flex-end; justify-content: center;
|
||||
z-index: 100; padding: 1rem;
|
||||
padding-bottom: calc(1rem + env(safe-area-inset-bottom));
|
||||
|
|
@ -336,35 +418,26 @@
|
|||
width: 100%; max-width: 480px; max-height: 92dvh; overflow-y: auto;
|
||||
}
|
||||
h2 { font-size: 1.1rem; font-weight: 700; color: #1e293b; margin-bottom: 1rem; }
|
||||
|
||||
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
|
||||
.field { display: flex; flex-direction: column; gap: 0.3rem; margin-bottom: 0.9rem; }
|
||||
label, .field-label { font-size: 0.875rem; font-weight: 500; color: #475569; }
|
||||
|
||||
input, textarea {
|
||||
padding: 0.65rem 0.85rem;
|
||||
border: 1.5px solid #e2e8f0; border-radius: 8px;
|
||||
input, textarea, select {
|
||||
padding: 0.65rem 0.85rem; border: 1.5px solid #e2e8f0; border-radius: 8px;
|
||||
font-size: 1rem; background: #fff; width: 100%;
|
||||
box-sizing: border-box; font-family: inherit;
|
||||
transition: border-color 0.15s; resize: vertical;
|
||||
box-sizing: border-box; font-family: inherit; resize: vertical;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
input:focus, textarea:focus { outline: none; border-color: #1e40af; }
|
||||
|
||||
input:focus, textarea:focus, select:focus { outline: none; border-color: #1e40af; }
|
||||
.checkboxes { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.2rem; }
|
||||
.check-label {
|
||||
display: flex; align-items: center; gap: 0.35rem;
|
||||
padding: 0.35rem 0.7rem;
|
||||
border: 1.5px solid #e2e8f0; border-radius: 20px;
|
||||
font-size: 0.82rem; cursor: pointer;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
padding: 0.35rem 0.7rem; border: 1.5px solid #e2e8f0;
|
||||
border-radius: 20px; font-size: 0.82rem; cursor: pointer;
|
||||
}
|
||||
.check-label.active { border-color: #1e40af; background: #e0e7ff; color: #1e40af; }
|
||||
.check-label input { display: none; }
|
||||
|
||||
.error { color: #dc2626; font-size: 0.875rem; margin-bottom: 0.75rem; }
|
||||
|
||||
.actions { display: flex; gap: 0.75rem; margin-top: 0.5rem; }
|
||||
|
||||
.btn-primary {
|
||||
flex: 1; padding: 0.75rem; background: #1e40af; color: #fff;
|
||||
border: none; border-radius: 8px; font-size: 1rem; font-weight: 600;
|
||||
|
|
@ -372,24 +445,21 @@
|
|||
}
|
||||
.btn-primary:hover:not(:disabled) { background: #1d3a9e; }
|
||||
.btn-primary:disabled { opacity: 0.55; cursor: not-allowed; }
|
||||
|
||||
.btn-ghost {
|
||||
padding: 0.75rem 1rem; background: none;
|
||||
border: 1.5px solid #e2e8f0; border-radius: 8px;
|
||||
font-size: 1rem; color: #64748b; cursor: pointer;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background: #fff; border-radius: 16px; padding: 1.5rem;
|
||||
width: 100%; max-width: 400px;
|
||||
}
|
||||
.dialog p { font-size: 1rem; color: #1e293b; margin-bottom: 0.4rem; font-weight: 600; }
|
||||
.dialog-sub { font-size: 0.875rem; color: #94a3b8; font-weight: 400 !important; }
|
||||
.dialog-sub { font-weight: 400 !important; font-size: 0.875rem; color: #94a3b8; }
|
||||
.dialog-actions { display: flex; gap: 0.75rem; margin-top: 1.25rem; }
|
||||
.btn-danger {
|
||||
flex: 1; padding: 0.75rem; background: #dc2626; color: #fff;
|
||||
border: none; border-radius: 8px; font-size: 0.95rem; font-weight: 600;
|
||||
cursor: pointer; transition: background 0.15s;
|
||||
border: none; border-radius: 8px; font-size: 0.95rem; font-weight: 600; cursor: pointer;
|
||||
}
|
||||
.btn-danger:hover { background: #b91c1c; }
|
||||
</style>
|
||||
|
|
|
|||
23
pocketbase/pb_migrations/1779230600_termin_verfuegbarkeit.js
Normal file
23
pocketbase/pb_migrations/1779230600_termin_verfuegbarkeit.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const c = app.findCollectionByNameOrId("pbc_2279568741") // termine
|
||||
|
||||
c.fields.addAt(99, new Field({
|
||||
"type": "relation", "id": "relation2001000080", "name": "durchfuehrender_id",
|
||||
"help": "", "hidden": false, "presentable": false, "required": false, "system": false,
|
||||
"cascadeDelete": false, "collectionId": "_pb_users_auth_", "maxSelect": 1, "minSelect": 0
|
||||
}))
|
||||
|
||||
c.fields.addAt(99, new Field({
|
||||
"type": "select", "id": "select2001000081", "name": "verfuegbarkeit",
|
||||
"help": "", "hidden": false, "presentable": false, "required": false, "system": false,
|
||||
"maxSelect": 1, "values": ["offen", "bestaetigt", "abgesagt", "vertretung_gesucht"]
|
||||
}))
|
||||
|
||||
app.save(c)
|
||||
}, (app) => {
|
||||
const c = app.findCollectionByNameOrId("pbc_2279568741")
|
||||
c.fields.removeById("relation2001000080")
|
||||
c.fields.removeById("select2001000081")
|
||||
app.save(c)
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue