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."