- Modified `loadAufgaben` to accept sort parameter and update state - Updated all `loadAufgaben` calls to pass current sort value - Changed sort change handler to reload current page instead of resetting to page 1 - Removed redundant sort state update in handler - Database file updated with test data changes This improves UX by maintaining pagination context when changing sort order rather than always resetting to the first page.
243 lines
7.2 KiB
TypeScript
243 lines
7.2 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import gstoolsLogo from './gstools_logo.jpg';
|
|
|
|
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, sort: SortOption = sortierung) => {
|
|
try {
|
|
setIsLoading(true);
|
|
setSortierung(sort);
|
|
const response = await fetch(`/api/tasks?page=${page}&limit=10&sort=${sort}`);
|
|
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, sortierung);
|
|
}, []);
|
|
|
|
// 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, sortierung);
|
|
}
|
|
} 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
|
|
await loadAufgaben(pagination.page, sortierung);
|
|
}
|
|
} 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, sortierung);
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Aktualisieren:', error);
|
|
}
|
|
};
|
|
|
|
// Sortierung ändern und Aufgaben auf aktueller Seite neu laden
|
|
const handleSortierungAendern = async (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
const neueSortierung = e.target.value as SortOption;
|
|
await loadAufgaben(pagination.page, neueSortierung);
|
|
};
|
|
|
|
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">
|
|
<img src={gstoolsLogo.src} alt="GSTools Logo" className="copyright-logo" />
|
|
<p>© 2026 GSTools</p>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|