213 lines
7.2 KiB
TypeScript
213 lines
7.2 KiB
TypeScript
'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();
|
||
if (!res.ok) {
|
||
setError(`Fehler beim Laden: ${json.error ?? res.status}`);
|
||
return;
|
||
}
|
||
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>
|
||
);
|
||
}
|