diff --git a/app.json b/app.json index 41833f5..2322639 100644 --- a/app.json +++ b/app.json @@ -10,7 +10,7 @@ "ios": { "supportsTablet": true, "bundleIdentifier": "com.beenvoice.app", - "buildNumber": "13", + "buildNumber": "17", "icon": "./assets/beenvoice.icon", "infoPlist": { "ITSAppUsesNonExemptEncryption": false, diff --git a/app/(app)/entities/index.tsx b/app/(app)/entities/index.tsx index f79f95c..0778aba 100644 --- a/app/(app)/entities/index.tsx +++ b/app/(app)/entities/index.tsx @@ -1,6 +1,7 @@ import { router } from "expo-router"; import { useState } from "react"; import { + Alert, Pressable, RefreshControl, ScrollView, @@ -15,6 +16,7 @@ import { FloatingActionButton } from "@/components/FloatingActionButton"; import { GlassSurface } from "@/components/GlassSurface"; import { LoadingScreen } from "@/components/LoadingScreen"; import { PageHeader } from "@/components/PageHeader"; +import { SwipeableRow } from "@/components/SwipeableRow"; import { TabPage } from "@/components/TabPage"; import { TabScrollView } from "@/components/TabScrollView"; import { fonts, spacing } from "@/constants/theme"; @@ -38,6 +40,12 @@ export default function EntitiesScreen() { const clientsQuery = api.clients.getAll.useQuery(); const businessesQuery = api.businesses.getAll.useQuery(); + const deleteClient = api.clients.delete.useMutation({ + onSuccess: () => void clientsQuery.refetch(), + }); + const deleteBusiness = api.businesses.delete.useMutation({ + onSuccess: () => void businessesQuery.refetch(), + }); const activeQuery = tab === "clients" ? clientsQuery : businessesQuery; const isLoading = @@ -68,6 +76,20 @@ export default function EntitiesScreen() { else void businessesQuery.refetch(); } + function confirmDelete(id: string, name: string) { + Alert.alert(`Delete ${tab === "clients" ? "client" : "business"}?`, `Remove ${name}?`, [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: () => { + if (tab === "clients") deleteClient.mutate({ id }); + else deleteBusiness.mutate({ id }); + }, + }, + ]); + } + return ( @@ -112,25 +134,45 @@ export default function EntitiesScreen() { ) : ( clients.map((client) => ( - router.push(`/(app)/entities/clients/${client.id}`)} + backgroundColor={colors.cardGlass} + actions={[ + { + key: "edit", + label: "Edit", + icon: "create-outline", + color: "#fff", + backgroundColor: colors.primary, + onPress: () => router.push(`/(app)/entities/clients/edit/${client.id}`), + }, + { + key: "delete", + label: "Delete", + icon: "trash-outline", + color: "#fff", + backgroundColor: colors.destructive, + onPress: () => confirmDelete(client.id, client.name), + }, + ]} > - - - {client.name} - {client.email ? ( - {client.email} - ) : null} - {client.defaultHourlyRate != null ? ( - - {formatCurrency(client.defaultHourlyRate, client.currency ?? "USD")} - /hr - - ) : null} - - - + router.push(`/(app)/entities/clients/${client.id}`)}> + + + {client.name} + {client.email ? ( + {client.email} + ) : null} + {client.defaultHourlyRate != null ? ( + + {formatCurrency(client.defaultHourlyRate, client.currency ?? "USD")} + /hr + + ) : null} + + + + )) ) ) : businesses.length === 0 ? ( @@ -142,25 +184,45 @@ export default function EntitiesScreen() { ) : ( businesses.map((business) => ( - router.push(`/(app)/entities/businesses/${business.id}`)} + backgroundColor={colors.cardGlass} + actions={[ + { + key: "edit", + label: "Edit", + icon: "create-outline", + color: "#fff", + backgroundColor: colors.primary, + onPress: () => router.push(`/(app)/entities/businesses/edit/${business.id}`), + }, + { + key: "delete", + label: "Delete", + icon: "trash-outline", + color: "#fff", + backgroundColor: colors.destructive, + onPress: () => confirmDelete(business.id, business.name), + }, + ]} > - - - - {business.name} - {business.isDefault ? ( - Default + router.push(`/(app)/entities/businesses/${business.id}`)}> + + + + {business.name} + {business.isDefault ? ( + Default + ) : null} + + {business.nickname ? ( + {business.nickname} ) : null} + {business.email ? {business.email} : null} - {business.nickname ? ( - {business.nickname} - ) : null} - {business.email ? {business.email} : null} - - - + + + )) )} diff --git a/app/(app)/index.tsx b/app/(app)/index.tsx index bdb64b8..36184a3 100644 --- a/app/(app)/index.tsx +++ b/app/(app)/index.tsx @@ -20,6 +20,7 @@ import { useThemedStyles } from "@/lib/use-themed-styles"; import { getInvoiceStatus } from "@/lib/invoice-status"; import { formatElapsedHoursMinutes, resolveClockDescription } from "@/lib/time-clock"; import { useRunningElapsed } from "@/lib/use-running-elapsed"; +import { formatTrpcErrorMessage } from "@/lib/trpc-errors"; import { api } from "@/lib/trpc"; export default function DashboardScreen() { @@ -41,7 +42,7 @@ export default function DashboardScreen() { Could not load dashboard - {statsQuery.error.message} + {formatTrpcErrorMessage(statsQuery.error)} @@ -62,7 +63,7 @@ export default function DashboardScreen() { : "No change vs last month"; const maxRevenue = Math.max(...stats.revenueChartData.map((d) => d.revenue), 1); - const sendReminderDue = stats.sendReminderDue ?? []; + const sendReminderDue = stats.recentInvoices.filter((inv) => inv.status === "draft"); return ( @@ -146,8 +147,31 @@ export default function DashboardScreen() { variant="secondary" onPress={() => router.push("/(app)/invoices")} /> +