learning_ai_common_plat/dashboards/tracker-web/src/lib/tracker-client.ts
saravanakumardb1 ac106ed917 feat(tracker): add product switcher — filter items by any product
- product-config.ts: add getRequestProductId(req) + KNOWN_PRODUCTS
- product-context.tsx: client-side product selection context
- product-switcher.tsx: native select dropdown component
- tracker-client.ts: inject x-product-id header on all API calls
- proxy route: forward x-product-id header to platform-service
- providers.tsx: wrap with ProductProvider
- dashboard/layout.tsx: render ProductSwitcher in top nav
2026-02-28 14:15:18 -08:00

162 lines
4.9 KiB
TypeScript

/**
* Client-side API helper for the Tracker Service.
* Uses @bytelyst/api-client shared package.
* All calls go through Next.js API routes to keep tokens server-side.
*/
import { createApiClient } from '@bytelyst/api-client';
export interface TrackerItem {
id: string;
productId: string;
type: 'bug' | 'feature' | 'task';
status: 'open' | 'in_progress' | 'done' | 'closed' | 'wont_fix';
priority: 'critical' | 'high' | 'medium' | 'low';
title: string;
description: string;
labels: string[];
assignee: string | null;
reportedBy: string;
source: 'internal' | 'user_submitted' | 'auto_detected';
visibility: 'internal' | 'public';
voteCount: number;
commentCount: number;
targetRelease: string | null;
createdAt: string;
updatedAt: string;
}
export interface Comment {
id: string;
itemId: string;
productId: string;
authorId: string;
authorEmail: string | null;
body: string;
createdAt: string;
updatedAt: string;
}
export interface TrackerStats {
productId: string;
total: number;
byType: Record<string, number>;
byStatus: Record<string, number>;
byPriority: Record<string, number>;
}
export interface ListItemsResponse {
items: TrackerItem[];
total: number;
limit: number;
offset: number;
}
const trackerApi = createApiClient({
baseUrl: '/api/tracker',
getToken: () => (typeof window !== 'undefined' ? localStorage.getItem('tracker_token') : null),
});
/** Wrap apiFetch to inject x-product-id header from localStorage. */
function apiFetch<T>(path: string, options?: RequestInit): Promise<T> {
const extra: Record<string, string> = {};
if (typeof window !== 'undefined') {
const pid = localStorage.getItem('tracker_selected_product');
if (pid) extra['x-product-id'] = pid;
}
return trackerApi.fetch<T>(path, {
...options,
headers: { ...extra, ...(options?.headers as Record<string, string>) },
});
}
export async function listItems(params?: Record<string, string>): Promise<ListItemsResponse> {
const qs = params ? `?${new URLSearchParams(params).toString()}` : '';
return apiFetch(`/items${qs}`);
}
export async function getItem(id: string): Promise<TrackerItem> {
return apiFetch(`/items/${id}`);
}
export async function createItem(data: Partial<TrackerItem>): Promise<TrackerItem> {
return apiFetch('/items', { method: 'POST', body: JSON.stringify(data) });
}
export async function updateItem(id: string, data: Partial<TrackerItem>): Promise<TrackerItem> {
return apiFetch(`/items/${id}`, { method: 'PUT', body: JSON.stringify(data) });
}
export async function updateItemStatus(id: string, status: string): Promise<TrackerItem> {
return apiFetch(`/items/${id}/status`, { method: 'PATCH', body: JSON.stringify({ status }) });
}
export async function deleteItem(id: string): Promise<void> {
await apiFetch(`/items/${id}`, { method: 'DELETE' });
}
export async function getStats(productId?: string): Promise<TrackerStats> {
const qs = productId ? `?productId=${productId}` : '';
return apiFetch(`/items/stats${qs}`);
}
export async function listComments(
itemId: string
): Promise<{ comments: Comment[]; count: number }> {
return apiFetch(`/items/${itemId}/comments`);
}
export async function addComment(itemId: string, body: string): Promise<Comment> {
return apiFetch(`/items/${itemId}/comments`, { method: 'POST', body: JSON.stringify({ body }) });
}
export async function toggleVote(itemId: string): Promise<{ voted: boolean; voteCount: number }> {
return apiFetch(`/items/${itemId}/vote`, { method: 'POST' });
}
// ── Public Roadmap API (no auth required) ────────────────────────────
const publicApi = createApiClient({ baseUrl: '/api/tracker' });
const publicFetch = publicApi.fetch;
export interface PublicRoadmapStats {
total: number;
byStatus: Record<string, number>;
byType: Record<string, number>;
totalVotes: number;
}
export async function getRoadmapItems(params?: Record<string, string>): Promise<ListItemsResponse> {
const qs = params ? `?${new URLSearchParams(params).toString()}` : '';
return publicFetch(`/public/roadmap${qs}`);
}
export async function getRoadmapStats(productId?: string): Promise<PublicRoadmapStats> {
const qs = productId ? `?productId=${productId}` : '';
return publicFetch(`/public/roadmap/stats${qs}`);
}
export async function getPublicItem(id: string): Promise<TrackerItem> {
return publicFetch(`/public/items/${id}`);
}
export async function submitPublicItem(data: {
type?: string;
title: string;
description?: string;
email: string;
name: string;
}): Promise<{ id: string; title: string; status: string }> {
return publicFetch('/public/submit', { method: 'POST', body: JSON.stringify(data) });
}
export async function publicVote(
itemId: string,
email: string
): Promise<{ voted: boolean; voteCount: number }> {
return publicFetch(`/public/items/${itemId}/vote`, {
method: 'POST',
body: JSON.stringify({ email }),
});
}