201 lines
6.3 KiB
JavaScript
201 lines
6.3 KiB
JavaScript
import {
|
|
getDeliveryOrders,
|
|
updateDeliveryOrderRoute,
|
|
getDeliveryOrderDetails,
|
|
getDeliveryAccessories,
|
|
} from "../api.jsx";
|
|
import { getCoordinates } from "../geocoder.jsx";
|
|
import React, { useState, useEffect } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import polyline from "@mapbox/polyline";
|
|
import "./DeliveryOrdersList.css";
|
|
|
|
const DELIVERY_CITY = {
|
|
name: "Челябинск",
|
|
latitude: 55.159902,
|
|
longitude: 61.402554,
|
|
};
|
|
|
|
const DeliveryOrdersList = ({
|
|
totalOrderId,
|
|
onSubOrderClick,
|
|
setDeliveryOrdersCount,
|
|
setTotalEstimatedTime,
|
|
}) => {
|
|
const [deliveryOrders, setDeliveryOrders] = useState([]);
|
|
const [loadingDeliveryOrders, setLoadingDeliveryOrders] = useState(true);
|
|
const [calculatingRoutes, setCalculatingRoutes] = useState([]);
|
|
const [hoveredOrderId, setHoveredOrderId] = useState(null);
|
|
const navigate = useNavigate();
|
|
|
|
useEffect(() => {
|
|
fetchDeliveryOrders();
|
|
}, [totalOrderId]);
|
|
|
|
const formatPrice = (price) => {
|
|
if (!price) {
|
|
return "не указана";
|
|
}
|
|
return `${price.toFixed(2)} ₽`;
|
|
};
|
|
|
|
const fetchDeliveryOrders = async () => {
|
|
try {
|
|
const orders = await getDeliveryOrders(totalOrderId);
|
|
setDeliveryOrdersCount(orders.length);
|
|
setDeliveryOrders(orders);
|
|
|
|
const totalTime = orders.reduce((sum, order) => {
|
|
return sum + (order.estimated_route_time_in_minutes || 0);
|
|
}, 0);
|
|
setTotalEstimatedTime(totalTime);
|
|
} catch (error) {
|
|
if (error.response && error.response.status === 401) {
|
|
navigate("/login");
|
|
}
|
|
console.error("Ошибка при загрузке подзаказов:", error);
|
|
} finally {
|
|
setLoadingDeliveryOrders(false);
|
|
}
|
|
};
|
|
|
|
const formatTimeInHours = (minutes) => {
|
|
if (!minutes || minutes === 0) {
|
|
return "ещё не рассчитывалось";
|
|
}
|
|
return (minutes / 60).toFixed(2) + " час.";
|
|
};
|
|
|
|
const calculateRoutes = async () => {
|
|
const ordersToCalculate = deliveryOrders.filter(
|
|
(order) => !order.estimated_route_time_in_minutes
|
|
);
|
|
|
|
setCalculatingRoutes(ordersToCalculate.map((order) => order.id));
|
|
|
|
await Promise.all(
|
|
ordersToCalculate.map(async (order) => {
|
|
try {
|
|
const deliveryOrderDetails = await getDeliveryOrderDetails(order.id);
|
|
const accessories = await getDeliveryAccessories(
|
|
deliveryOrderDetails.id
|
|
);
|
|
|
|
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,
|
|
};
|
|
}
|
|
})
|
|
);
|
|
|
|
const fullCoordinates = [...coords, DELIVERY_CITY];
|
|
|
|
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);
|
|
const duration = data.routes[0].duration;
|
|
|
|
await updateDeliveryOrderRoute(
|
|
order.id,
|
|
duration / 60,
|
|
decodedRoute
|
|
);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Ошибка при расчете маршрута для подзаказа:", error);
|
|
}
|
|
})
|
|
);
|
|
|
|
await fetchDeliveryOrders();
|
|
setCalculatingRoutes([]);
|
|
};
|
|
|
|
const ordersToCalculate = deliveryOrders.filter(
|
|
(order) => !order.estimated_route_time_in_minutes
|
|
);
|
|
|
|
return (
|
|
<div>
|
|
{loadingDeliveryOrders ? (
|
|
<div className="spinner-border" role="status">
|
|
<span className="visually-hidden"></span>
|
|
</div>
|
|
) : (
|
|
<>
|
|
{ordersToCalculate.length > 0 && (
|
|
<button onClick={calculateRoutes} className="btn btn-primary mb-3">
|
|
Рассчитать все
|
|
</button>
|
|
)}
|
|
{deliveryOrders.map((order, index) => (
|
|
<div
|
|
key={order.id}
|
|
className="card mb-1"
|
|
onClick={() => onSubOrderClick(order.id)}
|
|
onMouseEnter={() => setHoveredOrderId(order.id)}
|
|
onMouseLeave={() => setHoveredOrderId(null)}
|
|
>
|
|
<div className="card-body">
|
|
<p>Этап №{index + 1}</p>
|
|
<p>
|
|
Прогнозируемое время этапа:{" "}
|
|
{formatTimeInHours(order.estimated_route_time_in_minutes)}
|
|
</p>
|
|
<p>Стоимость: {formatPrice(order.price)}</p>
|
|
{calculatingRoutes.includes(order.id) && (
|
|
<div className="spinner-border" role="status">
|
|
<span className="visually-hidden"></span>
|
|
</div>
|
|
)}
|
|
{hoveredOrderId === order.id && (
|
|
<p className="hover-info">
|
|
Нажмите, чтобы посмотреть подробную информацию
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DeliveryOrdersList;
|