Files

116 lines
3.9 KiB
TypeScript

import { RefreshControl, StyleSheet, Text, View } from "react-native";
import { AppBackground } from "@/components/AppBackground";
import { LoadingScreen } from "@/components/LoadingScreen";
import { PageHeader } from "@/components/PageHeader";
import { StatCard } from "@/components/StatCard";
import { TabPage } from "@/components/TabPage";
import { TabScrollView } from "@/components/TabScrollView";
import { Card } from "@/components/ui/Card";
import { spacing } from "@/constants/theme";
import { useAppTheme } from "@/contexts/ThemeContext";
import { formatCurrency } from "@/lib/format";
import { formatTrpcErrorMessage } from "@/lib/trpc-errors";
import { api } from "@/lib/trpc";
export default function ReportsScreen() {
const { colors } = useAppTheme();
const statsQuery = api.dashboard.getStats.useQuery();
const expensesQuery = api.expenses.getAll.useQuery();
const summaryQuery = api.timeEntries.getSummary.useQuery();
if (statsQuery.isLoading || expensesQuery.isLoading || summaryQuery.isLoading) {
return <LoadingScreen message="Loading reports…" />;
}
if (statsQuery.error) {
return (
<AppBackground>
<TabPage showMoreBack>
<View style={styles.errorBox}>
<PageHeader title="Reports" subtitle="Business performance snapshot" />
<Text style={{ color: colors.mutedForeground }}>
{formatTrpcErrorMessage(statsQuery.error)}
</Text>
</View>
</TabPage>
</AppBackground>
);
}
const stats = statsQuery.data!;
const expenseTotal = (expensesQuery.data ?? []).reduce((sum, e) => sum + e.amount, 0);
const summary = summaryQuery.data;
return (
<AppBackground>
<TabPage showMoreBack>
<TabScrollView
header={<PageHeader title="Reports" subtitle="Business performance snapshot" />}
refreshControl={
<RefreshControl
refreshing={statsQuery.isRefetching}
onRefresh={() => {
void statsQuery.refetch();
void expensesQuery.refetch();
void summaryQuery.refetch();
}}
tintColor={colors.primary}
/>
}
>
<View style={styles.grid}>
<StatCard label="Revenue" value={formatCurrency(stats.totalRevenue)} />
<StatCard label="Pending" value={formatCurrency(stats.pendingAmount)} />
<StatCard label="Expenses" value={formatCurrency(expenseTotal)} />
<StatCard
label="Billable hours"
value={summary ? summary.totalHours.toFixed(1) : "0"}
hint={summary ? `${summary.count} entries` : undefined}
/>
</View>
<Card title="Invoice status">
{(stats.statusChartData ?? []).map((item) => (
<View key={item.status} style={styles.statusRow}>
<Text style={{ color: colors.foreground }}>{item.name}</Text>
<Text style={{ color: colors.mutedForeground }}>
{item.count} · {formatCurrency(item.value)}
</Text>
</View>
))}
</Card>
<Card title="Revenue trend (6 mo)">
{stats.revenueChartData.map((point) => (
<View key={point.month} style={styles.statusRow}>
<Text style={{ color: colors.foreground }}>{point.monthLabel}</Text>
<Text style={{ color: colors.mutedForeground }}>
{formatCurrency(point.revenue)}
</Text>
</View>
))}
</Card>
</TabScrollView>
</TabPage>
</AppBackground>
);
}
const styles = StyleSheet.create({
grid: {
flexDirection: "row",
flexWrap: "wrap",
gap: spacing.md,
},
statusRow: {
flexDirection: "row",
justifyContent: "space-between",
paddingVertical: spacing.sm,
},
errorBox: {
padding: spacing.lg,
gap: spacing.md,
},
});