implement calendar event display with day indicators and overlay

- Add ExpandedEvent type to shared package for recurring event instances
- Implement EventController and EventService with full CRUD operations
- Server-side recurring event expansion via recurrenceExpander
- Calendar grid shows orange dot indicator for days with events
- Tap on day opens modal overlay with EventCards
- EventCard component with Feather icons (calendar, clock, repeat, edit, trash)
- EventsStore with Zustand for client-side event state management
- Load events for visible grid range including adjacent month days
- Add textPrimary, borderPrimary, eventIndicator to theme
- Update test responses for multiple events on Saturdays
This commit is contained in:
2026-01-04 17:19:58 +01:00
parent e3f7a778c7
commit 1532acab78
12 changed files with 601 additions and 99 deletions

View File

@@ -1,23 +1,154 @@
import { View, Text, Pressable } from "react-native";
import { CalendarEvent } from "@caldav/shared";
import { ExpandedEvent } from "@caldav/shared";
import { Feather } from "@expo/vector-icons";
import currentTheme from "../Themes";
type EventCardProps = {
event: CalendarEvent;
onPress?: (event: CalendarEvent) => void;
event: ExpandedEvent;
onEdit: () => void;
onDelete: () => void;
};
const EventCard = ({ event: _event, onPress: _onPress }: EventCardProps) => {
// TODO: Display event title, time, and description preview
// TODO: Handle onPress to navigate to EventDetailScreen
// TODO: Style based on event type or time of day
throw new Error("Not implemented");
function formatDate(date: Date): string {
const d = new Date(date);
return d.toLocaleDateString("de-DE", {
weekday: "short",
day: "2-digit",
month: "2-digit",
year: "numeric",
});
}
function formatTime(date: Date): string {
const d = new Date(date);
return d.toLocaleTimeString("de-DE", {
hour: "2-digit",
minute: "2-digit",
});
}
function formatDuration(start: Date, end: Date): string {
const startDate = new Date(start);
const endDate = new Date(end);
const diffMs = endDate.getTime() - startDate.getTime();
const diffMins = Math.round(diffMs / 60000);
if (diffMins < 60) {
return `${diffMins} min`;
}
const hours = Math.floor(diffMins / 60);
const mins = diffMins % 60;
if (mins === 0) {
return hours === 1 ? "1 Std" : `${hours} Std`;
}
return `${hours} Std ${mins} min`;
}
export const EventCard = ({ event, onEdit, onDelete }: EventCardProps) => {
return (
<Pressable>
<View>
<Text>EventCard - Not Implemented</Text>
<View
className="rounded-xl overflow-hidden mb-3"
style={{ borderWidth: 2, borderColor: currentTheme.borderPrimary }}
>
{/* Header with title */}
<View
className="px-3 py-2"
style={{
backgroundColor: currentTheme.chatBot,
borderBottomWidth: 2,
borderBottomColor: currentTheme.borderPrimary,
}}
>
<Text className="font-bold text-base">{event.title}</Text>
</View>
</Pressable>
{/* Content */}
<View className="px-3 py-2 bg-white">
{/* Date */}
<View className="flex-row items-center mb-1">
<Feather
name="calendar"
size={16}
color={currentTheme.textPrimary}
style={{ marginRight: 8 }}
/>
<Text style={{ color: currentTheme.textPrimary }}>
{formatDate(event.occurrenceStart)}
</Text>
</View>
{/* Time with duration */}
<View className="flex-row items-center mb-1">
<Feather
name="clock"
size={16}
color={currentTheme.textPrimary}
style={{ marginRight: 8 }}
/>
<Text style={{ color: currentTheme.textPrimary }}>
{formatTime(event.occurrenceStart)} -{" "}
{formatTime(event.occurrenceEnd)} (
{formatDuration(event.occurrenceStart, event.occurrenceEnd)})
</Text>
</View>
{/* Recurring indicator */}
{event.isRecurring && (
<View className="flex-row items-center mb-1">
<Feather
name="repeat"
size={16}
color={currentTheme.textPrimary}
style={{ marginRight: 8 }}
/>
<Text style={{ color: currentTheme.textPrimary }}>
Wiederkehrend
</Text>
</View>
)}
{/* Description */}
{event.description && (
<Text
style={{ color: currentTheme.textPrimary }}
className="text-sm mt-1"
>
{event.description}
</Text>
)}
{/* Action buttons */}
<View className="flex-row justify-end mt-3 gap-3">
<Pressable
onPress={onEdit}
className="w-10 h-10 rounded-full items-center justify-center"
style={{
borderWidth: 1,
borderColor: currentTheme.borderPrimary,
}}
>
<Feather name="edit-2" size={18} color={currentTheme.textPrimary} />
</Pressable>
<Pressable
onPress={onDelete}
className="w-10 h-10 rounded-full items-center justify-center"
style={{
borderWidth: 1,
borderColor: currentTheme.borderPrimary,
}}
>
<Feather
name="trash-2"
size={18}
color={currentTheme.textPrimary}
/>
</Pressable>
</View>
</View>
</View>
);
};