556 lines
29 KiB
JavaScript
556 lines
29 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
|
import { useMaintenance } from '../hooks/useMaintenance';
|
|
import { useVehicles } from '../hooks/useVehicles';
|
|
import {
|
|
Wrench, Clock, Store, DollarSign, AlertTriangle,
|
|
FileSearch, CheckCircle, Plus, Pencil, Trash2,
|
|
Calendar, MapPin, FileText, Activity
|
|
} from 'lucide-react';
|
|
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import { cn } from '@/lib/utils';
|
|
import { motion } from 'framer-motion';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
|
|
import { useFleetOptions } from '../hooks/useFleetOptions';
|
|
|
|
export const MaintenanceView = () => {
|
|
const { getSelectOptions } = useFleetOptions();
|
|
const statusLinksOptions = getSelectOptions('status_manutencao');
|
|
const paymentOptions = getSelectOptions('condicao_pagamento');
|
|
const reasonOptions = getSelectOptions('motivo_atendimento');
|
|
const {
|
|
maintenances, workshops, loading,
|
|
fetchMaintenances, createMaintenance, updateMaintenance,
|
|
fetchWorkshops, createWorkshop, updateWorkshop
|
|
} = useMaintenance();
|
|
const { vehicles, fetchVehicles } = useVehicles();
|
|
|
|
const [isMaintenanceModalOpen, setIsMaintenanceModalOpen] = useState(false);
|
|
const [editingMaintenance, setEditingMaintenance] = useState(null);
|
|
|
|
const [isWorkshopModalOpen, setIsWorkshopModalOpen] = useState(false);
|
|
const [editingWorkshop, setEditingWorkshop] = useState(null);
|
|
|
|
// --- Form States ---
|
|
const [maintForm, setMaintForm] = useState({
|
|
placa: '',
|
|
placa_reserva: '',
|
|
manutencao: '', // Descrição curta / Status
|
|
oficina: '',
|
|
data_solicitacao: '',
|
|
data_agendamento: '',
|
|
previcao_entrega: '', // Note: API uses 'previcao' typo or 'previsao'? JSON says 'previcao_entrega'
|
|
data_finalizacao: '',
|
|
orcamento_inicial: '',
|
|
orcamento_final: '',
|
|
condicao_pagamento: '',
|
|
status: 'Aberto', // Pendente Aprovação, Em Andamento, Finalizado
|
|
tipo_manutencao: 'Corretiva', // Preventiva, Corretiva
|
|
motivo_atendimento: '',
|
|
sla_oficina: '',
|
|
obs: '',
|
|
pdf_orcamento: ''
|
|
});
|
|
|
|
const [workshopForm, setWorkshopForm] = useState({
|
|
oficina: '',
|
|
cidade: '',
|
|
uf: '',
|
|
endereco: ''
|
|
});
|
|
|
|
useEffect(() => {
|
|
fetchMaintenances();
|
|
fetchWorkshops();
|
|
fetchVehicles();
|
|
}, [fetchMaintenances, fetchWorkshops, fetchVehicles]);
|
|
|
|
// --- Maintenance Logic ---
|
|
|
|
const handleOpenMaintenanceModal = (maint = null) => {
|
|
if (maint) {
|
|
setEditingMaintenance(maint);
|
|
setMaintForm({
|
|
placa: maint.placa || '',
|
|
placa_reserva: maint.placa_reserva || '',
|
|
manutencao: maint.manutencao || '',
|
|
oficina: maint.oficina || '',
|
|
data_solicitacao: maint.data_solicitacao ? maint.data_solicitacao.split(' ')[0] : '',
|
|
data_agendamento: maint.data_agendamento ? maint.data_agendamento.split(' ')[0] : '',
|
|
previcao_entrega: maint.previcao_entrega ? maint.previcao_entrega.split(' ')[0] : '',
|
|
data_finalizacao: maint.data_finalizacao ? maint.data_finalizacao.split(' ')[0] : '',
|
|
orcamento_inicial: maint.orcamento_inicial || '',
|
|
orcamento_final: maint.orcamento_final || '',
|
|
condicao_pagamento: maint.condicao_pagamento || '',
|
|
status: maint.status || 'Aberto',
|
|
tipo_manutencao: maint.sla_oficina || 'Corretiva', // Mapping SLA to Type usually
|
|
motivo_atendimento: maint.motivo_atendimento || '',
|
|
sla_oficina: maint.sla_oficina || '',
|
|
obs: maint.obs || '',
|
|
pdf_orcamento: maint.pdf_orcamento || ''
|
|
});
|
|
} else {
|
|
setEditingMaintenance(null);
|
|
setMaintForm({
|
|
placa: '', placa_reserva: '', manutencao: 'Aberto', oficina: '',
|
|
data_solicitacao: new Date().toISOString().split('T')[0],
|
|
data_agendamento: '', previcao_entrega: '', data_finalizacao: '',
|
|
orcamento_inicial: '', orcamento_final: '', condicao_pagamento: '',
|
|
status: 'Aberto', tipo_manutencao: 'Corretiva', motivo_atendimento: '',
|
|
sla_oficina: '', obs: '', pdf_orcamento: ''
|
|
});
|
|
}
|
|
setIsMaintenanceModalOpen(true);
|
|
};
|
|
|
|
const handleMaintenanceSubmit = async (e) => {
|
|
e.preventDefault();
|
|
const payload = { ...maintForm }; // Add formatting if necessary
|
|
let success = false;
|
|
|
|
if (editingMaintenance) {
|
|
success = await updateMaintenance(editingMaintenance.idmanutencao_frota, payload);
|
|
} else {
|
|
success = await createMaintenance(payload);
|
|
}
|
|
if (success) setIsMaintenanceModalOpen(false);
|
|
};
|
|
|
|
// --- Workshop Logic ---
|
|
|
|
const handleOpenWorkshopModal = (ws = null) => {
|
|
if (ws) {
|
|
setEditingWorkshop(ws);
|
|
setWorkshopForm({
|
|
oficina: ws.oficina || '',
|
|
cidade: ws.cidade || '',
|
|
uf: ws.uf || '',
|
|
endereco: ws.endereco || '' // Assuming API calls it 'endereco' or 'endereco_prestador' - mapping:
|
|
});
|
|
} else {
|
|
setEditingWorkshop(null);
|
|
setWorkshopForm({ oficina: '', cidade: '', uf: '', endereco: '' });
|
|
}
|
|
setIsWorkshopModalOpen(true);
|
|
};
|
|
|
|
const handleWorkshopSubmit = async (e) => {
|
|
e.preventDefault();
|
|
let success = false;
|
|
if (editingWorkshop) {
|
|
success = await updateWorkshop(editingWorkshop.idoficinas_frota, workshopForm);
|
|
} else {
|
|
success = await createWorkshop(workshopForm);
|
|
}
|
|
if (success) setIsWorkshopModalOpen(false);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6 pb-20">
|
|
{/* Header */}
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
|
<div>
|
|
<h2 className="text-3xl font-black tracking-tight text-slate-900">Manutenção</h2>
|
|
<p className="text-slate-500 font-medium">Controle de ordens de serviço, oficinas e custos.</p>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<Button
|
|
variant="outline"
|
|
className="gap-2 font-bold text-slate-700 border-slate-200 hover:bg-slate-50"
|
|
onClick={() => handleOpenWorkshopModal()}
|
|
>
|
|
<Store size={16} /> Oficinas
|
|
</Button>
|
|
<Button
|
|
className="gap-2 bg-emerald-600 hover:bg-emerald-700 text-white font-bold shadow-lg shadow-emerald-500/20"
|
|
onClick={() => handleOpenMaintenanceModal()}
|
|
>
|
|
<Plus size={18} /> Nova Ordem
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* KPI Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<Card className="border-slate-200 shadow-sm bg-white">
|
|
<CardContent className="p-5 flex items-center gap-4">
|
|
<div className="w-10 h-10 bg-amber-100 rounded-lg flex items-center justify-center text-amber-600">
|
|
<Activity size={20} />
|
|
</div>
|
|
<div>
|
|
<p className="text-2xl font-bold text-slate-900">{maintenances.filter(m => m.status === 'Em Andamento').length}</p>
|
|
<p className="text-[10px] uppercase font-bold text-slate-400">Em Andamento</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="border-slate-200 shadow-sm bg-white">
|
|
<CardContent className="p-5 flex items-center gap-4">
|
|
<div className="w-10 h-10 bg-rose-100 rounded-lg flex items-center justify-center text-rose-600">
|
|
<AlertTriangle size={20} />
|
|
</div>
|
|
<div>
|
|
<p className="text-2xl font-bold text-slate-900">{maintenances.filter(m => m.status === 'Aberto').length}</p>
|
|
<p className="text-[10px] uppercase font-bold text-slate-400">Pendentes</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="border-slate-200 shadow-sm bg-white">
|
|
<CardContent className="p-5 flex items-center gap-4">
|
|
<div className="w-10 h-10 bg-emerald-100 rounded-lg flex items-center justify-center text-emerald-600">
|
|
<CheckCircle size={20} />
|
|
</div>
|
|
<div>
|
|
<p className="text-2xl font-bold text-slate-900">{maintenances.filter(m => m.status === 'Finalizado').length}</p>
|
|
<p className="text-[10px] uppercase font-bold text-slate-400">Finalizadas</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="border-slate-200 shadow-sm bg-white">
|
|
<CardContent className="p-5 flex items-center gap-4">
|
|
<div className="w-10 h-10 bg-slate-100 rounded-lg flex items-center justify-center text-slate-600">
|
|
<Store size={20} />
|
|
</div>
|
|
<div>
|
|
<p className="text-2xl font-bold text-slate-900">{workshops.length}</p>
|
|
<p className="text-[10px] uppercase font-bold text-slate-400">Oficinas</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<Card className="border border-slate-200 shadow-sm bg-white overflow-hidden">
|
|
<CardHeader className="bg-slate-50/50 border-b border-slate-100 px-6 py-4">
|
|
<CardTitle className="text-base font-bold text-slate-800">Histórico de Manutenções</CardTitle>
|
|
</CardHeader>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-left text-sm">
|
|
<thead className="bg-slate-50 text-xs uppercase font-bold text-slate-500 tracking-wider">
|
|
<tr>
|
|
<th className="px-6 py-4 border-b border-slate-100">Solicitação</th>
|
|
<th className="px-6 py-4 border-b border-slate-100">Veículo</th>
|
|
<th className="px-6 py-4 border-b border-slate-100">Serviço/Oficina</th>
|
|
<th className="px-6 py-4 border-b border-slate-100">Status</th>
|
|
<th className="px-6 py-4 border-b border-slate-100">Custo Total</th>
|
|
<th className="px-6 py-4 border-b border-slate-100 text-right">Ação</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-100">
|
|
{maintenances.map((m) => (
|
|
<tr key={m.idmanutencao_frota || Math.random()} className="hover:bg-slate-50/50">
|
|
<td className="px-6 py-4">
|
|
<div className="flex flex-col">
|
|
<span className="font-bold text-slate-900">{m.data_solicitacao ? new Date(m.data_solicitacao).toLocaleDateString('pt-BR') : '-'}</span>
|
|
<span className="text-[10px] text-slate-400 uppercase font-bold">OS #{m.idmanutencao_frota}</span>
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 font-mono font-bold text-emerald-950">
|
|
{m.placa}
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<div className="flex flex-col">
|
|
<span className="font-bold text-slate-700 text-xs">{m.obs || 'Manutenção Geral'}</span>
|
|
<div className="flex items-center gap-1 text-[11px] text-slate-500 mt-0.5">
|
|
<Store size={10} /> {m.oficina}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<StatusBadge status={m.status} />
|
|
</td>
|
|
<td className="px-6 py-4 font-bold text-slate-700">
|
|
R$ {Number(m.orcamento_final || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}
|
|
</td>
|
|
<td className="px-6 py-4 text-right">
|
|
<Button variant="ghost" size="icon" onClick={() => handleOpenMaintenanceModal(m)} className="text-slate-400 hover:text-emerald-600">
|
|
<Pencil size={16} />
|
|
</Button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* --- MODAL MANUTENÇÃO (TABS) --- */}
|
|
<Dialog open={isMaintenanceModalOpen} onOpenChange={setIsMaintenanceModalOpen}>
|
|
<DialogContent className="sm:max-w-[700px] bg-white gap-0 p-0 overflow-hidden">
|
|
<DialogHeader className="px-6 py-4 border-b border-slate-100 bg-slate-50/50">
|
|
<DialogTitle className="text-xl font-bold text-slate-900">
|
|
{editingMaintenance ? `Editar OS #${editingMaintenance.idmanutencao_frota}` : 'Nova Ordem de Serviço'}
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<form onSubmit={handleMaintenanceSubmit}>
|
|
<Tabs defaultValue="dados_gerais" className="w-full">
|
|
<div className="px-6 pt-4 bg-white">
|
|
<TabsList className="grid w-full grid-cols-3 bg-slate-100 p-1 rounded-lg">
|
|
<TabsTrigger value="dados_gerais" className="font-bold text-xs uppercase data-[state=active]:text-emerald-700">Geral</TabsTrigger>
|
|
<TabsTrigger value="financeiro" className="font-bold text-xs uppercase data-[state=active]:text-emerald-700">Financeiro</TabsTrigger>
|
|
<TabsTrigger value="datas" className="font-bold text-xs uppercase data-[state=active]:text-emerald-700">Agendamento</TabsTrigger>
|
|
</TabsList>
|
|
</div>
|
|
|
|
<div className="px-6 py-6 h-[400px] overflow-y-auto">
|
|
<TabsContent value="dados_gerais" className="space-y-4 m-0">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Veículo</Label>
|
|
<Select value={maintForm.placa} onValueChange={v => setMaintForm(p => ({...p, placa: v}))}>
|
|
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
|
|
<SelectContent>
|
|
{vehicles.map(v => <SelectItem key={v.idveiculo_frota} value={v.placa}>{v.placa} - {v.modelo}</SelectItem>)}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Oficina</Label>
|
|
<Select value={maintForm.oficina} onValueChange={v => setMaintForm(p => ({...p, oficina: v}))}>
|
|
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
|
|
<SelectContent>
|
|
{workshops.map(w => <SelectItem key={w.idoficinas_frota} value={w.oficina}>{w.oficina}</SelectItem>)}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Tipo</Label>
|
|
<Select value={maintForm.tipo_manutencao} onValueChange={v => setMaintForm(p => ({...p, tipo_manutencao: v}))}>
|
|
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="Corretiva">Corretiva</SelectItem>
|
|
<SelectItem value="Preventiva">Preventiva</SelectItem>
|
|
<SelectItem value="Preditiva">Preditiva</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Status</Label>
|
|
<Select value={maintForm.status} onValueChange={v => setMaintForm(p => ({...p, status: v}))}>
|
|
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
|
|
<SelectContent>
|
|
{statusLinksOptions.length > 0 ? (
|
|
statusLinksOptions.map(opt => <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>)
|
|
) : (
|
|
<>
|
|
<SelectItem value="Aberto">Aberto</SelectItem>
|
|
<SelectItem value="Pendente Aprovação">Pendente Aprovação</SelectItem>
|
|
<SelectItem value="Em Andamento">Em Andamento</SelectItem>
|
|
<SelectItem value="Finalizado">Finalizado</SelectItem>
|
|
<SelectItem value="Cancelado">Cancelado</SelectItem>
|
|
</>
|
|
)}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Motivo do Atendimento</Label>
|
|
{reasonOptions.length > 0 ? (
|
|
<Select value={maintForm.motivo_atendimento} onValueChange={v => setMaintForm(p => ({...p, motivo_atendimento: v}))}>
|
|
<SelectTrigger><SelectValue placeholder="Selecione o motivo" /></SelectTrigger>
|
|
<SelectContent>
|
|
{reasonOptions.map(opt => <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>)}
|
|
</SelectContent>
|
|
</Select>
|
|
) : (
|
|
<Input value={maintForm.motivo_atendimento} onChange={e => setMaintForm(p => ({...p, motivo_atendimento: e.target.value}))} placeholder="Ex: Quebra, Preventiva, etc" />
|
|
)}
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Observações do Serviço</Label>
|
|
<Textarea value={maintForm.obs} onChange={e => setMaintForm(p => ({...p, obs: e.target.value}))} placeholder="Descreva o serviço..." className="h-24 resize-none" />
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="financeiro" className="space-y-4 m-0">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Orçamento Inicial</Label>
|
|
<div className="relative">
|
|
<DollarSign className="absolute left-3 top-1/2 -translate-y-1/2 h-3 w-3 text-slate-400" />
|
|
<Input className="pl-9" value={maintForm.orcamento_inicial} onChange={e => setMaintForm(p => ({...p, orcamento_inicial: e.target.value}))} />
|
|
</div>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Orçamento Final</Label>
|
|
<div className="relative">
|
|
<DollarSign className="absolute left-3 top-1/2 -translate-y-1/2 h-3 w-3 text-slate-400" />
|
|
<Input className="pl-9" value={maintForm.orcamento_final} onChange={e => setMaintForm(p => ({...p, orcamento_final: e.target.value}))} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Condição Pagamento</Label>
|
|
{paymentOptions.length > 0 ? (
|
|
<Select value={maintForm.condicao_pagamento} onValueChange={v => setMaintForm(p => ({...p, condicao_pagamento: v}))}>
|
|
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
|
|
<SelectContent>
|
|
{paymentOptions.map(opt => <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>)}
|
|
</SelectContent>
|
|
</Select>
|
|
) : (
|
|
<Input value={maintForm.condicao_pagamento} onChange={e => setMaintForm(p => ({...p, condicao_pagamento: e.target.value}))} placeholder="Ex: 30 dias, À vista" />
|
|
)}
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Link PDF Orçamento</Label>
|
|
<Input value={maintForm.pdf_orcamento} onChange={e => setMaintForm(p => ({...p, pdf_orcamento: e.target.value}))} placeholder="https://..." />
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="datas" className="space-y-4 m-0">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Data Solicitação</Label>
|
|
<Input type="date" value={maintForm.data_solicitacao} onChange={e => setMaintForm(p => ({...p, data_solicitacao: e.target.value}))} />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Data Agendamento</Label>
|
|
<Input type="date" value={maintForm.data_agendamento} onChange={e => setMaintForm(p => ({...p, data_agendamento: e.target.value}))} />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Previsão Entrega</Label>
|
|
<Input type="date" value={maintForm.previcao_entrega} onChange={e => setMaintForm(p => ({...p, previcao_entrega: e.target.value}))} />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Data Finalização</Label>
|
|
<Input type="date" value={maintForm.data_finalizacao} onChange={e => setMaintForm(p => ({...p, data_finalizacao: e.target.value}))} />
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 p-3 bg-orange-50 rounded border border-orange-100 text-xs text-orange-700">
|
|
<AlertTriangle size={14} className="inline mr-1 mb-0.5" />
|
|
Datas são cruciais para o cálculo de SLA da oficina.
|
|
</div>
|
|
</TabsContent>
|
|
</div>
|
|
<DialogFooter className="px-6 py-4 border-t border-slate-100 bg-slate-50/50">
|
|
<Button type="button" variant="outline" onClick={() => setIsMaintenanceModalOpen(false)}>Cancelar</Button>
|
|
<Button type="submit" className="bg-emerald-600 hover:bg-emerald-700 text-white font-bold">Salvar Ordem</Button>
|
|
</DialogFooter>
|
|
</Tabs>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* --- MODAL OFICINA (REDESIGN) --- */}
|
|
<Dialog open={isWorkshopModalOpen} onOpenChange={setIsWorkshopModalOpen}>
|
|
<DialogContent className="sm:max-w-[800px] h-[500px] bg-white gap-0 p-0 flex flex-col md:flex-row overflow-hidden">
|
|
{/* Sidebar: Lista */}
|
|
<div className="w-full md:w-1/3 bg-slate-50 border-r border-slate-100 flex flex-col">
|
|
<div className="p-4 border-b border-slate-100 bg-white">
|
|
<h3 className="text-sm font-black text-slate-800 uppercase tracking-tight">Oficinas Cadastradas</h3>
|
|
<p className="text-[10px] text-slate-400 font-bold mt-1">{workshops.length} parceiros</p>
|
|
</div>
|
|
<div className="flex-1 overflow-y-auto p-2 space-y-2">
|
|
{workshops.map(w => (
|
|
<div
|
|
key={w.idoficinas_frota}
|
|
onClick={() => {
|
|
setEditingWorkshop(w);
|
|
setWorkshopForm({ oficina: w.oficina, cidade: w.cidade, uf: w.uf, endereco: w.endereco });
|
|
}}
|
|
className={cn(
|
|
"p-3 rounded-lg border text-left cursor-pointer transition-all",
|
|
editingWorkshop?.idoficinas_frota === w.idoficinas_frota
|
|
? "bg-white border-emerald-500 shadow-sm ring-1 ring-emerald-500/20"
|
|
: "bg-white border-slate-200 hover:border-emerald-300"
|
|
)}
|
|
>
|
|
<div className="font-bold text-xs text-slate-800">{w.oficina}</div>
|
|
<div className="text-[10px] text-slate-400 font-medium truncate mt-0.5">{w.cidade}-{w.uf}</div>
|
|
</div>
|
|
))}
|
|
<Button
|
|
variant="ghost"
|
|
className="w-full text-xs font-bold text-emerald-600 hover:text-emerald-700 hover:bg-emerald-50 mt-2 dashed border border-emerald-200"
|
|
onClick={() => {
|
|
setEditingWorkshop(null);
|
|
setWorkshopForm({ oficina: '', cidade: '', uf: '', endereco: '' });
|
|
}}
|
|
>
|
|
<Plus size={14} className="mr-1" /> Nova Oficina
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main: Form */}
|
|
<div className="flex-1 flex flex-col">
|
|
<div className="p-6 border-b border-slate-100">
|
|
<DialogTitle className="text-lg font-bold text-slate-800">
|
|
{editingWorkshop ? 'Editar Parceiro' : 'Novo Parceiro'}
|
|
</DialogTitle>
|
|
<DialogDescription className="text-xs">
|
|
Dados completos da oficina para ordens de serviço.
|
|
</DialogDescription>
|
|
</div>
|
|
<div className="p-6 flex-1 overflow-y-auto bg-white">
|
|
<form id="workshopForm" onSubmit={handleWorkshopSubmit} className="space-y-4">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Nome da Oficina</Label>
|
|
<Input value={workshopForm.oficina} onChange={e => setWorkshopForm(p => ({...p, oficina: e.target.value}))} placeholder="Ex: Mecânica Silva" />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Endereço Completo</Label>
|
|
<Input value={workshopForm.endereco} onChange={e => setWorkshopForm(p => ({...p, endereco: e.target.value}))} placeholder="Rua..." />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Cidade</Label>
|
|
<Input value={workshopForm.cidade} onChange={e => setWorkshopForm(p => ({...p, cidade: e.target.value}))} />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">UF</Label>
|
|
<Input value={workshopForm.uf} onChange={e => setWorkshopForm(p => ({...p, uf: e.target.value}))} maxLength={2} placeholder="SP" />
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div className="p-4 border-t border-slate-100 bg-slate-50 flex justify-end gap-2">
|
|
<Button variant="outline" onClick={() => setIsWorkshopModalOpen(false)}>Cancelar</Button>
|
|
<Button form="workshopForm" type="submit" className="bg-emerald-600 hover:bg-emerald-700 text-white font-bold">Salvar Dados</Button>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const StatusBadge = ({ status }) => {
|
|
const styles = {
|
|
'Aberto': 'bg-slate-100 text-slate-600 border-slate-200',
|
|
'Pendente Aprovação': 'bg-amber-50 text-amber-700 border-amber-200',
|
|
'Em Andamento': 'bg-blue-50 text-blue-700 border-blue-200',
|
|
'Finalizado': 'bg-emerald-50 text-emerald-700 border-emerald-200',
|
|
'Cancelado': 'bg-red-50 text-red-700 border-red-200'
|
|
};
|
|
return (
|
|
<Badge variant="outline" className={cn("text-[10px] font-bold uppercase tracking-wide", styles[status] || styles['Aberto'])}>
|
|
{status}
|
|
</Badge>
|
|
);
|
|
}
|