Compare commits

..

No commits in common. "92aa1cb2bb05edd2646c08d84a6bc7fe38f0d44e" and "c9e948b8ddcee1838075a31142e6d4b127c61bed" have entirely different histories.

8 changed files with 28 additions and 7487 deletions

View file

@ -11,7 +11,7 @@
"buildType": "apk" "buildType": "apk"
}, },
"env": { "env": {
"EXPO_PUBLIC_API_URL": "https://toptran.olymp.com.br/api" "EXPO_PUBLIC_API_URL": "https://toptran.olymp.com.br"
} }
}, },
"preview": { "preview": {
@ -20,7 +20,7 @@
"buildType": "apk" "buildType": "apk"
}, },
"env": { "env": {
"EXPO_PUBLIC_API_URL": "https://toptran.olymp.com.br/api" "EXPO_PUBLIC_API_URL": "https://toptran.olymp.com.br"
} }
}, },
"production": { "production": {
@ -29,7 +29,7 @@
"buildType": "app-bundle" "buildType": "app-bundle"
}, },
"env": { "env": {
"EXPO_PUBLIC_API_URL": "https://toptran.olymp.com.br/api" "EXPO_PUBLIC_API_URL": "https://toptran.olymp.com.br"
} }
} }
}, },

View file

@ -2,17 +2,4 @@ const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname); const config = getDefaultConfig(__dirname);
config.resolver.assetExts.push('wasm');
config.server = {
...config.server,
enhanceMiddleware: (middleware) => {
return (req, res, next) => {
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
return middleware(req, res, next);
};
},
};
module.exports = config; module.exports = config;

File diff suppressed because it is too large Load diff

View file

@ -38,7 +38,6 @@
}, },
"devDependencies": { "devDependencies": {
"@types/react": "~19.2.10", "@types/react": "~19.2.10",
"expo-module-scripts": "^55.0.2",
"react-test-renderer": "19.1.0", "react-test-renderer": "19.1.0",
"typescript": "~5.9.2" "typescript": "~5.9.2"
}, },

View file

@ -346,7 +346,7 @@ const styles = StyleSheet.create({
borderColor: COLORS.borderLight, borderColor: COLORS.borderLight,
}, },
statValue: { statValue: {
fontSize: 18, fontSize: 20,
fontWeight: "800", fontWeight: "800",
color: COLORS.text, color: COLORS.text,
marginBottom: 2, marginBottom: 2,

View file

@ -31,10 +31,7 @@ function getMonthOptions() {
return Array.from({ length: 12 }, (_, i) => { return Array.from({ length: 12 }, (_, i) => {
const d = new Date(now.getFullYear(), now.getMonth() - i, 1); const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
const value = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`; const value = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
const label = d.toLocaleDateString("pt-BR", { const label = d.toLocaleDateString("pt-BR", { month: "long", year: "numeric" });
month: "long",
year: "numeric",
});
return { value, label: label.charAt(0).toUpperCase() + label.slice(1) }; return { value, label: label.charAt(0).toUpperCase() + label.slice(1) };
}); });
} }
@ -63,12 +60,7 @@ type CompanyReport = { name: string; rides: number; km: number; total: number };
function buildCompanyReports(rides: RideDB[]): CompanyReport[] { function buildCompanyReports(rides: RideDB[]): CompanyReport[] {
const map = new Map<string, CompanyReport>(); const map = new Map<string, CompanyReport>();
for (const r of rides) { for (const r of rides) {
const prev = map.get(r.company) ?? { const prev = map.get(r.company) ?? { name: r.company, rides: 0, km: 0, total: 0 };
name: r.company,
rides: 0,
km: 0,
total: 0,
};
map.set(r.company, { map.set(r.company, {
...prev, ...prev,
rides: prev.rides + 1, rides: prev.rides + 1,
@ -117,10 +109,8 @@ function buildPdfHtml(
) )
.join(""); .join("");
const generated = const generated = new Date().toLocaleDateString("pt-BR") +
new Date().toLocaleDateString("pt-BR") + " às " + new Date().toLocaleTimeString("pt-BR");
" às " +
new Date().toLocaleTimeString("pt-BR");
return `<!DOCTYPE html> return `<!DOCTYPE html>
<html><head> <html><head>
@ -155,11 +145,9 @@ function buildPdfHtml(
</head> </head>
<body> <body>
<div class="header"> <div class="header">
${ ${logoSrc
logoSrc ? `<img class="logo" src="${logoSrc}" alt="TopTran"/>`
? `<img class="logo" src="${logoSrc}" alt="TopTran"/>` : `<span class="logo-text">TopTran</span>`}
: `<span class="logo-text">TopTran</span>`
}
<div class="header-info"> <div class="header-info">
<h2>Relatório de Corridas</h2> <h2>Relatório de Corridas</h2>
<p>${monthLabel}</p> <p>${monthLabel}</p>
@ -231,16 +219,11 @@ export default function RelatorioPage() {
const companies = buildCompanyReports(rides); const companies = buildCompanyReports(rides);
const totalKm = rides.reduce((s, r) => s + r.km, 0); const totalKm = rides.reduce((s, r) => s + r.km, 0);
const totalEarnings = rides.reduce((s, r) => s + r.total, 0); const totalEarnings = rides.reduce((s, r) => s + r.total, 0);
const monthLabel = const monthLabel = MONTH_OPTIONS.find((o) => o.value === selectedMonth)?.label ?? selectedMonth;
MONTH_OPTIONS.find((o) => o.value === selectedMonth)?.label ??
selectedMonth;
const handleExport = async () => { const handleExport = async () => {
if (rides.length === 0) { if (rides.length === 0) {
Alert.alert( Alert.alert("Sem dados", "Não há corridas registradas em " + monthLabel + ".");
"Sem dados",
"Não há corridas registradas em " + monthLabel + ".",
);
return; return;
} }
try { try {
@ -270,20 +253,13 @@ export default function RelatorioPage() {
return ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
<View style={styles.header}> <View style={styles.header}>
<TouchableOpacity <TouchableOpacity onPress={() => router.back()} style={styles.backButton} activeOpacity={0.7}>
onPress={() => router.back()}
style={styles.backButton}
activeOpacity={0.7}
>
<Text style={styles.backIcon}></Text> <Text style={styles.backIcon}></Text>
<Text style={styles.backLabel}>Voltar</Text> <Text style={styles.backLabel}>Voltar</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<ScrollView <ScrollView contentContainerStyle={styles.scroll} showsVerticalScrollIndicator={false}>
contentContainerStyle={styles.scroll}
showsVerticalScrollIndicator={false}
>
<View style={styles.titleSection}> <View style={styles.titleSection}>
<Text style={styles.title}>Relatório</Text> <Text style={styles.title}>Relatório</Text>
<Text style={styles.subtitle}>Análise de ganhos por período</Text> <Text style={styles.subtitle}>Análise de ganhos por período</Text>
@ -292,11 +268,7 @@ export default function RelatorioPage() {
{/* Filtro de mês */} {/* Filtro de mês */}
<View style={styles.filterCard}> <View style={styles.filterCard}>
<Text style={styles.cardLabel}>Período</Text> <Text style={styles.cardLabel}>Período</Text>
<Select <Select value={selectedMonth} onValueChange={setSelectedMonth} items={MONTH_OPTIONS} />
value={selectedMonth}
onValueChange={setSelectedMonth}
items={MONTH_OPTIONS}
/>
</View> </View>
{/* Cards de resumo */} {/* Cards de resumo */}
@ -322,9 +294,7 @@ export default function RelatorioPage() {
{companies.length === 0 ? ( {companies.length === 0 ? (
<View style={styles.emptyCard}> <View style={styles.emptyCard}>
<Text style={styles.emptyText}> <Text style={styles.emptyText}>Nenhuma corrida em {monthLabel}.</Text>
Nenhuma corrida em {monthLabel}.
</Text>
</View> </View>
) : ( ) : (
<> <>
@ -338,9 +308,7 @@ export default function RelatorioPage() {
</View> </View>
<View style={styles.companyStats}> <View style={styles.companyStats}>
<Text style={styles.companyKm}>{c.km.toFixed(1)} km</Text> <Text style={styles.companyKm}>{c.km.toFixed(1)} km</Text>
<Text style={styles.companyTotal}> <Text style={styles.companyTotal}>R$ {c.total.toFixed(2)}</Text>
R$ {c.total.toFixed(2)}
</Text>
</View> </View>
</View> </View>
))} ))}
@ -351,9 +319,7 @@ export default function RelatorioPage() {
</View> </View>
<View style={styles.companyStats}> <View style={styles.companyStats}>
<Text style={styles.companyKm}>{totalKm.toFixed(1)} km</Text> <Text style={styles.companyKm}>{totalKm.toFixed(1)} km</Text>
<Text style={styles.totalAmount}> <Text style={styles.totalAmount}>R$ {totalEarnings.toFixed(2)}</Text>
R$ {totalEarnings.toFixed(2)}
</Text>
</View> </View>
</View> </View>
</> </>
@ -361,10 +327,7 @@ export default function RelatorioPage() {
{/* Botão exportar */} {/* Botão exportar */}
<TouchableOpacity <TouchableOpacity
style={[ style={[styles.exportButton, exporting && styles.exportButtonDisabled]}
styles.exportButton,
exporting && styles.exportButtonDisabled,
]}
onPress={handleExport} onPress={handleExport}
disabled={exporting} disabled={exporting}
activeOpacity={0.85} activeOpacity={0.85}
@ -401,12 +364,7 @@ const styles = StyleSheet.create({
scroll: { padding: SPACING.lg, paddingBottom: 48 }, scroll: { padding: SPACING.lg, paddingBottom: 48 },
titleSection: { marginTop: SPACING.md, marginBottom: SPACING.xl }, titleSection: { marginTop: SPACING.md, marginBottom: SPACING.xl },
title: { title: { fontSize: 28, fontWeight: "800", color: COLORS.text, marginBottom: 4 },
fontSize: 28,
fontWeight: "800",
color: COLORS.text,
marginBottom: 4,
},
subtitle: { fontSize: 14, color: COLORS.textTertiary }, subtitle: { fontSize: 14, color: COLORS.textTertiary },
filterCard: { filterCard: {
@ -426,11 +384,7 @@ const styles = StyleSheet.create({
marginBottom: SPACING.sm, marginBottom: SPACING.sm,
}, },
summaryRow: { summaryRow: { flexDirection: "row", gap: SPACING.sm, marginBottom: SPACING.xl },
flexDirection: "row",
gap: SPACING.sm,
marginBottom: SPACING.xl,
},
summaryCard: { summaryCard: {
flex: 1, flex: 1,
backgroundColor: COLORS.surface, backgroundColor: COLORS.surface,
@ -441,12 +395,7 @@ const styles = StyleSheet.create({
alignItems: "center", alignItems: "center",
}, },
summaryCardAccent: { borderColor: COLORS.success }, summaryCardAccent: { borderColor: COLORS.success },
summaryValue: { summaryValue: { fontSize: 20, fontWeight: "800", color: COLORS.text, marginBottom: 2 },
fontSize: 18,
fontWeight: "800",
color: COLORS.text,
marginBottom: 2,
},
summaryValueAccent: { color: COLORS.success }, summaryValueAccent: { color: COLORS.success },
summaryLabel: { summaryLabel: {
fontSize: 10, fontSize: 10,
@ -500,11 +449,7 @@ const styles = StyleSheet.create({
borderRadius: BORDER_RADIUS.sm, borderRadius: BORDER_RADIUS.sm,
overflow: "hidden", overflow: "hidden",
}, },
companyStats: { companyStats: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" },
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
companyKm: { fontSize: 13, color: COLORS.textSecondary }, companyKm: { fontSize: 13, color: COLORS.textSecondary },
companyTotal: { fontSize: 18, fontWeight: "800", color: COLORS.text }, companyTotal: { fontSize: 18, fontWeight: "800", color: COLORS.text },
@ -528,9 +473,5 @@ const styles = StyleSheet.create({
alignItems: "center", alignItems: "center",
}, },
exportButtonDisabled: { backgroundColor: COLORS.borderLight }, exportButtonDisabled: { backgroundColor: COLORS.borderLight },
exportButtonText: { exportButtonText: { fontSize: 15, fontWeight: "700", color: COLORS.background },
fontSize: 15,
fontWeight: "700",
color: COLORS.background,
},
}); });

View file

@ -1,6 +1,6 @@
import * as SQLite from "expo-sqlite"; import * as SQLite from "expo-sqlite";
const dbPromise = SQLite.openDatabaseAsync("toptran.db"); const db = SQLite.openDatabaseSync("toptran.db");
export type UserDB = { export type UserDB = {
id: string; id: string;
@ -32,7 +32,6 @@ export type CompanyDB = {
export const initDB = async () => { export const initDB = async () => {
try { try {
const db = await dbPromise;
await db.execAsync( await db.execAsync(
`CREATE TABLE IF NOT EXISTS users ( `CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
@ -80,7 +79,6 @@ export const initDB = async () => {
// SETTINGS (key-value store) // SETTINGS (key-value store)
export const getSetting = async (key: string): Promise<string | null> => { export const getSetting = async (key: string): Promise<string | null> => {
try { try {
const db = await dbPromise;
const row = await db.getFirstAsync<{ value: string }>( const row = await db.getFirstAsync<{ value: string }>(
`SELECT value FROM settings WHERE key = ?`, `SELECT value FROM settings WHERE key = ?`,
[key], [key],
@ -94,7 +92,6 @@ export const getSetting = async (key: string): Promise<string | null> => {
export const setSetting = async (key: string, value: string): Promise<void> => { export const setSetting = async (key: string, value: string): Promise<void> => {
try { try {
const db = await dbPromise;
await db.runAsync( await db.runAsync(
`INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)`, `INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)`,
[key, value], [key, value],
@ -107,7 +104,6 @@ export const setSetting = async (key: string, value: string): Promise<void> => {
export const deleteSetting = async (key: string): Promise<void> => { export const deleteSetting = async (key: string): Promise<void> => {
try { try {
const db = await dbPromise;
await db.runAsync(`DELETE FROM settings WHERE key = ?`, [key]); await db.runAsync(`DELETE FROM settings WHERE key = ?`, [key]);
} catch (error) { } catch (error) {
console.error("Error deleting setting:", error); console.error("Error deleting setting:", error);
@ -120,7 +116,6 @@ export const salvarUsuario = async (
usuario: Omit<UserDB, "createdAt">, usuario: Omit<UserDB, "createdAt">,
): Promise<string> => { ): Promise<string> => {
try { try {
const db = await dbPromise;
const existing = await db.getFirstAsync<{ id: string }>( const existing = await db.getFirstAsync<{ id: string }>(
`SELECT id FROM users WHERE email = ?`, `SELECT id FROM users WHERE email = ?`,
[usuario.email], [usuario.email],
@ -161,7 +156,6 @@ export const obterUsuario = async (
usuarioId: string, usuarioId: string,
): Promise<UserDB | null> => { ): Promise<UserDB | null> => {
try { try {
const db = await dbPromise;
const result = await db.getFirstAsync<UserDB>( const result = await db.getFirstAsync<UserDB>(
`SELECT * FROM users WHERE id = ?`, `SELECT * FROM users WHERE id = ?`,
[usuarioId], [usuarioId],
@ -176,7 +170,6 @@ export const obterUsuario = async (
// RIDES // RIDES
export const salvarCorrida = async (corrida: Omit<RideDB, "createdAt">) => { export const salvarCorrida = async (corrida: Omit<RideDB, "createdAt">) => {
try { try {
const db = await dbPromise;
const result = await db.runAsync( const result = await db.runAsync(
`INSERT INTO rides (id, user_id, company, km, cost_per_km, total, ride_date, synced) `INSERT INTO rides (id, user_id, company, km, cost_per_km, total, ride_date, synced)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
@ -202,7 +195,6 @@ export const obterCorridas = async (
usuarioId: string, usuarioId: string,
): Promise<RideDB[]> => { ): Promise<RideDB[]> => {
try { try {
const db = await dbPromise;
const result = await db.getAllAsync<RideDB>( const result = await db.getAllAsync<RideDB>(
`SELECT * FROM rides WHERE user_id = ? ORDER BY createdAt DESC`, `SELECT * FROM rides WHERE user_id = ? ORDER BY createdAt DESC`,
[usuarioId], [usuarioId],
@ -218,7 +210,6 @@ export const obterCorridasNaoSincronizadas = async (
usuarioId: string, usuarioId: string,
): Promise<RideDB[]> => { ): Promise<RideDB[]> => {
try { try {
const db = await dbPromise;
const result = await db.getAllAsync<RideDB>( const result = await db.getAllAsync<RideDB>(
`SELECT * FROM rides WHERE user_id = ? AND synced = 0`, `SELECT * FROM rides WHERE user_id = ? AND synced = 0`,
[usuarioId], [usuarioId],
@ -232,7 +223,6 @@ export const obterCorridasNaoSincronizadas = async (
export const marcarCorridaComoSincronizada = async (corridaId: string) => { export const marcarCorridaComoSincronizada = async (corridaId: string) => {
try { try {
const db = await dbPromise;
await db.runAsync(`UPDATE rides SET synced = 1 WHERE id = ?`, [corridaId]); await db.runAsync(`UPDATE rides SET synced = 1 WHERE id = ?`, [corridaId]);
} catch (error) { } catch (error) {
console.error("Error marking ride as synced:", error); console.error("Error marking ride as synced:", error);
@ -242,7 +232,6 @@ export const marcarCorridaComoSincronizada = async (corridaId: string) => {
export const deletarCorrida = async (corridaId: string) => { export const deletarCorrida = async (corridaId: string) => {
try { try {
const db = await dbPromise;
await db.runAsync(`DELETE FROM rides WHERE id = ?`, [corridaId]); await db.runAsync(`DELETE FROM rides WHERE id = ?`, [corridaId]);
} catch (error) { } catch (error) {
console.error("Error deleting ride:", error); console.error("Error deleting ride:", error);
@ -252,7 +241,6 @@ export const deletarCorrida = async (corridaId: string) => {
export const limparBancoDados = async () => { export const limparBancoDados = async () => {
try { try {
const db = await dbPromise;
await db.execAsync(`DELETE FROM rides; DELETE FROM users;`); await db.execAsync(`DELETE FROM rides; DELETE FROM users;`);
console.log("Database cleared"); console.log("Database cleared");
} catch (error) { } catch (error) {
@ -264,7 +252,6 @@ export const limparBancoDados = async () => {
// COMPANIES // COMPANIES
export const obterEmpresas = async (): Promise<CompanyDB[]> => { export const obterEmpresas = async (): Promise<CompanyDB[]> => {
try { try {
const db = await dbPromise;
return ( return (
(await db.getAllAsync<CompanyDB>( (await db.getAllAsync<CompanyDB>(
`SELECT * FROM companies ORDER BY name ASC`, `SELECT * FROM companies ORDER BY name ASC`,
@ -280,7 +267,6 @@ export const salvarEmpresa = async (
empresa: Omit<CompanyDB, "createdAt">, empresa: Omit<CompanyDB, "createdAt">,
): Promise<void> => { ): Promise<void> => {
try { try {
const db = await dbPromise;
await db.runAsync( await db.runAsync(
`INSERT INTO companies (id, name, cost_per_km, notes) VALUES (?, ?, ?, ?)`, `INSERT INTO companies (id, name, cost_per_km, notes) VALUES (?, ?, ?, ?)`,
[empresa.id, empresa.name, empresa.cost_per_km, empresa.notes], [empresa.id, empresa.name, empresa.cost_per_km, empresa.notes],
@ -295,7 +281,6 @@ export const upsertEmpresaLocal = async (
empresa: Omit<CompanyDB, "createdAt">, empresa: Omit<CompanyDB, "createdAt">,
): Promise<void> => { ): Promise<void> => {
try { try {
const db = await dbPromise;
await db.runAsync( await db.runAsync(
`INSERT OR REPLACE INTO companies (id, name, cost_per_km, notes) VALUES (?, ?, ?, ?)`, `INSERT OR REPLACE INTO companies (id, name, cost_per_km, notes) VALUES (?, ?, ?, ?)`,
[empresa.id, empresa.name, empresa.cost_per_km, empresa.notes], [empresa.id, empresa.name, empresa.cost_per_km, empresa.notes],
@ -310,7 +295,6 @@ export const upsertCorridaLocal = async (
corrida: Omit<RideDB, "createdAt">, corrida: Omit<RideDB, "createdAt">,
): Promise<void> => { ): Promise<void> => {
try { try {
const db = await dbPromise;
await db.runAsync( await db.runAsync(
`INSERT OR REPLACE INTO rides (id, user_id, company, km, cost_per_km, total, ride_date, synced) `INSERT OR REPLACE INTO rides (id, user_id, company, km, cost_per_km, total, ride_date, synced)
VALUES (?, ?, ?, ?, ?, ?, ?, 1)`, VALUES (?, ?, ?, ?, ?, ?, ?, 1)`,
@ -326,7 +310,6 @@ export const atualizarEmpresa = async (
empresa: Omit<CompanyDB, "createdAt">, empresa: Omit<CompanyDB, "createdAt">,
): Promise<void> => { ): Promise<void> => {
try { try {
const db = await dbPromise;
await db.runAsync( await db.runAsync(
`UPDATE companies SET name = ?, cost_per_km = ?, notes = ? WHERE id = ?`, `UPDATE companies SET name = ?, cost_per_km = ?, notes = ? WHERE id = ?`,
[empresa.name, empresa.cost_per_km, empresa.notes, empresa.id], [empresa.name, empresa.cost_per_km, empresa.notes, empresa.id],
@ -339,7 +322,6 @@ export const atualizarEmpresa = async (
export const deletarEmpresa = async (id: string): Promise<void> => { export const deletarEmpresa = async (id: string): Promise<void> => {
try { try {
const db = await dbPromise;
await db.runAsync(`DELETE FROM companies WHERE id = ?`, [id]); await db.runAsync(`DELETE FROM companies WHERE id = ?`, [id]);
} catch (error) { } catch (error) {
console.error("Error deleting company:", error); console.error("Error deleting company:", error);

BIN
toptran-app/toptran-v1.apk Normal file

Binary file not shown.