WEB_logistics/src/pages/DeliveryOrderDetails.jsx
2024-10-08 11:18:39 +05:00

344 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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='&copy; <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;