diff --git a/app/(app)/_layout.tsx b/app/(app)/_layout.tsx index ea94674..1438ab0 100644 --- a/app/(app)/_layout.tsx +++ b/app/(app)/_layout.tsx @@ -37,10 +37,10 @@ export default function AppLayout() { > - Dashboard + Home @@ -67,15 +67,7 @@ export default function AppLayout() { Invoices - - - Settings - - - + 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, @@ -33,7 +45,7 @@ export default function DashboardScreen() { const runningElapsed = useRunningElapsed(runningQuery.data?.startedAt); if (statsQuery.isLoading) { - return ; + return ; } if (statsQuery.error) { @@ -41,7 +53,7 @@ export default function DashboardScreen() { - Could not load dashboard + Could not load home {formatTrpcErrorMessage(statsQuery.error)} @@ -51,27 +63,94 @@ export default function DashboardScreen() { const stats = statsQuery.data; if (!stats) { - return ; + return ; } + const now = new Date(); const running = runningQuery.data; - const revenueChange = - stats.revenueChange > 0 - ? `+${stats.revenueChange.toFixed(0)}% vs last month` - : stats.revenueChange < 0 - ? `${stats.revenueChange.toFixed(0)}% vs last month` - : "No change vs last month"; - + 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 sendReminderDue = stats.recentInvoices.filter((inv) => inv.status === "draft"); + 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 ( - } + header={} refreshControl={ } > + +