import { Ionicons } from "@expo/vector-icons"; import { router } from "expo-router"; import { Pressable, RefreshControl, StyleSheet, Text, View } from "react-native"; import { AppBackground } from "@/components/AppBackground"; import { GlassSurface } from "@/components/GlassSurface"; import { LoadingScreen } from "@/components/LoadingScreen"; import { PageHeader } from "@/components/PageHeader"; import { Screen } from "@/components/Screen"; import { StatCard } from "@/components/StatCard"; import { StatusBadge } from "@/components/StatusBadge"; import { TabPage } from "@/components/TabPage"; import { TabScrollView } from "@/components/TabScrollView"; import { Button } from "@/components/ui/Button"; import { Card } from "@/components/ui/Card"; import { fonts, radii, spacing } from "@/constants/theme"; import { useSession } from "@/contexts/AuthContext"; import { useAppTheme } from "@/contexts/ThemeContext"; import { formatCurrency, formatDate } from "@/lib/format"; import { getInvoiceStatus } from "@/lib/invoice-status"; import type { ThemeColors } from "@/lib/theme-palette"; import { formatElapsedHoursMinutes, resolveClockDescription } from "@/lib/time-clock"; import { formatTrpcErrorMessage } from "@/lib/trpc-errors"; import { useRunningElapsed } from "@/lib/use-running-elapsed"; import { useThemedStyles } from "@/lib/use-themed-styles"; import { api } from "@/lib/trpc"; type ActionItem = { key: string; title: string; detail: string; icon: keyof typeof Ionicons.glyphMap; tone: "warning" | "primary" | "success"; onPress: () => void; }; export default function DashboardScreen() { const { colors } = useAppTheme(); const styles = useThemedStyles(createDashboardStyles); const { data: session } = useSession(); const statsQuery = api.dashboard.getStats.useQuery(); const runningQuery = api.timeEntries.getRunning.useQuery(undefined, { refetchInterval: 30_000, }); const runningElapsed = useRunningElapsed(runningQuery.data?.startedAt); if (statsQuery.isLoading) { return ; } if (statsQuery.error) { return ( Could not load home {formatTrpcErrorMessage(statsQuery.error)} ); } const stats = statsQuery.data; if (!stats) { return ; } const now = new Date(); const running = runningQuery.data; const runningClient = running?.client?.name ?? "No client"; const monthInvoices = stats.monthInvoices ?? []; const monthTotal = monthInvoices.reduce((sum, invoice) => sum + invoice.totalAmount, 0); const maxRevenue = Math.max(...stats.revenueChartData.map((d) => d.revenue), 1); const drafts = stats.recentInvoices.filter((invoice) => invoice.status === "draft"); const pendingInvoices = monthInvoices.filter((invoice) => { const status = getInvoiceStatus(invoice); return status === "sent" || status === "overdue"; }); const overdueInvoices = monthInvoices.filter((invoice) => getInvoiceStatus(invoice) === "overdue"); const displayName = session?.user.name?.trim(); const firstName = (displayName ? displayName.split(/\s+/)[0] : undefined) ?? session?.user.email?.split("@")[0] ?? "there"; const actionItems: ActionItem[] = [ ...(running ? [ { key: "running", title: "Timer running", detail: `${formatElapsedHoursMinutes(runningElapsed)} on ${runningClient}`, icon: "timer-outline" as const, tone: "success" as const, onPress: () => router.push("/(app)/timer"), }, ] : []), ...(overdueInvoices.length > 0 ? [ { key: "overdue", title: `${overdueInvoices.length} overdue ${overdueInvoices.length === 1 ? "invoice" : "invoices"}`, detail: `${formatCurrency(overdueInvoices.reduce((sum, invoice) => sum + invoice.totalAmount, 0))} needs follow-up`, icon: "alert-circle-outline" as const, tone: "warning" as const, onPress: () => router.push("/(app)/invoices"), }, ] : []), ...(drafts.length > 0 ? [ { key: "drafts", title: `${drafts.length} draft ${drafts.length === 1 ? "invoice" : "invoices"}`, detail: "Review and send when ready", icon: "document-text-outline" as const, tone: "primary" as const, onPress: () => router.push("/(app)/invoices"), }, ] : []), ...(pendingInvoices.length > 0 ? [ { key: "pending", title: `${pendingInvoices.length} awaiting payment`, detail: `${formatCurrency(pendingInvoices.reduce((sum, invoice) => sum + invoice.totalAmount, 0))} outstanding this month`, icon: "card-outline" as const, tone: "primary" as const, onPress: () => router.push("/(app)/invoices"), }, ] : []), ]; if (actionItems.length === 0) { actionItems.push({ key: "clear", title: "No urgent action items", detail: "You are clear for the moment", icon: "checkmark-circle-outline", tone: "success", onPress: () => router.push("/(app)/invoices"), }); } return ( } refreshControl={ { void statsQuery.refetch(); void runningQuery.refetch(); }} tintColor={colors.primary} /> } >