Rebuild as InnungsApp project: replace stadtwerke analysis with full documentation
- PRD: vollständige Produktspezifikation (5 Module, Scope, Akzeptanzkriterien) - ARCHITECTURE: Tech Stack, Ordnerstruktur, Multi-Tenancy, Push, Kosten - DATABASE_SCHEMA: Vollständiges SQL-Schema mit RLS Policies und Views - USER_STORIES: 40+ Stories nach Rolle (Admin, Mitglied, Azubi, Obermeister) - PERSONAS: 5 detaillierte Nutzerprofile mit Alltag, Zitaten und Erwartungen - BUSINESS_MODEL: Preistabellen, Unit Economics, Revenue-Projektionen, Distribution - ROADMAP: 6 Phasen, Sprint-Planung, Meilensteine und KPIs - COMPETITIVE_ANALYSIS: Wettbewerbsmatrix, USPs, Preispositionierung - API_DESIGN: Supabase Query Patterns, Edge Functions, Realtime Subscriptions - ONBOARDING_FLOWS: 7 User Flows von Setup bis Fehlerfall - GTM_STRATEGY: 3-Phasen-Vertrieb, Outreach-Sequenz, Einwandbehandlung - AZUBI_MODULE: Video-Feed, 1-Click-Apply, Chat, Berichtsheft, Quiz - DSGVO_KONZEPT: Rechtsgrundlagen, TOMs, AVV, Minderjährige, Incident Response - FEATURES_BACKLOG: 72 Features nach MoSCoW + Technische Schulden Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
144
innungsapp/apps/admin/app/dashboard/termine/neu/page.tsx
Normal file
144
innungsapp/apps/admin/app/dashboard/termine/neu/page.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { trpc } from '@/lib/trpc-client'
|
||||
import Link from 'next/link'
|
||||
|
||||
const TYPEN = [
|
||||
{ value: 'Pruefung', label: 'Prüfung' },
|
||||
{ value: 'Versammlung', label: 'Versammlung' },
|
||||
{ value: 'Kurs', label: 'Kurs' },
|
||||
{ value: 'Event', label: 'Event' },
|
||||
{ value: 'Sonstiges', label: 'Sonstiges' },
|
||||
]
|
||||
|
||||
export default function TerminNeuPage() {
|
||||
const router = useRouter()
|
||||
const [form, setForm] = useState({
|
||||
titel: '',
|
||||
datum: '',
|
||||
uhrzeit: '',
|
||||
endeDatum: '',
|
||||
endeUhrzeit: '',
|
||||
ort: '',
|
||||
adresse: '',
|
||||
typ: 'Versammlung',
|
||||
beschreibung: '',
|
||||
maxTeilnehmer: '',
|
||||
})
|
||||
|
||||
const createMutation = trpc.termine.create.useMutation({
|
||||
onSuccess: () => router.push('/dashboard/termine'),
|
||||
})
|
||||
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
createMutation.mutate({
|
||||
titel: form.titel,
|
||||
datum: form.datum,
|
||||
uhrzeit: form.uhrzeit || undefined,
|
||||
endeDatum: form.endeDatum || undefined,
|
||||
endeUhrzeit: form.endeUhrzeit || undefined,
|
||||
ort: form.ort || undefined,
|
||||
adresse: form.adresse || undefined,
|
||||
typ: form.typ as never,
|
||||
beschreibung: form.beschreibung || undefined,
|
||||
maxTeilnehmer: form.maxTeilnehmer ? Number(form.maxTeilnehmer) : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
const F = (field: string) => (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) =>
|
||||
setForm({ ...form, [field]: e.target.value })
|
||||
|
||||
const inputClass =
|
||||
'w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-brand-500'
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/dashboard/termine" className="text-gray-400 hover:text-gray-600">
|
||||
← Zurück
|
||||
</Link>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Termin anlegen</h1>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="bg-white rounded-xl border shadow-sm p-6 space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Titel *</label>
|
||||
<input required value={form.titel} onChange={F('titel')} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Typ *</label>
|
||||
<select value={form.typ} onChange={F('typ')} className={inputClass}>
|
||||
{TYPEN.map((t) => <option key={t.value} value={t.value}>{t.label}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Max. Teilnehmer</label>
|
||||
<input
|
||||
type="number"
|
||||
value={form.maxTeilnehmer}
|
||||
onChange={F('maxTeilnehmer')}
|
||||
placeholder="Leer = unbegrenzt"
|
||||
className={inputClass}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Datum *</label>
|
||||
<input required type="date" value={form.datum} onChange={F('datum')} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Uhrzeit (von)</label>
|
||||
<input type="time" value={form.uhrzeit} onChange={F('uhrzeit')} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Ende Datum</label>
|
||||
<input type="date" value={form.endeDatum} onChange={F('endeDatum')} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Ende Uhrzeit</label>
|
||||
<input type="time" value={form.endeUhrzeit} onChange={F('endeUhrzeit')} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Ort</label>
|
||||
<input value={form.ort} onChange={F('ort')} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Adresse</label>
|
||||
<input value={form.adresse} onChange={F('adresse')} className={inputClass} />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung</label>
|
||||
<textarea
|
||||
value={form.beschreibung}
|
||||
onChange={F('beschreibung')}
|
||||
rows={4}
|
||||
className={inputClass}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{createMutation.error && (
|
||||
<p className="text-sm text-red-600 bg-red-50 px-4 py-2 rounded-lg">
|
||||
{createMutation.error.message}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3 pt-2 border-t">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={createMutation.isPending}
|
||||
className="bg-brand-500 text-white px-6 py-2 rounded-lg text-sm font-medium hover:bg-brand-600 disabled:opacity-60 transition-colors"
|
||||
>
|
||||
{createMutation.isPending ? 'Wird gespeichert...' : 'Termin anlegen'}
|
||||
</button>
|
||||
<Link href="/dashboard/termine" className="px-6 py-2 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-100 transition-colors">
|
||||
Abbrechen
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user