1
0

Create mobile calender service

This commit is contained in:
2025-07-18 11:49:28 +02:00
parent 53e2b15351
commit 3608750616
16 changed files with 6749 additions and 0 deletions

475
mobile-calendar/src/App.jsx Normal file
View 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;