Add CaldavConfigStore and preloadAppData() to load events (current month) and CalDAV config into stores before dismissing the auth loading spinner. This prevents the brief empty flash when first navigating to Calendar or Settings tabs. Also applies Prettier formatting across codebase.
188 lines
5.7 KiB
TypeScript
188 lines
5.7 KiB
TypeScript
import { View, Text, Pressable } from "react-native";
|
|
import { Feather, Ionicons } from "@expo/vector-icons";
|
|
import { ProposedEventChange, formatDate, formatTime } from "@calchat/shared";
|
|
import { rrulestr } from "rrule";
|
|
import { useThemeStore } from "../stores/ThemeStore";
|
|
import { EventCardBase } from "./EventCardBase";
|
|
|
|
type ProposedEventCardProps = {
|
|
proposedChange: ProposedEventChange;
|
|
onConfirm: (proposal: ProposedEventChange) => void;
|
|
onReject: () => void;
|
|
onEdit?: (proposal: ProposedEventChange) => void;
|
|
};
|
|
|
|
const ActionButtons = ({
|
|
isDisabled,
|
|
respondedAction,
|
|
showEdit,
|
|
onConfirm,
|
|
onReject,
|
|
onEdit,
|
|
}: {
|
|
isDisabled: boolean;
|
|
respondedAction?: "confirm" | "reject";
|
|
showEdit: boolean;
|
|
onConfirm: () => void;
|
|
onReject: () => void;
|
|
onEdit?: () => void;
|
|
}) => {
|
|
const { theme } = useThemeStore();
|
|
return (
|
|
<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
|
|
? theme.disabledButton
|
|
: theme.confirmButton,
|
|
borderWidth: respondedAction === "confirm" ? 2 : 0,
|
|
borderColor: theme.confirmButton,
|
|
}}
|
|
>
|
|
<Text style={{ color: theme.buttonText }} className="font-medium">
|
|
Annehmen
|
|
</Text>
|
|
</Pressable>
|
|
<Pressable
|
|
onPress={onReject}
|
|
disabled={isDisabled}
|
|
className="flex-1 py-2 rounded-lg items-center"
|
|
style={{
|
|
backgroundColor: isDisabled
|
|
? theme.disabledButton
|
|
: theme.rejectButton,
|
|
borderWidth: respondedAction === "reject" ? 2 : 0,
|
|
borderColor: theme.rejectButton,
|
|
}}
|
|
>
|
|
<Text style={{ color: theme.buttonText }} className="font-medium">
|
|
Ablehnen
|
|
</Text>
|
|
</Pressable>
|
|
{showEdit && onEdit && (
|
|
<Pressable
|
|
onPress={onEdit}
|
|
className="py-2 px-3 rounded-lg items-center"
|
|
style={{
|
|
backgroundColor: theme.secondaryBg,
|
|
borderWidth: 1,
|
|
borderColor: theme.borderPrimary,
|
|
}}
|
|
>
|
|
<Feather name="edit-2" size={18} color={theme.textPrimary} />
|
|
</Pressable>
|
|
)}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export const ProposedEventCard = ({
|
|
proposedChange,
|
|
onConfirm,
|
|
onReject,
|
|
onEdit,
|
|
}: ProposedEventCardProps) => {
|
|
const { theme } = useThemeStore();
|
|
const event = proposedChange.event;
|
|
const isDisabled = !!proposedChange.respondedAction;
|
|
|
|
// For delete/single action, the occurrenceDate becomes a new exception
|
|
const newExceptionDate =
|
|
proposedChange.action === "delete" &&
|
|
proposedChange.deleteMode === "single" &&
|
|
proposedChange.occurrenceDate;
|
|
|
|
// For update actions, check if a new UNTIL date is being set
|
|
const newUntilDate =
|
|
proposedChange.action === "update" &&
|
|
event?.recurrenceRule &&
|
|
rrulestr(event.recurrenceRule).options.until;
|
|
|
|
if (!event) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<View className="mt-2">
|
|
<EventCardBase
|
|
className="m-2"
|
|
title={event.title}
|
|
startTime={event.startTime}
|
|
endTime={event.endTime}
|
|
description={event.description}
|
|
recurrenceRule={event.recurrenceRule}
|
|
>
|
|
{/* Show new exception date for delete/single actions */}
|
|
{newExceptionDate && (
|
|
<View className="flex-row items-center mb-2">
|
|
<Feather
|
|
name="plus-circle"
|
|
size={16}
|
|
color={theme.confirmButton}
|
|
style={{ marginRight: 8 }}
|
|
/>
|
|
<Text
|
|
style={{ color: theme.confirmButton }}
|
|
className="font-medium"
|
|
>
|
|
Neue Ausnahme:{" "}
|
|
{formatDate(new Date(proposedChange.occurrenceDate!))}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
{/* Show new UNTIL date for update actions */}
|
|
{newUntilDate && (
|
|
<View className="flex-row items-center mb-2">
|
|
<Feather
|
|
name="plus-circle"
|
|
size={16}
|
|
color={theme.confirmButton}
|
|
style={{ marginRight: 8 }}
|
|
/>
|
|
<Text
|
|
style={{ color: theme.confirmButton }}
|
|
className="font-medium"
|
|
>
|
|
Neues Ende: {formatDate(newUntilDate)}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
{/* Show conflicting events warning */}
|
|
{proposedChange.conflictingEvents &&
|
|
proposedChange.conflictingEvents.length > 0 && (
|
|
<View className="mb-2">
|
|
{proposedChange.conflictingEvents.map((conflict, index) => (
|
|
<View key={index} className="flex-row items-center mt-1">
|
|
<Ionicons
|
|
name="alert-circle"
|
|
size={16}
|
|
color={theme.rejectButton}
|
|
style={{ marginRight: 8 }}
|
|
/>
|
|
<Text
|
|
style={{ color: theme.rejectButton }}
|
|
className="text-sm flex-1"
|
|
>
|
|
Konflikt: {conflict.title} ({formatTime(conflict.startTime)}{" "}
|
|
- {formatTime(conflict.endTime)})
|
|
</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
)}
|
|
<ActionButtons
|
|
isDisabled={isDisabled}
|
|
respondedAction={proposedChange.respondedAction}
|
|
showEdit={proposedChange.action !== "delete" && !isDisabled}
|
|
onConfirm={() => onConfirm(proposedChange)}
|
|
onReject={onReject}
|
|
onEdit={onEdit ? () => onEdit(proposedChange) : undefined}
|
|
/>
|
|
</EventCardBase>
|
|
</View>
|
|
);
|
|
};
|