v1.12: Dokumente-CRUD fuer die Verwaltung (Mietvertraege & Co. im Dokumentenmodul)
This commit is contained in:
@@ -189,6 +189,19 @@ tar -czf mp-dist.tar.gz -C dist .
|
|||||||
|
|
||||||
## 8. Verlauf / offene Punkte
|
## 8. Verlauf / offene Punkte
|
||||||
|
|
||||||
|
**Erledigt 04.10. (v1.12):** Dokumente-CRUD für die Verwaltung (Schwerpunkt
|
||||||
|
Mietverträge). API (adminOnly): POST/PATCH/DELETE /api/documents(/:id) mit
|
||||||
|
Validierung (Titel nicht leer, Kategorie aus den 5 erlaubten Werten, unitId
|
||||||
|
null oder vorhandene Unit, Datum default `deutschesDatum()`, size default '–',
|
||||||
|
id `d-${Date.now()}`). Frontend: Admin-Dialog auf /dokumente (Titel, Kategorie,
|
||||||
|
Wohnung inkl. „Alle Häuser", Datum, Größe optional; Radix-Select braucht für
|
||||||
|
„Alle Häuser" den Sentinel '__alle' – leerer value ist unzulässig), pro Zeile
|
||||||
|
Bearbeiten/Löschen (window.confirm), Store-Aktionen addDokument/updateDokument/
|
||||||
|
deleteDokument + apiDelete. Mieter-Ansicht unverändert. E2E verifiziert:
|
||||||
|
Anlegen/Patchen/Löschen (Umlaute sauber), Validierung 400, Mieter 403,
|
||||||
|
Testdaten aufgeräumt. Bewusst NICHT gebaut: echter PDF-Upload (kein Storage
|
||||||
|
im Stack, Demo-Charakter – Download-Button bleibt dekorativ).
|
||||||
|
|
||||||
**Erledigt 04.10. (v1.11):** KI-Antworten auf Schadenmeldungen via n8n/Ollama.
|
**Erledigt 04.10. (v1.11):** KI-Antworten auf Schadenmeldungen via n8n/Ollama.
|
||||||
Kette: Mieter-Ticket → mp-api `kiBenachrichtigen()` (POST an n8n, fire-and-
|
Kette: Mieter-Ticket → mp-api `kiBenachrichtigen()` (POST an n8n, fire-and-
|
||||||
forget) → n8n-Workflow `mpKiAntwort0001` (Webhook POST `mp-schadensmeldung`,
|
forget) → n8n-Workflow `mpKiAntwort0001` (Webhook POST `mp-schadensmeldung`,
|
||||||
|
|||||||
@@ -482,6 +482,70 @@ app.patch('/api/meters/:id', auth, adminOnly, async (req, res, next) => {
|
|||||||
res.json({ meter: mapMeter(aktualisiert[0]) })
|
res.json({ meter: mapMeter(aktualisiert[0]) })
|
||||||
} catch (e) { next(e) }
|
} catch (e) { next(e) }
|
||||||
})
|
})
|
||||||
|
// ── Dokumente (Verwaltung) ───────────────────────────────────
|
||||||
|
const DOKUMENT_KATEGORIEN = ['Mietvertrag', 'Nebenkostenabrechnung', 'Hausordnung', 'Protokoll', 'Sonstiges']
|
||||||
|
|
||||||
|
async function dokumentGueltig(b, res) {
|
||||||
|
const titel = String(b.title || '').trim()
|
||||||
|
const kategorie = String(b.category || '').trim()
|
||||||
|
if (!titel) { res.status(400).json({ fehler: 'Titel fehlt.' }); return null }
|
||||||
|
if (!DOKUMENT_KATEGORIEN.includes(kategorie)) { res.status(400).json({ fehler: 'Ungültige Kategorie.' }); return null }
|
||||||
|
let einheitId = b.unitId !== undefined ? (b.unitId || null) : undefined
|
||||||
|
if (einheitId) {
|
||||||
|
const { rows: u } = await pool.query('SELECT id FROM units WHERE id = $1', [einheitId])
|
||||||
|
if (!u.length) { res.status(400).json({ fehler: 'Wohnung unbekannt.' }); return null }
|
||||||
|
}
|
||||||
|
return { titel, kategorie, einheitId }
|
||||||
|
}
|
||||||
|
|
||||||
|
app.post('/api/documents', auth, adminOnly, async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const g = await dokumentGueltig(req.body || {}, res)
|
||||||
|
if (!g) return
|
||||||
|
const { rows } = await pool.query(
|
||||||
|
`INSERT INTO documents (id, unit_id, title, category, size, date)
|
||||||
|
VALUES ($1,$2,$3,$4,$5,$6) RETURNING *`,
|
||||||
|
[`d-${Date.now()}`, g.einheitId ?? null, g.titel, g.kategorie,
|
||||||
|
String(req.body.size || '').trim() || '–', String(req.body.date || '').trim() || deutschesDatum()],
|
||||||
|
)
|
||||||
|
res.json({ dokument: mapDocument(rows[0]) })
|
||||||
|
} catch (e) { next(e) }
|
||||||
|
})
|
||||||
|
|
||||||
|
app.patch('/api/documents/:id', auth, adminOnly, async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { rows } = await pool.query('SELECT * FROM documents WHERE id = $1', [req.params.id])
|
||||||
|
if (!rows.length) return res.status(404).json({ fehler: 'Dokument nicht gefunden.' })
|
||||||
|
const d = rows[0]
|
||||||
|
const b = req.body || {}
|
||||||
|
const kandidat = {
|
||||||
|
title: b.title !== undefined ? b.title : d.title,
|
||||||
|
category: b.category !== undefined ? b.category : d.category,
|
||||||
|
unitId: b.unitId !== undefined ? b.unitId : d.unit_id,
|
||||||
|
}
|
||||||
|
const g = await dokumentGueltig(kandidat, res)
|
||||||
|
if (!g) return
|
||||||
|
const einheitId = g.einheitId === undefined ? d.unit_id : g.einheitId
|
||||||
|
const { rows: aktualisiert } = await pool.query(
|
||||||
|
`UPDATE documents SET unit_id = $1, title = $2, category = $3, size = $4, date = $5
|
||||||
|
WHERE id = $6 RETURNING *`,
|
||||||
|
[einheitId, g.titel, g.kategorie,
|
||||||
|
b.size !== undefined ? (String(b.size).trim() || '–') : d.size,
|
||||||
|
b.date !== undefined ? (String(b.date).trim() || d.date) : d.date,
|
||||||
|
req.params.id],
|
||||||
|
)
|
||||||
|
res.json({ dokument: mapDocument(aktualisiert[0]) })
|
||||||
|
} catch (e) { next(e) }
|
||||||
|
})
|
||||||
|
|
||||||
|
app.delete('/api/documents/:id', auth, adminOnly, async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { rowCount } = await pool.query('DELETE FROM documents WHERE id = $1', [req.params.id])
|
||||||
|
if (!rowCount) return res.status(404).json({ fehler: 'Dokument nicht gefunden.' })
|
||||||
|
res.json({ ok: true })
|
||||||
|
} catch (e) { next(e) }
|
||||||
|
})
|
||||||
|
|
||||||
// ── Bestand: Häuser & Wohnungen (Verwaltung) ────────────────
|
// ── Bestand: Häuser & Wohnungen (Verwaltung) ────────────────
|
||||||
function pruefeHaus(b) {
|
function pruefeHaus(b) {
|
||||||
const fehler = []
|
const fehler = []
|
||||||
|
|||||||
@@ -52,3 +52,4 @@ export async function api<T>(pfad: string, init?: RequestInit, json?: unknown):
|
|||||||
export const apiGet = <T>(pfad: string) => api<T>(pfad)
|
export const apiGet = <T>(pfad: string) => api<T>(pfad)
|
||||||
export const apiPost = <T>(pfad: string, body?: unknown) => api<T>(pfad, { method: 'POST' }, body ?? {})
|
export const apiPost = <T>(pfad: string, body?: unknown) => api<T>(pfad, { method: 'POST' }, body ?? {})
|
||||||
export const apiPatch = <T>(pfad: string, body?: unknown) => api<T>(pfad, { method: 'PATCH' }, body ?? {})
|
export const apiPatch = <T>(pfad: string, body?: unknown) => api<T>(pfad, { method: 'PATCH' }, body ?? {})
|
||||||
|
export const apiDelete = <T>(pfad: string) => api<T>(pfad, { method: 'DELETE' })
|
||||||
|
|||||||
+167
-13
@@ -1,7 +1,24 @@
|
|||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { Download, FileText, FolderOpen, Scale, ReceiptText, FileSignature, FileIcon } from 'lucide-react'
|
import { Download, FileText, FolderOpen, Scale, ReceiptText, FileSignature, FileIcon, Plus, Pencil, Trash2 } from 'lucide-react'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogFooter,
|
||||||
|
} from '@/components/ui/dialog'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select'
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -11,6 +28,9 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from '@/components/ui/table'
|
} from '@/components/ui/table'
|
||||||
import { useStore } from '../store/store'
|
import { useStore } from '../store/store'
|
||||||
|
import type { DocumentItem, Unit } from '../types'
|
||||||
|
|
||||||
|
const KATEGORIEN: DocumentItem['category'][] = ['Mietvertrag', 'Nebenkostenabrechnung', 'Hausordnung', 'Protokoll', 'Sonstiges']
|
||||||
|
|
||||||
const catIcon = (cat: string) => {
|
const catIcon = (cat: string) => {
|
||||||
switch (cat) {
|
switch (cat) {
|
||||||
@@ -27,8 +47,124 @@ const catIcon = (cat: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const heute = () => new Date().toLocaleDateString('de-DE')
|
||||||
|
|
||||||
|
function DokumentDialog({ dokument, einheiten }: { dokument?: DocumentItem; einheiten: Unit[] }) {
|
||||||
|
const { addDokument, updateDokument } = useStore()
|
||||||
|
const bearbeiten = Boolean(dokument)
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [titel, setTitel] = useState(dokument?.title ?? '')
|
||||||
|
const [kategorie, setKategorie] = useState<DocumentItem['category']>(dokument?.category ?? 'Mietvertrag')
|
||||||
|
const [einheit, setEinheit] = useState(dokument?.unitId ?? '__alle') // '__alle' = Alle Häuser
|
||||||
|
const [datum, setDatum] = useState(dokument?.date ?? heute())
|
||||||
|
const [groesse, setGroesse] = useState(dokument?.size === '–' ? '' : (dokument?.size ?? ''))
|
||||||
|
const [sende, setSende] = useState(false)
|
||||||
|
const [fehler, setFehler] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
if (sende) return
|
||||||
|
setSende(true)
|
||||||
|
setFehler(null)
|
||||||
|
const payload = {
|
||||||
|
title: titel.trim(),
|
||||||
|
category: kategorie,
|
||||||
|
unitId: einheit === '__alle' ? null : einheit,
|
||||||
|
date: datum.trim() || heute(),
|
||||||
|
...(groesse.trim() ? { size: groesse.trim() } : {}),
|
||||||
|
}
|
||||||
|
const err = bearbeiten ? await updateDokument(dokument!.id, payload) : await addDokument(payload)
|
||||||
|
setSende(false)
|
||||||
|
if (err) return setFehler(err)
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
{bearbeiten ? (
|
||||||
|
<Button variant="outline" size="sm" className="gap-1.5">
|
||||||
|
<Pencil className="h-3.5 w-3.5" /> Bearbeiten
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button className="gap-2">
|
||||||
|
<Plus className="h-4 w-4" /> Neues Dokument
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{bearbeiten ? 'Dokument bearbeiten' : 'Neues Dokument anlegen'}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="d-titel">Titel</Label>
|
||||||
|
<Input
|
||||||
|
id="d-titel"
|
||||||
|
placeholder="z. B. Mietvertrag – Hauptstraße 12, Whg. 7"
|
||||||
|
value={titel}
|
||||||
|
onChange={(e) => setTitel(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label>Kategorie</Label>
|
||||||
|
<Select value={kategorie} onValueChange={(v) => setKategorie(v as DocumentItem['category'])}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{KATEGORIEN.map((k) => (
|
||||||
|
<SelectItem key={k} value={k}>
|
||||||
|
{k}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label>Wohnung</Label>
|
||||||
|
<Select value={einheit} onValueChange={setEinheit}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Alle Häuser" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__alle">Alle Häuser</SelectItem>
|
||||||
|
{einheiten.map((u) => (
|
||||||
|
<SelectItem key={u.id} value={u.id}>
|
||||||
|
{u.building}, Whg. {u.number}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="d-datum">Datum</Label>
|
||||||
|
<Input id="d-datum" placeholder="TT.MM.JJJJ" value={datum} onChange={(e) => setDatum(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="d-groesse">Dateigröße (optional)</Label>
|
||||||
|
<Input id="d-groesse" placeholder="z. B. 1,8 MB" value={groesse} onChange={(e) => setGroesse(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{fehler && <p className="text-sm font-medium text-red-600">{fehler}</p>}
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||||
|
Abbrechen
|
||||||
|
</Button>
|
||||||
|
<Button onClick={submit} disabled={sende || !titel.trim()}>
|
||||||
|
{sende ? 'Speichere …' : bearbeiten ? 'Speichern' : 'Anlegen'}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function Documents() {
|
export default function Documents() {
|
||||||
const { documents, role, units, meineEinheit } = useStore()
|
const { documents, role, units, meineEinheit, deleteDokument } = useStore()
|
||||||
const [query, setQuery] = useState('')
|
const [query, setQuery] = useState('')
|
||||||
|
|
||||||
const rows = useMemo(() => {
|
const rows = useMemo(() => {
|
||||||
@@ -45,12 +181,17 @@ export default function Documents() {
|
|||||||
(units.find((u) => u.id === d.unitId)?.building ?? '').toLowerCase().includes(q),
|
(units.find((u) => u.id === d.unitId)?.building ?? '').toLowerCase().includes(q),
|
||||||
)
|
)
|
||||||
: base
|
: base
|
||||||
}, [documents, role, query, units])
|
}, [documents, role, query, units, meineEinheit.id])
|
||||||
|
|
||||||
const cats = ['Alle', ...Array.from(new Set(rows.map((d) => d.category)))]
|
const cats = ['Alle', ...Array.from(new Set(rows.map((d) => d.category)))]
|
||||||
const [cat, setCat] = useState('Alle')
|
const [cat, setCat] = useState('Alle')
|
||||||
const filtered = cat === 'Alle' ? rows : rows.filter((d) => d.category === cat)
|
const filtered = cat === 'Alle' ? rows : rows.filter((d) => d.category === cat)
|
||||||
|
|
||||||
|
const loeschen = async (d: DocumentItem) => {
|
||||||
|
if (!window.confirm(`Dokument „${d.title}" wirklich löschen?`)) return
|
||||||
|
await deleteDokument(d.id)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex flex-wrap items-end justify-between gap-3">
|
<div className="flex flex-wrap items-end justify-between gap-3">
|
||||||
@@ -59,15 +200,18 @@ export default function Documents() {
|
|||||||
<p className="text-sm text-slate-500">
|
<p className="text-sm text-slate-500">
|
||||||
{role === 'tenant'
|
{role === 'tenant'
|
||||||
? 'Verträge, Abrechnungen und Unterlagen für Ihre Wohnung'
|
? 'Verträge, Abrechnungen und Unterlagen für Ihre Wohnung'
|
||||||
: 'Zentraler Dokumentenspeicher des gesamten Bestands'}
|
: 'Zentraler Dokumentenspeicher des gesamten Bestands – Verträge, Abrechnungen und Unterlagen'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
placeholder="Dokument suchen …"
|
<Input
|
||||||
value={query}
|
placeholder="Dokument suchen …"
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
value={query}
|
||||||
className="w-64"
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
/>
|
className="w-64"
|
||||||
|
/>
|
||||||
|
{role === 'admin' && <DokumentDialog einheiten={units} />}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
@@ -132,9 +276,19 @@ export default function Documents() {
|
|||||||
<TableCell>{d.date}</TableCell>
|
<TableCell>{d.date}</TableCell>
|
||||||
<TableCell>{d.size}</TableCell>
|
<TableCell>{d.size}</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<button className="inline-flex items-center gap-1 rounded-lg bg-teal-50 px-3 py-1.5 text-xs font-semibold text-teal-700 transition hover:bg-teal-100">
|
<div className="flex items-center justify-end gap-2">
|
||||||
<Download className="h-3.5 w-3.5" /> PDF
|
<button className="inline-flex items-center gap-1 rounded-lg bg-teal-50 px-3 py-1.5 text-xs font-semibold text-teal-700 transition hover:bg-teal-100">
|
||||||
</button>
|
<Download className="h-3.5 w-3.5" /> PDF
|
||||||
|
</button>
|
||||||
|
{role === 'admin' && (
|
||||||
|
<>
|
||||||
|
<DokumentDialog dokument={d} einheiten={units} />
|
||||||
|
<Button variant="outline" size="sm" className="gap-1.5 text-red-600 hover:text-red-700" onClick={() => loeschen(d)}>
|
||||||
|
<Trash2 className="h-3.5 w-3.5" /> Löschen
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
|||||||
+42
-1
@@ -33,7 +33,7 @@ import type {
|
|||||||
ZaehlerArt,
|
ZaehlerArt,
|
||||||
Building,
|
Building,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
import { apiGet, apiPost, apiPatch, ApiFehler } from '../lib/api'
|
import { apiGet, apiPost, apiPatch, apiDelete, ApiFehler } from '../lib/api'
|
||||||
import { sha256, DEMO_PASSWORT } from '../lib/auth'
|
import { sha256, DEMO_PASSWORT } from '../lib/auth'
|
||||||
|
|
||||||
/** Demo-Einheit als Fallback, falls ein Mieter noch keine Wohnung zugewiesen hat. */
|
/** Demo-Einheit als Fallback, falls ein Mieter noch keine Wohnung zugewiesen hat. */
|
||||||
@@ -124,6 +124,19 @@ interface Store extends Bootstrap {
|
|||||||
status: 'vermietet' | 'frei'
|
status: 'vermietet' | 'frei'
|
||||||
}) => Promise<string | null>
|
}) => Promise<string | null>
|
||||||
updateWohnung: (id: string, u: Partial<Unit>) => Promise<string | null>
|
updateWohnung: (id: string, u: Partial<Unit>) => Promise<string | null>
|
||||||
|
// ── Dokumente (Verwaltung pflegt, Mieter lesen) ─────────
|
||||||
|
addDokument: (d: {
|
||||||
|
title: string
|
||||||
|
category: DocumentItem['category']
|
||||||
|
unitId: string | null
|
||||||
|
date: string
|
||||||
|
size?: string
|
||||||
|
}) => Promise<string | null>
|
||||||
|
updateDokument: (
|
||||||
|
id: string,
|
||||||
|
d: Partial<{ title: string; category: DocumentItem['category']; unitId: string | null; date: string; size: string }>,
|
||||||
|
) => Promise<string | null>
|
||||||
|
deleteDokument: (id: string) => Promise<string | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
const StoreContext = createContext<Store | null>(null)
|
const StoreContext = createContext<Store | null>(null)
|
||||||
@@ -348,6 +361,34 @@ export function StoreProvider({ children }: { children: ReactNode }) {
|
|||||||
return fehlerNachricht(e)
|
return fehlerNachricht(e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// ── Dokumente ───────────────────────────────────────────
|
||||||
|
async addDokument(d) {
|
||||||
|
try {
|
||||||
|
const { dokument } = await apiPost<{ dokument: DocumentItem }>('/api/documents', d)
|
||||||
|
setDaten((x) => ({ ...x, documents: [dokument, ...x.documents] }))
|
||||||
|
return null
|
||||||
|
} catch (e) {
|
||||||
|
return fehlerNachricht(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updateDokument(id, d) {
|
||||||
|
try {
|
||||||
|
const { dokument } = await apiPatch<{ dokument: DocumentItem }>(`/api/documents/${id}`, d)
|
||||||
|
setDaten((x) => ({ ...x, documents: x.documents.map((m) => (m.id === id ? dokument : m)) }))
|
||||||
|
return null
|
||||||
|
} catch (e) {
|
||||||
|
return fehlerNachricht(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deleteDokument(id) {
|
||||||
|
try {
|
||||||
|
await apiDelete(`/api/documents/${id}`)
|
||||||
|
setDaten((x) => ({ ...x, documents: x.documents.filter((m) => m.id !== id) }))
|
||||||
|
return null
|
||||||
|
} catch (e) {
|
||||||
|
return fehlerNachricht(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
}), [daten, bereit, me, role, meineEinheit, ladeAlles])
|
}), [daten, bereit, me, role, meineEinheit, ladeAlles])
|
||||||
|
|
||||||
return <StoreContext.Provider value={value}>{children}</StoreContext.Provider>
|
return <StoreContext.Provider value={value}>{children}</StoreContext.Provider>
|
||||||
|
|||||||
Reference in New Issue
Block a user