todo_app/app/page.tsx

244 lines
7.1 KiB
TypeScript
Raw Normal View History

2026-06-04 17:18:27 +00:00
'use client';
import { useState, useEffect } from 'react';
2026-06-04 17:50:02 +00:00
import gstoolsLogo from './gstools_logo.jpg';
2026-06-04 17:18:27 +00:00
interface Aufgabe {
id: number;
beschreibung: string;
erledigt: boolean;
erstellt: string;
}
interface Pagination {
page: number;
limit: number;
total: number;
totalPages: number;
}
type SortOption = 'text_asc' | 'text_desc' | 'datum_asc' | 'datum_desc';
export default function TodoApp() {
const [aufgaben, setAufgaben] = useState<Aufgabe[]>([]);
const [neueAufgabe, setNeueAufgabe] = useState('');
const [sortierung, setSortierung] = useState<SortOption>('datum_desc');
const [isLoading, setIsLoading] = useState(true);
const [pagination, setPagination] = useState<Pagination>({
page: 1,
limit: 10,
total: 0,
totalPages: 1
});
// Lade Aufgaben vom Server mit Paginierung und Sortierung
const loadAufgaben = async (page: number = 1) => {
try {
setIsLoading(true);
const response = await fetch(`/api/tasks?page=${page}&limit=10&sort=${sortierung}`);
if (response.ok) {
const data = await response.json();
setAufgaben(data.aufgaben);
setPagination(data.pagination);
}
} catch (error) {
console.error('Fehler beim Laden der Aufgaben:', error);
} finally {
setIsLoading(false);
}
};
// Initial laden
useEffect(() => {
loadAufgaben(1);
}, []);
// Aufgaben werden bereits serverseitig sortiert, keine clientseitige Sortierung nötig
const sortierteAufgaben = aufgaben;
const handleHinzufuegen = async (e: React.FormEvent) => {
e.preventDefault();
if (neueAufgabe.trim()) {
try {
const response = await fetch('/api/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ beschreibung: neueAufgabe.trim() }),
});
if (response.ok) {
setNeueAufgabe('');
// Zur ersten Seite zurück, wenn neue Aufgabe hinzugefügt wird
await loadAufgaben(1);
}
} catch (error) {
console.error('Fehler beim Hinzufügen:', error);
}
}
};
const handleLoeschen = async (id: number) => {
if (confirm('Möchtest du diese Aufgabe wirklich löschen?')) {
try {
const response = await fetch(`/api/tasks/${id}`, {
method: 'DELETE',
});
if (response.ok) {
// Aktuelle Seite neu laden oder zur vorherigen, wenn leer
await loadAufgaben(pagination.page);
}
} catch (error) {
console.error('Fehler beim Löschen:', error);
}
}
};
const handleErledigen = async (id: number) => {
try {
const response = await fetch(`/api/tasks/${id}`, {
method: 'PUT',
});
if (response.ok) {
await loadAufgaben(pagination.page);
}
} catch (error) {
console.error('Fehler beim Aktualisieren:', error);
}
};
// Sortierung ändern und Aufgaben neu laden
const handleSortierungAendern = async (e: React.ChangeEvent<HTMLSelectElement>) => {
const neueSortierung = e.target.value as SortOption;
setSortierung(neueSortierung);
await loadAufgaben(1);
};
return (
<div className="container">
<h1>To-Do-Liste</h1>
{/* Sortieroptionen und Export */}
<div className="sort-container">
<span>Sortieren nach:</span>
<select
value={sortierung}
onChange={handleSortierungAendern}
className="sort-dropdown"
>
<option value="text_asc">Name (A-Z)</option>
<option value="text_desc">Name (Z-A)</option>
<option value="datum_asc">Datum (älteste)</option>
<option value="datum_desc">Datum (neueste)</option>
</select>
<button
onClick={() => window.location.href = '/api/tasks/export'}
className="export-button"
disabled={isLoading}
>
CSV Export
</button>
</div>
{/* Formular zum Hinzufügen */}
<form onSubmit={handleHinzufuegen} className="form-group">
<input
type="text"
value={neueAufgabe}
onChange={(e) => setNeueAufgabe(e.target.value)}
placeholder="Neue Aufgabe eingeben..."
required
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Lädt...' : 'Hinzufügen'}
</button>
</form>
{/* To-Do-Liste */}
{isLoading ? (
<p className="empty-message">Lädt Aufgaben...</p>
) : sortierteAufgaben.length > 0 ? (
<ul className="todo-list">
{sortierteAufgaben.map((aufgabe) => (
<li
key={aufgabe.id}
className={`todo-item ${aufgabe.erledigt ? 'completed' : ''}`}
>
<div className="content">
<span>{aufgabe.beschreibung}</span>
<span className="datum">
{new Date(aufgabe.erstellt).toLocaleString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</span>
</div>
<div className="actions">
<a
href="#"
className="erledigen-btn"
onClick={(e) => {
e.preventDefault();
handleErledigen(aufgabe.id);
}}
title={aufgabe.erledigt ? 'Rückgängig' : 'Erledigt'}
>
{aufgabe.erledigt ? '↩' : '✓'}
</a>
<a
href="#"
className="loeschen-btn"
onClick={(e) => {
e.preventDefault();
handleLoeschen(aufgabe.id);
}}
title="Löschen"
>
</a>
</div>
</li>
))}
</ul>
) : (
<p className="empty-message">
Die To-Do-Liste ist leer. Füge eine neue Aufgabe hinzu!
</p>
)}
{/* Paginierung */}
{pagination.totalPages > 1 && (
<div className="pagination">
<button
onClick={() => loadAufgaben(Math.max(1, pagination.page - 1))}
disabled={pagination.page <= 1 || isLoading}
>
Zurück
</button>
<span className="pagination-info">
Seite {pagination.page} von {pagination.totalPages} ({pagination.total} Aufgaben)
</span>
<button
onClick={() => loadAufgaben(Math.min(pagination.totalPages, pagination.page + 1))}
disabled={pagination.page >= pagination.totalPages || isLoading}
>
Weiter
</button>
</div>
)}
<footer className="copyright">
2026-06-04 17:50:02 +00:00
<img src={gstoolsLogo.src} alt="GSTools Logo" className="copyright-logo" />
2026-06-04 17:18:27 +00:00
<p>© 2026 GSTools</p>
</footer>
</div>
);
}