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)} />}
);
}