Refresh home and more navigation

This commit is contained in:
2026-07-01 02:46:39 -04:00
parent 6497ee4dec
commit 530d0dc34d
21 changed files with 1043 additions and 594 deletions
+86 -66
View File
@@ -1,6 +1,6 @@
import { router } from "expo-router";
import { useMemo, useState } from "react";
import { Alert, ScrollView, StyleSheet, Text, View } from "react-native";
import { Alert, StyleSheet, Text, View } from "react-native";
import { AppBackground } from "@/components/AppBackground";
import {
@@ -11,10 +11,11 @@ import {
import { ReceiptItemSelector } from "@/components/expenses/ReceiptItemSelector";
import { PageHeader } from "@/components/PageHeader";
import { TabPage } from "@/components/TabPage";
import { TabScrollView } from "@/components/TabScrollView";
import { Button } from "@/components/ui/Button";
import { spacing } from "@/constants/theme";
import { Card } from "@/components/ui/Card";
import { fonts, spacing } from "@/constants/theme";
import { useAppTheme } from "@/contexts/ThemeContext";
import { mlKitOcrAvailable } from "@/lib/receipt-ocr";
import {
scanReceiptImage,
type PickedReceiptImage,
@@ -96,7 +97,7 @@ export default function NewExpenseScreen() {
? "Select the items this person owes, then apply the split amount."
: result.amountText
? `Filled amount $${result.amountText}${result.description ? ` from ${result.description}` : ""}. Review and save.`
: "Review the fields — OCR could not find a total automatically.",
: "Review the fields and enter the total before saving.",
);
} finally {
setScanning(false);
@@ -150,42 +151,44 @@ export default function NewExpenseScreen() {
return (
<AppBackground>
<TabPage>
<ScrollView
contentContainerStyle={styles.body}
<TabPage showMoreBack>
<TabScrollView
header={
<PageHeader
title="New expense"
subtitle="Add a receipt, fill the details, and save it"
/>
}
keyboardShouldPersistTaps="handled"
>
<PageHeader
title="New expense"
subtitle={
mlKitOcrAvailable()
? "Scan a receipt with on-device ML Kit OCR"
: "Manual entry (OCR unavailable on this device)"
}
/>
<Card title="Receipt">
<View style={styles.actions}>
<Button
title={scanning ? "Scanning..." : "Take photo"}
variant="secondary"
leftIcon="camera-outline"
loading={scanning}
style={styles.actionButton}
onPress={() => void runScan(true)}
/>
<Button
title="Choose photo"
variant="secondary"
leftIcon="image-outline"
loading={scanning}
style={styles.actionButton}
onPress={() => void runScan(false)}
/>
</View>
<View style={styles.actions}>
<Button
title={scanning ? "Scanning…" : "Scan receipt"}
variant="secondary"
loading={scanning}
style={styles.actionButton}
onPress={() => void runScan(true)}
/>
<Button
title="Import photo"
variant="secondary"
loading={scanning}
style={styles.actionButton}
onPress={() => void runScan(false)}
/>
</View>
{pendingReceipt ? (
<Text style={{ color: colors.success, fontSize: 13 }}>
Receipt image ready it will attach when you save.
</Text>
) : null}
{pendingReceipt ? (
<View style={[styles.notice, { backgroundColor: colors.successBg }]}>
<Text style={[styles.noticeText, { color: colors.success }]}>
Receipt attached. Review the details below before saving.
</Text>
</View>
) : null}
</Card>
{receiptSplit ? (
<ReceiptItemSelector
@@ -203,39 +206,41 @@ export default function NewExpenseScreen() {
/>
) : null}
<ExpenseFormFields
value={{
...form,
businessId: form.businessId || defaultBusinessId,
}}
businesses={businesses}
clients={clients}
onChange={setForm}
notesLabel="Notes"
notesPlaceholder="Receipt OCR text or internal notes"
/>
<Card title="Details">
<ExpenseFormFields
value={{
...form,
businessId: form.businessId || defaultBusinessId,
}}
businesses={businesses}
clients={clients}
onChange={setForm}
notesLabel="Notes"
notesPlaceholder="Internal details"
/>
</Card>
<Button
title="Save expense"
loading={createExpense.isPending}
onPress={() => void handleSave()}
/>
<Button
title="Cancel"
variant="secondary"
onPress={() => router.back()}
/>
</ScrollView>
<View style={styles.saveActions}>
<Button
title="Save expense"
leftIcon="checkmark-circle-outline"
loading={createExpense.isPending}
onPress={() => void handleSave()}
/>
<Button
title="Cancel"
leftIcon="close-circle-outline"
variant="secondary"
onPress={() => router.back()}
/>
</View>
</TabScrollView>
</TabPage>
</AppBackground>
);
}
const styles = StyleSheet.create({
body: {
padding: spacing.lg,
gap: spacing.md,
},
actions: {
flexDirection: "row",
gap: spacing.sm,
@@ -243,14 +248,29 @@ const styles = StyleSheet.create({
actionButton: {
flex: 1,
},
saveActions: {
gap: spacing.sm,
},
notice: {
borderRadius: 12,
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
},
noticeText: {
fontFamily: fonts.bodyMedium,
fontSize: 13,
lineHeight: 18,
},
});
function mergeNotes(prefix: string, existing: string) {
const trimmed = existing.trim();
if (!trimmed) return prefix;
if (trimmed.startsWith("Receipt split")) {
const ocrStart = trimmed.indexOf("\n\nOCR text:");
return ocrStart >= 0 ? `${prefix}${trimmed.slice(ocrStart)}` : prefix;
const detailsStart = trimmed.indexOf("\n\nReceipt details:");
const legacyStart = trimmed.indexOf("\n\nOCR text:");
const noteStart = detailsStart >= 0 ? detailsStart : legacyStart;
return noteStart >= 0 ? `${prefix}${trimmed.slice(noteStart)}` : prefix;
}
return `${prefix}\n\nOCR text:\n${trimmed}`;
return `${prefix}\n\nReceipt details:\n${trimmed}`;
}