🚀 Initial commit: Versão atual do TrackSteel APP

This commit is contained in:
2026-03-18 21:17:53 +00:00
commit bde410c9ad
633 changed files with 108150 additions and 0 deletions
@@ -0,0 +1,195 @@
import React from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Search, Calendar as CalendarIcon, Filter, RefreshCw } from 'lucide-react';
import { format } from 'date-fns';
import { cn } from '@/lib/utils';
interface ApontamentosFiltersProps {
searchTerm: string;
setSearchTerm: (value: string) => void;
filterOF: string;
filterFase: string;
filterProcesso: string;
dataInicio: Date | undefined;
dataFim: Date | undefined;
setDataInicio: (date: Date | undefined) => void;
setDataFim: (date: Date | undefined) => void;
uniqueOFs: string[];
uniqueFases: string[];
uniqueProcessos: string[];
handleOFChange: (value: string) => void;
handleFaseChange: (value: string) => void;
handleProcessoChange: (value: string) => void;
clearFilters: () => void;
}
export const ApontamentosFilters: React.FC<ApontamentosFiltersProps> = ({
searchTerm,
setSearchTerm,
filterOF,
filterFase,
filterProcesso,
dataInicio,
dataFim,
setDataInicio,
setDataFim,
uniqueOFs,
uniqueFases,
uniqueProcessos,
handleOFChange,
handleFaseChange,
handleProcessoChange,
clearFilters
}) => {
return (
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm flex items-center gap-2">
<Filter className="h-4 w-4" />
Filtros Inteligentes
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* Primeira linha de filtros - mais compacta */}
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-2">
<div className="space-y-1">
<label className="text-xs font-medium">Buscar</label>
<div className="relative">
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 h-3 w-3 text-muted-foreground" />
<Input
placeholder="OF, Marca..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-7 h-8 text-xs"
/>
</div>
</div>
<div className="space-y-1">
<label className="text-xs font-medium">OF</label>
<Select value={filterOF || 'all'} onValueChange={handleOFChange}>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Todas as OFs</SelectItem>
{uniqueOFs.map((of) => (
<SelectItem key={of} value={of}>{of}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<label className="text-xs font-medium">Fase</label>
<Select value={filterFase || 'all'} onValueChange={handleFaseChange}>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Todas as fases</SelectItem>
{uniqueFases.map((fase) => (
<SelectItem key={fase} value={fase}>{fase}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<label className="text-xs font-medium">Processo</label>
<Select value={filterProcesso || 'all'} onValueChange={handleProcessoChange}>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Todos os processos</SelectItem>
{uniqueProcessos.map((processo) => (
<SelectItem key={processo} value={processo}>{processo}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex items-end">
<Button variant="outline" onClick={clearFilters} className="h-8 text-xs">
<RefreshCw className="h-3 w-3 mr-1" />
Limpar
</Button>
</div>
</div>
{/* Segunda linha - filtros de data */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-2">
<div className="space-y-1">
<label className="text-xs font-medium">Data Início</label>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className={cn(
"h-8 justify-start text-left font-normal text-xs",
!dataInicio && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-1 h-3 w-3" />
{dataInicio ? format(dataInicio, "dd/MM/yyyy") : "Selecionar"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={dataInicio}
onSelect={setDataInicio}
initialFocus
className="pointer-events-auto"
/>
</PopoverContent>
</Popover>
</div>
<div className="space-y-1">
<label className="text-xs font-medium">Data Fim</label>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className={cn(
"h-8 justify-start text-left font-normal text-xs",
!dataFim && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-1 h-3 w-3" />
{dataFim ? format(dataFim, "dd/MM/yyyy") : "Selecionar"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={dataFim}
onSelect={setDataFim}
initialFocus
className="pointer-events-auto"
/>
</PopoverContent>
</Popover>
</div>
</div>
<div className="flex justify-between items-center">
<div className="text-xs text-muted-foreground">
{filterOF || filterFase || filterProcesso || dataInicio || dataFim ?
'Filtros aplicados automaticamente baseados no último uso' :
'Use os filtros para otimizar a visualização'
}
</div>
</div>
</CardContent>
</Card>
);
};
@@ -0,0 +1,106 @@
import React from 'react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
import { Package, Wrench, Undo2 } from 'lucide-react';
import { format } from 'date-fns';
import { ptBR } from 'date-fns/locale';
import { ApontamentoProducao } from '@/hooks/useApontamentosProducao';
interface ApontamentosListItemProps {
apontamento: ApontamentoProducao;
onRevert: (id: string) => void;
}
export const ApontamentosListItem: React.FC<ApontamentosListItemProps> = ({
apontamento,
onRevert
}) => {
const formatDate = (dateString: string) => {
try {
const date = new Date(dateString + 'T00:00:00');
return format(date, 'dd/MM/yyyy', { locale: ptBR });
} catch (error) {
console.error('Erro ao formatar data:', error);
return dateString;
}
};
const getMarcaItem = (apontamento: ApontamentoProducao) => {
if (apontamento.tipo_apontamento === 'componente') {
return apontamento.componente?.marca_componente || 'N/A';
} else {
return apontamento.peca?.marca || 'N/A';
}
};
return (
<div className="flex items-center justify-between p-3 border rounded-lg hover:bg-muted/50 transition-colors">
<div className="flex items-center gap-3">
{apontamento.tipo_apontamento === 'componente' ? (
<Wrench className="h-4 w-4 text-blue-500" />
) : (
<Package className="h-4 w-4 text-green-500" />
)}
<div className="space-y-1">
<div className="flex items-center gap-2">
<span className="font-medium">
{getMarcaItem(apontamento)}
</span>
<Badge variant="outline">
{apontamento.of_number || 'N/A'}
</Badge>
</div>
<div className="text-sm text-muted-foreground">
{apontamento.processo?.nome || 'N/A'} • {formatDate(apontamento.data_apontamento)}
</div>
</div>
</div>
<div className="flex items-center gap-3">
<div className="text-right">
<div className="font-medium">
{apontamento.quantidade_produzida || 0} un.
</div>
<div className="text-xs text-muted-foreground">
{format(new Date(apontamento.created_at), 'HH:mm', { locale: ptBR })}
</div>
</div>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="outline"
size="sm"
className="text-orange-600 hover:text-orange-700 hover:bg-orange-50"
>
<Undo2 className="h-3 w-3" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Reverter Apontamento</AlertDialogTitle>
<AlertDialogDescription>
Tem certeza de que deseja reverter este apontamento?
Esta ação não pode ser desfeita e irá remover permanentemente o registro de:
<br />
<strong>{getMarcaItem(apontamento)}</strong> - {apontamento.quantidade_produzida} unidades
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancelar</AlertDialogCancel>
<AlertDialogAction
onClick={() => onRevert(apontamento.id)}
className="bg-orange-600 hover:bg-orange-700"
>
Confirmar Reversão
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
);
};
@@ -0,0 +1,54 @@
import React from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Calendar as CalendarIcon } from 'lucide-react';
import { ApontamentosListItem } from './ApontamentosListItem';
import { ApontamentoProducao } from '@/hooks/useApontamentosProducao';
interface ApontamentosResultsListProps {
filteredApontamentos: ApontamentoProducao[];
onRevert: (id: string) => void;
}
export const ApontamentosResultsList: React.FC<ApontamentosResultsListProps> = ({
filteredApontamentos,
onRevert
}) => {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<div className="flex items-center gap-2">
<CalendarIcon className="h-5 w-5" />
Histórico de Apontamentos
</div>
<Badge variant="secondary">
{filteredApontamentos.length} {filteredApontamentos.length === 1 ? 'registro' : 'registros'}
</Badge>
</CardTitle>
</CardHeader>
<CardContent>
{filteredApontamentos.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">
<CalendarIcon className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>Nenhum apontamento encontrado</p>
<p className="text-sm">
Tente ajustar os filtros para ver mais resultados
</p>
</div>
) : (
<div className="space-y-2 max-h-96 overflow-y-auto">
{filteredApontamentos.map((apontamento) => (
<ApontamentosListItem
key={apontamento.id}
apontamento={apontamento}
onRevert={onRevert}
/>
))}
</div>
)}
</CardContent>
</Card>
);
};
@@ -0,0 +1,273 @@
import { useState, useMemo, useEffect } from 'react';
import { ApontamentoProducao } from '@/hooks/useApontamentosProducao';
// Cache para lembrar os últimos filtros utilizados
const historicCache = {
lastOF: '',
lastFase: '',
lastProcesso: ''
};
export const useApontamentosFilters = (apontamentos: ApontamentoProducao[]) => {
const [searchTerm, setSearchTerm] = useState('');
const [filterOF, setFilterOF] = useState('');
const [filterFase, setFilterFase] = useState('');
const [filterProcesso, setFilterProcesso] = useState('');
const [dataInicio, setDataInicio] = useState<Date | undefined>();
const [dataFim, setDataFim] = useState<Date | undefined>();
const [initialized, setInitialized] = useState(false);
// Logs detalhados para debug
useEffect(() => {
console.log(`🔍 DADOS RECEBIDOS NO FILTRO: ${apontamentos.length} apontamentos`);
if (apontamentos.length > 0) {
// Verificar OF B118 especificamente
const b118Records = apontamentos.filter(apt => apt.of_number === 'B118');
console.log(`🎯 OF B118 no filtro: ${b118Records.length} registros`);
// Mostrar alguns exemplos
if (b118Records.length > 0) {
console.log('🔍 Primeiros 5 registros B118:',
b118Records.slice(0, 5).map(apt => ({
id: apt.id,
of: apt.of_number,
peca: apt.peca?.marca,
processo: apt.processo?.nome,
data: apt.data_apontamento
}))
);
}
}
}, [apontamentos]);
// Carregar cache inicial apenas uma vez
useEffect(() => {
if (!initialized) {
console.log(`📊 Inicializando filtros com ${apontamentos.length} apontamentos totais`);
const savedCache = localStorage.getItem('historico_apontamentos_cache');
if (savedCache) {
try {
const parsed = JSON.parse(savedCache);
Object.assign(historicCache, parsed);
console.log('📋 Cache carregado:', historicCache);
} catch (error) {
console.log('⚠️ Erro ao carregar cache do histórico:', error);
}
}
// NÃO aplicar filtros do cache automaticamente - deixar limpo por padrão
setInitialized(true);
}
}, [apontamentos, initialized]);
// Salvar cache quando filtros mudam
const updateCache = (updates: Partial<typeof historicCache>) => {
Object.assign(historicCache, updates);
localStorage.setItem('historico_apontamentos_cache', JSON.stringify(historicCache));
};
// Obter valores únicos para filtros - SEMPRE todos os valores disponíveis
const uniqueOFs = useMemo(() => {
const ofs = apontamentos
.map(apt => apt.of_number)
.filter((of): of is string => {
return typeof of === 'string' && of.trim() !== '';
})
.filter((of, index, array) => array.indexOf(of) === index)
.sort();
console.log(`📋 OFs únicas encontradas: ${ofs.length}`, ofs.slice(0, 10), '...');
return ofs;
}, [apontamentos]);
const uniqueFases = useMemo(() => {
// Se não há OF selecionada, mostrar todas as fases
const apontamentosFiltrados = filterOF && filterOF !== ''
? apontamentos.filter(apt => apt.of_number === filterOF)
: apontamentos;
const fases = apontamentosFiltrados
.map(apt => apt.peca?.etapa_fase)
.filter((fase): fase is string => {
return typeof fase === 'string' && fase.trim() !== '';
})
.filter((fase, index, array) => array.indexOf(fase) === index)
.sort();
console.log(`📋 Fases únicas encontradas para OF "${filterOF}": ${fases.length}`, fases);
return fases;
}, [apontamentos, filterOF]);
const uniqueProcessos = useMemo(() => {
// Aplicar filtros hierárquicos apenas se existirem
let apontamentosFiltrados = apontamentos;
if (filterOF && filterOF !== '') {
apontamentosFiltrados = apontamentosFiltrados.filter(apt => apt.of_number === filterOF);
}
if (filterFase && filterFase !== '') {
apontamentosFiltrados = apontamentosFiltrados.filter(apt => apt.peca?.etapa_fase === filterFase);
}
const processos = apontamentosFiltrados
.map(apt => apt.processo?.nome)
.filter((processo): processo is string => {
return typeof processo === 'string' && processo.trim() !== '';
})
.filter((processo, index, array) => array.indexOf(processo) === index)
.sort();
console.log(`📋 Processos únicos encontrados para OF "${filterOF}" e Fase "${filterFase}": ${processos.length}`, processos);
return processos;
}, [apontamentos, filterOF, filterFase]);
// Filtrar apontamentos - LÓGICA SIMPLIFICADA E CORRIGIDA
const filteredApontamentos = useMemo(() => {
if (!initialized) {
console.log('⏳ Aguardando inicialização...');
return [];
}
console.log(`🔍 INICIANDO FILTRAGEM DE ${apontamentos.length} APONTAMENTOS`);
console.log('🔍 Filtros ativos:', {
searchTerm: searchTerm || 'VAZIO',
filterOF: filterOF || 'VAZIO',
filterFase: filterFase || 'VAZIO',
filterProcesso: filterProcesso || 'VAZIO',
dataInicio: dataInicio ? 'SIM' : 'NÃO',
dataFim: dataFim ? 'SIM' : 'NÃO'
});
let resultado = [...apontamentos]; // Começar com TODOS os apontamentos
// Aplicar filtro de busca textual APENAS se preenchido
if (searchTerm && searchTerm.trim() !== '') {
const searchLower = searchTerm.toLowerCase();
const beforeSearch = resultado.length;
resultado = resultado.filter(apt =>
(apt.of_number && apt.of_number.toLowerCase().includes(searchLower)) ||
(apt.peca?.marca && apt.peca.marca.toLowerCase().includes(searchLower)) ||
(apt.componente?.marca_componente && apt.componente.marca_componente.toLowerCase().includes(searchLower))
);
console.log(`🔎 Filtro texto "${searchTerm}": ${beforeSearch} → ${resultado.length} registros`);
}
// Aplicar filtro por OF APENAS se selecionado
if (filterOF && filterOF !== '' && filterOF !== 'all') {
const beforeOF = resultado.length;
resultado = resultado.filter(apt => apt.of_number === filterOF);
console.log(`🏷️ Filtro OF "${filterOF}": ${beforeOF} → ${resultado.length} registros`);
}
// Aplicar filtro por Fase APENAS se selecionado
if (filterFase && filterFase !== '' && filterFase !== 'all') {
const beforeFase = resultado.length;
resultado = resultado.filter(apt => apt.peca?.etapa_fase === filterFase);
console.log(`📋 Filtro Fase "${filterFase}": ${beforeFase} → ${resultado.length} registros`);
}
// Aplicar filtro por Processo APENAS se selecionado
if (filterProcesso && filterProcesso !== '' && filterProcesso !== 'all') {
const beforeProcesso = resultado.length;
resultado = resultado.filter(apt => apt.processo?.nome === filterProcesso);
console.log(`⚙️ Filtro Processo "${filterProcesso}": ${beforeProcesso} → ${resultado.length} registros`);
}
// Aplicar filtros de data APENAS se definidos
if (dataInicio || dataFim) {
const beforeData = resultado.length;
resultado = resultado.filter(apt => {
const apontamentoDate = new Date(apt.data_apontamento);
let matchesDataInicio = true;
let matchesDataFim = true;
if (dataInicio) {
matchesDataInicio = apontamentoDate >= dataInicio;
}
if (dataFim) {
matchesDataFim = apontamentoDate <= dataFim;
}
return matchesDataInicio && matchesDataFim;
});
console.log(`📅 Filtro Data: ${beforeData} → ${resultado.length} registros`);
}
console.log(`✅ RESULTADO FINAL DA FILTRAGEM: ${resultado.length} de ${apontamentos.length} apontamentos`);
// Log específico para B118 se estiver sendo filtrado
if (filterOF === 'B118') {
console.log(`🎯 Resultado B118: ${resultado.length} registros filtrados`);
}
return resultado;
}, [apontamentos, searchTerm, filterOF, filterFase, filterProcesso, dataInicio, dataFim, initialized]);
const clearFilters = () => {
console.log('🧹 Limpando todos os filtros');
setSearchTerm('');
setFilterOF('');
setFilterFase('');
setFilterProcesso('');
setDataInicio(undefined);
setDataFim(undefined);
// Limpar cache
Object.assign(historicCache, { lastOF: '', lastFase: '', lastProcesso: '' });
localStorage.removeItem('historico_apontamentos_cache');
};
const handleOFChange = (value: string) => {
const selectedValue = value === 'all' ? '' : value;
console.log('🔄 Mudando OF para:', selectedValue);
setFilterOF(selectedValue);
// Limpar fase e processo quando OF muda
setFilterFase('');
setFilterProcesso('');
updateCache({ lastOF: selectedValue, lastFase: '', lastProcesso: '' });
};
const handleFaseChange = (value: string) => {
const selectedValue = value === 'all' ? '' : value;
console.log('🔄 Mudando Fase para:', selectedValue);
setFilterFase(selectedValue);
// Limpar processo quando fase muda
setFilterProcesso('');
updateCache({ lastFase: selectedValue, lastProcesso: '' });
};
const handleProcessoChange = (value: string) => {
const selectedValue = value === 'all' ? '' : value;
console.log('🔄 Mudando Processo para:', selectedValue);
setFilterProcesso(selectedValue);
updateCache({ lastProcesso: selectedValue });
};
return {
searchTerm,
setSearchTerm,
filterOF,
filterFase,
filterProcesso,
dataInicio,
dataFim,
setDataInicio,
setDataFim,
uniqueOFs,
uniqueFases,
uniqueProcessos,
filteredApontamentos,
initialized,
handleOFChange,
handleFaseChange,
handleProcessoChange,
clearFilters
};
};