diff --git a/.gitignore b/.gitignore index cdcd084..0bdd302 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ node_modules/ dist/ __pycache__/ *.pyc +*.db +*.db.bak .env .env.* !.env.example diff --git a/backend/app/api/v1/endpoints/analytics.py b/backend/app/api/v1/endpoints/analytics.py index a670678..fede431 100644 --- a/backend/app/api/v1/endpoints/analytics.py +++ b/backend/app/api/v1/endpoints/analytics.py @@ -3,12 +3,13 @@ from typing import Optional from fastapi import APIRouter, Depends from pydantic import BaseModel +from sqlalchemy import case from sqlmodel import Session, func, select from app.auth import get_current_user from app.db import get_session from app.models.models import Category, Transaction -from app.api.v1.endpoints.transactions import get_cycle_range +from app.services.budget_projection import get_cycle_range router = APIRouter(prefix="/analytics", tags=["analytics"]) @@ -104,7 +105,7 @@ def monthly_trend( func.count(), func.coalesce( func.sum( - func.case( + case( (Transaction.currency == "CRC", Transaction.amount), else_=0, ) @@ -113,7 +114,7 @@ def monthly_trend( ), func.coalesce( func.sum( - func.case( + case( (Transaction.currency == "USD", Transaction.amount), else_=0, ) diff --git a/frontend/src/pages/Analytics.tsx b/frontend/src/pages/Analytics.tsx index 6ba94e6..c6180eb 100644 --- a/frontend/src/pages/Analytics.tsx +++ b/frontend/src/pages/Analytics.tsx @@ -46,9 +46,8 @@ interface DailySpending { } const COLORS = [ - 'var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', 'var(--chart-5)', - 'oklch(0.7 0.15 30)', 'oklch(0.65 0.2 300)', 'oklch(0.6 0.15 150)', - 'oklch(0.75 0.12 60)', 'oklch(0.55 0.18 250)', + '#B45309', '#16A34A', '#2563EB', '#DC2626', '#7C3AED', + '#D97706', '#0F766E', '#DB2777', '#EA580C', '#4F46E5', ]; function formatCRC(value: number) { @@ -132,7 +131,7 @@ export default function Analytics() { ) : (
- + {cat.category_name} - {cat.percentage}% + {cat.percentage}%
))} @@ -190,7 +189,7 @@ export default function Analytics() { No data ) : ( - + ) : ( - + {cat.category_name} - {cat.count} txns - + {cat.count} txns + {formatCRC(cat.total)} -
+
"$DUMP_FILE" + +if [[ ! -s "$DUMP_FILE" ]]; then + echo "ERROR: Dump file is empty. SSH or pg_dump may have failed." + exit 1 +fi + +DUMP_SIZE=$(du -h "$DUMP_FILE" | cut -f1) +echo " Done. Dump size: $DUMP_SIZE" + +# ── 2. Ensure local DB container is running ───────────────────── +echo "[2/5] Ensuring local dev database is running..." +cd "$PROJECT_ROOT" + +if ! docker inspect --format='{{.State.Running}}' "$LOCAL_CONTAINER" 2>/dev/null | grep -q "true"; then + echo " Starting db service..." + docker compose up -d db +fi + +for i in $(seq 1 30); do + if docker inspect --format='{{.State.Health.Status}}' "$LOCAL_CONTAINER" 2>/dev/null | grep -q "healthy"; then + break + fi + if [[ $i -eq 30 ]]; then + echo "ERROR: Local DB container did not become healthy within 30s." + exit 1 + fi + sleep 1 +done +echo " Local DB is running and healthy." + +# ── 3. Drop and recreate local database ───────────────────────── +echo "[3/5] Dropping and recreating local dev database..." +docker exec "$LOCAL_CONTAINER" bash -c \ + "PGPASSWORD='$LOCAL_PASS' dropdb -U $LOCAL_USER --if-exists $LOCAL_DB && \ + PGPASSWORD='$LOCAL_PASS' createdb -U $LOCAL_USER $LOCAL_DB" +echo " Done." + +# ── 4. Restore ────────────────────────────────────────────────── +echo "[4/5] Restoring dump into local dev database..." +docker exec -i "$LOCAL_CONTAINER" pg_restore \ + --no-owner \ + --no-acl \ + --dbname="$LOCAL_DB" \ + -U "$LOCAL_USER" < "$DUMP_FILE" + +# ── 5. Run pending migrations ─────────────────────────────────── +echo "[5/5] Running pending migrations..." +docker exec "$LOCAL_CONTAINER" psql -U "$LOCAL_USER" -d "$LOCAL_DB" -c \ + "ALTER TABLE transaction ADD COLUMN IF NOT EXISTS deferred_to_next_cycle BOOLEAN NOT NULL DEFAULT false;" \ + 2>/dev/null || true +echo " Done." + +echo "" +echo "=== Sync complete! ===" +echo "Local dev database now mirrors production."