Refresh home and more navigation
This commit is contained in:
@@ -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}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user