1
0
Files
OpenWall/mobile-calendar/src/App.jsx

476 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react';
import {
FaPlus,
FaTrash,
FaEdit,
FaCalendarAlt,
FaUsers,
FaUser,
FaChevronLeft,
FaChevronRight,
FaSave,
FaTimes
} from 'react-icons/fa';
import calendarService from './services/CalendarService';
import './App.sass';
const App = () => {
const [events, setEvents] = useState([]);
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [currentDate, setCurrentDate] = useState(new Date());
const [viewType, setViewType] = useState('family'); // 'family' or 'individual'
const [selectedUser, setSelectedUser] = useState(null);
const [showAddForm, setShowAddForm] = useState(false);
const [editingEvent, setEditingEvent] = useState(null);
const [newEvent, setNewEvent] = useState({
user: '',
date: '',
text: ''
});
useEffect(() => {
loadUsers();
loadEvents();
}, [currentDate, viewType, selectedUser]);
const loadUsers = async () => {
try {
const userData = await calendarService.getUsers();
setUsers(userData);
if (userData.length > 0 && !selectedUser) {
setSelectedUser(userData[0].name);
setNewEvent(prev => ({ ...prev, user: userData[0].name }));
}
} catch (err) {
setError('Fehler beim Laden der Benutzer');
console.error('Error loading users:', err);
}
};
const loadEvents = async () => {
try {
setLoading(true);
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
const eventData = await calendarService.getEventsForMonth(
year,
month,
selectedUser,
viewType
);
setEvents(eventData);
setError(null);
} catch (err) {
setError('Fehler beim Laden der Termine');
console.error('Error loading events:', err);
} finally {
setLoading(false);
}
};
const handleAddEvent = async (e) => {
e.preventDefault();
if (!newEvent.text.trim() || !newEvent.date || !newEvent.user) return;
try {
const createdEvent = await calendarService.createEvent(newEvent);
setEvents([...events, createdEvent]);
setNewEvent({
user: selectedUser || users[0]?.name || '',
date: '',
text: ''
});
setShowAddForm(false);
} catch (err) {
setError('Fehler beim Erstellen des Termins');
console.error('Error creating event:', err);
}
};
const handleUpdateEvent = async (eventId, updatedData) => {
try {
const updatedEvent = await calendarService.updateEvent(eventId, updatedData);
setEvents(events.map(event =>
event.id === eventId ? updatedEvent : event
));
setEditingEvent(null);
} catch (err) {
setError('Fehler beim Aktualisieren des Termins');
console.error('Error updating event:', err);
}
};
const handleDeleteEvent = async (eventId, user) => {
try {
await calendarService.deleteEvent(eventId, user);
setEvents(events.filter(event => event.id !== eventId));
} catch (err) {
setError('Fehler beim Löschen des Termins');
console.error('Error deleting event:', err);
}
};
const navigateMonth = (direction) => {
const newDate = new Date(currentDate);
newDate.setMonth(newDate.getMonth() + direction);
setCurrentDate(newDate);
};
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('de-DE', {
weekday: 'short',
day: '2-digit',
month: '2-digit'
});
};
const getCurrentMonthYear = () => {
return currentDate.toLocaleDateString('de-DE', {
month: 'long',
year: 'numeric'
});
};
const groupEventsByDate = (events) => {
const grouped = {};
events.forEach(event => {
const date = event.date;
if (!grouped[date]) {
grouped[date] = [];
}
grouped[date].push(event);
});
// Sort dates
const sortedDates = Object.keys(grouped).sort();
const result = {};
sortedDates.forEach(date => {
result[date] = grouped[date];
});
return result;
};
const groupedEvents = groupEventsByDate(events);
const getUserColor = (userName) => {
const colors = [
'#3b82f6', // blue
'#10b981', // green
'#f59e0b', // amber
'#ef4444', // red
'#8b5cf6', // purple
'#06b6d4', // cyan
'#ec4899', // pink
'#84cc16' // lime
];
const userIndex = users.findIndex(user => user.name === userName);
return colors[userIndex % colors.length];
};
if (loading && events.length === 0) {
return (
<div className="app">
<div className="loading">
<FaCalendarAlt className="loading-icon" />
<p>Kalender wird geladen...</p>
</div>
</div>
);
}
return (
<div className="app calendar-app">
<header className="header">
<div className="header-content">
<h1>
<FaCalendarAlt />
Kalender
</h1>
<div className="month-navigation">
<button
className="btn btn-icon"
onClick={() => navigateMonth(-1)}
>
<FaChevronLeft />
</button>
<h2 className="current-month">{getCurrentMonthYear()}</h2>
<button
className="btn btn-icon"
onClick={() => navigateMonth(1)}
>
<FaChevronRight />
</button>
</div>
</div>
<div className="view-controls">
<div className="view-type-toggle">
<button
className={`btn ${viewType === 'family' ? 'btn-primary' : 'btn-secondary'}`}
onClick={() => setViewType('family')}
>
<FaUsers />
Familie
</button>
<button
className={`btn ${viewType === 'individual' ? 'btn-primary' : 'btn-secondary'}`}
onClick={() => setViewType('individual')}
>
<FaUser />
Einzeln
</button>
</div>
{viewType === 'individual' && (
<select
className="user-select"
value={selectedUser || ''}
onChange={(e) => setSelectedUser(e.target.value)}
>
{users.map(user => (
<option key={user.id} value={user.name}>
{user.name}
</option>
))}
</select>
)}
</div>
</header>
<main className="main">
{error && (
<div className="error-message">
{error}
<button onClick={() => setError(null)}>×</button>
</div>
)}
{showAddForm && (
<div className="add-form-overlay">
<form className="add-form" onSubmit={handleAddEvent}>
<h3>Neuer Termin</h3>
<div className="form-group">
<label>Benutzer</label>
<select
value={newEvent.user}
onChange={(e) => setNewEvent({ ...newEvent, user: e.target.value })}
required
>
<option value="">Benutzer auswählen</option>
{users.map(user => (
<option key={user.id} value={user.name}>
{user.name}
</option>
))}
</select>
</div>
<div className="form-group">
<label>Datum</label>
<input
type="date"
value={newEvent.date}
onChange={(e) => setNewEvent({ ...newEvent, date: e.target.value })}
required
/>
</div>
<div className="form-group">
<label>Beschreibung</label>
<input
type="text"
placeholder="Termin beschreiben..."
value={newEvent.text}
onChange={(e) => setNewEvent({ ...newEvent, text: e.target.value })}
autoFocus
required
/>
</div>
<div className="form-actions">
<button type="button" className="btn btn-secondary" onClick={() => setShowAddForm(false)}>
<FaTimes />
Abbrechen
</button>
<button type="submit" className="btn btn-primary">
<FaSave />
Speichern
</button>
</div>
</form>
</div>
)}
<div className="calendar-content">
{Object.keys(groupedEvents).length === 0 ? (
<div className="empty-state">
<FaCalendarAlt />
<p>Keine Termine in diesem Monat</p>
<small>
{viewType === 'family'
? 'Füge einen neuen Termin hinzu für alle Familienmitglieder'
: `Keine Termine für ${selectedUser} gefunden`
}
</small>
</div>
) : (
<div className="events-list">
{Object.entries(groupedEvents).map(([date, dayEvents]) => (
<div key={date} className="day-section">
<div className="day-header">
<h3>{formatDate(date)}</h3>
<span className="event-count">{dayEvents.length} Termin{dayEvents.length !== 1 ? 'e' : ''}</span>
</div>
<div className="day-events">
{dayEvents.map(event => (
<CalendarEvent
key={event.id}
event={event}
onUpdate={handleUpdateEvent}
onDelete={handleDeleteEvent}
editingEvent={editingEvent}
setEditingEvent={setEditingEvent}
userColor={getUserColor(event.user)}
showUser={viewType === 'family'}
users={users}
/>
))}
</div>
</div>
))}
</div>
)}
</div>
</main>
<div className="fab-container">
<button
className="fab"
onClick={() => setShowAddForm(true)}
>
<FaPlus />
</button>
</div>
</div>
);
};
// Calendar Event Component
const CalendarEvent = ({
event,
onUpdate,
onDelete,
editingEvent,
setEditingEvent,
userColor,
showUser,
users
}) => {
const [editData, setEditData] = useState({
user: event.user,
date: event.date,
text: event.text
});
const handleEdit = () => {
setEditingEvent(event.id);
setEditData({
user: event.user,
date: event.date,
text: event.text
});
};
const handleSave = () => {
onUpdate(event.id, editData);
};
const handleCancel = () => {
setEditingEvent(null);
setEditData({
user: event.user,
date: event.date,
text: event.text
});
};
const isEditing = editingEvent === event.id;
return (
<div className="calendar-event" style={{ borderLeftColor: userColor }}>
{isEditing ? (
<div className="edit-form">
<div className="form-group">
<select
value={editData.user}
onChange={(e) => setEditData({ ...editData, user: e.target.value })}
>
{users.map(user => (
<option key={user.id} value={user.name}>
{user.name}
</option>
))}
</select>
</div>
<div className="form-group">
<input
type="date"
value={editData.date}
onChange={(e) => setEditData({ ...editData, date: e.target.value })}
/>
</div>
<div className="form-group">
<input
type="text"
value={editData.text}
onChange={(e) => setEditData({ ...editData, text: e.target.value })}
onKeyDown={(e) => {
if (e.key === 'Enter') handleSave();
if (e.key === 'Escape') handleCancel();
}}
autoFocus
/>
</div>
<div className="edit-actions">
<button className="btn btn-primary btn-small" onClick={handleSave}>
<FaSave />
</button>
<button className="btn btn-secondary btn-small" onClick={handleCancel}>
<FaTimes />
</button>
</div>
</div>
) : (
<>
<div className="event-content">
<div className="event-text">{event.text}</div>
{showUser && (
<div className="event-user" style={{ color: userColor }}>
{event.user}
</div>
)}
</div>
<div className="event-actions">
<button className="btn btn-secondary btn-small" onClick={handleEdit}>
<FaEdit />
</button>
<button
className="btn btn-danger btn-small"
onClick={() => onDelete(event.id, event.user)}
>
<FaTrash />
</button>
</div>
</>
)}
</div>
);
};
export default App;