import { IPublicClientApplication } from '@azure/msal-browser'; import { getAccessToken } from './authService'; import type { AgentReview, FlaggedItem, ResolvedItem, ErrorItem, PDFPage, KnowledgeBaseListItem, KnowledgeBaseDetail, SourceDocument, ProcessingJob, SpecVersionListItem, SpecVersionDetail, DiffResult, } from '../types'; const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:8000'; // Types for API responses export interface CampaignResponse { id: string; name: string; workfront_id: string | null; client_lead: string | null; agency_lead: string | null; brand_guidelines: string | null; status: string; agency: string | null; created_by: string | null; created_at: string; updated_at: string; proofs: number; } export interface ProofVersionResponse { id: string; version: number; file_storage_key: string | null; thumbnail_url: string | null; agent_review: AgentReview | null; overall_status: string | null; workfront_id: string | null; is_identical_file: boolean | null; created_at: string; } export interface ProofResponse { id: string; proof_name: string; channel: string | null; sub_channel: string | null; proof_type: string | null; workfront_id: string | null; created_at: string; versions: ProofVersionResponse[]; } export interface AnalyticsResponse { total_reviews: number; passed: number; failed: number; errors: number; legal_review: number; } export interface AgencyAnalyticsItem { agency_id: string; agency_name: string; total_reviews: number; passed: number; failed: number; errors: number; legal_review: number; } export interface AgencyAnalyticsResponse { agencies: AgencyAnalyticsItem[]; } export interface FlaggedItemResponse { id: string; proof_version_id: string; agent_flagged: string; comments: string | null; submitter_name: string | null; submitter_agency: string | null; campaign_name: string | null; proof_name: string | null; version: number | null; created_at: string; } export interface ResolvedItemResponse { id: string; proof_version_id: string; agent: string; issue: string | null; resolution: string | null; submitter_name: string | null; submitter_agency: string | null; campaign_name: string | null; proof_name: string | null; version: number | null; created_at: string; } export interface ErrorItemResponse { id: string; proof_version_id: string; error_summary: string | null; submitter_name: string | null; submitter_agency: string | null; campaign_name: string | null; proof_name: string | null; version: number | null; created_at: string; } class ApiService { private msalInstance: IPublicClientApplication | null = null; setMsalInstance(instance: IPublicClientApplication) { this.msalInstance = instance; } private async getHeaders(): Promise { const headers: HeadersInit = { 'Content-Type': 'application/json', }; if (this.msalInstance) { const token = await getAccessToken(this.msalInstance); if (token) { headers['Authorization'] = `Bearer ${token}`; } } return headers; } private async fetch(endpoint: string, options: RequestInit = {}): Promise { const headers = await this.getHeaders(); const response = await fetch(`${API_URL}/api${endpoint}`, { ...options, headers: { ...headers, ...options.headers, }, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || `HTTP ${response.status}: ${response.statusText}`); } // Handle 204 No Content if (response.status === 204) { return undefined as T; } return response.json(); } // Current user endpoint async getMe(): Promise { return this.fetch('/me'); } // Campaign endpoints async getCampaigns(agencyId?: string): Promise { const params = agencyId ? `?agency_id=${agencyId}` : ''; return this.fetch(`/campaigns${params}`); } async getCampaign(id: string): Promise { return this.fetch(`/campaigns/${id}`); } async createCampaign(data: { name: string; workfront_id?: string; client_lead?: string; agency_lead?: string; brand_guidelines?: string; }): Promise { return this.fetch('/campaigns', { method: 'POST', body: JSON.stringify(data), }); } async updateCampaign(id: string, data: { name?: string; workfront_id?: string; client_lead?: string; agency_lead?: string; brand_guidelines?: string; status?: string; }): Promise { return this.fetch(`/campaigns/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } async deleteCampaign(id: string): Promise { return this.fetch(`/campaigns/${id}`, { method: 'DELETE', }); } // Proof endpoints async getProofs(campaignId: string): Promise { return this.fetch(`/campaigns/${campaignId}/proofs`); } async getProof(id: string): Promise { return this.fetch(`/proofs/${id}`); } async getFile(storageKey: string): Promise { const headers = await this.getHeaders(); const response = await fetch(`${API_URL}/api/files/${storageKey}`, { headers }); if (!response.ok) { throw new Error(`Failed to fetch file: HTTP ${response.status}`); } const blob = await response.blob(); const filename = storageKey.split('/').pop() || 'proof'; return new File([blob], filename, { type: blob.type }); } async getPdfPages(storageKey: string, maxPages: number = 10): Promise { const headers = await this.getHeaders(); const response = await fetch( `${API_URL}/api/files/${storageKey}/pages?max_pages=${maxPages}`, { headers } ); if (!response.ok) { throw new Error(`Failed to fetch PDF pages: HTTP ${response.status}`); } const data = await response.json(); return data.pages as PDFPage[]; } async deleteProof(id: string): Promise { return this.fetch(`/proofs/${id}`, { method: 'DELETE', }); } // Audit endpoints async flagProofVersion(proofId: string, version: number, data: { agent_flagged: string; comments?: string; }): Promise { return this.fetch(`/proofs/${proofId}/versions/${version}/flag`, { method: 'POST', body: JSON.stringify(data), }); } async resolveProofVersion(proofId: string, version: number, data: { agent: string; issue?: string; resolution?: string; }): Promise { return this.fetch(`/proofs/${proofId}/versions/${version}/resolve`, { method: 'POST', body: JSON.stringify(data), }); } async getFlaggedItems(agencyId?: string): Promise { const params = agencyId ? `?agency_id=${agencyId}` : ''; return this.fetch(`/audit/flagged${params}`); } async getResolvedItems(agencyId?: string): Promise { const params = agencyId ? `?agency_id=${agencyId}` : ''; return this.fetch(`/audit/resolved${params}`); } async getErrorItems(agencyId?: string): Promise { const params = agencyId ? `?agency_id=${agencyId}` : ''; return this.fetch(`/audit/errors${params}`); } // Analytics endpoints async getAnalytics(agencyId?: string): Promise { const params = agencyId ? `?agency_id=${agencyId}` : ''; return this.fetch(`/analytics${params}`); } async getAnalyticsByAgency(): Promise { return this.fetch('/analytics/by-agency'); } // Helper to convert API response to frontend format convertCampaignToFrontend(campaign: CampaignResponse) { return { name: campaign.name, workfrontId: campaign.workfront_id || '', clientLead: campaign.client_lead || '', agency: campaign.agency || '', agencyLead: campaign.agency_lead || '', proofs: campaign.proofs, status: campaign.status as 'In Progress' | 'Completed', lastModified: campaign.updated_at, brandGuidelines: campaign.brand_guidelines || 'Barclays', createdBy: campaign.created_by || null, _id: campaign.id, }; } convertProofToFrontend(proof: ProofResponse) { const latestVersion = proof.versions[0]; return { proofName: proof.proof_name, channel: proof.channel || '', subChannel: proof.sub_channel || '', proofType: proof.proof_type || '', status: latestVersion?.overall_status === 'Analysis Error' ? 'error' : 'completed' as 'completed' | 'analyzing' | 'error' | 'loading', overallStatus: latestVersion?.overall_status as any, versions: proof.versions.map(v => ({ version: v.version, timestamp: v.created_at.split('T')[0], workfrontId: v.workfront_id || '', proofPreviewUrl: v.thumbnail_url || '', feedback: v.agent_review || {} as AgentReview, overallStatus: v.overall_status as any, fileStorageKey: v.file_storage_key || '', isIdenticalFile: v.is_identical_file || false, })), _id: proof.id, fileStorageKey: latestVersion?.file_storage_key || '', }; } convertFlaggedItemToFrontend(item: FlaggedItemResponse): FlaggedItem { return { id: item.id, campaignName: item.campaign_name || '', proofName: item.proof_name || '', version: item.version || 1, submitter: item.submitter_name || '', submitAgency: item.submitter_agency || '', agentFlagged: item.agent_flagged, comments: item.comments || '', timestamp: item.created_at, }; } convertResolvedItemToFrontend(item: ResolvedItemResponse): ResolvedItem { return { id: item.id, campaignName: item.campaign_name || '', proofName: item.proof_name || '', version: item.version || 1, submitter: item.submitter_name || '', submitAgency: item.submitter_agency || '', agent: item.agent, issue: item.issue || '', resolution: item.resolution || '', timestamp: item.created_at, }; } convertErrorItemToFrontend(item: ErrorItemResponse): ErrorItem { return { id: item.id, campaignName: item.campaign_name || '', proofName: item.proof_name || '', version: item.version || 1, submitter: item.submitter_name || '', submitAgency: item.submitter_agency || '', errorSummary: item.error_summary || '', timestamp: item.created_at, }; } // Dropdown options endpoints async getDropdownOptions(): Promise { const response = await this.fetch('/dropdown-options'); // Debug logging console.log('[DEBUG API Response] Raw dropdown options:', JSON.stringify(response, null, 2)); console.log('[DEBUG API Response] Social.Meta proof types:', response.channels?.Social?.Meta); return response; } async addChannel(name: string): Promise { await this.fetch(`/dropdown-options/channels?name=${encodeURIComponent(name)}`, { method: 'POST', }); } async addSubChannel(channel: string, name: string): Promise { await this.fetch(`/dropdown-options/channels/${encodeURIComponent(channel)}/sub-channels?name=${encodeURIComponent(name)}`, { method: 'POST', }); } async addProofType(channel: string, subChannel: string, name: string): Promise { await this.fetch(`/dropdown-options/channels/${encodeURIComponent(channel)}/sub-channels/${encodeURIComponent(subChannel)}/proof-types?name=${encodeURIComponent(name)}`, { method: 'POST', }); } async deleteChannel(channel: string): Promise { await this.fetch(`/dropdown-options/channels/${encodeURIComponent(channel)}`, { method: 'DELETE', }); } async deleteSubChannel(channel: string, subChannel: string): Promise { await this.fetch(`/dropdown-options/channels/${encodeURIComponent(channel)}/sub-channels/${encodeURIComponent(subChannel)}`, { method: 'DELETE', }); } async deleteProofType(channel: string, subChannel: string, proofType: string): Promise { await this.fetch(`/dropdown-options/channels/${encodeURIComponent(channel)}/sub-channels/${encodeURIComponent(subChannel)}/proof-types/${encodeURIComponent(proofType)}`, { method: 'DELETE', }); } // User management endpoints (super_admin only) async getUsers(): Promise { return this.fetch('/users'); } async updateUser(userId: string, data: { role?: string; agency_id?: string | null }): Promise { return this.fetch(`/users/${userId}`, { method: 'PUT', body: JSON.stringify(data), }); } async getUserChangeHistory(userId: string): Promise { return this.fetch(`/users/${userId}/change-history`); } async createAgency(name: string): Promise { return this.fetch('/agencies', { method: 'POST', body: JSON.stringify({ name }), }); } // Agency endpoints async getAgencies(): Promise { return this.fetch('/agencies'); } // Support email endpoint async sendSupportEmail(data: { message: string; subject: string; user_name?: string; user_email?: string; }): Promise<{ success: boolean; message: string }> { return this.fetch('/support/email', { method: 'POST', body: JSON.stringify(data), }); } // Export endpoints async downloadCampaignsCsv(agencyId?: string): Promise { const headers = await this.getHeaders(); const params = agencyId ? `?agency_id=${agencyId}` : ''; const response = await fetch(`${API_URL}/api/export/campaigns-csv${params}`, { headers }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || `HTTP ${response.status}: ${response.statusText}`); } const blob = await response.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; const disposition = response.headers.get('Content-Disposition') || ''; const match = disposition.match(/filename="([^"]+)"/); a.download = match ? match[1] : 'campaigns_export.csv'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } // Knowledge Base endpoints async getKnowledgeBases(): Promise { return this.fetch('/knowledge-base'); } async getKnowledgeBase(kbId: string): Promise { return this.fetch(`/knowledge-base/${kbId}`); } async uploadSourceDocument(kbId: string, file: File): Promise { const headers = await this.getHeaders(); // Remove Content-Type so browser sets multipart boundary delete (headers as Record)['Content-Type']; const formData = new FormData(); formData.append('file', file); const response = await fetch(`${API_URL}/api/knowledge-base/${kbId}/documents`, { method: 'POST', headers, body: formData, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || `HTTP ${response.status}: ${response.statusText}`); } return response.json(); } async removeSourceDocument(kbId: string, docId: string): Promise { return this.fetch(`/knowledge-base/${kbId}/documents/${docId}`, { method: 'DELETE', }); } async triggerProcessing(kbId: string): Promise { return this.fetch(`/knowledge-base/${kbId}/process`, { method: 'POST', }); } async getProcessingJob(kbId: string, jobId: string): Promise { return this.fetch(`/knowledge-base/${kbId}/jobs/${jobId}`); } async getSpecVersions(kbId: string): Promise { return this.fetch(`/knowledge-base/${kbId}/versions`); } async getSpecVersion(kbId: string, versionId: string): Promise { return this.fetch(`/knowledge-base/${kbId}/versions/${versionId}`); } async getSpecDiff(kbId: string, versionIdA: string, versionIdB: string): Promise { return this.fetch(`/knowledge-base/${kbId}/versions/${versionIdA}/diff/${versionIdB}`); } async activateSpecVersion(kbId: string, versionId: string): Promise { return this.fetch(`/knowledge-base/${kbId}/versions/${versionId}/activate`, { method: 'POST', }); } } export interface DropdownOptionsResponse { campaigns: string[]; channels: Record>; brand_guidelines: string[]; } export interface AgencyResponse { id: string; name: string; } export interface CurrentUserResponse { id: string; email: string; name: string; role: string; agency_id: string | null; agency_name: string | null; } export interface UserManagementResponse { id: string; email: string; name: string; role: string; agency: string | null; agency_id: string | null; created_at: string; } export interface UserChangeLogEntry { id: string; change_type: string; field_changed: string | null; old_value: string | null; new_value: string | null; changed_by_name: string | null; created_at: string; } export const apiService = new ApiService(); export default apiService;