Initial commit: SIBU 2.0 MISSION

This commit is contained in:
2026-02-21 09:53:31 -05:00
commit 0c7aa53c8b
400 changed files with 67708 additions and 0 deletions

View File

@ -0,0 +1,47 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const token = ref<string | null>(localStorage.getItem('auth_token'))
const role = ref<string | null>(localStorage.getItem('user_role'))
const userName = ref<string | null>(localStorage.getItem('user_name'))
const isAuthenticated = computed(() => !!token.value)
const isAdmin = computed(() => role.value?.toUpperCase() === 'ADMIN')
const isDriver = computed(() => role.value?.toUpperCase() === 'DRIVER')
const isPromoter = computed(() => role.value?.toUpperCase() === 'PROMOTER')
const isPassenger = computed(() => !role.value || role.value?.toUpperCase() === 'PASSENGER')
function login(newToken: string, newRole: string, newName: string) {
token.value = newToken
role.value = newRole
userName.value = newName
localStorage.setItem('auth_token', newToken)
localStorage.setItem('user_role', newRole)
localStorage.setItem('user_name', newName)
}
function logout() {
token.value = null
role.value = null
userName.value = null
localStorage.removeItem('auth_token')
localStorage.removeItem('user_role')
localStorage.removeItem('user_name')
window.location.href = '/'
}
return {
token,
role,
userName,
isAuthenticated,
isAdmin,
isDriver,
isPromoter,
isPassenger,
login,
logout
}
})

View File

@ -0,0 +1,57 @@
/** Pinia store for bus stop management */
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { BusStop } from '@/types'
import { busStopsService } from '@/services/busStopsService'
export const useBusStopStore = defineStore('busStop', () => {
const selectedStop = ref<BusStop | null>(null)
const busStops = ref<BusStop[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
async function loadBusStops(force = false) {
if (!force && busStops.value.length > 0) {
return
}
isLoading.value = true
error.value = null
try {
busStops.value = await busStopsService.getAllBusStops()
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to load bus stops'
console.error('Error loading bus stops:', e)
} finally {
isLoading.value = false
}
}
async function loadBusStopById(id: string) {
isLoading.value = true
error.value = null
try {
selectedStop.value = await busStopsService.getBusStopById(id)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to load bus stop'
console.error('Error loading bus stop:', e)
} finally {
isLoading.value = false
}
}
function setSelectedStop(stop: BusStop | null) {
selectedStop.value = stop
}
return {
selectedStop,
busStops,
isLoading,
error,
loadBusStops,
loadBusStopById,
setSelectedStop,
}
})

View File

@ -0,0 +1,65 @@
/** Pinia store for coupon management */
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { Coupon } from '@/types'
import { couponsService, type CouponFilters } from '@/services/couponsService'
export const useCouponStore = defineStore('coupon', () => {
const coupons = ref<Coupon[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
const myCoupons = ref<any[]>([])
const filters = ref<CouponFilters>({})
async function loadCoupons(newFilters?: CouponFilters) {
isLoading.value = true
error.value = null
if (newFilters) {
filters.value = newFilters
}
try {
coupons.value = await couponsService.getAllCoupons(filters.value)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to load coupons'
console.error('Error loading coupons:', e)
} finally {
isLoading.value = false
}
}
async function loadMyCoupons() {
try {
myCoupons.value = await couponsService.getMyCoupons()
} catch (e) {
console.error('Error loading my coupons:', e)
}
}
async function claimCoupon(id: string) {
try {
await couponsService.claimCoupon(id)
await loadMyCoupons()
return true
} catch (e: any) {
const msg = e.response?.data?.detail || e.message
throw new Error(msg)
}
}
function setFilters(newFilters: CouponFilters) {
filters.value = newFilters
loadCoupons()
}
return {
coupons,
myCoupons,
isLoading,
error,
filters,
loadCoupons,
loadMyCoupons,
claimCoupon,
setFilters,
}
})

View File

@ -0,0 +1,114 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { apiClient } from '@/services/apiClient'
export interface Favorite {
id: string
user_id: string
item_type: 'coupon' | 'business' | 'taxi' | 'route'
item_id: string
item_name?: string
item_image?: string
created_at: string
}
export const useFavoritesStore = defineStore('favorites', () => {
const favorites = ref<Favorite[]>([])
const isLoading = ref(false)
// Computed
const coupons = computed(() => favorites.value.filter(f => f.item_type === 'coupon'))
const businesses = computed(() => favorites.value.filter(f => f.item_type === 'business'))
const taxis = computed(() => favorites.value.filter(f => f.item_type === 'taxi'))
const routes = computed(() => favorites.value.filter(f => f.item_type === 'route'))
// Actions
async function loadFavorites() {
isLoading.value = true
try {
const response = await apiClient.get('/api/favorites')
favorites.value = response.data
} catch (error) {
console.error('Error loading favorites:', error)
} finally {
isLoading.value = false
}
}
async function addFavorite(
itemType: 'coupon' | 'business' | 'taxi' | 'route',
itemId: string,
itemName?: string,
itemImage?: string
) {
try {
const response = await apiClient.post('/api/favorites', {
item_type: itemType,
item_id: itemId,
item_name: itemName,
item_image: itemImage
})
favorites.value.unshift(response.data)
return true
} catch (error: any) {
if (error.response?.status === 400) {
// Already favorited
return false
}
console.error('Error adding favorite:', error)
throw error
}
}
async function removeFavorite(itemType: string, itemId: string) {
try {
await apiClient.delete(`/api/favorites/${itemType}/${itemId}`)
favorites.value = favorites.value.filter(
f => !(f.item_type === itemType && f.item_id === itemId)
)
return true
} catch (error) {
console.error('Error removing favorite:', error)
throw error
}
}
async function toggleFavorite(
itemType: 'coupon' | 'business' | 'taxi' | 'route',
itemId: string,
itemName?: string,
itemImage?: string
) {
const existing = favorites.value.find(
f => f.item_type === itemType && f.item_id === itemId
)
if (existing) {
await removeFavorite(itemType, itemId)
return false
} else {
await addFavorite(itemType, itemId, itemName, itemImage)
return true
}
}
function isFavorite(itemType: string, itemId: string): boolean {
return favorites.value.some(
f => f.item_type === itemType && f.item_id === itemId
)
}
return {
favorites,
isLoading,
coupons,
businesses,
taxis,
routes,
loadFavorites,
addFavorite,
removeFavorite,
toggleFavorite,
isFavorite
}
})

View File

@ -0,0 +1,39 @@
/** Pinia store for map state */
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { BusStop } from '@/types'
export const useMapStore = defineStore('map', () => {
const markers = ref<BusStop[]>([])
const selectedStop = ref<BusStop | null>(null)
const center = ref({ lat: 8.4177, lng: -82.4270 }) // Panama coordinates (David/Boquete area)
const zoom = ref(12)
function setMarkers(stops: BusStop[]) {
markers.value = stops
}
function setSelectedStop(stop: BusStop | null) {
selectedStop.value = stop
}
function setCenter(lat: number, lng: number) {
center.value = { lat, lng }
}
function setZoom(level: number) {
zoom.value = level
}
return {
markers,
selectedStop,
center,
zoom,
setMarkers,
setSelectedStop,
setCenter,
setZoom,
}
})

View File

@ -0,0 +1,78 @@
/** Pinia store for route management */
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Route, BusStop } from '@/types'
import { routesService } from '@/services/routesService'
export const useRouteStore = defineStore('route', () => {
const selectedRouteId = ref<string | null>(null)
const selectedRouteName = ref<string | null>(null)
const selectedRouteStops = ref<BusStop[]>([])
const allRoutes = ref<Route[]>([])
const isLoadingRoutes = ref(false)
const isLoadingStops = ref(false)
const error = ref<string | null>(null)
const hasSelectedRoute = computed(() => selectedRouteId.value !== null && selectedRouteName.value !== null)
async function loadRoutes(filters?: { originCity?: string, destinationCity?: string }, force = false) {
if (!force && !filters && allRoutes.value.length > 0) {
return
}
isLoadingRoutes.value = true
error.value = null
try {
allRoutes.value = await routesService.getAllRoutes(filters)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to load routes'
console.error('Error loading routes:', e)
} finally {
isLoadingRoutes.value = false
}
}
async function loadRouteStops(routeId: string) {
isLoadingStops.value = true
error.value = null
try {
selectedRouteStops.value = await routesService.getRouteStops(routeId)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to load route stops'
console.error('Error loading route stops:', e)
selectedRouteStops.value = []
} finally {
isLoadingStops.value = false
}
}
async function selectRoute(routeId: string, routeName: string) {
if (selectedRouteId.value === routeId) return
selectedRouteId.value = routeId
selectedRouteName.value = routeName
selectedRouteStops.value = [] // Clear old stops immediately
await loadRouteStops(routeId)
}
function clearSelection() {
selectedRouteId.value = null
selectedRouteName.value = null
selectedRouteStops.value = []
}
return {
selectedRouteId,
selectedRouteName,
selectedRouteStops,
allRoutes,
isLoadingRoutes,
isLoadingStops,
error,
hasSelectedRoute,
loadRoutes,
loadRouteStops,
selectRoute,
clearSelection,
}
})

View File

@ -0,0 +1,46 @@
/** Pinia store for schedule management */
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { BusSchedule } from '@/types'
import { schedulesService } from '@/services/schedulesService'
export const useScheduleStore = defineStore('schedule', () => {
const schedules = ref<BusSchedule[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
async function loadRouteSchedules(routeId: string) {
isLoading.value = true
error.value = null
try {
schedules.value = await schedulesService.getRouteSchedules(routeId)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to load schedules'
console.error('Error loading schedules:', e)
} finally {
isLoading.value = false
}
}
async function loadStopSchedules(stopId: string) {
isLoading.value = true
error.value = null
try {
schedules.value = await schedulesService.getStopSchedules(stopId)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to load schedules'
console.error('Error loading schedules:', e)
} finally {
isLoading.value = false
}
}
return {
schedules,
isLoading,
error,
loadRouteSchedules,
loadStopSchedules,
}
})

View File

@ -0,0 +1,36 @@
/** Pinia store for shuttle management (Intercity/Tourism) */
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { Shuttle } from '@/types'
import { shuttlesService, type ShuttleFilters } from '@/services/shuttlesService'
export const useShuttleStore = defineStore('shuttle', () => {
const shuttles = ref<Shuttle[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
const filters = ref<ShuttleFilters>({})
async function loadShuttles(newFilters?: ShuttleFilters) {
isLoading.value = true
error.value = null
if (newFilters) {
filters.value = newFilters
}
try {
shuttles.value = await shuttlesService.getAllShuttles(filters.value)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to load shuttles'
console.error('Error loading shuttles:', e)
} finally {
isLoading.value = false
}
}
return {
shuttles,
isLoading,
error,
filters,
loadShuttles,
}
})

View File

@ -0,0 +1,43 @@
/** Pinia store for taxi management */
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { Taxi } from '@/types'
import { taxisService, type TaxiFilters } from '@/services/taxisService'
export const useTaxiStore = defineStore('taxi', () => {
const taxis = ref<Taxi[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
const filters = ref<TaxiFilters>({})
async function loadTaxis(newFilters?: TaxiFilters) {
isLoading.value = true
error.value = null
if (newFilters) {
filters.value = newFilters
}
try {
taxis.value = await taxisService.getAllTaxis(filters.value)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to load taxis'
console.error('Error loading taxis:', e)
} finally {
isLoading.value = false
}
}
function setFilters(newFilters: TaxiFilters) {
filters.value = newFilters
loadTaxis()
}
return {
taxis,
isLoading,
error,
filters,
loadTaxis,
setFilters,
}
})

View File

@ -0,0 +1,60 @@
/** Pinia store for theme management */
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useThemeStore = defineStore('theme', () => {
// Check localStorage first, then system preference
const getInitialTheme = (): boolean => {
const stored = localStorage.getItem('darkMode')
if (stored !== null) {
return stored === 'true'
}
// Check system preference
return window.matchMedia('(prefers-color-scheme: dark)').matches
}
const isDarkMode = ref<boolean>(getInitialTheme())
// Apply theme to document
function applyTheme() {
if (isDarkMode.value) {
document.documentElement.classList.add('dark')
document.documentElement.classList.remove('light-theme')
} else {
document.documentElement.classList.remove('dark')
document.documentElement.classList.add('light-theme')
}
localStorage.setItem('darkMode', String(isDarkMode.value))
}
function toggleDarkMode() {
isDarkMode.value = !isDarkMode.value
applyTheme()
}
function setDarkMode(value: boolean) {
isDarkMode.value = value
applyTheme()
}
// Watch for system theme changes
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleSystemThemeChange = (e: MediaQueryListEvent) => {
if (!localStorage.getItem('darkMode')) {
isDarkMode.value = e.matches
applyTheme()
}
}
mediaQuery.addEventListener('change', handleSystemThemeChange)
// Apply theme on initialization
applyTheme()
return {
isDarkMode,
toggleDarkMode,
setDarkMode,
applyTheme,
}
})