Polish mobile app for App Store review and expand CRUD.

Default to beenvoice.soconnor.dev with server settings hidden behind Advanced; add Entities tab with clients/businesses, invoice creation, UI fixes for dashboard layout, date fields, FAB position, and card-matched button radius.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-17 23:14:58 -04:00
parent 14c880123c
commit 6d2711e36e
41 changed files with 2410 additions and 181 deletions
+40 -25
View File
@@ -128,40 +128,50 @@ export default function DashboardScreen() {
</View>
<View style={styles.statsGrid}>
<StatCard label="Total revenue" value={formatCurrency(stats.totalRevenue)} />
<StatCard label="Pending" value={formatCurrency(stats.pendingAmount)} />
<StatCard
label="Overdue"
value={String(stats.overdueCount)}
hint={stats.overdueCount === 1 ? "invoice" : "invoices"}
/>
<StatCard label="Clients" value={String(stats.totalClients)} hint={revenueChange} />
<View style={styles.statCell}>
<StatCard label="Total revenue" value={formatCurrency(stats.totalRevenue)} />
</View>
<View style={styles.statCell}>
<StatCard label="Pending" value={formatCurrency(stats.pendingAmount)} />
</View>
<View style={styles.statCell}>
<StatCard
label="Overdue"
value={String(stats.overdueCount)}
hint={stats.overdueCount === 1 ? "invoice" : "invoices"}
/>
</View>
<Pressable style={styles.statCell} onPress={() => router.push("/(app)/entities")}>
<StatCard
label="Clients"
value={String(stats.totalClients)}
hint={revenueChange}
/>
</Pressable>
</View>
<Card title="Revenue (6 months)">
<View style={styles.chart}>
{stats.revenueChartData.map((point) => (
<View key={point.month} style={styles.chartColumn}>
<View style={styles.chartBarTrack}>
<View
style={[
styles.chartBar,
{ height: `${Math.max(8, (point.revenue / maxRevenue) * 100)}%` },
]}
/>
{stats.revenueChartData.map((point) => {
const barHeight = Math.max(4, (point.revenue / maxRevenue) * 80);
return (
<View key={point.month} style={styles.chartColumn}>
<View style={styles.chartBarTrack}>
<View style={[styles.chartBar, { height: barHeight }]} />
</View>
<Text style={styles.chartLabel}>{point.monthLabel}</Text>
<Text style={styles.chartValue}>
{point.revenue > 0 ? formatCurrency(point.revenue) : "—"}
</Text>
</View>
<Text style={styles.chartLabel}>{point.monthLabel}</Text>
<Text style={styles.chartValue}>
{point.revenue > 0 ? formatCurrency(point.revenue) : "—"}
</Text>
</View>
))}
);
})}
</View>
</Card>
<Card title="Recent invoices">
{stats.recentInvoices.length === 0 ? (
<Text style={styles.empty}>No invoices yet. Create one on the web app.</Text>
<Text style={styles.empty}>No invoices yet. Create one from the Invoices tab.</Text>
) : (
stats.recentInvoices.map((invoice) => {
const status = getInvoiceStatus(invoice);
@@ -262,12 +272,17 @@ const createDashboardStyles = (colors: ThemeColors, isDark: boolean) =>
flexDirection: "row",
flexWrap: "wrap",
gap: spacing.md,
alignContent: "flex-start",
},
statCell: {
flexGrow: 0,
flexShrink: 0,
flexBasis: "47%",
},
chart: {
flexDirection: "row",
justifyContent: "space-between",
gap: spacing.xs,
minHeight: 140,
},
chartColumn: {
flex: 1,