This commit is contained in:
2026-01-18 00:49:39 +04:00
parent 46cd4ef38c
commit 21ef59ff94
21 changed files with 830 additions and 251 deletions

5
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"recommendations": [
"ms-dotnettools.csharp"
]
}

3
.vscode/launch.json vendored
View File

@@ -2,10 +2,11 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Debug React App", "name": "Debug React App",
"type": "firefox", "type": "msedge",
"request": "launch", "request": "launch",
"url": "http://localhost:3000", "url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/src", "webRoot": "${workspaceFolder}/src",

30
db.json
View File

@@ -5,7 +5,7 @@
"clientName": "Иванов Иван Иванович", "clientName": "Иванов Иван Иванович",
"orderCost": 15000, "orderCost": 15000,
"orderDate": "2025-01-15", "orderDate": "2025-01-15",
"status": "in_progress", "status": "completed",
"address": "ул. Ленина, 10", "address": "ул. Ленина, 10",
"description": "Доставка строительных материалов" "description": "Доставка строительных материалов"
}, },
@@ -32,7 +32,7 @@
"clientName": "Сергеев Алексей Петрович", "clientName": "Сергеев Алексей Петрович",
"orderCost": 12000, "orderCost": 12000,
"orderDate": "2025-01-18", "orderDate": "2025-01-18",
"status": "pending", "status": "in_progress",
"address": "пр. Космонавтов, 78", "address": "пр. Космонавтов, 78",
"description": "Переезд квартиры" "description": "Переезд квартиры"
}, },
@@ -77,7 +77,7 @@
"clientName": "Александрова Ольга Викторовна", "clientName": "Александрова Ольга Викторовна",
"orderCost": 9500, "orderCost": 9500,
"orderDate": "2025-01-23", "orderDate": "2025-01-23",
"status": "pending", "status": "cancelled",
"address": "ул. Центральная, 89", "address": "ул. Центральная, 89",
"description": "Перевозка личных вещей" "description": "Перевозка личных вещей"
}, },
@@ -184,14 +184,6 @@
"endTime": "12:00", "endTime": "12:00",
"date": "2025-11-21" "date": "2025-11-21"
}, },
{
"id": "e320",
"vehicleId": "1",
"orderId": "1",
"startTime": "08:00",
"endTime": "10:00",
"date": "2025-11-21"
},
{ {
"id": "08a1", "id": "08a1",
"vehicleId": "2", "vehicleId": "2",
@@ -207,6 +199,22 @@
"startTime": "08:00", "startTime": "08:00",
"endTime": "09:00", "endTime": "09:00",
"date": "2025-11-21" "date": "2025-11-21"
},
{
"id": "6f66",
"vehicleId": "1",
"orderId": "1",
"startTime": "08:00",
"endTime": "11:00",
"date": "2025-11-21"
},
{
"id": "f275",
"vehicleId": "2",
"orderId": "4",
"startTime": "08:00",
"endTime": "10:00",
"date": "2025-11-23"
} }
] ]
} }

View File

@@ -1,53 +1,122 @@
import React from 'react'; import React, { useState, useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom'; import { BrowserRouter as Router, Routes, Route, Link, Navigate } from 'react-router-dom';
import OrdersPage from './pages/OrdersPage'; import OrdersPage from './pages/OrdersPage';
import VehiclesPage from './pages/VehiclesPage'; import VehiclesPage from './pages/VehiclesPage';
import VehicleDetailPage from './pages/VehicleDetailPage'; import VehicleDetailPage from './pages/VehicleDetailPage';
import LoginPage from './pages/LoginPage';
import AdminPage from './pages/AdminPage';
import ProtectedRoute from './components/ProtectedRoute';
import AdminRoute from './components/AdminRoute';
import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css'; import './App.css';
const App: React.FC = () => { const App: React.FC = () => {
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
const checkAuth = (): boolean => {
return document.cookie.includes('auth_token');
};
useEffect(() => {
setIsAuthenticated(checkAuth());
const interval = setInterval(() => {
const currentAuth = checkAuth();
if (currentAuth !== isAuthenticated) {
setIsAuthenticated(currentAuth);
}
}, 1000);
return () => clearInterval(interval);
}, [isAuthenticated]);
const handleLogout = () => {
document.cookie = 'auth_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
document.cookie = 'user_data=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
setIsAuthenticated(false);
window.location.href = '/login';
};
return ( return (
<Router> <Router>
<div className="App"> <div className="App">
{/* Навигация */} {isAuthenticated && (
<nav className="navbar navbar-expand-lg navbar-dark bg-primary"> <nav className="navbar navbar-expand-lg navbar-dark bg-primary">
<div className="container"> <div className="container">
<Link className="navbar-brand" to="/"> <Link className="navbar-brand" to="/orders">
Логистическая компания Логистическая компания
</Link> </Link>
<button <div className="collapse navbar-collapse">
className="navbar-toggler" <ul className="navbar-nav me-auto">
type="button" <li className="nav-item">
data-bs-toggle="collapse" <Link className="nav-link" to="/orders">Заказы</Link>
data-bs-target="#navbarNav" </li>
> <li className="nav-item">
<span className="navbar-toggler-icon"></span> <Link className="nav-link" to="/vehicles">Машины</Link>
</button> </li>
<div className="collapse navbar-collapse" id="navbarNav"> <li className="nav-item">
<ul className="navbar-nav"> <Link className="nav-link" to="/admin">Админ-панель</Link>
<li className="nav-item"> </li>
<Link className="nav-link" to="/orders"> </ul>
Заказы <div className="navbar-nav">
</Link> <button
</li> className="btn btn-outline-light btn-sm"
<li className="nav-item"> onClick={handleLogout}
<Link className="nav-link" to="/vehicles"> >
Машины Выйти
</Link> </button>
</li> </div>
</ul> </div>
</div> </div>
</div> </nav>
</nav> )}
{/* Основной контент */}
<main> <main>
<Routes> <Routes>
<Route path="/" element={<OrdersPage />} /> <Route path="/" element={<Navigate to="/login" replace />} />
<Route path="/orders" element={<OrdersPage />} />
<Route path="/vehicles" element={<VehiclesPage />} /> <Route
<Route path="/vehicles/:id" element={<VehicleDetailPage />} /> path="/login"
element={<LoginPage onLogin={() => setIsAuthenticated(true)} />}
/>
<Route
path="/orders"
element={
<ProtectedRoute>
<OrdersPage />
</ProtectedRoute>
}
/>
<Route
path="/vehicles"
element={
<ProtectedRoute>
<VehiclesPage />
</ProtectedRoute>
}
/>
<Route
path="/vehicles/:id"
element={
<ProtectedRoute>
<VehicleDetailPage />
</ProtectedRoute>
}
/>
<Route
path="/admin"
element={
<AdminRoute>
<AdminPage />
</AdminRoute>
}
/>
<Route path="*" element={<Navigate to="/login" replace />} />
</Routes> </Routes>
</main> </main>
</div> </div>

View File

@@ -0,0 +1,58 @@
import React from 'react';
import { Navigate } from 'react-router-dom';
interface AdminRouteProps {
children: React.ReactNode;
}
const AdminRoute: React.FC<AdminRouteProps> = ({ children }) => {
// Функция для получения значения cookie
const getCookie = (name: string): string | null => {
const nameEQ = name + "=";
const ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
}
return null;
};
const getUserRole = (): string | null => {
const token = getCookie('auth_token');
if (!token) return null;
try {
const payload = token.split('.')[1];
const decodedPayload = JSON.parse(atob(payload));
return decodedPayload['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'] ||
decodedPayload.role ||
null;
} catch (error) {
console.error('Error decoding token:', error);
return null;
}
};
const token = getCookie('auth_token');
const userRole = getUserRole();
console.log('AdminRoute - checking admin access...');
console.log('Token exists:', !!token);
console.log('User role:', userRole);
if (!token) {
console.log('No token found, redirecting to login');
return <Navigate to="/login" replace />;
}
if (userRole !== 'admin') {
console.log('User is not admin, redirecting to orders');
return <Navigate to="/orders" replace />;
}
console.log('User is admin, allowing access to admin panel');
return <>{children}</>;
};
export default AdminRoute;

View File

@@ -1,28 +1,54 @@
import React from 'react'; import React from 'react';
import { OrderFiltersType as OrderFiltersType } from '../types';
interface OrderFiltersProps { interface OrderFiltersProps {
filters: { filters: OrderFiltersType;
clientName: string; onFiltersChange: (filters: OrderFiltersType) => void;
minCost: string; onApplyFilters: () => void;
maxCost: string;
orderDate: string;
status: string;
};
onFiltersChange: (filters: any) => void;
} }
const OrderFilters: React.FC<OrderFiltersProps> = ({ filters, onFiltersChange }) => { const OrderFilters: React.FC<OrderFiltersProps> = ({ filters, onFiltersChange, onApplyFilters }) => {
const handleChange = (field: string, value: string) => { const handleChange = (field: keyof OrderFiltersType, value: string | number | undefined) => {
onFiltersChange({ onFiltersChange({
...filters, ...filters,
[field]: value [field]: value
}); });
}; };
const handleClearFilters = () => {
onFiltersChange({
clientName: '',
minCost: undefined,
maxCost: undefined,
orderDate: '',
status: '',
page: 1,
pageSize: 50
});
};
const handleApply = () => {
onApplyFilters();
};
return ( return (
<div className="card mb-4"> <div className="card mb-4">
<div className="card-header"> <div className="card-header d-flex justify-content-between align-items-center">
<h5 className="mb-0">Фильтры заказов</h5> <h5 className="mb-0">Фильтры заказов</h5>
<div className="d-flex">
<button
className="btn btn-sm btn-outline-secondary me-2"
onClick={handleClearFilters}
>
Сбросить
</button>
<button
className="btn btn-sm btn-primary"
onClick={handleApply}
>
Применить
</button>
</div>
</div> </div>
<div className="card-body"> <div className="card-body">
<div className="row g-3"> <div className="row g-3">
@@ -32,54 +58,54 @@ const OrderFilters: React.FC<OrderFiltersProps> = ({ filters, onFiltersChange })
type="text" type="text"
className="form-control" className="form-control"
id="clientName" id="clientName"
value={filters.clientName} value={filters.clientName || ''}
onChange={(e) => handleChange('clientName', e.target.value)} onChange={(e) => handleChange('clientName', e.target.value)}
placeholder="ФИО или наименование" placeholder="ФИО или наименование"
/> />
</div> </div>
<div className="col-md-2"> <div className="col-md-2">
<label htmlFor="minCost" className="form-label">Мин. стоимость</label> <label htmlFor="minCost" className="form-label">Мин. стоимость</label>
<input <input
type="number" type="number"
className="form-control" className="form-control"
id="minCost" id="minCost"
value={filters.minCost} value={filters.minCost || ''}
onChange={(e) => handleChange('minCost', e.target.value)} onChange={(e) => handleChange('minCost', e.target.value ? parseFloat(e.target.value) : undefined)}
placeholder="0" placeholder="0"
/> />
</div> </div>
<div className="col-md-2"> <div className="col-md-2">
<label htmlFor="maxCost" className="form-label">Макс. стоимость</label> <label htmlFor="maxCost" className="form-label">Макс. стоимость</label>
<input <input
type="number" type="number"
className="form-control" className="form-control"
id="maxCost" id="maxCost"
value={filters.maxCost} value={filters.maxCost || ''}
onChange={(e) => handleChange('maxCost', e.target.value)} onChange={(e) => handleChange('maxCost', e.target.value ? parseFloat(e.target.value) : undefined)}
placeholder="100000" placeholder="100000"
/> />
</div> </div>
<div className="col-md-2"> <div className="col-md-2">
<label htmlFor="orderDate" className="form-label">Дата заказа</label> <label htmlFor="orderDate" className="form-label">Дата заказа</label>
<input <input
type="date" type="date"
className="form-control" className="form-control"
id="orderDate" id="orderDate"
value={filters.orderDate} value={filters.orderDate || ''}
onChange={(e) => handleChange('orderDate', e.target.value)} onChange={(e) => handleChange('orderDate', e.target.value)}
/> />
</div> </div>
<div className="col-md-3"> <div className="col-md-3">
<label htmlFor="status" className="form-label">Статус</label> <label htmlFor="status" className="form-label">Статус</label>
<select <select
className="form-select" className="form-select"
id="status" id="status"
value={filters.status} value={filters.status || ''}
onChange={(e) => handleChange('status', e.target.value)} onChange={(e) => handleChange('status', e.target.value || undefined)}
> >
<option value="">Все статусы</option> <option value="">Все статусы</option>
<option value="pending">Ожидает</option> <option value="pending">Ожидает</option>

View File

@@ -0,0 +1,18 @@
import React from 'react';
import { Navigate } from 'react-router-dom';
interface ProtectedRouteProps {
children: React.ReactNode;
}
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
const token = document.cookie.includes('auth_token');
if (!token) {
return <Navigate to="/login" replace />;
}
return <>{children}</>;
};
export default ProtectedRoute;

View File

@@ -1,26 +1,52 @@
import React from 'react'; import React from 'react';
import { VehicleFiltersType as VehicleFiltersType } from '../types';
interface VehicleFiltersProps { interface VehicleFiltersProps {
filters: { filters: VehicleFiltersType;
driverName: string; onFiltersChange: (filters: VehicleFiltersType) => void;
vehicleType: string; onApplyFilters: () => void;
licensePlate: string;
};
onFiltersChange: (filters: any) => void;
} }
const VehicleFilters: React.FC<VehicleFiltersProps> = ({ filters, onFiltersChange }) => { const VehicleFilters: React.FC<VehicleFiltersProps> = ({ filters, onFiltersChange, onApplyFilters }) => {
const handleChange = (field: string, value: string) => { const handleChange = (field: keyof VehicleFiltersType, value: string | undefined) => {
onFiltersChange({ onFiltersChange({
...filters, ...filters,
[field]: value [field]: value
}); });
}; };
const handleClearFilters = () => {
onFiltersChange({
driverName: '',
vehicleType: '',
licensePlate: '',
page: 1,
pageSize: 50
});
};
const handleApply = () => {
onApplyFilters();
};
return ( return (
<div className="card mb-4"> <div className="card mb-4">
<div className="card-header"> <div className="card-header d-flex justify-content-between align-items-center">
<h5 className="mb-0">Фильтры машин</h5> <h5 className="mb-0">Фильтры машин</h5>
<div className="d-flex">
<button
className="btn btn-sm btn-outline-secondary me-2"
onClick={handleClearFilters}
>
Сбросить
</button>
<button
className="btn btn-sm btn-primary"
onClick={handleApply}
>
Применить
</button>
</div>
</div> </div>
<div className="card-body"> <div className="card-body">
<div className="row g-3"> <div className="row g-3">
@@ -30,7 +56,7 @@ const VehicleFilters: React.FC<VehicleFiltersProps> = ({ filters, onFiltersChang
type="text" type="text"
className="form-control" className="form-control"
id="driverName" id="driverName"
value={filters.driverName} value={filters.driverName || ''}
onChange={(e) => handleChange('driverName', e.target.value)} onChange={(e) => handleChange('driverName', e.target.value)}
placeholder="ФИО водителя" placeholder="ФИО водителя"
/> />
@@ -42,7 +68,7 @@ const VehicleFilters: React.FC<VehicleFiltersProps> = ({ filters, onFiltersChang
type="text" type="text"
className="form-control" className="form-control"
id="vehicleType" id="vehicleType"
value={filters.vehicleType} value={filters.vehicleType || ''}
onChange={(e) => handleChange('vehicleType', e.target.value)} onChange={(e) => handleChange('vehicleType', e.target.value)}
placeholder="Тип машины" placeholder="Тип машины"
/> />
@@ -54,7 +80,7 @@ const VehicleFilters: React.FC<VehicleFiltersProps> = ({ filters, onFiltersChang
type="text" type="text"
className="form-control" className="form-control"
id="licensePlate" id="licensePlate"
value={filters.licensePlate} value={filters.licensePlate || ''}
onChange={(e) => handleChange('licensePlate', e.target.value)} onChange={(e) => handleChange('licensePlate', e.target.value)}
placeholder="Гос. номер" placeholder="Гос. номер"
/> />

View File

@@ -9,8 +9,6 @@ interface WaybillWidgetProps {
date: string; date: string;
} }
let durationOrder;
const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => { const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
const [entries, setEntries] = useState<WaybillEntry[]>([]); const [entries, setEntries] = useState<WaybillEntry[]>([]);
const [orders, setOrders] = useState<Order[]>([]); const [orders, setOrders] = useState<Order[]>([]);
@@ -21,7 +19,7 @@ const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
const [selectedTimeSlot, setSelectedTimeSlot] = useState<string | null>(null); const [selectedTimeSlot, setSelectedTimeSlot] = useState<string | null>(null);
const [duration, setDuration] = useState<number>(2); const [duration, setDuration] = useState<number>(2);
// Часовые интервалы с 8:00 до 20:00
const timeSlots = Array.from({ length: 13 }, (_, i) => { const timeSlots = Array.from({ length: 13 }, (_, i) => {
const hour = i + 8; const hour = i + 8;
return `${hour.toString().padStart(2, '0')}:00`; return `${hour.toString().padStart(2, '0')}:00`;
@@ -30,22 +28,23 @@ const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
const loadData = async () => { const loadData = async () => {
try { try {
const response = await ordersApi.getOrders(); const response = await ordersApi.getOrders();
setOrders(response.data); setOrders((response.data as any).data.items);
setLoading(true); setLoading(true);
setError(null); setError(null);
// Загружаем записи путевого листа const entriesResponse = (await waybillApi.getEntries() as any).data;
const entriesResponse = await waybillApi.getEntries();
const vehicleEntries = entriesResponse.data.filter( const vehicleEntries = entriesResponse.data.filter(
entry => entry.vehicleId === vehicleId && entry.date === date (entry: { vehicleId: number; date: string; }) => entry.vehicleId === vehicleId && entry.date === date
); );
setEntries(vehicleEntries); setEntries(vehicleEntries);
// Загружаем доступные заказы
const ordersResponse = await ordersApi.getOrders(); const ordersResponse = (await ordersApi.getOrders() as any).data.data.items;
const assignedOrderIds = new Set(vehicleEntries.map(entry => entry.orderId));
const available = ordersResponse.data.filter( const assignedOrderIds = new Set(vehicleEntries.map((entry: { orderId: any; }) => entry.orderId));
order => !assignedOrderIds.has(order.id) && order.status !== 'completed' const available = ordersResponse.filter(
(order: Order) => !assignedOrderIds.has(order.id) && order.status !== 'completed'
); );
setAvailableOrders(available); setAvailableOrders(available);
@@ -71,13 +70,12 @@ const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
}; };
const getOrderInfo = (orderId: number, allOrders: Order[]): Order | undefined => { const getOrderInfo = (orderId: number, allOrders: Order[]): Order | undefined => {
// Ищем заказ среди всех заказов
const foundOrder = allOrders.find(order => order.id === orderId); const foundOrder = allOrders.find(order => order.id === orderId);
if (foundOrder) { if (foundOrder) {
return foundOrder; return foundOrder;
} }
// Если заказ не найден, но есть в путевом листе - создаем базовый объект
const waybillEntry = entries.find(entry => entry.orderId === orderId); const waybillEntry = entries.find(entry => entry.orderId === orderId);
if (waybillEntry) { if (waybillEntry) {
const basicOrder: Order = { const basicOrder: Order = {
@@ -92,7 +90,6 @@ const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
return basicOrder; return basicOrder;
} }
// Если заказ не найден нигде
return undefined; return undefined;
}; };
@@ -100,7 +97,7 @@ const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
if (selectedOrder) { if (selectedOrder) {
try { try {
const startHour = parseInt(timeSlot.split(':')[0]); const startHour = parseInt(timeSlot.split(':')[0]);
const endHour = startHour + duration; // Используем выбранную длительность const endHour = startHour + duration;
const newEntry: Omit<WaybillEntry, 'id'> = { const newEntry: Omit<WaybillEntry, 'id'> = {
vehicleId, vehicleId,
@@ -109,6 +106,7 @@ const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
endTime: `${endHour.toString().padStart(2, '0')}:00`, endTime: `${endHour.toString().padStart(2, '0')}:00`,
date date
}; };
console.log('Sending waybill entry:', newEntry);
await waybillApi.createEntry(newEntry); await waybillApi.createEntry(newEntry);
setSelectedOrder(null); setSelectedOrder(null);
@@ -128,7 +126,7 @@ const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
if (selectedTimeSlot) { if (selectedTimeSlot) {
try { try {
const startHour = parseInt(selectedTimeSlot.split(':')[0]); const startHour = parseInt(selectedTimeSlot.split(':')[0]);
const endHour = startHour + duration; // Используем выбранную длительность const endHour = startHour + duration;
const newEntry: Omit<WaybillEntry, 'id'> = { const newEntry: Omit<WaybillEntry, 'id'> = {
vehicleId, vehicleId,
@@ -137,6 +135,7 @@ const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
endTime: `${endHour.toString().padStart(2, '0')}:00`, endTime: `${endHour.toString().padStart(2, '0')}:00`,
date date
}; };
console.log('Sending waybill entry:', newEntry);
await waybillApi.createEntry(newEntry); await waybillApi.createEntry(newEntry);
setSelectedTimeSlot(null); setSelectedTimeSlot(null);
@@ -172,7 +171,6 @@ const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
<h5 className="mb-0">Путевой лист на {new Date(date).toLocaleDateString()}</h5> <h5 className="mb-0">Путевой лист на {new Date(date).toLocaleDateString()}</h5>
</div> </div>
<div className="card-body"> <div className="card-body">
{/* Состояние выбора */}
<div className="alert alert-info mb-3"> <div className="alert alert-info mb-3">
{selectedOrder && !selectedTimeSlot && ( {selectedOrder && !selectedTimeSlot && (
<div> <div>
@@ -220,7 +218,6 @@ const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
</p> </p>
)} )}
</div> </div>
{/* Таблица временных интервалов */}
<div className="table-responsive mb-4"> <div className="table-responsive mb-4">
<table className="table table-bordered"> <table className="table table-bordered">
<thead> <thead>
@@ -275,7 +272,6 @@ const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
</table> </table>
</div> </div>
{/* Список доступных заказов */}
<div className="available-orders"> <div className="available-orders">
<h6>Доступные заказы:</h6> <h6>Доступные заказы:</h6>
<div className="row"> <div className="row">

139
src/pages/AdminPage.tsx Normal file
View File

@@ -0,0 +1,139 @@
import React, { useState, useEffect } from 'react';
import { WaybillEntry, Order, Vehicle } from '../types';
import { waybillApi, ordersApi, vehiclesApi } from '../services/api';
import Loading from '../components/Loading';
import ErrorAlert from '../components/ErrorAlert';
const AdminPage: React.FC = () => {
const [waybillEntries, setWaybillEntries] = useState<WaybillEntry[]>([]);
const [orders, setOrders] = useState<Order[]>([]);
const [vehicles, setVehicles] = useState<Vehicle[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadData = async () => {
try {
setLoading(true);
setError(null);
const entriesData = (await waybillApi.getEntries() as any).data.data;
const ordersData = (await ordersApi.getOrders() as any).data.data.items;
const vehiclesData = (await vehiclesApi.getVehicles() as any).data.data.items;
setWaybillEntries(Array.isArray(entriesData) ? entriesData : []);
setOrders(Array.isArray(ordersData) ? ordersData : []);
setVehicles(Array.isArray(vehiclesData) ? vehiclesData : []);
} catch (err) {
setError('Не удалось загрузить данные');
console.error('Error loading admin data:', err);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadData();
}, []);
const handleDeleteEntry = async (id: number) => {
if (window.confirm('Вы уверены, что хотите удалить эту запись?')) {
try {
await waybillApi.deleteEntry(id);
await loadData();
} catch (err) {
setError('Не удалось удалить запись');
console.error('Error deleting entry:', err);
}
}
};
if (loading) return <Loading />;
if (error) return <ErrorAlert message={error} onRetry={loadData} />;
return (
<div className="container mt-4">
<h1 className="mb-4">Панель администратора</h1>
<div className="row">
<div className="col-md-4 mb-4">
<div className="card text-white bg-primary">
<div className="card-body">
<h5 className="card-title">Заказы</h5>
<p className="card-text display-4">{orders.length}</p>
</div>
</div>
</div>
<div className="col-md-4 mb-4">
<div className="card text-white bg-success">
<div className="card-body">
<h5 className="card-title">Машины</h5>
<p className="card-text display-4">{vehicles.length}</p>
</div>
</div>
</div>
<div className="col-md-4 mb-4">
<div className="card text-white bg-info">
<div className="card-body">
<h5 className="card-title">Записи путевых листов</h5>
<p className="card-text display-4">{waybillEntries.length}</p>
</div>
</div>
</div>
</div>
<div className="card">
<div className="card-header">
<h5 className="mb-0">Записи путевых листов</h5>
</div>
<div className="card-body">
{waybillEntries.length === 0 ? (
<p className="text-muted">Записи не найдены</p>
) : (
<div className="table-responsive">
<table className="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Машина</th>
<th>Заказ</th>
<th>Дата</th>
<th>Время</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
{waybillEntries.map(entry => (
<tr key={entry.id}>
<td>{entry.id}</td>
<td>
{entry.vehicleId} -
{vehicles.find(v => v.id === entry.vehicleId)?.driverName}
</td>
<td>
{entry.orderId} -
{orders.find(o => o.id === entry.orderId)?.clientName}
</td>
<td>{entry.date}</td>
<td>{entry.startTime} - {entry.endTime}</td>
<td>
<button
className="btn btn-sm btn-danger"
onClick={() => handleDeleteEntry(entry.id!)}
>
Удалить
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
</div>
);
};
export default AdminPage;

181
src/pages/LoginPage.tsx Normal file
View File

@@ -0,0 +1,181 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { authApi } from '../services/api';
interface LoginPageProps {
onLogin: () => void;
}
const LoginPage: React.FC<LoginPageProps> = ({ onLogin }) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const navigate = useNavigate();
useEffect(() => {
const savedError = localStorage.getItem('login_error');
if (savedError) {
setError(savedError);
localStorage.removeItem('login_error');
}
if (document.cookie.includes('auth_token')) {
onLogin();
navigate('/orders');
}
}, [navigate, onLogin]);
const handleLoginClick = async () => {
if (!username.trim() || !password.trim()) {
const errorMsg = 'Введите логин и пароль';
setError(errorMsg);
localStorage.setItem('login_error', errorMsg);
return;
}
setLoading(true);
setError('');
localStorage.removeItem('login_error');
try {
console.log('Отправка запроса на вход...');
const response = await authApi.login({ username, password });
console.log('Ответ от сервера:', response);
if (response.data.success && response.data.data?.token) {
const token = response.data.data.token;
document.cookie = `auth_token=${token}; path=/; max-age=86400`;
localStorage.removeItem('login_error');
console.log('Вход успешен, перенаправление...');
onLogin();
navigate('/orders');
} else {
const errorMsg = 'Ошибка сервера при авторизации';
setError(errorMsg);
localStorage.setItem('login_error', errorMsg);
}
} catch (err: any) {
console.error('Ошибка входа:', err);
let errorMsg = '';
if (err.response?.status === 401) {
errorMsg = 'Неверный логин или пароль';
} else if (err.response?.data?.message) {
errorMsg = err.response.data.message;
} else if (err.message && err.message.includes('Network Error')) {
errorMsg = 'Ошибка соединения с сервером. Проверьте подключение.';
} else {
errorMsg = 'Ошибка при входе в систему';
}
setError(errorMsg);
localStorage.setItem('login_error', errorMsg);
} finally {
setLoading(false);
}
};
const handleClearError = () => {
setError('');
localStorage.removeItem('login_error');
};
return (
<div className="container mt-5">
<div className="row justify-content-center">
<div className="col-md-6">
<div className="card">
<div className="card-header">
<h4 className="mb-0">Вход в систему</h4>
</div>
<div className="card-body">
{error && (
<div className="alert alert-danger alert-dismissible fade show">
<div className="d-flex justify-content-between align-items-center">
<span>
<strong>Ошибка:</strong> {error}
</span>
<button
type="button"
className="btn-close"
onClick={handleClearError}
aria-label="Закрыть"
></button>
</div>
</div>
)}
<div className="mb-3">
<label htmlFor="username" className="form-label">Логин:</label>
<input
type="text"
className="form-control"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={loading}
autoFocus
onKeyPress={(e) => {
if (e.key === 'Enter' && !loading) {
handleLoginClick();
}
}}
/>
</div>
<div className="mb-3">
<label htmlFor="password" className="form-label">Пароль:</label>
<input
type="password"
className="form-control"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
onKeyPress={(e) => {
if (e.key === 'Enter' && !loading) {
handleLoginClick();
}
}}
/>
</div>
<button
type="button"
className="btn btn-primary w-100"
onClick={handleLoginClick}
disabled={loading || !username || !password}
>
{loading ? (
<>
<span className="spinner-border spinner-border-sm me-2" role="status"></span>
Проверка...
</>
) : (
'Войти в систему'
)}
</button>
<div className="mt-3 text-center">
<small className="text-muted">
Тестовые данные:<br/>
Логин: <strong>admin</strong> Пароль: <strong>admin123</strong> (админ)<br/>
Логин: <strong>user</strong> Пароль: <strong>user123</strong> (пользователь)
</small>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default LoginPage;

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Order } from '../types'; import { Order, OrderFiltersType } from '../types';
import { ordersApi } from '../services/api'; import { ordersApi } from '../services/api';
import OrderFilters from '../components/OrderFilters'; import OrderFilters from '../components/OrderFilters';
import Loading from '../components/Loading'; import Loading from '../components/Loading';
@@ -7,26 +7,47 @@ import ErrorAlert from '../components/ErrorAlert';
const OrdersPage: React.FC = () => { const OrdersPage: React.FC = () => {
const [orders, setOrders] = useState<Order[]>([]); const [orders, setOrders] = useState<Order[]>([]);
const [filteredOrders, setFilteredOrders] = useState<Order[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [totalCount, setTotalCount] = useState(0);
const [filters, setFilters] = useState({ const [filters, setFilters] = useState<OrderFiltersType>({
clientName: '', clientName: '',
minCost: '', minCost: undefined,
maxCost: '', maxCost: undefined,
orderDate: '', orderDate: '',
status: '' status: '',
page: 1,
pageSize: 50
}); });
// Загрузка данных const loadOrders = async (currentFilters: OrderFiltersType) => {
const loadOrders = async () => {
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
const response = await ordersApi.getOrders();
setOrders(response.data); const apiFilters: OrderFiltersType = {
setFilteredOrders(response.data); clientName: currentFilters.clientName || undefined,
minCost: currentFilters.minCost ? parseFloat(currentFilters.minCost.toString()) : undefined,
maxCost: currentFilters.maxCost ? parseFloat(currentFilters.maxCost.toString()) : undefined,
orderDate: currentFilters.orderDate || undefined,
status: currentFilters.status || undefined,
page: currentFilters.page,
pageSize: currentFilters.pageSize
};
Object.keys(apiFilters).forEach(key => {
if (apiFilters[key as keyof OrderFiltersType] === undefined || apiFilters[key as keyof OrderFiltersType] === '') {
delete apiFilters[key as keyof OrderFiltersType];
}
});
const response = await ordersApi.getOrders(apiFilters);
let count = (response.data as any).data.items.length;
setOrders((response.data as any).data.items);
setTotalCount(count);
} catch (err) { } catch (err) {
setError('Не удалось загрузить список заказов'); setError('Не удалось загрузить список заказов');
console.error('Error loading orders:', err); console.error('Error loading orders:', err);
@@ -35,29 +56,24 @@ const OrdersPage: React.FC = () => {
} }
}; };
// Функция изменения статуса заказа
const updateOrderStatus = async (orderId: number, newStatus: Order['status']) => { const updateOrderStatus = async (orderId: number, newStatus: Order['status']) => {
try { try {
// Находим заказ для обновления
const orderToUpdate = orders.find(order => order.id === orderId); const orderToUpdate = orders.find(order => order.id === orderId);
if (!orderToUpdate) return; if (!orderToUpdate) return;
// Создаем обновленный заказ
const updatedOrder = { const updatedOrder = {
...orderToUpdate, ...orderToUpdate,
status: newStatus status: newStatus
}; };
// Отправляем запрос на сервер
await ordersApi.updateOrder(orderId, updatedOrder); await ordersApi.updateOrder(orderId, updatedOrder);
// Обновляем локальное состояние setOrders(prevOrders =>
setOrders(prevOrders => prevOrders.map(order =>
prevOrders.map(order =>
order.id === orderId ? updatedOrder : order order.id === orderId ? updatedOrder : order
) )
); );
console.log(`Статус заказа ${orderId} изменен на: ${newStatus}`); console.log(`Статус заказа ${orderId} изменен на: ${newStatus}`);
} catch (err) { } catch (err) {
setError('Не удалось обновить статус заказа'); setError('Не удалось обновить статус заказа');
@@ -66,63 +82,33 @@ const OrdersPage: React.FC = () => {
}; };
useEffect(() => { useEffect(() => {
loadOrders(); loadOrders(filters);
}, []); }, []);
// Применение фильтров const handleApplyFilters = () => {
useEffect(() => { loadOrders(filters);
let result = orders; };
if (filters.clientName) {
result = result.filter(order =>
order.clientName.toLowerCase().includes(filters.clientName.toLowerCase())
);
}
if (filters.minCost) {
result = result.filter(order =>
order.orderCost >= parseFloat(filters.minCost)
);
}
if (filters.maxCost) {
result = result.filter(order =>
order.orderCost <= parseFloat(filters.maxCost)
);
}
if (filters.orderDate) {
result = result.filter(order =>
order.orderDate === filters.orderDate
);
}
if (filters.status) {
result = result.filter(order =>
order.status === filters.status
);
}
setFilteredOrders(result);
}, [filters, orders]);
if (loading) return <Loading />; if (loading) return <Loading />;
if (error) return <ErrorAlert message={error} onRetry={loadOrders} />; if (error) return <ErrorAlert message={error} onRetry={() => loadOrders(filters)} />;
return ( return (
<div className="container mt-4"> <div className="container mt-4">
<h1 className="mb-4">Список заказов</h1> <h1 className="mb-4">Список заказов</h1>
<OrderFilters filters={filters} onFiltersChange={setFilters} /> <OrderFilters
filters={filters}
onFiltersChange={setFilters}
onApplyFilters={handleApplyFilters}
/>
<div className="card"> <div className="card">
<div className="card-header"> <div className="card-header">
<h5 className="mb-0"> <h5 className="mb-0">
Найдено заказов: {filteredOrders.length} Найдено заказов: {totalCount}
</h5> </h5>
</div> </div>
<div className="card-body"> <div className="card-body">
{filteredOrders.length === 0 ? ( {orders.length === 0 ? (
<p className="text-muted">Заказы не найдены</p> <p className="text-muted">Заказы не найдены</p>
) : ( ) : (
<div className="table-responsive"> <div className="table-responsive">
@@ -139,26 +125,25 @@ const OrdersPage: React.FC = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{filteredOrders.map(order => ( {orders.map(order => (
<tr key={order.id}> <tr key={order.id}>
<td>{order.id}</td> <td>{order.id}</td>
<td>{order.clientName}</td> <td>{order.clientName}</td>
<td>{order.orderCost.toLocaleString()} руб.</td> <td>{order.orderCost.toLocaleString()} руб.</td>
<td>{new Date(order.orderDate).toLocaleDateString()}</td> <td>{new Date(order.orderDate).toLocaleDateString()}</td>
<td> <td>
<span className={`badge ${ <span className={`badge ${order.status === 'completed' ? 'bg-success' :
order.status === 'completed' ? 'bg-success' :
order.status === 'in_progress' ? 'bg-primary' : order.status === 'in_progress' ? 'bg-primary' :
order.status === 'pending' ? 'bg-warning' : 'bg-danger' order.status === 'pending' ? 'bg-warning' : 'bg-danger'
}`}> }`}>
{order.status === 'pending' ? 'Ожидает' : {order.status === 'pending' ? 'Ожидает' :
order.status === 'in_progress' ? 'В процессе' : order.status === 'in_progress' ? 'В процессе' :
order.status === 'completed' ? 'Завершен' : 'Отменен'} order.status === 'completed' ? 'Завершен' : 'Отменен'}
</span> </span>
</td> </td>
<td>{order.address}</td> <td>{order.address}</td>
<td> <td>
<select <select
className="form-select form-select-sm" className="form-select form-select-sm"
value={order.status} value={order.status}
onChange={(e) => updateOrderStatus(order.id, e.target.value as Order['status'])} onChange={(e) => updateOrderStatus(order.id, e.target.value as Order['status'])}

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom'; import { useParams, Link } from 'react-router-dom';
import { Vehicle, Order, WaybillEntry } from '../types'; import { Vehicle, Order } from '../types';
import { vehiclesApi, ordersApi, waybillApi } from '../services/api'; import { vehiclesApi, ordersApi, waybillApi } from '../services/api';
import WaybillWidget from '../components/WaybillWidget'; import WaybillWidget from '../components/WaybillWidget';
import Loading from '../components/Loading'; import Loading from '../components/Loading';
@@ -16,31 +16,38 @@ const VehicleDetailPage: React.FC = () => {
new Date().toISOString().split('T')[0] new Date().toISOString().split('T')[0]
); );
const loadVehicleData = async () => { const loadVehicleData = async () => {
if (!id) return; if (!id) return;
try {
setLoading(true);
setError(null);
const vehicleId = parseInt(id);
try {
setLoading(true); const entriesData = (await waybillApi.getEntries() as any).data.data;
setError(null); const ordersData = (await ordersApi.getOrders() as any).data.data.items;
// Загружаем данные машины const result = ordersData.filter((order: any) => {
const vehicleResponse = await vehiclesApi.getVehicle(parseInt(id)); const entryExists = entriesData.some((entry: any) =>
setVehicle(vehicleResponse.data); entry.orderId === order.id && entry.vehicleId === vehicleId
// Загружаем историю выполненных заказов
const ordersResponse = await ordersApi.getOrders();
const completed = ordersResponse.data.filter(
order => order.status === 'completed'
); );
setCompletedOrders(completed); return order.status === 'completed' && entryExists;
});
} catch (err) {
setError('Не удалось загрузить данные машины'); setCompletedOrders(result);
console.error('Error loading vehicle data:', err);
} finally { const vehicleData = await vehiclesApi.getVehicle(vehicleId);
setLoading(false); setVehicle((vehicleData.data as any).data);
}
}; } catch (err) {
setError('Не удалось загрузить данные машины');
console.error('Error loading vehicle data:', err);
} finally {
setLoading(false);
}
};
useEffect(() => { useEffect(() => {
loadVehicleData(); loadVehicleData();
@@ -88,7 +95,7 @@ const VehicleDetailPage: React.FC = () => {
<h6 className="mb-1">{order.clientName}</h6> <h6 className="mb-1">{order.clientName}</h6>
<p className="mb-1 small">{order.address}</p> <p className="mb-1 small">{order.address}</p>
<small className="text-muted"> <small className="text-muted">
{new Date(order.orderDate).toLocaleDateString()} - {' '} {new Date(order.orderDate).toLocaleDateString()} -
{order.orderCost.toLocaleString()} руб. {order.orderCost.toLocaleString()} руб.
</small> </small>
</div> </div>
@@ -117,8 +124,8 @@ const VehicleDetailPage: React.FC = () => {
</div> </div>
</div> </div>
<div className="card-body"> <div className="card-body">
<WaybillWidget <WaybillWidget
vehicleId={vehicle.id} vehicleId={vehicle.id}
date={selectedDate} date={selectedDate}
/> />
</div> </div>

View File

@@ -1,30 +1,52 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Vehicle } from '../types'; import { Vehicle, VehicleFiltersType } from '../types';
import { vehiclesApi } from '../services/api'; import { vehiclesApi } from '../services/api';
import VehicleFilters from '../components/VehicleFilters'; import VehicleFiltersComponent from '../components/VehicleFilters';
import Loading from '../components/Loading'; import Loading from '../components/Loading';
import ErrorAlert from '../components/ErrorAlert'; import ErrorAlert from '../components/ErrorAlert';
const VehiclesPage: React.FC = () => { const VehiclesPage: React.FC = () => {
const [vehicles, setVehicles] = useState<Vehicle[]>([]); const [vehicles, setVehicles] = useState<Vehicle[]>([]);
const [filteredVehicles, setFilteredVehicles] = useState<Vehicle[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [totalCount, setTotalCount] = useState(0);
const [filters, setFilters] = useState({
const [filters, setFilters] = useState<VehicleFiltersType>({
driverName: '', driverName: '',
vehicleType: '', vehicleType: '',
licensePlate: '' licensePlate: '',
page: 1,
pageSize: 50
}); });
const loadVehicles = async () => {
const loadVehicles = async (currentFilters: VehicleFiltersType) => {
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
const response = await vehiclesApi.getVehicles();
setVehicles(response.data);
setFilteredVehicles(response.data); const apiFilters: VehicleFiltersType = {
driverName: currentFilters.driverName || undefined,
vehicleType: currentFilters.vehicleType || undefined,
licensePlate: currentFilters.licensePlate || undefined,
page: currentFilters.page,
pageSize: currentFilters.pageSize
};
Object.keys(apiFilters).forEach(key => {
if (apiFilters[key as keyof VehicleFiltersType] === undefined || apiFilters[key as keyof VehicleFiltersType] === '') {
delete apiFilters[key as keyof VehicleFiltersType];
}
});
const response = await vehiclesApi.getVehicles(apiFilters);
let count = (response.data as any).data.items.length;
setVehicles((response.data as any).data.items);
setTotalCount(count);
} catch (err) { } catch (err) {
setError('Не удалось загрузить список машин'); setError('Не удалось загрузить список машин');
console.error('Error loading vehicles:', err); console.error('Error loading vehicles:', err);
@@ -33,55 +55,41 @@ const VehiclesPage: React.FC = () => {
} }
}; };
useEffect(() => { useEffect(() => {
loadVehicles(); loadVehicles(filters);
}, []); }, []);
useEffect(() => {
let result = vehicles;
if (filters.driverName) { const handleApplyFilters = () => {
result = result.filter(vehicle => loadVehicles(filters);
vehicle.driverName.toLowerCase().includes(filters.driverName.toLowerCase()) };
);
}
if (filters.vehicleType) {
result = result.filter(vehicle =>
vehicle.vehicleType.toLowerCase().includes(filters.vehicleType.toLowerCase())
);
}
if (filters.licensePlate) {
result = result.filter(vehicle =>
vehicle.licensePlate.toLowerCase().includes(filters.licensePlate.toLowerCase())
);
}
setFilteredVehicles(result);
}, [filters, vehicles]);
if (loading) return <Loading />; if (loading) return <Loading />;
if (error) return <ErrorAlert message={error} onRetry={loadVehicles} />; if (error) return <ErrorAlert message={error} onRetry={() => loadVehicles(filters)} />;
return ( return (
<div className="container mt-4"> <div className="container mt-4">
<h1 className="mb-4">Список машин</h1> <h1 className="mb-4">Список машин</h1>
<VehicleFilters filters={filters} onFiltersChange={setFilters} /> <VehicleFiltersComponent
filters={filters}
onFiltersChange={setFilters}
onApplyFilters={handleApplyFilters}
/>
<div className="card"> <div className="card">
<div className="card-header"> <div className="card-header">
<h5 className="mb-0"> <h5 className="mb-0">
Найдено машин: {filteredVehicles.length} Найдено машин: {totalCount}
</h5> </h5>
</div> </div>
<div className="card-body"> <div className="card-body">
{filteredVehicles.length === 0 ? ( {vehicles.length === 0 ? (
<p className="text-muted">Машины не найдены</p> <p className="text-muted">Машины не найдены</p>
) : ( ) : (
<div className="row"> <div className="row">
{filteredVehicles.map(vehicle => ( {vehicles.map(vehicle => (
<div key={vehicle.id} className="col-md-6 mb-3"> <div key={vehicle.id} className="col-md-6 mb-3">
<div className="card"> <div className="card">
<div className="card-body"> <div className="card-body">

View File

@@ -1,34 +1,68 @@
import axios from 'axios'; import axios from 'axios';
import { Order, Vehicle, WaybillEntry } from '../types'; import { Order, Vehicle, WaybillEntry, OrderFiltersType, VehicleFiltersType } from '../types';
const API_BASE = 'http://localhost:3001'; const API_BASE = 'http://localhost:5155/api';
const api = axios.create({ const api = axios.create({
baseURL: API_BASE, baseURL: API_BASE,
withCredentials: true,
}); });
// Сервис для работы с заказами const getCookie = (name: string): string | null => {
// Сервис для работы с заказами const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) {
return parts.pop()?.split(';').shift() || null;
}
return null;
};
api.interceptors.request.use((config) => {
const token = getCookie('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export const ordersApi = { export const ordersApi = {
getOrders: () => api.get<Order[]>('/orders'), getOrders: (filters?: OrderFiltersType) => api.get<Order[]>('/orders', {params: filters}),
getOrder: (id: number) => api.get<Order>(`/orders/${id}`), getOrder: (id: number) => api.get<Order>(`/orders/${id}`),
updateOrder: (id: number, order: Order) => api.put<Order>(`/orders/${id}`, order), updateOrder: (id: number, order: Order) => api.put<Order>(`/orders/${id}`, order),
}; };
// Сервис для работы с машинами
export const vehiclesApi = { export const vehiclesApi = {
getVehicles: () => api.get<Vehicle[]>('/vehicles'), getVehicles: (filters?: VehicleFiltersType) => api.get<Vehicle[]>('/vehicles', {params: filters}),
getVehicle: (id: number) => api.get<Vehicle>(`/vehicles/${id}`), getVehicle: (id: number) => api.get<Vehicle>(`/vehicles/${id}`),
}; };
// Сервис для работы с путевыми листами
export const waybillApi = { export const waybillApi = {
getEntries: () => api.get<WaybillEntry[]>('/waybillEntries'), getEntries: () =>
api.get<WaybillEntry[]>('/waybillentries'),
createEntry: (entry: Omit<WaybillEntry, 'id'>) => createEntry: (entry: Omit<WaybillEntry, 'id'>) =>
api.post<WaybillEntry>('/waybillEntries', entry), api.post<WaybillEntry>('/waybillentries', entry),
updateEntry: (id: number, entry: Partial<WaybillEntry>) => updateEntry: (id: number, entry: Partial<WaybillEntry>) => api.put<WaybillEntry>(`/waybillEntries/${id}`, entry),
api.put<WaybillEntry>(`/waybillEntries/${id}`, entry),
deleteEntry: (id: number) => api.delete(`/waybillEntries/${id}`), deleteEntry: (id: number) => api.delete(`/waybillEntries/${id}`),
}; };
export const authApi = {
login: (credentials: { username: string; password: string }) =>
api.post<{
success: string; data: { token: string; username: string; role: string; expires: string }
}>('/auth/login', credentials),
logout: () => api.post('/auth/logout'),
};
export default api; export default api;

View File

@@ -1,4 +1,4 @@
// Основные типы данных
export interface Order { export interface Order {
id: number; id: number;
clientName: string; clientName: string;
@@ -9,6 +9,24 @@ export interface Order {
description: string; description: string;
} }
export interface OrderFiltersType {
clientName?: string;
minCost?: number;
maxCost?: number;
orderDate?: string;
status?: string;
page?: number;
pageSize?: number;
}
export interface VehicleFiltersType {
driverName?: string;
vehicleType?: string;
licensePlate?: string;
page?: number;
pageSize?: number;
}
export interface Vehicle { export interface Vehicle {
id: number; id: number;
driverName: string; driverName: string;