diff --git a/app/dashboard/backend/router.py b/app/dashboard/backend/router.py index 72ef10b..e804a5d 100644 --- a/app/dashboard/backend/router.py +++ b/app/dashboard/backend/router.py @@ -1,5 +1,5 @@ from fastapi import APIRouter, HTTPException -from app.core.database import execute_query +from app.core.database import execute_query, execute_query_single from typing import Dict, Any, List import logging @@ -15,49 +15,95 @@ async def get_dashboard_stats(): try: logger.info("đ Fetching dashboard stats...") - # 1. Customer Counts + # 1. Customer Counts & Trends logger.info("Fetching customer count...") customer_res = execute_query_single("SELECT COUNT(*) as count FROM customers WHERE deleted_at IS NULL") customer_count = customer_res['count'] if customer_res else 0 - # 2. Contact Counts - logger.info("Fetching contact count...") - contact_res = execute_query_single("SELECT COUNT(*) as count FROM contacts") - contact_count = contact_res['count'] if contact_res else 0 - - # 3. Vendor Counts - logger.info("Fetching vendor count...") - vendor_res = execute_query_single("SELECT COUNT(*) as count FROM vendors") - vendor_count = vendor_res['count'] if vendor_res else 0 - - # 4. Recent Customers (Real "Activity") - logger.info("Fetching recent customers...") - recent_customers = execute_query_single(""" - SELECT id, name, created_at, 'customer' as type + # New customers this month + new_customers_res = execute_query_single(""" + SELECT COUNT(*) as count FROM customers - WHERE deleted_at IS NULL - ORDER BY created_at DESC - LIMIT 5 + WHERE deleted_at IS NULL + AND created_at >= DATE_TRUNC('month', CURRENT_DATE) """) + new_customers_this_month = new_customers_res['count'] if new_customers_res else 0 - # 5. Vendor Categories Distribution - logger.info("Fetching vendor distribution...") - vendor_categories = execute_query(""" - SELECT category, COUNT(*) as count - FROM vendors - GROUP BY category + # Previous month's new customers for trend calculation + prev_month_customers_res = execute_query_single(""" + SELECT COUNT(*) as count + FROM customers + WHERE deleted_at IS NULL + AND created_at >= DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month') + AND created_at < DATE_TRUNC('month', CURRENT_DATE) """) + prev_month_customers = prev_month_customers_res['count'] if prev_month_customers_res else 0 + + customer_growth_pct = 0 + if prev_month_customers > 0: + customer_growth_pct = round(((new_customers_this_month - prev_month_customers) / prev_month_customers) * 100, 1) + elif new_customers_this_month > 0: + customer_growth_pct = 100 + + # 2. Ticket Counts + logger.info("Fetching ticket stats...") + ticket_res = execute_query_single(""" + SELECT COUNT(*) as total_count, + COUNT(*) FILTER (WHERE status IN ('open', 'in_progress')) as open_count, + COUNT(*) FILTER (WHERE priority = 'high' AND status IN ('open', 'in_progress')) as urgent_count + FROM tticket_tickets + """) + ticket_count = ticket_res['open_count'] if ticket_res else 0 + urgent_ticket_count = ticket_res['urgent_count'] if ticket_res else 0 + + # 3. Hardware Count + logger.info("Fetching hardware count...") + hardware_res = execute_query_single("SELECT COUNT(*) as count FROM hardware") + hardware_count = hardware_res['count'] if hardware_res else 0 + + # 4. Revenue (from fixed price billing periods - current month) + logger.info("Fetching revenue stats...") + revenue_res = execute_query_single(""" + SELECT COALESCE(SUM(base_amount + COALESCE(overtime_amount, 0)), 0) as total + FROM fixed_price_billing_periods + WHERE period_start >= DATE_TRUNC('month', CURRENT_DATE) + AND period_start < DATE_TRUNC('month', CURRENT_DATE + INTERVAL '1 month') + """) + current_revenue = float(revenue_res['total']) if revenue_res and revenue_res['total'] else 0 + + # Previous month revenue for trend + prev_revenue_res = execute_query_single(""" + SELECT COALESCE(SUM(base_amount + COALESCE(overtime_amount, 0)), 0) as total + FROM fixed_price_billing_periods + WHERE period_start >= DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month') + AND period_start < DATE_TRUNC('month', CURRENT_DATE) + """) + prev_revenue = float(prev_revenue_res['total']) if prev_revenue_res and prev_revenue_res['total'] else 0 + + revenue_growth_pct = 0 + if prev_revenue > 0: + revenue_growth_pct = round(((current_revenue - prev_revenue) / prev_revenue) * 100, 1) + elif current_revenue > 0: + revenue_growth_pct = 100 logger.info("â Dashboard stats fetched successfully") return { - "counts": { - "customers": customer_count, - "contacts": contact_count, - "vendors": vendor_count + "customers": { + "total": customer_count, + "new_this_month": new_customers_this_month, + "growth_pct": customer_growth_pct }, - "recent_activity": recent_customers or [], - "vendor_distribution": vendor_categories or [], - "system_status": "online" + "tickets": { + "open_count": ticket_count, + "urgent_count": urgent_ticket_count + }, + "hardware": { + "total": hardware_count + }, + "revenue": { + "current_month": current_revenue, + "growth_pct": revenue_growth_pct + } } except Exception as e: logger.error(f"â Error fetching dashboard stats: {e}", exc_info=True) @@ -213,10 +259,41 @@ async def get_live_stats(): } +@router.get("/reminders/upcoming", response_model=List[Dict[str, Any]]) +async def get_upcoming_reminders(): + """ + Get upcoming reminders for the dashboard calendar widget + """ + try: + # Get active reminders with next check date within 7 days + reminders = execute_query(""" + SELECT + r.id, + r.sag_id, + r.title, + r.next_check_at as due_date, + r.priority, + s.titel as case_title + FROM sag_reminders r + LEFT JOIN sag_sager s ON r.sag_id = s.id + WHERE r.is_active = true + AND r.deleted_at IS NULL + AND r.next_check_at IS NOT NULL + AND r.next_check_at <= CURRENT_DATE + INTERVAL '7 days' + ORDER BY r.next_check_at ASC + LIMIT 10 + """) + + return reminders or [] + except Exception as e: + logger.error(f"â Error fetching upcoming reminders: {e}", exc_info=True) + return [] + + @router.get("/recent-activity", response_model=List[Dict[str, Any]]) async def get_recent_activity(): """ - Get recent activity across the system for the sidebar + Get recent activity across the system for the dashboard feed """ try: activities = [] @@ -227,37 +304,38 @@ async def get_recent_activity(): FROM customers WHERE deleted_at IS NULL ORDER BY created_at DESC - LIMIT 3 + LIMIT 5 """) - # Recent contacts - recent_contacts = execute_query(""" - SELECT id, first_name || ' ' || last_name as name, created_at, 'contact' as activity_type, 'bi-person' as icon, 'success' as color - FROM contacts + # Recent tickets + recent_tickets = execute_query(""" + SELECT id, subject as name, created_at, 'ticket' as activity_type, 'bi-ticket' as icon, 'warning' as color + FROM tticket_tickets ORDER BY created_at DESC - LIMIT 3 + LIMIT 5 """) - # Recent vendors - recent_vendors = execute_query(""" - SELECT id, name, created_at, 'vendor' as activity_type, 'bi-shop' as icon, 'info' as color - FROM vendors + # Recent cases (sager) + recent_cases = execute_query(""" + SELECT id, titel as name, created_at, 'case' as activity_type, 'bi-folder' as icon, 'info' as color + FROM sag_sager + WHERE deleted_at IS NULL ORDER BY created_at DESC - LIMIT 2 + LIMIT 5 """) # Combine all activities if recent_customers: activities.extend(recent_customers) - if recent_contacts: - activities.extend(recent_contacts) - if recent_vendors: - activities.extend(recent_vendors) + if recent_tickets: + activities.extend(recent_tickets) + if recent_cases: + activities.extend(recent_cases) # Sort by created_at and limit activities.sort(key=lambda x: x.get('created_at', ''), reverse=True) - return activities[:10] + return activities[:15] except Exception as e: logger.error(f"â Error fetching recent activity: {e}", exc_info=True) return [] diff --git a/app/dashboard/frontend/index.html b/app/dashboard/frontend/index.html index 2c3b0a9..3acb2f6 100644 --- a/app/dashboard/frontend/index.html +++ b/app/dashboard/frontend/index.html @@ -2,164 +2,676 @@ {% block title %}Dashboard - BMC Hub{% endblock %} +{% block extra_css %} + +{% endblock %} + {% block content %} -
Velkommen tilbage, Christian
-Oversigt over BMC Hub - alt pÄ ét sted
Aktive Kunder
- -Hardware
- -Support
- -OmsĂŠtning
- -| Kunde | -Handling | -Status | -Tid | -
|---|---|---|---|
| Advokatgruppen A/S | -Firewall konfiguration | -FuldfĂžrt | -10:23 | -
| Byg & Bo ApS | -Licens fornyelse | -Afventer | -I gÄr | -
| Cafe MÞller | -NetvÊrksnedbrud | -Kritisk | -I gÄr | -