feat: add EditEventScreen with calendar and chat mode support

Add a unified event editor that works in two modes:
- Calendar mode: Create/edit events directly via EventService API
- Chat mode: Edit AI-proposed events before confirming them

The chat mode allows users to modify proposed events (title, time,
recurrence) and persists changes both locally and to the server.

New components: DateTimePicker, ScrollableDropdown, useDropdownPosition
New API: PUT /api/chat/messages/:messageId/proposal
This commit is contained in:
2026-01-31 18:46:31 +01:00
parent 617543a603
commit 6f0d172bf2
33 changed files with 1394 additions and 289 deletions

View File

@@ -76,6 +76,7 @@ src/
│ │ ├── chat.tsx # Chat screen (AI conversation)
│ │ ├── calendar.tsx # Calendar overview
│ │ └── settings.tsx # Settings screen (theme switcher, logout)
│ ├── editEvent.tsx # Event edit screen (dual-mode: calendar/chat)
│ ├── event/
│ │ └── [id].tsx # Event detail screen (dynamic route)
│ └── note/
@@ -93,8 +94,10 @@ src/
│ ├── EventCardBase.tsx # Event card layout with icons (uses CardBase)
│ ├── EventCard.tsx # Calendar event card (uses EventCardBase + edit/delete buttons)
│ ├── EventConfirmDialog.tsx # AI-proposed event confirmation modal (skeleton)
│ ├── ProposedEventCard.tsx # Chat event proposal (uses EventCardBase + confirm/reject buttons)
── DeleteEventModal.tsx # Delete confirmation modal (uses ModalBase)
│ ├── ProposedEventCard.tsx # Chat event proposal (uses EventCardBase + confirm/reject/edit buttons)
── DeleteEventModal.tsx # Delete confirmation modal (uses ModalBase)
│ ├── DateTimePicker.tsx # Date and time picker components
│ └── ScrollableDropdown.tsx # Scrollable dropdown component
├── Themes.tsx # Theme definitions: THEMES object with defaultLight/defaultDark, Theme type
├── logging/
│ ├── index.ts # Re-exports
@@ -104,14 +107,16 @@ src/
│ ├── ApiClient.ts # HTTP client with X-User-Id header injection, request logging, handles empty responses (204)
│ ├── AuthService.ts # login(), register(), logout() - calls API and updates AuthStore
│ ├── EventService.ts # getAll(), getById(), getByDateRange(), create(), update(), delete(mode, occurrenceDate)
│ └── ChatService.ts # sendMessage(), confirmEvent(deleteMode, occurrenceDate), rejectEvent(), getConversations(), getConversation()
── stores/ # Zustand state management
├── index.ts # Re-exports all stores
├── AuthStore.ts # user, isAuthenticated, isLoading, login(), logout(), loadStoredUser()
│ # Uses expo-secure-store (native) / localStorage (web)
├── ChatStore.ts # messages[], isWaitingForResponse, addMessage(), addMessages(), updateMessage(), clearMessages(), setWaitingForResponse(), chatMessageToMessageData()
├── EventsStore.ts # events[], setEvents(), addEvent(), updateEvent(), deleteEvent()
└── ThemeStore.ts # theme, setTheme() - reactive theme switching with Zustand
│ └── ChatService.ts # sendMessage(), confirmEvent(deleteMode, occurrenceDate), rejectEvent(), getConversations(), getConversation(), updateProposalEvent()
── stores/ # Zustand state management
├── index.ts # Re-exports all stores
├── AuthStore.ts # user, isAuthenticated, isLoading, login(), logout(), loadStoredUser()
│ # Uses expo-secure-store (native) / localStorage (web)
├── ChatStore.ts # messages[], isWaitingForResponse, addMessage(), addMessages(), updateMessage(), clearMessages(), setWaitingForResponse(), chatMessageToMessageData()
├── EventsStore.ts # events[], setEvents(), addEvent(), updateEvent(), deleteEvent()
└── ThemeStore.ts # theme, setTheme() - reactive theme switching with Zustand
└── hooks/
└── useDropdownPosition.ts # Hook for positioning dropdowns relative to trigger element
```
**Routing:** Tab-based navigation with Chat, Calendar, and Settings as main screens. Auth screens (login, register) outside tabs. Dynamic routes for event detail and note editing.
@@ -223,7 +228,7 @@ src/
├── app.ts # Entry point, DI setup, Express config
├── controllers/ # Request handlers + middleware (per architecture diagram)
│ ├── AuthController.ts # login(), register(), refresh(), logout()
│ ├── ChatController.ts # sendMessage(), confirmEvent(), rejectEvent(), getConversations(), getConversation()
│ ├── ChatController.ts # sendMessage(), confirmEvent(), rejectEvent(), getConversations(), getConversation(), updateProposalEvent()
│ ├── EventController.ts # create(), getById(), getAll(), getByDateRange(), update(), delete()
│ ├── AuthMiddleware.ts # authenticate() - X-User-Id header validation
│ └── LoggingMiddleware.ts # httpLogger - pino-http request logging
@@ -290,6 +295,7 @@ src/
- `POST /api/chat/reject/:conversationId/:messageId` - Reject proposed event (protected)
- `GET /api/chat/conversations` - Get all conversations (protected)
- `GET /api/chat/conversations/:id` - Get messages of a conversation with cursor-based pagination (protected)
- `PUT /api/chat/messages/:messageId/proposal` - Update proposal event data before confirming (protected)
- `GET /health` - Health check
- `POST /api/ai/test` - AI test endpoint (development only)
@@ -465,8 +471,8 @@ NODE_ENV=development # development = pretty logs, production = JSON
- `utils/recurrenceExpander`: expandRecurringEvents() using rrule library for RRULE parsing
- `ChatController`: getConversations(), getConversation() with cursor-based pagination support
- `ChatService`: getConversations(), getConversation(), processMessage() uses real AI or test responses (via USE_TEST_RESPONSES), confirmEvent()/rejectEvent() update respondedAction and persist response messages
- `MongoChatRepository`: Full CRUD implemented (getConversationsByUser, createConversation, getMessages with cursor pagination, createMessage, updateMessage, updateProposalResponse)
- `ChatRepository` interface: updateMessage() and updateProposalResponse() for per-proposal respondedAction tracking
- `MongoChatRepository`: Full CRUD implemented (getConversationsByUser, createConversation, getMessages with cursor pagination, createMessage, updateMessage, updateProposalResponse, updateProposalEvent)
- `ChatRepository` interface: updateMessage(), updateProposalResponse(), updateProposalEvent() for per-proposal tracking
- `GPTAdapter`: Full implementation with OpenAI GPT (gpt-4o-mini model), function calling for calendar operations, collects multiple proposals per response
- `ai/utils/`: Provider-agnostic shared utilities (systemPrompt, toolDefinitions, toolExecutor, eventFormatter)
- `ai/utils/systemPrompt`: Includes RRULE documentation - AI knows to create separate events when times differ by day, warns AI not to put RRULE in description field
@@ -527,12 +533,12 @@ NODE_ENV=development # development = pretty logs, production = JSON
- Auto-scroll to end on new messages and keyboard show
- keyboardDismissMode="interactive" and keyboardShouldPersistTaps="handled"
- `EventService`: getAll(), getById(), getByDateRange(), create(), update(), delete(mode, occurrenceDate) - fully implemented with recurring delete modes
- `ChatService`: sendMessage(), confirmEvent(deleteMode, occurrenceDate), rejectEvent(), getConversations(), getConversation() - fully implemented with cursor pagination and recurring delete support
- `ChatService`: sendMessage(), confirmEvent(deleteMode, occurrenceDate), rejectEvent(), getConversations(), getConversation(), updateProposalEvent() - fully implemented with cursor pagination, recurring delete support, and proposal editing
- `CardBase`: Reusable card component with header (title/subtitle), content area, and optional footer button - configurable padding, border, text size via props, ScrollView uses `nestedScrollEnabled` for Android
- `ModalBase`: Reusable modal wrapper with backdrop (absolute-positioned behind card), uses CardBase internally - provides click-outside-to-close, Android back button support, and proper scrolling on Android
- `EventCardBase`: Event card with date/time/recurring icons - uses CardBase for structure
- `EventCard`: Uses EventCardBase + edit/delete buttons (TouchableOpacity with delayPressIn for scroll-friendly touch handling)
- `ProposedEventCard`: Uses EventCardBase + confirm/reject buttons for chat proposals, displays green highlighted text for new changes ("Neue Ausnahme: [date]" for single delete, "Neues Ende: [date]" for UNTIL updates)
- `ProposedEventCard`: Uses EventCardBase + confirm/reject/edit buttons for chat proposals, displays green highlighted text for new changes ("Neue Ausnahme: [date]" for single delete, "Neues Ende: [date]" for UNTIL updates). Edit button allows modifying proposals before confirming.
- `DeleteEventModal`: Delete confirmation modal using ModalBase - shows three options for recurring events (single/future/all), simple confirm for non-recurring
- `EventOverlay` (in calendar.tsx): Day events overlay using ModalBase - shows EventCards for selected day
- `Themes.tsx`: Theme definitions with THEMES object (defaultLight, defaultDark) including all color tokens (textPrimary, borderPrimary, eventIndicator, secondaryBg, shadowColor, etc.)
@@ -542,6 +548,11 @@ NODE_ENV=development # development = pretty logs, production = JSON
- `ChatBubble`: Reusable chat bubble component with Tailwind styling, used by ChatMessage and TypingIndicator
- `TypingIndicator`: Animated typing indicator component showing `. → .. → ...` loop while waiting for AI response
- Event Detail and Note screens exist as skeletons
- `editEvent.tsx`: Dual-mode event editor screen
- **Calendar mode**: Edit existing events, create new events - calls EventService API
- **Chat mode**: Edit AI-proposed events before confirming - updates ChatStore locally and persists to server via ChatService.updateProposalEvent()
- Route params: `mode` ('calendar' | 'chat'), `id?`, `date?`, `eventData?` (JSON), `proposalContext?` (JSON with messageId, proposalId, conversationId)
- Supports recurring events with RRULE configuration (daily/weekly/monthly/yearly)
## Building