CROP
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/invalidate

React 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 recharts
import { 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-csv
import { 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

LevelTTLWhen Used
Server5 minAll endpoints
React Query5 minstaleTime
Total~10 minWorst 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-600

Testing 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);

  • 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

On this page