🚀 Initial commit: Versão atual do TrackSteel APP
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { CalendarIcon } from 'lucide-react';
|
||||
import { format } from 'date-fns';
|
||||
import { ptBR } from 'date-fns/locale';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { OrdemFabricacao } from '@/hooks/useOFs';
|
||||
|
||||
const editOFSchema = z.object({
|
||||
descritivo: z.string().optional(),
|
||||
data_abertura: z.string().optional(),
|
||||
data_prazo: z.string().optional(),
|
||||
data_termino_prev: z.string().optional(),
|
||||
peso_total: z.number().optional(),
|
||||
gestor: z.string().optional(),
|
||||
prioridade: z.string(),
|
||||
nivel_qualidade: z.string(),
|
||||
criterio_qualidade: z.string().optional(),
|
||||
tratamento_final: z.string().optional(),
|
||||
local_uf: z.string().optional(),
|
||||
});
|
||||
|
||||
type EditOFFormData = z.infer<typeof editOFSchema>;
|
||||
|
||||
interface EditOFModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => void;
|
||||
ordem: OrdemFabricacao | null;
|
||||
onUpdate: (id: string, data: Partial<OrdemFabricacao>) => Promise<void>;
|
||||
}
|
||||
|
||||
export const EditOFModal: React.FC<EditOFModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSuccess,
|
||||
ordem,
|
||||
onUpdate,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const form = useForm<EditOFFormData>({
|
||||
resolver: zodResolver(editOFSchema),
|
||||
defaultValues: {
|
||||
descritivo: '',
|
||||
prioridade: 'Normal',
|
||||
nivel_qualidade: 'Normal',
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (ordem && isOpen) {
|
||||
form.reset({
|
||||
descritivo: ordem.descritivo || '',
|
||||
data_abertura: ordem.data_abertura || '',
|
||||
data_prazo: ordem.data_prazo || '',
|
||||
data_termino_prev: ordem.data_termino_prev || '',
|
||||
peso_total: ordem.peso_total || 0,
|
||||
gestor: ordem.gestor || '',
|
||||
prioridade: ordem.prioridade || 'Normal',
|
||||
nivel_qualidade: ordem.nivel_qualidade || 'Normal',
|
||||
criterio_qualidade: ordem.criterio_qualidade || '',
|
||||
tratamento_final: ordem.tratamento_final || '',
|
||||
local_uf: ordem.local_uf || '',
|
||||
});
|
||||
}
|
||||
}, [ordem, isOpen, form]);
|
||||
|
||||
const onSubmit = async (data: EditOFFormData) => {
|
||||
if (!ordem) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
await onUpdate(ordem.id, data);
|
||||
onClose();
|
||||
onSuccess?.();
|
||||
} catch (error) {
|
||||
console.error('Erro ao atualizar OF:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!ordem) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Editar Ordem de Fabricação - {ordem.num_of}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="descritivo"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Descritivo</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Descrição da OF" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="data_abertura"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Data de Abertura</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-full pl-3 text-left font-normal",
|
||||
!field.value && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{field.value ? (
|
||||
format(new Date(field.value), "dd/MM/yyyy")
|
||||
) : (
|
||||
<span>Selecione</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value ? new Date(field.value) : undefined}
|
||||
onSelect={(date) => field.onChange(date ? format(date, 'yyyy-MM-dd') : '')}
|
||||
disabled={(date) => date < new Date("1900-01-01")}
|
||||
initialFocus
|
||||
locale={ptBR}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="data_prazo"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Data de Prazo</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-full pl-3 text-left font-normal",
|
||||
!field.value && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{field.value ? (
|
||||
format(new Date(field.value), "dd/MM/yyyy")
|
||||
) : (
|
||||
<span>Selecione</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value ? new Date(field.value) : undefined}
|
||||
onSelect={(date) => field.onChange(date ? format(date, 'yyyy-MM-dd') : '')}
|
||||
disabled={(date) => date < new Date("1900-01-01")}
|
||||
initialFocus
|
||||
locale={ptBR}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="data_termino_prev"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Prazo Término (Prev)</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-full pl-3 text-left font-normal",
|
||||
!field.value && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{field.value ? (
|
||||
format(new Date(field.value), "dd/MM/yyyy")
|
||||
) : (
|
||||
<span>Selecione</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value ? new Date(field.value) : undefined}
|
||||
onSelect={(date) => field.onChange(date ? format(date, 'yyyy-MM-dd') : '')}
|
||||
disabled={(date) => date < new Date("1900-01-01")}
|
||||
initialFocus
|
||||
locale={ptBR}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="peso_total"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Peso Total (kg)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
{...field}
|
||||
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||
placeholder="0"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="prioridade"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Prioridade</FormLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="Baixa">Baixa</SelectItem>
|
||||
<SelectItem value="Normal">Normal</SelectItem>
|
||||
<SelectItem value="Urgente">Urgente</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="nivel_qualidade"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nível de Qualidade</FormLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="Normal">Normal</SelectItem>
|
||||
<SelectItem value="Especial">Especial</SelectItem>
|
||||
<SelectItem value="Severa">Severa</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="gestor"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Gestor</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Nome do gestor responsável" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="criterio_qualidade"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Critério de Qualidade</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea {...field} placeholder="Especifique os critérios de qualidade" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="tratamento_final"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Tratamento Final</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Ex: Pintura, Galvanização, etc." />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="local_uf"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Local/UF</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Ex: São Paulo/SP" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 pt-4">
|
||||
<Button type="button" variant="outline" onClick={onClose}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Salvando...' : 'Salvar Alterações'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,366 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { CalendarIcon } from 'lucide-react';
|
||||
import { format } from 'date-fns';
|
||||
import { ptBR } from 'date-fns/locale';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useNovaOF } from '@/hooks/useNovaOF';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
|
||||
const novaOFSchema = z.object({
|
||||
ficha_tecnica_id: z.string().min(1, 'Ficha técnica é obrigatória'),
|
||||
data_abertura: z.string().optional(),
|
||||
data_prazo: z.string().optional(),
|
||||
prioridade: z.string().default('Normal'),
|
||||
nivel_qualidade: z.string().default('Normal'),
|
||||
criterio_qualidade: z.string().optional(),
|
||||
tratamento_final: z.string().optional(),
|
||||
local_uf: z.string().optional(),
|
||||
});
|
||||
|
||||
type NovaOFFormData = z.infer<typeof novaOFSchema>;
|
||||
|
||||
interface NovaOFModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export const NovaOFModal: React.FC<NovaOFModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const form = useForm<NovaOFFormData>({
|
||||
resolver: zodResolver(novaOFSchema),
|
||||
defaultValues: {
|
||||
ficha_tecnica_id: '',
|
||||
data_abertura: new Date().toISOString().split('T')[0],
|
||||
prioridade: 'Normal',
|
||||
nivel_qualidade: 'Normal',
|
||||
},
|
||||
});
|
||||
|
||||
const { loading, fichasTecnicas, buscarFichasTecnicas, criarNovaOF } = useNovaOF();
|
||||
const [fichaSelecionada, setFichaSelecionada] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
buscarFichasTecnicas();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const onSubmit = async (data: NovaOFFormData) => {
|
||||
if (!fichaSelecionada) {
|
||||
form.setError('ficha_tecnica_id', { message: 'Selecione uma ficha técnica' });
|
||||
return;
|
||||
}
|
||||
|
||||
const novaOFData = {
|
||||
of_number: fichaSelecionada.of_number,
|
||||
descritivo: fichaSelecionada.descritivo,
|
||||
data_abertura: data.data_abertura || new Date().toISOString().split('T')[0],
|
||||
data_prazo: data.data_prazo || fichaSelecionada.data_termino_prev,
|
||||
data_termino_prev: fichaSelecionada.data_termino_prev,
|
||||
peso_total: fichaSelecionada.peso_total || 0,
|
||||
gestor: fichaSelecionada.gestor,
|
||||
prioridade: data.prioridade,
|
||||
nivel_qualidade: data.nivel_qualidade,
|
||||
criterio_qualidade: data.criterio_qualidade,
|
||||
tratamento_final: data.tratamento_final,
|
||||
local_uf: data.local_uf,
|
||||
status: 'ativa',
|
||||
ficha_tecnica_id: data.ficha_tecnica_id,
|
||||
};
|
||||
|
||||
const success = await criarNovaOF(novaOFData);
|
||||
if (success) {
|
||||
form.reset();
|
||||
setFichaSelecionada(null);
|
||||
onClose();
|
||||
onSuccess?.();
|
||||
}
|
||||
};
|
||||
|
||||
const handleFichaTecnicaChange = (fichaId: string) => {
|
||||
const ficha = fichasTecnicas.find(f => f.id === fichaId);
|
||||
setFichaSelecionada(ficha);
|
||||
|
||||
if (ficha) {
|
||||
form.setValue('data_prazo', ficha.data_termino_prev || '');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Nova Ordem de Fabricação</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="ficha_tecnica_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Ficha Técnica *</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
handleFichaTecnicaChange(value);
|
||||
}}
|
||||
value={field.value}
|
||||
disabled={loading}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={loading ? "Carregando..." : "Selecione uma ficha técnica"} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{fichasTecnicas.map((ficha) => (
|
||||
<SelectItem key={ficha.id} value={ficha.id}>
|
||||
{ficha.of_number} - {ficha.descritivo} ({ficha.cliente})
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{fichaSelecionada && (
|
||||
<div className="bg-slate-50 p-4 rounded-lg">
|
||||
<h4 className="font-medium mb-2">Dados da Ficha Técnica Selecionada:</h4>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div><strong>OF:</strong> {fichaSelecionada.of_number}</div>
|
||||
<div><strong>Cliente:</strong> {fichaSelecionada.cliente}</div>
|
||||
<div><strong>Descritivo:</strong> {fichaSelecionada.descritivo}</div>
|
||||
<div><strong>Peso Total:</strong> {fichaSelecionada.peso_total}kg</div>
|
||||
<div><strong>Gestor:</strong> {fichaSelecionada.gestor}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="data_abertura"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Data de Abertura</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-full pl-3 text-left font-normal",
|
||||
!field.value && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{field.value ? (
|
||||
format(new Date(field.value), "PPP", { locale: ptBR })
|
||||
) : (
|
||||
<span>Selecione uma data</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value ? new Date(field.value) : undefined}
|
||||
onSelect={(date) => field.onChange(date ? format(date, 'yyyy-MM-dd') : '')}
|
||||
disabled={(date) => date < new Date("1900-01-01")}
|
||||
initialFocus
|
||||
locale={ptBR}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="data_prazo"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Data de Prazo</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-full pl-3 text-left font-normal",
|
||||
!field.value && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{field.value ? (
|
||||
format(new Date(field.value), "PPP", { locale: ptBR })
|
||||
) : (
|
||||
<span>Selecione uma data</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value ? new Date(field.value) : undefined}
|
||||
onSelect={(date) => field.onChange(date ? format(date, 'yyyy-MM-dd') : '')}
|
||||
disabled={(date) => date < new Date("1900-01-01")}
|
||||
initialFocus
|
||||
locale={ptBR}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="prioridade"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Prioridade</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione a prioridade" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="Baixa">Baixa</SelectItem>
|
||||
<SelectItem value="Normal">Normal</SelectItem>
|
||||
<SelectItem value="Urgente">Urgente</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="nivel_qualidade"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nível de Qualidade</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione o nível" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="Normal">Normal</SelectItem>
|
||||
<SelectItem value="Especial">Especial</SelectItem>
|
||||
<SelectItem value="Severa">Severa</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="criterio_qualidade"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Critério de Qualidade</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea {...field} placeholder="Especifique os critérios de qualidade" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="tratamento_final"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Tratamento Final</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Ex: Pintura, Galvanização, etc." />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="local_uf"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Local/UF</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Ex: São Paulo/SP" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end gap-3 pt-4">
|
||||
<Button type="button" variant="outline" onClick={onClose}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Criando...' : 'Criar OF'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,204 @@
|
||||
|
||||
import { FileText, Calendar, Package, Truck, Undo2 } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
|
||||
interface OFConcluidaCardProps {
|
||||
of: any;
|
||||
onEntregar?: (of: any) => void;
|
||||
onReverterEntregue?: (of: any) => void;
|
||||
onReverterConcluida?: (of: any) => void;
|
||||
showEntregarButton?: boolean;
|
||||
showAdminButtons?: boolean;
|
||||
}
|
||||
|
||||
export const OFConcluidaCard = ({
|
||||
of,
|
||||
onEntregar,
|
||||
onReverterEntregue,
|
||||
onReverterConcluida,
|
||||
showEntregarButton = false,
|
||||
showAdminButtons = false,
|
||||
}: OFConcluidaCardProps) => {
|
||||
const { isAdmin } = useUserRole();
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'concluida':
|
||||
return 'bg-green-600';
|
||||
case 'entregue':
|
||||
return 'bg-blue-600';
|
||||
default:
|
||||
return 'bg-gray-500';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusLabel = (status: string) => {
|
||||
switch (status) {
|
||||
case 'concluida':
|
||||
return 'Concluída';
|
||||
case 'entregue':
|
||||
return 'Entregue';
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="bg-slate-700/50 border-slate-600 hover:bg-slate-700/70 transition-colors">
|
||||
<CardContent className="p-3 sm:p-4 lg:p-6">
|
||||
<div className="grid grid-cols-1 gap-3 lg:grid-cols-12 lg:gap-3 lg:items-center">
|
||||
<div className="space-y-2 lg:col-span-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="w-3 h-3 sm:w-4 sm:h-4 text-slate-400" />
|
||||
<span className="text-xs sm:text-sm text-slate-300 font-medium">OF</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-white font-bold text-sm sm:text-base">{of.num_of}</p>
|
||||
<Badge variant="outline" className={`${getStatusColor(of.status_detalhado)} text-white border-none text-xs px-2 py-1 w-fit`}>
|
||||
{getStatusLabel(of.status_detalhado)}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 sm:space-y-2 lg:col-span-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs sm:text-sm text-slate-300 font-medium">Descritivo</span>
|
||||
</div>
|
||||
<p className="text-white text-sm sm:text-base truncate" title={of.descritivo || 'Sem descrição'}>
|
||||
{of.descritivo || 'Sem descrição'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 sm:space-y-2 lg:col-span-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Package className="w-3 h-3 sm:w-4 sm:h-4 text-slate-400" />
|
||||
<span className="text-xs sm:text-sm text-slate-300 font-medium">Peso</span>
|
||||
</div>
|
||||
<p className="text-white text-sm sm:text-base">{of.peso_total ? `${of.peso_total}kg` : 'N/A'}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 sm:space-y-2 lg:col-span-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-3 h-3 sm:w-4 sm:h-4 text-slate-400" />
|
||||
<span className="text-xs sm:text-sm text-slate-300 font-medium">Data Arquivamento</span>
|
||||
</div>
|
||||
<p className="text-white text-sm sm:text-base">
|
||||
{of.data_arquivamento ? new Date(of.data_arquivamento).toLocaleDateString('pt-BR') : 'N/A'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 sm:space-y-2 lg:col-span-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs sm:text-sm text-slate-300 font-medium">Critério Qualidade</span>
|
||||
</div>
|
||||
<p className="text-white text-sm sm:text-base">{of.criterio_qualidade || 'N/A'}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1 lg:col-span-2 flex-wrap">
|
||||
{showEntregarButton && of.status_detalhado === 'concluida' && onEntregar && (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="bg-blue-600 border-blue-500 text-white hover:bg-blue-500 text-xs h-8 px-2 flex-1"
|
||||
>
|
||||
<Truck className="w-3 h-3 sm:w-4 sm:h-4 mr-1" />
|
||||
Entregar
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Confirmar Entrega</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Tem certeza que deseja marcar a OF {of.num_of} como entregue?
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => onEntregar(of)} className="bg-blue-600 hover:bg-blue-700">
|
||||
Entregar
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
|
||||
{showAdminButtons && isAdmin && of.status_detalhado === 'entregue' && onReverterEntregue && (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="bg-orange-600 border-orange-500 text-white hover:bg-orange-500 text-xs h-8 px-2"
|
||||
>
|
||||
<Undo2 className="w-3 h-3 sm:w-4 sm:h-4 mr-1" />
|
||||
Reverter
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Reverter para Concluída</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Tem certeza que deseja reverter a OF {of.num_of} de "Entregue" para "Concluída"?
|
||||
Esta ação só deve ser realizada em casos excepcionais.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => onReverterEntregue(of)} className="bg-orange-600 hover:bg-orange-700">
|
||||
Reverter
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
|
||||
{showAdminButtons && isAdmin && of.status_detalhado === 'concluida' && onReverterConcluida && (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="bg-red-600 border-red-500 text-white hover:bg-red-500 text-xs h-8 px-2"
|
||||
>
|
||||
<Undo2 className="w-3 h-3 sm:w-4 sm:h-4 mr-1" />
|
||||
Reativar
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Reativar OF</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Tem certeza que deseja reativar a OF {of.num_of}, movendo-a de volta para as Ordens Ativas?
|
||||
Esta ação só deve ser realizada em casos excepcionais.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => onReverterConcluida(of)} className="bg-red-600 hover:bg-red-700">
|
||||
Reativar
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,159 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Calendar, User, Weight, BarChart3, Monitor, Edit, CheckCircle, Trash2 } from 'lucide-react';
|
||||
import { format } from 'date-fns';
|
||||
import { ptBR } from 'date-fns/locale';
|
||||
|
||||
interface OrdemCardProps {
|
||||
ordem: any;
|
||||
onVerCronograma: (ordem: any) => void;
|
||||
onVerDashboard: (ordem: any) => void;
|
||||
onEdit?: (ordem: any) => void;
|
||||
onConcluir?: (ordem: any) => void;
|
||||
onDelete?: (ordem: any) => void;
|
||||
showActions?: boolean;
|
||||
}
|
||||
|
||||
export function OrdemCard({
|
||||
ordem,
|
||||
onVerCronograma,
|
||||
onVerDashboard,
|
||||
onEdit,
|
||||
onConcluir,
|
||||
onDelete,
|
||||
showActions = true
|
||||
}: OrdemCardProps) {
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status?.toLowerCase()) {
|
||||
case 'normal':
|
||||
return <Badge className="bg-blue-500/20 text-blue-400 border-blue-500/30">Normal</Badge>;
|
||||
case 'especial':
|
||||
return <Badge className="bg-orange-500/20 text-orange-400 border-orange-500/30">Especial</Badge>;
|
||||
case 'concluida':
|
||||
return <Badge className="bg-green-500/20 text-green-400 border-green-500/30">Concluída</Badge>;
|
||||
default:
|
||||
return <Badge variant="secondary">{status || 'Não definido'}</Badge>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="bg-card border-border hover:bg-accent/5 transition-colors">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<h3 className="text-xl font-bold text-card-foreground">
|
||||
OF: {ordem.num_of}
|
||||
</h3>
|
||||
{getStatusBadge(ordem.status)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onVerCronograma(ordem)}
|
||||
className="border-blue-200 bg-blue-50 hover:bg-blue-100 text-blue-700 dark:border-blue-800 dark:bg-blue-900/20 dark:hover:bg-blue-800/30 dark:text-blue-300"
|
||||
>
|
||||
<BarChart3 className="w-4 h-4 mr-2" />
|
||||
Ver Cronograma
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onVerDashboard(ordem)}
|
||||
className="border-green-200 bg-green-50 hover:bg-green-100 text-green-700 dark:border-green-800 dark:bg-green-900/20 dark:hover:bg-green-800/30 dark:text-green-300"
|
||||
>
|
||||
<Monitor className="w-4 h-4 mr-2" />
|
||||
Dashboard
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-semibold text-card-foreground">{ordem.descritivo}</h4>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Weight className="w-4 h-4" />
|
||||
<span>Peso: {ordem.peso_total || 'N/A'}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>
|
||||
Abertura: {ordem.data_abertura
|
||||
? format(new Date(ordem.data_abertura), 'dd/MM/yyyy', { locale: ptBR })
|
||||
: 'N/A'
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<User className="w-4 h-4" />
|
||||
<span>Gestor: {ordem.gestor || 'N/A'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ordem.prazo_termino && (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>
|
||||
Prazo: {format(new Date(ordem.prazo_termino), 'dd/MM/yyyy', { locale: ptBR })}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showActions && (onEdit || onConcluir || onDelete) && (
|
||||
<div className="flex flex-wrap gap-2 pt-2 border-t border-border">
|
||||
{onEdit && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onEdit(ordem)}
|
||||
className="border-gray-200 hover:bg-gray-100 text-gray-700 dark:border-gray-600 dark:hover:bg-gray-800 dark:text-gray-300"
|
||||
>
|
||||
<Edit className="w-4 h-4 mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{onConcluir && ordem.status !== 'concluida' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onConcluir(ordem)}
|
||||
className="border-green-200 bg-green-50 hover:bg-green-100 text-green-700 dark:border-green-800 dark:bg-green-900/20 dark:hover:bg-green-800/30 dark:text-green-300"
|
||||
>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
Concluir
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{onDelete && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (window.confirm(`Tem certeza que deseja excluir a OF ${ordem.num_of}?`)) {
|
||||
onDelete(ordem);
|
||||
}
|
||||
}}
|
||||
className="border-red-200 bg-red-50 hover:bg-red-100 text-red-700 dark:border-red-800 dark:bg-red-900/20 dark:hover:bg-red-800/30 dark:text-red-300"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
Excluir
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
import { Search, Filter } from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
|
||||
interface OrdensFiltrosProps {
|
||||
searchTerm: string;
|
||||
setSearchTerm: (value: string) => void;
|
||||
statusFilter: string;
|
||||
setStatusFilter: (value: string) => void;
|
||||
}
|
||||
|
||||
export const OrdensFiltros = ({
|
||||
searchTerm,
|
||||
setSearchTerm,
|
||||
statusFilter,
|
||||
setStatusFilter,
|
||||
}: OrdensFiltrosProps) => {
|
||||
return (
|
||||
<Card className="bg-slate-100 border-slate-300 dark:bg-slate-800/50 dark:border-slate-700">
|
||||
<CardHeader className="pb-2 sm:pb-3">
|
||||
<CardTitle className="text-slate-800 dark:text-white text-sm sm:text-lg flex items-center gap-2">
|
||||
<Filter className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||
Filtros
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-3 sm:space-y-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
|
||||
<div className="space-y-1 sm:space-y-2">
|
||||
<label className="text-slate-700 dark:text-slate-300 text-xs sm:text-sm font-medium">Buscar</label>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 sm:left-3 top-1/2 transform -translate-y-1/2 h-3 w-3 sm:h-4 sm:w-4 text-slate-400" />
|
||||
<Input
|
||||
placeholder="OF, cliente ou obra..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="bg-white border-slate-300 text-slate-900 dark:bg-slate-700 dark:border-slate-600 dark:text-white pl-8 sm:pl-10 text-xs sm:text-sm h-8 sm:h-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 sm:space-y-2">
|
||||
<label className="text-slate-700 dark:text-slate-300 text-xs sm:text-sm font-medium">Status</label>
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="bg-white border-slate-300 text-slate-900 dark:bg-slate-700 dark:border-slate-600 dark:text-white text-xs sm:text-sm h-8 sm:h-10">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-white border-slate-300 dark:bg-slate-700 dark:border-slate-600">
|
||||
<SelectItem value="all" className="text-slate-900 dark:text-white hover:bg-slate-100 dark:hover:bg-slate-600 text-xs sm:text-sm">Todos</SelectItem>
|
||||
<SelectItem value="ativa" className="text-slate-900 dark:text-white hover:bg-slate-100 dark:hover:bg-slate-600 text-xs sm:text-sm">Ativa</SelectItem>
|
||||
<SelectItem value="pausada" className="text-slate-900 dark:text-white hover:bg-slate-100 dark:hover:bg-slate-600 text-xs sm:text-sm">Pausada</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setSearchTerm('');
|
||||
setStatusFilter('all');
|
||||
}}
|
||||
className="bg-white border-slate-300 text-slate-700 hover:bg-slate-100 dark:bg-slate-700 dark:border-slate-600 dark:text-white dark:hover:bg-slate-600 w-full text-xs sm:text-sm h-8 sm:h-10 px-3"
|
||||
>
|
||||
Limpar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Factory, Plus, CheckCircle } from 'lucide-react';
|
||||
|
||||
interface OrdensHeaderProps {
|
||||
onNovaOF?: () => void;
|
||||
onOFsConcluidas: () => void;
|
||||
showNovaOF?: boolean;
|
||||
}
|
||||
|
||||
export function OrdensHeader({ onNovaOF, onOFsConcluidas, showNovaOF = true }: OrdensHeaderProps) {
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div className="space-y-1 sm:space-y-2">
|
||||
<h1 className="text-lg sm:text-2xl lg:text-3xl font-bold text-foreground flex items-center gap-2">
|
||||
<Factory className="w-5 h-5 sm:w-6 sm:h-6 lg:w-8 lg:h-8" />
|
||||
Ordens de Fabricação
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-xs sm:text-sm">
|
||||
Gerencie todas as ordens de fabricação
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2 sm:gap-3">
|
||||
<Button
|
||||
onClick={onOFsConcluidas}
|
||||
variant="outline"
|
||||
className="bg-slate-700 border-slate-600 text-white hover:bg-slate-600 flex items-center gap-2 text-xs sm:text-sm"
|
||||
>
|
||||
<CheckCircle className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
OFs Concluídas
|
||||
</Button>
|
||||
|
||||
{showNovaOF && onNovaOF && (
|
||||
<Button
|
||||
onClick={onNovaOF}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white flex items-center gap-2 text-xs sm:text-sm"
|
||||
>
|
||||
<Plus className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
Nova OF
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
import React from 'react';
|
||||
import { OrdemCard } from './OrdemCard';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Package } from 'lucide-react';
|
||||
|
||||
interface OrdensListaProps {
|
||||
ordens: any[];
|
||||
totalOrdens: number;
|
||||
searchTerm: string;
|
||||
statusFilter: string;
|
||||
onVerCronograma: (ordem: any) => void;
|
||||
onVerDashboard: (ordem: any) => void;
|
||||
onEdit?: (ordem: any) => void;
|
||||
onConcluir?: (ordem: any) => void;
|
||||
onDelete?: (ordem: any) => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
}
|
||||
|
||||
export function OrdensLista({
|
||||
ordens,
|
||||
totalOrdens,
|
||||
searchTerm,
|
||||
statusFilter,
|
||||
onVerCronograma,
|
||||
onVerDashboard,
|
||||
onEdit,
|
||||
onConcluir,
|
||||
onDelete,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: OrdensListaProps) {
|
||||
if (ordens.length === 0) {
|
||||
return (
|
||||
<Card className="bg-card border-border">
|
||||
<CardContent className="flex flex-col items-center justify-center py-12">
|
||||
<Package className="w-12 h-12 text-muted-foreground mb-4" />
|
||||
<h3 className="text-lg font-semibold text-card-foreground mb-2">
|
||||
{searchTerm || statusFilter !== 'all'
|
||||
? 'Nenhuma ordem encontrada'
|
||||
: 'Nenhuma ordem cadastrada'
|
||||
}
|
||||
</h3>
|
||||
<p className="text-muted-foreground text-center max-w-md">
|
||||
{searchTerm || statusFilter !== 'all'
|
||||
? 'Tente ajustar os filtros para encontrar as ordens desejadas.'
|
||||
: 'Comece criando sua primeira ordem de fabricação.'
|
||||
}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Mostrando {ordens.length} de {totalOrdens} {totalOrdens === 1 ? 'ordem' : 'ordens'}
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4">
|
||||
{ordens.map((ordem) => (
|
||||
<OrdemCard
|
||||
key={ordem.id}
|
||||
ordem={ordem}
|
||||
onVerCronograma={onVerCronograma}
|
||||
onVerDashboard={onVerDashboard}
|
||||
onEdit={canEdit && onEdit ? onEdit : undefined}
|
||||
onConcluir={canEdit && onConcluir ? onConcluir : undefined}
|
||||
onDelete={canDelete && onDelete ? onDelete : undefined}
|
||||
showActions={canEdit || canDelete}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user