feat(frontend): rewire dashboard to use AZ data endpoint
Main dashboard now uses /api/az/dashboard for dispensary, product, and brand counts instead of the legacy /api/dashboard/stats endpoint. This ensures the dashboard displays consistent data with the /az pages. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,7 @@ import {
|
|||||||
Package,
|
Package,
|
||||||
Target,
|
Target,
|
||||||
Image as ImageIcon,
|
Image as ImageIcon,
|
||||||
MousePointer,
|
Tag,
|
||||||
Shield,
|
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
TrendingDown,
|
TrendingDown,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
@@ -62,12 +61,42 @@ export function Dashboard() {
|
|||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
const [statsData, activityData] = await Promise.all([
|
// Fetch AZ dashboard data (primary data source)
|
||||||
api.getDashboardStats(),
|
const azDashboard = await api.getDutchieAZDashboard();
|
||||||
api.getDashboardActivity()
|
|
||||||
]);
|
// Map AZ dashboard data to the expected stats format
|
||||||
setStats(statsData);
|
setStats({
|
||||||
|
products: {
|
||||||
|
total: azDashboard.productCount,
|
||||||
|
in_stock: azDashboard.productCount, // All AZ products are in stock by default
|
||||||
|
with_images: 0 // Not tracked in AZ pipeline
|
||||||
|
},
|
||||||
|
stores: {
|
||||||
|
total: azDashboard.dispensaryCount,
|
||||||
|
active: azDashboard.dispensaryCount // All are active in AZ
|
||||||
|
},
|
||||||
|
brands: {
|
||||||
|
total: azDashboard.brandCount
|
||||||
|
},
|
||||||
|
campaigns: {
|
||||||
|
active: 0,
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
clicks: {
|
||||||
|
clicks_24h: azDashboard.snapshotCount24h // Use snapshots as activity metric
|
||||||
|
},
|
||||||
|
failedJobs: azDashboard.failedJobCount,
|
||||||
|
lastCrawlTime: azDashboard.lastCrawlTime
|
||||||
|
});
|
||||||
|
|
||||||
|
// Try to fetch activity data (may fail if not authenticated)
|
||||||
|
try {
|
||||||
|
const activityData = await api.getDashboardActivity();
|
||||||
setActivity(activityData);
|
setActivity(activityData);
|
||||||
|
} catch {
|
||||||
|
// Activity data requires auth, just skip it
|
||||||
|
setActivity(null);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load dashboard:', error);
|
console.error('Failed to load dashboard:', error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -196,9 +225,9 @@ export function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-sm font-medium text-gray-600">Active Stores</p>
|
<p className="text-sm font-medium text-gray-600">Total Dispensaries</p>
|
||||||
<p className="text-3xl font-semibold text-gray-900">{stats?.stores?.active || 0}</p>
|
<p className="text-3xl font-semibold text-gray-900">{stats?.stores?.total || 0}</p>
|
||||||
<p className="text-xs text-gray-500">{stats?.stores?.total || 0} total stores</p>
|
<p className="text-xs text-gray-500">{stats?.stores?.active || 0} active (crawlable)</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -242,33 +271,33 @@ export function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Clicks */}
|
{/* Snapshots (24h) */}
|
||||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div className="p-2 bg-cyan-50 rounded-lg">
|
<div className="p-2 bg-cyan-50 rounded-lg">
|
||||||
<MousePointer className="w-5 h-5 text-cyan-600" />
|
<Activity className="w-5 h-5 text-cyan-600" />
|
||||||
</div>
|
</div>
|
||||||
<Clock className="w-4 h-4 text-gray-400" />
|
<Clock className="w-4 h-4 text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-sm font-medium text-gray-600">Clicks (24h)</p>
|
<p className="text-sm font-medium text-gray-600">Snapshots (24h)</p>
|
||||||
<p className="text-3xl font-semibold text-gray-900">{stats?.clicks?.clicks_24h?.toLocaleString() || 0}</p>
|
<p className="text-3xl font-semibold text-gray-900">{stats?.clicks?.clicks_24h?.toLocaleString() || 0}</p>
|
||||||
<p className="text-xs text-gray-500">Last 24 hours</p>
|
<p className="text-xs text-gray-500">Product snapshots created</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Proxies */}
|
{/* Brands */}
|
||||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div className="p-2 bg-indigo-50 rounded-lg">
|
<div className="p-2 bg-indigo-50 rounded-lg">
|
||||||
<Shield className="w-5 h-5 text-indigo-600" />
|
<Tag className="w-5 h-5 text-indigo-600" />
|
||||||
</div>
|
</div>
|
||||||
<Activity className="w-4 h-4 text-gray-400" />
|
<Activity className="w-4 h-4 text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-sm font-medium text-gray-600">Active Proxies</p>
|
<p className="text-sm font-medium text-gray-600">Brands</p>
|
||||||
<p className="text-3xl font-semibold text-gray-900">{stats?.proxies?.active || 0}</p>
|
<p className="text-3xl font-semibold text-gray-900">{stats?.brands?.total || stats?.products?.unique_brands || 0}</p>
|
||||||
<p className="text-xs text-gray-500">{stats?.proxies?.total || 0} total proxies</p>
|
<p className="text-xs text-gray-500">Unique brands tracked</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user