upd
This commit is contained in:
311
src/components/WaybillWidget.tsx
Normal file
311
src/components/WaybillWidget.tsx
Normal file
@@ -0,0 +1,311 @@
|
||||
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;
|
||||
}
|
||||
|
||||
let durationOrder;
|
||||
|
||||
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);
|
||||
|
||||
// Часовые интервалы с 8:00 до 20:00
|
||||
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);
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Загружаем записи путевого листа
|
||||
const entriesResponse = await waybillApi.getEntries();
|
||||
const vehicleEntries = entriesResponse.data.filter(
|
||||
entry => entry.vehicleId === vehicleId && entry.date === date
|
||||
);
|
||||
setEntries(vehicleEntries);
|
||||
|
||||
// Загружаем доступные заказы
|
||||
const ordersResponse = await ordersApi.getOrders();
|
||||
const assignedOrderIds = new Set(vehicleEntries.map(entry => entry.orderId));
|
||||
const available = ordersResponse.data.filter(
|
||||
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
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user