"use client";
import { use, useState } from "react";
import Link from "next/link";
import {
BarChart,
Bar,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
Cell,
} from "recharts";
import { useTripAnalytics, useTrip, useTransactions } from "@/lib/hooks";
import { CreateTripModal } from "@/components/create-trip-modal";
import { formatCategory } from "@/lib/categories";
import { CATEGORY_COLORS, TOOLTIP_STYLE } from "@/lib/category-colors";
function fmtDate(d: string | null) {
if (!d) return null;
return new Date(d).toLocaleDateString("en-AU", { day: "2-digit", month: "short", year: "numeric" });
}
function StatCard({
label,
value,
sub,
color,
}: {
label: string;
value: string;
sub?: string;
color: string;
}) {
return (
{label}
{value}
{sub &&
{sub}
}
);
}
function DailyTooltip({ active, payload, label }: { active?: boolean; payload?: { value: number }[]; label?: string }) {
if (!active || !payload?.length) return null;
return (
{label ? new Date(label).toLocaleDateString("en-AU", { day: "2-digit", month: "short", year: "numeric" }) : ""}
${Number(payload[0].value).toFixed(2)}
);
}
function CategoryTooltip({ active, payload }: { active?: boolean; payload?: { payload: { category: string }; value: number }[] }) {
if (!active || !payload?.length) return null;
return (
{formatCategory(payload[0].payload.category)}
${Number(payload[0].value).toFixed(2)}
);
}
export default function TripDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = use(params);
const tripId = Number(id);
const { data: analytics, isLoading } = useTripAnalytics(tripId);
const { data: trip } = useTrip(tripId);
const [tab, setTab] = useState<"overview" | "transactions">("overview");
const [editModal, setEditModal] = useState(false);
const { data: txData } = useTransactions({ trip_id: id, limit: 500 });
if (isLoading || !analytics) {
return (
{[...Array(4)].map((_, i) =>
)}
);
}
const { total_spend, transaction_count, num_days, daily_average, category_breakdown, daily_spend, top_merchants, tag_breakdown, participant_splits } = analytics;
const t = analytics.trip;
const maxMerchant = top_merchants[0]?.amount ?? 1;
const dateRange = t.start_date && t.end_date
? `${fmtDate(t.start_date)} – ${fmtDate(t.end_date)}`
: t.start_date
? `From ${fmtDate(t.start_date)}`
: null;
return (
{/* Hero */}
← Trips
{t.name}
{dateRange &&
{dateRange}
}
{t.description &&
{t.description}
}
{/* Stat cards */}
{/* Tab bar */}
{(["overview", "transactions"] as const).map((tabName) => (
))}
{tab === "overview" && (
{/* Daily spend */}
{daily_spend.length > 0 && (
Daily Spend
new Date(v).toLocaleDateString("en-AU", { day: "2-digit", month: "short" })}
interval="preserveStartEnd"
/>
`$${v}`}
width={52}
/>
} cursor={{ fill: "#27272a" }} />
)}
{/* Category breakdown */}
{category_breakdown.length > 0 && (
By Category
`$${v}`} />
} cursor={{ fill: "#27272a" }} />
{category_breakdown.map((entry) => (
|
))}
)}
{/* Top merchants */}
{top_merchants.length > 0 && (
Top Merchants
{top_merchants.map((m, i) => (
{i + 1}
{m.merchant || "Unknown"}
${Number(m.amount).toFixed(2)}
))}
)}
{/* Tag breakdown */}
{tag_breakdown.length > 0 && (
By Tag
{tag_breakdown.map((tag) => (
{tag.name}
{tag.count} txns
${Number(tag.amount).toFixed(2)}
))}
)}
{/* Participant splits */}
{participant_splits.length > 0 && (
Participant Splits
View in Shared →
{["Person", "Total Owed", "Settled", "Unsettled"].map((h) => (
|
{h}
|
))}
{participant_splits.map((p) => (
| {p.name} |
${Number(p.owed).toFixed(2)} |
${Number(p.settled).toFixed(2)} |
0 ? "text-amber-400" : "text-zinc-600"}`}>
${Number(p.unsettled).toFixed(2)}
|
))}
)}
{category_breakdown.length === 0 && daily_spend.length === 0 && (
No transactions assigned to this trip yet.
Go to Transactions to assign some →
)}
)}
{tab === "transactions" && (
{!txData?.data.length ? (
No transactions assigned to this trip yet.
Go to Transactions to assign some →
) : (
{["Date", "Description", "Merchant", "Category", "Amount"].map((h) => (
|
{h}
|
))}
{txData.data.map((tx) => (
|
{new Date(tx.transaction_date).toLocaleDateString("en-AU", { day: "2-digit", month: "short" })}
|
{tx.description} |
{tx.effective_merchant || "—"} |
{formatCategory(tx.effective_category)} |
${Number(tx.amount).toFixed(2)}
|
))}
)}
)}
{editModal && trip && (
setEditModal(false)} />
)}
);
}