feat: ordenacao por marca no kanban e regra de quadradinhos de contagem no pdf

This commit is contained in:
2026-09-10 12:51:53 +00:00
parent 50edb79642
commit 51f0f9a914
3 changed files with 241 additions and 83 deletions
@@ -8,7 +8,7 @@ import { Badge } from '@/components/ui/badge';
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
import { ItemPrioridade } from '@/hooks/useItensPrioridadeFabricacao';
import { usePrioridades } from '@/hooks/usePrioridades';
import { Edit, Trash2, GripVertical, X, Search } from 'lucide-react';
import { Edit, Trash2, GripVertical, X, Search, ArrowUpDown } from 'lucide-react';
import { toast } from 'sonner';
interface KanbanPrioridadesFabricacaoProps {
@@ -32,6 +32,8 @@ export const KanbanPrioridadesFabricacao: React.FC<KanbanPrioridadesFabricacaoPr
const [editingItem, setEditingItem] = useState<string | null>(null);
const [editQuantity, setEditQuantity] = useState<number>(1);
const [showDeleteAllDialog, setShowDeleteAllDialog] = useState<string | null>(null);
const [showSortDialog, setShowSortDialog] = useState<string | null>(null);
const [isSorting, setIsSorting] = useState(false);
const [filtrosPecas, setFiltrosPecas] = useState<{ [key: string]: string }>({
P1: '',
P2: '',
@@ -202,6 +204,45 @@ export const KanbanPrioridadesFabricacao: React.FC<KanbanPrioridadesFabricacaoPr
setShowDeleteAllDialog(null);
};
const handleOrdenarPorMarca = async (codigo: string) => {
const itens = itensPorPrioridade[codigo] || [];
if (itens.length <= 1) {
setShowSortDialog(null);
return;
}
try {
setIsSorting(true);
// Ordenação sequencial natural por Marca (tag)
const itensOrdenadosPorMarca = [...itens].sort((a, b) => {
const marcaA = a.peca?.marca || '';
const marcaB = b.peca?.marca || '';
return marcaA.localeCompare(marcaB, undefined, { numeric: true, sensitivity: 'base' });
});
const itensOrdenados = itensOrdenadosPorMarca.map((item, index) => ({
id: item.id,
ordem_fabricacao: index + 1
}));
const primeiroItem = itens[0];
if (primeiroItem?.prioridade_fabricacao_id) {
const sucesso = await onReorderItems(primeiroItem.prioridade_fabricacao_id, itensOrdenados);
if (sucesso) {
toast.success(`Peças da prioridade ${codigo} ordenadas sequencialmente por marca!`);
} else {
toast.error(`Não foi possível salvar a nova ordenação da prioridade ${codigo}`);
}
}
} catch (error) {
console.error('Erro ao ordenar peças por marca:', error);
toast.error('Ocorreu um erro ao tentar ordenar as peças');
} finally {
setIsSorting(false);
setShowSortDialog(null);
}
};
return (
<>
<DragDropContext onDragEnd={handleDragEnd}>
@@ -243,9 +284,9 @@ export const KanbanPrioridadesFabricacao: React.FC<KanbanPrioridadesFabricacaoPr
</div>
</div>
{/* Campo de filtro */}
<div className="mb-3">
<div className="relative">
{/* Campo de filtro e botão de ordenação por marca */}
<div className="mb-3 flex items-center gap-1.5">
<div className="relative flex-1">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Filtrar peça..."
@@ -254,6 +295,17 @@ export const KanbanPrioridadesFabricacao: React.FC<KanbanPrioridadesFabricacaoPr
className="pl-8 h-8 text-sm bg-background/50 border-input/50 focus:bg-background focus:border-input"
/>
</div>
<Button
type="button"
size="sm"
variant="outline"
onClick={() => setShowSortDialog(codigo)}
disabled={itens.length <= 1 || isSorting}
className="h-8 px-2 bg-background/80 hover:bg-background border-input/50 text-xs font-medium flex items-center gap-1 shrink-0"
title="Ordenar sequencialmente por marca (tag)"
>
<ArrowUpDown className="h-3.5 w-3.5 text-primary" />
</Button>
</div>
{/* Área de drop dos itens */}
@@ -377,6 +429,7 @@ export const KanbanPrioridadesFabricacao: React.FC<KanbanPrioridadesFabricacaoPr
</DragDropContext>
{/* Dialog de confirmação para excluir todas as peças */}
{/* Dialog de confirmação para exclusão de todas as peças */}
<AlertDialog open={showDeleteAllDialog !== null} onOpenChange={handleCancelDeleteAll}>
<AlertDialogContent>
<AlertDialogHeader>
@@ -407,6 +460,32 @@ export const KanbanPrioridadesFabricacao: React.FC<KanbanPrioridadesFabricacaoPr
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Dialog de confirmação para ordenação por Marca */}
<AlertDialog open={showSortDialog !== null} onOpenChange={() => setShowSortDialog(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Confirmar Ordenação por Marca</AlertDialogTitle>
<AlertDialogDescription>
Atenção: A ordem manual atual das peças da prioridade <strong>{showSortDialog}</strong> será perdida e substituída pela sequência ordenada de <strong>Marca (Tag)</strong>.
<br /><br />
Tem certeza que deseja aplicar esta ordenação?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setShowSortDialog(null)} disabled={isSorting}>
Cancelar
</AlertDialogCancel>
<AlertDialogAction
onClick={() => showSortDialog && handleOrdenarPorMarca(showSortDialog)}
disabled={isSorting}
className="bg-primary hover:bg-primary/90"
>
{isSorting ? 'Ordenando...' : 'Ordenar por Marca'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
};
@@ -48,59 +48,98 @@ export const PrioridadesPDFTemplate: React.FC<PrioridadesPDFTemplateProps> = ({
};
const generateTickBoxes = (quantity: number) => {
const boxes = [];
if (quantity > 10) {
const numBigBoxes = Math.floor(quantity / 5);
const numSmallBoxes = quantity % 5;
// Quadrados com "5"
for (let i = 0; i < numBigBoxes; i++) {
boxes.push(
<svg
key={`big-${i}`}
width="13"
height="13"
viewBox="0 0 13 13"
style={{ display: 'inline-block', verticalAlign: '-1px', marginRight: '2px' }}
>
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#f3f4f6" stroke="#4b5563" strokeWidth="1" />
<text x="6.5" y="9.5" textAnchor="middle" fontSize="8.5" fontFamily="Arial, sans-serif" fontWeight="bold" fill="#4b5563">5</text>
</svg>
);
}
// Quadrados unitários restantes
for (let i = 0; i < numSmallBoxes; i++) {
boxes.push(
<svg
key={`small-${i}`}
width="13"
height="13"
viewBox="0 0 13 13"
style={{ display: 'inline-block', verticalAlign: '-1px', marginRight: '2px' }}
>
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#ffffff" stroke="#4b5563" strokeWidth="1" />
</svg>
);
}
if (!quantity || quantity <= 0) return null;
let qtd50 = 0;
let qtd10 = 0;
let qtd5 = 0;
let qtd1 = 0;
if (quantity <= 10) {
qtd1 = quantity;
} else if (quantity <= 50) {
qtd5 = Math.floor(quantity / 5);
qtd1 = quantity % 5;
} else if (quantity <= 100) {
qtd10 = Math.floor(quantity / 10);
const resto10 = quantity % 10;
qtd5 = Math.floor(resto10 / 5);
qtd1 = resto10 % 5;
} else {
// Apenas quadrados unitários
for (let i = 0; i < quantity; i++) {
boxes.push(
<svg
key={i}
width="13"
height="13"
viewBox="0 0 13 13"
style={{ display: 'inline-block', verticalAlign: '-1px', marginRight: '2px' }}
>
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#ffffff" stroke="#4b5563" strokeWidth="1" />
</svg>
);
}
// Acima de 100 unidades
qtd50 = Math.floor(quantity / 50);
const resto50 = quantity % 50;
qtd10 = Math.floor(resto50 / 10);
const resto10 = resto50 % 10;
qtd5 = Math.floor(resto10 / 5);
qtd1 = resto10 % 5;
}
const boxes: React.ReactNode[] = [];
// Quadrados de peso 50
for (let i = 0; i < qtd50; i++) {
boxes.push(
<svg
key={`box50-${i}`}
width="13"
height="13"
viewBox="0 0 13 13"
style={{ display: 'inline-block', verticalAlign: '-1px', marginRight: '2px' }}
>
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#f3f4f6" stroke="#4b5563" strokeWidth="1" />
<text x="6.5" y="9.2" textAnchor="middle" fontSize="7" fontFamily="Arial, sans-serif" fontWeight="bold" fill="#374151">50</text>
</svg>
);
}
// Quadrados de peso 10
for (let i = 0; i < qtd10; i++) {
boxes.push(
<svg
key={`box10-${i}`}
width="13"
height="13"
viewBox="0 0 13 13"
style={{ display: 'inline-block', verticalAlign: '-1px', marginRight: '2px' }}
>
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#f3f4f6" stroke="#4b5563" strokeWidth="1" />
<text x="6.5" y="9.2" textAnchor="middle" fontSize="7" fontFamily="Arial, sans-serif" fontWeight="bold" fill="#374151">10</text>
</svg>
);
}
// Quadrados de peso 5
for (let i = 0; i < qtd5; i++) {
boxes.push(
<svg
key={`box5-${i}`}
width="13"
height="13"
viewBox="0 0 13 13"
style={{ display: 'inline-block', verticalAlign: '-1px', marginRight: '2px' }}
>
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#f3f4f6" stroke="#4b5563" strokeWidth="1" />
<text x="6.5" y="9.5" textAnchor="middle" fontSize="8.5" fontFamily="Arial, sans-serif" fontWeight="bold" fill="#4b5563">5</text>
</svg>
);
}
// Quadrados unitários (vazios)
for (let i = 0; i < qtd1; i++) {
boxes.push(
<svg
key={`box1-${i}`}
width="13"
height="13"
viewBox="0 0 13 13"
style={{ display: 'inline-block', verticalAlign: '-1px', marginRight: '2px' }}
>
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#ffffff" stroke="#4b5563" strokeWidth="1" />
</svg>
);
}
return <span style={{ display: 'inline-block', verticalAlign: 'middle', marginLeft: '4px' }}>{boxes}</span>;
};
@@ -221,7 +260,7 @@ export const PrioridadesPDFTemplate: React.FC<PrioridadesPDFTemplateProps> = ({
>
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#ffffff" stroke="#4b5563" strokeWidth="1" />
</svg>
<span>indicam o controle de peças fabricadas.</span>
<span>indicam o controle de peças (quadrados com número = lotes de 50, 10 ou 5 unidades; vazios = 1 unidade).</span>
</div>
{/* Itens por Prioridade */}
+67 -27
View File
@@ -54,34 +54,74 @@ const PrioridadesFabricacao = () => {
};
const generateTickBoxesHTML = (quantity: number): string => {
let boxesHtml = '';
if (quantity > 10) {
const numBigBoxes = Math.floor(quantity / 5);
const numSmallBoxes = quantity % 5;
for (let i = 0; i < numBigBoxes; i++) {
boxesHtml += `
<svg width="13" height="13" viewBox="0 0 13 13" style="display: inline-block; vertical-align: -1px; margin-right: 2px;">
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#f3f4f6" stroke="#4b5563" stroke-width="1" />
<text x="6.5" y="9.5" text-anchor="middle" font-size="8.5" font-family="Arial, sans-serif" font-weight="bold" fill="#4b5563">5</text>
</svg>
`;
}
for (let i = 0; i < numSmallBoxes; i++) {
boxesHtml += `
<svg width="13" height="13" viewBox="0 0 13 13" style="display: inline-block; vertical-align: -1px; margin-right: 2px;">
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#ffffff" stroke="#4b5563" stroke-width="1" />
</svg>
`;
}
if (!quantity || quantity <= 0) return '';
let qtd50 = 0;
let qtd10 = 0;
let qtd5 = 0;
let qtd1 = 0;
if (quantity <= 10) {
qtd1 = quantity;
} else if (quantity <= 50) {
qtd5 = Math.floor(quantity / 5);
qtd1 = quantity % 5;
} else if (quantity <= 100) {
qtd10 = Math.floor(quantity / 10);
const resto10 = quantity % 10;
qtd5 = Math.floor(resto10 / 5);
qtd1 = resto10 % 5;
} else {
for (let i = 0; i < quantity; i++) {
boxesHtml += `
<svg width="13" height="13" viewBox="0 0 13 13" style="display: inline-block; vertical-align: -1px; margin-right: 2px;">
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#ffffff" stroke="#4b5563" stroke-width="1" />
</svg>
`;
}
// Acima de 100 unidades
qtd50 = Math.floor(quantity / 50);
const resto50 = quantity % 50;
qtd10 = Math.floor(resto50 / 10);
const resto10 = resto50 % 10;
qtd5 = Math.floor(resto10 / 5);
qtd1 = resto10 % 5;
}
let boxesHtml = '';
// Quadrados com "50"
for (let i = 0; i < qtd50; i++) {
boxesHtml += `
<svg width="13" height="13" viewBox="0 0 13 13" style="display: inline-block; vertical-align: -1px; margin-right: 2px;">
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#f3f4f6" stroke="#4b5563" stroke-width="1" />
<text x="6.5" y="9.2" text-anchor="middle" font-size="7" font-family="Arial, sans-serif" font-weight="bold" fill="#374151">50</text>
</svg>
`;
}
// Quadrados com "10"
for (let i = 0; i < qtd10; i++) {
boxesHtml += `
<svg width="13" height="13" viewBox="0 0 13 13" style="display: inline-block; vertical-align: -1px; margin-right: 2px;">
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#f3f4f6" stroke="#4b5563" stroke-width="1" />
<text x="6.5" y="9.2" text-anchor="middle" font-size="7" font-family="Arial, sans-serif" font-weight="bold" fill="#374151">10</text>
</svg>
`;
}
// Quadrados com "5"
for (let i = 0; i < qtd5; i++) {
boxesHtml += `
<svg width="13" height="13" viewBox="0 0 13 13" style="display: inline-block; vertical-align: -1px; margin-right: 2px;">
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#f3f4f6" stroke="#4b5563" stroke-width="1" />
<text x="6.5" y="9.5" text-anchor="middle" font-size="8.5" font-family="Arial, sans-serif" font-weight="bold" fill="#4b5563">5</text>
</svg>
`;
}
// Quadrados unitários (vazios)
for (let i = 0; i < qtd1; i++) {
boxesHtml += `
<svg width="13" height="13" viewBox="0 0 13 13" style="display: inline-block; vertical-align: -1px; margin-right: 2px;">
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#ffffff" stroke="#4b5563" stroke-width="1" />
</svg>
`;
}
return `<span style="display: inline-block; vertical-align: middle; margin-left: 4px;">${boxesHtml}</span>`;
};
@@ -309,7 +349,7 @@ const PrioridadesFabricacao = () => {
<svg width="13" height="13" viewBox="0 0 13 13" style="display: inline-block; vertical-align: -2px; margin: 0 4px;">
<rect x="0.5" y="0.5" width="12" height="12" rx="1.5" fill="#ffffff" stroke="#4b5563" stroke-width="1" />
</svg>
<span>indicam o controle de peças fabricadas.</span>
<span>indicam o controle de peças (quadrados com número = lotes de 50, 10 ou 5 unidades; vazios = 1 unidade).</span>
</div>
<div id="main-container" class="space-y-8">