feat: add chart annotations

Inline annotation markers on the dashboard chart with create/edit/delete UI.
Color-coded categories: deploy, campaign, incident, other.
This commit is contained in:
Usman Baig
2026-03-09 03:44:05 +01:00
parent 3002c4f58c
commit 4d99334bcf
5 changed files with 319 additions and 1 deletions

55
lib/api/annotations.ts Normal file
View File

@@ -0,0 +1,55 @@
import apiRequest from './client'
export type AnnotationCategory = 'deploy' | 'campaign' | 'incident' | 'other'
export interface Annotation {
id: string
site_id: string
date: string
text: string
category: AnnotationCategory
created_by: string
created_at: string
updated_at: string
}
export interface CreateAnnotationRequest {
date: string
text: string
category?: AnnotationCategory
}
export interface UpdateAnnotationRequest {
date: string
text: string
category: AnnotationCategory
}
export async function listAnnotations(siteId: string, startDate?: string, endDate?: string): Promise<Annotation[]> {
const params = new URLSearchParams()
if (startDate) params.set('start_date', startDate)
if (endDate) params.set('end_date', endDate)
const qs = params.toString()
const res = await apiRequest<{ annotations: Annotation[] }>(`/sites/${siteId}/annotations${qs ? `?${qs}` : ''}`)
return res?.annotations ?? []
}
export async function createAnnotation(siteId: string, data: CreateAnnotationRequest): Promise<Annotation> {
return apiRequest<Annotation>(`/sites/${siteId}/annotations`, {
method: 'POST',
body: JSON.stringify(data),
})
}
export async function updateAnnotation(siteId: string, annotationId: string, data: UpdateAnnotationRequest): Promise<Annotation> {
return apiRequest<Annotation>(`/sites/${siteId}/annotations/${annotationId}`, {
method: 'PUT',
body: JSON.stringify(data),
})
}
export async function deleteAnnotation(siteId: string, annotationId: string): Promise<void> {
await apiRequest(`/sites/${siteId}/annotations/${annotationId}`, {
method: 'DELETE',
})
}

View File

@@ -16,6 +16,8 @@ import {
getStats,
getDailyStats,
} from '@/lib/api/stats'
import { listAnnotations } from '@/lib/api/annotations'
import type { Annotation } from '@/lib/api/annotations'
import { getSite } from '@/lib/api/sites'
import type { Site } from '@/lib/api/sites'
import type {
@@ -48,6 +50,7 @@ const fetchers = {
realtime: (siteId: string) => getRealtime(siteId),
campaigns: (siteId: string, start: string, end: string, limit: number) =>
getCampaigns(siteId, start, end, limit),
annotations: (siteId: string, start: string, end: string) => listAnnotations(siteId, start, end),
}
// * Standard SWR config for dashboard data
@@ -247,5 +250,18 @@ export function useCampaigns(siteId: string, start: string, end: string, limit =
)
}
// * Hook for annotations data
export function useAnnotations(siteId: string, startDate: string, endDate: string) {
return useSWR<Annotation[]>(
siteId && startDate && endDate ? ['annotations', siteId, startDate, endDate] : null,
() => fetchers.annotations(siteId, startDate, endDate),
{
...dashboardSWRConfig,
refreshInterval: 60 * 1000,
dedupingInterval: 10 * 1000,
}
)
}
// * Re-export for convenience
export { fetchers }