375 lines
11 KiB
TypeScript
375 lines
11 KiB
TypeScript
import { Component, OnInit, inject } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FormsModule } from '@angular/forms';
|
|
import {
|
|
IonHeader,
|
|
IonToolbar,
|
|
IonTitle,
|
|
IonContent,
|
|
IonCard,
|
|
IonCardHeader,
|
|
IonCardSubtitle,
|
|
IonCardTitle,
|
|
IonCardContent,
|
|
IonButton,
|
|
IonIcon,
|
|
IonBadge,
|
|
IonItem,
|
|
IonLabel,
|
|
IonSelect,
|
|
IonSelectOption,
|
|
IonInput,
|
|
IonSpinner,
|
|
IonList,
|
|
IonProgressBar,
|
|
} from '@ionic/angular/standalone';
|
|
import { WazeNavigationService, WazeStop } from '../../services/waze-navigation.service';
|
|
import { TripService, BookingPayload } from '../../services/trip.service';
|
|
|
|
@Component({
|
|
selector: 'app-trip-pooling',
|
|
standalone: true,
|
|
imports: [
|
|
CommonModule,
|
|
FormsModule,
|
|
IonHeader,
|
|
IonToolbar,
|
|
IonTitle,
|
|
IonContent,
|
|
IonCard,
|
|
IonCardHeader,
|
|
IonCardSubtitle,
|
|
IonCardTitle,
|
|
IonCardContent,
|
|
IonButton,
|
|
IonIcon,
|
|
IonBadge,
|
|
IonItem,
|
|
IonLabel,
|
|
IonSelect,
|
|
IonSelectOption,
|
|
IonInput,
|
|
IonSpinner,
|
|
IonList,
|
|
IonProgressBar,
|
|
],
|
|
template: `
|
|
<ion-header>
|
|
<ion-toolbar color="primary">
|
|
<ion-title>Pacifictrans Go</ion-title>
|
|
</ion-toolbar>
|
|
</ion-header>
|
|
|
|
<ion-content class="ion-padding">
|
|
<!-- ALERTA DE PENALIDAD ACTIVA / BLOQUEO -->
|
|
<ion-card *ngIf="userPenaltyBalance > 0" color="danger">
|
|
<ion-card-header>
|
|
<ion-card-title>Cuenta Bloqueada por Penalidad</ion-card-title>
|
|
<ion-card-subtitle>Sanción por cancelación tardía</ion-card-subtitle>
|
|
</ion-card-header>
|
|
<ion-card-content>
|
|
<p>
|
|
Tienes un saldo pendiente de <strong>\${{ userPenaltyBalance | number }} COP</strong>.
|
|
Debes saldar esta penalidad para poder solicitar nuevos viajes o servicios de mensajería.
|
|
</p>
|
|
<ion-button
|
|
expand="block"
|
|
color="light"
|
|
class="ion-margin-top"
|
|
*ngIf="boldPenaltyCheckoutUrl"
|
|
(click)="openUrl(boldPenaltyCheckoutUrl)"
|
|
>
|
|
Pagar Penalidad con Bold (PSE / Nequi / QR Bre-B)
|
|
</ion-button>
|
|
</ion-card-content>
|
|
</ion-card>
|
|
|
|
<!-- FORMULARIO DE RESERVA (Si no tiene penalidad) -->
|
|
<ion-card *ngIf="userPenaltyBalance === 0 && !activeTrip">
|
|
<ion-card-header>
|
|
<ion-card-title>Solicitud de Puesto (Pool de 4)</ion-card-title>
|
|
<ion-card-subtitle>Agrupación inteligente por cercanía</ion-card-subtitle>
|
|
</ion-card-header>
|
|
<ion-card-content>
|
|
<ion-list>
|
|
<ion-item>
|
|
<ion-label position="stacked">Ruta</ion-label>
|
|
<ion-select [(ngModel)]="booking.sentido">
|
|
<ion-select-option value="CALI_A_BUENAVENTURA">Cali ➔ Buenaventura</ion-select-option>
|
|
<ion-select-option value="BUENAVENTURA_A_CALI">Buenaventura ➔ Cali</ion-select-option>
|
|
</ion-select>
|
|
</ion-item>
|
|
|
|
<ion-item>
|
|
<ion-label position="stacked">Dirección de recogida en Cali</ion-label>
|
|
<ion-input
|
|
placeholder="Ej. Calle 5 # 38-25, San Fernando"
|
|
[(ngModel)]="booking.direccionTexto"
|
|
></ion-input>
|
|
</ion-item>
|
|
|
|
<ion-item>
|
|
<ion-label position="stacked">Parada fija en Buenaventura</ion-label>
|
|
<ion-select [(ngModel)]="selectedBuenaventuraStop">
|
|
<ion-select-option *ngFor="let stop of bvStops" [value]="stop">
|
|
{{ stop.nombre }}
|
|
</ion-select-option>
|
|
</ion-select>
|
|
</ion-item>
|
|
|
|
<ion-item>
|
|
<ion-label position="stacked">Método de Pago Preferido</ion-label>
|
|
<ion-select [(ngModel)]="booking.metodoPago">
|
|
<ion-select-option value="BOLD">Bold (PSE, Tarjetas, Bre-B)</ion-select-option>
|
|
<ion-select-option value="NEQUI">Nequi Digital</ion-select-option>
|
|
<ion-select-option value="EFECTIVO">Efectivo al abordar</ion-select-option>
|
|
</ion-select>
|
|
</ion-item>
|
|
</ion-list>
|
|
|
|
<ion-button
|
|
expand="block"
|
|
class="ion-margin-top"
|
|
[disabled]="loading"
|
|
(click)="submitBooking()"
|
|
>
|
|
<ion-spinner *ngIf="loading"></ion-spinner>
|
|
<span *ngIf="!loading">Solicitar Puesto ($35.000 COP)</span>
|
|
</ion-button>
|
|
</ion-card-content>
|
|
</ion-card>
|
|
|
|
<!-- DETALLE DEL VIAJE ACTIVO Y NAVEGACIÓN WAZE -->
|
|
<ion-card *ngIf="activeTrip">
|
|
<ion-card-header>
|
|
<div class="flex-header">
|
|
<ion-card-title>Viaje #{{ activeTrip._id?.slice(-6) }}</ion-card-title>
|
|
<ion-badge [color]="getStatusColor(activeTrip.estado)">
|
|
{{ activeTrip.estado }}
|
|
</ion-badge>
|
|
</div>
|
|
<ion-card-subtitle>
|
|
{{ activeTrip.origen?.ciudad }} ➔ {{ activeTrip.destino?.ciudad }}
|
|
</ion-card-subtitle>
|
|
</ion-card-header>
|
|
|
|
<ion-card-content>
|
|
<!-- Mensaje de estado PENDING_APPROVAL -->
|
|
<div *ngIf="activeTrip.estado === 'PENDING_APPROVAL'" class="notice-box">
|
|
<p>⏳ <strong>En revisión del Administrador</strong></p>
|
|
<p>Tu solicitud ha sido agrupada en el pool. Recibirás una notificación push cuando sea aprobada.</p>
|
|
</div>
|
|
|
|
<!-- Información del Conductor (Cuando pasa a ACTIVE) -->
|
|
<div *ngIf="activeTrip.estado === 'ACTIVE'" class="driver-box">
|
|
<h4>🚗 Conductor Asignado</h4>
|
|
<p><strong>Nombre:</strong> {{ activeTrip.conductorId?.nombre || 'Confirmado' }}</p>
|
|
<p><strong>Hora estimada de recogida:</strong> {{ activeTrip.horaEstimadaRecogida | date:'shortTime' }}</p>
|
|
</div>
|
|
|
|
<!-- Secuencia de Navegación Waze para Conductores -->
|
|
<div *ngIf="wazeStops.length > 0" class="waze-section ion-margin-top">
|
|
<h4>🗺️ Ruta Optimizada Waze (Parada por Parada)</h4>
|
|
<ion-progress-bar [value]="wazeProgress.porcentaje / 100"></ion-progress-bar>
|
|
<p class="small-text">{{ wazeProgress.completadas }} de {{ wazeProgress.total }} paradas completadas</p>
|
|
|
|
<div class="current-stop-card" *ngIf="currentStop">
|
|
<p><strong>Siguiente recogida:</strong> {{ currentStop.nombrePasajero }}</p>
|
|
<p class="address-text">{{ currentStop.direccion }}</p>
|
|
|
|
<div class="action-buttons">
|
|
<ion-button color="success" (click)="openWazeForCurrentStop()">
|
|
Abrir en Waze
|
|
</ion-button>
|
|
<ion-button fill="outline" (click)="completeStop()">
|
|
Marcar Recogido
|
|
</ion-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Botón de Cancelación -->
|
|
<ion-button
|
|
expand="block"
|
|
color="danger"
|
|
fill="clear"
|
|
class="ion-margin-top"
|
|
(click)="cancelTrip()"
|
|
>
|
|
Cancelar Reserva
|
|
</ion-button>
|
|
</ion-card-content>
|
|
</ion-card>
|
|
</ion-content>
|
|
`,
|
|
styles: [
|
|
`
|
|
.flex-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
.notice-box {
|
|
background: #fff3cd;
|
|
color: #856404;
|
|
padding: 12px;
|
|
border-radius: 8px;
|
|
margin-bottom: 12px;
|
|
}
|
|
.driver-box {
|
|
background: #e8f4fd;
|
|
color: #0b5394;
|
|
padding: 12px;
|
|
border-radius: 8px;
|
|
margin-bottom: 12px;
|
|
}
|
|
.current-stop-card {
|
|
background: #f8f9fa;
|
|
border: 1px solid #dee2e6;
|
|
padding: 12px;
|
|
border-radius: 8px;
|
|
margin-top: 8px;
|
|
}
|
|
.address-text {
|
|
font-size: 0.9rem;
|
|
color: #6c757d;
|
|
}
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-top: 10px;
|
|
}
|
|
.small-text {
|
|
font-size: 0.8rem;
|
|
color: #6c757d;
|
|
margin-top: 4px;
|
|
}
|
|
`,
|
|
],
|
|
})
|
|
export class TripPoolingComponent implements OnInit {
|
|
private wazeService = inject(WazeNavigationService);
|
|
private tripService = inject(TripService);
|
|
|
|
loading: boolean = false;
|
|
userPenaltyBalance: number = 0;
|
|
boldPenaltyCheckoutUrl?: string;
|
|
|
|
activeTrip: any = null;
|
|
|
|
bvStops = [
|
|
{ nombre: 'Terminal de Transportes Buenaventura', coordenadas: [-77.018, 3.882] },
|
|
{ nombre: 'Centro / Muelle Turístico Buenaventura', coordenadas: [-77.031, 3.889] },
|
|
{ nombre: 'Sector El Piñal Buenaventura', coordenadas: [-77.025, 3.879] },
|
|
];
|
|
|
|
selectedBuenaventuraStop = this.bvStops[0];
|
|
|
|
booking: BookingPayload = {
|
|
usuarioId: 'usr-demo-123',
|
|
direccionTexto: '',
|
|
coordenadas: [-76.532, 3.4516],
|
|
sentido: 'CALI_A_BUENAVENTURA',
|
|
puntoFijoBuenaventura: this.selectedBuenaventuraStop,
|
|
metodoPago: 'BOLD',
|
|
};
|
|
|
|
wazeStops: WazeStop[] = [];
|
|
currentStop: WazeStop | null = null;
|
|
wazeProgress = { completadas: 0, total: 0, porcentaje: 0 };
|
|
|
|
async ngOnInit() {
|
|
await this.checkUserStatus();
|
|
}
|
|
|
|
async checkUserStatus() {
|
|
try {
|
|
const res = await this.tripService.getUserStatus(this.booking.usuarioId);
|
|
if (res.success) {
|
|
this.userPenaltyBalance = res.montoPenalidad || 0;
|
|
this.boldPenaltyCheckoutUrl = res.linkLiquidacionBold;
|
|
}
|
|
} catch (err) {
|
|
console.warn('Error verificando penalidades de usuario:', err);
|
|
}
|
|
}
|
|
|
|
async submitBooking() {
|
|
this.loading = true;
|
|
this.booking.puntoFijoBuenaventura = this.selectedBuenaventuraStop;
|
|
|
|
try {
|
|
const res = await this.tripService.requestPoolTrip(this.booking);
|
|
if (res.success) {
|
|
this.activeTrip = res.trip;
|
|
if (res.secuenciaParadas) {
|
|
this.wazeStops = res.secuenciaParadas.map((p: any) => ({
|
|
orden: p.orden,
|
|
nombrePasajero: `Parada ${p.orden}`,
|
|
direccion: p.direccion,
|
|
lat: p.lat,
|
|
lng: p.lng,
|
|
completada: p.completada,
|
|
}));
|
|
this.wazeService.setStops(this.wazeStops);
|
|
this.updateWazeState();
|
|
}
|
|
}
|
|
} catch (err: any) {
|
|
alert(`Error al solicitar viaje: ${err.message}`);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
}
|
|
|
|
openWazeForCurrentStop() {
|
|
this.wazeService.navigateToCurrentStop();
|
|
}
|
|
|
|
completeStop() {
|
|
this.wazeService.completeCurrentStopAndAdvance();
|
|
this.updateWazeState();
|
|
}
|
|
|
|
updateWazeState() {
|
|
this.currentStop = this.wazeService.getCurrentStop();
|
|
this.wazeProgress = this.wazeService.getProgress();
|
|
}
|
|
|
|
getStatusColor(estado: string): string {
|
|
switch (estado) {
|
|
case 'PENDING_APPROVAL':
|
|
return 'warning';
|
|
case 'ACTIVE':
|
|
return 'success';
|
|
case 'IN_PROGRESS':
|
|
return 'primary';
|
|
case 'COMPLETED':
|
|
return 'medium';
|
|
case 'CANCELLED':
|
|
return 'danger';
|
|
default:
|
|
return 'medium';
|
|
}
|
|
}
|
|
|
|
async cancelTrip() {
|
|
if (!confirm('¿Seguro que deseas cancelar? Si el viaje ya está aprobado, incurrirás en una penalidad.')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const res = await this.tripService.cancelTrip(this.activeTrip._id, this.booking.usuarioId);
|
|
alert(res.message);
|
|
this.activeTrip = null;
|
|
await this.checkUserStatus();
|
|
} catch (err: any) {
|
|
alert(`Error cancelando viaje: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
openUrl(url: string) {
|
|
window.open(url, '_system');
|
|
}
|
|
}
|