- Dashboard redesign as command center with filters, quick actions, inline approve/reject - Reusable components: BookingRow, BookingFilters, ActionMenu, BookingPreviewModal, BookingEditModal - Calendar: drag & drop reschedule, eventClick preview modal, grid/list toggle - Mobile: segmented control bookings/calendar toggle, compact pills, responsive layout - Collapsible filters with active count badge - Smart menu positioning with Teleport - Calendar/list bidirectional data sync - Navigation: unified History page, removed AdminPending - Google Calendar OAuth integration - Dark mode contrast improvements, breadcrumb navigation - useLocalStorage composable for state persistence Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
402 lines
12 KiB
TypeScript
402 lines
12 KiB
TypeScript
import axios, { AxiosError } from 'axios'
|
|
import type {
|
|
LoginRequest,
|
|
TokenResponse,
|
|
UserRegister,
|
|
RegistrationResponse,
|
|
EmailVerificationRequest,
|
|
VerificationResponse,
|
|
Space,
|
|
User,
|
|
Settings,
|
|
Booking,
|
|
BookingCreate,
|
|
BookingUpdate,
|
|
BookingAdminCreate,
|
|
BookingTemplate,
|
|
BookingTemplateCreate,
|
|
Notification,
|
|
AuditLog,
|
|
Attachment,
|
|
RecurringBookingCreate,
|
|
RecurringBookingResult,
|
|
SpaceUsageReport,
|
|
TopUsersReport,
|
|
ApprovalRateReport
|
|
} from '@/types'
|
|
|
|
const api = axios.create({
|
|
baseURL: '/api',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
|
|
// Add token to requests
|
|
api.interceptors.request.use((config) => {
|
|
const token = localStorage.getItem('token')
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`
|
|
}
|
|
return config
|
|
})
|
|
|
|
// Auth API
|
|
export const authApi = {
|
|
login: async (credentials: LoginRequest): Promise<TokenResponse> => {
|
|
const response = await api.post<TokenResponse>('/auth/login', credentials)
|
|
return response.data
|
|
},
|
|
|
|
register: async (data: UserRegister): Promise<RegistrationResponse> => {
|
|
const response = await api.post<RegistrationResponse>('/auth/register', data)
|
|
return response.data
|
|
},
|
|
|
|
verifyEmail: async (data: EmailVerificationRequest): Promise<VerificationResponse> => {
|
|
const response = await api.post<VerificationResponse>('/auth/verify', data)
|
|
return response.data
|
|
},
|
|
|
|
resendVerification: async (email: string): Promise<{ message: string }> => {
|
|
const response = await api.post<{ message: string }>('/auth/resend-verification', null, {
|
|
params: { email }
|
|
})
|
|
return response.data
|
|
}
|
|
}
|
|
|
|
// Users API
|
|
export const usersApi = {
|
|
me: async (): Promise<User> => {
|
|
const response = await api.get<User>('/users/me')
|
|
return response.data
|
|
},
|
|
|
|
list: async (params?: { role?: string; organization?: string }): Promise<User[]> => {
|
|
const response = await api.get<User[]>('/admin/users', { params })
|
|
return response.data
|
|
},
|
|
|
|
create: async (
|
|
data: Omit<User, 'id' | 'is_active'> & { password: string }
|
|
): Promise<User> => {
|
|
const response = await api.post<User>('/admin/users', data)
|
|
return response.data
|
|
},
|
|
|
|
update: async (
|
|
id: number,
|
|
data: Partial<Omit<User, 'id' | 'is_active'>>
|
|
): Promise<User> => {
|
|
const response = await api.put<User>(`/admin/users/${id}`, data)
|
|
return response.data
|
|
},
|
|
|
|
updateStatus: async (id: number, is_active: boolean): Promise<User> => {
|
|
const response = await api.patch<User>(`/admin/users/${id}/status`, { is_active })
|
|
return response.data
|
|
},
|
|
|
|
resetPassword: async (id: number, new_password: string): Promise<User> => {
|
|
const response = await api.post<User>(`/admin/users/${id}/reset-password`, {
|
|
new_password
|
|
})
|
|
return response.data
|
|
},
|
|
|
|
getTimezones: async (): Promise<string[]> => {
|
|
const response = await api.get<string[]>('/users/timezones')
|
|
return response.data
|
|
},
|
|
|
|
updateTimezone: async (timezone: string): Promise<{ message: string; timezone: string }> => {
|
|
const response = await api.put<{ message: string; timezone: string }>('/users/me/timezone', {
|
|
timezone
|
|
})
|
|
return response.data
|
|
}
|
|
}
|
|
|
|
// Spaces API
|
|
export const spacesApi = {
|
|
list: async (): Promise<Space[]> => {
|
|
const response = await api.get<Space[]>('/spaces')
|
|
return response.data
|
|
},
|
|
|
|
create: async (data: Omit<Space, 'id' | 'is_active'>): Promise<Space> => {
|
|
const response = await api.post<Space>('/admin/spaces', data)
|
|
return response.data
|
|
},
|
|
|
|
update: async (id: number, data: Omit<Space, 'id' | 'is_active'>): Promise<Space> => {
|
|
const response = await api.put<Space>(`/admin/spaces/${id}`, data)
|
|
return response.data
|
|
},
|
|
|
|
updateStatus: async (id: number, is_active: boolean): Promise<Space> => {
|
|
const response = await api.patch<Space>(`/admin/spaces/${id}/status`, { is_active })
|
|
return response.data
|
|
}
|
|
}
|
|
|
|
// Settings API
|
|
export const settingsApi = {
|
|
get: async (): Promise<Settings> => {
|
|
const response = await api.get<Settings>('/admin/settings')
|
|
return response.data
|
|
},
|
|
|
|
update: async (data: Omit<Settings, 'id'>): Promise<Settings> => {
|
|
const response = await api.put<Settings>('/admin/settings', data)
|
|
return response.data
|
|
}
|
|
}
|
|
|
|
// Bookings API
|
|
export const bookingsApi = {
|
|
getForSpace: async (spaceId: number, start: string, end: string): Promise<Booking[]> => {
|
|
const response = await api.get<Booking[]>(`/spaces/${spaceId}/bookings`, {
|
|
params: { start, end }
|
|
})
|
|
return response.data
|
|
},
|
|
|
|
checkAvailability: async (params: {
|
|
space_id: number
|
|
start_datetime: string
|
|
end_datetime: string
|
|
}) => {
|
|
return api.get('/bookings/check-availability', { params })
|
|
},
|
|
|
|
create: async (data: BookingCreate): Promise<Booking> => {
|
|
const response = await api.post<Booking>('/bookings', data)
|
|
return response.data
|
|
},
|
|
|
|
getMy: async (status?: string): Promise<Booking[]> => {
|
|
const response = await api.get<Booking[]>('/bookings/my', {
|
|
params: status ? { status } : {}
|
|
})
|
|
return response.data
|
|
},
|
|
|
|
update: async (id: number, data: BookingUpdate): Promise<Booking> => {
|
|
const response = await api.put<Booking>(`/bookings/${id}`, data)
|
|
return response.data
|
|
},
|
|
|
|
createRecurring: async (data: RecurringBookingCreate): Promise<RecurringBookingResult> => {
|
|
const response = await api.post<RecurringBookingResult>('/bookings/recurring', data)
|
|
return response.data
|
|
},
|
|
|
|
getMyCalendar: async (start: string, end: string): Promise<Booking[]> => {
|
|
const response = await api.get<Booking[]>('/bookings/my/calendar', {
|
|
params: { start, end }
|
|
})
|
|
return response.data
|
|
}
|
|
}
|
|
|
|
// Admin Bookings API
|
|
export const adminBookingsApi = {
|
|
getAll: async (params?: {
|
|
status?: string
|
|
space_id?: number
|
|
user_id?: number
|
|
start?: string
|
|
limit?: number
|
|
}): Promise<Booking[]> => {
|
|
const response = await api.get<Booking[]>('/admin/bookings/all', { params })
|
|
return response.data
|
|
},
|
|
|
|
getPending: async (filters?: { space_id?: number; user_id?: number }): Promise<Booking[]> => {
|
|
const response = await api.get<Booking[]>('/admin/bookings/pending', { params: filters })
|
|
return response.data
|
|
},
|
|
|
|
approve: async (id: number): Promise<Booking> => {
|
|
const response = await api.put<Booking>(`/admin/bookings/${id}/approve`)
|
|
return response.data
|
|
},
|
|
|
|
reject: async (id: number, reason?: string): Promise<Booking> => {
|
|
const response = await api.put<Booking>(`/admin/bookings/${id}/reject`, { reason })
|
|
return response.data
|
|
},
|
|
|
|
update: async (id: number, data: BookingUpdate): Promise<Booking> => {
|
|
const response = await api.put<Booking>(`/admin/bookings/${id}`, data)
|
|
return response.data
|
|
},
|
|
|
|
reschedule: async (
|
|
id: number,
|
|
data: { start_datetime: string; end_datetime: string }
|
|
): Promise<Booking> => {
|
|
const response = await api.put<Booking>(`/admin/bookings/${id}/reschedule`, data)
|
|
return response.data
|
|
},
|
|
|
|
create: async (data: BookingAdminCreate): Promise<Booking> => {
|
|
const response = await api.post<Booking>('/admin/bookings', data)
|
|
return response.data
|
|
}
|
|
}
|
|
|
|
// Notifications API
|
|
export const notificationsApi = {
|
|
getAll: async (isRead?: boolean): Promise<Notification[]> => {
|
|
const params = isRead !== undefined ? { is_read: isRead } : {}
|
|
const response = await api.get<Notification[]>('/notifications', { params })
|
|
return response.data
|
|
},
|
|
|
|
markAsRead: async (id: number): Promise<Notification> => {
|
|
const response = await api.put<Notification>(`/notifications/${id}/read`)
|
|
return response.data
|
|
}
|
|
}
|
|
|
|
// Audit Log API
|
|
export const auditLogApi = {
|
|
getAll: async (params?: {
|
|
action?: string
|
|
start_date?: string
|
|
end_date?: string
|
|
page?: number
|
|
limit?: number
|
|
}): Promise<AuditLog[]> => {
|
|
const response = await api.get<AuditLog[]>('/admin/audit-log', { params })
|
|
return response.data
|
|
}
|
|
}
|
|
|
|
// Booking Templates API
|
|
export const bookingTemplatesApi = {
|
|
getAll: async (): Promise<BookingTemplate[]> => {
|
|
const response = await api.get<BookingTemplate[]>('/booking-templates')
|
|
return response.data
|
|
},
|
|
|
|
create: async (data: BookingTemplateCreate): Promise<BookingTemplate> => {
|
|
const response = await api.post<BookingTemplate>('/booking-templates', data)
|
|
return response.data
|
|
},
|
|
|
|
delete: async (id: number): Promise<void> => {
|
|
await api.delete(`/booking-templates/${id}`)
|
|
},
|
|
|
|
createBookingFromTemplate: async (
|
|
templateId: number,
|
|
startDatetime: string
|
|
): Promise<Booking> => {
|
|
const response = await api.post<Booking>(
|
|
`/booking-templates/from-template/${templateId}`,
|
|
null,
|
|
{ params: { start_datetime: startDatetime } }
|
|
)
|
|
return response.data
|
|
}
|
|
}
|
|
|
|
// Attachments API
|
|
export const attachmentsApi = {
|
|
upload: async (bookingId: number, file: File): Promise<Attachment> => {
|
|
const formData = new FormData()
|
|
formData.append('file', file)
|
|
const response = await api.post<Attachment>(`/bookings/${bookingId}/attachments`, formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' }
|
|
})
|
|
return response.data
|
|
},
|
|
|
|
list: async (bookingId: number): Promise<Attachment[]> => {
|
|
const response = await api.get<Attachment[]>(`/bookings/${bookingId}/attachments`)
|
|
return response.data
|
|
},
|
|
|
|
download: (attachmentId: number): string => {
|
|
return `/api/attachments/${attachmentId}/download`
|
|
},
|
|
|
|
delete: async (attachmentId: number): Promise<void> => {
|
|
await api.delete(`/attachments/${attachmentId}`)
|
|
}
|
|
}
|
|
|
|
// Reports API
|
|
export const reportsApi = {
|
|
getUsage: async (params?: {
|
|
start_date?: string
|
|
end_date?: string
|
|
space_id?: number
|
|
}): Promise<SpaceUsageReport> => {
|
|
const response = await api.get<SpaceUsageReport>('/admin/reports/usage', { params })
|
|
return response.data
|
|
},
|
|
|
|
getTopUsers: async (params?: {
|
|
start_date?: string
|
|
end_date?: string
|
|
limit?: number
|
|
}): Promise<TopUsersReport> => {
|
|
const response = await api.get<TopUsersReport>('/admin/reports/top-users', { params })
|
|
return response.data
|
|
},
|
|
|
|
getApprovalRate: async (params?: {
|
|
start_date?: string
|
|
end_date?: string
|
|
}): Promise<ApprovalRateReport> => {
|
|
const response = await api.get<ApprovalRateReport>('/admin/reports/approval-rate', {
|
|
params
|
|
})
|
|
return response.data
|
|
}
|
|
}
|
|
|
|
// Google Calendar API
|
|
export const googleCalendarApi = {
|
|
connect: async (): Promise<{ authorization_url: string; state: string }> => {
|
|
const response = await api.get<{ authorization_url: string; state: string }>(
|
|
'/integrations/google/connect'
|
|
)
|
|
return response.data
|
|
},
|
|
|
|
disconnect: async (): Promise<{ message: string }> => {
|
|
const response = await api.post<{ message: string }>('/integrations/google/disconnect')
|
|
return response.data
|
|
},
|
|
|
|
sync: async (): Promise<{ synced: number; created: number; updated: number; failed: number; total_bookings: number }> => {
|
|
const response = await api.post<{ synced: number; created: number; updated: number; failed: number; total_bookings: number }>(
|
|
'/integrations/google/sync'
|
|
)
|
|
return response.data
|
|
},
|
|
|
|
status: async (): Promise<{ connected: boolean; expires_at: string | null }> => {
|
|
const response = await api.get<{ connected: boolean; expires_at: string | null }>(
|
|
'/integrations/google/status'
|
|
)
|
|
return response.data
|
|
}
|
|
}
|
|
|
|
// Helper to handle API errors
|
|
export const handleApiError = (error: unknown): string => {
|
|
if (error instanceof AxiosError) {
|
|
return error.response?.data?.detail || error.message
|
|
}
|
|
return 'An unexpected error occurred'
|
|
}
|
|
|
|
export default api
|