diff --git a/.gitignore b/.gitignore index 8b1024b..e4a1afd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules *.tsbuildinfo docs/praesi_2_context.md docs/*.png +.env diff --git a/CLAUDE.md b/CLAUDE.md index 1998451..ea76a58 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -296,7 +296,7 @@ MONGODB_URI=mongodb://root:mongoose@localhost:27017/calchat?authSource=admin - Tab navigation (Chat, Calendar) implemented with basic UI - Calendar screen fully functional: - Month navigation with grid display and Ionicons (chevron-back/forward) - - MonthSelector dropdown with infinite scroll (dynamically loads months) + - MonthSelector dropdown with infinite scroll (dynamically loads months, lazy-loaded when modal opens, cleared on close for memory efficiency) - Events loaded from API via EventService.getByDateRange() - Orange dot indicator for days with events - Tap-to-open modal overlay showing EventCards for selected day @@ -304,6 +304,9 @@ MONGODB_URI=mongodb://root:mongoose@localhost:27017/calchat?authSource=admin - Uses `useFocusEffect` for automatic reload on tab focus - Chat screen functional with FlashList, message sending, and event confirm/reject - Messages persisted via ChatStore (survives tab switches) + - KeyboardAvoidingView for proper keyboard handling (iOS padding, Android height) + - Auto-scroll to end on new messages and keyboard show + - keyboardDismissMode="interactive" and keyboardShouldPersistTaps="handled" - `ApiClient`: get(), post(), put(), delete() implemented - `EventService`: getAll(), getById(), getByDateRange(), create(), update(), delete() - fully implemented - `ChatService`: sendMessage(), confirmEvent(convId, msgId, action, event?, eventId?, updates?), rejectEvent() - supports create/update/delete actions diff --git a/apps/client/src/app/(tabs)/calendar.tsx b/apps/client/src/app/(tabs)/calendar.tsx index 9d016c4..0c2b393 100644 --- a/apps/client/src/app/(tabs)/calendar.tsx +++ b/apps/client/src/app/(tabs)/calendar.tsx @@ -323,16 +323,7 @@ const MonthSelector = ({ const listRef = useRef>>(null); const INITIAL_RANGE = 12; // 12 months before and after current - const [monthSelectorData, setMonthSelectorData] = useState(() => - generateMonths(currentYear, currentMonthIndex, INITIAL_RANGE), - ); - - // Reset data when current month changes - useEffect(() => { - setMonthSelectorData( - generateMonths(currentYear, currentMonthIndex, INITIAL_RANGE), - ); - }, [currentYear, currentMonthIndex]); + const [monthSelectorData, setMonthSelectorData] = useState([]); const appendMonths = (direction: "start" | "end", count: number) => { setMonthSelectorData((prevData) => { @@ -378,6 +369,10 @@ const MonthSelector = ({ useEffect(() => { if (modalVisible) { + // Generate fresh data centered on current month + setMonthSelectorData( + generateMonths(currentYear, currentMonthIndex, INITIAL_RANGE), + ); Animated.timing(heightAnim, { toValue: 200, duration: 200, @@ -385,8 +380,10 @@ const MonthSelector = ({ }).start(); } else { heightAnim.setValue(0); + // Clear data when closing + setMonthSelectorData([]); } - }, [modalVisible, heightAnim]); + }, [modalVisible, heightAnim, currentYear, currentMonthIndex]); const renderItem = ({ item }: { item: MonthItem }) => ( { const { messages, addMessage, updateMessage } = useChatStore(); + const listRef = useRef>(null); + + useEffect(() => { + const keyboardDidShow = Keyboard.addListener("keyboardDidShow", scrollToEnd); + return () => keyboardDidShow.remove(); + }, []); + + const scrollToEnd = () => { + setTimeout(() => { + listRef.current?.scrollToEnd({ animated: true }); + }, 100); + }; const handleEventResponse = async ( action: "confirm" | "reject", @@ -61,6 +73,7 @@ const Chat = () => { conversationId: response.conversationId, }; addMessage(botMessage); + scrollToEnd(); } catch (error) { console.error(`Failed to ${action} event:`, error); // Revert on error @@ -76,6 +89,7 @@ const Chat = () => { content: text, }; addMessage(userMessage); + scrollToEnd(); try { // Fetch server response @@ -90,6 +104,7 @@ const Chat = () => { conversationId: response.conversationId, }; addMessage(botMessage); + scrollToEnd(); } catch (error) { console.error("Failed to send message:", error); } @@ -98,30 +113,38 @@ const Chat = () => { return ( - ( - - handleEventResponse( - "confirm", - item.id, - item.conversationId!, - item.proposedChange, - ) - } - onReject={() => - handleEventResponse("reject", item.id, item.conversationId!) - } - /> - )} - keyExtractor={(item) => item.id} - /> - + + ( + + handleEventResponse( + "confirm", + item.id, + item.conversationId!, + item.proposedChange, + ) + } + onReject={() => + handleEventResponse("reject", item.id, item.conversationId!) + } + /> + )} + keyExtractor={(item) => item.id} + keyboardDismissMode="interactive" + keyboardShouldPersistTaps="handled" + /> + + ); };