nxtgauge-backend-rust/apps/users/src/handlers/dashboard.rs

159 lines
5 KiB
Rust

use axum::{extract::State, routing::get, Json, Router};
use serde::Serialize;
use serde_json::{json, Value};
#[derive(Serialize)]
pub struct DashboardMetricsResponse {
kpis: Vec<Value>,
trend_series: Vec<Value>,
rev_series: Vec<Value>,
lead_rows: Vec<Value>,
}
pub fn router() -> Router<crate::AppState> {
Router::new().route("/metrics", get(get_metrics))
}
async fn get_metrics(State(state): State<crate::AppState>) -> Json<DashboardMetricsResponse> {
let total_users: i64 = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM users")
.fetch_one(&state.pool)
.await
.unwrap_or(0);
let active_companies: i64 = sqlx::query_scalar::<_, i64>(
"SELECT COUNT(*) FROM company_profiles WHERE status = 'APPROVED'",
)
.fetch_one(&state.pool)
.await
.unwrap_or(0);
let open_leads: i64 = sqlx::query_scalar::<_, i64>(
"SELECT COUNT(*) FROM leads WHERE status = 'PENDING_APPROVAL' OR status = 'APPROVED'",
)
.fetch_one(&state.pool)
.await
.unwrap_or(0);
let pending_approvals: i64 = sqlx::query_scalar::<_, i64>(
r#"
SELECT COUNT(*) FROM (
SELECT id FROM user_role_profiles WHERE status = 'PENDING_APPROVAL'
) sub
"#,
)
.fetch_one(&state.pool)
.await
.unwrap_or(0);
let total_revenue: i64 = sqlx::query_scalar::<_, i64>(
"SELECT COALESCE(SUM(amount_inr), 0) FROM payments WHERE status = 'SUCCESS'",
)
.fetch_one(&state.pool)
.await
.unwrap_or(0);
let kpis = vec![
json!({ "id": "users", "title": "Total Users", "value": format!("{}", total_users), "trend": "-", "trendUp": true }),
json!({ "id": "companies", "title": "Active Companies", "value": format!("{}", active_companies), "trend": "-", "trendUp": true }),
json!({ "id": "leads", "title": "Open Leads", "value": format!("{}", open_leads), "trend": "-", "trendUp": true }),
json!({ "id": "approvals", "title": "Pending Approvals", "value": format!("{}", pending_approvals), "trend": "-", "trendUp": false }),
json!({ "id": "revenue", "title": "Total Revenue", "value": format!("₹{:.0}", total_revenue as f64 / 100.0), "trend": "-", "trendUp": true }),
];
// User registrations per day (last 7 days)
#[derive(sqlx::FromRow)]
struct TrendRow {
day_name: Option<String>,
count: Option<i64>,
}
let trend_rows = sqlx::query_as::<_, TrendRow>(
r#"
SELECT TO_CHAR(DATE_TRUNC('day', created_at), 'Dy') AS day_name,
COUNT(*) AS count
FROM users
WHERE created_at >= NOW() - INTERVAL '7 days'
GROUP BY DATE_TRUNC('day', created_at), day_name
ORDER BY DATE_TRUNC('day', created_at)
"#,
)
.fetch_all(&state.pool)
.await
.unwrap_or_default();
let trend_series: Vec<Value> = trend_rows
.into_iter()
.map(|r| json!({ "name": r.day_name.unwrap_or_default(), "Users": r.count.unwrap_or(0) }))
.collect();
// Revenue per week (last 4 weeks)
#[derive(sqlx::FromRow)]
struct RevRow {
week_name: Option<String>,
total: Option<i64>,
}
let rev_rows = sqlx::query_as::<_, RevRow>(
r#"
SELECT TO_CHAR(DATE_TRUNC('week', created_at), '"Week" W') AS week_name,
COALESCE(SUM(amount_inr), 0) AS total
FROM payments
WHERE status = 'SUCCESS' AND created_at >= NOW() - INTERVAL '28 days'
GROUP BY DATE_TRUNC('week', created_at), week_name
ORDER BY DATE_TRUNC('week', created_at)
"#,
)
.fetch_all(&state.pool)
.await
.unwrap_or_default();
let rev_series: Vec<Value> = rev_rows
.into_iter()
.map(|r| json!({ "name": r.week_name.unwrap_or_default(), "Revenue": r.total.unwrap_or(0) }))
.collect();
// Recent open leads
#[derive(sqlx::FromRow)]
struct LeadRow {
id: uuid::Uuid,
title: String,
status: String,
created_at: chrono::DateTime<chrono::Utc>,
requester_name: Option<String>,
}
let recent_leads = sqlx::query_as::<_, LeadRow>(
r#"
SELECT r.id, r.title, r.status, r.created_at,
CONCAT(u.first_name, ' ', u.last_name) AS requester_name
FROM leads r
LEFT JOIN users u ON u.id = r.created_by_user_id
WHERE r.status IN ('PENDING_APPROVAL', 'APPROVED')
ORDER BY r.created_at DESC
LIMIT 5
"#,
)
.fetch_all(&state.pool)
.await
.unwrap_or_default();
let lead_rows: Vec<Value> = recent_leads
.into_iter()
.map(|r| {
json!({
"id": format!("L-{}", &r.id.to_string()[..8].to_uppercase()),
"client": r.requester_name.unwrap_or_else(|| "Unknown".to_string()),
"service": r.title,
"status": r.status,
"date": r.created_at.format("%b %d, %Y").to_string()
})
})
.collect();
Json(DashboardMetricsResponse {
kpis,
trend_series,
rev_series,
lead_rows,
})
}