feat: implementar opcao 3 hibrida com acordeao inline e popup de componentes
This commit is contained in:
@@ -24,6 +24,17 @@ import {
|
|||||||
import { toast } from 'sonner';
|
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';
|
||||||
|
|
||||||
|
export interface ComponentItem {
|
||||||
|
marca: string;
|
||||||
|
quantidade: number;
|
||||||
|
perfil: string;
|
||||||
|
material: string;
|
||||||
|
comprimento: number;
|
||||||
|
pesoUnitario?: number;
|
||||||
|
pesoTotal: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ExtractedPiece {
|
export interface ExtractedPiece {
|
||||||
of: string;
|
of: string;
|
||||||
@@ -38,15 +49,7 @@ export interface ExtractedPiece {
|
|||||||
pesoUnit: number | string;
|
pesoUnit: number | string;
|
||||||
pesoTotal: number | string;
|
pesoTotal: number | string;
|
||||||
tratamentoSuperficial: string;
|
tratamentoSuperficial: string;
|
||||||
}
|
components?: ComponentItem[];
|
||||||
|
|
||||||
interface ComponentItem {
|
|
||||||
quantidade: number;
|
|
||||||
perfil: string;
|
|
||||||
material: string;
|
|
||||||
comprimento: number;
|
|
||||||
pesoUnitario?: number;
|
|
||||||
pesoTotal: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AssemblyItem {
|
interface AssemblyItem {
|
||||||
@@ -127,9 +130,11 @@ const ensurePdfJs = async (): Promise<PdfJsLib> => {
|
|||||||
|
|
||||||
const AdvanceSteelConverterContent: React.FC = () => {
|
const AdvanceSteelConverterContent: React.FC = () => {
|
||||||
const { labels, isComponente } = useAppLabels();
|
const { labels, isComponente } = useAppLabels();
|
||||||
|
const { importPecas } = usePecas();
|
||||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [isProcessing, setIsProcessing] = useState(false);
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
|
const [isDirectImporting, setIsDirectImporting] = useState(false);
|
||||||
const [extractedData, setExtractedData] = useState<ExtractedPiece[]>([]);
|
const [extractedData, setExtractedData] = useState<ExtractedPiece[]>([]);
|
||||||
const [headerOf, setHeaderOf] = useState<string>('');
|
const [headerOf, setHeaderOf] = useState<string>('');
|
||||||
const [headerFase, setHeaderFase] = useState<string>('1');
|
const [headerFase, setHeaderFase] = useState<string>('1');
|
||||||
@@ -488,13 +493,15 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
if (currentAssembly) {
|
if (currentAssembly) {
|
||||||
const isSubComp =
|
const isSubComp =
|
||||||
itemMark === '-' ||
|
itemMark === '-' ||
|
||||||
|
isComponente(itemMark) ||
|
||||||
itemMark.includes('-100') ||
|
itemMark.includes('-100') ||
|
||||||
itemMark.includes('-10') ||
|
itemMark.includes('-10') ||
|
||||||
(itemMark.match(/-\d+$/) && parseInt(itemMark.match(/-(\d+)$/)![1], 10) >= 1000);
|
(itemMark.match(/-\d+$/) && parseInt(itemMark.match(/-(\d+)$/)![1], 10) >= (labels.faixaComponenteMin || 1000));
|
||||||
|
|
||||||
if (isSubComp) {
|
if (isSubComp) {
|
||||||
currentAssembly.isComposed = 'SIM';
|
currentAssembly.isComposed = 'SIM';
|
||||||
currentAssembly.components.push({
|
currentAssembly.components.push({
|
||||||
|
marca: itemMark,
|
||||||
quantidade: itemQuant,
|
quantidade: itemQuant,
|
||||||
perfil: itemDesc || currentAssembly.descricao,
|
perfil: itemDesc || currentAssembly.descricao,
|
||||||
material: detectedMaterial || 'A36',
|
material: detectedMaterial || 'A36',
|
||||||
@@ -548,17 +555,15 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (bestComp) {
|
if (bestComp) {
|
||||||
perfilPrincipal = bestComp.perfil || asm.descricao;
|
perfilPrincipal = bestComp.perfil;
|
||||||
materialPrincipal = bestComp.material || 'A36';
|
materialPrincipal = bestComp.material;
|
||||||
maxComp = bestComp.comprimento;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se não obteve subtotal na linha isolada, usar a somatória dos componentes
|
if (sumComponentsWeight > 0) {
|
||||||
if (!totalPeso || totalPeso === 0) {
|
|
||||||
totalPeso = sumComponentsWeight;
|
totalPeso = sumComponentsWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback de segurança para conjunto caso a quantidade na linha 1 não tenha sido lida
|
// Se a quantidade do assembly principal veio 1 mas a soma unitária x total dá a razão
|
||||||
if (asm.quantidade === 1 && sumUnitComponentsWeight > 0 && totalPeso > 0) {
|
if (asm.quantidade === 1 && sumUnitComponentsWeight > 0 && totalPeso > 0) {
|
||||||
const ratio = Math.round(totalPeso / sumUnitComponentsWeight);
|
const ratio = Math.round(totalPeso / sumUnitComponentsWeight);
|
||||||
if (ratio > 1) {
|
if (ratio > 1) {
|
||||||
@@ -566,7 +571,7 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Monopeça simples
|
// Monopeça: se totalPeso não veio, calcula
|
||||||
if (!totalPeso || totalPeso === 0) {
|
if (!totalPeso || totalPeso === 0) {
|
||||||
totalPeso = (asm.pesoUnitario || 0) * asm.quantidade;
|
totalPeso = (asm.pesoUnitario || 0) * asm.quantidade;
|
||||||
}
|
}
|
||||||
@@ -595,7 +600,8 @@ 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: 'pintura',
|
||||||
|
components: isComposed ? asm.components : []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -663,7 +669,8 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
const exportToExcel = () => {
|
const exportToExcel = () => {
|
||||||
if (extractedData.length === 0) return;
|
if (extractedData.length === 0) return;
|
||||||
|
|
||||||
const excelRows = extractedData.map((item) => ({
|
// Aba 1: Lista consolidada de Peças
|
||||||
|
const excelRowsPecas = extractedData.map((item) => ({
|
||||||
OF: item.of,
|
OF: item.of,
|
||||||
Fase: isNaN(Number(item.fase)) ? item.fase : Number(item.fase),
|
Fase: isNaN(Number(item.fase)) ? item.fase : Number(item.fase),
|
||||||
Marca: item.marca,
|
Marca: item.marca,
|
||||||
@@ -672,35 +679,167 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
Quantidade: Number(item.quantidade),
|
Quantidade: Number(item.quantidade),
|
||||||
'Peso Unitário (kg)': item.pesoUnit === '-' ? '' : Number(item.pesoUnit),
|
'Peso Unitário (kg)': item.pesoUnit === '-' ? '' : Number(item.pesoUnit),
|
||||||
'Peso Total (kg)': item.pesoTotal === '-' ? '' : Number(item.pesoTotal),
|
'Peso Total (kg)': item.pesoTotal === '-' ? '' : Number(item.pesoTotal),
|
||||||
'Tratamento Superficial': item.tratamentoSuperficial,
|
'Tratamento Superficial': item.tratamentoSuperficial || tratamentoGlobal,
|
||||||
Material: item.material,
|
Material: item.material,
|
||||||
'Perfil Principal': item.perfilPrincipal,
|
'Perfil Principal': item.perfilPrincipal,
|
||||||
'Comprimento Ref. (mm)': item.comprimentoMax === '-' ? '' : Number(item.comprimentoMax)
|
'Comprimento Ref. (mm)': item.comprimentoMax === '-' ? '' : Number(item.comprimentoMax)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Aba 2: Lista detalhada de Componentes
|
||||||
|
const excelRowsComponentes: any[] = [];
|
||||||
|
extractedData.forEach((item) => {
|
||||||
|
if (item.components && item.components.length > 0) {
|
||||||
|
item.components.forEach((comp) => {
|
||||||
|
const qtdPorPeca = Number(comp.quantidade) || 1;
|
||||||
|
const qtdTotal = qtdPorPeca * Number(item.quantidade);
|
||||||
|
excelRowsComponentes.push({
|
||||||
|
OF: item.of,
|
||||||
|
Fase: isNaN(Number(item.fase)) ? item.fase : Number(item.fase),
|
||||||
|
'Peça Principal': item.marca,
|
||||||
|
'Marca Componente': comp.marca || '-',
|
||||||
|
'Descrição Componente': comp.perfil || item.descricao,
|
||||||
|
Material: comp.material || item.material,
|
||||||
|
'Comprimento (mm)': comp.comprimento || '',
|
||||||
|
'Qtd / Peça': qtdPorPeca,
|
||||||
|
'Qtd Total no Lote': qtdTotal,
|
||||||
|
'Peso Unitário (kg)': comp.pesoUnitario || '',
|
||||||
|
'Peso Total (kg)': comp.pesoTotal || ''
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Aba 3: Formato Plano Oficial para Importação TrackSteel
|
||||||
|
const excelRowsImportacao: any[] = [];
|
||||||
|
extractedData.forEach((item) => {
|
||||||
|
if (item.components && item.components.length > 0) {
|
||||||
|
item.components.forEach((comp) => {
|
||||||
|
excelRowsImportacao.push({
|
||||||
|
of_number: item.of,
|
||||||
|
etapa_fase: item.fase,
|
||||||
|
marca: item.marca,
|
||||||
|
descricao: item.descricao,
|
||||||
|
quantidade: Number(item.quantidade),
|
||||||
|
peso_unitario: item.pesoUnit === '-' ? 0 : Number(item.pesoUnit),
|
||||||
|
peso_total: item.pesoTotal === '-' ? 0 : Number(item.pesoTotal),
|
||||||
|
tratamento_superficial: item.tratamentoSuperficial || tratamentoGlobal,
|
||||||
|
material: item.material,
|
||||||
|
perfil_principal: item.perfilPrincipal,
|
||||||
|
tem_componentes: true,
|
||||||
|
marca_componente: comp.marca || '',
|
||||||
|
descricao_componente: comp.perfil || item.descricao,
|
||||||
|
perfil_componente: comp.perfil || item.descricao,
|
||||||
|
peso_unitario_componente: comp.pesoUnitario || 0,
|
||||||
|
quantidade_por_peca: Number(comp.quantidade) || 1
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
excelRowsImportacao.push({
|
||||||
|
of_number: item.of,
|
||||||
|
etapa_fase: item.fase,
|
||||||
|
marca: item.marca,
|
||||||
|
descricao: item.descricao,
|
||||||
|
quantidade: Number(item.quantidade),
|
||||||
|
peso_unitario: item.pesoUnit === '-' ? 0 : Number(item.pesoUnit),
|
||||||
|
peso_total: item.pesoTotal === '-' ? 0 : Number(item.pesoTotal),
|
||||||
|
tratamento_superficial: item.tratamentoSuperficial || tratamentoGlobal,
|
||||||
|
material: item.material,
|
||||||
|
perfil_principal: item.perfilPrincipal,
|
||||||
|
tem_componentes: false,
|
||||||
|
marca_componente: '',
|
||||||
|
descricao_componente: '',
|
||||||
|
perfil_componente: '',
|
||||||
|
peso_unitario_componente: 0,
|
||||||
|
quantidade_por_peca: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const wb = XLSX.utils.book_new();
|
const wb = XLSX.utils.book_new();
|
||||||
const ws = XLSX.utils.json_to_sheet(excelRows);
|
const wsPecas = XLSX.utils.json_to_sheet(excelRowsPecas);
|
||||||
|
wsPecas['!cols'] = [
|
||||||
ws['!cols'] = [
|
{ wch: 12 }, { wch: 8 }, { wch: 12 }, { wch: 20 }, { wch: 15 },
|
||||||
{ wch: 12 },
|
{ wch: 12 }, { wch: 18 }, { wch: 16 }, { wch: 22 }, { wch: 16 },
|
||||||
{ wch: 8 },
|
{ wch: 22 }, { wch: 20 }
|
||||||
{ wch: 12 },
|
|
||||||
{ wch: 20 },
|
|
||||||
{ wch: 15 },
|
|
||||||
{ wch: 12 },
|
|
||||||
{ wch: 18 },
|
|
||||||
{ wch: 16 },
|
|
||||||
{ wch: 22 },
|
|
||||||
{ wch: 16 },
|
|
||||||
{ wch: 22 },
|
|
||||||
{ wch: 20 }
|
|
||||||
];
|
];
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsPecas, 'Lista_Pecas');
|
||||||
|
|
||||||
|
if (excelRowsComponentes.length > 0) {
|
||||||
|
const wsComp = XLSX.utils.json_to_sheet(excelRowsComponentes);
|
||||||
|
wsComp['!cols'] = [
|
||||||
|
{ wch: 12 }, { wch: 8 }, { wch: 16 }, { wch: 18 }, { wch: 22 },
|
||||||
|
{ wch: 16 }, { wch: 18 }, { wch: 12 }, { wch: 16 }, { wch: 18 }, { wch: 16 }
|
||||||
|
];
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsComp, 'Lista_Componentes');
|
||||||
|
}
|
||||||
|
|
||||||
|
const wsImport = XLSX.utils.json_to_sheet(excelRowsImportacao);
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsImport, 'Importacao_TrackSteel');
|
||||||
|
|
||||||
XLSX.utils.book_append_sheet(wb, ws, 'Lista_Pecas');
|
|
||||||
const outFileName = `${headerOf || currentFileName}_Fase_${headerFase || '1'}_Lista_Pecas_AdvanceSteel.xlsx`;
|
const outFileName = `${headerOf || currentFileName}_Fase_${headerFase || '1'}_Lista_Pecas_AdvanceSteel.xlsx`;
|
||||||
XLSX.writeFile(wb, outFileName);
|
XLSX.writeFile(wb, outFileName);
|
||||||
addLog(`Planilha Excel baixada: ${outFileName}`);
|
addLog(`Planilha Excel baixada com ${excelRowsPecas.length} peças e ${excelRowsComponentes.length} componentes: ${outFileName}`);
|
||||||
toast.success(`Planilha padrão baixada com sucesso: ${outFileName}`);
|
toast.success(`Planilha gerada com sucesso: ${outFileName}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDirectImport = async () => {
|
||||||
|
if (extractedData.length === 0) return;
|
||||||
|
setIsDirectImporting(true);
|
||||||
|
try {
|
||||||
|
const importacaoRows: any[] = [];
|
||||||
|
extractedData.forEach((item) => {
|
||||||
|
if (item.components && item.components.length > 0) {
|
||||||
|
item.components.forEach((comp) => {
|
||||||
|
importacaoRows.push({
|
||||||
|
of_number: item.of,
|
||||||
|
etapa_fase: item.fase,
|
||||||
|
marca: item.marca,
|
||||||
|
descricao: item.descricao,
|
||||||
|
quantidade: Number(item.quantidade),
|
||||||
|
peso_unitario: item.pesoUnit === '-' ? 0 : Number(item.pesoUnit),
|
||||||
|
peso_total: item.pesoTotal === '-' ? 0 : Number(item.pesoTotal),
|
||||||
|
tratamento_superficial: item.tratamentoSuperficial || tratamentoGlobal,
|
||||||
|
material: item.material,
|
||||||
|
perfil_principal: item.perfilPrincipal,
|
||||||
|
tem_componentes: true,
|
||||||
|
marca_componente: comp.marca || '',
|
||||||
|
descricao_componente: comp.perfil || item.descricao,
|
||||||
|
perfil_componente: comp.perfil || item.descricao,
|
||||||
|
peso_unitario_componente: comp.pesoUnitario || 0,
|
||||||
|
quantidade_por_peca: Number(comp.quantidade) || 1
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
importacaoRows.push({
|
||||||
|
of_number: item.of,
|
||||||
|
etapa_fase: item.fase,
|
||||||
|
marca: item.marca,
|
||||||
|
descricao: item.descricao,
|
||||||
|
quantidade: Number(item.quantidade),
|
||||||
|
peso_unitario: item.pesoUnit === '-' ? 0 : Number(item.pesoUnit),
|
||||||
|
peso_total: item.pesoTotal === '-' ? 0 : Number(item.pesoTotal),
|
||||||
|
tratamento_superficial: item.tratamentoSuperficial || tratamentoGlobal,
|
||||||
|
material: item.material,
|
||||||
|
perfil_principal: item.perfilPrincipal,
|
||||||
|
tem_componentes: false,
|
||||||
|
marca_componente: '',
|
||||||
|
descricao_componente: '',
|
||||||
|
perfil_componente: '',
|
||||||
|
peso_unitario_componente: 0,
|
||||||
|
quantidade_por_peca: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await importPecas(importacaoRows);
|
||||||
|
toast.success(`${extractedData.length} peças e seus componentes foram importados com sucesso!`);
|
||||||
|
addLog(`✅ Importação concluída: ${extractedData.length} peças gravadas no banco.`);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('Erro na importação direta:', err);
|
||||||
|
toast.error(`Falha ao importar: ${err?.message || 'Erro no banco'}`);
|
||||||
|
} finally {
|
||||||
|
setIsDirectImporting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const totalPesoGeral = extractedData.reduce((acc, curr) => {
|
const totalPesoGeral = extractedData.reduce((acc, curr) => {
|
||||||
@@ -880,10 +1019,29 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={exportToExcel}
|
onClick={exportToExcel}
|
||||||
className="bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs px-4 h-9 shadow-lg shadow-emerald-950/40"
|
variant="outline"
|
||||||
|
className="border-emerald-600/60 text-emerald-400 hover:bg-emerald-600/10 font-semibold text-xs px-3.5 h-9"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-1.5" />
|
<Download className="w-4 h-4 mr-1.5" />
|
||||||
Baixar Planilha Padrão (.xlsx)
|
Baixar Planilha Excel
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleDirectImport}
|
||||||
|
disabled={isDirectImporting}
|
||||||
|
className="bg-indigo-600 hover:bg-indigo-500 text-white font-semibold text-xs px-4 h-9 shadow-lg shadow-indigo-950/40"
|
||||||
|
>
|
||||||
|
{isDirectImporting ? (
|
||||||
|
<>
|
||||||
|
<RefreshCw className="w-4 h-4 mr-1.5 animate-spin" />
|
||||||
|
Gravando no Banco...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Upload className="w-4 h-4 mr-1.5" />
|
||||||
|
Importar no Sistema (Peças + Componentes)
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
import { SortField, SortOrder } from '@/hooks/usePecasTable';
|
import { SortField, SortOrder } from '@/hooks/usePecasTable';
|
||||||
|
import { useAppLabels } from '@/hooks/useAppLabels';
|
||||||
|
|
||||||
interface TableHeaderSortableProps {
|
interface TableHeaderSortableProps {
|
||||||
selectAll: boolean;
|
selectAll: boolean;
|
||||||
@@ -21,6 +22,8 @@ export function TableHeaderSortable({
|
|||||||
sortOrder,
|
sortOrder,
|
||||||
onSort
|
onSort
|
||||||
}: TableHeaderSortableProps) {
|
}: TableHeaderSortableProps) {
|
||||||
|
const { labels } = useAppLabels();
|
||||||
|
|
||||||
const SortButton = ({ field, children }: { field: SortField; children: React.ReactNode }) => (
|
const SortButton = ({ field, children }: { field: SortField; children: React.ReactNode }) => (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -50,15 +53,15 @@ export function TableHeaderSortable({
|
|||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
<TableHead className="w-16 px-2">
|
<TableHead className="w-16 px-2">
|
||||||
<SortButton field="of_number">OF</SortButton>
|
<SortButton field="of_number">{labels.ofLabel}</SortButton>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
<TableHead className="w-12 px-2">
|
<TableHead className="w-12 px-2">
|
||||||
<SortButton field="etapa_fase">Fase</SortButton>
|
<SortButton field="etapa_fase">{labels.faseLabel}</SortButton>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
<TableHead className="w-14 px-2">
|
<TableHead className="w-14 px-2">
|
||||||
<SortButton field="marca">Marca</SortButton>
|
<SortButton field="marca">{labels.pecaLabel}</SortButton>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
<TableHead className="w-32 px-2">
|
<TableHead className="w-32 px-2">
|
||||||
@@ -70,7 +73,7 @@ export function TableHeaderSortable({
|
|||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
<TableHead className="w-16 px-2 text-center">
|
<TableHead className="w-16 px-2 text-center">
|
||||||
<SortButton field="tem_componentes">Sem Comp.</SortButton>
|
<SortButton field="tem_componentes">Tipo / {labels.componenteLabel}</SortButton>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
<TableHead className="w-12 px-2 text-right">
|
<TableHead className="w-12 px-2 text-right">
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
import React from 'react';
|
|
||||||
import { TableCell, TableRow as UITableRow } from '@/components/ui/table';
|
import { TableCell, TableRow as UITableRow } from '@/components/ui/table';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { Edit, Trash2, Package, Check, X } from 'lucide-react';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import {
|
||||||
|
Edit,
|
||||||
|
Trash2,
|
||||||
|
Package,
|
||||||
|
Check,
|
||||||
|
X,
|
||||||
|
ChevronRight,
|
||||||
|
ChevronDown,
|
||||||
|
Layers,
|
||||||
|
Loader2,
|
||||||
|
ExternalLink
|
||||||
|
} from 'lucide-react';
|
||||||
import { Peca } from '@/hooks/usePecas';
|
import { Peca } from '@/hooks/usePecas';
|
||||||
|
import { useComponentesPeca } from '@/hooks/useComponentesPeca';
|
||||||
|
import { useAppLabels } from '@/hooks/useAppLabels';
|
||||||
import { PriorityBadge } from '../PriorityBadge';
|
import { PriorityBadge } from '../PriorityBadge';
|
||||||
|
|
||||||
interface TableRowProps {
|
interface TableRowProps {
|
||||||
@@ -24,107 +37,257 @@ export function TableRow({
|
|||||||
onEdit,
|
onEdit,
|
||||||
onDelete
|
onDelete
|
||||||
}: TableRowProps) {
|
}: TableRowProps) {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const { labels } = useAppLabels();
|
||||||
|
const { componentes, loading } = useComponentesPeca(isExpanded ? peca.id : '');
|
||||||
|
|
||||||
|
const hasSubComponents = peca.tem_componentes;
|
||||||
|
|
||||||
|
const toggleExpand = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsExpanded(prev => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UITableRow className="hover:bg-muted/50">
|
<>
|
||||||
<TableCell className="w-8 px-2">
|
<UITableRow
|
||||||
<Checkbox
|
className={`hover:bg-muted/50 transition-colors ${
|
||||||
checked={isSelected}
|
isExpanded ? 'bg-blue-50/40 dark:bg-blue-950/20 border-b-0' : ''
|
||||||
onCheckedChange={(checked) => onSelect(checked as boolean)}
|
}`}
|
||||||
aria-label={`Selecionar peça ${peca.id}`}
|
>
|
||||||
/>
|
<TableCell className="w-8 px-2">
|
||||||
</TableCell>
|
<div className="flex items-center gap-1">
|
||||||
|
<Checkbox
|
||||||
|
checked={isSelected}
|
||||||
|
onCheckedChange={(checked) => onSelect(checked as boolean)}
|
||||||
|
aria-label={`Selecionar peça ${peca.id}`}
|
||||||
|
/>
|
||||||
|
{hasSubComponents && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={toggleExpand}
|
||||||
|
className="p-0.5 text-slate-400 hover:text-blue-600 dark:hover:text-blue-400 rounded transition-colors"
|
||||||
|
title={isExpanded ? 'Recolher componentes' : 'Expandir componentes inline'}
|
||||||
|
>
|
||||||
|
{isExpanded ? (
|
||||||
|
<ChevronDown className="h-3.5 w-3.5 text-blue-600 dark:text-blue-400 font-bold" />
|
||||||
|
) : (
|
||||||
|
<ChevronRight className="h-3.5 w-3.5 text-slate-500 hover:text-blue-600 font-bold" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="w-16 px-2 font-medium text-foreground text-xs">
|
<TableCell className="w-16 px-2 font-medium text-foreground text-xs font-mono">
|
||||||
{peca.of_number}
|
{peca.of_number}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="w-12 px-2 text-muted-foreground text-xs">
|
<TableCell className="w-12 px-2 text-muted-foreground text-xs font-mono">
|
||||||
{peca.etapa_fase}
|
{peca.etapa_fase}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="w-14 px-2 text-muted-foreground text-xs">
|
<TableCell className="w-14 px-2 text-foreground text-xs font-bold font-mono">
|
||||||
{peca.marca}
|
<div className="flex items-center gap-1.5">
|
||||||
</TableCell>
|
<span>{peca.marca}</span>
|
||||||
|
{hasSubComponents && (
|
||||||
|
<span
|
||||||
|
onClick={toggleExpand}
|
||||||
|
className="cursor-pointer text-[10px] bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300 px-1 py-0.2 rounded font-semibold"
|
||||||
|
title="Possui subcomponentes"
|
||||||
|
>
|
||||||
|
Comp
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="w-32 px-2 text-foreground text-xs">
|
<TableCell className="w-32 px-2 text-foreground text-xs">
|
||||||
{peca.descricao}
|
{peca.descricao}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="w-16 px-2">
|
<TableCell className="w-16 px-2">
|
||||||
<PriorityBadge prioridade={peca.prioridade as any} />
|
<PriorityBadge prioridade={peca.prioridade as any} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="w-16 px-2 text-center">
|
<TableCell className="w-16 px-2 text-center">
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
{!peca.tem_componentes ? (
|
{!peca.tem_componentes ? (
|
||||||
<div className="flex items-center justify-center w-5 h-5 bg-green-500 rounded-full shadow-sm">
|
<div className="flex items-center justify-center w-5 h-5 bg-green-500 rounded-full shadow-sm" title="Monopeça (sem componentes)">
|
||||||
<Check className="h-3 w-3 text-white font-bold" />
|
<Check className="h-3 w-3 text-white font-bold" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center w-5 h-5 bg-red-500 rounded-full shadow-sm">
|
<div
|
||||||
<X className="h-3 w-3 text-white font-bold" />
|
onClick={toggleExpand}
|
||||||
</div>
|
className="cursor-pointer flex items-center justify-center w-5 h-5 bg-blue-500 hover:bg-blue-600 rounded-full shadow-sm transition-transform hover:scale-105"
|
||||||
)}
|
title="Peça composta por componentes. Clique para abrir."
|
||||||
</div>
|
>
|
||||||
</TableCell>
|
<Layers className="h-3 w-3 text-white font-bold" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="w-12 px-2 text-right text-muted-foreground text-xs">
|
<TableCell className="w-12 px-2 text-right font-medium text-foreground text-xs">
|
||||||
{peca.quantidade}
|
{peca.quantidade}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="w-16 px-2 text-right text-muted-foreground text-xs">
|
<TableCell className="w-16 px-2 text-right text-muted-foreground text-xs">
|
||||||
{peca.peso_unitario} kg
|
{peca.peso_unitario} kg
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="w-16 px-2 text-right font-medium text-foreground text-xs">
|
<TableCell className="w-16 px-2 text-right font-semibold text-foreground text-xs">
|
||||||
{(peca.peso_total || (peca.quantidade * peca.peso_unitario)).toFixed(2)} kg
|
{(peca.peso_total || (peca.quantidade * peca.peso_unitario)).toFixed(2)} kg
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="w-16 px-2 text-muted-foreground text-xs">
|
<TableCell className="w-16 px-2 text-muted-foreground text-xs">
|
||||||
{peca.material || '-'}
|
{peca.material || '-'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="w-16 px-2 text-muted-foreground text-xs">
|
<TableCell className="w-16 px-2 text-muted-foreground text-xs">
|
||||||
{peca.perfil_principal || '-'}
|
{peca.perfil_principal || '-'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="w-20 px-2">
|
<TableCell className="w-20 px-2">
|
||||||
<div className="flex items-center gap-1 justify-end">
|
<div className="flex items-center gap-1 justify-end">
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={onOpenComponentPopup}
|
|
||||||
title="Ver componentes"
|
|
||||||
className="h-6 w-6 p-0"
|
|
||||||
>
|
|
||||||
<Package className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{onEdit && (
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onEdit}
|
onClick={onOpenComponentPopup}
|
||||||
title="Editar peça"
|
title={`Gerenciar ${labels.componentePlural}`}
|
||||||
className="h-6 w-6 p-0"
|
className="h-6 w-6 p-0 text-blue-600 hover:text-blue-700 hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-950"
|
||||||
>
|
>
|
||||||
<Edit className="h-3 w-3" />
|
<Package className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
|
|
||||||
{onDelete && (
|
{onEdit && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onDelete}
|
onClick={onEdit}
|
||||||
title="Excluir peça"
|
title="Editar peça"
|
||||||
className="h-6 w-6 p-0 text-destructive hover:text-destructive"
|
className="h-6 w-6 p-0"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3" />
|
<Edit className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</TableCell>
|
{onDelete && (
|
||||||
</UITableRow>
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={onDelete}
|
||||||
|
title="Excluir peça"
|
||||||
|
className="h-6 w-6 p-0 text-destructive hover:text-destructive"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</UITableRow>
|
||||||
|
|
||||||
|
{/* Sub-linha Expansível de Componentes (Acordeão / Master-Detail Inline) */}
|
||||||
|
{isExpanded && (
|
||||||
|
<UITableRow className="bg-slate-50/90 dark:bg-slate-900/80 border-b border-slate-200 dark:border-slate-800">
|
||||||
|
<TableCell colSpan={13} className="p-0">
|
||||||
|
<div className="p-3 pl-8 pr-4 border-l-4 border-l-blue-500 bg-gradient-to-r from-blue-50/50 via-slate-50/30 to-transparent dark:from-blue-950/30 dark:via-slate-900/30">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Layers className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
||||||
|
<span className="text-xs font-bold text-slate-800 dark:text-slate-200">
|
||||||
|
{labels.componentePlural} da {labels.pecaLabel} {peca.marca}
|
||||||
|
</span>
|
||||||
|
<Badge variant="outline" className="text-[10px] bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-900/40 dark:text-blue-300">
|
||||||
|
{loading ? 'Carregando...' : `${componentes.length} subitens cadastrados`}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-[11px] text-slate-500">
|
||||||
|
• Lote total da peça: {peca.quantidade} un
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={onOpenComponentPopup}
|
||||||
|
className="h-6 text-[11px] px-2 text-blue-600 border-blue-200 hover:bg-blue-50 dark:text-blue-400 dark:border-blue-800 dark:hover:bg-blue-950"
|
||||||
|
>
|
||||||
|
<ExternalLink className="w-3 h-3 mr-1" />
|
||||||
|
Abrir Gerenciador Completo
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div className="py-4 flex items-center justify-center gap-2 text-xs text-slate-500">
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin text-blue-600" />
|
||||||
|
Buscando {labels.componentePlural.toLowerCase()} no banco...
|
||||||
|
</div>
|
||||||
|
) : componentes.length === 0 ? (
|
||||||
|
<div className="py-3 px-4 rounded-lg bg-amber-50/60 border border-amber-200/70 dark:bg-amber-950/20 dark:border-amber-900/40 flex items-center justify-between">
|
||||||
|
<span className="text-xs text-amber-800 dark:text-amber-300">
|
||||||
|
Nenhum {labels.componenteLabel.toLowerCase()} individual foi vinculado a esta peça no banco ainda.
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={onOpenComponentPopup}
|
||||||
|
className="h-6 text-xs bg-amber-600 hover:bg-amber-700 text-white font-medium px-2.5"
|
||||||
|
>
|
||||||
|
+ Cadastrar Componente
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="rounded-lg border border-slate-200 dark:border-slate-800 overflow-hidden bg-white dark:bg-slate-900 shadow-sm">
|
||||||
|
<table className="w-full text-xs text-left border-collapse">
|
||||||
|
<thead className="bg-slate-100/80 dark:bg-slate-800/80 text-[11px] font-semibold text-slate-600 dark:text-slate-300 border-b border-slate-200 dark:border-slate-700">
|
||||||
|
<tr>
|
||||||
|
<th className="p-2 pl-3">Marca {labels.componenteLabel}</th>
|
||||||
|
<th className="p-2">Descrição / Perfil</th>
|
||||||
|
<th className="p-2 text-center">Qtd / {labels.pecaLabel}</th>
|
||||||
|
<th className="p-2 text-center font-bold">Qtd Total no Lote</th>
|
||||||
|
<th className="p-2 text-right">Peso Unit. (kg)</th>
|
||||||
|
<th className="p-2 text-right">Peso Total (kg)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-slate-100 dark:divide-slate-800 font-sans">
|
||||||
|
{componentes.map((comp) => {
|
||||||
|
const qtdPorPeca = comp.quantidade_por_peca || 1;
|
||||||
|
const qtdTotalLote = qtdPorPeca * (peca.quantidade || 1);
|
||||||
|
const pesoUnit = comp.peso_unitario || 0;
|
||||||
|
const pesoTotalLote = qtdTotalLote * pesoUnit;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={comp.id} className="hover:bg-slate-50 dark:hover:bg-slate-800/50">
|
||||||
|
<td className="p-2 pl-3 font-mono font-bold text-blue-600 dark:text-blue-400">
|
||||||
|
<Badge variant="outline" className="bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-300 font-mono text-[11px]">
|
||||||
|
{comp.marca_componente}
|
||||||
|
</Badge>
|
||||||
|
</td>
|
||||||
|
<td className="p-2 text-slate-700 dark:text-slate-300 font-medium">
|
||||||
|
{comp.perfil || comp.descricao || '-'}
|
||||||
|
</td>
|
||||||
|
<td className="p-2 text-center font-mono text-slate-600 dark:text-slate-400">
|
||||||
|
{qtdPorPeca} un
|
||||||
|
</td>
|
||||||
|
<td className="p-2 text-center font-mono font-bold text-slate-900 dark:text-white bg-slate-50/50 dark:bg-slate-800/30">
|
||||||
|
{qtdTotalLote} un
|
||||||
|
</td>
|
||||||
|
<td className="p-2 text-right font-mono text-slate-600 dark:text-slate-400">
|
||||||
|
{pesoUnit > 0 ? `${pesoUnit.toFixed(2)} kg` : '-'}
|
||||||
|
</td>
|
||||||
|
<td className="p-2 text-right font-mono font-bold text-slate-900 dark:text-white">
|
||||||
|
{pesoTotalLote > 0 ? `${pesoTotalLote.toFixed(2)} kg` : '-'}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</UITableRow>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { usePecas, Peca } from '@/hooks/usePecas';
|
|||||||
import { usePecasP4Listener } from '@/hooks/usePecasP4Listener';
|
import { usePecasP4Listener } from '@/hooks/usePecasP4Listener';
|
||||||
import { PecaForm } from '@/components/pecas/PecaForm';
|
import { PecaForm } from '@/components/pecas/PecaForm';
|
||||||
import { PecasTable } from '@/components/pecas/PecasTable';
|
import { PecasTable } from '@/components/pecas/PecasTable';
|
||||||
|
import { ComponentesPopup } from '@/components/pecas/ComponentesPopup';
|
||||||
import { Package, Plus, RefreshCw } from 'lucide-react';
|
import { Package, Plus, RefreshCw } from 'lucide-react';
|
||||||
import { usePermissionControl } from '@/hooks/usePermissionControl';
|
import { usePermissionControl } from '@/hooks/usePermissionControl';
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ export default function CadastroPecas() {
|
|||||||
const [syncing, setSyncing] = useState(false);
|
const [syncing, setSyncing] = useState(false);
|
||||||
const [editingPeca, setEditingPeca] = useState<Peca | null>(null);
|
const [editingPeca, setEditingPeca] = useState<Peca | null>(null);
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
|
const [selectedPecaForPopup, setSelectedPecaForPopup] = useState<Peca | null>(null);
|
||||||
|
|
||||||
const handleSave = async (formData: any) => {
|
const handleSave = async (formData: any) => {
|
||||||
if (!canCreate()) return false;
|
if (!canCreate()) return false;
|
||||||
@@ -92,8 +94,12 @@ export default function CadastroPecas() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenComponentPopup = (pecaId: string) => {
|
const handleOpenComponentPopup = (pecaId: string) => {
|
||||||
console.log('Abrir popup de componentes para peça:', pecaId);
|
const found = pecas.find(p => p.id === pecaId);
|
||||||
toast.info('Funcionalidade de componentes será implementada em breve');
|
if (found) {
|
||||||
|
setSelectedPecaForPopup(found);
|
||||||
|
} else {
|
||||||
|
toast.error('Peça não encontrada');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImportPecas = async (pecasData: any[]) => {
|
const handleImportPecas = async (pecasData: any[]) => {
|
||||||
@@ -235,6 +241,18 @@ export default function CadastroPecas() {
|
|||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Modal de Gerenciamento de Componentes */}
|
||||||
|
{selectedPecaForPopup && (
|
||||||
|
<ComponentesPopup
|
||||||
|
isOpen={!!selectedPecaForPopup}
|
||||||
|
onClose={() => {
|
||||||
|
setSelectedPecaForPopup(null);
|
||||||
|
loadPecas();
|
||||||
|
}}
|
||||||
|
peca={selectedPecaForPopup}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|||||||
import { ArrowLeft, Plus, Package } from 'lucide-react';
|
import { ArrowLeft, Plus, Package } from 'lucide-react';
|
||||||
import { PecaForm } from '@/components/pecas/PecaForm';
|
import { PecaForm } from '@/components/pecas/PecaForm';
|
||||||
import { PecasTable } from '@/components/pecas/PecasTable';
|
import { PecasTable } from '@/components/pecas/PecasTable';
|
||||||
|
import { ComponentesPopup } from '@/components/pecas/ComponentesPopup';
|
||||||
import { usePecas, Peca } from '@/hooks/usePecas';
|
import { usePecas, Peca } from '@/hooks/usePecas';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { usePermissionControl } from '@/hooks/usePermissionControl';
|
import { usePermissionControl } from '@/hooks/usePermissionControl';
|
||||||
@@ -35,6 +36,7 @@ export default function CadastroPecasFiltrado() {
|
|||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [editingPeca, setEditingPeca] = useState<Peca | null>(null);
|
const [editingPeca, setEditingPeca] = useState<Peca | null>(null);
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
|
const [selectedPecaForPopup, setSelectedPecaForPopup] = useState<Peca | null>(null);
|
||||||
|
|
||||||
// Filtrar peças pela OF selecionada
|
// Filtrar peças pela OF selecionada
|
||||||
const pecasFiltradas = pecas.filter(peca => peca.of_number === ofSelecionada);
|
const pecasFiltradas = pecas.filter(peca => peca.of_number === ofSelecionada);
|
||||||
@@ -106,8 +108,12 @@ export default function CadastroPecasFiltrado() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenComponentPopup = (pecaId: string) => {
|
const handleOpenComponentPopup = (pecaId: string) => {
|
||||||
console.log('Abrir popup de componentes para peça:', pecaId);
|
const found = pecas.find(p => p.id === pecaId);
|
||||||
toast.info('Funcionalidade de componentes será implementada em breve');
|
if (found) {
|
||||||
|
setSelectedPecaForPopup(found);
|
||||||
|
} else {
|
||||||
|
toast.error('Peça não encontrada');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImportPecas = async (pecasData: any[]) => {
|
const handleImportPecas = async (pecasData: any[]) => {
|
||||||
@@ -282,6 +288,18 @@ export default function CadastroPecasFiltrado() {
|
|||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Modal de Gerenciamento de Componentes */}
|
||||||
|
{selectedPecaForPopup && (
|
||||||
|
<ComponentesPopup
|
||||||
|
isOpen={!!selectedPecaForPopup}
|
||||||
|
onClose={() => {
|
||||||
|
setSelectedPecaForPopup(null);
|
||||||
|
loadPecas();
|
||||||
|
}}
|
||||||
|
peca={selectedPecaForPopup}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user