Create mobile calender service
This commit is contained in:
475
mobile-calendar/src/App.jsx
Normal file
475
mobile-calendar/src/App.jsx
Normal file
@@ -0,0 +1,475 @@
|
||||
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;
|
Reference in New Issue
Block a user