import { EmptyState } from "@/src/components/ui/EmptyState"; import { ModalHeader } from "@/src/components/ui/ModalHeader"; import { TAB_COLORS } from "@/src/constants/colors"; import { useTrips, useCreateTrip, type Trip, type CreateTripInput, } from "@/src/hooks/useTrips"; import { formatEur } from "@/src/utils/format"; import { Ionicons } from "@expo/vector-icons"; import { useRouter } from "expo-router"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { ActivityIndicator, FlatList, Modal, Pressable, ScrollView, Text, TextInput, View, } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; const ACCENT = TAB_COLORS.shopping; // green #16A34A // ── Helpers ─────────────────────────────────────────────────────────────────── function formatDateRange(startDate: string, endDate: string): string { const fmt = (d: string) => { const parts = d.split("-"); return `${parts[2]}.${parts[1]}.${parts[0]?.slice(2)}`; }; return `${fmt(startDate)} – ${fmt(endDate)}`; } function getBudgetColor(remaining: number, budget: number): string { if (remaining <= 0) return "#dc2626"; // red if (remaining < budget * 0.1) return "#ea580c"; // orange return ACCENT; // green } // ── Progress Bar ────────────────────────────────────────────────────────────── function ProgressBar({ spent, budget, color, }: { spent: number; budget: number; color: string; }) { const ratio = budget > 0 ? Math.min(spent / budget, 1) : 0; return ( ); } // ── Active Trip Card ────────────────────────────────────────────────────────── function ActiveTripCard({ trip, onPress }: { trip: Trip; onPress: () => void }) { const { t } = useTranslation(); const remaining = trip.budget - trip.spent; const color = getBudgetColor(remaining, trip.budget); const isOver = remaining <= 0; return ( {trip.name} {trip.destination && ( {trip.destination} )} {t("trips.active")} {formatDateRange(trip.startDate, trip.endDate)} {t("trips.spent")}: {formatEur(trip.spent)} {isOver ? ( {t("trips.overBudget", { amount: formatEur(Math.abs(remaining)) })} ) : ( {t("trips.remaining")}: {formatEur(remaining)} )} {t("trips.budget")}: {formatEur(trip.budget)} ); } // ── Past Trip Row ───────────────────────────────────────────────────────────── function PastTripRow({ trip, onPress }: { trip: Trip; onPress: () => void }) { const { t } = useTranslation(); const hasSettlement = trip.settlementAmount !== null && trip.settlementAmount > 0.01; const isBalanced = trip.settlementAmount !== null && trip.settlementAmount <= 0.01; return ( {trip.name} {trip.destination && ( {trip.destination} )} {hasSettlement && ( {t("trips.settlement.closedBanner")} · {formatEur(trip.settlementAmount ?? 0)} )} {isBalanced && ( {t("trips.settlement.balanced")} )} {formatEur(trip.spent)} {formatDateRange(trip.startDate, trip.endDate)} {t("trips.completed")} ); } // ── Create Trip Modal ───────────────────────────────────────────────────────── function CreateTripModal({ onClose }: { onClose: () => void }) { const { t } = useTranslation(); const { mutate: createTrip, isPending } = useCreateTrip(); const [name, setName] = useState(""); const [destination, setDestination] = useState(""); const [budgetStr, setBudgetStr] = useState(""); const [startDate, setStartDate] = useState(""); const [endDate, setEndDate] = useState(""); const canSave = name.trim().length > 0 && parseFloat(budgetStr.replace(",", ".")) > 0 && startDate.length === 10 && endDate.length === 10; function handleSave() { const budget = parseFloat(budgetStr.replace(",", ".")); if (!canSave || isNaN(budget)) return; const input: CreateTripInput = { name: name.trim(), budget, startDate, endDate, ...(destination.trim() ? { destination: destination.trim() } : {}), }; createTrip(input, { onSuccess: onClose }); } return ( {/* Name */} {t("trips.name")} * {/* Destination */} {t("trips.destination")} {/* Budget */} {t("trips.budget")} (€) * {/* Start Date */} {t("trips.startDate")} * {/* End Date */} {t("trips.endDate")} * ); } // ── Main Screen ─────────────────────────────────────────────────────────────── type SectionItem = | { type: "header"; label: string } | { type: "active-trip"; trip: Trip } | { type: "past-trip"; trip: Trip } | { type: "empty" }; export default function UrlaubScreen() { const insets = useSafeAreaInsets(); const router = useRouter(); const { t } = useTranslation(); const { data: trips = [], isLoading, refetch, isRefetching } = useTrips(); const [showCreate, setShowCreate] = useState(false); const activeTrips = trips.filter((t) => t.status === "active"); const pastTrips = trips.filter((t) => t.status === "completed"); // Build flat list items for sectioned rendering const listItems: SectionItem[] = []; if (activeTrips.length > 0) { listItems.push({ type: "header", label: t("trips.active") }); activeTrips.forEach((trip) => listItems.push({ type: "active-trip", trip })); } if (pastTrips.length > 0) { listItems.push({ type: "header", label: t("trips.past") }); pastTrips.forEach((trip) => listItems.push({ type: "past-trip", trip })); } if (trips.length === 0 && !isLoading) { listItems.push({ type: "empty" }); } function renderItem({ item }: { item: SectionItem }) { if (item.type === "header") { return ( {item.label} ); } if (item.type === "active-trip") { return ( router.push({ pathname: "/(app)/urlaub/[id]", params: { id: item.trip.id }, }) } /> ); } if (item.type === "past-trip") { return ( router.push({ pathname: "/(app)/urlaub/[id]", params: { id: item.trip.id }, }) } /> ); } // empty state return ( ); } return ( {/* Header */} router.push("/(app)/mehr")} className="mr-1 p-1"> {t("trips.title")} setShowCreate(true)} className="w-9 h-9 rounded-full items-center justify-center active:opacity-70" style={{ backgroundColor: ACCENT }} > {isLoading ? ( ) : ( { if (item.type === "header") return `header-${item.label}`; if (item.type === "active-trip" || item.type === "past-trip") return item.trip.id; return `empty-${index}`; }} renderItem={renderItem} contentContainerStyle={ trips.length === 0 ? { flex: 1, paddingBottom: insets.bottom + 16 } : { paddingBottom: insets.bottom + 16, paddingTop: 4 } } refreshing={isRefetching} onRefresh={() => void refetch()} /> )} {showCreate && setShowCreate(false)} />} ); }