Compare commits
2 Commits
c8aba94879
...
8e58ab4249
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e58ab4249 | |||
| 24ab6f0420 |
10
CLAUDE.md
10
CLAUDE.md
@@ -77,9 +77,10 @@ src/
|
|||||||
├── components/
|
├── components/
|
||||||
│ ├── BaseBackground.tsx # Common screen wrapper
|
│ ├── BaseBackground.tsx # Common screen wrapper
|
||||||
│ ├── Header.tsx # Header component
|
│ ├── Header.tsx # Header component
|
||||||
│ ├── EventCard.tsx # Event card for calendar display
|
│ ├── EventCardBase.tsx # Shared event card layout with icons (used by EventCard & ProposedEventCard)
|
||||||
|
│ ├── EventCard.tsx # Calendar event card (uses EventCardBase + edit/delete buttons)
|
||||||
│ ├── EventConfirmDialog.tsx # AI-proposed event confirmation modal
|
│ ├── EventConfirmDialog.tsx # AI-proposed event confirmation modal
|
||||||
│ └── ProposedEventCard.tsx # Inline event proposal with confirm/reject buttons
|
│ └── ProposedEventCard.tsx # Chat event proposal (uses EventCardBase + confirm/reject buttons)
|
||||||
├── Themes.tsx # Centralized color/theme definitions
|
├── Themes.tsx # Centralized color/theme definitions
|
||||||
├── services/
|
├── services/
|
||||||
│ ├── index.ts # Re-exports all services
|
│ ├── index.ts # Re-exports all services
|
||||||
@@ -305,8 +306,9 @@ MONGODB_URI=mongodb://root:mongoose@localhost:27017/calchat?authSource=admin
|
|||||||
- `ApiClient`: get(), post(), put(), delete() implemented
|
- `ApiClient`: get(), post(), put(), delete() implemented
|
||||||
- `EventService`: getAll(), getById(), getByDateRange(), create(), update(), delete() - fully 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
|
- `ChatService`: sendMessage(), confirmEvent(convId, msgId, action, event?, eventId?, updates?), rejectEvent() - supports create/update/delete actions
|
||||||
- `EventCard`: Displays event details (title, date, time, duration, recurring indicator) with Feather icons and edit/delete buttons
|
- `EventCardBase`: Shared base component with event layout (header, date/time/recurring icons, description) - used by both EventCard and ProposedEventCard
|
||||||
- `ProposedEventCard`: Displays proposed events (title, date, description, recurring indicator) with confirm/reject buttons
|
- `EventCard`: Uses EventCardBase + edit/delete buttons for calendar display
|
||||||
|
- `ProposedEventCard`: Uses EventCardBase + confirm/reject buttons for chat proposals (supports create/update/delete actions)
|
||||||
- `Themes.tsx`: Centralized color definitions including textPrimary, borderPrimary, eventIndicator
|
- `Themes.tsx`: Centralized color definitions including textPrimary, borderPrimary, eventIndicator
|
||||||
- `EventsStore`: Zustand store with setEvents(), addEvent(), updateEvent(), deleteEvent() - stores ExpandedEvent[]
|
- `EventsStore`: Zustand store with setEvents(), addEvent(), updateEvent(), deleteEvent() - stores ExpandedEvent[]
|
||||||
- `ChatStore`: Zustand store with addMessage(), updateMessage(), clearMessages() - persists messages across tab switches
|
- `ChatStore`: Zustand store with addMessage(), updateMessage(), clearMessages() - persists messages across tab switches
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { View, Text, Pressable } from "react-native";
|
import { View, Pressable } from "react-native";
|
||||||
import { ExpandedEvent } from "@caldav/shared";
|
import { ExpandedEvent } from "@caldav/shared";
|
||||||
import { Feather } from "@expo/vector-icons";
|
import { Feather } from "@expo/vector-icons";
|
||||||
import currentTheme from "../Themes";
|
import currentTheme from "../Themes";
|
||||||
|
import { EventCardBase } from "./EventCardBase";
|
||||||
|
|
||||||
type EventCardProps = {
|
type EventCardProps = {
|
||||||
event: ExpandedEvent;
|
event: ExpandedEvent;
|
||||||
@@ -9,117 +10,16 @@ type EventCardProps = {
|
|||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatDate(date: Date): string {
|
|
||||||
const d = new Date(date);
|
|
||||||
return d.toLocaleDateString("de-DE", {
|
|
||||||
weekday: "short",
|
|
||||||
day: "2-digit",
|
|
||||||
month: "2-digit",
|
|
||||||
year: "numeric",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTime(date: Date): string {
|
|
||||||
const d = new Date(date);
|
|
||||||
return d.toLocaleTimeString("de-DE", {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDuration(start: Date, end: Date): string {
|
|
||||||
const startDate = new Date(start);
|
|
||||||
const endDate = new Date(end);
|
|
||||||
const diffMs = endDate.getTime() - startDate.getTime();
|
|
||||||
const diffMins = Math.round(diffMs / 60000);
|
|
||||||
|
|
||||||
if (diffMins < 60) {
|
|
||||||
return `${diffMins} min`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hours = Math.floor(diffMins / 60);
|
|
||||||
const mins = diffMins % 60;
|
|
||||||
|
|
||||||
if (mins === 0) {
|
|
||||||
return hours === 1 ? "1 Std" : `${hours} Std`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${hours} Std ${mins} min`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const EventCard = ({ event, onEdit, onDelete }: EventCardProps) => {
|
export const EventCard = ({ event, onEdit, onDelete }: EventCardProps) => {
|
||||||
return (
|
return (
|
||||||
<View
|
<View className="mb-3">
|
||||||
className="rounded-xl overflow-hidden mb-3"
|
<EventCardBase
|
||||||
style={{ borderWidth: 2, borderColor: currentTheme.borderPrimary }}
|
title={event.title}
|
||||||
>
|
startTime={event.occurrenceStart}
|
||||||
{/* Header with title */}
|
endTime={event.occurrenceEnd}
|
||||||
<View
|
description={event.description}
|
||||||
className="px-3 py-2"
|
isRecurring={event.isRecurring}
|
||||||
style={{
|
|
||||||
backgroundColor: currentTheme.chatBot,
|
|
||||||
borderBottomWidth: 2,
|
|
||||||
borderBottomColor: currentTheme.borderPrimary,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Text className="font-bold text-base">{event.title}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<View className="px-3 py-2 bg-white">
|
|
||||||
{/* Date */}
|
|
||||||
<View className="flex-row items-center mb-1">
|
|
||||||
<Feather
|
|
||||||
name="calendar"
|
|
||||||
size={16}
|
|
||||||
color={currentTheme.textPrimary}
|
|
||||||
style={{ marginRight: 8 }}
|
|
||||||
/>
|
|
||||||
<Text style={{ color: currentTheme.textPrimary }}>
|
|
||||||
{formatDate(event.occurrenceStart)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Time with duration */}
|
|
||||||
<View className="flex-row items-center mb-1">
|
|
||||||
<Feather
|
|
||||||
name="clock"
|
|
||||||
size={16}
|
|
||||||
color={currentTheme.textPrimary}
|
|
||||||
style={{ marginRight: 8 }}
|
|
||||||
/>
|
|
||||||
<Text style={{ color: currentTheme.textPrimary }}>
|
|
||||||
{formatTime(event.occurrenceStart)} -{" "}
|
|
||||||
{formatTime(event.occurrenceEnd)} (
|
|
||||||
{formatDuration(event.occurrenceStart, event.occurrenceEnd)})
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Recurring indicator */}
|
|
||||||
{event.isRecurring && (
|
|
||||||
<View className="flex-row items-center mb-1">
|
|
||||||
<Feather
|
|
||||||
name="repeat"
|
|
||||||
size={16}
|
|
||||||
color={currentTheme.textPrimary}
|
|
||||||
style={{ marginRight: 8 }}
|
|
||||||
/>
|
|
||||||
<Text style={{ color: currentTheme.textPrimary }}>
|
|
||||||
Wiederkehrend
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Description */}
|
|
||||||
{event.description && (
|
|
||||||
<Text
|
|
||||||
style={{ color: currentTheme.textPrimary }}
|
|
||||||
className="text-sm mt-1"
|
|
||||||
>
|
|
||||||
{event.description}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Action buttons */}
|
{/* Action buttons */}
|
||||||
<View className="flex-row justify-end mt-3 gap-3">
|
<View className="flex-row justify-end mt-3 gap-3">
|
||||||
<Pressable
|
<Pressable
|
||||||
@@ -147,7 +47,7 @@ export const EventCard = ({ event, onEdit, onDelete }: EventCardProps) => {
|
|||||||
/>
|
/>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</EventCardBase>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
141
apps/client/src/components/EventCardBase.tsx
Normal file
141
apps/client/src/components/EventCardBase.tsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import { View, Text } from "react-native";
|
||||||
|
import { Feather } from "@expo/vector-icons";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import currentTheme from "../Themes";
|
||||||
|
|
||||||
|
type EventCardBaseProps = {
|
||||||
|
className?: string;
|
||||||
|
title: string;
|
||||||
|
startTime: Date;
|
||||||
|
endTime: Date;
|
||||||
|
description?: string;
|
||||||
|
isRecurring?: boolean;
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatDate(date: Date): string {
|
||||||
|
const d = new Date(date);
|
||||||
|
return d.toLocaleDateString("de-DE", {
|
||||||
|
weekday: "short",
|
||||||
|
day: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
year: "numeric",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(date: Date): string {
|
||||||
|
const d = new Date(date);
|
||||||
|
return d.toLocaleTimeString("de-DE", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDuration(start: Date, end: Date): string {
|
||||||
|
const startDate = new Date(start);
|
||||||
|
const endDate = new Date(end);
|
||||||
|
const diffMs = endDate.getTime() - startDate.getTime();
|
||||||
|
const diffMins = Math.round(diffMs / 60000);
|
||||||
|
|
||||||
|
if (diffMins < 60) {
|
||||||
|
return `${diffMins} min`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hours = Math.floor(diffMins / 60);
|
||||||
|
const mins = diffMins % 60;
|
||||||
|
|
||||||
|
if (mins === 0) {
|
||||||
|
return hours === 1 ? "1 Std" : `${hours} Std`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${hours} Std ${mins} min`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EventCardBase = ({
|
||||||
|
className,
|
||||||
|
title,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
description,
|
||||||
|
isRecurring,
|
||||||
|
children,
|
||||||
|
}: EventCardBaseProps) => {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
className={`rounded-xl overflow-hidden ${className}`}
|
||||||
|
style={{ borderWidth: 2, borderColor: currentTheme.borderPrimary }}
|
||||||
|
>
|
||||||
|
{/* Header with title */}
|
||||||
|
<View
|
||||||
|
className="px-3 py-2"
|
||||||
|
style={{
|
||||||
|
backgroundColor: currentTheme.chatBot,
|
||||||
|
borderBottomWidth: 2,
|
||||||
|
borderBottomColor: currentTheme.borderPrimary,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text className="font-bold text-base">{title}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<View className="px-3 py-2 bg-white">
|
||||||
|
{/* Date */}
|
||||||
|
<View className="flex-row items-center mb-1">
|
||||||
|
<Feather
|
||||||
|
name="calendar"
|
||||||
|
size={16}
|
||||||
|
color={currentTheme.textPrimary}
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
/>
|
||||||
|
<Text style={{ color: currentTheme.textPrimary }}>
|
||||||
|
{formatDate(startTime)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Time with duration */}
|
||||||
|
<View className="flex-row items-center mb-1">
|
||||||
|
<Feather
|
||||||
|
name="clock"
|
||||||
|
size={16}
|
||||||
|
color={currentTheme.textPrimary}
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
/>
|
||||||
|
<Text style={{ color: currentTheme.textPrimary }}>
|
||||||
|
{formatTime(startTime)} - {formatTime(endTime)} (
|
||||||
|
{formatDuration(startTime, endTime)})
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Recurring indicator */}
|
||||||
|
{isRecurring && (
|
||||||
|
<View className="flex-row items-center mb-1">
|
||||||
|
<Feather
|
||||||
|
name="repeat"
|
||||||
|
size={16}
|
||||||
|
color={currentTheme.textPrimary}
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
/>
|
||||||
|
<Text style={{ color: currentTheme.textPrimary }}>
|
||||||
|
Wiederkehrend
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
{description && (
|
||||||
|
<Text
|
||||||
|
style={{ color: currentTheme.textPrimary }}
|
||||||
|
className="text-sm mt-1"
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Action buttons slot */}
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventCardBase;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { View, Text, Pressable } from "react-native";
|
import { View, Text, Pressable } from "react-native";
|
||||||
import { ProposedEventChange } from "@caldav/shared";
|
import { ProposedEventChange } from "@caldav/shared";
|
||||||
import currentTheme from "../Themes";
|
import currentTheme from "../Themes";
|
||||||
|
import { EventCardBase } from "./EventCardBase";
|
||||||
|
|
||||||
type ProposedEventCardProps = {
|
type ProposedEventCardProps = {
|
||||||
proposedChange: ProposedEventChange;
|
proposedChange: ProposedEventChange;
|
||||||
@@ -9,17 +10,52 @@ type ProposedEventCardProps = {
|
|||||||
onReject: () => void;
|
onReject: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatDateTime(date?: Date): string {
|
const ConfirmRejectButtons = ({
|
||||||
if (!date) return "";
|
isDisabled,
|
||||||
const d = new Date(date);
|
respondedAction,
|
||||||
return d.toLocaleDateString("de-DE", {
|
onConfirm,
|
||||||
weekday: "long",
|
onReject,
|
||||||
day: "2-digit",
|
}: {
|
||||||
month: "2-digit",
|
isDisabled: boolean;
|
||||||
hour: "2-digit",
|
respondedAction?: "confirm" | "reject";
|
||||||
minute: "2-digit",
|
onConfirm: () => void;
|
||||||
});
|
onReject: () => void;
|
||||||
}
|
}) => (
|
||||||
|
<View className="flex-row mt-3 gap-2">
|
||||||
|
<Pressable
|
||||||
|
onPress={onConfirm}
|
||||||
|
disabled={isDisabled}
|
||||||
|
className="flex-1 py-2 rounded-lg items-center"
|
||||||
|
style={{
|
||||||
|
backgroundColor: isDisabled
|
||||||
|
? currentTheme.disabledButton
|
||||||
|
: currentTheme.confirmButton,
|
||||||
|
borderWidth: respondedAction === "confirm" ? 2 : 0,
|
||||||
|
borderColor: currentTheme.confirmButton,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={{ color: currentTheme.buttonText }} className="font-medium">
|
||||||
|
Annehmen
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable
|
||||||
|
onPress={onReject}
|
||||||
|
disabled={isDisabled}
|
||||||
|
className="flex-1 py-2 rounded-lg items-center"
|
||||||
|
style={{
|
||||||
|
backgroundColor: isDisabled
|
||||||
|
? currentTheme.disabledButton
|
||||||
|
: currentTheme.rejectButton,
|
||||||
|
borderWidth: respondedAction === "reject" ? 2 : 0,
|
||||||
|
borderColor: currentTheme.rejectButton,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={{ color: currentTheme.buttonText }} className="font-medium">
|
||||||
|
Ablehnen
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
export const ProposedEventCard = ({
|
export const ProposedEventCard = ({
|
||||||
proposedChange,
|
proposedChange,
|
||||||
@@ -30,71 +66,27 @@ export const ProposedEventCard = ({
|
|||||||
const event = proposedChange.event;
|
const event = proposedChange.event;
|
||||||
const isDisabled = !!respondedAction;
|
const isDisabled = !!respondedAction;
|
||||||
|
|
||||||
return (
|
if (!event) {
|
||||||
<View
|
return null;
|
||||||
className="border-t p-2 mt-2"
|
}
|
||||||
style={{ borderTopColor: currentTheme.placeholderBg }}
|
|
||||||
>
|
|
||||||
{/* Event Details */}
|
|
||||||
<Text className="font-bold text-base">{event?.title}</Text>
|
|
||||||
<Text style={{ color: currentTheme.textSecondary }}>
|
|
||||||
{formatDateTime(event?.startTime)}
|
|
||||||
</Text>
|
|
||||||
{event?.description && (
|
|
||||||
<Text
|
|
||||||
style={{ color: currentTheme.textSecondary }}
|
|
||||||
className="text-sm mt-1"
|
|
||||||
>
|
|
||||||
{event.description}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
{event?.isRecurring && (
|
|
||||||
<Text style={{ color: currentTheme.textMuted }} className="text-sm">
|
|
||||||
Wiederkehrend
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Buttons */}
|
return (
|
||||||
<View className="flex-row mt-3 gap-2">
|
<View className="mt-2">
|
||||||
<Pressable
|
<EventCardBase
|
||||||
onPress={onConfirm}
|
className="m-2"
|
||||||
disabled={isDisabled}
|
title={event.title}
|
||||||
className="flex-1 py-2 rounded-lg items-center"
|
startTime={event.startTime}
|
||||||
style={{
|
endTime={event.endTime}
|
||||||
backgroundColor: isDisabled
|
description={event.description}
|
||||||
? currentTheme.disabledButton
|
isRecurring={event.isRecurring}
|
||||||
: currentTheme.confirmButton,
|
>
|
||||||
borderWidth: respondedAction === "confirm" ? 2 : 0,
|
<ConfirmRejectButtons
|
||||||
borderColor: currentTheme.confirmButton,
|
isDisabled={isDisabled}
|
||||||
}}
|
respondedAction={respondedAction}
|
||||||
>
|
onConfirm={onConfirm}
|
||||||
<Text
|
onReject={onReject}
|
||||||
style={{ color: currentTheme.buttonText }}
|
/>
|
||||||
className="font-medium"
|
</EventCardBase>
|
||||||
>
|
|
||||||
Annehmen
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable
|
|
||||||
onPress={onReject}
|
|
||||||
disabled={isDisabled}
|
|
||||||
className="flex-1 py-2 rounded-lg items-center"
|
|
||||||
style={{
|
|
||||||
backgroundColor: isDisabled
|
|
||||||
? currentTheme.disabledButton
|
|
||||||
: currentTheme.rejectButton,
|
|
||||||
borderWidth: respondedAction === "reject" ? 2 : 0,
|
|
||||||
borderColor: currentTheme.rejectButton,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={{ color: currentTheme.buttonText }}
|
|
||||||
className="font-medium"
|
|
||||||
>
|
|
||||||
Ablehnen
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,16 @@ let responseIndex = 0;
|
|||||||
// Static test responses (event proposals)
|
// Static test responses (event proposals)
|
||||||
const staticResponses: TestResponse[] = [
|
const staticResponses: TestResponse[] = [
|
||||||
// {{{
|
// {{{
|
||||||
// Response 0: Meeting mit Jens - next Friday 14:00
|
// Response 0: Help response (text only)
|
||||||
|
{
|
||||||
|
content:
|
||||||
|
"Ich bin dein Kalender-Assistent! Du kannst mir einfach sagen, welche Termine du erstellen, ändern oder löschen möchtest. Zum Beispiel:\n\n" +
|
||||||
|
'• "Erstelle einen Termin für morgen um 15 Uhr"\n' +
|
||||||
|
'• "Was habe ich nächste Woche vor?"\n' +
|
||||||
|
'• "Verschiebe das Meeting auf Donnerstag"\n\n' +
|
||||||
|
"Wie kann ich dir helfen?",
|
||||||
|
},
|
||||||
|
// Response 1: Meeting mit Jens - next Friday 14:00
|
||||||
{
|
{
|
||||||
content:
|
content:
|
||||||
"Alles klar! Ich erstelle dir einen Termin für das Meeting mit Jens am nächsten Freitag um 14:00 Uhr:",
|
"Alles klar! Ich erstelle dir einen Termin für das Meeting mit Jens am nächsten Freitag um 14:00 Uhr:",
|
||||||
@@ -35,7 +44,7 @@ const staticResponses: TestResponse[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Response 1: Recurring event - every Saturday 10:00
|
// Response 2: Recurring event - every Saturday 10:00
|
||||||
{
|
{
|
||||||
content:
|
content:
|
||||||
"Verstanden! Ich erstelle einen wiederkehrenden Termin: Jeden Samstag um 10:00 Uhr Badezimmer putzen:",
|
"Verstanden! Ich erstelle einen wiederkehrenden Termin: Jeden Samstag um 10:00 Uhr Badezimmer putzen:",
|
||||||
@@ -50,11 +59,11 @@ const staticResponses: TestResponse[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Response 2: 2-week overview (DYNAMIC - placeholder)
|
// Response 3: 2-week overview (DYNAMIC - placeholder)
|
||||||
{ content: "" },
|
{ content: "" },
|
||||||
// Response 3: Delete "Meeting mit Jens" (DYNAMIC - placeholder)
|
// Response 4: Delete "Meeting mit Jens" (DYNAMIC - placeholder)
|
||||||
{ content: "" },
|
{ content: "" },
|
||||||
// Response 4: Doctor appointment with description
|
// Response 5: Doctor appointment with description
|
||||||
{
|
{
|
||||||
content:
|
content:
|
||||||
"Ich habe dir einen Arzttermin eingetragen. Denk daran, deine Versichertenkarte mitzunehmen!",
|
"Ich habe dir einen Arzttermin eingetragen. Denk daran, deine Versichertenkarte mitzunehmen!",
|
||||||
@@ -68,7 +77,7 @@ const staticResponses: TestResponse[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Response 5: Birthday - yearly recurring
|
// Response 6: Birthday - yearly recurring
|
||||||
{
|
{
|
||||||
content:
|
content:
|
||||||
"Geburtstage vergisst man leicht - aber nicht mit mir! Ich habe Mamas Geburtstag eingetragen:",
|
"Geburtstage vergisst man leicht - aber nicht mit mir! Ich habe Mamas Geburtstag eingetragen:",
|
||||||
@@ -83,7 +92,7 @@ const staticResponses: TestResponse[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Response 6: Gym - recurring for 2 months (8 weeks)
|
// Response 7: Gym - recurring for 2 months (8 weeks)
|
||||||
{
|
{
|
||||||
content:
|
content:
|
||||||
"Perfekt! Ich habe dein Probetraining eingetragen - jeden Dienstag für die nächsten 2 Monate:",
|
"Perfekt! Ich habe dein Probetraining eingetragen - jeden Dienstag für die nächsten 2 Monate:",
|
||||||
@@ -98,17 +107,8 @@ const staticResponses: TestResponse[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Response 7: 1-week overview (DYNAMIC - placeholder)
|
// Response 8: 1-week overview (DYNAMIC - placeholder)
|
||||||
{ content: "" },
|
{ content: "" },
|
||||||
// Response 8: Help response (text only)
|
|
||||||
{
|
|
||||||
content:
|
|
||||||
"Ich bin dein Kalender-Assistent! Du kannst mir einfach sagen, welche Termine du erstellen, ändern oder löschen möchtest. Zum Beispiel:\n\n" +
|
|
||||||
'• "Erstelle einen Termin für morgen um 15 Uhr"\n' +
|
|
||||||
'• "Was habe ich nächste Woche vor?"\n' +
|
|
||||||
'• "Verschiebe das Meeting auf Donnerstag"\n\n' +
|
|
||||||
"Wie kann ich dir helfen?",
|
|
||||||
},
|
|
||||||
// Response 9: Phone call - short appointment (Wednesday, so +2 days = Friday)
|
// Response 9: Phone call - short appointment (Wednesday, so +2 days = Friday)
|
||||||
{
|
{
|
||||||
content:
|
content:
|
||||||
@@ -165,28 +165,34 @@ async function getTestResponse(
|
|||||||
const responseIdx = index % staticResponses.length;
|
const responseIdx = index % staticResponses.length;
|
||||||
|
|
||||||
// Dynamic responses: fetch events from DB and format
|
// Dynamic responses: fetch events from DB and format
|
||||||
if (responseIdx === 2) {
|
if (responseIdx === 3) {
|
||||||
return { content: await getWeeksOverview(eventRepo, userId, 2) };
|
return { content: await getWeeksOverview(eventRepo, userId, 2) };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseIdx === 3) {
|
if (responseIdx === 4) {
|
||||||
// Delete "Meeting mit Jens"
|
// Delete "Meeting mit Jens"
|
||||||
const events = await eventRepo.findByUserId(userId);
|
const events = await eventRepo.findByUserId(userId);
|
||||||
const jensEvent = events.find((e) => e.title === "Meeting mit Jens");
|
const jensEvent = events.find((e) => e.title === "Meeting mit Jens");
|
||||||
if (jensEvent) {
|
if (jensEvent) {
|
||||||
return {
|
return {
|
||||||
content:
|
content: "Soll ich diesen Termin wirklich löschen?",
|
||||||
"Alles klar, ich lösche den Termin 'Meeting mit Jens' für dich:",
|
|
||||||
proposedChange: {
|
proposedChange: {
|
||||||
action: "delete",
|
action: "delete",
|
||||||
eventId: jensEvent.id,
|
eventId: jensEvent.id,
|
||||||
|
event: {
|
||||||
|
title: jensEvent.title,
|
||||||
|
startTime: jensEvent.startTime,
|
||||||
|
endTime: jensEvent.endTime,
|
||||||
|
description: jensEvent.description,
|
||||||
|
isRecurring: jensEvent.isRecurring,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { content: "Ich konnte keinen Termin 'Meeting mit Jens' finden." };
|
return { content: "Ich konnte keinen Termin 'Meeting mit Jens' finden." };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseIdx === 7) {
|
if (responseIdx === 8) {
|
||||||
return { content: await getWeeksOverview(eventRepo, userId, 1) };
|
return { content: await getWeeksOverview(eventRepo, userId, 1) };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,7 +291,9 @@ export class ChatService {
|
|||||||
: "Termin nicht gefunden.";
|
: "Termin nicht gefunden.";
|
||||||
} else if (action === "delete" && eventId) {
|
} else if (action === "delete" && eventId) {
|
||||||
await this.eventRepo.delete(eventId);
|
await this.eventRepo.delete(eventId);
|
||||||
content = "Der Termin wurde gelöscht.";
|
content = event?.title
|
||||||
|
? `Der Termin "${event.title}" wurde gelöscht.`
|
||||||
|
: "Der Termin wurde gelöscht.";
|
||||||
} else {
|
} else {
|
||||||
content = "Ungültige Aktion.";
|
content = "Ungültige Aktion.";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user