Vendaweb-portal/components/Baldinho.tsx

326 lines
13 KiB
TypeScript

import React, { useState, useEffect } from "react";
import {
shippingService,
DeliveryScheduleItem,
} from "../src/services/shipping.service";
export interface DeliveryDay {
date: string;
day: string;
cap: number;
sales: number;
capDisp: number;
available: "Sim" | "Não";
}
interface BaldinhoProps {
selectedDeliveryDate: string;
onDateChange: (date: string) => void;
deliveryDays?: DeliveryDay[]; // Opcional agora, pois será carregado dinamicamente
}
const Baldinho: React.FC<BaldinhoProps> = ({
selectedDeliveryDate,
onDateChange,
deliveryDays: deliveryDaysProp,
}) => {
const [deliveryDays, setDeliveryDays] = useState<DeliveryDay[]>(
deliveryDaysProp || []
);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
/**
* Converte um item da API para o formato do componente
*/
const mapApiItemToDeliveryDay = (item: DeliveryScheduleItem): DeliveryDay => {
const date = new Date(item.dateDelivery);
const dayNames = [
"domingo",
"segunda-feira",
"terça-feira",
"quarta-feira",
"quinta-feira",
"sexta-feira",
"sábado",
];
const dayName = dayNames[date.getUTCDay()];
// Formatar data como DD/MM/YYYY
const formattedDate = date.toLocaleDateString("pt-BR", {
day: "2-digit",
month: "2-digit",
year: "numeric",
timeZone: "UTC",
});
return {
date: formattedDate,
day: dayName,
cap: item.deliverySize || 0,
sales: item.saleWeigth || 0,
capDisp: item.avaliableDelivery || 0,
available: item.delivery === "S" ? "Sim" : "Não",
};
};
/**
* Carrega os dados do agendamento de entrega da API
* Segue o mesmo padrão do Angular: getScheduleDelivery()
*/
useEffect(() => {
const loadDeliverySchedule = async () => {
try {
setLoading(true);
setError(null);
// Se já tiver dados via props, não carrega da API
if (deliveryDaysProp && deliveryDaysProp.length > 0) {
setDeliveryDays(deliveryDaysProp);
setLoading(false);
return;
}
const response = await shippingService.getScheduleDelivery();
if (
response &&
response.deliveries &&
Array.isArray(response.deliveries)
) {
const mappedDays = response.deliveries.map(mapApiItemToDeliveryDay);
setDeliveryDays(mappedDays);
// Se não houver data selecionada e houver dias disponíveis, selecionar o primeiro disponível
if (!selectedDeliveryDate && mappedDays.length > 0) {
const firstAvailable = mappedDays.find(
(d) => d.available === "Sim"
);
if (firstAvailable) {
onDateChange(firstAvailable.date);
}
}
} else {
setDeliveryDays([]);
}
} catch (err: any) {
console.error("Erro ao carregar agendamento de entrega:", err);
setError(err.message || "Erro ao carregar dados de entrega");
setDeliveryDays([]);
} finally {
setLoading(false);
}
};
loadDeliverySchedule();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // Carrega apenas uma vez ao montar o componente
return (
<div className="bg-white rounded-2xl shadow-lg shadow-slate-200/60 border border-slate-100 overflow-hidden mb-12">
<div className="grid grid-cols-1 lg:grid-cols-12">
{/* Lateral: Seleção Atual */}
<div className="lg:col-span-4 bg-[#002147] p-6 text-white relative overflow-hidden flex flex-col justify-center">
<div className="relative z-10">
<div className="w-10 h-10 bg-orange-500 rounded-xl flex items-center justify-center mb-5 shadow-lg shadow-orange-500/20">
<svg
className="w-5 h-5 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2.5"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
</div>
<span className="text-orange-400 font-black text-[9px] uppercase tracking-[0.3em] mb-3 block">
Fluxo de Logística
</span>
<h4 className="text-sm font-bold text-blue-100/70 mb-1.5">
Agendamento de Entrega:
</h4>
<div className="flex items-baseline space-x-2">
<span className="text-4xl font-black tracking-tighter">
{selectedDeliveryDate}
</span>
</div>
<div className="mt-6 p-4 rounded-xl bg-white/5 border border-white/10 backdrop-blur-sm">
<p className="text-xs font-medium text-blue-50 leading-relaxed">
Confira a grade ao lado. Dias em{" "}
<span className="text-red-400 font-black">VERMELHO</span> estão
com capacidade esgotada ou sem operação.
</p>
</div>
</div>
{/* Elementos decorativos */}
<div className="absolute top-[-20%] right-[-10%] w-80 h-80 bg-orange-500/10 rounded-full blur-3xl"></div>
<div className="absolute bottom-[-10%] left-[-10%] w-64 h-64 bg-blue-400/10 rounded-full blur-3xl"></div>
</div>
{/* Tabela de Disponibilidade */}
<div className="lg:col-span-8 p-6">
<div className="flex items-center justify-between mb-6">
<div>
<h4 className="text-lg font-black text-[#002147] tracking-tight mb-0.5">
Capacidade Operacional
</h4>
<p className="text-slate-400 text-xs font-medium">
Selecione uma data disponível para continuar o pedido
</p>
</div>
<div className="flex items-center space-x-4">
<div className="flex items-center text-[9px] font-black uppercase text-slate-400 tracking-widest">
<span className="w-2.5 h-2.5 bg-red-500 rounded-full mr-1.5 shadow-sm shadow-red-500/20"></span>{" "}
Bloqueado
</div>
<div className="flex items-center text-[9px] font-black uppercase text-slate-400 tracking-widest">
<span className="w-2.5 h-2.5 bg-emerald-500 rounded-full mr-1.5 shadow-sm shadow-emerald-500/20"></span>{" "}
Liberado
</div>
</div>
</div>
<div className="rounded-xl border border-slate-200 overflow-hidden shadow-inner bg-slate-50/30">
{loading ? (
<div className="flex items-center justify-center py-12">
<div className="flex flex-col items-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-orange-500 mb-3"></div>
<span className="text-xs text-slate-500 font-medium">
Carregando disponibilidade...
</span>
</div>
</div>
) : error ? (
<div className="p-6 bg-red-50 border border-red-200 rounded-lg m-4">
<p className="text-xs text-red-600 font-medium">{error}</p>
</div>
) : deliveryDays.length === 0 ? (
<div className="flex items-center justify-center py-12">
<p className="text-xs text-slate-400 font-medium">
Nenhum agendamento disponível
</p>
</div>
) : (
<div className="max-h-[300px] overflow-auto custom-scrollbar">
<table className="w-full text-left text-xs border-collapse">
<thead className="bg-white sticky top-0 z-20 border-b border-slate-200">
<tr className="text-slate-500 font-black uppercase tracking-widest text-[9px]">
<th className="px-5 py-3 border-r border-slate-100">
Data
</th>
<th className="px-5 py-3 border-r border-slate-100">
Dia
</th>
<th className="px-5 py-3 border-r border-slate-100 text-center">
Capacidade
</th>
<th className="px-5 py-3 border-r border-slate-100 text-center">
Carga Atual
</th>
<th className="px-5 py-3">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{deliveryDays.map((d, i) => {
const occupancy =
d.cap > 0 ? (d.sales / d.cap) * 100 : 100;
const isFull = d.available === "Não";
return (
<tr
key={i}
onClick={() => !isFull && onDateChange(d.date)}
className={`transition-all duration-200 group cursor-pointer ${
isFull
? "bg-red-600 text-white hover:bg-red-700"
: selectedDeliveryDate === d.date
? "bg-orange-50 text-orange-900 border-l-4 border-l-orange-500"
: "bg-white hover:bg-slate-50"
}`}
>
<td className="px-5 py-3.5 font-black border-r border-slate-100/10 text-sm">
{d.date}
</td>
<td className="px-5 py-3.5 font-bold border-r border-slate-100/10 uppercase text-[9px] tracking-widest opacity-80">
{d.day}
</td>
<td className="px-5 py-3.5 border-r border-slate-100/10 text-center font-bold text-xs">
{d.cap} Ton
</td>
<td className="px-5 py-3.5 border-r border-slate-100/10">
<div className="flex flex-col items-center">
<span className="font-bold text-[10px] mb-1.5">
{d.sales.toFixed(3)} Ton
</span>
<div
className={`w-20 h-1 rounded-full overflow-hidden ${
isFull
? "bg-white/20"
: "bg-slate-100 shadow-inner"
}`}
>
<div
className={`h-full transition-all duration-1000 ${
isFull
? "bg-white"
: occupancy > 85
? "bg-orange-500"
: "bg-emerald-500"
}`}
style={{
width: `${Math.min(occupancy, 100)}%`,
}}
></div>
</div>
</div>
</td>
<td className="px-5 py-3.5">
<div className="flex items-center justify-between">
<span
className={`px-3 py-1 rounded-full text-[8px] font-black uppercase tracking-[0.1em] shadow-sm ${
isFull
? "bg-white text-red-600"
: "bg-emerald-100 text-emerald-700 border border-emerald-200"
}`}
>
{d.available === "Sim"
? "• Disponível"
: "• Esgotado"}
</span>
{!isFull && (
<svg
className="w-4 h-4 opacity-0 group-hover:opacity-100 group-hover:translate-x-1 transition-all text-orange-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="3"
d="M9 5l7 7-7 7"
/>
</svg>
)}
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default Baldinho;