feat: add RRULE parsing to shared package and improve ProposedEventCard UI
- Add rrule library to shared package for RRULE string parsing - Add rruleHelpers.ts with parseRRule() returning freq, until, count, interval, byDay - Add formatters.ts with German date/time formatters for client and server - Extend CreateEventDTO with exceptionDates field for proposals - Extend ChatModel schema with exceptionDates, deleteMode, occurrenceDate - Update proposeUpdateEvent tool to support isRecurring and recurrenceRule params - ProposedEventCard now shows green "Neue Ausnahme" and "Neues Ende" text - Add Sport test scenario with dynamic exception and UNTIL responses - Update CLAUDE.md documentation
This commit is contained in:
@@ -28,6 +28,7 @@ export interface CreateEventDTO {
|
||||
note?: string;
|
||||
isRecurring?: boolean;
|
||||
recurrenceRule?: string;
|
||||
exceptionDates?: string[]; // For display in proposals
|
||||
}
|
||||
|
||||
export interface UpdateEventDTO {
|
||||
|
||||
48
packages/shared/src/utils/formatters.ts
Normal file
48
packages/shared/src/utils/formatters.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* German date/time formatting helpers.
|
||||
* Used across client and server.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Format date as DD.MM.YYYY
|
||||
*/
|
||||
export function formatDate(date: Date): string {
|
||||
const d = new Date(date);
|
||||
return d.toLocaleDateString("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time as HH:MM
|
||||
*/
|
||||
export function formatTime(date: Date): string {
|
||||
const d = new Date(date);
|
||||
return d.toLocaleTimeString("de-DE", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date and time as DD.MM.YYYY HH:MM:SS
|
||||
*/
|
||||
export function formatDateTime(date: Date): string {
|
||||
const d = new Date(date);
|
||||
return `${formatDate(d)} ${d.toLocaleTimeString("de-DE")}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date with weekday as "Mo., DD.MM.YYYY"
|
||||
*/
|
||||
export function formatDateWithWeekday(date: Date): string {
|
||||
const d = new Date(date);
|
||||
return d.toLocaleDateString("de-DE", {
|
||||
weekday: "short",
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
export * from "./dateHelpers";
|
||||
export * from "./rruleHelpers";
|
||||
export * from "./formatters";
|
||||
|
||||
49
packages/shared/src/utils/rruleHelpers.ts
Normal file
49
packages/shared/src/utils/rruleHelpers.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { rrulestr, Frequency } from "rrule";
|
||||
|
||||
export interface ParsedRRule {
|
||||
freq: string; // "YEARLY", "MONTHLY", "WEEKLY", "DAILY", etc.
|
||||
until?: Date;
|
||||
count?: number;
|
||||
interval?: number;
|
||||
byDay?: string[]; // ["MO", "WE", "FR"]
|
||||
}
|
||||
|
||||
const FREQ_NAMES: Record<Frequency, string> = {
|
||||
[Frequency.YEARLY]: "YEARLY",
|
||||
[Frequency.MONTHLY]: "MONTHLY",
|
||||
[Frequency.WEEKLY]: "WEEKLY",
|
||||
[Frequency.DAILY]: "DAILY",
|
||||
[Frequency.HOURLY]: "HOURLY",
|
||||
[Frequency.MINUTELY]: "MINUTELY",
|
||||
[Frequency.SECONDLY]: "SECONDLY",
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses an RRULE string and extracts the relevant fields.
|
||||
* Handles both with and without "RRULE:" prefix.
|
||||
*/
|
||||
export function parseRRule(ruleString: string): ParsedRRule | null {
|
||||
if (!ruleString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Ensure RRULE: prefix is present
|
||||
const normalized = ruleString.startsWith("RRULE:")
|
||||
? ruleString
|
||||
: `RRULE:${ruleString}`;
|
||||
|
||||
const rule = rrulestr(normalized);
|
||||
const options = rule.options;
|
||||
|
||||
return {
|
||||
freq: FREQ_NAMES[options.freq] || "UNKNOWN",
|
||||
until: options.until || undefined,
|
||||
count: options.count || undefined,
|
||||
interval: options.interval > 1 ? options.interval : undefined,
|
||||
byDay: options.byweekday?.map((d) => d.toString()) || undefined,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user