Files
tabletten/components/TablettenTable.tsx
2026-03-11 20:33:19 +01:00

209 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useEffect, useState, useCallback } from 'react';
import { Tablette } from '@/types/tablette';
const EMPTY: Omit<Tablette, 'akt' | 'until' | 'warn'> = {
tab: '',
pday: 1,
cnt: 0,
at: '',
rem: '',
order: '',
};
type FormData = Omit<Tablette, 'akt' | 'until' | 'warn'>;
export default function TablettenTable() {
const [rows, setRows] = useState<Tablette[]>([]);
const [sortField, setSortField] = useState('until');
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
const [modal, setModal] = useState<null | 'add' | 'edit' | 'del'>(null);
const [selected, setSelected] = useState<Tablette | null>(null);
const [form, setForm] = useState<FormData>({ ...EMPTY });
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const fetchData = useCallback(async () => {
setLoading(true);
try {
const res = await fetch(`/api/data?sidx=${sortField}&sord=${sortDir}`);
const json = await res.json();
setRows(json.values || []);
} catch {
setError('Fehler beim Laden der Daten.');
} finally {
setLoading(false);
}
}, [sortField, sortDir]);
useEffect(() => {
fetchData();
}, [fetchData]);
function handleSort(field: string) {
if (field === sortField) {
setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));
} else {
setSortField(field);
setSortDir('asc');
}
}
function openAdd() {
setForm({ ...EMPTY });
setModal('add');
}
function openEdit(row: Tablette) {
setSelected(row);
setForm({ tab: row.tab, pday: row.pday, cnt: row.cnt, at: row.at, rem: row.rem, order: row.order });
setModal('edit');
}
function openDel(row: Tablette) {
setSelected(row);
setModal('del');
}
async function handleSave() {
const payload = { ...form, oper: modal === 'add' ? 'add' : 'edit' };
await fetch('/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
setModal(null);
fetchData();
}
async function handleDelete() {
if (!selected) return;
await fetch('/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ oper: 'del', tab: selected.tab }),
});
setModal(null);
fetchData();
}
const SortIndicator = ({ field }: { field: string }) =>
sortField === field ? (sortDir === 'asc' ? ' ▲' : ' ▼') : '';
const colHeader = (label: string, field: string) => (
<th
key={field}
onClick={() => handleSort(field)}
className="sortable-header"
>
{label}
<SortIndicator field={field} />
</th>
);
return (
<div className="table-container">
{error && <p className="error-msg">{error}</p>}
<div className="toolbar">
<button onClick={openAdd} className="btn btn-add">+ Hinzufügen</button>
<button onClick={fetchData} className="btn btn-refresh"> Aktualisieren</button>
</div>
<table className="main-table">
<thead>
<tr>
{colHeader('Tabletten', 'tab')}
{colHeader('pro Tag', 'pday')}
{colHeader('aktuell', 'akt')}
{colHeader('reicht bis', 'until')}
{colHeader('Anzahl…', 'cnt')}
{colHeader('…am', 'at')}
{colHeader('Bemerkungen', 'rem')}
{colHeader('bestellt am', 'order')}
<th>Aktion</th>
</tr>
</thead>
<tbody>
{loading && (
<tr><td colSpan={9} style={{ textAlign: 'center' }}>Lade</td></tr>
)}
{!loading && rows.map((row) => (
<tr
key={row.tab}
className={
row.warn ? 'row-warn' : row.rem === 'abgesetzt' ? 'row-abgesetzt' : ''
}
>
<td className="col-tab">{row.tab}</td>
<td className="col-pday">{row.pday}</td>
<td className="cell-center">{row.akt}</td>
<td className={`col-date ${row.warn ? 'cell-warn' : ''}`}>{row.until}</td>
<td className="cell-center">{row.cnt}</td>
<td className="col-date">{row.at}</td>
<td>{row.rem}</td>
<td className="col-date">{row.order}</td>
<td className="cell-center action-cell">
<button onClick={() => openEdit(row)} className="btn-icon" title="Bearbeiten"></button>
<button onClick={() => openDel(row)} className="btn-icon" title="Löschen">🗑</button>
</td>
</tr>
))}
</tbody>
</table>
{/* Add/Edit Modal */}
{(modal === 'add' || modal === 'edit') && (
<div className="modal-overlay" onClick={() => setModal(null)}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<h2>{modal === 'add' ? 'Neuer Eintrag' : 'Bearbeiten'}</h2>
<label>Tabletten Name
<input value={form.tab} onChange={(e) => setForm({ ...form, tab: e.target.value })}
disabled={modal === 'edit'} />
</label>
<label>pro Tag
<input type="number" step="0.25" value={form.pday}
onChange={(e) => setForm({ ...form, pday: parseFloat(e.target.value) || 0 })} />
</label>
<label>Anzahl
<input type="number" value={form.cnt}
onChange={(e) => setForm({ ...form, cnt: parseInt(e.target.value, 10) || 0 })} />
</label>
<label>Kaufdatum (am)
<input type="text" value={form.at} placeholder="YYYY-MM-DD"
pattern="\d{4}-\d{2}-\d{2}"
onChange={(e) => setForm({ ...form, at: e.target.value })} />
</label>
<label>Bemerkungen
<input value={form.rem}
onChange={(e) => setForm({ ...form, rem: e.target.value })} />
</label>
<label>Bestellt am
<input type="text" value={form.order} placeholder="YYYY-MM-DD"
pattern="\d{4}-\d{2}-\d{2}"
onChange={(e) => setForm({ ...form, order: e.target.value })} />
</label>
<div className="modal-buttons">
<button onClick={handleSave} className="btn btn-add">Speichern</button>
<button onClick={() => setModal(null)} className="btn btn-refresh">Abbrechen</button>
</div>
</div>
</div>
)}
{/* Delete Modal */}
{modal === 'del' && selected && (
<div className="modal-overlay" onClick={() => setModal(null)}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<h2>Eintrag löschen</h2>
<p>Möchtest du <strong>{selected.tab}</strong> wirklich löschen?</p>
<div className="modal-buttons">
<button onClick={handleDelete} className="btn btn-delete">Löschen</button>
<button onClick={() => setModal(null)} className="btn btn-refresh">Abbrechen</button>
</div>
</div>
</div>
)}
</div>
);
}