- Dockerfile + deploy.sh for Hetzner server - Email verification via Better Auth + Resend - Invite code flow (6-digit OTP, generate/join) - Settlement share percent fix (payer vs debtor) - OCR scanner fixes (date display, retry, viewfinder) - app.json icon/splash/adaptive-icon configured - iOS deployment target 15.5 (ML Kit requirement) - DB migration 0014: household_invitations table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
4.2 KiB
TypeScript
110 lines
4.2 KiB
TypeScript
import { QuickAddModal } from "@/src/components/features/transactions/QuickAddModal";
|
|
import { SummaryHeader } from "@/src/components/features/transactions/SummaryHeader";
|
|
import { TransactionItem } from "@/src/components/features/transactions/TransactionItem";
|
|
import { EditTransactionModal } from "@/src/components/features/transactions/EditTransactionModal";
|
|
import { useTransactions, useTransactionSummary, useDeleteTransaction, type TransactionWithCategory } from "@/src/hooks/useTransactions";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { useState } from "react";
|
|
import {
|
|
ActivityIndicator,
|
|
FlatList,
|
|
Pressable,
|
|
RefreshControl,
|
|
Text,
|
|
View,
|
|
} from "react-native";
|
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
|
|
type FilterType = "all" | "income" | "expense";
|
|
|
|
export default function TransactionsScreen() {
|
|
const insets = useSafeAreaInsets();
|
|
const [filter, setFilter] = useState<FilterType>("all");
|
|
const [showAddModal, setShowAddModal] = useState(false);
|
|
const [editTransaction, setEditTransaction] = useState<TransactionWithCategory | null>(null);
|
|
const { mutate: deleteTransaction } = useDeleteTransaction();
|
|
|
|
const transactionFilter = filter === "all" ? undefined : { type: filter as "income" | "expense" };
|
|
const { data: transactions = [], isLoading, refetch, isRefetching } = useTransactions(transactionFilter);
|
|
const { data: summary, isLoading: summaryLoading } = useTransactionSummary();
|
|
|
|
function renderEmpty() {
|
|
if (isLoading) return null;
|
|
return (
|
|
<View className="flex-1 items-center justify-center py-20">
|
|
<Ionicons name="wallet-outline" size={48} color="#d1d5db" style={{ marginBottom: 12 }} />
|
|
<Text className="text-base font-medium text-gray-700 mb-1">Noch keine Buchungen</Text>
|
|
<Text className="text-sm text-gray-400">Tippe auf + um deine erste Buchung einzutragen</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View className="flex-1 bg-white">
|
|
<View className="bg-blue-600" style={{ paddingTop: insets.top }}>
|
|
<SummaryHeader summary={summary} isLoading={summaryLoading} />
|
|
</View>
|
|
|
|
{/* Filter Bar */}
|
|
<View className="flex-row px-4 py-3 gap-2 border-b border-gray-100">
|
|
{(["all", "expense", "income"] as const).map((f) => (
|
|
<Pressable
|
|
key={f}
|
|
onPress={() => setFilter(f)}
|
|
className={`px-4 py-1.5 rounded-full ${filter === f ? "bg-blue-600" : "bg-gray-100"}`}
|
|
>
|
|
<Text className={`text-sm font-medium ${filter === f ? "text-white" : "text-gray-600"}`}>
|
|
{f === "all" ? "Alle" : f === "expense" ? "Ausgaben" : "Einnahmen"}
|
|
</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
|
|
{/* Transaction List */}
|
|
{isLoading ? (
|
|
<View className="flex-1 items-center justify-center">
|
|
<ActivityIndicator size="large" color="#2563EB" />
|
|
</View>
|
|
) : (
|
|
<FlatList
|
|
data={transactions}
|
|
keyExtractor={(item) => item.id}
|
|
renderItem={({ item }) => (
|
|
<TransactionItem
|
|
transaction={item}
|
|
onPress={setEditTransaction}
|
|
onDelete={(t) => deleteTransaction(t.id)}
|
|
/>
|
|
)}
|
|
ListEmptyComponent={renderEmpty}
|
|
refreshControl={
|
|
<RefreshControl refreshing={isRefetching} onRefresh={() => void refetch()} />
|
|
}
|
|
ItemSeparatorComponent={() => <View className="h-px bg-gray-50 ml-16" />}
|
|
contentContainerStyle={transactions.length === 0 ? { flex: 1 } : undefined}
|
|
/>
|
|
)}
|
|
|
|
{/* FAB */}
|
|
<Pressable
|
|
onPress={() => setShowAddModal(true)}
|
|
className="absolute bottom-6 right-6 w-14 h-14 bg-blue-600 rounded-full items-center justify-center shadow-lg active:opacity-80"
|
|
>
|
|
<Ionicons name="add" size={28} color="#fff" />
|
|
</Pressable>
|
|
|
|
<QuickAddModal
|
|
visible={showAddModal}
|
|
onClose={() => setShowAddModal(false)}
|
|
onRequestAddCategory={() => {}}
|
|
/>
|
|
{editTransaction && (
|
|
<EditTransactionModal
|
|
transaction={editTransaction}
|
|
onClose={() => setEditTransaction(null)}
|
|
/>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|