Files
logistics-app-forWeb/src/components/WaybillWidget.tsx
2026-01-18 00:49:39 +04:00

307 lines
11 KiB
TypeScript
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 { Order, WaybillEntry } from '../types';
import { waybillApi, ordersApi } from '../services/api';
import Loading from '../components/Loading';
import ErrorAlert from '../components/ErrorAlert';
interface WaybillWidgetProps {
vehicleId: number;
date: string;
}
const WaybillWidget: React.FC<WaybillWidgetProps> = ({ vehicleId, date }) => {
const [entries, setEntries] = useState<WaybillEntry[]>([]);
const [orders, setOrders] = useState<Order[]>([]);
const [availableOrders, setAvailableOrders] = useState<Order[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [selectedOrder, setSelectedOrder] = useState<Order | null>(null);
const [selectedTimeSlot, setSelectedTimeSlot] = useState<string | null>(null);
const [duration, setDuration] = useState<number>(2);
const timeSlots = Array.from({ length: 13 }, (_, i) => {
const hour = i + 8;
return `${hour.toString().padStart(2, '0')}:00`;
});
const loadData = async () => {
try {
const response = await ordersApi.getOrders();
setOrders((response.data as any).data.items);
setLoading(true);
setError(null);
const entriesResponse = (await waybillApi.getEntries() as any).data;
const vehicleEntries = entriesResponse.data.filter(
(entry: { vehicleId: number; date: string; }) => entry.vehicleId === vehicleId && entry.date === date
);
setEntries(vehicleEntries);
const ordersResponse = (await ordersApi.getOrders() as any).data.data.items;
const assignedOrderIds = new Set(vehicleEntries.map((entry: { orderId: any; }) => entry.orderId));
const available = ordersResponse.filter(
(order: Order) => !assignedOrderIds.has(order.id) && order.status !== 'completed'
);
setAvailableOrders(available);
} catch (err) {
setError('Не удалось загрузить данные путевого листа');
console.error('Error loading waybill data:', err);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadData();
}, [vehicleId, date]);
const getOrderForTimeSlot = (timeSlot: string): WaybillEntry | undefined => {
return entries.find(entry => {
const startHour = parseInt(entry.startTime.split(':')[0]);
const endHour = parseInt(entry.endTime.split(':')[0]);
const slotHour = parseInt(timeSlot.split(':')[0]);
return slotHour >= startHour && slotHour < endHour;
});
};
const getOrderInfo = (orderId: number, allOrders: Order[]): Order | undefined => {
const foundOrder = allOrders.find(order => order.id === orderId);
if (foundOrder) {
return foundOrder;
}
const waybillEntry = entries.find(entry => entry.orderId === orderId);
if (waybillEntry) {
const basicOrder: Order = {
id: waybillEntry.orderId,
clientName: `Заказ #${waybillEntry.orderId}`,
orderCost: 0,
orderDate: new Date().toISOString().split('T')[0],
status: 'in_progress' as const,
address: 'Адрес не указан',
description: 'Заказ запланирован в путевом листе'
};
return basicOrder;
}
return undefined;
};
const handleTimeSlotClick = async (timeSlot: string) => {
if (selectedOrder) {
try {
const startHour = parseInt(timeSlot.split(':')[0]);
const endHour = startHour + duration;
const newEntry: Omit<WaybillEntry, 'id'> = {
vehicleId,
orderId: selectedOrder.id,
startTime: timeSlot,
endTime: `${endHour.toString().padStart(2, '0')}:00`,
date
};
console.log('Sending waybill entry:', newEntry);
await waybillApi.createEntry(newEntry);
setSelectedOrder(null);
setSelectedTimeSlot(null);
await loadData();
} catch (err) {
setError('Не удалось добавить заказ в путевой лист');
console.error('Error creating waybill entry:', err);
}
} else {
setSelectedTimeSlot(timeSlot);
}
};
const handleOrderSelect = async (order: Order) => {
if (selectedTimeSlot) {
try {
const startHour = parseInt(selectedTimeSlot.split(':')[0]);
const endHour = startHour + duration;
const newEntry: Omit<WaybillEntry, 'id'> = {
vehicleId,
orderId: order.id,
startTime: selectedTimeSlot,
endTime: `${endHour.toString().padStart(2, '0')}:00`,
date
};
console.log('Sending waybill entry:', newEntry);
await waybillApi.createEntry(newEntry);
setSelectedTimeSlot(null);
setSelectedOrder(null);
await loadData();
} catch (err) {
setError('Не удалось добавить заказ в путевой лист');
console.error('Error creating waybill entry:', err);
}
} else {
setSelectedOrder(order);
}
};
const handleRemoveEntry = async (entryId: number) => {
try {
await waybillApi.deleteEntry(entryId);
await loadData();
} catch (err) {
setError('Не удалось удалить запись из путевого листа');
console.error('Error deleting waybill entry:', err);
}
};
if (loading) return <Loading />;
if (error) return <ErrorAlert message={error} onRetry={loadData} />;
return (
<div className="waybill-widget">
<div className="card">
<div className="card-header">
<h5 className="mb-0">Путевой лист на {new Date(date).toLocaleDateString()}</h5>
</div>
<div className="card-body">
<div className="alert alert-info mb-3">
{selectedOrder && !selectedTimeSlot && (
<div>
<p>Выбран заказ: <strong>{selectedOrder.clientName}</strong>. Теперь выберите время.</p>
<div className="mt-2">
<label htmlFor="duration" className="form-label">Длительность заказа (часы):</label>
<input
type="number"
id="duration"
className="form-control"
min="1"
max="8"
value={duration}
onChange={(e) => setDuration(parseInt(e.target.value) || 1)}
style={{ width: '100px', display: 'inline-block', marginLeft: '10px' }}
/>
</div>
</div>
)}
{selectedTimeSlot && !selectedOrder && (
<div>
<p>Выбрано время: <strong>{selectedTimeSlot}</strong>. Теперь выберите заказ.</p>
<div className="mt-2">
<label htmlFor="duration" className="form-label">Длительность заказа (часы):</label>
<input
type="number"
id="duration"
className="form-control"
min="1"
max="8"
value={duration}
onChange={(e) => setDuration(parseInt(e.target.value) || 1)}
style={{ width: '100px', display: 'inline-block', marginLeft: '10px' }}
/>
</div>
</div>
)}
{!selectedOrder && !selectedTimeSlot && (
<p>Выберите время или заказ для планирования доставки.</p>
)}
{selectedOrder && selectedTimeSlot && (
<p>
Готово к добавлению: <strong>{selectedOrder.clientName}</strong> на время <strong>{selectedTimeSlot}</strong>
продолжительностью <strong>{duration} час(ов)</strong>
</p>
)}
</div>
<div className="table-responsive mb-4">
<table className="table table-bordered">
<thead>
<tr>
<th>Время</th>
<th>Заказ</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
{timeSlots.map(timeSlot => {
const entry = getOrderForTimeSlot(timeSlot);
const order = entry ? getOrderInfo(entry.orderId, orders) : undefined;
return (
<tr
key={timeSlot}
className={selectedTimeSlot === timeSlot ? 'table-primary' : ''}
>
<td>{timeSlot}</td>
<td>
{order ? (
<div>
<strong>{order.clientName}</strong>
<br />
<small className="text-muted">{order.address}</small>
</div>
) : (
<span className="text-muted">Свободно</span>
)}
</td>
<td>
{order && entry ? (
<button
className="btn btn-sm btn-danger"
onClick={() => handleRemoveEntry(entry.id)}
>
Удалить
</button>
) : (
<button
className="btn btn-sm btn-outline-primary"
onClick={() => handleTimeSlotClick(timeSlot)}
>
Выбрать
</button>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
<div className="available-orders">
<h6>Доступные заказы:</h6>
<div className="row">
{availableOrders.map(order => (
<div key={order.id} className="col-md-6 mb-2">
<div
className={`card ${selectedOrder?.id === order.id ? 'border-primary' : ''}`}
style={{ cursor: 'pointer' }}
onClick={() => handleOrderSelect(order)}
>
<div className="card-body py-2">
<h6 className="card-title mb-1">{order.clientName}</h6>
<p className="card-text mb-1">
<small>{order.address}</small>
</p>
<p className="card-text mb-0">
<small className="text-muted">
{order.orderCost.toLocaleString()} руб.
</small>
</p>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
};
export default WaybillWidget;