Implements the ability to delete an expense, including its associated items and shares. Also refactors the expense update logic to correctly handle share updates and adds the corresponding API endpoint and mock DB function.
353 lines
10 KiB
TypeScript
353 lines
10 KiB
TypeScript
|
|
import {
|
|
Condo, Family, Payment, AppSettings, User, AuthResponse,
|
|
Ticket, TicketComment, ExtraordinaryExpense, Notice,
|
|
AlertDefinition, NoticeRead
|
|
} from '../types';
|
|
|
|
const API_URL = '/api';
|
|
|
|
async function request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
|
const token = localStorage.getItem('condo_token');
|
|
const headers: HeadersInit = {
|
|
'Content-Type': 'application/json',
|
|
...options.headers as any,
|
|
};
|
|
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
const response = await fetch(`${API_URL}${endpoint}`, {
|
|
...options,
|
|
headers,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
throw new Error(errorText || response.statusText);
|
|
}
|
|
|
|
// Handle empty responses
|
|
const text = await response.text();
|
|
return text ? JSON.parse(text) : undefined;
|
|
}
|
|
|
|
export const CondoService = {
|
|
// Auth & User
|
|
login: async (email: string, password: string): Promise<void> => {
|
|
const data = await request<AuthResponse>('/auth/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ email, password })
|
|
});
|
|
localStorage.setItem('condo_token', data.token);
|
|
localStorage.setItem('condo_user', JSON.stringify(data.user));
|
|
},
|
|
|
|
logout: () => {
|
|
localStorage.removeItem('condo_token');
|
|
localStorage.removeItem('condo_user');
|
|
window.location.href = '/#/login';
|
|
},
|
|
|
|
getCurrentUser: (): User | null => {
|
|
const u = localStorage.getItem('condo_user');
|
|
return u ? JSON.parse(u) : null;
|
|
},
|
|
|
|
updateProfile: async (data: any): Promise<void> => {
|
|
const res = await request<{success: boolean, user: User}>('/profile', {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
});
|
|
if (res.user) {
|
|
localStorage.setItem('condo_user', JSON.stringify(res.user));
|
|
}
|
|
},
|
|
|
|
// Settings
|
|
getSettings: async (): Promise<AppSettings> => {
|
|
return request<AppSettings>('/settings');
|
|
},
|
|
|
|
updateSettings: async (settings: AppSettings): Promise<void> => {
|
|
return request('/settings', {
|
|
method: 'PUT',
|
|
body: JSON.stringify(settings)
|
|
});
|
|
},
|
|
|
|
testSmtpConfig: async (config: any): Promise<void> => {
|
|
return request('/settings/smtp-test', {
|
|
method: 'POST',
|
|
body: JSON.stringify(config)
|
|
});
|
|
},
|
|
|
|
getAvailableYears: async (): Promise<number[]> => {
|
|
return request<number[]>('/years');
|
|
},
|
|
|
|
// Condos
|
|
getCondos: async (): Promise<Condo[]> => {
|
|
return request<Condo[]>('/condos');
|
|
},
|
|
|
|
getActiveCondoId: (): string | undefined => {
|
|
return localStorage.getItem('active_condo_id') || undefined;
|
|
},
|
|
|
|
getActiveCondo: async (): Promise<Condo | undefined> => {
|
|
const id = localStorage.getItem('active_condo_id');
|
|
const condos = await CondoService.getCondos();
|
|
if (id) {
|
|
return condos.find(c => c.id === id);
|
|
}
|
|
return condos.length > 0 ? condos[0] : undefined;
|
|
},
|
|
|
|
setActiveCondo: (id: string) => {
|
|
localStorage.setItem('active_condo_id', id);
|
|
window.dispatchEvent(new Event('condo-updated'));
|
|
window.location.reload();
|
|
},
|
|
|
|
saveCondo: async (condo: Condo): Promise<Condo> => {
|
|
if (condo.id) {
|
|
await request(`/condos/${condo.id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(condo)
|
|
});
|
|
return condo;
|
|
} else {
|
|
return request<Condo>('/condos', {
|
|
method: 'POST',
|
|
body: JSON.stringify(condo)
|
|
});
|
|
}
|
|
},
|
|
|
|
deleteCondo: async (id: string): Promise<void> => {
|
|
return request(`/condos/${id}`, { method: 'DELETE' });
|
|
},
|
|
|
|
// Families
|
|
getFamilies: async (condoId?: string): Promise<Family[]> => {
|
|
let url = '/families';
|
|
const activeId = condoId || CondoService.getActiveCondoId();
|
|
if (activeId) url += `?condoId=${activeId}`;
|
|
return request<Family[]>(url);
|
|
},
|
|
|
|
addFamily: async (family: any): Promise<Family> => {
|
|
const activeId = CondoService.getActiveCondoId();
|
|
return request<Family>('/families', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ ...family, condoId: activeId })
|
|
});
|
|
},
|
|
|
|
updateFamily: async (family: Family): Promise<void> => {
|
|
return request(`/families/${family.id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(family)
|
|
});
|
|
},
|
|
|
|
deleteFamily: async (id: string): Promise<void> => {
|
|
return request(`/families/${id}`, { method: 'DELETE' });
|
|
},
|
|
|
|
// Payments
|
|
seedPayments: () => { /* No-op for real backend */ },
|
|
|
|
getPaymentsByFamily: async (familyId: string): Promise<Payment[]> => {
|
|
return request<Payment[]>(`/payments?familyId=${familyId}`);
|
|
},
|
|
|
|
getCondoPayments: async (condoId: string): Promise<Payment[]> => {
|
|
return request<Payment[]>(`/payments?condoId=${condoId}`);
|
|
},
|
|
|
|
addPayment: async (payment: any): Promise<Payment> => {
|
|
return request<Payment>('/payments', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payment)
|
|
});
|
|
},
|
|
|
|
// Users
|
|
getUsers: async (condoId?: string): Promise<User[]> => {
|
|
let url = '/users';
|
|
if (condoId) url += `?condoId=${condoId}`;
|
|
return request<User[]>(url);
|
|
},
|
|
|
|
createUser: async (user: any): Promise<void> => {
|
|
return request('/users', {
|
|
method: 'POST',
|
|
body: JSON.stringify(user)
|
|
});
|
|
},
|
|
|
|
updateUser: async (id: string, user: any): Promise<void> => {
|
|
return request(`/users/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(user)
|
|
});
|
|
},
|
|
|
|
deleteUser: async (id: string): Promise<void> => {
|
|
return request(`/users/${id}`, { method: 'DELETE' });
|
|
},
|
|
|
|
// Alerts
|
|
getAlerts: async (condoId?: string): Promise<AlertDefinition[]> => {
|
|
let url = '/alerts';
|
|
if (condoId) url += `?condoId=${condoId}`;
|
|
return request<AlertDefinition[]>(url);
|
|
},
|
|
|
|
saveAlert: async (alert: AlertDefinition): Promise<AlertDefinition> => {
|
|
const activeId = CondoService.getActiveCondoId();
|
|
if (alert.id) {
|
|
await request(`/alerts/${alert.id}`, { method: 'PUT', body: JSON.stringify(alert) });
|
|
return alert;
|
|
} else {
|
|
return request('/alerts', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ ...alert, condoId: activeId })
|
|
});
|
|
}
|
|
},
|
|
|
|
deleteAlert: async (id: string): Promise<void> => {
|
|
return request(`/alerts/${id}`, { method: 'DELETE' });
|
|
},
|
|
|
|
// Notices
|
|
getNotices: async (condoId?: string): Promise<Notice[]> => {
|
|
let url = '/notices';
|
|
const activeId = condoId || CondoService.getActiveCondoId();
|
|
if (activeId) url += `?condoId=${activeId}`;
|
|
return request<Notice[]>(url);
|
|
},
|
|
|
|
getUnreadNoticesForUser: async (userId: string, condoId: string): Promise<Notice[]> => {
|
|
return request<Notice[]>(`/notices/unread?userId=${userId}&condoId=${condoId}`);
|
|
},
|
|
|
|
getNoticeReadStatus: async (noticeId: string): Promise<NoticeRead[]> => {
|
|
return request<NoticeRead[]>(`/notices/${noticeId}/read-status`);
|
|
},
|
|
|
|
markNoticeAsRead: async (noticeId: string, userId: string): Promise<void> => {
|
|
return request(`/notices/${noticeId}/read`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId })
|
|
});
|
|
},
|
|
|
|
saveNotice: async (notice: Notice): Promise<void> => {
|
|
if (notice.id) {
|
|
return request(`/notices/${notice.id}`, { method: 'PUT', body: JSON.stringify(notice) });
|
|
} else {
|
|
return request('/notices', { method: 'POST', body: JSON.stringify(notice) });
|
|
}
|
|
},
|
|
|
|
deleteNotice: async (id: string): Promise<void> => {
|
|
return request(`/notices/${id}`, { method: 'DELETE' });
|
|
},
|
|
|
|
// Tickets
|
|
getTickets: async (): Promise<Ticket[]> => {
|
|
const activeId = CondoService.getActiveCondoId();
|
|
return request<Ticket[]>(`/tickets?condoId=${activeId}`);
|
|
},
|
|
|
|
createTicket: async (data: any): Promise<void> => {
|
|
const activeId = CondoService.getActiveCondoId();
|
|
return request('/tickets', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ ...data, condoId: activeId })
|
|
});
|
|
},
|
|
|
|
updateTicket: async (id: string, data: any): Promise<void> => {
|
|
return request(`/tickets/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
});
|
|
},
|
|
|
|
deleteTicket: async (id: string): Promise<void> => {
|
|
return request(`/tickets/${id}`, { method: 'DELETE' });
|
|
},
|
|
|
|
getTicketComments: async (ticketId: string): Promise<TicketComment[]> => {
|
|
return request<TicketComment[]>(`/tickets/${ticketId}/comments`);
|
|
},
|
|
|
|
addTicketComment: async (ticketId: string, text: string): Promise<void> => {
|
|
return request(`/tickets/${ticketId}/comments`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ text })
|
|
});
|
|
},
|
|
|
|
getTicketAttachment: async (ticketId: string, attachmentId: string): Promise<any> => {
|
|
return request(`/tickets/${ticketId}/attachments/${attachmentId}`);
|
|
},
|
|
|
|
// Extraordinary Expenses
|
|
getExpenses: async (condoId?: string): Promise<ExtraordinaryExpense[]> => {
|
|
let url = '/expenses';
|
|
const activeId = condoId || CondoService.getActiveCondoId();
|
|
if (activeId) url += `?condoId=${activeId}`;
|
|
return request<ExtraordinaryExpense[]>(url);
|
|
},
|
|
|
|
getExpenseDetails: async (id: string): Promise<ExtraordinaryExpense> => {
|
|
return request<ExtraordinaryExpense>(`/expenses/${id}`);
|
|
},
|
|
|
|
createExpense: async (data: any): Promise<void> => {
|
|
const activeId = CondoService.getActiveCondoId();
|
|
if (!activeId) throw new Error("No active condo");
|
|
return request('/expenses', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ ...data, condoId: activeId })
|
|
});
|
|
},
|
|
|
|
updateExpense: async (id: string, data: any): Promise<void> => {
|
|
return request(`/expenses/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
});
|
|
},
|
|
|
|
deleteExpense: async (id: string): Promise<void> => {
|
|
return request(`/expenses/${id}`, {
|
|
method: 'DELETE'
|
|
});
|
|
},
|
|
|
|
getExpenseAttachment: async (expenseId: string, attachmentId: string): Promise<any> => {
|
|
return request(`/expenses/${expenseId}/attachments/${attachmentId}`);
|
|
},
|
|
|
|
getMyExpenses: async (): Promise<any[]> => {
|
|
const activeId = CondoService.getActiveCondoId();
|
|
return request(`/my-expenses?condoId=${activeId}`);
|
|
},
|
|
|
|
payExpense: async (expenseId: string, amount: number): Promise<void> => {
|
|
return request(`/expenses/${expenseId}/pay`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ amount, notes: 'PayPal Payment' })
|
|
});
|
|
}
|
|
};
|