für Docker angepasst
This commit is contained in:
274
components/WerteForm.tsx
Normal file
274
components/WerteForm.tsx
Normal file
@@ -0,0 +1,274 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { CreateWerteEntry, WerteEntry } from '@/types/werte';
|
||||
|
||||
interface WerteFormProps {
|
||||
onSuccess: () => void;
|
||||
selectedEntry?: WerteEntry | null;
|
||||
}
|
||||
|
||||
export default function WerteForm({ onSuccess, selectedEntry }: WerteFormProps) {
|
||||
const [formData, setFormData] = useState<CreateWerteEntry>({
|
||||
Datum: '',
|
||||
Zeit: '',
|
||||
Zucker: '',
|
||||
Essen: 'nüchtern',
|
||||
Gewicht: '',
|
||||
DruckS: '',
|
||||
DruckD: '',
|
||||
Puls: '',
|
||||
});
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [weekday, setWeekday] = useState('');
|
||||
const [editId, setEditId] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEntry) {
|
||||
// Load selected entry for editing
|
||||
const dateStr = selectedEntry.Datum.toString().split('T')[0];
|
||||
const timeStr = selectedEntry.Zeit.toString().substring(0, 5);
|
||||
|
||||
setFormData({
|
||||
Datum: dateStr,
|
||||
Zeit: timeStr,
|
||||
Zucker: selectedEntry.Zucker || '',
|
||||
Essen: selectedEntry.Essen || '',
|
||||
Gewicht: selectedEntry.Gewicht || '',
|
||||
DruckS: selectedEntry.DruckS || '',
|
||||
DruckD: selectedEntry.DruckD || '',
|
||||
Puls: selectedEntry.Puls || '',
|
||||
});
|
||||
|
||||
setEditId(selectedEntry.ID || null);
|
||||
|
||||
// Parse date to avoid timezone issues
|
||||
const [year, month, day] = dateStr.split('T')[0].split('-');
|
||||
const date = new Date(Number(year), Number(month) - 1, Number(day));
|
||||
updateWeekday(date);
|
||||
} else {
|
||||
// Initialize with current date and time for new entry
|
||||
const now = new Date();
|
||||
const dateStr = now.toISOString().split('T')[0];
|
||||
const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
||||
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
Datum: dateStr,
|
||||
Zeit: timeStr,
|
||||
}));
|
||||
|
||||
setEditId(null);
|
||||
updateWeekday(now);
|
||||
}
|
||||
}, [selectedEntry]);
|
||||
|
||||
const updateWeekday = (date: Date) => {
|
||||
const weekdays = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
|
||||
setWeekday(weekdays[date.getDay()]);
|
||||
};
|
||||
|
||||
const handleDateChange = (dateStr: string) => {
|
||||
setFormData(prev => ({ ...prev, Datum: dateStr }));
|
||||
// Parse date to avoid timezone issues
|
||||
const [year, month, day] = dateStr.split('-');
|
||||
const date = new Date(Number(year), Number(month) - 1, Number(day));
|
||||
updateWeekday(date);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const url = editId ? `/api/werte/${editId}` : '/api/werte';
|
||||
const method = editId ? 'PUT' : 'POST';
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Reset form but keep date and time
|
||||
const now = new Date();
|
||||
const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
||||
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
Zeit: timeStr,
|
||||
Zucker: '',
|
||||
Essen: 'nüchtern',
|
||||
Gewicht: '',
|
||||
DruckS: '',
|
||||
DruckD: '',
|
||||
Puls: '',
|
||||
}));
|
||||
|
||||
setEditId(null);
|
||||
onSuccess();
|
||||
} else {
|
||||
alert('Fehler beim Speichern!');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Fehler beim Speichern!');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
const now = new Date();
|
||||
const dateStr = now.toISOString().split('T')[0];
|
||||
const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
||||
|
||||
setFormData({
|
||||
Datum: dateStr,
|
||||
Zeit: timeStr,
|
||||
Zucker: '',
|
||||
Essen: 'nüchtern',
|
||||
Gewicht: '',
|
||||
DruckS: '',
|
||||
DruckD: '',
|
||||
Puls: '',
|
||||
});
|
||||
|
||||
setEditId(null);
|
||||
updateWeekday(now);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-[#CCCCFF] border border-black p-6 rounded-lg mb-6">
|
||||
{editId && (
|
||||
<div className="mb-4 p-3 bg-blue-100 border border-blue-400 rounded text-sm text-blue-800">
|
||||
ℹ️ <strong>Bearbeitungsmodus:</strong> Sie bearbeiten einen bestehenden Eintrag. Klicken Sie auf "Aktualisieren", um die Änderungen zu speichern, oder "Abbrechen", um zur Neuerfassung zurückzukehren.
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<table className="w-full text-center">
|
||||
<thead>
|
||||
<tr className="border-b-2 border-gray-400">
|
||||
<th className="p-2">Datum</th>
|
||||
<th className="p-2">Zeit</th>
|
||||
<th className="p-2">Zucker</th>
|
||||
<th className="p-2">Essen</th>
|
||||
<th className="p-2">Gewicht</th>
|
||||
<th className="p-2">Druck sys</th>
|
||||
<th className="p-2">Druck dia</th>
|
||||
<th className="p-2">Puls</th>
|
||||
</tr>
|
||||
<tr className="border-b-2 border-gray-400">
|
||||
<th className="p-2 font-normal text-sm">{weekday}</th>
|
||||
<th className="p-2"></th>
|
||||
<th className="p-2 font-normal text-sm">mg/dl</th>
|
||||
<th className="p-2"></th>
|
||||
<th className="p-2 font-normal text-sm">kg</th>
|
||||
<th className="p-2 font-normal text-sm">mmHg</th>
|
||||
<th className="p-2 font-normal text-sm">mmHg</th>
|
||||
<th className="p-2 font-normal text-sm">bpm</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="p-2">
|
||||
<input
|
||||
type="date"
|
||||
className="w-full px-2 py-1 text-sm rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
|
||||
value={formData.Datum}
|
||||
onChange={(e) => handleDateChange(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</td>
|
||||
<td className="p-2">
|
||||
<input
|
||||
type="time"
|
||||
className="w-full px-2 py-1 text-sm rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
|
||||
value={formData.Zeit}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, Zeit: e.target.value }))}
|
||||
required
|
||||
/>
|
||||
</td>
|
||||
<td className="p-2">
|
||||
<input
|
||||
type="number"
|
||||
className="w-20 px-2 py-1 text-sm rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
|
||||
value={formData.Zucker}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, Zucker: e.target.value }))}
|
||||
maxLength={4}
|
||||
/>
|
||||
</td>
|
||||
<td className="p-2">
|
||||
<textarea
|
||||
className="w-full px-2 py-1 text-sm rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none resize-none align-middle"
|
||||
style={{ height: '28px' }}
|
||||
value={formData.Essen}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, Essen: e.target.value }))}
|
||||
rows={1}
|
||||
/>
|
||||
</td>
|
||||
<td className="p-2">
|
||||
<input
|
||||
type="number"
|
||||
step="0.1"
|
||||
className="w-20 px-2 py-1 text-sm rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
|
||||
value={formData.Gewicht}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, Gewicht: e.target.value }))}
|
||||
maxLength={4}
|
||||
/>
|
||||
</td>
|
||||
<td className="p-2">
|
||||
<input
|
||||
type="number"
|
||||
className="w-20 px-2 py-1 text-sm rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
|
||||
value={formData.DruckS}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, DruckS: e.target.value }))}
|
||||
maxLength={4}
|
||||
/>
|
||||
</td>
|
||||
<td className="p-2">
|
||||
<input
|
||||
type="number"
|
||||
className="w-20 px-2 py-1 text-sm rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
|
||||
value={formData.DruckD}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, DruckD: e.target.value }))}
|
||||
maxLength={4}
|
||||
/>
|
||||
</td>
|
||||
<td className="p-2">
|
||||
<input
|
||||
type="number"
|
||||
className="w-20 px-2 py-1 text-sm rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
|
||||
value={formData.Puls}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, Puls: e.target.value }))}
|
||||
maxLength={4}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="flex justify-center gap-4 mt-6">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="bg-[#85B7D7] hover:bg-[#6a9fc5] text-black font-medium py-2 px-8 rounded-lg transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isSubmitting ? (editId ? 'Aktualisieren...' : 'Speichern...') : (editId ? 'Aktualisieren' : 'Speichern')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleReset}
|
||||
className="bg-[#85B7D7] hover:bg-[#6a9fc5] text-black font-medium py-2 px-8 rounded-lg transition-colors"
|
||||
>
|
||||
{editId ? 'Abbrechen' : 'Löschen'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
135
components/WerteList.tsx
Normal file
135
components/WerteList.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
'use client';
|
||||
|
||||
import { WerteEntry } from '@/types/werte';
|
||||
|
||||
interface WerteListProps {
|
||||
entries: WerteEntry[];
|
||||
onDelete?: (id: number) => void;
|
||||
onEdit?: (entry: WerteEntry) => void;
|
||||
}
|
||||
|
||||
export default function WerteList({ entries, onDelete, onEdit }: WerteListProps) {
|
||||
const formatValue = (value: number | string | null | undefined) => {
|
||||
if (value === null || value === undefined || value === '' || value === '0' || value === '0.0') {
|
||||
return '-';
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
// Convert YYYY-MM-DD to DD.MM.YYYY
|
||||
if (!dateStr) return '-';
|
||||
// Parse the date string directly (MySQL returns YYYY-MM-DD)
|
||||
const parts = dateStr.toString().split('T')[0].split('-');
|
||||
if (parts.length === 3) {
|
||||
return `${parts[2]}.${parts[1]}.${parts[0]}`;
|
||||
}
|
||||
return dateStr;
|
||||
};
|
||||
|
||||
const formatTime = (timeStr: string) => {
|
||||
// Convert HH:MM:SS to HH:MM
|
||||
if (!timeStr) return '-';
|
||||
const timeString = timeStr.toString();
|
||||
return timeString.substring(0, 5);
|
||||
};
|
||||
|
||||
const getWeekday = (dateStr: string) => {
|
||||
if (!dateStr) return '-';
|
||||
const weekdays = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
|
||||
// Parse date parts to avoid timezone issues
|
||||
const [year, month, day] = dateStr.toString().split('T')[0].split('-');
|
||||
const date = new Date(Number(year), Number(month) - 1, Number(day));
|
||||
return weekdays[date.getDay()];
|
||||
};
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
if (!confirm('Eintrag wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/werte/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (response.ok && onDelete) {
|
||||
onDelete(id);
|
||||
} else {
|
||||
alert('Fehler beim Löschen!');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Fehler beim Löschen!');
|
||||
}
|
||||
};
|
||||
|
||||
if (entries.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
Keine Einträge vorhanden
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-[#CCCCFF] border-b-2 border-gray-400">
|
||||
<th className="p-2 text-center">Datum</th>
|
||||
<th className="p-2 text-center">Tag</th>
|
||||
<th className="p-2 text-center">Zeit</th>
|
||||
<th className="p-2 text-center">Zucker<br/><span className="text-xs font-normal">mg/dl</span></th>
|
||||
<th className="p-2 text-center">Essen</th>
|
||||
<th className="p-2 text-center">Gewicht<br/><span className="text-xs font-normal">kg</span></th>
|
||||
<th className="p-2 text-center">Druck sys<br/><span className="text-xs font-normal">mmHg</span></th>
|
||||
<th className="p-2 text-center">Druck dia<br/><span className="text-xs font-normal">mmHg</span></th>
|
||||
<th className="p-2 text-center">Puls<br/><span className="text-xs font-normal">bpm</span></th>
|
||||
{onDelete && <th className="p-2 text-center">Aktion</th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{entries.map((entry, index) => (
|
||||
<tr
|
||||
key={entry.ID}
|
||||
className={`border-b border-gray-300 hover:bg-gray-50 ${index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}`}
|
||||
>
|
||||
<td className="p-2 text-center">{formatDate(entry.Datum)}</td>
|
||||
<td className="p-2 text-center">{getWeekday(entry.Datum)}</td>
|
||||
<td className="p-2 text-center">{formatTime(entry.Zeit)}</td>
|
||||
<td className="p-2 text-center">{formatValue(entry.Zucker)}</td>
|
||||
<td className="p-2 text-center">{formatValue(entry.Essen)}</td>
|
||||
<td className="p-2 text-center">{formatValue(entry.Gewicht)}</td>
|
||||
<td className="p-2 text-center">{formatValue(entry.DruckS)}</td>
|
||||
<td className="p-2 text-center">{formatValue(entry.DruckD)}</td>
|
||||
<td className="p-2 text-center">{formatValue(entry.Puls)}</td>
|
||||
{(onDelete || onEdit) && (
|
||||
<td className="p-2 text-center">
|
||||
<div className="flex gap-2 justify-center">
|
||||
{onEdit && (
|
||||
<button
|
||||
onClick={() => onEdit(entry)}
|
||||
className="text-blue-600 hover:text-blue-800 text-sm"
|
||||
>
|
||||
Editieren
|
||||
</button>
|
||||
)}
|
||||
{onDelete && (
|
||||
<button
|
||||
onClick={() => entry.ID && handleDelete(entry.ID)}
|
||||
className="text-red-600 hover:text-red-800 text-sm"
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user