369 lines
9.3 KiB
TypeScript
369 lines
9.3 KiB
TypeScript
/**
|
|
* 차량 운행 이력 API 클라이언트
|
|
*/
|
|
import { apiClient } from "./client";
|
|
|
|
// 타입 정의
|
|
export interface TripSummary {
|
|
id: number;
|
|
trip_id: string;
|
|
user_id: string;
|
|
user_name?: string;
|
|
vehicle_id?: number;
|
|
vehicle_number?: string;
|
|
departure?: string;
|
|
arrival?: string;
|
|
departure_name?: string;
|
|
destination_name?: string;
|
|
start_time: string;
|
|
end_time?: string;
|
|
total_distance: number;
|
|
duration_minutes?: number;
|
|
status: "active" | "completed" | "cancelled";
|
|
location_count: number;
|
|
company_code: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface TripLocation {
|
|
id: number;
|
|
latitude: number;
|
|
longitude: number;
|
|
accuracy?: number;
|
|
speed?: number;
|
|
distance_from_prev?: number;
|
|
trip_status: "start" | "tracking" | "end";
|
|
recorded_at: string;
|
|
}
|
|
|
|
export interface TripDetail {
|
|
summary: TripSummary;
|
|
route: TripLocation[];
|
|
}
|
|
|
|
export interface TripListFilters {
|
|
userId?: string;
|
|
vehicleId?: number;
|
|
status?: string;
|
|
startDate?: string;
|
|
endDate?: string;
|
|
departure?: string;
|
|
arrival?: string;
|
|
limit?: number;
|
|
offset?: number;
|
|
}
|
|
|
|
export interface StartTripParams {
|
|
vehicleId?: number;
|
|
departure?: string;
|
|
arrival?: string;
|
|
departureName?: string;
|
|
destinationName?: string;
|
|
latitude: number;
|
|
longitude: number;
|
|
}
|
|
|
|
export interface EndTripParams {
|
|
tripId: string;
|
|
latitude: number;
|
|
longitude: number;
|
|
}
|
|
|
|
export interface AddLocationParams {
|
|
tripId: string;
|
|
latitude: number;
|
|
longitude: number;
|
|
accuracy?: number;
|
|
speed?: number;
|
|
}
|
|
|
|
// API 함수들
|
|
|
|
/**
|
|
* 운행 시작
|
|
*/
|
|
export async function startTrip(params: StartTripParams) {
|
|
const response = await apiClient.post("/vehicle/trip/start", params);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* 운행 종료
|
|
*/
|
|
export async function endTrip(params: EndTripParams) {
|
|
const response = await apiClient.post("/vehicle/trip/end", params);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* 위치 기록 추가 (연속 추적)
|
|
*/
|
|
export async function addTripLocation(params: AddLocationParams) {
|
|
const response = await apiClient.post("/vehicle/trip/location", params);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* 활성 운행 조회
|
|
*/
|
|
export async function getActiveTrip() {
|
|
const response = await apiClient.get("/vehicle/trip/active");
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* 운행 취소
|
|
*/
|
|
export async function cancelTrip(tripId: string) {
|
|
const response = await apiClient.post("/vehicle/trip/cancel", { tripId });
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* 운행 이력 목록 조회
|
|
*/
|
|
export async function getTripList(filters?: TripListFilters) {
|
|
const params = new URLSearchParams();
|
|
|
|
if (filters) {
|
|
if (filters.userId) params.append("userId", filters.userId);
|
|
if (filters.vehicleId) params.append("vehicleId", String(filters.vehicleId));
|
|
if (filters.status) params.append("status", filters.status);
|
|
if (filters.startDate) params.append("startDate", filters.startDate);
|
|
if (filters.endDate) params.append("endDate", filters.endDate);
|
|
if (filters.departure) params.append("departure", filters.departure);
|
|
if (filters.arrival) params.append("arrival", filters.arrival);
|
|
if (filters.limit) params.append("limit", String(filters.limit));
|
|
if (filters.offset) params.append("offset", String(filters.offset));
|
|
}
|
|
|
|
const queryString = params.toString();
|
|
const url = queryString ? `/vehicle/trips?${queryString}` : "/vehicle/trips";
|
|
|
|
const response = await apiClient.get(url);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* 운행 상세 조회 (경로 포함)
|
|
*/
|
|
export async function getTripDetail(tripId: string): Promise<{ success: boolean; data?: TripDetail; message?: string }> {
|
|
const response = await apiClient.get(`/vehicle/trips/${tripId}`);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* 거리 포맷팅 (km)
|
|
*/
|
|
export function formatDistance(distanceKm: number): string {
|
|
if (distanceKm < 1) {
|
|
return `${Math.round(distanceKm * 1000)}m`;
|
|
}
|
|
return `${distanceKm.toFixed(2)}km`;
|
|
}
|
|
|
|
/**
|
|
* 운행 시간 포맷팅
|
|
*/
|
|
export function formatDuration(minutes: number): string {
|
|
if (minutes < 60) {
|
|
return `${minutes}분`;
|
|
}
|
|
const hours = Math.floor(minutes / 60);
|
|
const mins = minutes % 60;
|
|
return mins > 0 ? `${hours}시간 ${mins}분` : `${hours}시간`;
|
|
}
|
|
|
|
/**
|
|
* 상태 한글 변환
|
|
*/
|
|
export function getStatusLabel(status: string): string {
|
|
switch (status) {
|
|
case "active":
|
|
return "운행 중";
|
|
case "completed":
|
|
return "완료";
|
|
case "cancelled":
|
|
return "취소됨";
|
|
default:
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 상태별 색상
|
|
*/
|
|
export function getStatusColor(status: string): string {
|
|
switch (status) {
|
|
case "active":
|
|
return "bg-green-100 text-green-800";
|
|
case "completed":
|
|
return "bg-blue-100 text-blue-800";
|
|
case "cancelled":
|
|
return "bg-gray-100 text-gray-800";
|
|
default:
|
|
return "bg-gray-100 text-gray-800";
|
|
}
|
|
}
|
|
|
|
// ============== 리포트 API ==============
|
|
|
|
export interface DailyStat {
|
|
date: string;
|
|
tripCount: number;
|
|
completedCount: number;
|
|
cancelledCount: number;
|
|
totalDistance: number;
|
|
totalDuration: number;
|
|
avgDistance: number;
|
|
avgDuration: number;
|
|
}
|
|
|
|
export interface WeeklyStat {
|
|
weekNumber: number;
|
|
weekStart: string;
|
|
weekEnd: string;
|
|
tripCount: number;
|
|
completedCount: number;
|
|
totalDistance: number;
|
|
totalDuration: number;
|
|
avgDistance: number;
|
|
}
|
|
|
|
export interface MonthlyStat {
|
|
month: number;
|
|
tripCount: number;
|
|
completedCount: number;
|
|
cancelledCount: number;
|
|
totalDistance: number;
|
|
totalDuration: number;
|
|
avgDistance: number;
|
|
avgDuration: number;
|
|
driverCount: number;
|
|
}
|
|
|
|
export interface SummaryReport {
|
|
period: string;
|
|
totalTrips: number;
|
|
completedTrips: number;
|
|
activeTrips: number;
|
|
cancelledTrips: number;
|
|
completionRate: number;
|
|
totalDistance: number;
|
|
totalDuration: number;
|
|
avgDistance: number;
|
|
avgDuration: number;
|
|
activeDrivers: number;
|
|
}
|
|
|
|
export interface DriverStat {
|
|
userId: string;
|
|
userName: string;
|
|
tripCount: number;
|
|
completedCount: number;
|
|
totalDistance: number;
|
|
totalDuration: number;
|
|
avgDistance: number;
|
|
}
|
|
|
|
export interface RouteStat {
|
|
departure: string;
|
|
arrival: string;
|
|
departureName: string;
|
|
destinationName: string;
|
|
tripCount: number;
|
|
completedCount: number;
|
|
totalDistance: number;
|
|
avgDistance: number;
|
|
avgDuration: number;
|
|
}
|
|
|
|
/**
|
|
* 요약 통계 조회 (대시보드용)
|
|
*/
|
|
export async function getSummaryReport(period?: string) {
|
|
const url = period ? `/vehicle/reports/summary?period=${period}` : "/vehicle/reports/summary";
|
|
const response = await apiClient.get(url);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* 일별 통계 조회
|
|
*/
|
|
export async function getDailyReport(filters?: { startDate?: string; endDate?: string; userId?: string }) {
|
|
const params = new URLSearchParams();
|
|
if (filters?.startDate) params.append("startDate", filters.startDate);
|
|
if (filters?.endDate) params.append("endDate", filters.endDate);
|
|
if (filters?.userId) params.append("userId", filters.userId);
|
|
|
|
const queryString = params.toString();
|
|
const url = queryString ? `/vehicle/reports/daily?${queryString}` : "/vehicle/reports/daily";
|
|
|
|
const response = await apiClient.get(url);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* 주별 통계 조회
|
|
*/
|
|
export async function getWeeklyReport(filters?: { year?: number; month?: number; userId?: string }) {
|
|
const params = new URLSearchParams();
|
|
if (filters?.year) params.append("year", String(filters.year));
|
|
if (filters?.month) params.append("month", String(filters.month));
|
|
if (filters?.userId) params.append("userId", filters.userId);
|
|
|
|
const queryString = params.toString();
|
|
const url = queryString ? `/vehicle/reports/weekly?${queryString}` : "/vehicle/reports/weekly";
|
|
|
|
const response = await apiClient.get(url);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* 월별 통계 조회
|
|
*/
|
|
export async function getMonthlyReport(filters?: { year?: number; userId?: string }) {
|
|
const params = new URLSearchParams();
|
|
if (filters?.year) params.append("year", String(filters.year));
|
|
if (filters?.userId) params.append("userId", filters.userId);
|
|
|
|
const queryString = params.toString();
|
|
const url = queryString ? `/vehicle/reports/monthly?${queryString}` : "/vehicle/reports/monthly";
|
|
|
|
const response = await apiClient.get(url);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* 운전자별 통계 조회
|
|
*/
|
|
export async function getDriverReport(filters?: { startDate?: string; endDate?: string; limit?: number }) {
|
|
const params = new URLSearchParams();
|
|
if (filters?.startDate) params.append("startDate", filters.startDate);
|
|
if (filters?.endDate) params.append("endDate", filters.endDate);
|
|
if (filters?.limit) params.append("limit", String(filters.limit));
|
|
|
|
const queryString = params.toString();
|
|
const url = queryString ? `/vehicle/reports/by-driver?${queryString}` : "/vehicle/reports/by-driver";
|
|
|
|
const response = await apiClient.get(url);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* 구간별 통계 조회
|
|
*/
|
|
export async function getRouteReport(filters?: { startDate?: string; endDate?: string; limit?: number }) {
|
|
const params = new URLSearchParams();
|
|
if (filters?.startDate) params.append("startDate", filters.startDate);
|
|
if (filters?.endDate) params.append("endDate", filters.endDate);
|
|
if (filters?.limit) params.append("limit", String(filters.limit));
|
|
|
|
const queryString = params.toString();
|
|
const url = queryString ? `/vehicle/reports/by-route?${queryString}` : "/vehicle/reports/by-route";
|
|
|
|
const response = await apiClient.get(url);
|
|
return response.data;
|
|
}
|
|
|