ProjectsParts Services
Media Coverage API - Quick Reference Card
For: Frontend developers who need a quick lookup
Media Coverage API - Quick Reference Card
For: Frontend developers who need a quick lookup
API Endpoints Cheat Sheet
# Base URL
http://localhost:3005
# Get coverage summary
GET /api/health/media/coverage?environment=prod
# Get image distribution
GET /api/health/media/distribution?environment=prod
# Get media gaps
GET /api/health/media/gaps?minQuality=60&limit=100&environment=prod
# Invalidate cache
POST /api/health/media/cache/invalidateReact Hooks Quick Setup
// 1. Install dependencies
npm install @tanstack/react-query axios
// 2. Create API hook (src/hooks/useMediaApi.ts)
import { useQuery } from '@tanstack/react-query';
export function useMediaCoverage(environment = 'prod') {
return useQuery({
queryKey: ['media-coverage', environment],
queryFn: async () => {
const res = await fetch(
`http://localhost:3005/api/health/media/coverage?environment=${environment}`
);
return res.json();
},
staleTime: 5 * 60 * 1000,
});
}
// 3. Use in component
function Dashboard() {
const { data, isLoading, error } = useMediaCoverage('prod');
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error!</div>;
return <div>Total Parts: {data.data.summary.totalParts}</div>;
}TypeScript Types Quick Copy
// Coverage response
interface MediaCoverageReport {
summary: {
totalParts: number;
environment: string;
};
images: {
view360: {
with360: { count: number; percentage: number };
without360: { count: number; percentage: number };
};
gallery: {
withGallery: { count: number; percentage: number };
averageGallerySize: number;
};
withAnyMedia: { count: number; percentage: number };
withNoMedia: { count: number; percentage: number };
};
qualityCorrelation: {
with360Score: number;
without360Score: number;
scoreDifference: number;
};
}
// Gaps response
interface MediaGapsReport {
summary: {
totalGaps: number;
minQualityThreshold: number;
};
parts: Array<{
_id: string;
partNumber: string;
title: string;
qualityScore: number;
media: {
galleryCount: number;
has360: boolean;
pdfCount: number;
};
missingMedia: string[];
}>;
gapBreakdown: {
missingGallery: number;
missing360: number;
missingPdfs: number;
missingAll: number;
};
}Common Component Patterns
Stat Card
<div className="p-6 bg-blue-50 border border-blue-200 rounded-lg">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium">Total Parts</span>
<PackageIcon className="w-5 h-5 text-blue-600" />
</div>
<div className="text-3xl font-bold">{totalParts.toLocaleString()}</div>
<p className="text-sm text-gray-600 mt-1">in production</p>
</div>Loading State
{isLoading && (
<div className="flex justify-center items-center p-8">
<div className="w-8 h-8 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin" />
</div>
)}Error State
{error && (
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
<div className="flex gap-2">
<AlertCircle className="w-5 h-5 text-red-600" />
<div>
<h3 className="font-semibold text-red-900">Error</h3>
<p className="text-sm text-red-700">{error.message}</p>
</div>
</div>
</div>
)}Chart Quick Setup
npm install rechartsimport { PieChart, Pie, Cell, Legend, Tooltip, ResponsiveContainer } from 'recharts';
const COLORS = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444'];
function DistributionChart({ data }) {
const chartData = data.categories.map(cat => ({
name: cat.description,
value: cat.count,
}));
return (
<ResponsiveContainer width="100%" height={400}>
<PieChart>
<Pie
data={chartData}
dataKey="value"
nameKey="name"
cx="50%"
cy="50%"
outerRadius={120}
label
>
{chartData.map((entry, index) => (
<Cell key={index} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
);
}CSV Export Quick Setup
npm install react-csvimport { CSVLink } from 'react-csv';
const csvData = parts.map(part => ({
'Part Number': part.partNumber,
'Title': part.title,
'Quality': part.qualityScore,
'Missing': part.missingMedia.join(', '),
}));
<CSVLink
data={csvData}
filename={`media-gaps-${new Date().toISOString()}.csv`}
className="px-4 py-2 bg-blue-600 text-white rounded-lg"
>
Export CSV
</CSVLink>Environment Variables
# .env.local
VITE_API_URL=http://localhost:3005
VITE_DEFAULT_ENV=prod// Use in code
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3005';Caching Strategy
| Level | TTL | When Used |
|---|---|---|
| Server | 5 min | All endpoints |
| React Query | 5 min | staleTime |
| Total | ~10 min | Worst case |
Bypass cache:
// Add nocache parameter
fetch('/api/health/media/coverage?nocache=1')
// Or invalidate React Query
queryClient.invalidateQueries(['media-coverage']);Error Handling
try {
const response = await fetch(API_URL);
const data = await response.json();
if (!data.success) {
throw new Error(data.error.message);
}
return data;
} catch (error) {
if (error.message.includes('503')) {
// Database connection failed
showNotification('Service temporarily unavailable');
} else {
// Other errors
showNotification('Failed to load data');
}
}Responsive Breakpoints
/* Mobile */
@media (max-width: 768px) {
/* Stack stat cards */
/* Hide less important table columns */
/* Single column charts */
}
/* Tablet */
@media (min-width: 769px) and (max-width: 1023px) {
/* 2-column stat cards */
/* Scrollable table */
}
/* Desktop */
@media (min-width: 1024px) {
/* 4-column stat cards */
/* 2-column charts */
/* Full table */
}Color Palette (Tailwind)
/* Primary */
bg-blue-50 border-blue-200 text-blue-600
/* Success */
bg-green-50 border-green-200 text-green-600
/* Warning */
bg-orange-50 border-orange-200 text-orange-600
/* Error */
bg-red-50 border-red-200 text-red-600
/* Neutral */
bg-gray-50 border-gray-200 text-gray-600Testing Quick Examples
// Mock API response
const mockCoverage = {
success: true,
data: {
summary: { totalParts: 3740 },
images: {
view360: {
with360: { count: 2517, percentage: 67.3 }
}
}
}
};
// Test hook
test('should fetch coverage', async () => {
const { result } = renderHook(() => useMediaCoverage());
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toBeDefined();
});
// Test component
test('should display total parts', async () => {
render(<Dashboard />);
expect(await screen.findByText(/3740/)).toBeInTheDocument();
});Common Gotchas
1. CORS in Development
// vite.config.ts
export default defineConfig({
server: {
proxy: { '/api': 'http://localhost:3005' }
}
});2. Number Formatting
// Use toLocaleString() for large numbers
totalParts.toLocaleString() // "3,740" not "3740"
// Use toFixed() for percentages
percentage.toFixed(1) // "67.3%" not "67.293847%"3. Chart Height
// Always set explicit height for Recharts
<ResponsiveContainer width="100%" height={400}>
<PieChart>...</PieChart>
</ResponsiveContainer>4. Table Performance
// Virtualize for 500+ rows
import { useVirtual } from 'react-virtual';
// Or paginate
const pageSize = 50;
const currentPage = parts.slice(page * pageSize, (page + 1) * pageSize);Documentation Links
- Full Integration Guide:
docs/FRONTEND_MEDIA_INTEGRATION.md - Design Reference:
docs/FRONTEND_DESIGN_REFERENCE.md - TypeScript SDK:
docs/media-coverage-client.ts - Example Dashboard:
docs/examples/media-dashboard-example.tsx - API Swagger: http://localhost:3005/docs
Support
Quick Questions: Check full docs first API Issues: Contact backend team UI/UX Questions: Check design reference Bug Reports: Create Linear ticket
Last Updated: 2025-11-17