perf: use cached database data for dashboard stats

- Created /api/dashboard/stats endpoint that queries local PostgreSQL only
- Updated companies API to use cached database instead of Autotask API
- Dashboard now loads instantly from cached data instead of waiting for external APIs
- Scheduled syncs keep data fresh throughout the day
This commit is contained in:
root 2026-02-02 21:45:18 -05:00
parent 6df319166d
commit db4d431d26
3 changed files with 92 additions and 49 deletions

View file

@ -1,18 +1,15 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAutotaskClient } from '@/lib/services/autotask-factory';
import { postgresClient } from '@/lib/services/postgres-client';
export async function GET(request: NextRequest) {
try {
const client = getAutotaskClient();
const allCompanies = await client.getAllCompanies();
const result = await postgresClient.query(
'SELECT * FROM companies WHERE is_active = true AND company_type = 1 ORDER BY company_name'
);
// Filter to only show customers (companyType = 1)
// companyType 1 = Customer, 2 = Lead, 3 = Prospect, 4 = Dead, 5 = Cancelation, 6 = Vendor, 7 = Partner
const companies = allCompanies.filter(company => company.companyType === 1);
return NextResponse.json({ companies });
return NextResponse.json({ companies: result.rows });
} catch (error) {
console.error('Error fetching companies:', error);
console.error('Error fetching companies from database:', error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to fetch companies' },
{ status: 500 }

View file

@ -0,0 +1,77 @@
import { NextRequest, NextResponse } from 'next/server';
import { postgresClient } from '@/lib/services/postgres-client';
export async function GET(request: NextRequest) {
try {
// Get companies count from database
const companiesResult = await postgresClient.query(
'SELECT COUNT(*) as total, COUNT(CASE WHEN is_active = true THEN 1 END) as active FROM companies WHERE company_type = 1'
);
// Get Auvik mapping stats from database
const auvikResult = await postgresClient.query(
'SELECT COUNT(*) as mapped FROM auvik_tenant_mappings WHERE autotask_company_id IS NOT NULL'
);
// Get total Auvik tenants count
const auvikTotalResult = await postgresClient.query(
'SELECT COUNT(*) as total FROM auvik_tenants'
);
// Get RMM mapping stats from database
const rmmResult = await postgresClient.query(
'SELECT COUNT(*) as mapped FROM rmm_site_mappings WHERE autotask_company_id IS NOT NULL'
);
// Get total RMM sites count
const rmmTotalResult = await postgresClient.query(
'SELECT COUNT(*) as total FROM rmm_sites'
);
// Get quotes count (if quotes table exists)
let quotesOpen = 0;
let quotesTotal = 0;
try {
const quotesResult = await postgresClient.query(
"SELECT COUNT(*) as open FROM quotes WHERE status = 'open'"
);
quotesOpen = parseInt(quotesResult.rows[0]?.open || '0');
const quotesTotalResult = await postgresClient.query(
'SELECT COUNT(*) as total FROM quotes'
);
quotesTotal = parseInt(quotesTotalResult.rows[0]?.total || '0');
} catch {
// Quotes table may not exist yet
}
const stats = {
companies: {
total: parseInt(companiesResult.rows[0]?.total || '0'),
active: parseInt(companiesResult.rows[0]?.active || '0'),
},
mappings: {
auvik: {
mapped: parseInt(auvikResult.rows[0]?.mapped || '0'),
unmapped: parseInt(auvikTotalResult.rows[0]?.total || '0') - parseInt(auvikResult.rows[0]?.mapped || '0'),
},
rmm: {
mapped: parseInt(rmmResult.rows[0]?.mapped || '0'),
unmapped: parseInt(rmmTotalResult.rows[0]?.total || '0') - parseInt(rmmResult.rows[0]?.mapped || '0'),
},
},
quotes: {
open: quotesOpen,
total: quotesTotal,
},
};
return NextResponse.json(stats);
} catch (error) {
console.error('Error fetching dashboard stats:', error);
return NextResponse.json(
{ error: 'Failed to fetch dashboard stats' },
{ status: 500 }
);
}
}

View file

@ -69,52 +69,21 @@ export default function DashboardPage() {
const fetchStats = async () => {
try {
// Fetch companies
const companiesRes = await fetch('/api/companies');
const companiesData = await companiesRes.json();
// Fetch Auvik mappings
const auvikRes = await fetch('/api/auvik/tenant-mappings?includeUnmapped=true');
const auvikData = await auvikRes.json();
// Fetch RMM mappings
const rmmRes = await fetch('/api/rmm/site-mappings?includeUnmapped=true');
const rmmData = await rmmRes.json();
// Fetch SalesBldr quotes
let quotesData = { results: [], total: 0 };
try {
const quotesRes = await fetch('/api/salesbldr/quotes?status=open&size=100');
if (quotesRes.ok) {
quotesData = await quotesRes.json();
}
} catch (quotesError) {
console.error('Error fetching quotes:', quotesError);
}
// Fetch cached stats from database (fast, no external API calls)
const statsRes = await fetch('/api/dashboard/stats');
const statsData = await statsRes.json();
setStats({
companies: {
total: companiesData.companies?.length || 0,
active: companiesData.companies?.filter((c: any) => c.isActive).length || 0
},
companies: statsData.companies || { total: 0, active: 0 },
configurationItems: {
total: 0, // Would need to fetch this
total: 0,
active: 0
},
mappings: {
auvik: {
mapped: auvikData.stats?.mapped || 0,
unmapped: auvikData.stats?.unmapped || 0
},
rmm: {
mapped: rmmData.stats?.mapped || 0,
unmapped: rmmData.stats?.unmapped || 0
}
mappings: statsData.mappings || {
auvik: { mapped: 0, unmapped: 0 },
rmm: { mapped: 0, unmapped: 0 }
},
quotes: {
open: quotesData.results?.length || 0,
total: quotesData.total || 0
}
quotes: statsData.quotes || { open: 0, total: 0 }
});
} catch (error) {
console.error('Error fetching dashboard stats:', error);