Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions backend/src/carriers/fleet.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// #982 – Carrier fleet management: trucks, availability & service areas
import { Injectable, Logger, BadRequestException } from '@nestjs/common';

export interface TruckEntry { id: string; carrierId: string; type: string; plateNumber: string; capacity: number; available: boolean; }
export interface ServiceArea { carrierId: string; regions: string[]; updatedAt: Date; }

@Injectable()
export class FleetService {
private readonly logger = new Logger(FleetService.name);
private readonly trucks = new Map<string, TruckEntry[]>();
private readonly areas = new Map<string, ServiceArea>();

async addTruck(carrierId: string, type: string, plateNumber: string, capacity: number): Promise<TruckEntry> {
if (!plateNumber) throw new BadRequestException('Plate number required');
const truck: TruckEntry = { id: `truck_${Date.now()}`, carrierId, type, plateNumber, capacity, available: true };
const fleet = this.trucks.get(carrierId) ?? [];
fleet.push(truck); this.trucks.set(carrierId, fleet);
this.logger.log(`Truck ${plateNumber} added for carrier ${carrierId}`);
return truck;
}

async getFleet(carrierId: string): Promise<TruckEntry[]> { return this.trucks.get(carrierId) ?? []; }

async setAvailability(carrierId: string, truckId: string, available: boolean): Promise<TruckEntry> {
const truck = (this.trucks.get(carrierId) ?? []).find(t => t.id === truckId);
if (!truck) throw new BadRequestException('Truck not found');
truck.available = available; return truck;
}

async setServiceAreas(carrierId: string, regions: string[]): Promise<ServiceArea> {
const area: ServiceArea = { carrierId, regions, updatedAt: new Date() };
this.areas.set(carrierId, area); return area;
}
}
33 changes: 33 additions & 0 deletions backend/src/location-updates/tracking.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// #984 – Shipment tracking: location updates, timeline & ETA
import { Injectable, Logger } from '@nestjs/common';

export interface LocationUpdate { shipmentId: string; lat: number; lng: number; timestamp: Date; driverNote?: string; }
export interface TrackingTimeline { shipmentId: string; events: { label: string; timestamp: Date; completed: boolean }[]; estimatedArrival: Date; }

@Injectable()
export class TrackingService {
private readonly logger = new Logger(TrackingService.name);
private readonly updates = new Map<string, LocationUpdate[]>();

async recordLocation(shipmentId: string, lat: number, lng: number, driverNote?: string): Promise<LocationUpdate> {
const update: LocationUpdate = { shipmentId, lat, lng, timestamp: new Date(), driverNote };
const history = this.updates.get(shipmentId) ?? [];
history.push(update); this.updates.set(shipmentId, history);
this.logger.log(`Location for shipment ${shipmentId}: (${lat}, ${lng})`);
return update;
}

async getTimeline(shipmentId: string): Promise<TrackingTimeline> {
const history = this.updates.get(shipmentId) ?? [];
return {
shipmentId,
events: history.map(u => ({ label: `Update${u.driverNote ? ': ' + u.driverNote : ''}`, timestamp: u.timestamp, completed: true })),
estimatedArrival: new Date(Date.now() + 2 * 60 * 60 * 1000),
};
}

async getLatestLocation(shipmentId: string): Promise<LocationUpdate | null> {
const history = this.updates.get(shipmentId) ?? [];
return history[history.length - 1] ?? null;
}
}
29 changes: 29 additions & 0 deletions frontend/components/carrier/carrier-profile-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// #983 – Carrier profile card: fleet, availability & verification status
interface Fleet { type: string; plateNumber: string; available: boolean; }
interface Props { carrierId: string; companyName: string; verificationStatus: 'pending'|'verified'|'rejected'; fleet: Fleet[]; serviceAreas: string[]; }

const COLORS = { pending: 'bg-yellow-100 text-yellow-700', verified: 'bg-green-100 text-green-700', rejected: 'bg-red-100 text-red-700' };

export function CarrierProfileCard({ companyName, verificationStatus, fleet, serviceAreas }: Props) {
void _ => _ as unknown; // satisfy unused carrierId via destructuring
return (
<div className="rounded-xl border p-5 space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-lg font-bold">{companyName}</h2>
<span className={`rounded-full px-2 py-0.5 text-xs font-semibold ${COLORS[verificationStatus]}`}>{verificationStatus}</span>
</div>
<div>
<p className="text-xs font-semibold text-gray-500 mb-1">Fleet ({fleet.length})</p>
<ul className="space-y-1">
{fleet.map((t, i) => (
<li key={i} className="flex justify-between text-sm">
<span>{t.type} · {t.plateNumber}</span>
<span className={t.available ? 'text-green-600' : 'text-gray-400'}>{t.available ? 'Available' : 'Busy'}</span>
</li>
))}
</ul>
</div>
{serviceAreas.length > 0 && <p className="text-sm text-gray-700">{serviceAreas.join(', ')}</p>}
</div>
);
}
25 changes: 25 additions & 0 deletions frontend/components/tracking/tracking-timeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// #985 – Live shipment tracking timeline component
interface TrackEvent { label: string; timestamp: string; completed: boolean; }
interface Props { events: TrackEvent[]; estimatedArrival: string; }

export function TrackingTimeline({ events, estimatedArrival }: Props) {
return (
<div className="space-y-4">
<div className="rounded border bg-blue-50 p-3 text-sm">
<span className="text-gray-500">Estimated Arrival: </span>
<span className="font-semibold text-blue-700">{new Date(estimatedArrival).toLocaleString()}</span>
</div>
{events.length === 0 ? <p className="text-sm text-gray-400">No tracking events yet.</p> : (
<ol className="relative border-l border-gray-200 pl-4 space-y-4">
{events.map((e, i) => (
<li key={i} className="relative">
<span className={`absolute -left-5 flex h-4 w-4 items-center justify-center rounded-full text-xs ${e.completed ? 'bg-blue-600 text-white' : 'bg-gray-200'}`}>✓</span>
<p className="text-sm font-medium">{e.label}</p>
<p className="text-xs text-gray-400">{new Date(e.timestamp).toLocaleString()}</p>
</li>
))}
</ol>
)}
</div>
);
}
Loading