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,27 +1,51 @@
import { CalendarEvent, CreateEventDTO, UpdateEventDTO } from "@caldav/shared";
import {
CalendarEvent,
CreateEventDTO,
UpdateEventDTO,
ExpandedEvent,
} from "@caldav/shared";
import { EventRepository } from "./interfaces";
import { expandRecurringEvents } from "../utils/recurrenceExpander";
export class EventService {
constructor(private eventRepo: EventRepository) {}
async create(userId: string, data: CreateEventDTO): Promise<CalendarEvent> {
throw new Error("Not implemented");
return this.eventRepo.create(userId, data);
}
async getById(id: string, userId: string): Promise<CalendarEvent | null> {
throw new Error("Not implemented");
const event = await this.eventRepo.findById(id);
if (!event || event.userId !== userId) {
return null;
}
return event;
}
async getAll(userId: string): Promise<CalendarEvent[]> {
throw new Error("Not implemented");
return this.eventRepo.findByUserId(userId);
}
async getByDateRange(
userId: string,
startDate: Date,
endDate: Date,
): Promise<CalendarEvent[]> {
throw new Error("Not implemented");
): Promise<ExpandedEvent[]> {
// Get all events for the user
const allEvents = await this.eventRepo.findByUserId(userId);
// Separate recurring and non-recurring events
const recurringEvents = allEvents.filter((e) => e.isRecurring);
const nonRecurringEvents = allEvents.filter((e) => !e.isRecurring);
// Expand all events (recurring get multiple instances, non-recurring stay as-is)
const expanded = expandRecurringEvents(
[...nonRecurringEvents, ...recurringEvents],
startDate,
endDate,
);
return expanded;
}
async update(
@@ -29,10 +53,18 @@ export class EventService {
userId: string,
data: UpdateEventDTO,
): Promise<CalendarEvent | null> {
throw new Error("Not implemented");
const event = await this.eventRepo.findById(id);
if (!event || event.userId !== userId) {
return null;
}
return this.eventRepo.update(id, data);
}
async delete(id: string, userId: string): Promise<boolean> {
throw new Error("Not implemented");
const event = await this.eventRepo.findById(id);
if (!event || event.userId !== userId) {
return false;
}
return this.eventRepo.delete(id);
}
}