import { router } from "expo-router"; import { useState } from "react"; import { Alert, Pressable, RefreshControl, ScrollView, StyleSheet, Text, View, } from "react-native"; import { AppBackground } from "@/components/AppBackground"; import { FilterChip } from "@/components/FilterChip"; import { GlassSurface } from "@/components/GlassSurface"; import { LoadingScreen } from "@/components/LoadingScreen"; import { PageHeader } from "@/components/PageHeader"; import { StatusBadge } from "@/components/StatusBadge"; import { TabPage } from "@/components/TabPage"; import { TabScrollView } from "@/components/TabScrollView"; import { fonts, spacing } from "@/constants/theme"; import { useAppTheme } from "@/contexts/ThemeContext"; import { formatCurrency, formatDate } from "@/lib/format"; import type { ThemeColors } from "@/lib/theme-palette"; import { useThemedStyles } from "@/lib/use-themed-styles"; import { getInvoiceStatus, type InvoiceStatus } from "@/lib/invoice-status"; import { api } from "@/lib/trpc"; const filters: Array<{ label: string; value?: InvoiceStatus | "all" }> = [ { label: "All", value: "all" }, { label: "Draft", value: "draft" }, { label: "Sent", value: "sent" }, { label: "Paid", value: "paid" }, { label: "Overdue", value: "overdue" }, ]; export default function InvoicesScreen() { const { colors } = useAppTheme(); const styles = useThemedStyles(createInvoicesStyles); const [filter, setFilter] = useState<(typeof filters)[number]["value"]>("all"); const utils = api.useUtils(); const invoicesQuery = api.invoices.getAll.useQuery(); const updateStatus = api.invoices.updateStatus.useMutation({ onSuccess: () => { utils.invoices.getAll.invalidate(); utils.dashboard.getStats.invalidate(); }, onError: (err) => Alert.alert("Update failed", err.message), }); if (invoicesQuery.isLoading) { return ; } if (invoicesQuery.error) { return ( Could not load invoices {invoicesQuery.error.message} ); } const invoices = (invoicesQuery.data ?? []).filter((invoice) => { if (filter === "all") return true; return getInvoiceStatus(invoice) === filter; }); function promptStatusChange(invoiceId: string, current: InvoiceStatus) { const options: Array<{ label: string; status: "draft" | "sent" | "paid" }> = []; if (current !== "draft") options.push({ label: "Mark as draft", status: "draft" }); if (current !== "sent" && current !== "overdue") { options.push({ label: "Mark as sent", status: "sent" }); } if (current !== "paid") options.push({ label: "Mark as paid", status: "paid" }); if (options.length === 0) return; Alert.alert("Update status", "Choose a new status", [ ...options.map((option) => ({ text: option.label, onPress: () => { updateStatus.mutate({ id: invoiceId, status: option.status }); }, })), { text: "Cancel", style: "cancel" }, ]); } return ( } refreshControl={ invoicesQuery.refetch()} tintColor={colors.primary} /> } > {filters.map((item) => ( setFilter(item.value)} /> ))} {invoices.length === 0 ? ( No invoices found Create invoices on the web app, then view and edit them here. ) : ( invoices.map((invoice) => { const status = getInvoiceStatus(invoice); return ( router.push(`/(app)/invoices/${invoice.id}`)} onLongPress={() => promptStatusChange(invoice.id, status)} > {invoice.invoicePrefix} {invoice.invoiceNumber} {invoice.client?.name ?? "Client"} {formatCurrency(invoice.totalAmount, invoice.currency)} Due {formatDate(invoice.dueDate)} ); }) )} ); } const createInvoicesStyles = (colors: ThemeColors, _isDark: boolean) => StyleSheet.create({ filterScroll: { flexGrow: 0, marginBottom: spacing.sm, }, filters: { gap: spacing.sm, paddingRight: spacing.md, }, card: {}, cardInner: { padding: spacing.md, gap: spacing.md, }, cardTop: { flexDirection: "row", justifyContent: "space-between", gap: spacing.md, }, cardMeta: { flex: 1, gap: 4, }, invoiceNumber: { fontSize: 16, fontFamily: fonts.bodySemiBold, color: colors.foreground, }, clientName: { color: colors.mutedForeground, fontSize: 14, fontFamily: fonts.body, }, amount: { fontSize: 16, fontFamily: fonts.bodySemiBold, color: colors.foreground, }, cardBottom: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", }, date: { color: colors.mutedForeground, fontSize: 13, fontFamily: fonts.body, }, empty: { padding: spacing.lg, alignItems: "center", gap: spacing.sm, }, emptyTitle: { fontSize: 18, fontFamily: fonts.bodySemiBold, color: colors.foreground, }, emptyText: { textAlign: "center", color: colors.mutedForeground, fontFamily: fonts.body, lineHeight: 20, }, errorBox: { flex: 1, justifyContent: "center", padding: spacing.lg, gap: spacing.sm, }, errorTitle: { fontSize: 18, fontFamily: fonts.bodySemiBold, color: colors.foreground, }, errorText: { color: colors.mutedForeground, fontFamily: fonts.body, lineHeight: 20, }, });