completed frontend v1

This commit is contained in:
2026-06-13 00:49:16 +03:00
parent 4a5357ee5f
commit 0d98a060e7
35 changed files with 1175 additions and 409 deletions
+62
View File
@@ -0,0 +1,62 @@
import type {ApiErrorResponse} from "#/types/api";
const API_BASE_URL =
import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080/api";
export class ApiError extends Error {
status: number;
code: string;
details: unknown;
constructor(status: number, error: ApiErrorResponse) {
super(error.message || "Request failed");
this.name = "ApiError";
this.status = status;
this.code = error.error;
this.details = error.details;
}
}
type RequestOptions = {
method?: "GET" | "POST" | "DELETE" | "PUT" | "PATCH";
body?: unknown;
signal?: AbortSignal;
};
export async function apiRequest<T>(
path: string,
options: RequestOptions = {},
): Promise<T> {
const response = await fetch(`${API_BASE_URL}${path}`, {
method: options.method ?? "GET",
headers: {
"Content-Type": "application/json",
},
body: options.body ? JSON.stringify(options.body) : undefined,
signal: options.signal,
});
if (!response.ok) {
let errorBody: ApiErrorResponse;
try {
errorBody = await response.json();
} catch {
errorBody = {
error: "REQUEST_FAILED",
message: `Request failed with status ${response.status}`,
details: null,
timestamp: new Date().toISOString(),
path,
};
}
throw new ApiError(response.status, errorBody);
}
if (response.status === 204) {
return undefined as T;
}
return response.json() as Promise<T>;
}
+20
View File
@@ -0,0 +1,20 @@
import {apiRequest} from "#/api/client";
import type {FareCalculationResponse} from "#/types/fare";
export function calculateFare(
distanceKm: number,
surgeMultiplier?: number,
signal?: AbortSignal,
) {
const params = new URLSearchParams();
params.set("distanceKm", String(distanceKm));
if (surgeMultiplier !== undefined) {
params.set("surgeMultiplier", String(surgeMultiplier));
}
return apiRequest<FareCalculationResponse>(
`/fare/calculate?${params.toString()}`,
{signal},
);
}
+24
View File
@@ -0,0 +1,24 @@
import {apiRequest} from "#/api/client";
import type {InventoryResponse, LowStockInventoryResponse,} from "#/types/inventory";
export function getInventory(signal?: AbortSignal) {
return apiRequest<InventoryResponse>("/inventory", {signal});
}
export function getLowStockInventory(
threshold?: number,
signal?: AbortSignal,
) {
const params = new URLSearchParams();
if (threshold !== undefined) {
params.set("threshold", String(threshold));
}
const query = params.toString();
return apiRequest<LowStockInventoryResponse>(
`/inventory/low-stock${query ? `?${query}` : ""}`,
{signal},
);
}
+41
View File
@@ -0,0 +1,41 @@
import {apiRequest} from "#/api/client";
import type {PageResponse} from "#/types/api";
import type {CancelOrderResponse, Order, OrderFilters, OrderSummary,} from "#/types/order";
export function getOrders(
filters: OrderFilters,
page = 0,
size = 20,
signal?: AbortSignal,
) {
const params = new URLSearchParams();
if (filters.status) {
params.set("status", filters.status);
}
if (filters.from) {
params.set("from", filters.from);
}
if (filters.to) {
params.set("to", filters.to);
}
params.set("page", String(page));
params.set("size", String(size));
return apiRequest<PageResponse<OrderSummary>>(`/orders?${params.toString()}`, {
signal,
});
}
export function getOrder(id: string, signal?: AbortSignal) {
return apiRequest<Order>(`/orders/${id}`, {signal});
}
export function cancelOrder(id: string) {
return apiRequest<CancelOrderResponse>(`/orders/${id}`, {
method: "DELETE",
});
}