class ApiService { constructor() { this.baseUrl = '/api'; this.token = localStorage.getItem('dashy_token'); this.subscribers = new Set(); this.connectSSE(); } connectSSE() { const url = `${this.baseUrl}/stream`; this.sse = new EventSource(url); this.sse.onmessage = (event) => { try { const data = JSON.parse(event.data); if (data.type === 'refresh') { this.notify(); } } catch (e) { // Fallback for non-JSON messages or just generic refresh this.notify(); } }; this.sse.onerror = (err) => { console.error('SSE connection error. EventSource will auto-reconnect.', err); }; } subscribe(fn) { this.subscribers.add(fn); return () => this.subscribers.delete(fn); } notify() { for (const fn of this.subscribers) fn(); } async request(endpoint, options = {}) { const headers = { 'Content-Type': 'application/json', ...options.headers, }; if (this.token) { headers['Authorization'] = `Bearer ${this.token}`; } const response = await fetch(`${this.baseUrl}${endpoint}`, { ...options, headers, }); if (!response.ok) { if (response.status === 401) { this.logout(); } let errorMsg = response.statusText; try { const data = await response.json(); if (data && data.detail) errorMsg = data.detail; } catch (e) {} throw new Error(`API Error: ${errorMsg}`); } return response.json(); } async login(id, password) { const response = await fetch(`${this.baseUrl}/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, password }), }); if (!response.ok) { throw new Error('Login failed'); } const data = await response.json(); this.token = data.access_token; localStorage.setItem('dashy_token', this.token); this.notify(); return data; } logout() { this.token = null; localStorage.removeItem('dashy_token'); this.notify(); } async getUsers() { return this.request('/users'); } async getWorkspace() { return this.request('/workspace'); } async updateWorkspace(updates) { const data = await this.request('/workspace', { method: 'PATCH', body: JSON.stringify(updates), }); this.notify(); return data; } async createUser(userData) { const data = await this.request('/users', { method: 'POST', body: JSON.stringify(userData), }); this.notify(); return data; } async updateUser(id, updates) { const data = await this.request(`/users/${id}`, { method: 'PATCH', body: JSON.stringify(updates), }); this.notify(); return data; } async deleteUser(id) { const data = await this.request(`/users/${id}`, { method: 'DELETE', }); this.notify(); return data; } async changePassword(id, oldPassword, newPassword) { const response = await fetch(`${this.baseUrl}/users/${id}/password`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), }, body: JSON.stringify({ old_password: oldPassword, new_password: newPassword }), }); if (!response.ok) { let detail = response.statusText; try { const j = await response.json(); if (j.detail) detail = j.detail; } catch {} throw new Error(detail); } return response.json(); } async getTasks() { return this.request('/tasks'); } async getDeletedTasks() { return this.request('/tasks/deleted'); } async getAudit() { return this.request('/audit'); } async createTask(taskData) { const data = await this.request('/tasks', { method: 'POST', body: JSON.stringify(taskData), }); this.notify(); return data; } async updateTask(id, updates) { const data = await this.request(`/tasks/${id}`, { method: 'PATCH', body: JSON.stringify(updates), }); this.notify(); return data; } async restoreTask(id) { const data = await this.request(`/tasks/${id}/restore`, { method: 'POST', }); this.notify(); return data; } async deleteTask(id) { const data = await this.request(`/tasks/${id}`, { method: 'DELETE', }); this.notify(); return data; } async getTaskNotes(taskId) { return this.request(`/tasks/${taskId}/notes`); } async createTaskNote(taskId, body) { const data = await this.request(`/tasks/${taskId}/notes`, { method: 'POST', body: JSON.stringify({ body }), }); this.notify(); return data; } async getSchedule(startDate, endDate) { return this.request(`/calendar/schedule?start_date=${startDate}&end_date=${endDate}`); } async triggerSync() { const data = await this.request('/calendar/sync', { method: 'POST' }); this.notify(); return data; } async addAudit(auditData) { const data = await this.request('/audit', { method: 'POST', body: JSON.stringify(auditData), }); this.notify(); return data; } } window.api = new ApiService();