344 lines
11 KiB
JavaScript
344 lines
11 KiB
JavaScript
import React, { useState, useEffect } from "react";
|
||
import {
|
||
getDeliveryAccessories,
|
||
getDeliveryOrderDetails,
|
||
updateDeliveryOrderRoute,
|
||
getTotalOrder,
|
||
getDeliveryOrder,
|
||
} from "../api.jsx";
|
||
import { useParams, useNavigate } from "react-router-dom";
|
||
import {
|
||
MapContainer,
|
||
TileLayer,
|
||
Marker,
|
||
Popup,
|
||
Polyline,
|
||
} from "react-leaflet";
|
||
import { DivIcon } from "leaflet";
|
||
import polyline from "@mapbox/polyline";
|
||
import { getCoordinates } from "../geocoder.jsx";
|
||
import "./DeliveryOrderDetails.css";
|
||
|
||
const DELIVERY_CITY = {
|
||
name: "Челябинск",
|
||
latitude: 55.159902,
|
||
longitude: 61.402554,
|
||
};
|
||
|
||
const DeliveryOrderDetails = () => {
|
||
const { id: deliveryOrderId } = useParams();
|
||
const [deliveryAccessories, setDeliveryAccessories] = useState([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [coordinates, setCoordinates] = useState([]);
|
||
const [route, setRoute] = useState([]);
|
||
const [totalCost, setTotalCost] = useState(0);
|
||
const [truckCount, setTruckCount] = useState(0);
|
||
const [totalWeight, setTotalWeight] = useState(0);
|
||
const [truckName, setTruckName] = useState("");
|
||
const [truckCapacity, setTruckCapacity] = useState("");
|
||
const [totalOrder, setTotalOrder] = useState(null);
|
||
const [deliveryOrder, setDeliveryOrder] = useState(null);
|
||
const [orderDuration, setOrderDuration] = useState(null);
|
||
const navigate = useNavigate();
|
||
|
||
useEffect(() => {
|
||
fetchDeliveryOrder();
|
||
}, [deliveryOrderId]);
|
||
|
||
const formatDuration = (minutes) => {
|
||
const days = Math.floor(minutes / (24 * 60));
|
||
const hours = Math.floor((minutes % (24 * 60)) / 60);
|
||
const remainingMinutes = Math.floor(minutes % 60);
|
||
|
||
return {
|
||
days,
|
||
hours,
|
||
minutes: remainingMinutes,
|
||
};
|
||
};
|
||
|
||
const fetchDeliveryOrder = async () => {
|
||
try {
|
||
const order = await getDeliveryOrder(deliveryOrderId);
|
||
setDeliveryOrder(order);
|
||
if (order) {
|
||
fetchTotalOrder(order.total_order_id);
|
||
}
|
||
} catch (error) {
|
||
console.error("Ошибка при загрузке deliveryOrder:", error);
|
||
}
|
||
};
|
||
|
||
const fetchTotalOrder = async (orderId) => {
|
||
try {
|
||
const order = await getTotalOrder(orderId);
|
||
setTotalOrder(order);
|
||
if (order) {
|
||
fetchDeliveryAccessories(order);
|
||
}
|
||
} catch (error) {
|
||
if (error.response && error.response.status === 401) {
|
||
navigate("/login");
|
||
}
|
||
console.error("Ошибка при загрузке totalOrder:", error);
|
||
}
|
||
};
|
||
|
||
const fetchDeliveryAccessories = async (totalOrder) => {
|
||
try {
|
||
const deliveryOrderDetails = await getDeliveryOrderDetails(
|
||
deliveryOrderId
|
||
);
|
||
setTotalCost(Math.round(deliveryOrderDetails.price));
|
||
setTruckCount(deliveryOrderDetails.count_trucks);
|
||
setTruckName(deliveryOrderDetails.truck_name);
|
||
setTruckCapacity(deliveryOrderDetails.truck_capacity);
|
||
|
||
const accessories = await getDeliveryAccessories(deliveryOrderId);
|
||
setDeliveryAccessories(accessories);
|
||
|
||
if (totalOrder) {
|
||
const totalWeightCalculated = accessories.reduce((acc, accessory) => {
|
||
return (
|
||
acc +
|
||
Math.round(
|
||
(accessory.accessory_weight / 100) * totalOrder.count_robots
|
||
)
|
||
);
|
||
}, 0);
|
||
setTotalWeight(totalWeightCalculated);
|
||
}
|
||
|
||
const coords = await Promise.all(
|
||
accessories.map(async (accessory) => {
|
||
if (accessory.latitude && accessory.longitude) {
|
||
return {
|
||
city: accessory.city_name,
|
||
latitude: accessory.latitude,
|
||
longitude: accessory.longitude,
|
||
accessory_name:
|
||
accessory.accessory_name +
|
||
Math.round(
|
||
(accessory.accessory_volume * accessory.count) / 100
|
||
) +
|
||
"шт.",
|
||
};
|
||
} else {
|
||
const coords = await getCoordinates(accessory.city_name);
|
||
return {
|
||
city: accessory.city_name,
|
||
accessory_name:
|
||
accessory.accessory_name +
|
||
": " +
|
||
Math.round(
|
||
(accessory.accessory_volume * accessory.count) / 100
|
||
) +
|
||
"шт.",
|
||
...coords,
|
||
};
|
||
}
|
||
})
|
||
);
|
||
setCoordinates(coords);
|
||
|
||
const fullCoordinates = [...coords, DELIVERY_CITY];
|
||
|
||
if (
|
||
!deliveryOrderDetails.route ||
|
||
deliveryOrderDetails.estimated_route_time_in_minutes === null
|
||
) {
|
||
if (fullCoordinates.length > 1) {
|
||
const waypoints = fullCoordinates
|
||
.map(({ longitude, latitude }) => `${longitude},${latitude}`)
|
||
.join(";");
|
||
|
||
const routeUrl = `https://router.project-osrm.org/route/v1/driving/${waypoints}?overview=full`;
|
||
|
||
const response = await fetch(routeUrl);
|
||
const data = await response.json();
|
||
|
||
if (data.routes && data.routes.length > 0) {
|
||
const geometry = data.routes[0].geometry;
|
||
const decodedRoute = polyline.decode(geometry);
|
||
setRoute(decodedRoute);
|
||
const duration = data.routes[0].duration;
|
||
setOrderDuration(duration);
|
||
|
||
await updateDeliveryOrderRoute(
|
||
deliveryOrderId,
|
||
duration / 60,
|
||
decodedRoute
|
||
);
|
||
}
|
||
}
|
||
} else {
|
||
const decodedRoute = deliveryOrderDetails.route;
|
||
setOrderDuration(deliveryOrderDetails.estimated_route_time_in_minutes);
|
||
setRoute(decodedRoute);
|
||
}
|
||
} catch (error) {
|
||
if (error.response && error.response.status === 401) {
|
||
navigate("/login");
|
||
}
|
||
console.error("Ошибка при загрузке доставок:", error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleBack = () => {
|
||
navigate("/");
|
||
};
|
||
|
||
return (
|
||
<div className="delivery-order-details">
|
||
{loading ? (
|
||
<div className="spinner-border" role="status">
|
||
<span className="visually-hidden"></span>
|
||
</div>
|
||
) : (
|
||
<div className="content-container">
|
||
<button onClick={handleBack} className="btn btn-secondary mb-4">
|
||
Назад
|
||
</button>
|
||
|
||
<h2 style={{ textAlign: "center" }}>Общая информация</h2>
|
||
<table className="table table-bordered mb-5">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">Общая стоимость</th>
|
||
<th scope="col">Тип транспортного средства</th>
|
||
<th scope="col">Количество транспортных средств</th>
|
||
<th scope="col">Прогнозируемое время этапа</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>{totalCost} ₽</td>
|
||
<td>
|
||
{truckName}
|
||
<br />
|
||
Грузоподъемность: {truckCapacity}кг
|
||
</td>
|
||
<td>{truckCount}</td>
|
||
<td>
|
||
{deliveryOrder?.estimated_route_time_in_minutes && (
|
||
<>
|
||
{(() => {
|
||
const { days, hours, minutes } = formatDuration(
|
||
deliveryOrder?.estimated_route_time_in_minutes
|
||
);
|
||
return days > 0
|
||
? `${days} дн. ${hours} ч.`
|
||
: `${hours} ч.`;
|
||
})()}
|
||
</>
|
||
)}
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h2 style={{ textAlign: "center" }}>Маршрут</h2>
|
||
|
||
<table className="table table-bordered table-striped mb-5">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">Город</th>
|
||
<th scope="col">Комплектующие</th>
|
||
<th scope="col">Объем, шт.</th>
|
||
<th scope="col">Вес, кг</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{deliveryAccessories.map((accessory) => (
|
||
<tr key={accessory.id}>
|
||
<td>{accessory.city_name}</td>
|
||
<td>{accessory.accessory_name}</td>
|
||
<td>
|
||
{Math.round(
|
||
(accessory.accessory_volume / 100) *
|
||
totalOrder.count_robots
|
||
)}
|
||
</td>
|
||
<td>
|
||
{Math.round(
|
||
(accessory.accessory_weight / 100) *
|
||
totalOrder.count_robots
|
||
)}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
<tr>
|
||
<td>{DELIVERY_CITY.name}</td>
|
||
<td>Конечный пункт</td>
|
||
<td>Итого:</td>
|
||
<td>{totalWeight}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h2 style={{ textAlign: "center" }}>Схема маршрута</h2>
|
||
|
||
{coordinates.length > 0 && (
|
||
<div className="map-container">
|
||
<MapContainer
|
||
center={[coordinates[0].latitude, coordinates[0].longitude]}
|
||
zoom={5}
|
||
style={{ height: "400px", width: "100%" }}
|
||
>
|
||
<TileLayer
|
||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||
/>
|
||
{coordinates.map((coord, index) => (
|
||
<Marker
|
||
key={index}
|
||
position={[coord.latitude, coord.longitude]}
|
||
icon={
|
||
new DivIcon({
|
||
className: "custom-marker",
|
||
html: `<div class='marker-number'>${index + 1}</div>`,
|
||
iconSize: [30, 30],
|
||
})
|
||
}
|
||
>
|
||
<Popup>
|
||
{coord.city}
|
||
<br />
|
||
{coord.accessory_name}
|
||
</Popup>
|
||
</Marker>
|
||
))}
|
||
<Marker
|
||
position={[DELIVERY_CITY.latitude, DELIVERY_CITY.longitude]}
|
||
icon={
|
||
new DivIcon({
|
||
className: "custom-marker",
|
||
html: `<div class='marker-number'>${
|
||
deliveryAccessories.length + 1
|
||
}</div>`,
|
||
iconSize: [30, 30],
|
||
})
|
||
}
|
||
>
|
||
<Popup>
|
||
{DELIVERY_CITY.name}
|
||
<br />
|
||
Конечный пункт
|
||
</Popup>
|
||
</Marker>
|
||
{route.length > 0 && (
|
||
<Polyline positions={route} color="blue" />
|
||
)}
|
||
</MapContainer>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default DeliveryOrderDetails;
|