feat: add recurring event deletion with three modes
Implement three deletion modes for recurring events: - single: exclude specific occurrence via EXDATE mechanism - future: set RRULE UNTIL to stop future occurrences - all: delete entire event series Changes include: - Add exceptionDates field to CalendarEvent model - Add RecurringDeleteMode type and DeleteRecurringEventDTO - EventService.deleteRecurring() with mode-based logic using rrule library - EventController DELETE endpoint accepts mode/occurrenceDate query params - recurrenceExpander filters out exception dates during expansion - AI tools support deleteMode and occurrenceDate for proposed deletions - ChatService.confirmEvent() handles recurring delete modes - New DeleteEventModal component for unified delete confirmation UI - Calendar screen integrates modal for both recurring and non-recurring events
This commit is contained in:
@@ -20,7 +20,7 @@ import {
|
||||
chatMessageToMessageData,
|
||||
MessageData,
|
||||
} from "../../stores";
|
||||
import { ProposedEventChange } from "@calchat/shared";
|
||||
import { ProposedEventChange, RespondedAction } from "@calchat/shared";
|
||||
import { ProposedEventCard } from "../../components/ProposedEventCard";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import TypingIndicator from "../../components/TypingIndicator";
|
||||
@@ -104,7 +104,7 @@ const Chat = () => {
|
||||
};
|
||||
|
||||
const handleEventResponse = async (
|
||||
action: "confirm" | "reject",
|
||||
action: RespondedAction,
|
||||
messageId: string,
|
||||
conversationId: string,
|
||||
proposalId: string,
|
||||
@@ -114,7 +114,9 @@ const Chat = () => {
|
||||
const message = messages.find((m) => m.id === messageId);
|
||||
if (message?.proposedChanges) {
|
||||
const updatedProposals = message.proposedChanges.map((p) =>
|
||||
p.id === proposalId ? { ...p, respondedAction: action as "confirm" | "reject" } : p,
|
||||
p.id === proposalId
|
||||
? { ...p, respondedAction: action }
|
||||
: p,
|
||||
);
|
||||
updateMessage(messageId, { proposedChanges: updatedProposals });
|
||||
}
|
||||
@@ -130,8 +132,14 @@ const Chat = () => {
|
||||
proposedChange.event,
|
||||
proposedChange.eventId,
|
||||
proposedChange.updates,
|
||||
proposedChange.deleteMode,
|
||||
proposedChange.occurrenceDate,
|
||||
)
|
||||
: await ChatService.rejectEvent(conversationId, messageId, proposalId);
|
||||
: await ChatService.rejectEvent(
|
||||
conversationId,
|
||||
messageId,
|
||||
proposalId,
|
||||
);
|
||||
|
||||
const botMessage: MessageData = {
|
||||
id: response.message.id,
|
||||
@@ -225,14 +233,21 @@ const Chat = () => {
|
||||
)
|
||||
}
|
||||
onReject={(proposalId) =>
|
||||
handleEventResponse("reject", item.id, item.conversationId!, proposalId)
|
||||
handleEventResponse(
|
||||
"reject",
|
||||
item.id,
|
||||
item.conversationId!,
|
||||
proposalId,
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
keyExtractor={(item) => item.id}
|
||||
keyboardDismissMode="interactive"
|
||||
keyboardShouldPersistTaps="handled"
|
||||
ListFooterComponent={isWaitingForResponse ? <TypingIndicator /> : null}
|
||||
ListFooterComponent={
|
||||
isWaitingForResponse ? <TypingIndicator /> : null
|
||||
}
|
||||
/>
|
||||
<ChatInput onSend={handleSend} />
|
||||
</KeyboardAvoidingView>
|
||||
@@ -251,7 +266,9 @@ const ChatHeader = () => {
|
||||
borderColor: theme.primeFg,
|
||||
}}
|
||||
></View>
|
||||
<Text className="text-lg pl-3" style={{ color: theme.textPrimary }}>CalChat</Text>
|
||||
<Text className="text-lg pl-3" style={{ color: theme.textPrimary }}>
|
||||
CalChat
|
||||
</Text>
|
||||
<View
|
||||
className="h-2 bg-black"
|
||||
style={{
|
||||
@@ -329,9 +346,7 @@ const ChatMessage = ({
|
||||
|
||||
const goToPrev = () => setCurrentIndex((i) => Math.max(0, i - 1));
|
||||
const goToNext = () =>
|
||||
setCurrentIndex((i) =>
|
||||
Math.min((proposedChanges?.length || 1) - 1, i + 1),
|
||||
);
|
||||
setCurrentIndex((i) => Math.min((proposedChanges?.length || 1) - 1, i + 1));
|
||||
|
||||
const canGoPrev = currentIndex > 0;
|
||||
const canGoNext = currentIndex < (proposedChanges?.length || 1) - 1;
|
||||
@@ -344,7 +359,9 @@ const ChatMessage = ({
|
||||
minWidth: hasProposals ? "75%" : undefined,
|
||||
}}
|
||||
>
|
||||
<Text className="p-2" style={{ color: theme.textPrimary }}>{content}</Text>
|
||||
<Text className="p-2" style={{ color: theme.textPrimary }}>
|
||||
{content}
|
||||
</Text>
|
||||
|
||||
{hasProposals && currentProposal && onConfirm && onReject && (
|
||||
<View>
|
||||
@@ -358,11 +375,7 @@ const ChatMessage = ({
|
||||
className="p-1"
|
||||
style={{ opacity: canGoPrev ? 1 : 0.3 }}
|
||||
>
|
||||
<Ionicons
|
||||
name="chevron-back"
|
||||
size={24}
|
||||
color={theme.primeFg}
|
||||
/>
|
||||
<Ionicons name="chevron-back" size={24} color={theme.primeFg} />
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user