feat: tratamento superficial automatico da OF e radio buttons em cadastro de OF
This commit is contained in:
@@ -25,6 +25,7 @@ import { toast } from 'sonner';
|
|||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
import { useAppLabels } from '@/hooks/useAppLabels';
|
import { useAppLabels } from '@/hooks/useAppLabels';
|
||||||
import { usePecas } from '@/hooks/usePecas';
|
import { usePecas } from '@/hooks/usePecas';
|
||||||
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
|
||||||
export interface ComponentItem {
|
export interface ComponentItem {
|
||||||
marca: string;
|
marca: string;
|
||||||
@@ -159,6 +160,29 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
}, 50);
|
}, 50);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buscarTratamentoOF = async (ofCode: string): Promise<'pintura' | 'galvanizacao'> => {
|
||||||
|
if (!ofCode) return 'pintura';
|
||||||
|
try {
|
||||||
|
const cleanOf = ofCode.trim();
|
||||||
|
const { data } = await supabase
|
||||||
|
.from('ordens_fabricacao')
|
||||||
|
.select('num_of, tratamento_final')
|
||||||
|
.or(`num_of.eq.${cleanOf},num_of.ilike.%${cleanOf}%`)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (data && data.length > 0 && data[0].tratamento_final) {
|
||||||
|
const trat = data[0].tratamento_final.toLowerCase();
|
||||||
|
if (trat.includes('galv')) {
|
||||||
|
return 'galvanizacao';
|
||||||
|
}
|
||||||
|
return 'pintura';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Aviso ao buscar tratamento da OF:', err);
|
||||||
|
}
|
||||||
|
return 'pintura';
|
||||||
|
};
|
||||||
|
|
||||||
const handleTratamentoChange = (novoTratamento: 'pintura' | 'galvanizacao') => {
|
const handleTratamentoChange = (novoTratamento: 'pintura' | 'galvanizacao') => {
|
||||||
setTratamentoGlobal(novoTratamento);
|
setTratamentoGlobal(novoTratamento);
|
||||||
setExtractedData((prev) =>
|
setExtractedData((prev) =>
|
||||||
@@ -169,12 +193,15 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOfChange = (newOf: string) => {
|
const handleOfChange = async (newOf: string) => {
|
||||||
setHeaderOf(newOf);
|
setHeaderOf(newOf);
|
||||||
|
const trat = await buscarTratamentoOF(newOf);
|
||||||
|
setTratamentoGlobal(trat);
|
||||||
setExtractedData((prev) =>
|
setExtractedData((prev) =>
|
||||||
prev.map((item) => ({
|
prev.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
of: newOf
|
of: newOf,
|
||||||
|
tratamentoSuperficial: trat
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -234,7 +261,7 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
return isNaN(num) ? 0 : num;
|
return isNaN(num) ? 0 : num;
|
||||||
};
|
};
|
||||||
|
|
||||||
const processPdfLines = (items: PdfItem[]) => {
|
const processPdfLines = async (items: PdfItem[]) => {
|
||||||
// 1. Agrupamento robusto de linhas por Clustering de Coordenada Y em cada página
|
// 1. Agrupamento robusto de linhas por Clustering de Coordenada Y em cada página
|
||||||
const pagesMap = new Map<number, PdfItem[]>();
|
const pagesMap = new Map<number, PdfItem[]>();
|
||||||
items.forEach((it) => {
|
items.forEach((it) => {
|
||||||
@@ -539,6 +566,11 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
|
|
||||||
addLog(`Total de Peças Principais extraídas: ${assemblies.length}`);
|
addLog(`Total de Peças Principais extraídas: ${assemblies.length}`);
|
||||||
|
|
||||||
|
const targetOf = defaultOf || headerOf || (assemblies[0] ? assemblies[0].of : '');
|
||||||
|
const detectedTratamento = await buscarTratamentoOF(targetOf);
|
||||||
|
setTratamentoGlobal(detectedTratamento);
|
||||||
|
addLog(`Tratamento superficial obtido automaticamente da OF ${targetOf}: ${detectedTratamento === 'galvanizacao' ? 'Galvanizado' : 'Pintura'}`);
|
||||||
|
|
||||||
const finalPieces: ExtractedPiece[] = assemblies.map((asm) => {
|
const finalPieces: ExtractedPiece[] = assemblies.map((asm) => {
|
||||||
const isComposed = asm.isComposed === 'SIM' && asm.components.length > 0;
|
const isComposed = asm.isComposed === 'SIM' && asm.components.length > 0;
|
||||||
let perfilPrincipal = asm.descricao;
|
let perfilPrincipal = asm.descricao;
|
||||||
@@ -609,7 +641,7 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
comprimentoMax: maxComp > 0 ? maxComp : '-',
|
comprimentoMax: maxComp > 0 ? maxComp : '-',
|
||||||
pesoUnit: pesoUnit > 0 ? pesoUnit.toFixed(2) : '-',
|
pesoUnit: pesoUnit > 0 ? pesoUnit.toFixed(2) : '-',
|
||||||
pesoTotal: totalPeso > 0 ? totalPeso.toFixed(2) : '-',
|
pesoTotal: totalPeso > 0 ? totalPeso.toFixed(2) : '-',
|
||||||
tratamentoSuperficial: 'pintura',
|
tratamentoSuperficial: detectedTratamento,
|
||||||
components: isComposed ? asm.components : []
|
components: isComposed ? asm.components : []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -663,7 +695,7 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addLog(`Total de elementos de texto extraídos: ${allItems.length}`);
|
addLog(`Total de elementos de texto extraídos: ${allItems.length}`);
|
||||||
processPdfLines(allItems);
|
await processPdfLines(allItems);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
const errMsg = err instanceof Error ? err.message : 'Erro desconhecido';
|
const errMsg = err instanceof Error ? err.message : 'Erro desconhecido';
|
||||||
@@ -878,37 +910,21 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Configuração de Tratamento Superficial */}
|
{/* Indicador Automático de Tratamento Superficial da OF */}
|
||||||
<div className="bg-slate-800/80 border border-slate-700/80 rounded-xl p-3 shadow-inner flex flex-col gap-2 min-w-[280px]">
|
<div className="bg-slate-800/80 border border-slate-700/80 rounded-xl p-3 shadow-inner flex flex-col gap-1 min-w-[240px]">
|
||||||
<div className="flex items-center justify-between text-xs font-semibold text-slate-300">
|
<div className="flex items-center justify-between text-xs font-semibold text-slate-300">
|
||||||
<span className="flex items-center gap-1.5 text-indigo-300">
|
<span className="flex items-center gap-1.5 text-indigo-300">
|
||||||
<Paintbrush className="w-3.5 h-3.5 text-indigo-400" />
|
<Paintbrush className="w-3.5 h-3.5 text-indigo-400" />
|
||||||
Tratamento Superficial:
|
Tratamento Superficial:
|
||||||
</span>
|
</span>
|
||||||
<Badge variant="outline" className="text-[10px] bg-slate-900 text-slate-400 border-slate-700">
|
<Badge variant="outline" className="text-[10px] bg-indigo-500/10 text-indigo-300 border-indigo-500/30">
|
||||||
Configuração Global
|
Automático da OF
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-2 pt-1 text-sm font-semibold text-white">
|
||||||
<RadioGroup
|
<span className={`w-2.5 h-2.5 rounded-full ${tratamentoGlobal === 'galvanizacao' ? 'bg-amber-400' : 'bg-blue-400'}`}></span>
|
||||||
value={tratamentoGlobal}
|
{tratamentoGlobal === 'galvanizacao' ? 'Galvanizado (OF)' : 'Pintura (OF)'}
|
||||||
onValueChange={(val) => handleTratamentoChange(val as 'pintura' | 'galvanizacao')}
|
</div>
|
||||||
className="grid grid-cols-2 gap-2"
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-2 bg-slate-900/90 hover:bg-slate-900 px-3 py-1.5 rounded-lg border border-slate-700/70 cursor-pointer transition-colors">
|
|
||||||
<RadioGroupItem value="pintura" id="adv-trat-pintura" className="border-indigo-400 text-indigo-500" />
|
|
||||||
<Label htmlFor="adv-trat-pintura" className="text-xs font-medium text-slate-200 cursor-pointer">
|
|
||||||
Pintura
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-2 bg-slate-900/90 hover:bg-slate-900 px-3 py-1.5 rounded-lg border border-slate-700/70 cursor-pointer transition-colors">
|
|
||||||
<RadioGroupItem value="galvanizacao" id="adv-trat-galvanizacao" className="border-indigo-400 text-indigo-500" />
|
|
||||||
<Label htmlFor="adv-trat-galvanizacao" className="text-xs font-medium text-slate-200 cursor-pointer">
|
|
||||||
Galvanização
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</RadioGroup>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import { format } from 'date-fns';
|
|||||||
import { ptBR } from 'date-fns/locale';
|
import { ptBR } from 'date-fns/locale';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
import { OrdemFabricacao } from '@/hooks/useOFs';
|
import { OrdemFabricacao } from '@/hooks/useOFs';
|
||||||
|
|
||||||
const editOFSchema = z.object({
|
const editOFSchema = z.object({
|
||||||
@@ -360,10 +362,27 @@ export const EditOFModal: React.FC<EditOFModalProps> = ({
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="tratamento_final"
|
name="tratamento_final"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem className="space-y-2">
|
||||||
<FormLabel>Tratamento Final</FormLabel>
|
<FormLabel>Tratamento Final</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} placeholder="Ex: Pintura, Galvanização, etc." />
|
<RadioGroup
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={
|
||||||
|
field.value?.toLowerCase().includes('galv')
|
||||||
|
? 'Galvanizado'
|
||||||
|
: (field.value || 'Pintura')
|
||||||
|
}
|
||||||
|
className="flex items-center gap-6 pt-1.5"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-2 cursor-pointer">
|
||||||
|
<RadioGroupItem value="Pintura" id="edit-trat-pintura" />
|
||||||
|
<Label htmlFor="edit-trat-pintura" className="cursor-pointer font-normal text-sm">Pintura</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2 cursor-pointer">
|
||||||
|
<RadioGroupItem value="Galvanizado" id="edit-trat-galvanizado" />
|
||||||
|
<Label htmlFor="edit-trat-galvanizado" className="cursor-pointer font-normal text-sm">Galvanizado</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -34,13 +34,18 @@ import { ptBR } from 'date-fns/locale';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useNovaOF } from '@/hooks/useNovaOF';
|
import { useNovaOF } from '@/hooks/useNovaOF';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
|
||||||
const novaOFSchema = z.object({
|
const novaOFSchema = z.object({
|
||||||
ficha_tecnica_id: z.string().min(1, 'Ficha técnica é obrigatória'),
|
ficha_tecnica_id: z.string().min(1, 'Ficha técnica é obrigatória'),
|
||||||
data_abertura: z.string().optional(),
|
data_abertura: z.string().optional(),
|
||||||
data_prazo: z.string().optional(),
|
data_prazo: z.string().optional(),
|
||||||
prioridade: z.string().default('Normal'),
|
data_termino_prev: z.string().optional(),
|
||||||
nivel_qualidade: z.string().default('Normal'),
|
peso_total: z.number().optional(),
|
||||||
|
gestor: z.string().optional(),
|
||||||
|
prioridade: z.string(),
|
||||||
|
nivel_qualidade: z.string(),
|
||||||
criterio_qualidade: z.string().optional(),
|
criterio_qualidade: z.string().optional(),
|
||||||
tratamento_final: z.string().optional(),
|
tratamento_final: z.string().optional(),
|
||||||
local_uf: z.string().optional(),
|
local_uf: z.string().optional(),
|
||||||
@@ -66,6 +71,7 @@ export const NovaOFModal: React.FC<NovaOFModalProps> = ({
|
|||||||
data_abertura: new Date().toISOString().split('T')[0],
|
data_abertura: new Date().toISOString().split('T')[0],
|
||||||
prioridade: 'Normal',
|
prioridade: 'Normal',
|
||||||
nivel_qualidade: 'Normal',
|
nivel_qualidade: 'Normal',
|
||||||
|
tratamento_final: 'Pintura',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -323,10 +329,27 @@ export const NovaOFModal: React.FC<NovaOFModalProps> = ({
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="tratamento_final"
|
name="tratamento_final"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem className="space-y-2">
|
||||||
<FormLabel>Tratamento Final</FormLabel>
|
<FormLabel>Tratamento Final</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} placeholder="Ex: Pintura, Galvanização, etc." />
|
<RadioGroup
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={
|
||||||
|
field.value?.toLowerCase().includes('galv')
|
||||||
|
? 'Galvanizado'
|
||||||
|
: (field.value || 'Pintura')
|
||||||
|
}
|
||||||
|
className="flex items-center gap-6 pt-1.5"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-2 cursor-pointer">
|
||||||
|
<RadioGroupItem value="Pintura" id="nova-trat-pintura" />
|
||||||
|
<Label htmlFor="nova-trat-pintura" className="cursor-pointer font-normal text-sm">Pintura</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2 cursor-pointer">
|
||||||
|
<RadioGroupItem value="Galvanizado" id="nova-trat-galvanizado" />
|
||||||
|
<Label htmlFor="nova-trat-galvanizado" className="cursor-pointer font-normal text-sm">Galvanizado</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
Reference in New Issue
Block a user