diff --git a/src/App.jsx b/src/App.jsx index b50d788..c6dde97 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -13,6 +13,18 @@ function addMonths(ym, delta) { return fmt(d); } +// Detecta móvil para ajustar alturas de charts +function useIsMobile() { + const [isMobile, setIsMobile] = useState(false); + useEffect(() => { + const onResize = () => setIsMobile(window.innerWidth < 640); + onResize(); + window.addEventListener('resize', onResize); + return () => window.removeEventListener('resize', onResize); + }, []); + return isMobile; +} + const FinanceTracker = () => { // ====== Filtros (mes / histórico) ====== const [rangeMode, setRangeMode] = useState('month'); // 'month' | 'all' @@ -35,11 +47,11 @@ const FinanceTracker = () => { return () => { mounted = false; }; }, []); - // ====== Datos (hook SIEMPRE se llama para mantener orden de hooks) ====== - const userId = user?.userId ?? null; // el backend ya toma userId del token + // ====== Datos (el hook SIEMPRE se llama para mantener orden) ====== + const userId = user?.userId ?? null; const { items, loading, error, create, remove } = useTransactions(userId, queryMonth); - // ====== Estado local (resto de hooks antes de cualquier return) ====== + // ====== Estado local UI ====== const [creditCards, setCreditCards] = useState([ { id: 1, name: 'Visa Oro', limit: 30000, balance: 15000, cutoffDay: 15, paymentDay: 25 }, { id: 2, name: 'Mastercard', limit: 20000, balance: 5000, cutoffDay: 10, paymentDay: 20 } @@ -80,7 +92,6 @@ const FinanceTracker = () => { if (!category || Number.isNaN(n)) return; const signed = type === 'egreso' ? -Math.abs(n) : Math.abs(n); const res = await create({ date, amount: signed, category, note: description, account: 'Other' }); - // reset setNewTransaction({ date: new Date().toISOString().split('T')[0], type: 'egreso', category: '', amount: '', description: '' @@ -162,6 +173,9 @@ const FinanceTracker = () => { return { totalIngresos, totalEgresos, balance, pieData, lineData, totalCreditLimit, totalCreditUsed, creditAvailable }; }, [transactions, creditCards]); + const isMobile = useIsMobile(); + const chartHeight = isMobile ? 220 : 300; + // ====== Early returns (después de TODOS los hooks) ====== if (loadingUser) return
Cargando sesión…
; if (!user) { window.location.href = '/login'; return null; } @@ -171,35 +185,35 @@ const FinanceTracker = () => { // ====== UI ====== return ( -
+
-
-

- - Control de Finanzas Personales +
+

+ + Control de Finanzas Personales

-
- {user.userDetails} - -
{/* Tabs */} -
+
{['dashboard', 'transacciones', 'tarjetas'].map(tab => (
{/* Selector de rango (Mes / Histórico) */} -
- setRangeMode(e.target.value)} className="border rounded px-2 h-10 text-sm"> {rangeMode === 'month' ? ( <> - + setMonth(e.target.value)} - className="border rounded px-2 py-1" + className="border rounded px-2 h-10 text-sm w-[11.5rem] sm:w-auto" /> - - Mostrando: {month} + + Mostrando: {month} ) : ( - Mostrando: histórico completo + Mostrando: histórico completo )}
@@ -241,55 +255,55 @@ const FinanceTracker = () => { {activeTab === 'dashboard' && (
-
-
+
+
-

Total Ingresos

-

${statistics.totalIngresos.toLocaleString()}

+

Total Ingresos

+

${statistics.totalIngresos.toLocaleString()}

- +
-
+
-

Total Egresos

-

${statistics.totalEgresos.toLocaleString()}

+

Total Egresos

+

${statistics.totalEgresos.toLocaleString()}

- +
-
+
-

Balance

-

= 0 ? 'text-blue-600' : 'text-red-600'}`}> +

Balance

+

= 0 ? 'text-blue-600' : 'text-red-600'}`}> ${statistics.balance.toLocaleString()}

- +
-
+
-

Crédito Disponible

-

${statistics.creditAvailable.toLocaleString()}

+

Crédito Disponible

+

${statistics.creditAvailable.toLocaleString()}

- +
-
-

Egresos por Categoría

+
+

Egresos por Categoría

{statistics.pieData.length > 0 ? ( - + { ) : ( -

No hay datos de egresos

+

No hay datos de egresos

)}
-
-

Historial Mensual

+
+

Historial Mensual

{statistics.lineData.length > 0 ? ( - + @@ -328,13 +342,13 @@ const FinanceTracker = () => { ) : ( -

No hay datos históricos

+

No hay datos históricos

)}
-
-

Estado de Tarjetas de Crédito

+
+

Estado de Tarjetas de Crédito

{creditCards.map(card => { const usage = (card.balance / card.limit) * 100; @@ -373,19 +387,19 @@ const FinanceTracker = () => { {activeTab === 'transacciones' && (
-
-

Agregar Transacción

-
+
+

Agregar Transacción

+
setNewTransaction({ ...newTransaction, date: e.target.value })} - className="border border-slate-300 rounded-lg px-4 py-2" + className="border border-slate-300 rounded-lg px-3 h-11 text-sm" /> setNewTransaction({ ...newTransaction, category: e.target.value })} - className="border border-slate-300 rounded-lg px-4 py-2" + className="border border-slate-300 rounded-lg px-3 h-11 text-sm" > {categories[newTransaction.type].map(cat => ( @@ -405,29 +419,31 @@ const FinanceTracker = () => { placeholder="Monto" value={newTransaction.amount} onChange={(e) => setNewTransaction({ ...newTransaction, amount: e.target.value })} - className="border border-slate-300 rounded-lg px-4 py-2" + className="border border-slate-300 rounded-lg px-3 h-11 text-sm" /> setNewTransaction({ ...newTransaction, description: e.target.value })} - className="border border-slate-300 rounded-lg px-4 py-2" + className="border border-slate-300 rounded-lg px-3 h-11 text-sm" />
-
-

Historial de Transacciones

-
- +
+

Historial de Transacciones

+ + {/* Tabla desktop */} +
+
@@ -467,49 +483,73 @@ const FinanceTracker = () => {
Fecha
+ + {/* Lista móvil */} +
+ {transactions + .slice() + .sort((a, b) => new Date(b.date) - new Date(a.date)) + .map(t => ( +
+
+
{t.category}
+
+ ${t.amount.toLocaleString()} +
+
+
{t.date} • {t.type}
+ {t.description &&
{t.description}
} +
+ +
+
+ ))} +
)} {activeTab === 'tarjetas' && (
-
-

Agregar Tarjeta de Crédito

-
+
+

Agregar Tarjeta de Crédito

+
setNewCard({ ...newCard, name: e.target.value })} - className="border border-slate-300 rounded-lg px-4 py-2" + className="border border-slate-300 rounded-lg px-3 h-11 text-sm" /> setNewCard({ ...newCard, limit: e.target.value })} - className="border border-slate-300 rounded-lg px-4 py-2" + className="border border-slate-300 rounded-lg px-3 h-11 text-sm" /> setNewCard({ ...newCard, balance: e.target.value })} - className="border border-slate-300 rounded-lg px-4 py-2" + className="border border-slate-300 rounded-lg px-3 h-11 text-sm" /> setNewCard({ ...newCard, cutoffDay: e.target.value })} - className="border border-slate-300 rounded-lg px-4 py-2" + className="border border-slate-300 rounded-lg px-3 h-11 text-sm" /> setNewCard({ ...newCard, paymentDay: e.target.value })} - className="border border-slate-300 rounded-lg px-4 py-2" + className="border border-slate-300 rounded-lg px-3 h-11 text-sm" />
-
-

Mis Tarjetas de Crédito

+
+

Mis Tarjetas de Crédito

{creditCards.map(card => { const usage = (card.balance / card.limit) * 100; @@ -573,7 +613,7 @@ const FinanceTracker = () => { style={{ width: `${usage}%` }} />
-
+
Corte: día {card.cutoffDay} Pago: día {card.paymentDay}
diff --git a/src/index.css b/src/index.css index bd6213e..b59456c 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,24 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +/* Oculta la scrollbar en contenedores horizontales (tabs en iOS) */ +.no-scrollbar::-webkit-scrollbar { + display: none; +} +.no-scrollbar { + -ms-overflow-style: none; /* IE y Edge */ + scrollbar-width: none; /* Firefox */ +} + +/* Mejoras táctiles en inputs para iOS/Android */ +input, select, button { + touch-action: manipulation; +} + +/* Evita zoom indeseado en iOS cuando hay inputs pequeños */ +@supports (-webkit-touch-callout: none) { + input, select, button { + font-size: 16px; + } +}