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:
@@ -9,7 +9,7 @@ import {
|
||||
} from "react-native";
|
||||
import { useThemeStore } from "../../stores/ThemeStore";
|
||||
import React, { useState, useRef, useEffect, useCallback } from "react";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import { useFocusEffect, router } from "expo-router";
|
||||
import Header from "../../components/Header";
|
||||
import BaseBackground from "../../components/BaseBackground";
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
@@ -38,6 +38,7 @@ type ChatMessageProps = {
|
||||
proposedChanges?: ProposedEventChange[];
|
||||
onConfirm?: (proposalId: string, proposal: ProposedEventChange) => void;
|
||||
onReject?: (proposalId: string) => void;
|
||||
onEdit?: (proposalId: string, proposal: ProposedEventChange) => void;
|
||||
};
|
||||
|
||||
type ChatInputProps = {
|
||||
@@ -62,6 +63,7 @@ const Chat = () => {
|
||||
const [currentConversationId, setCurrentConversationId] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [hasLoadedMessages, setHasLoadedMessages] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const keyboardDidShow = Keyboard.addListener(
|
||||
@@ -71,10 +73,11 @@ const Chat = () => {
|
||||
return () => keyboardDidShow.remove();
|
||||
}, []);
|
||||
|
||||
// Load existing messages from database once authenticated and screen is focused
|
||||
// Load existing messages from database only once (on initial mount)
|
||||
// Skip on subsequent focus events to preserve local edits (e.g., edited proposals)
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (isAuthLoading || !isAuthenticated) return;
|
||||
if (isAuthLoading || !isAuthenticated || hasLoadedMessages) return;
|
||||
|
||||
const fetchMessages = async () => {
|
||||
try {
|
||||
@@ -91,10 +94,12 @@ const Chat = () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load messages:", error);
|
||||
} finally {
|
||||
setHasLoadedMessages(true);
|
||||
}
|
||||
};
|
||||
fetchMessages();
|
||||
}, [isAuthLoading, isAuthenticated]),
|
||||
}, [isAuthLoading, isAuthenticated, hasLoadedMessages]),
|
||||
);
|
||||
|
||||
const scrollToEnd = () => {
|
||||
@@ -159,6 +164,22 @@ const Chat = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditProposal = (
|
||||
messageId: string,
|
||||
conversationId: string,
|
||||
proposalId: string,
|
||||
proposal: ProposedEventChange,
|
||||
) => {
|
||||
router.push({
|
||||
pathname: "/editEvent",
|
||||
params: {
|
||||
mode: "chat",
|
||||
eventData: JSON.stringify(proposal.event),
|
||||
proposalContext: JSON.stringify({ messageId, proposalId, conversationId }),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleSend = async (text: string) => {
|
||||
// Show user message immediately
|
||||
const userMessage: MessageData = {
|
||||
@@ -238,6 +259,14 @@ const Chat = () => {
|
||||
proposalId,
|
||||
)
|
||||
}
|
||||
onEdit={(proposalId, proposal) =>
|
||||
handleEditProposal(
|
||||
item.id,
|
||||
item.conversationId!,
|
||||
proposalId,
|
||||
proposal,
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
keyExtractor={(item) => item.id}
|
||||
@@ -334,6 +363,7 @@ const ChatMessage = ({
|
||||
proposedChanges,
|
||||
onConfirm,
|
||||
onReject,
|
||||
onEdit,
|
||||
}: ChatMessageProps) => {
|
||||
const { theme } = useThemeStore();
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
@@ -361,7 +391,7 @@ const ChatMessage = ({
|
||||
{content}
|
||||
</Text>
|
||||
|
||||
{hasProposals && currentProposal && onConfirm && onReject && (
|
||||
{hasProposals && currentProposal && onConfirm && onReject && onEdit && (
|
||||
<View>
|
||||
{/* Event card with optional navigation arrows */}
|
||||
<View className="flex-row items-center">
|
||||
@@ -381,8 +411,9 @@ const ChatMessage = ({
|
||||
<View className="flex-1">
|
||||
<ProposedEventCard
|
||||
proposedChange={currentProposal}
|
||||
onConfirm={() => onConfirm(currentProposal.id, currentProposal)}
|
||||
onConfirm={(proposal) => onConfirm(proposal.id, proposal)}
|
||||
onReject={() => onReject(currentProposal.id)}
|
||||
onEdit={(proposal) => onEdit(proposal.id, proposal)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user