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 { FloatingActionButton } from "@/components/FloatingActionButton";
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
Tap + to create your first invoice, or pull to refresh.
) : (
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)}
);
})
)}
router.push("/(app)/invoices/new")}
/>
);
}
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,
},
});