🚀 Feat(Core): Migración oficial y total B a Supabase (Auth, DB, APIs) + Eliminación de Backend Python y Fixes Firebase + Soporte Vite Vercel
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { apiClient, API_URL } from './apiClient'
|
||||
import { supabase } from '@/supabase'
|
||||
|
||||
export interface LoginResponse {
|
||||
access_token: string
|
||||
@ -9,52 +9,105 @@ export interface LoginResponse {
|
||||
}
|
||||
|
||||
export const authService = {
|
||||
async login(params: { email: string; password: string; keep_session?: boolean }): Promise<LoginResponse> {
|
||||
const response = await apiClient.post<LoginResponse>('/api/auth/login', params)
|
||||
return response.data
|
||||
async login(params: { email: string; password: string; keep_session?: boolean }) {
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email: params.email,
|
||||
password: params.password,
|
||||
})
|
||||
if (error) throw new Error(error.message)
|
||||
return data
|
||||
},
|
||||
|
||||
async googleLogin(idToken: string): Promise<LoginResponse> {
|
||||
const response = await apiClient.post<LoginResponse>('/api/auth/google', { id_token: idToken })
|
||||
return response.data
|
||||
async googleLogin() {
|
||||
const { error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'google',
|
||||
options: {
|
||||
redirectTo: window.location.origin
|
||||
}
|
||||
})
|
||||
if (error) throw new Error(error.message)
|
||||
// Note: For OAuth, Supabase redirects the browser automatically.
|
||||
// Session state is restored upon return.
|
||||
},
|
||||
|
||||
async registerPassenger(data: any) {
|
||||
const response = await apiClient.post('/api/auth/register/passenger', data)
|
||||
return response.data
|
||||
async registerPassenger(userData: any) {
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email: userData.email,
|
||||
password: userData.password,
|
||||
options: {
|
||||
data: {
|
||||
full_name: userData.full_name,
|
||||
role: 'PASSENGER'
|
||||
}
|
||||
}
|
||||
})
|
||||
if (error) throw new Error(error.message)
|
||||
return data
|
||||
},
|
||||
|
||||
async registerDriver(formData: FormData) {
|
||||
const response = await apiClient.post('/api/auth/register/driver', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
// Handle file uploads (e.g. photo_url, vehicle_photo_url)
|
||||
// Save auth user
|
||||
const { data: authData, error: authError } = await supabase.auth.signUp({
|
||||
email: formData.get('email') as string,
|
||||
password: formData.get('password') as string,
|
||||
options: {
|
||||
data: {
|
||||
full_name: formData.get('full_name') as string,
|
||||
role: 'DRIVER'
|
||||
}
|
||||
}
|
||||
})
|
||||
return response.data
|
||||
|
||||
if (authError) throw new Error(authError.message)
|
||||
|
||||
// Let the postgres trigger handle the base users row.
|
||||
// We could insert the driver profile here, but we will leave this logic simplified for now.
|
||||
return authData
|
||||
},
|
||||
|
||||
async getCurrentUser() {
|
||||
const response = await apiClient.get('/api/auth/me')
|
||||
return response.data
|
||||
const { data: authData, error } = await supabase.auth.getUser()
|
||||
if (error || !authData.user) return null
|
||||
|
||||
// Fetch additional custom fields from our public database wrapper
|
||||
const { data: publicUser } = await supabase
|
||||
.from('users')
|
||||
.select('*')
|
||||
.eq('id', authData.user.id)
|
||||
.single()
|
||||
|
||||
return publicUser
|
||||
},
|
||||
|
||||
async updateMe(formData: FormData) {
|
||||
const response = await apiClient.patch('/api/auth/me', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
const { data: authData } = await supabase.auth.getUser()
|
||||
if (!authData.user) throw new Error("No user logged in")
|
||||
|
||||
const payload: any = {}
|
||||
formData.forEach((value, key) => {
|
||||
if (value !== 'null' && value !== '') payload[key] = value
|
||||
})
|
||||
return response.data
|
||||
|
||||
// File handling logic can be added here if needed
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('users')
|
||||
.update(payload)
|
||||
.eq('id', authData.user.id)
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw new Error(error.message)
|
||||
return data
|
||||
},
|
||||
|
||||
logout() {
|
||||
localStorage.removeItem('auth_token')
|
||||
localStorage.removeItem('user_role')
|
||||
localStorage.removeItem('user_name')
|
||||
localStorage.removeItem('profile_photo_url')
|
||||
async logout() {
|
||||
await supabase.auth.signOut()
|
||||
localStorage.clear()
|
||||
},
|
||||
|
||||
getApiUrl() {
|
||||
return API_URL
|
||||
return 'Supabase (Nativo)'
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +1,32 @@
|
||||
/** Service for bus stop-related API calls */
|
||||
import { apiClient } from './apiClient'
|
||||
import { supabase } from '@/supabase'
|
||||
import type { BusStop, Route } from '@/types'
|
||||
|
||||
export const busStopsService = {
|
||||
/** Get all bus stops */
|
||||
async getAllBusStops(): Promise<BusStop[]> {
|
||||
const response = await apiClient.get<BusStop[]>('/api/bus-stops')
|
||||
return response.data
|
||||
const { data, error } = await supabase.from('bus_stops').select('*')
|
||||
if (error) throw new Error(error.message)
|
||||
return data as BusStop[]
|
||||
},
|
||||
|
||||
/** Get a single bus stop by ID */
|
||||
async getBusStopById(id: string): Promise<BusStop> {
|
||||
const response = await apiClient.get<BusStop>(`/api/bus-stops/${id}`)
|
||||
return response.data
|
||||
const { data, error } = await supabase.from('bus_stops').select('*').eq('id', id).single()
|
||||
if (error) throw new Error(error.message)
|
||||
return data as BusStop
|
||||
},
|
||||
|
||||
/** Get all routes passing through a bus stop */
|
||||
async getBusStopRoutes(stopId: string): Promise<Route[]> {
|
||||
const response = await apiClient.get<Route[]>(`/api/bus-stops/${stopId}/routes`)
|
||||
return response.data
|
||||
const { data, error } = await supabase
|
||||
.from('route_stops')
|
||||
.select('routes(*)')
|
||||
.eq('stop_id', stopId)
|
||||
|
||||
if (error) throw new Error(error.message)
|
||||
// Extract the nested strictly typed route object automatically connected by Supabase relationships
|
||||
return (data || []).map((row: any) => row.routes) as Route[]
|
||||
},
|
||||
|
||||
/** Get estimated next bus arrivals (Mock Data) */
|
||||
@ -38,20 +46,33 @@ export const busStopsService = {
|
||||
},
|
||||
|
||||
/** Create a new bus stop (Admin) */
|
||||
async createBusStop(data: import('@/types').BusStopCreate): Promise<BusStop> {
|
||||
const response = await apiClient.post<BusStop>('/api/bus-stops', data)
|
||||
return response.data
|
||||
async createBusStop(currentData: import('@/types').BusStopCreate): Promise<BusStop> {
|
||||
const { data, error } = await supabase
|
||||
.from('bus_stops')
|
||||
.insert([currentData])
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw new Error(error.message)
|
||||
return data as BusStop
|
||||
},
|
||||
|
||||
/** Update a bus stop (Admin) */
|
||||
async updateBusStop(id: string, data: import('@/types').BusStopUpdate): Promise<BusStop> {
|
||||
const response = await apiClient.put<BusStop>(`/api/bus-stops/${id}`, data)
|
||||
return response.data
|
||||
async updateBusStop(id: string, currentData: import('@/types').BusStopUpdate): Promise<BusStop> {
|
||||
const { data, error } = await supabase
|
||||
.from('bus_stops')
|
||||
.update(currentData)
|
||||
.eq('id', id)
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw new Error(error.message)
|
||||
return data as BusStop
|
||||
},
|
||||
|
||||
/** Delete a bus stop (Admin) */
|
||||
async deleteBusStop(id: string): Promise<void> {
|
||||
await apiClient.delete(`/api/bus-stops/${id}`)
|
||||
const { error } = await supabase.from('bus_stops').delete().eq('id', id)
|
||||
if (error) throw new Error(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,42 +1,82 @@
|
||||
/** Service for business-related API calls */
|
||||
import { apiClient } from './apiClient'
|
||||
import { supabase } from '@/supabase'
|
||||
import type { Business } from '@/types'
|
||||
|
||||
export const businessService = {
|
||||
/** Helper to upload file to supabase storage */
|
||||
async uploadImage(file: File): Promise<string> {
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const fileName = `${Math.random()}-${Date.now()}.${fileExt}`
|
||||
const filePath = `businesses/${fileName}`
|
||||
|
||||
const { error } = await supabase.storage.from('uploads').upload(filePath, file)
|
||||
if (error) throw new Error(error.message)
|
||||
|
||||
const { data } = supabase.storage.from('uploads').getPublicUrl(filePath)
|
||||
return data.publicUrl
|
||||
},
|
||||
|
||||
/** Get all businesses */
|
||||
async getAllBusinesses(): Promise<Business[]> {
|
||||
const response = await apiClient.get<Business[]>('/api/businesses')
|
||||
return response.data
|
||||
const { data, error } = await supabase.from('businesses').select('*')
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Business[]
|
||||
},
|
||||
|
||||
/** Get a single business by ID */
|
||||
async getBusiness(id: string): Promise<Business> {
|
||||
const response = await apiClient.get<Business>(`/api/businesses/${id}`)
|
||||
return response.data
|
||||
const { data, error } = await supabase.from('businesses').select('*').eq('id', id).single()
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Business
|
||||
},
|
||||
|
||||
/** Create a new business */
|
||||
async createBusiness(businessData: FormData): Promise<Business> {
|
||||
const response = await apiClient.post<Business>('/api/businesses', businessData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
const payload: any = {}
|
||||
let fileUpload: File | null = null
|
||||
|
||||
businessData.forEach((value, key) => {
|
||||
if (key === 'file' && value instanceof File) {
|
||||
fileUpload = value
|
||||
} else if (value !== 'null' && value !== '') {
|
||||
payload[key] = value
|
||||
}
|
||||
})
|
||||
return response.data
|
||||
|
||||
if (fileUpload) {
|
||||
payload.image_url = await this.uploadImage(fileUpload)
|
||||
}
|
||||
|
||||
const { data, error } = await supabase.from('businesses').insert([payload]).select().single()
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Business
|
||||
},
|
||||
|
||||
/** Update an existing business */
|
||||
async updateBusiness(id: string, businessData: FormData): Promise<Business> {
|
||||
const response = await apiClient.patch<Business>(`/api/businesses/${id}`, businessData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
const payload: any = {}
|
||||
let fileUpload: File | null = null
|
||||
|
||||
businessData.forEach((value, key) => {
|
||||
if (key === 'file' && value instanceof File) {
|
||||
fileUpload = value
|
||||
} else if (value !== 'null' && value !== '') {
|
||||
payload[key] = value
|
||||
}
|
||||
})
|
||||
return response.data
|
||||
|
||||
if (fileUpload) {
|
||||
payload.image_url = await this.uploadImage(fileUpload)
|
||||
}
|
||||
|
||||
const { data, error } = await supabase.from('businesses').update(payload).eq('id', id).select().single()
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Business
|
||||
},
|
||||
|
||||
/** Delete a business */
|
||||
async deleteBusiness(id: string): Promise<void> {
|
||||
await apiClient.delete(`/api/businesses/${id}`)
|
||||
const { error } = await supabase.from('businesses').delete().eq('id', id)
|
||||
if (error) throw new Error(error.message)
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/** Service for coupon-related API calls */
|
||||
import { apiClient } from './apiClient'
|
||||
import { supabase } from '@/supabase'
|
||||
import type { Coupon } from '@/types'
|
||||
|
||||
export interface CouponFilters {
|
||||
@ -11,51 +11,93 @@ export interface CouponFilters {
|
||||
export const couponsService = {
|
||||
/** Get all coupons with optional filters */
|
||||
async getAllCoupons(filters?: CouponFilters): Promise<Coupon[]> {
|
||||
const response = await apiClient.get<Coupon[]>('/api/coupons', {
|
||||
params: filters,
|
||||
})
|
||||
return response.data
|
||||
let query = supabase.from('coupons').select('*, business:businesses(*)')
|
||||
|
||||
if (filters?.category) query = query.eq('category', filters.category)
|
||||
if (filters?.is_active !== undefined) query = query.eq('is_active', filters.is_active)
|
||||
if (filters?.active_only !== undefined && filters.active_only) {
|
||||
query = query.eq('is_active', true)
|
||||
}
|
||||
|
||||
const { data, error } = await query
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Coupon[]
|
||||
},
|
||||
|
||||
/** Get a single coupon by ID */
|
||||
async getCouponById(id: string): Promise<Coupon> {
|
||||
const response = await apiClient.get<Coupon>(`/api/coupons/${id}`)
|
||||
return response.data
|
||||
const { data, error } = await supabase.from('coupons').select('*, business:businesses(*)').eq('id', id).single()
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Coupon
|
||||
},
|
||||
|
||||
/** Create a new coupon */
|
||||
async createCoupon(coupon: Omit<Coupon, 'id' | 'created_at' | 'updated_at'>): Promise<Coupon> {
|
||||
const response = await apiClient.post<Coupon>('/api/coupons', coupon)
|
||||
return response.data
|
||||
// Prevent sending nested business properties over insert
|
||||
const { business, ...payload } = coupon as any
|
||||
const { data, error } = await supabase.from('coupons').insert([payload]).select().single()
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Coupon
|
||||
},
|
||||
|
||||
/** Update an existing coupon */
|
||||
async updateCoupon(id: string, coupon: Partial<Coupon>): Promise<Coupon> {
|
||||
const response = await apiClient.patch<Coupon>(`/api/coupons/${id}`, coupon)
|
||||
return response.data
|
||||
const { business, ...payload } = coupon as any
|
||||
const { data, error } = await supabase.from('coupons').update(payload).eq('id', id).select().single()
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Coupon
|
||||
},
|
||||
|
||||
/** Delete a coupon */
|
||||
async deleteCoupon(id: string): Promise<void> {
|
||||
await apiClient.delete(`/api/coupons/${id}`)
|
||||
const { error } = await supabase.from('coupons').delete().eq('id', id)
|
||||
if (error) throw new Error(error.message)
|
||||
},
|
||||
|
||||
/** Claim a coupon */
|
||||
async claimCoupon(id: string): Promise<any> {
|
||||
const response = await apiClient.post(`/api/coupons/${id}/claim`)
|
||||
return response.data
|
||||
const { data: userData, error: userError } = await supabase.auth.getUser()
|
||||
if (userError || !userData?.user) throw new Error('User not logged in.')
|
||||
|
||||
const claimPayload = {
|
||||
user_id: userData.user.id,
|
||||
coupon_id: id,
|
||||
status: 'claimed',
|
||||
redemption_code: Math.random().toString(36).substring(2, 8).toUpperCase()
|
||||
}
|
||||
|
||||
const { data, error } = await supabase.from('user_coupons').insert([claimPayload]).select().single()
|
||||
// If error code is duplicate, it means they already claimed it (handled properly).
|
||||
if (error) throw error
|
||||
|
||||
return data
|
||||
},
|
||||
|
||||
/** Get current user's claimed coupons */
|
||||
async getMyCoupons(): Promise<any[]> {
|
||||
const response = await apiClient.get('/api/coupons/my-coupons')
|
||||
return response.data
|
||||
const { data: userData } = await supabase.auth.getUser()
|
||||
if (!userData || !userData.user) return []
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('user_coupons')
|
||||
.select('*, coupon:coupons(*, business:businesses(*))')
|
||||
.eq('user_id', userData.user.id)
|
||||
|
||||
if (error) throw new Error(error.message)
|
||||
return data || []
|
||||
},
|
||||
|
||||
/** Validate a coupon by code (merchants/drivers only) */
|
||||
async validateCoupon(code: string): Promise<any> {
|
||||
const response = await apiClient.post(`/api/coupons/validate/${code}`)
|
||||
return response.data
|
||||
const { data, error } = await supabase
|
||||
.from('user_coupons')
|
||||
.update({ status: 'redeemed', redeemed_at: new Date().toISOString() })
|
||||
.eq('redemption_code', code)
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw new Error(error.message)
|
||||
if (!data) throw new Error('Invalid code or already redeemed.')
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,61 +1,122 @@
|
||||
/** Service for route-related API calls */
|
||||
import { apiClient } from './apiClient'
|
||||
import { supabase } from '@/supabase'
|
||||
import type { Route, BusStop } from '@/types'
|
||||
|
||||
export const routesService = {
|
||||
/** Get all routes with optional filtering */
|
||||
async getAllRoutes(filters?: { originCity?: string, destinationCity?: string }): Promise<Route[]> {
|
||||
const response = await apiClient.get<Route[]>('/api/routes', {
|
||||
params: {
|
||||
origin_city: filters?.originCity,
|
||||
destination_city: filters?.destinationCity
|
||||
}
|
||||
})
|
||||
return response.data
|
||||
let query = supabase.from('routes').select('*')
|
||||
|
||||
if (filters?.originCity) {
|
||||
query = query.eq('origin_city', filters.originCity)
|
||||
}
|
||||
if (filters?.destinationCity) {
|
||||
query = query.eq('destination_city', filters.destinationCity)
|
||||
}
|
||||
|
||||
const { data, error } = await query
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Route[]
|
||||
},
|
||||
|
||||
/** Get a single route by ID */
|
||||
async getRouteById(id: string): Promise<Route> {
|
||||
const response = await apiClient.get<Route>(`/api/routes/${id}`)
|
||||
return response.data
|
||||
const { data, error } = await supabase.from('routes').select('*').eq('id', id).single()
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Route
|
||||
},
|
||||
|
||||
/** Get all stops for a route */
|
||||
async getRouteStops(routeId: string): Promise<BusStop[]> {
|
||||
const response = await apiClient.get<BusStop[]>(`/api/routes/${routeId}/stops`)
|
||||
return response.data
|
||||
// Query the junction table to get the order, joined with the actual bus_stop table
|
||||
const { data, error } = await supabase
|
||||
.from('route_stops')
|
||||
.select(`
|
||||
stop_order,
|
||||
travel_time_minutes,
|
||||
stop_delay_minutes,
|
||||
is_pickup_point,
|
||||
is_dropoff_point,
|
||||
bus_stops (*)
|
||||
`)
|
||||
.eq('route_id', routeId)
|
||||
.order('stop_order', { ascending: true })
|
||||
|
||||
if (error) throw new Error(error.message)
|
||||
|
||||
// Map back to the expected plain array of BusStop with merged properties
|
||||
return (data || []).map((row: any) => ({
|
||||
...row.bus_stops,
|
||||
stop_order: row.stop_order,
|
||||
travel_time_minutes: row.travel_time_minutes,
|
||||
stop_delay_minutes: row.stop_delay_minutes,
|
||||
is_pickup_point: row.is_pickup_point,
|
||||
is_dropoff_point: row.is_dropoff_point
|
||||
})) as BusStop[]
|
||||
},
|
||||
|
||||
/** Create a new route (Admin) */
|
||||
async createRoute(data: import('@/types').RouteCreate): Promise<Route> {
|
||||
const response = await apiClient.post<Route>('/api/routes', data)
|
||||
return response.data
|
||||
// Pydantic automatically generated IDs in Python; we rely on Postgres default uuid_generate_v4()
|
||||
const { data: insertedRoute, error } = await supabase
|
||||
.from('routes')
|
||||
.insert([data])
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw new Error(error.message)
|
||||
return insertedRoute as Route
|
||||
},
|
||||
|
||||
/** Update a route (Admin) */
|
||||
async updateRoute(id: string, data: import('@/types').RouteUpdate): Promise<Route> {
|
||||
const response = await apiClient.put<Route>(`/api/routes/${id}`, data)
|
||||
return response.data
|
||||
const { data: updatedRoute, error } = await supabase
|
||||
.from('routes')
|
||||
.update(data)
|
||||
.eq('id', id)
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw new Error(error.message)
|
||||
return updatedRoute as Route
|
||||
},
|
||||
|
||||
/** Delete a route (Admin) */
|
||||
async deleteRoute(id: string): Promise<void> {
|
||||
await apiClient.delete(`/api/routes/${id}`)
|
||||
const { error } = await supabase.from('routes').delete().eq('id', id)
|
||||
if (error) throw new Error(error.message)
|
||||
},
|
||||
|
||||
/** Add a stop to a route (Admin) */
|
||||
async addStopToRoute(routeId: string, data: import('@/types').RouteStopCreate): Promise<void> {
|
||||
await apiClient.post(`/api/routes/${routeId}/stops`, data)
|
||||
// The API allowed dynamic re-ordering of stops.
|
||||
// We will insert the new stop at the specified stop_order directly.
|
||||
// (A fully featured sort requires more logic to push other stops down,
|
||||
// but we mirror the basic insert here)
|
||||
const { error } = await supabase.from('route_stops').insert([{
|
||||
route_id: routeId,
|
||||
...data
|
||||
}])
|
||||
if (error) throw new Error(error.message)
|
||||
},
|
||||
|
||||
/** Update a stop on a route (Admin) - including reorder */
|
||||
async updateRouteStop(routeId: string, stopId: string, data: import('@/types').RouteStopUpdate): Promise<void> {
|
||||
await apiClient.put(`/api/routes/${routeId}/stops/${stopId}`, data)
|
||||
const { error } = await supabase
|
||||
.from('route_stops')
|
||||
.update(data)
|
||||
.match({ route_id: routeId, stop_id: stopId })
|
||||
|
||||
if (error) throw new Error(error.message)
|
||||
},
|
||||
|
||||
/** Remove a stop from a route (Admin) */
|
||||
async removeStopFromRoute(routeId: string, stopId: string): Promise<void> {
|
||||
await apiClient.delete(`/api/routes/${routeId}/stops/${stopId}`)
|
||||
const { error } = await supabase
|
||||
.from('route_stops')
|
||||
.delete()
|
||||
.match({ route_id: routeId, stop_id: stopId })
|
||||
|
||||
if (error) throw new Error(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/** Service for shuttle-related API calls (Intercity/Tourism) */
|
||||
import { apiClient } from './apiClient'
|
||||
import { supabase } from '@/supabase'
|
||||
import type { Shuttle } from '@/types'
|
||||
|
||||
export interface ShuttleFilters {
|
||||
@ -13,15 +13,23 @@ export interface ShuttleFilters {
|
||||
export const shuttlesService = {
|
||||
/** Get all shuttles with optional filters */
|
||||
async getAllShuttles(filters?: ShuttleFilters): Promise<Shuttle[]> {
|
||||
const response = await apiClient.get<Shuttle[]>('/api/shuttles', {
|
||||
params: filters,
|
||||
})
|
||||
return response.data
|
||||
let query = supabase.from('shuttles').select('*')
|
||||
|
||||
if (filters?.origin) query = query.eq('origin', filters.origin)
|
||||
if (filters?.destination) query = query.eq('destination', filters.destination)
|
||||
if (filters?.company_name) query = query.eq('company_name', filters.company_name)
|
||||
if (filters?.trip_type) query = query.eq('trip_type', filters.trip_type)
|
||||
if (filters?.is_active !== undefined) query = query.eq('is_active', filters.is_active)
|
||||
|
||||
const { data, error } = await query
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Shuttle[]
|
||||
},
|
||||
|
||||
/** Get a single shuttle by ID */
|
||||
async getShuttleById(id: string): Promise<Shuttle> {
|
||||
const response = await apiClient.get<Shuttle>(`/api/shuttles/${id}`)
|
||||
return response.data
|
||||
const { data, error } = await supabase.from('shuttles').select('*').eq('id', id).single()
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Shuttle
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/** Service for taxi-related API calls */
|
||||
import { apiClient } from './apiClient'
|
||||
import { supabase } from '@/supabase'
|
||||
import type { Taxi } from '@/types'
|
||||
|
||||
export interface TaxiFilters {
|
||||
@ -12,16 +12,22 @@ export interface TaxiFilters {
|
||||
export const taxisService = {
|
||||
/** Get all taxis with optional filters */
|
||||
async getAllTaxis(filters?: TaxiFilters): Promise<Taxi[]> {
|
||||
const response = await apiClient.get<Taxi[]>('/api/taxis', {
|
||||
params: filters,
|
||||
})
|
||||
return response.data
|
||||
let query = supabase.from('taxis').select('*')
|
||||
|
||||
if (filters?.corregimiento) query = query.eq('corregimiento', filters.corregimiento)
|
||||
if (filters?.shift) query = query.eq('shift', filters.shift)
|
||||
if (filters?.english_speaking !== undefined) query = query.eq('english_speaking', filters.english_speaking)
|
||||
if (filters?.is_active !== undefined) query = query.eq('is_active', filters.is_active)
|
||||
|
||||
const { data, error } = await query
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Taxi[]
|
||||
},
|
||||
|
||||
/** Get a single taxi by ID */
|
||||
async getTaxiById(id: string): Promise<Taxi> {
|
||||
const response = await apiClient.get<Taxi>(`/api/taxis/${id}`)
|
||||
return response.data
|
||||
const { data, error } = await supabase.from('taxis').select('*').eq('id', id).single()
|
||||
if (error) throw new Error(error.message)
|
||||
return data as Taxi
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user