478 lines
16 KiB
TypeScript
478 lines
16 KiB
TypeScript
|
|
import { Select } from "@/components/Select";
|
||
|
|
import { BORDER_RADIUS, COLORS, SPACING } from "@/constants/theme";
|
||
|
|
import { useAuth } from "@/contexts/AuthContext";
|
||
|
|
import { RideDB, obterCorridas } from "@/services/db";
|
||
|
|
import { Asset } from "expo-asset";
|
||
|
|
import { File } from "expo-file-system";
|
||
|
|
import * as Print from "expo-print";
|
||
|
|
import { router, useFocusEffect } from "expo-router";
|
||
|
|
import * as Sharing from "expo-sharing";
|
||
|
|
import React, { useCallback, useState } from "react";
|
||
|
|
import {
|
||
|
|
ActivityIndicator,
|
||
|
|
Alert,
|
||
|
|
ScrollView,
|
||
|
|
StyleSheet,
|
||
|
|
Text,
|
||
|
|
TouchableOpacity,
|
||
|
|
View,
|
||
|
|
} from "react-native";
|
||
|
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||
|
|
|
||
|
|
// ── Month helpers ─────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
function currentYearMonth() {
|
||
|
|
const now = new Date();
|
||
|
|
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getMonthOptions() {
|
||
|
|
const now = new Date();
|
||
|
|
return Array.from({ length: 12 }, (_, i) => {
|
||
|
|
const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
|
||
|
|
const value = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
|
||
|
|
const label = d.toLocaleDateString("pt-BR", { month: "long", year: "numeric" });
|
||
|
|
return { value, label: label.charAt(0).toUpperCase() + label.slice(1) };
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseRideDate(dateStr: string): Date | null {
|
||
|
|
if (!dateStr) return null;
|
||
|
|
const br = dateStr.match(/^(\d{2})\/(\d{2})\/(\d{4})/);
|
||
|
|
if (br) return new Date(`${br[3]}-${br[2]}-${br[1]}T00:00:00`);
|
||
|
|
const iso = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||
|
|
if (iso) return new Date(`${iso[1]}-${iso[2]}-${iso[3]}T00:00:00`);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
function filterByMonth(rides: RideDB[], yearMonth: string): RideDB[] {
|
||
|
|
const [year, month] = yearMonth.split("-").map(Number);
|
||
|
|
return rides.filter((r) => {
|
||
|
|
const d = parseRideDate(r.ride_date) ?? parseRideDate(r.createdAt);
|
||
|
|
return d ? d.getFullYear() === year && d.getMonth() + 1 === month : false;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Report calculation ────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
type CompanyReport = { name: string; rides: number; km: number; total: number };
|
||
|
|
|
||
|
|
function buildCompanyReports(rides: RideDB[]): CompanyReport[] {
|
||
|
|
const map = new Map<string, CompanyReport>();
|
||
|
|
for (const r of rides) {
|
||
|
|
const prev = map.get(r.company) ?? { name: r.company, rides: 0, km: 0, total: 0 };
|
||
|
|
map.set(r.company, {
|
||
|
|
...prev,
|
||
|
|
rides: prev.rides + 1,
|
||
|
|
km: prev.km + r.km,
|
||
|
|
total: prev.total + r.total,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return Array.from(map.values()).sort((a, b) => b.total - a.total);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── PDF generation ────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
async function getLogoBase64(): Promise<string> {
|
||
|
|
try {
|
||
|
|
const asset = Asset.fromModule(require("@/assets/toptran.png"));
|
||
|
|
await asset.downloadAsync();
|
||
|
|
if (asset.localUri) {
|
||
|
|
const file = new File(asset.localUri);
|
||
|
|
const base64 = await file.base64();
|
||
|
|
return `data:image/png;base64,${base64}`;
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
// logo optional — PDF will show text fallback
|
||
|
|
}
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
function buildPdfHtml(
|
||
|
|
monthLabel: string,
|
||
|
|
userName: string,
|
||
|
|
companies: CompanyReport[],
|
||
|
|
totalRides: number,
|
||
|
|
totalKm: number,
|
||
|
|
totalEarnings: number,
|
||
|
|
logoSrc: string,
|
||
|
|
): string {
|
||
|
|
const rows = companies
|
||
|
|
.map(
|
||
|
|
(c) => `
|
||
|
|
<tr>
|
||
|
|
<td>${c.name}</td>
|
||
|
|
<td class="center">${c.rides}</td>
|
||
|
|
<td class="right">${c.km.toFixed(1)} km</td>
|
||
|
|
<td class="right">R$ ${c.total.toFixed(2)}</td>
|
||
|
|
</tr>`,
|
||
|
|
)
|
||
|
|
.join("");
|
||
|
|
|
||
|
|
const generated = new Date().toLocaleDateString("pt-BR") +
|
||
|
|
" às " + new Date().toLocaleTimeString("pt-BR");
|
||
|
|
|
||
|
|
return `<!DOCTYPE html>
|
||
|
|
<html><head>
|
||
|
|
<meta charset="utf-8"/>
|
||
|
|
<style>
|
||
|
|
* { margin:0; padding:0; box-sizing:border-box; }
|
||
|
|
body { font-family:Arial,sans-serif; color:#1a1a1a; padding:40px; font-size:13px; }
|
||
|
|
.header { display:flex; align-items:center; justify-content:space-between;
|
||
|
|
border-bottom:2px solid #1a1a1a; padding-bottom:16px; margin-bottom:28px; }
|
||
|
|
.logo { height:52px; object-fit:contain; }
|
||
|
|
.logo-text { font-size:26px; font-weight:900; letter-spacing:1px; }
|
||
|
|
.header-info { text-align:right; }
|
||
|
|
.header-info h2 { font-size:18px; font-weight:800; }
|
||
|
|
.header-info p { color:#666; font-size:12px; margin-top:3px; }
|
||
|
|
.summary { display:flex; gap:14px; margin-bottom:32px; }
|
||
|
|
.card { flex:1; border:1px solid #e0e0e0; border-radius:8px; padding:16px; background:#f8f8f8; }
|
||
|
|
.card .val { font-size:22px; font-weight:800; }
|
||
|
|
.card .val.green { color:#10b981; }
|
||
|
|
.card .lbl { font-size:10px; color:#888; text-transform:uppercase; letter-spacing:.5px; margin-top:4px; }
|
||
|
|
h3 { font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:1.2px;
|
||
|
|
color:#666; margin-bottom:12px; }
|
||
|
|
table { width:100%; border-collapse:collapse; margin-bottom:28px; }
|
||
|
|
thead th { background:#1a1a1a; color:#fff; padding:10px 14px; text-align:left; font-size:12px; }
|
||
|
|
tbody tr:nth-child(even) { background:#f5f5f5; }
|
||
|
|
tbody td { padding:10px 14px; border-bottom:1px solid #eee; }
|
||
|
|
.center { text-align:center; }
|
||
|
|
.right { text-align:right; }
|
||
|
|
.total-row td { background:#1a1a1a; color:#fff; font-weight:700; padding:12px 14px; }
|
||
|
|
.footer { margin-top:40px; text-align:center; font-size:11px; color:#aaa;
|
||
|
|
border-top:1px solid #eee; padding-top:16px; }
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="header">
|
||
|
|
${logoSrc
|
||
|
|
? `<img class="logo" src="${logoSrc}" alt="TopTran"/>`
|
||
|
|
: `<span class="logo-text">TopTran</span>`}
|
||
|
|
<div class="header-info">
|
||
|
|
<h2>Relatório de Corridas</h2>
|
||
|
|
<p>${monthLabel}</p>
|
||
|
|
<p>Motorista: ${userName}</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="summary">
|
||
|
|
<div class="card">
|
||
|
|
<div class="val">${totalRides}</div>
|
||
|
|
<div class="lbl">Corridas</div>
|
||
|
|
</div>
|
||
|
|
<div class="card">
|
||
|
|
<div class="val">${totalKm.toFixed(1)}</div>
|
||
|
|
<div class="lbl">Km Rodados</div>
|
||
|
|
</div>
|
||
|
|
<div class="card">
|
||
|
|
<div class="val green">R$ ${totalEarnings.toFixed(2)}</div>
|
||
|
|
<div class="lbl">Total Ganho</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<h3>Detalhamento por Empresa</h3>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Empresa</th>
|
||
|
|
<th class="center">Corridas</th>
|
||
|
|
<th class="right">Km</th>
|
||
|
|
<th class="right">Total</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
${rows}
|
||
|
|
<tr><td class="total-row" colspan="4" style="height:1px;padding:0;background:#555;"></td></tr>
|
||
|
|
<tr>
|
||
|
|
<td class="total-row">TOTAL DO PERÍODO</td>
|
||
|
|
<td class="total-row center">${totalRides}</td>
|
||
|
|
<td class="total-row right">${totalKm.toFixed(1)} km</td>
|
||
|
|
<td class="total-row right">R$ ${totalEarnings.toFixed(2)}</td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
|
||
|
|
<div class="footer">
|
||
|
|
Gerado em ${generated} • TopTran Sistema de Gestão
|
||
|
|
</div>
|
||
|
|
</body></html>`;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Screen ────────────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
const MONTH_OPTIONS = getMonthOptions();
|
||
|
|
|
||
|
|
export default function RelatorioPage() {
|
||
|
|
const { user } = useAuth();
|
||
|
|
const [selectedMonth, setSelectedMonth] = useState(currentYearMonth());
|
||
|
|
const [allRides, setAllRides] = useState<RideDB[]>([]);
|
||
|
|
const [exporting, setExporting] = useState(false);
|
||
|
|
|
||
|
|
useFocusEffect(
|
||
|
|
useCallback(() => {
|
||
|
|
if (!user?.id) return;
|
||
|
|
obterCorridas(user.id).then(setAllRides).catch(console.error);
|
||
|
|
}, [user?.id]),
|
||
|
|
);
|
||
|
|
|
||
|
|
const rides = filterByMonth(allRides, selectedMonth);
|
||
|
|
const companies = buildCompanyReports(rides);
|
||
|
|
const totalKm = rides.reduce((s, r) => s + r.km, 0);
|
||
|
|
const totalEarnings = rides.reduce((s, r) => s + r.total, 0);
|
||
|
|
const monthLabel = MONTH_OPTIONS.find((o) => o.value === selectedMonth)?.label ?? selectedMonth;
|
||
|
|
|
||
|
|
const handleExport = async () => {
|
||
|
|
if (rides.length === 0) {
|
||
|
|
Alert.alert("Sem dados", "Não há corridas registradas em " + monthLabel + ".");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
setExporting(true);
|
||
|
|
const logoSrc = await getLogoBase64();
|
||
|
|
const html = buildPdfHtml(
|
||
|
|
monthLabel,
|
||
|
|
user?.name ?? "Motorista",
|
||
|
|
companies,
|
||
|
|
rides.length,
|
||
|
|
totalKm,
|
||
|
|
totalEarnings,
|
||
|
|
logoSrc,
|
||
|
|
);
|
||
|
|
const { uri } = await Print.printToFileAsync({ html });
|
||
|
|
await Sharing.shareAsync(uri, {
|
||
|
|
mimeType: "application/pdf",
|
||
|
|
dialogTitle: `Relatório ${monthLabel}`,
|
||
|
|
});
|
||
|
|
} catch (e: any) {
|
||
|
|
Alert.alert("Erro", e?.message ?? "Não foi possível gerar o PDF.");
|
||
|
|
} finally {
|
||
|
|
setExporting(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<SafeAreaView style={styles.container}>
|
||
|
|
<View style={styles.header}>
|
||
|
|
<TouchableOpacity onPress={() => router.back()} style={styles.backButton} activeOpacity={0.7}>
|
||
|
|
<Text style={styles.backIcon}>←</Text>
|
||
|
|
<Text style={styles.backLabel}>Voltar</Text>
|
||
|
|
</TouchableOpacity>
|
||
|
|
</View>
|
||
|
|
|
||
|
|
<ScrollView contentContainerStyle={styles.scroll} showsVerticalScrollIndicator={false}>
|
||
|
|
<View style={styles.titleSection}>
|
||
|
|
<Text style={styles.title}>Relatório</Text>
|
||
|
|
<Text style={styles.subtitle}>Análise de ganhos por período</Text>
|
||
|
|
</View>
|
||
|
|
|
||
|
|
{/* Filtro de mês */}
|
||
|
|
<View style={styles.filterCard}>
|
||
|
|
<Text style={styles.cardLabel}>Período</Text>
|
||
|
|
<Select value={selectedMonth} onValueChange={setSelectedMonth} items={MONTH_OPTIONS} />
|
||
|
|
</View>
|
||
|
|
|
||
|
|
{/* Cards de resumo */}
|
||
|
|
<View style={styles.summaryRow}>
|
||
|
|
<View style={styles.summaryCard}>
|
||
|
|
<Text style={styles.summaryValue}>{rides.length}</Text>
|
||
|
|
<Text style={styles.summaryLabel}>Corridas</Text>
|
||
|
|
</View>
|
||
|
|
<View style={styles.summaryCard}>
|
||
|
|
<Text style={styles.summaryValue}>{totalKm.toFixed(1)}</Text>
|
||
|
|
<Text style={styles.summaryLabel}>Km</Text>
|
||
|
|
</View>
|
||
|
|
<View style={[styles.summaryCard, styles.summaryCardAccent]}>
|
||
|
|
<Text style={[styles.summaryValue, styles.summaryValueAccent]}>
|
||
|
|
R$ {totalEarnings.toFixed(2)}
|
||
|
|
</Text>
|
||
|
|
<Text style={styles.summaryLabel}>Total</Text>
|
||
|
|
</View>
|
||
|
|
</View>
|
||
|
|
|
||
|
|
{/* Tabela por empresa */}
|
||
|
|
<Text style={styles.sectionTitle}>Por Empresa</Text>
|
||
|
|
|
||
|
|
{companies.length === 0 ? (
|
||
|
|
<View style={styles.emptyCard}>
|
||
|
|
<Text style={styles.emptyText}>Nenhuma corrida em {monthLabel}.</Text>
|
||
|
|
</View>
|
||
|
|
) : (
|
||
|
|
<>
|
||
|
|
{companies.map((c) => (
|
||
|
|
<View key={c.name} style={styles.companyCard}>
|
||
|
|
<View style={styles.companyHeader}>
|
||
|
|
<Text style={styles.companyName}>{c.name}</Text>
|
||
|
|
<Text style={styles.companyBadge}>
|
||
|
|
{c.rides} corrida{c.rides !== 1 ? "s" : ""}
|
||
|
|
</Text>
|
||
|
|
</View>
|
||
|
|
<View style={styles.companyStats}>
|
||
|
|
<Text style={styles.companyKm}>{c.km.toFixed(1)} km</Text>
|
||
|
|
<Text style={styles.companyTotal}>R$ {c.total.toFixed(2)}</Text>
|
||
|
|
</View>
|
||
|
|
</View>
|
||
|
|
))}
|
||
|
|
|
||
|
|
<View style={styles.totalCard}>
|
||
|
|
<View style={styles.companyHeader}>
|
||
|
|
<Text style={styles.totalLabel}>Total do Período</Text>
|
||
|
|
</View>
|
||
|
|
<View style={styles.companyStats}>
|
||
|
|
<Text style={styles.companyKm}>{totalKm.toFixed(1)} km</Text>
|
||
|
|
<Text style={styles.totalAmount}>R$ {totalEarnings.toFixed(2)}</Text>
|
||
|
|
</View>
|
||
|
|
</View>
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Botão exportar */}
|
||
|
|
<TouchableOpacity
|
||
|
|
style={[styles.exportButton, exporting && styles.exportButtonDisabled]}
|
||
|
|
onPress={handleExport}
|
||
|
|
disabled={exporting}
|
||
|
|
activeOpacity={0.85}
|
||
|
|
>
|
||
|
|
{exporting ? (
|
||
|
|
<ActivityIndicator color={COLORS.background} size="small" />
|
||
|
|
) : (
|
||
|
|
<Text style={styles.exportButtonText}>Exportar PDF</Text>
|
||
|
|
)}
|
||
|
|
</TouchableOpacity>
|
||
|
|
</ScrollView>
|
||
|
|
</SafeAreaView>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const styles = StyleSheet.create({
|
||
|
|
container: { flex: 1, backgroundColor: COLORS.background },
|
||
|
|
|
||
|
|
header: {
|
||
|
|
paddingHorizontal: SPACING.lg,
|
||
|
|
paddingVertical: SPACING.md,
|
||
|
|
borderBottomWidth: 1,
|
||
|
|
borderBottomColor: COLORS.border,
|
||
|
|
},
|
||
|
|
backButton: {
|
||
|
|
flexDirection: "row",
|
||
|
|
alignItems: "center",
|
||
|
|
gap: SPACING.xs,
|
||
|
|
alignSelf: "flex-start",
|
||
|
|
},
|
||
|
|
backIcon: { fontSize: 18, color: COLORS.text },
|
||
|
|
backLabel: { fontSize: 15, color: COLORS.textSecondary, fontWeight: "500" },
|
||
|
|
|
||
|
|
scroll: { padding: SPACING.lg, paddingBottom: 48 },
|
||
|
|
|
||
|
|
titleSection: { marginTop: SPACING.md, marginBottom: SPACING.xl },
|
||
|
|
title: { fontSize: 28, fontWeight: "800", color: COLORS.text, marginBottom: 4 },
|
||
|
|
subtitle: { fontSize: 14, color: COLORS.textTertiary },
|
||
|
|
|
||
|
|
filterCard: {
|
||
|
|
backgroundColor: COLORS.surface,
|
||
|
|
borderRadius: BORDER_RADIUS.lg,
|
||
|
|
padding: SPACING.lg,
|
||
|
|
borderWidth: 1,
|
||
|
|
borderColor: COLORS.border,
|
||
|
|
marginBottom: SPACING.lg,
|
||
|
|
},
|
||
|
|
cardLabel: {
|
||
|
|
fontSize: 11,
|
||
|
|
fontWeight: "700",
|
||
|
|
color: COLORS.textTertiary,
|
||
|
|
textTransform: "uppercase",
|
||
|
|
letterSpacing: 0.8,
|
||
|
|
marginBottom: SPACING.sm,
|
||
|
|
},
|
||
|
|
|
||
|
|
summaryRow: { flexDirection: "row", gap: SPACING.sm, marginBottom: SPACING.xl },
|
||
|
|
summaryCard: {
|
||
|
|
flex: 1,
|
||
|
|
backgroundColor: COLORS.surface,
|
||
|
|
borderRadius: BORDER_RADIUS.lg,
|
||
|
|
padding: SPACING.md,
|
||
|
|
borderWidth: 1,
|
||
|
|
borderColor: COLORS.border,
|
||
|
|
alignItems: "center",
|
||
|
|
},
|
||
|
|
summaryCardAccent: { borderColor: COLORS.success },
|
||
|
|
summaryValue: { fontSize: 20, fontWeight: "800", color: COLORS.text, marginBottom: 2 },
|
||
|
|
summaryValueAccent: { color: COLORS.success },
|
||
|
|
summaryLabel: {
|
||
|
|
fontSize: 10,
|
||
|
|
color: COLORS.textTertiary,
|
||
|
|
fontWeight: "600",
|
||
|
|
textTransform: "uppercase",
|
||
|
|
letterSpacing: 0.5,
|
||
|
|
},
|
||
|
|
|
||
|
|
sectionTitle: {
|
||
|
|
fontSize: 11,
|
||
|
|
fontWeight: "700",
|
||
|
|
color: COLORS.textTertiary,
|
||
|
|
textTransform: "uppercase",
|
||
|
|
letterSpacing: 1.2,
|
||
|
|
marginBottom: SPACING.md,
|
||
|
|
},
|
||
|
|
|
||
|
|
emptyCard: {
|
||
|
|
backgroundColor: COLORS.surface,
|
||
|
|
borderRadius: BORDER_RADIUS.lg,
|
||
|
|
padding: SPACING.xl,
|
||
|
|
borderWidth: 1,
|
||
|
|
borderColor: COLORS.border,
|
||
|
|
alignItems: "center",
|
||
|
|
marginBottom: SPACING.xl,
|
||
|
|
},
|
||
|
|
emptyText: { color: COLORS.textTertiary, fontSize: 14 },
|
||
|
|
|
||
|
|
companyCard: {
|
||
|
|
backgroundColor: COLORS.surface,
|
||
|
|
borderRadius: BORDER_RADIUS.lg,
|
||
|
|
padding: SPACING.lg,
|
||
|
|
borderWidth: 1,
|
||
|
|
borderColor: COLORS.border,
|
||
|
|
marginBottom: SPACING.sm,
|
||
|
|
},
|
||
|
|
companyHeader: {
|
||
|
|
flexDirection: "row",
|
||
|
|
justifyContent: "space-between",
|
||
|
|
alignItems: "center",
|
||
|
|
marginBottom: SPACING.sm,
|
||
|
|
},
|
||
|
|
companyName: { fontSize: 15, fontWeight: "700", color: COLORS.text, flex: 1 },
|
||
|
|
companyBadge: {
|
||
|
|
fontSize: 12,
|
||
|
|
color: COLORS.textTertiary,
|
||
|
|
backgroundColor: COLORS.surfaceLight,
|
||
|
|
paddingHorizontal: SPACING.sm,
|
||
|
|
paddingVertical: 3,
|
||
|
|
borderRadius: BORDER_RADIUS.sm,
|
||
|
|
overflow: "hidden",
|
||
|
|
},
|
||
|
|
companyStats: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" },
|
||
|
|
companyKm: { fontSize: 13, color: COLORS.textSecondary },
|
||
|
|
companyTotal: { fontSize: 18, fontWeight: "800", color: COLORS.text },
|
||
|
|
|
||
|
|
totalCard: {
|
||
|
|
backgroundColor: COLORS.surfaceLight,
|
||
|
|
borderRadius: BORDER_RADIUS.lg,
|
||
|
|
padding: SPACING.lg,
|
||
|
|
borderWidth: 1,
|
||
|
|
borderColor: COLORS.borderLight,
|
||
|
|
marginBottom: SPACING.xl,
|
||
|
|
borderLeftWidth: 3,
|
||
|
|
borderLeftColor: COLORS.success,
|
||
|
|
},
|
||
|
|
totalLabel: { fontSize: 13, fontWeight: "700", color: COLORS.text },
|
||
|
|
totalAmount: { fontSize: 22, fontWeight: "800", color: COLORS.success },
|
||
|
|
|
||
|
|
exportButton: {
|
||
|
|
backgroundColor: COLORS.text,
|
||
|
|
borderRadius: BORDER_RADIUS.lg,
|
||
|
|
paddingVertical: SPACING.lg,
|
||
|
|
alignItems: "center",
|
||
|
|
},
|
||
|
|
exportButtonDisabled: { backgroundColor: COLORS.borderLight },
|
||
|
|
exportButtonText: { fontSize: 15, fontWeight: "700", color: COLORS.background },
|
||
|
|
});
|