Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions components/auth/login-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export function LoginForm() {
id="email"
type="email"
placeholder="seu@email.com"
Stockue={email}
onChange={(e) => setEmail(e.target.Stockue)}
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="mobile-optimized"
/>
Expand All @@ -47,8 +47,8 @@ export function LoginForm() {
id="password"
type="password"
placeholder="••••••••"
Stockue={password}
onChange={(e) => setPassword(e.target.Stockue)}
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="mobile-optimized"
/>
Expand Down
176 changes: 158 additions & 18 deletions components/ordi/kanban-board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import {
Trash2,
Archive,
Building2,
ShoppingCart,
Box,
CheckSquare,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { useRequisicoesStore } from "@/lib/requisicoes-store";
import { RequisicaoModal } from "./requisicao-modal";
import { RequisicaoSheet } from "./requisicao-sheet";
import { TrashSheet } from "@/components/ordi/trash/trash-sheet";
import { useToast } from "@/hooks/use-toast";
import {
Expand Down Expand Up @@ -72,20 +75,99 @@ export function KanbanBoard({ requisicoes }: KanbanBoardProps) {
const [selectedRequisicao, setSelectedRequisicao] =
useState<Requisicao | null>(null);
const [isTrashOpen, setIsTrashOpen] = useState(false);
const [selectedCards, setSelectedCards] = useState<string[]>([]);
const { toast } = useToast();

const handleNextStatus = (id: string, currentStatus: StatusRequisicao) => {
const handleNextStatus = (req: Requisicao, currentStatus: StatusRequisicao) => {
// Status Lock Validation
if (currentStatus === "nova") {
const allItemsTagged = req.itens.every(
(item) => item.tag === "separar" || item.tag === "comprar"
);

if (!allItemsTagged) {
toast({
title: "Ação Bloqueada",
description: "Todos os itens devem ser classificados como 'Separar' ou 'Comprar' antes de mover para Em Atendimento.",
variant: "destructive",
});
return;
}
}

let next: StatusRequisicao = "nova";
if (currentStatus === "nova") next = "em_atendimento";
else if (currentStatus === "em_atendimento") next = "concluida";

updateStatus(id, next);
updateStatus(req.id, next);
toast({
title: "Status atualizado",
description: `Pedido movido para ${statusLabels[next]}.`,
});
};

const handleBatchMove = () => {
// For simplicity, we only assume moving forward one step for all selected
// In a real app we might need to check individual status compatibility
// But usually batch actions apply when they are in the same column?
// The requirement says: "show a floating action bar to move them all to the next status at once."

// We should only move those that are valid.

const processed = [];
const failed = [];

selectedCards.forEach(id => {
const req = requisicoes.find(r => r.id === id);
if(!req) return;

// Logic for next status
let next: StatusRequisicao | null = null;
if (req.status === "nova") {
const allItemsTagged = req.itens.every(
(item) => item.tag === "separar" || item.tag === "comprar"
);
if (allItemsTagged) next = "em_atendimento";
else failed.push(id);
}
else if (req.status === "em_atendimento") next = "concluida";

if (next) {
updateStatus(id, next);
processed.push(id);
}
});

if (processed.length > 0) {
toast({
title: "Lote processado",
description: `${processed.length} itens movidos com sucesso.`,
});
setSelectedCards([]);
}

if (failed.length > 0) {
toast({
title: "Alguns itens não foram movidos",
description: `${failed.length} itens precisam ser classificados primeiro.`,
variant: "destructive",
});
}
};

const toggleSelectCard = (id: string) => {
setSelectedCards(prev =>
prev.includes(id) ? prev.filter(c => c !== id) : [...prev, id]
);
};

const handleCardClick = (e: React.MouseEvent, req: Requisicao) => {
if (e.shiftKey) {
e.preventDefault();
toggleSelectCard(req.id);
}
};

const handleDeny = (id: string) => {
updateStatus(id, "negada");
toast({
Expand Down Expand Up @@ -141,12 +223,22 @@ export function KanbanBoard({ requisicoes }: KanbanBoardProps) {
.filter((r) => r.status === col.status)
.map((req) => {
const setor = getSetorById(req.setorId);
const isSelected = selectedCards.includes(req.id);
const hasComprar = req.itens.some(i => i.tag === "comprar");
const allSeparar = req.itens.length > 0 && req.itens.every(i => i.tag === "separar");

return (
<div
key={req.id}
className="bg-background p-3 rounded-lg border shadow-sm hover:shadow-md transition-all group"
onClick={(e) => handleCardClick(e, req)}
className={`bg-background p-3 rounded-lg border shadow-sm hover:shadow-md transition-all group relative cursor-pointer ${isSelected ? "ring-2 ring-primary border-primary" : ""}`}
>
{isSelected && (
<div className="absolute -top-2 -right-2 bg-primary text-primary-foreground rounded-full p-0.5 shadow-sm z-10">
<CheckSquare className="h-4 w-4" />
</div>
)}

{/* Topo: Setor e Data */}
<div className="flex justify-between items-start mb-3">
<div className="flex items-center text-sm font-semibold text-primary">
Expand Down Expand Up @@ -195,17 +287,33 @@ export function KanbanBoard({ requisicoes }: KanbanBoardProps) {
Detalhes
</Button>

{/* Botões de Ação Condicionais */}
{(col.status === "nova" ||
{/* Visual Indicators */}
<div className="flex items-center gap-1.5 ml-auto">
{hasComprar && (
<div title="Itens para Comprar" className="bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 p-1 rounded-md">
<ShoppingCart className="h-3 w-3" />
</div>
)}
{allSeparar && (
<div title="Todos itens em Estoque" className="bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 p-1 rounded-md">
<Box className="h-3 w-3" />
</div>
)}
</div>
</div>

{/* Botões de Ação Condicionais */}
{(col.status === "nova" ||
col.status === "em_atendimento") && (
<>
<Button
variant="ghost"
size="icon"
className="h-7 w-7 rounded-md hover:text-green-600 hover:bg-green-50 dark:hover:bg-green-950/20"
onClick={() =>
handleNextStatus(req.id, col.status)
}
onClick={(e) => {
e.stopPropagation();
handleNextStatus(req, col.status);
}}
title={`Mover para ${
statusLabels[
col.status === "nova"
Expand All @@ -220,7 +328,10 @@ export function KanbanBoard({ requisicoes }: KanbanBoardProps) {
variant="ghost"
size="icon"
className="h-7 w-7 rounded-md hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-950/20"
onClick={() => handleDeny(req.id)}
onClick={(e) => {
e.stopPropagation();
handleDeny(req.id);
}}
title="Negar Requisição"
>
<X className="h-3.5 w-3.5" />
Expand All @@ -233,7 +344,10 @@ export function KanbanBoard({ requisicoes }: KanbanBoardProps) {
variant="ghost"
size="icon"
className="h-7 w-7 rounded-md hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-950/20"
onClick={() => handleArchive(req.id)}
onClick={(e) => {
e.stopPropagation();
handleArchive(req.id);
}}
title="Arquivar"
>
<Archive className="h-3.5 w-3.5" />
Expand All @@ -245,7 +359,10 @@ export function KanbanBoard({ requisicoes }: KanbanBoardProps) {
variant="ghost"
size="icon"
className="h-7 w-7 rounded-md hover:text-destructive hover:bg-destructive/10"
onClick={() => handleMoveToTrash(req.id)}
onClick={(e) => {
e.stopPropagation();
handleMoveToTrash(req.id);
}}
title="Excluir"
>
<Trash2 className="h-3.5 w-3.5" />
Expand All @@ -270,14 +387,37 @@ export function KanbanBoard({ requisicoes }: KanbanBoardProps) {
))}
</div>

{selectedRequisicao && (
<RequisicaoModal
requisicao={selectedRequisicao}
onClose={() => setSelectedRequisicao(null)}
/>
)}
<RequisicaoSheet
requisicao={selectedRequisicao}
open={!!selectedRequisicao}
onOpenChange={(open) => !open && setSelectedRequisicao(null)}
/>

<TrashSheet open={isTrashOpen} onOpenChange={setIsTrashOpen} />

{/* Floating Action Bar for Batch Selection */}
{selectedCards.length > 0 && (
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 bg-foreground text-background px-6 py-3 rounded-full shadow-xl z-50 flex items-center gap-4 animate-in slide-in-from-bottom-4">
<span className="text-sm font-medium">{selectedCards.length} selecionado(s)</span>
<div className="h-4 w-px bg-background/20" />
<Button
size="sm"
variant="secondary"
onClick={handleBatchMove}
className="h-8 text-xs font-semibold"
>
Mover para Próxima Etapa
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => setSelectedCards([])}
className="h-8 w-8 p-0 rounded-full hover:bg-background/20 text-background hover:text-background"
>
<X className="h-4 w-4" />
</Button>
</div>
)}
</div>
);
}
Loading