import { useState, useEffect } from "react"; import { BrowserFrame } from "@/components/Browser/BrowserFrame"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { useToast } from "@/hooks/use-toast"; import { Store, ShoppingCart, Wrench, Package, Users, BarChart3, Plus, Search, Smartphone, ArrowRightLeft, ClipboardList, DollarSign, Clock, AlertTriangle, CheckCircle, XCircle, Truck, FileText, Settings, RefreshCw, Trash2, Edit, GripVertical, ListChecks, FileCheck, ExternalLink, UserPlus, User, CreditCard, Lock, RefreshCcw, X, Loader2, ShoppingBag, FileBarChart, Percent, Tag, Banknote, Gift, Printer, Target, Calendar, Headphones, Upload, List, FileSpreadsheet, ClipboardCheck, Circle } from "lucide-react"; import { Progress } from "@/components/ui/progress"; import { Checkbox } from "@/components/ui/checkbox"; import { Switch } from "@/components/ui/switch"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Pencil } from "lucide-react"; import TradeInForm from "@/components/TradeInForm"; interface DashboardStats { devicesInStock: number; openServiceOrders: number; todaySalesTotal: number; todaySalesCount: number; pendingEvaluations: number; } interface ActivityFeedItem { id: number; activityType: string; entityType?: string; entityId?: number; title: string; description?: string; severity: string; isRead: boolean; createdAt: string; metadata?: any; } interface MobileDevice { id: number; imei: string; brand: string; model: string; color?: string; storage?: string; condition: string; sellingPrice?: string; purchasePrice?: string; acquisitionType?: string; acquisitionCost?: string; status: string; storeId?: number; } interface ServiceOrder { id: number; orderNumber: string; imei: string; brand?: string; model?: string; customerName: string; customerPhone?: string; issueDescription: string; status: string; priority: string; totalCost?: string; isInternal?: boolean; origin?: string; serviceType?: string; deviceId?: number; createdAt: string; } interface DeviceEvaluation { id: number; imei: string; brand: string; model: string; customerName?: string; overallCondition?: string; estimatedValue?: string; evaluationDate?: string; maintenanceOrderId?: number; status: string; createdAt: string; } interface ChecklistTemplate { id: number; name: string; description?: string; deviceCategory: string; isActive: boolean; items?: ChecklistItem[]; } interface ChecklistItem { id: number; templateId: number; category: string; itemName: string; itemDescription?: string; evaluationType: string; options?: string; impactOnValue?: string; isRequired: boolean; displayOrder: number; } interface PaymentMethod { id: number; name: string; type: string; brand?: string; feePercent?: string; fixedFee?: string; installmentsMax?: number; daysToReceive?: number; isActive: boolean; } interface Seller { id: number; code?: string; name: string; email?: string; phone?: string; storeId?: number; personId?: number; commissionPlanId?: number; hireDate?: string; isActive: boolean; } interface CommissionPlan { id: number; name: string; description?: string; type: string; baseValue?: string; basePercent?: string; isActive: boolean; } interface PriceTable { id: number; name: string; code?: string; description?: string; customerType?: string; discountPercent?: string; markupPercent?: string; validFrom?: string; validTo?: string; isDefault: boolean; isActive: boolean; } interface Promotion { id: number; name: string; description?: string; type: string; discountValue?: string; discountPercent?: string; applyTo?: string; minQuantity?: number; validFrom?: string; validTo?: string; isActive: boolean; } export default function ArcadiaRetail() { const [activeTab, setActiveTab] = useState("dashboard"); const [stats, setStats] = useState(null); const [devices, setDevices] = useState([]); const [serviceOrders, setServiceOrders] = useState([]); const [evaluations, setEvaluations] = useState([]); const [activities, setActivities] = useState([]); const [searchTerm, setSearchTerm] = useState(""); const [empresas, setEmpresas] = useState([]); const [sellers, setSellers] = useState([]); const [selectedEmpresaId, setSelectedEmpresaId] = useState(() => { const stored = localStorage.getItem("retail_empresa_id"); return stored ? parseInt(stored) : null; }); const [selectedSellerId, setSelectedSellerId] = useState(() => { const stored = localStorage.getItem("retail_seller_id"); return stored ? parseInt(stored) : null; }); const [showSessionRequired, setShowSessionRequired] = useState(false); const [showNewDeviceDialog, setShowNewDeviceDialog] = useState(false); const [showNewServiceDialog, setShowNewServiceDialog] = useState(false); const [showEvaluationDialog, setShowEvaluationDialog] = useState(false); const [showTradeInForm, setShowTradeInForm] = useState(false); const [evalForm, setEvalForm] = useState({ imei: "", cliente: "", cpf: "", personId: null as number | null, customerPhone: "", marca: "", modelo: "", cor: "", tela: "", corpo: "", bateria: "", condicaoGeral: "" }); const [evalFilteredPersons, setEvalFilteredPersons] = useState([]); const [showEvalClientDropdown, setShowEvalClientDropdown] = useState(false); const [showQuickPersonDialog, setShowQuickPersonDialog] = useState(false); const [quickPerson, setQuickPerson] = useState({ fullName: "", cpfCnpj: "", phone: "" }); const [loading, setLoading] = useState(false); const [checklistTemplates, setChecklistTemplates] = useState([]); const [selectedTemplate, setSelectedTemplate] = useState(null); const [showNewTemplateDialog, setShowNewTemplateDialog] = useState(false); const [selectedEvaluation, setSelectedEvaluation] = useState(null); const [showEvaluationDetailsDialog, setShowEvaluationDetailsDialog] = useState(false); const [approving, setApproving] = useState(false); const [editingEvaluation, setEditingEvaluation] = useState(null); const [savingEvaluation, setSavingEvaluation] = useState(false); const [selectedProductForTradeIn, setSelectedProductForTradeIn] = useState(null); const [matchingProducts, setMatchingProducts] = useState([]); const [showNewItemDialog, setShowNewItemDialog] = useState(false); const [editingItem, setEditingItem] = useState(null); const [newTemplate, setNewTemplate] = useState({ name: "", description: "", deviceCategory: "smartphone" }); const [newItem, setNewItem] = useState({ category: "visual", itemName: "", itemDescription: "", evaluationType: "condition", options: '["Perfeito","Bom","Regular","Ruim"]', impactOnValue: "0", isRequired: true, displayOrder: 0 }); // Person Registry States const [personsList, setPersonsList] = useState([]); const [personSearch, setPersonSearch] = useState(""); const [personRoleFilter, setPersonRoleFilter] = useState("all"); const [showNewPersonDialog, setShowNewPersonDialog] = useState(false); const [editingPerson, setEditingPerson] = useState(null); const [viewingPerson, setViewingPerson] = useState(null); const [personHistory, setPersonHistory] = useState<{sales: any[], services: any[], tradeIns: any[], credits: any[]}>({sales: [], services: [], tradeIns: [], credits: []}); // Créditos e Devoluções const [showCreditsDialog, setShowCreditsDialog] = useState(false); const [viewingCredits, setViewingCredits] = useState<{person: any, credits: any[], totalAvailable: number} | null>(null); const [returnGenerateCredit, setReturnGenerateCredit] = useState(true); const [returnCreditExpiration, setReturnCreditExpiration] = useState(90); const [newPerson, setNewPerson] = useState({ fullName: "", cpfCnpj: "", email: "", phone: "", whatsapp: "", address: "", city: "", state: "", zipCode: "", notes: "", roles: [] as string[] }); // PDV States interface CartItem { id: string; type: "device" | "accessory" | "service" | "product"; deviceId?: number; productId?: number; imei?: string; name: string; description?: string; code?: string; ncm?: string; quantity: number; unitPrice: number; discount: number; serviceOrderId?: number; } const [cart, setCart] = useState([]); const [pdvCustomer, setPdvCustomer] = useState(null); const [pdvSearch, setPdvSearch] = useState(""); const [pdvProductSearch, setPdvProductSearch] = useState(""); const [pdvProducts, setPdvProducts] = useState([]); const [pdvOsSearch, setPdvOsSearch] = useState(""); const [showImeiModal, setShowImeiModal] = useState(false); const [pendingDevice, setPendingDevice] = useState(null); const [imeiInput, setImeiInput] = useState(""); const [showPaymentModal, setShowPaymentModal] = useState(false); const [showCustomerModal, setShowCustomerModal] = useState(false); const [customerSearch, setCustomerSearch] = useState(""); const [paymentMethods, setPaymentMethods] = useState<{method: string; amount: number}[]>([]); const [tradeInCredit, setTradeInCredit] = useState(0); const [pdvDiscount, setPdvDiscount] = useState(0); const [availableOs, setAvailableOs] = useState([]); const [customerCredits, setCustomerCredits] = useState([]); const [customerTotalCredit, setCustomerTotalCredit] = useState(0); const [useCredit, setUseCredit] = useState(false); const [creditAmountToUse, setCreditAmountToUse] = useState(0); const [showCashMovementDialog, setShowCashMovementDialog] = useState(false); const [cashMovementType, setCashMovementType] = useState<"withdrawal"|"reinforcement">("withdrawal"); const [cashMovements, setCashMovements] = useState([]); const [cashMovementAmount, setCashMovementAmount] = useState(""); const [cashMovementReason, setCashMovementReason] = useState(""); // Trade-In Alerts & Status const [customerTradeIns, setCustomerTradeIns] = useState([]); const [showTradeInAlert, setShowTradeInAlert] = useState(false); // Manager Password const [showManagerPasswordModal, setShowManagerPasswordModal] = useState(false); const [managerPassword, setManagerPassword] = useState(""); const [pendingManagerAction, setPendingManagerAction] = useState<{type: string; data?: any} | null>(null); // Returns/Refunds const [showReturnModal, setShowReturnModal] = useState(false); const [returnSearch, setReturnSearch] = useState(""); const [returnSales, setReturnSales] = useState([]); const [selectedReturnSale, setSelectedReturnSale] = useState(null); const [returnItems, setReturnItems] = useState([]); const [returnReason, setReturnReason] = useState(""); const [processingReturn, setProcessingReturn] = useState(false); // Service Order Checklist View const [showOsChecklistModal, setShowOsChecklistModal] = useState(false); const [selectedOsForChecklist, setSelectedOsForChecklist] = useState(null); // Service Order Details/Edit Modal const [showOsDetailsModal, setShowOsDetailsModal] = useState(false); const [editingServiceOrder, setEditingServiceOrder] = useState(null); const [osEstimatedValue, setOsEstimatedValue] = useState(""); const [osEvaluatedValue, setOsEvaluatedValue] = useState(""); const [osStatus, setOsStatus] = useState(""); const [osEvaluationStatus, setOsEvaluationStatus] = useState(""); const [osNotes, setOsNotes] = useState(""); const [osChecklistData, setOsChecklistData] = useState>({}); const [osItems, setOsItems] = useState([]); const [osItemSearch, setOsItemSearch] = useState(""); const [osItemResults, setOsItemResults] = useState([]); const [osItemQuantity, setOsItemQuantity] = useState(1); const [loadingOsItems, setLoadingOsItems] = useState(false); // Estados para Cadastros const [cadastroPaymentMethods, setCadastroPaymentMethods] = useState([]); const [cadastroSellers, setCadastroSellers] = useState([]); const [cadastroCommissionPlans, setCadastroCommissionPlans] = useState([]); const [cadastroPriceTables, setCadastroPriceTables] = useState([]); const [cadastroPromotions, setCadastroPromotions] = useState([]); // Estados para Tipos de Produtos (Dispositivos e Acessórios) const [productTypes, setProductTypes] = useState([]); const [deviceTypes, setDeviceTypes] = useState([]); const [accessoryTypes, setAccessoryTypes] = useState([]); const [editingProductType, setEditingProductType] = useState(null); const [showProductTypeDialog, setShowProductTypeDialog] = useState(false); const [productTypeCategory, setProductTypeCategory] = useState("device"); // Estados para Depósitos e Movimentações const [warehouses, setWarehouses] = useState([]); const [selectedWarehouse, setSelectedWarehouse] = useState(null); const [warehouseStock, setWarehouseStock] = useState([]); const [stockMovements, setStockMovements] = useState([]); const [stockTransfers, setStockTransfers] = useState([]); const [showWarehouseDialog, setShowWarehouseDialog] = useState(false); const [editingWarehouse, setEditingWarehouse] = useState(null); const [showMovementDialog, setShowMovementDialog] = useState(false); const [showTransferDialog, setShowTransferDialog] = useState(false); const [movementForm, setMovementForm] = useState({ movementType: "entry", operationType: "purchase", quantity: "", serials: [] }); const [transferForm, setTransferForm] = useState({ destinationWarehouseId: "", items: [], notes: "" }); const [allProducts, setAllProducts] = useState([]); const [showLancarEstoqueDialog, setShowLancarEstoqueDialog] = useState(false); const [lancarEstoqueData, setLancarEstoqueData] = useState({ order: null, warehouseId: "", productId: "", hasProduct: false }); const [showNovaCompraDialog, setShowNovaCompraDialog] = useState(false); const [showImportarNFDialog, setShowImportarNFDialog] = useState(false); const [compraForm, setCompraForm] = useState({ supplierId: "", supplierName: "", invoiceNumber: "", invoiceDate: "", warehouseId: "", notes: "", items: [] }); const [showPaymentMethodsDialog, setShowPaymentMethodsDialog] = useState(false); const [showSellersDialog, setShowSellersDialog] = useState(false); const [showCommissionsDialog, setShowCommissionsDialog] = useState(false); const [showPriceTablesDialog, setShowPriceTablesDialog] = useState(false); const [showPromotionsDialog, setShowPromotionsDialog] = useState(false); const [showCreateOsDialog, setShowCreateOsDialog] = useState(false); const [salesDetailData, setSalesDetailData] = useState([]); const [salesDetailLoading, setSalesDetailLoading] = useState(false); const [reportDateFrom, setReportDateFrom] = useState(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]); const [reportDateTo, setReportDateTo] = useState(new Date().toISOString().split('T')[0]); const [dailyCashDate, setDailyCashDate] = useState(new Date().toISOString().split('T')[0]); const [reportSellerId, setReportSellerId] = useState("all"); const [stockAlerts, setStockAlerts] = useState([]); const [reportSubTab, setReportSubTab] = useState("os-status"); const [reportOsByStatus, setReportOsByStatus] = useState([]); const [reportOsByTech, setReportOsByTech] = useState([]); const [reportSalesBySeller, setReportSalesBySeller] = useState([]); const [reportMarginByImei, setReportMarginByImei] = useState([]); const [reportDailyCash, setReportDailyCash] = useState(null); const [reportStockTurnover, setReportStockTurnover] = useState([]); const [reportLoading, setReportLoading] = useState(false); const [warrantyCache, setWarrantyCache] = useState>({}); // Estados para Comissões const [commissionDashboard, setCommissionDashboard] = useState(null); const [commissionDashboardLoading, setCommissionDashboardLoading] = useState(false); const [commissionMonth, setCommissionMonth] = useState(new Date().getMonth() + 1); const [commissionYear, setCommissionYear] = useState(new Date().getFullYear()); const [sellerGoals, setSellerGoals] = useState([]); const [storeGoals, setStoreGoals] = useState([]); const [commissionClosures, setCommissionClosures] = useState([]); const [showGoalDialog, setShowGoalDialog] = useState(false); const [editingGoal, setEditingGoal] = useState(null); const [showClosureDialog, setShowClosureDialog] = useState(false); const [closureCalcResult, setClosureCalcResult] = useState(null); const [closurePeriodType, setClosurePeriodType] = useState("monthly"); const [closureDateFrom, setClosureDateFrom] = useState(new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().split('T')[0]); const [closureDateTo, setClosureDateTo] = useState(new Date().toISOString().split('T')[0]); const [closureSellerId, setClosureSellerId] = useState(""); const [closureCommissionRate, setClosureCommissionRate] = useState("5"); const [editingPaymentMethod, setEditingPaymentMethod] = useState(null); const [editingSeller, setEditingSeller] = useState(null); const [editingCommissionPlan, setEditingCommissionPlan] = useState(null); const [editingPriceTable, setEditingPriceTable] = useState(null); const [editingPromotion, setEditingPromotion] = useState(null); const { toast } = useToast(); useEffect(() => { loadDashboardStats(); loadDevices(); loadServiceOrders(); loadEvaluations(); loadChecklistTemplates(); loadPersons(); loadActivities(); loadEmpresas(); loadSellers(); }, []); const loadActivities = async () => { try { const res = await fetch("/api/retail/activity-feed?limit=20", { credentials: "include" }); if (res.ok) { const data = await res.json(); setActivities(data); } } catch (error) { console.error("Error loading activities:", error); } }; const loadStockAlerts = async () => { try { const res = await fetch("/api/retail/stock-alerts", { credentials: "include" }); if (res.ok) setStockAlerts(await res.json()); } catch (error) { console.error("Error loading stock alerts:", error); } }; const loadCashMovements = async () => { try { const res = await fetch("/api/retail/cash-movements", { credentials: "include" }); if (res.ok) setCashMovements(await res.json()); } catch (error) { console.error("Error loading cash movements:", error); } }; const handleCreateCashMovement = async () => { if (!cashMovementAmount || parseFloat(cashMovementAmount) <= 0) { toast({ title: "Informe um valor válido", variant: "destructive" }); return; } try { const res = await fetch("/api/retail/cash-movements", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ sessionId: null, storeId: selectedEmpresaId || 1, type: cashMovementType, amount: cashMovementAmount, reason: cashMovementReason }) }); if (res.ok) { toast({ title: cashMovementType === "withdrawal" ? "Sangria registrada!" : "Reforço registrado!" }); setShowCashMovementDialog(false); setCashMovementAmount(""); setCashMovementReason(""); loadCashMovements(); } } catch (error) { toast({ title: "Erro ao registrar movimentação", variant: "destructive" }); } }; const loadReportData = async (reportType: string) => { setReportLoading(true); try { const params = new URLSearchParams(); if (reportDateFrom) params.append("dateFrom", reportDateFrom); if (reportDateTo) params.append("dateTo", reportDateTo); if (reportType === "daily-cash") params.append("date", dailyCashDate); const res = await fetch(`/api/retail/reports/${reportType}?${params}`, { credentials: "include" }); if (res.ok) { const data = await res.json(); switch (reportType) { case "os-by-status": setReportOsByStatus(data); break; case "os-by-technician": setReportOsByTech(data); break; case "sales-by-seller": setReportSalesBySeller(data); break; case "margin-by-imei": setReportMarginByImei(data); break; case "daily-cash": setReportDailyCash(data); break; case "stock-turnover": setReportStockTurnover(data); break; } } } catch (error) { console.error("Error loading report:", error); } setReportLoading(false); }; const checkWarranty = async (imei: string) => { if (warrantyCache[imei] !== undefined) return; try { const res = await fetch(`/api/retail/warranties/check/${imei}`, { credentials: "include" }); if (res.ok) { const data = await res.json(); setWarrantyCache(prev => ({ ...prev, [imei]: data.hasActiveWarranty })); } } catch (error) { console.error("Error checking warranty:", error); } }; const loadOsItems = async (orderId: number) => { try { setLoadingOsItems(true); const res = await fetch(`/api/retail/service-orders/${orderId}/items`, { credentials: "include" }); if (res.ok) { const data = await res.json(); setOsItems(Array.isArray(data) ? data : []); } } catch (e) { console.error(e); } finally { setLoadingOsItems(false); } }; const searchOsProducts = async (term: string) => { if (term.length < 2) { setOsItemResults([]); return; } try { const res = await fetch(`/api/soe/products?search=${encodeURIComponent(term)}`, { credentials: "include" }); if (res.ok) { const data = await res.json(); setOsItemResults(Array.isArray(data) ? data.slice(0, 10) : []); } } catch (e) { console.error(e); } }; const addOsItem = async (product: any) => { if (!editingServiceOrder) return; try { const res = await fetch(`/api/retail/service-orders/${editingServiceOrder.id}/items`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ productId: product.id, itemType: "part", itemCode: product.code, itemName: product.name, quantity: osItemQuantity, unitPrice: parseFloat(product.costPrice || product.salePrice || 0), }) }); if (res.ok) { await loadOsItems(editingServiceOrder.id); setOsItemSearch(""); setOsItemResults([]); setOsItemQuantity(1); toast({ title: "Peça adicionada!", description: product.name }); } } catch (e) { toast({ title: "Erro ao adicionar peça", variant: "destructive" }); } }; const removeOsItem = async (itemId: number) => { if (!editingServiceOrder) return; try { const res = await fetch(`/api/retail/service-orders/${editingServiceOrder.id}/items/${itemId}`, { method: "DELETE", credentials: "include" }); if (res.ok) { await loadOsItems(editingServiceOrder.id); toast({ title: "Peça removida" }); } } catch (e) { toast({ title: "Erro ao remover peça", variant: "destructive" }); } }; useEffect(() => { if (activeTab === "dashboard") { loadStockAlerts(); } }, [activeTab]); useEffect(() => { if (activeTab === "relatorios") { loadReportData(reportSubTab === "os-status" ? "os-by-status" : reportSubTab === "os-tech" ? "os-by-technician" : reportSubTab === "sales-seller" ? "sales-by-seller" : reportSubTab === "margin-imei" ? "margin-by-imei" : reportSubTab === "daily-cash" ? "daily-cash" : "stock-turnover"); } }, [activeTab, reportSubTab]); useEffect(() => { if (activeTab === "servicos") { serviceOrders.forEach(order => { if (order.imei) checkWarranty(order.imei); }); } }, [activeTab, serviceOrders]); useEffect(() => { if (activeTab === "pdv") { loadCashMovements(); } }, [activeTab]); // Auto-refresh activity feed every 30 seconds useEffect(() => { const interval = setInterval(() => { if (activeTab === "dashboard") { loadActivities(); } }, 30000); return () => clearInterval(interval); }, [activeTab]); // Carregar dados de comissões quando aba for selecionada useEffect(() => { if (activeTab === "comissoes") { loadCadastroSellers(); loadCadastroCommissionPlans(); loadCommissionDashboard(); loadSellerGoals(); loadStoreGoals(); loadCommissionClosures(); } }, [activeTab, commissionMonth, commissionYear]); // Carregar tipos de produtos quando aba Estoque for selecionada useEffect(() => { if (activeTab === "estoque") { loadProductTypes(); } }, [activeTab]); // Funções de carregamento dos Cadastros const loadCadastroPaymentMethods = async () => { try { const res = await fetch("/api/retail/payment-methods", { credentials: "include" }); if (res.ok) setCadastroPaymentMethods(await res.json()); } catch (error) { console.error("Error loading payment methods:", error); } }; const loadCadastroSellers = async () => { try { const res = await fetch("/api/retail/sellers", { credentials: "include" }); if (res.ok) setCadastroSellers(await res.json()); } catch (error) { console.error("Error loading sellers:", error); } }; const loadCadastroCommissionPlans = async () => { try { const res = await fetch("/api/retail/commission-plans", { credentials: "include" }); if (res.ok) setCadastroCommissionPlans(await res.json()); } catch (error) { console.error("Error loading commission plans:", error); } }; const loadCadastroPriceTables = async () => { try { const res = await fetch("/api/retail/price-tables", { credentials: "include" }); if (res.ok) setCadastroPriceTables(await res.json()); } catch (error) { console.error("Error loading price tables:", error); } }; const loadCadastroPromotions = async () => { try { const res = await fetch("/api/retail/promotions", { credentials: "include" }); if (res.ok) setCadastroPromotions(await res.json()); } catch (error) { console.error("Error loading promotions:", error); } }; const loadProductTypes = async () => { try { const res = await fetch("/api/retail/product-types", { credentials: "include" }); if (res.ok) { const types = await res.json(); setProductTypes(types); setDeviceTypes(types.filter((t: any) => t.category === "device")); setAccessoryTypes(types.filter((t: any) => t.category === "accessory")); } } catch (error) { console.error("Error loading product types:", error); } }; const saveProductType = async () => { if (!editingProductType?.name) return; try { const method = editingProductType.id ? "PUT" : "POST"; const url = editingProductType.id ? `/api/retail/product-types/${editingProductType.id}` : "/api/retail/product-types"; await fetch(url, { method, headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ ...editingProductType, category: productTypeCategory }) }); setEditingProductType(null); setShowProductTypeDialog(false); loadProductTypes(); toast({ title: "Tipo de produto salvo!" }); } catch (error) { console.error("Error saving product type:", error); toast({ title: "Erro ao salvar tipo de produto", variant: "destructive" }); } }; const loadWarehouses = async () => { try { const res = await fetch("/api/retail/warehouses", { credentials: "include" }); if (res.ok) { const data = await res.json(); setWarehouses(data); } } catch (error) { console.error("Error loading warehouses:", error); } }; const loadWarehouseStock = async (warehouseId: number) => { try { const res = await fetch(`/api/retail/warehouse-stock/${warehouseId}/summary`, { credentials: "include" }); if (res.ok) { const data = await res.json(); setWarehouseStock(data); } } catch (error) { console.error("Error loading warehouse stock:", error); } }; const loadStockMovements = async (warehouseId?: number) => { try { const url = warehouseId ? `/api/retail/stock-movements?warehouseId=${warehouseId}&limit=50` : "/api/retail/stock-movements?limit=50"; const res = await fetch(url, { credentials: "include" }); if (res.ok) { const data = await res.json(); setStockMovements(data); } } catch (error) { console.error("Error loading stock movements:", error); } }; const loadStockTransfers = async () => { try { const res = await fetch("/api/retail/stock-transfers", { credentials: "include" }); if (res.ok) { const data = await res.json(); setStockTransfers(data); } } catch (error) { console.error("Error loading stock transfers:", error); } }; const loadAllProducts = async () => { try { const res = await fetch("/api/soe/products", { credentials: "include" }); if (res.ok) { const data = await res.json(); setAllProducts(data); } } catch (error) { console.error("Error loading products:", error); } }; const saveStockMovement = async () => { if (!movementForm.productId || !movementForm.quantity || !selectedWarehouse) return; try { await fetch("/api/retail/stock-movements", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ warehouseId: selectedWarehouse.id, productId: movementForm.productId, movementType: movementForm.movementType, operationType: movementForm.operationType, quantity: movementForm.quantity, unitCost: movementForm.unitCost, referenceNumber: movementForm.referenceNumber, notes: movementForm.notes, serials: movementForm.serials.filter((s: any) => s.imei || s.serialNumber), }) }); setShowMovementDialog(false); setMovementForm({ movementType: "entry", operationType: "purchase", quantity: "", serials: [] }); loadWarehouseStock(selectedWarehouse.id); loadStockMovements(selectedWarehouse.id); toast({ title: "Movimentação registrada!" }); } catch (error) { console.error("Error saving stock movement:", error); toast({ title: "Erro ao salvar movimentação", variant: "destructive" }); } }; const saveStockTransfer = async () => { if (!transferForm.destinationWarehouseId || transferForm.items.length === 0 || !selectedWarehouse) return; try { await fetch("/api/retail/stock-transfers", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ sourceWarehouseId: selectedWarehouse.id, destinationWarehouseId: parseInt(transferForm.destinationWarehouseId), items: transferForm.items, notes: transferForm.notes, }) }); setShowTransferDialog(false); setTransferForm({ destinationWarehouseId: "", items: [], notes: "" }); loadStockTransfers(); toast({ title: "Transferência criada!" }); } catch (error) { console.error("Error saving stock transfer:", error); toast({ title: "Erro ao salvar transferência", variant: "destructive" }); } }; const savePurchaseOrder = async () => { if (!compraForm.warehouseId || compraForm.items.length === 0) { toast({ title: "Preencha o depósito e adicione itens", variant: "destructive" }); return; } try { for (const item of compraForm.items) { if (!item.productId || !item.quantity) continue; await fetch("/api/retail/stock-movements", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ warehouseId: parseInt(compraForm.warehouseId), productId: parseInt(item.productId), movementType: "entry", operationType: "purchase", quantity: item.quantity, unitCost: item.unitCost || "0", referenceNumber: compraForm.invoiceNumber, notes: `Fornecedor: ${compraForm.supplierName || "N/A"} | ${compraForm.notes}`, serials: item.serials || [], }) }); } setShowNovaCompraDialog(false); setCompraForm({ supplierId: "", supplierName: "", invoiceNumber: "", invoiceDate: "", warehouseId: "", notes: "", items: [] }); toast({ title: "Compra registrada com sucesso!", description: `${compraForm.items.length} itens lançados no estoque` }); if (selectedWarehouse) { loadWarehouseStock(selectedWarehouse.id); loadStockMovements(selectedWarehouse.id); } } catch (error) { console.error("Error saving purchase:", error); toast({ title: "Erro ao registrar compra", variant: "destructive" }); } }; const saveWarehouse = async () => { if (!editingWarehouse?.name || !editingWarehouse?.code) return; try { const method = editingWarehouse.id ? "PUT" : "POST"; const url = editingWarehouse.id ? `/api/retail/warehouses/${editingWarehouse.id}` : "/api/retail/warehouses"; await fetch(url, { method, headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(editingWarehouse) }); setEditingWarehouse(null); setShowWarehouseDialog(false); loadWarehouses(); toast({ title: "Depósito salvo!" }); } catch (error) { console.error("Error saving warehouse:", error); toast({ title: "Erro ao salvar depósito", variant: "destructive" }); } }; const loadCommissionDashboard = async () => { setCommissionDashboardLoading(true); try { const res = await fetch(`/api/retail/commission-dashboard?month=${commissionMonth}&year=${commissionYear}`, { credentials: "include" }); if (res.ok) setCommissionDashboard(await res.json()); } catch (error) { console.error("Error loading commission dashboard:", error); } setCommissionDashboardLoading(false); }; const loadSellerGoals = async () => { try { const res = await fetch(`/api/retail/seller-goals?month=${commissionMonth}&year=${commissionYear}`, { credentials: "include" }); if (res.ok) setSellerGoals(await res.json()); } catch (error) { console.error("Error loading seller goals:", error); } }; const loadStoreGoals = async () => { try { const res = await fetch(`/api/retail/store-goals?month=${commissionMonth}&year=${commissionYear}`, { credentials: "include" }); if (res.ok) setStoreGoals(await res.json()); } catch (error) { console.error("Error loading store goals:", error); } }; const loadCommissionClosures = async () => { try { const res = await fetch("/api/retail/commission-closures", { credentials: "include" }); if (res.ok) setCommissionClosures(await res.json()); } catch (error) { console.error("Error loading commission closures:", error); } }; const calculateCommission = async () => { try { const res = await fetch("/api/retail/commission-closures/calculate", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ sellerId: closureSellerId && closureSellerId !== "_all" ? parseInt(closureSellerId) : null, periodType: closurePeriodType, periodStart: closureDateFrom, periodEnd: closureDateTo, commissionRate: closureCommissionRate }) }); if (res.ok) { const result = await res.json(); setClosureCalcResult(result); } } catch (error) { console.error("Error calculating commission:", error); } }; const saveCommissionClosure = async () => { if (!closureCalcResult) return; try { const res = await fetch("/api/retail/commission-closures", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ sellerId: closureSellerId && closureSellerId !== "_all" ? parseInt(closureSellerId) : null, periodType: closurePeriodType, periodStart: closureDateFrom, periodEnd: closureDateTo, totalSales: closureCalcResult.totalSales, totalReturns: closureCalcResult.totalReturns, netSales: closureCalcResult.netSales, commissionRate: closureCommissionRate, commissionAmount: closureCalcResult.commissionAmount, bonusAmount: closureCalcResult.bonusAmount, totalAmount: closureCalcResult.totalAmount, salesCount: closureCalcResult.salesCount, returnsCount: closureCalcResult.returnsCount, status: "closed", closedAt: new Date().toISOString() }) }); if (res.ok) { toast({ title: "Fechamento salvo", description: "O fechamento de comissão foi registrado com sucesso." }); setShowClosureDialog(false); setClosureCalcResult(null); loadCommissionClosures(); } } catch (error) { console.error("Error saving commission closure:", error); } }; const saveSellerGoal = async () => { if (!editingGoal) return; try { const method = editingGoal.id ? "PUT" : "POST"; const url = editingGoal.id ? `/api/retail/seller-goals/${editingGoal.id}` : "/api/retail/seller-goals"; const res = await fetch(url, { method, headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ ...editingGoal, month: commissionMonth, year: commissionYear }) }); if (res.ok) { toast({ title: "Meta salva", description: "A meta do vendedor foi salva com sucesso." }); setEditingGoal(null); setShowGoalDialog(false); loadSellerGoals(); loadCommissionDashboard(); } } catch (error) { console.error("Error saving seller goal:", error); } }; const loadChecklistTemplates = async () => { try { const res = await fetch("/api/retail/checklist/templates", { credentials: "include" }); if (res.ok) { const data = await res.json(); setChecklistTemplates(data); } } catch (error) { console.error("Error loading checklist templates:", error); } }; const loadPersons = async () => { try { let url = "/api/soe/persons"; const params = new URLSearchParams(); if (personSearch) params.append("search", personSearch); if (personRoleFilter && personRoleFilter !== "all") params.append("role", personRoleFilter); if (params.toString()) url += `?${params.toString()}`; const res = await fetch(url, { credentials: "include" }); if (res.ok) { const data = await res.json(); setPersonsList(data); } } catch (error) { console.error("Error loading persons:", error); } }; useEffect(() => { loadPersons(); }, [personSearch, personRoleFilter]); const handleEditPerson = (person: any) => { setEditingPerson(person); setNewPerson({ fullName: person.fullName || "", cpfCnpj: person.cpfCnpj || "", email: person.email || "", phone: person.phone || "", whatsapp: person.whatsapp || "", address: person.address || "", city: person.city || "", state: person.state || "", zipCode: person.zipCode || "", notes: person.notes || "", roles: Array.isArray(person.roles) ? (typeof person.roles[0] === 'string' ? person.roles : person.roles.map((r: any) => r.roleType || r)) : [] }); setShowNewPersonDialog(true); }; const handleViewPersonHistory = async (person: any) => { setViewingPerson(person); setPersonHistory({ sales: [], services: [], tradeIns: [], credits: [] }); try { const [salesRes, servicesRes, tradeInsRes, creditsRes] = await Promise.all([ fetch(`/api/retail/persons/${person.id}/sales`, { credentials: "include" }), fetch(`/api/retail/persons/${person.id}/services`, { credentials: "include" }), fetch(`/api/retail/persons/${person.id}/trade-ins`, { credentials: "include" }), fetch(`/api/retail/persons/${person.id}/credits`, { credentials: "include" }) ]); const [sales, services, tradeIns, credits] = await Promise.all([ salesRes.ok ? salesRes.json() : [], servicesRes.ok ? servicesRes.json() : [], tradeInsRes.ok ? tradeInsRes.json() : [], creditsRes.ok ? creditsRes.json() : [] ]); setPersonHistory({ sales, services, tradeIns, credits }); } catch (error) { console.error("Error loading person history:", error); } }; useEffect(() => { if (evalForm.cliente.length >= 2 && !evalForm.personId) { const filtered = personsList.filter(p => p.fullName.toLowerCase().includes(evalForm.cliente.toLowerCase()) || (p.cpfCnpj && p.cpfCnpj.includes(evalForm.cliente)) ); setEvalFilteredPersons(filtered); setShowEvalClientDropdown(filtered.length > 0); } else { setShowEvalClientDropdown(false); } }, [evalForm.cliente, personsList, evalForm.personId]); const selectEvalPerson = (person: any) => { setEvalForm({ ...evalForm, cliente: person.fullName, cpf: person.cpfCnpj || "", personId: person.id, customerPhone: person.phone || "" }); setShowEvalClientDropdown(false); }; useEffect(() => { const handleClickOutside = (e: MouseEvent) => { const target = e.target as HTMLElement; if (!target.closest('[data-eval-client-search]')) { setShowEvalClientDropdown(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); const handleQuickPersonSave = async () => { try { const res = await fetch("/api/soe/persons", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...quickPerson, roles: ["customer"] }), credentials: "include" }); if (res.ok) { const newPersonData = await res.json(); toast({ title: "Cliente cadastrado!" }); setEvalForm({ ...evalForm, cliente: newPersonData.fullName, cpf: newPersonData.cpfCnpj || "", personId: newPersonData.id }); setShowQuickPersonDialog(false); setQuickPerson({ fullName: "", cpfCnpj: "", phone: "" }); loadPersons(); } else { const errorData = await res.json(); toast({ title: errorData.error || "Erro ao cadastrar", variant: "destructive" }); } } catch (error) { toast({ title: "Erro ao cadastrar cliente", variant: "destructive" }); } }; const handleApproveEvaluation = async () => { if (!selectedEvaluation) return; setApproving(true); try { const res = await fetch(`/api/retail/evaluations/${selectedEvaluation.id}/approve`, { method: "PUT", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ productId: selectedProductForTradeIn }) }); if (res.ok) { const result = await res.json(); toast({ title: "Trade-In Aprovado!", description: result.serviceOrder ? `O.S. #${result.serviceOrder.orderNumber} criada para preparação. ${result.credit ? `Crédito de R$ ${parseFloat(result.credit.amount).toFixed(2)} gerado.` : ''}` : "Dispositivo encaminhado para preparação" }); setShowEvaluationDetailsDialog(false); setSelectedEvaluation(null); setSelectedProductForTradeIn(null); setMatchingProducts([]); loadEvaluations(); loadServiceOrders(); } else { const errorData = await res.json(); toast({ title: errorData.error || "Erro ao aprovar", variant: "destructive" }); } } catch (error) { toast({ title: "Erro ao aprovar avaliação", variant: "destructive" }); } finally { setApproving(false); } }; const searchMatchingProducts = async (brand: string, model: string) => { try { const res = await fetch(`/api/retail/products?search=${encodeURIComponent(brand + ' ' + model)}`, { credentials: "include" }); if (res.ok) { const data = await res.json(); setMatchingProducts(data); } } catch (error) { console.error("Error searching products:", error); } }; const handleOpenEvaluationDetails = (evaluation: any) => { setSelectedEvaluation(evaluation); setEditingEvaluation({ ...evaluation }); setShowEvaluationDetailsDialog(true); setSelectedProductForTradeIn(null); // Buscar produtos similares if (evaluation.brand && evaluation.model) { searchMatchingProducts(evaluation.brand, evaluation.model); } }; const handleSaveEvaluation = async () => { if (!editingEvaluation) return; setSavingEvaluation(true); try { const res = await fetch(`/api/retail/evaluations/${editingEvaluation.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(editingEvaluation), credentials: "include" }); if (res.ok) { const updated = await res.json(); setSelectedEvaluation(updated); toast({ title: "Avaliação salva com sucesso" }); loadEvaluations(); } else { toast({ title: "Erro ao salvar avaliação", variant: "destructive" }); } } catch (error) { toast({ title: "Erro ao salvar avaliação", variant: "destructive" }); } finally { setSavingEvaluation(false); } }; const handleRejectEvaluation = async (reason: string) => { if (!selectedEvaluation) return; try { const res = await fetch(`/api/retail/evaluations/${selectedEvaluation.id}/reject`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ reason }), credentials: "include" }); if (res.ok) { toast({ title: "Avaliação Rejeitada" }); setShowEvaluationDetailsDialog(false); setSelectedEvaluation(null); loadEvaluations(); } } catch (error) { toast({ title: "Erro ao rejeitar avaliação", variant: "destructive" }); } }; const handleCreateEvaluation = async () => { if (!evalForm.imei || !evalForm.marca || !evalForm.modelo || !evalForm.condicaoGeral) { toast({ title: "Preencha os campos obrigatórios", variant: "destructive" }); return; } try { const evalRes = await fetch("/api/retail/evaluations", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ imei: evalForm.imei, brand: evalForm.marca, model: evalForm.modelo, color: evalForm.cor, customerName: evalForm.cliente, customerCpf: evalForm.cpf, personId: evalForm.personId, screenCondition: evalForm.tela, bodyCondition: evalForm.corpo, batteryHealth: evalForm.bateria ? parseInt(evalForm.bateria) : null, overallCondition: evalForm.condicaoGeral, status: "pending" }), credentials: "include" }); if (evalRes.ok) { const evaluation = await evalRes.json(); const orderNumber = `OS${Date.now().toString().slice(-8)}`; const osRes = await fetch("/api/retail/service-orders", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ orderNumber, storeId: 1, imei: evalForm.imei, brand: evalForm.marca, model: evalForm.modelo, customerName: evalForm.cliente, customerPhone: evalForm.customerPhone, personId: evalForm.personId, serviceType: "diagnostic", issueDescription: `Avaliação de Trade-In - ${evalForm.marca} ${evalForm.modelo}`, origin: "device_acquisition", isInternal: true, internalType: "revision", sourceEvaluationId: evaluation.id, status: "open" }), credentials: "include" }); if (osRes.ok) { toast({ title: "Avaliação e O.S. criadas com sucesso!" }); } else { toast({ title: "Avaliação criada, mas erro ao criar O.S." }); } setShowEvaluationDialog(false); setEvalForm({ imei: "", cliente: "", cpf: "", personId: null, customerPhone: "", marca: "", modelo: "", cor: "", tela: "", corpo: "", bateria: "", condicaoGeral: "" }); loadEvaluations(); loadServiceOrders(); } else { toast({ title: "Erro ao criar avaliação", variant: "destructive" }); } } catch (error) { toast({ title: "Erro ao criar avaliação", variant: "destructive" }); } }; const handleSavePerson = async () => { try { const personData = { ...newPerson, roles: newPerson.roles }; let res; if (editingPerson) { res = await fetch(`/api/soe/persons/${editingPerson.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(personData), credentials: "include" }); } else { res = await fetch("/api/soe/persons", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(personData), credentials: "include" }); } if (res.ok) { toast({ title: editingPerson ? "Pessoa atualizada!" : "Pessoa cadastrada!" }); setShowNewPersonDialog(false); setEditingPerson(null); setNewPerson({ fullName: "", cpfCnpj: "", email: "", phone: "", whatsapp: "", address: "", city: "", state: "", zipCode: "", notes: "", roles: [] }); loadPersons(); } else { const errorData = await res.json(); toast({ title: errorData.error || "Erro ao salvar pessoa", variant: "destructive" }); } } catch (error) { console.error("Error saving person:", error); toast({ title: "Erro ao salvar pessoa", variant: "destructive" }); } }; const togglePersonRole = (role: string) => { if (newPerson.roles.includes(role)) { setNewPerson({ ...newPerson, roles: newPerson.roles.filter(r => r !== role) }); } else { setNewPerson({ ...newPerson, roles: [...newPerson.roles, role] }); } }; // Estado para preços sugeridos na aba Compras const [comprasPrices, setComprasPrices] = useState>({}); // Função para abrir diálogo de lançar em estoque const handleLancarEstoque = (order: ServiceOrder) => { loadWarehouses(); loadAllProducts(); setLancarEstoqueData({ order, warehouseId: "", productId: "", hasProduct: false }); setShowLancarEstoqueDialog(true); }; // Função para confirmar lançamento em estoque const confirmLancarEstoque = async () => { const { order, warehouseId, productId, hasProduct } = lancarEstoqueData; if (!order || !warehouseId) { toast({ title: "Selecione um depósito", variant: "destructive" }); return; } try { const acquisitionCost = parseFloat(String((order as any).estimatedValue || 0)); const repairCost = parseFloat(String((order as any).laborCost || 0)); const totalCost = acquisitionCost + repairCost; const priceFromState = comprasPrices[order.id]; const sellingPrice = parseFloat(priceFromState) || (totalCost > 0 ? totalCost * 1.5 : 100); if (sellingPrice <= 0) { toast({ title: "Preço inválido", description: "O preço de venda deve ser maior que zero", variant: "destructive" }); return; } const profitMargin = totalCost > 0 ? ((sellingPrice - totalCost) / totalCost * 100).toFixed(2) : "100.00"; const deviceData = { imei: order.imei, brand: order.brand, model: order.model, color: (order as any).color || null, condition: "refurbished", status: "in_stock", acquisitionType: "trade_in", purchasePrice: String(totalCost), sellingPrice: String(sellingPrice), acquisitionCost: String(acquisitionCost), relatedServiceOrderId: order.id, suggestedPrice: String(sellingPrice), profitMargin: profitMargin, warehouseId: parseInt(warehouseId), productId: hasProduct && productId ? parseInt(productId) : null }; const res = await fetch("/api/retail/devices", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(deviceData) }); if (res.ok) { const warehouseName = warehouses.find(w => w.id === parseInt(warehouseId))?.name || "Depósito"; toast({ title: "Dispositivo lançado no estoque!", description: `${order.brand} ${order.model} adicionado em ${warehouseName}` }); loadDevices(); loadServiceOrders(); setComprasPrices(prev => { const newPrices = { ...prev }; delete newPrices[order.id]; return newPrices; }); setShowLancarEstoqueDialog(false); setLancarEstoqueData({ order: null, warehouseId: "", productId: "", hasProduct: false }); } else { const error = await res.json(); toast({ title: "Erro ao lançar em estoque", description: error.message || "Verifique os dados", variant: "destructive" }); } } catch (error) { console.error("Error launching to stock:", error); toast({ title: "Erro ao lançar em estoque", variant: "destructive" }); } }; const handleTogglePersonActive = async (personId: number, isActive: boolean) => { try { const res = await fetch(`/api/soe/persons/${personId}`, { method: "PUT", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ isActive }) }); if (res.ok) { toast({ title: isActive ? "Pessoa ativada!" : "Pessoa inativada!" }); loadPersons(); } else { toast({ title: "Erro ao alterar status", variant: "destructive" }); } } catch (error) { console.error("Error toggling person status:", error); toast({ title: "Erro ao alterar status", variant: "destructive" }); } }; const handleDeletePerson = async (personId: number) => { if (!confirm("Tem certeza que deseja excluir esta pessoa? Esta ação não pode ser desfeita.")) { return; } try { const res = await fetch(`/api/soe/persons/${personId}`, { method: "DELETE", credentials: "include" }); if (res.ok) { toast({ title: "Pessoa excluída com sucesso!" }); loadPersons(); } else { toast({ title: "Erro ao excluir pessoa", variant: "destructive" }); } } catch (error) { console.error("Error deleting person:", error); toast({ title: "Erro ao excluir pessoa", variant: "destructive" }); } }; // Carregar créditos do cliente const loadCustomerCredits = async (person: any) => { try { const res = await fetch(`/api/retail/customer-credits/${person.id}`, { credentials: "include" }); if (res.ok) { const data = await res.json(); setViewingCredits({ person, credits: data.credits, totalAvailable: data.totalAvailable }); setShowCreditsDialog(true); } } catch (error) { console.error("Error loading credits:", error); toast({ title: "Erro ao carregar créditos", variant: "destructive" }); } }; // Imprimir comprovante de crédito const printCreditReceipt = async (creditId: number) => { try { const res = await fetch(`/api/retail/customer-credits/${creditId}/receipt`, { credentials: "include" }); if (res.ok) { const data = await res.json(); const receipt = data.receiptData; const printWindow = window.open("", "_blank", "width=400,height=600"); if (printWindow) { printWindow.document.write(` Comprovante de Crédito
${receipt.title}
Nº ${receipt.creditNumber}
Cliente:${receipt.customerName}
${receipt.customerCpf ? `
CPF/CNPJ:${receipt.customerCpf}
` : ''}
Origem:${receipt.origin}
${receipt.originNumber ? `
Ref:${receipt.originNumber}
` : ''}
R$ ${parseFloat(receipt.remainingAmount).toFixed(2)}
Valor Original:R$ ${parseFloat(receipt.amount).toFixed(2)}
Data:${new Date(receipt.createdAt).toLocaleDateString('pt-BR')}
${receipt.expiresAt ? `
Validade:${new Date(receipt.expiresAt).toLocaleDateString('pt-BR')}
` : '
Válido indefinidamente
'} `); printWindow.document.close(); printWindow.print(); } } } catch (error) { console.error("Error printing receipt:", error); toast({ title: "Erro ao gerar comprovante", variant: "destructive" }); } }; const loadTemplateDetails = async (templateId: number) => { try { const res = await fetch(`/api/retail/checklist/templates/${templateId}`); if (res.ok) { const data = await res.json(); setSelectedTemplate(data); } } catch (error) { console.error("Error loading template details:", error); } }; const createTemplate = async () => { try { const res = await fetch("/api/retail/checklist/templates", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(newTemplate) }); if (res.ok) { const data = await res.json(); setChecklistTemplates([data, ...checklistTemplates]); setShowNewTemplateDialog(false); setNewTemplate({ name: "", description: "", deviceCategory: "smartphone" }); toast({ title: "Modelo criado com sucesso!" }); loadTemplateDetails(data.id); } } catch (error) { toast({ title: "Erro ao criar modelo", variant: "destructive" }); } }; const deleteTemplate = async (id: number) => { if (!confirm("Deseja excluir este modelo de checklist?")) return; try { await fetch(`/api/retail/checklist/templates/${id}`, { method: "DELETE" }); setChecklistTemplates(checklistTemplates.filter(t => t.id !== id)); if (selectedTemplate?.id === id) setSelectedTemplate(null); toast({ title: "Modelo excluído" }); } catch (error) { toast({ title: "Erro ao excluir", variant: "destructive" }); } }; const createChecklistItem = async () => { if (!selectedTemplate) return; try { const res = await fetch("/api/retail/checklist/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...newItem, templateId: selectedTemplate.id }) }); if (res.ok) { setShowNewItemDialog(false); setNewItem({ category: "visual", itemName: "", itemDescription: "", evaluationType: "condition", options: '["Perfeito","Bom","Regular","Ruim"]', impactOnValue: "0", isRequired: true, displayOrder: 0 }); loadTemplateDetails(selectedTemplate.id); toast({ title: "Item adicionado!" }); } } catch (error) { toast({ title: "Erro ao adicionar item", variant: "destructive" }); } }; const updateChecklistItem = async () => { if (!editingItem) return; try { await fetch(`/api/retail/checklist/items/${editingItem.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(editingItem) }); setEditingItem(null); if (selectedTemplate) loadTemplateDetails(selectedTemplate.id); toast({ title: "Item atualizado!" }); } catch (error) { toast({ title: "Erro ao atualizar", variant: "destructive" }); } }; const deleteChecklistItem = async (id: number) => { if (!confirm("Excluir este item?")) return; try { await fetch(`/api/retail/checklist/items/${id}`, { method: "DELETE" }); if (selectedTemplate) loadTemplateDetails(selectedTemplate.id); toast({ title: "Item excluído" }); } catch (error) { toast({ title: "Erro ao excluir", variant: "destructive" }); } }; const getCategoryLabel = (cat: string) => { const labels: Record = { visual: "Condição Visual", funcional: "Testes Funcionais", acessorios: "Acessórios", documentacao: "Documentação" }; return labels[cat] || cat; }; const getEvalTypeLabel = (type: string) => { const labels: Record = { condition: "Escala de Condição", boolean: "Sim/Não", percentage: "Percentual", text: "Texto" }; return labels[type] || type; }; const loadEmpresas = async () => { try { const res = await fetch("/api/soe/empresas", { credentials: "include" }); if (res.ok) { const data = await res.json(); setEmpresas(data); if (data.length > 0 && !selectedEmpresaId) { const firstId = data[0].id; setSelectedEmpresaId(firstId); localStorage.setItem("retail_empresa_id", String(firstId)); } } } catch (error) { console.error("Error loading empresas:", error); } }; const loadSellers = async () => { try { const res = await fetch("/api/retail/sellers", { credentials: "include" }); if (res.ok) { const data = await res.json(); setSellers(data.filter((s: any) => s.isActive !== false)); } } catch (error) { console.error("Error loading sellers:", error); } }; const handleSelectEmpresa = (id: string) => { const numId = parseInt(id); setSelectedEmpresaId(numId); localStorage.setItem("retail_empresa_id", String(numId)); }; const handleSelectSeller = (id: string) => { const numId = parseInt(id); setSelectedSellerId(numId); localStorage.setItem("retail_seller_id", String(numId)); }; const requireSession = (): boolean => { if (!selectedEmpresaId || !selectedSellerId) { setShowSessionRequired(true); return false; } return true; }; const selectedEmpresa = empresas.find(e => e.id === selectedEmpresaId); const selectedSeller = sellers.find(s => s.id === selectedSellerId); const loadDashboardStats = async () => { try { const res = await fetch("/api/retail/dashboard/stats", { credentials: "include" }); if (res.ok) { const data = await res.json(); setStats(data); } } catch (error) { console.error("Error loading stats:", error); } }; const loadDevices = async () => { try { const res = await fetch(`/api/retail/devices${searchTerm ? `?search=${searchTerm}` : ""}`, { credentials: "include" }); if (res.ok) { const data = await res.json(); setDevices(data); } } catch (error) { console.error("Error loading devices:", error); } }; const loadServiceOrders = async () => { try { const res = await fetch("/api/retail/service-orders", { credentials: "include" }); if (res.ok) { const data = await res.json(); setServiceOrders(data); } } catch (error) { console.error("Error loading service orders:", error); } }; const loadEvaluations = async () => { try { const res = await fetch("/api/retail/evaluations", { credentials: "include" }); if (res.ok) { const data = await res.json(); setEvaluations(data); } } catch (error) { console.error("Error loading evaluations:", error); } }; const handleSearch = () => { loadDevices(); }; const formatCurrency = (value: string | number | undefined) => { if (!value) return "R$ 0,00"; const num = typeof value === "string" ? parseFloat(value) : value; return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(num); }; const getStatusBadge = (status: string) => { const statusConfig: Record = { in_stock: { variant: "default", label: "Em Estoque" }, sold: { variant: "secondary", label: "Vendido" }, in_service: { variant: "outline", label: "Em Serviço" }, leased: { variant: "outline", label: "Alugado" }, open: { variant: "default", label: "Aberta" }, in_progress: { variant: "outline", label: "Em Andamento" }, waiting_parts: { variant: "outline", label: "Aguardando Peças" }, completed: { variant: "secondary", label: "Concluída" }, cancelled: { variant: "destructive", label: "Cancelada" }, pending: { variant: "outline", label: "Pendente" }, in_analysis: { variant: "secondary", label: "Em Análise" }, approved: { variant: "default", label: "Aprovado" }, rejected: { variant: "destructive", label: "Rejeitado" }, pending_preparation: { variant: "outline", label: "Em Preparação" } }; const config = statusConfig[status] || { variant: "outline" as const, label: status }; return {config.label}; }; const getConditionBadge = (condition: string) => { const conditionConfig: Record = { new: { variant: "default", label: "Novo" }, refurbished: { variant: "secondary", label: "Recondicionado" }, used: { variant: "outline", label: "Usado" }, trade_in: { variant: "secondary", label: "Trade-In" }, excellent: { variant: "default", label: "Excelente" }, good: { variant: "secondary", label: "Bom" }, fair: { variant: "outline", label: "Regular" }, poor: { variant: "destructive", label: "Ruim" } }; const config = conditionConfig[condition] || { variant: "outline" as const, label: condition }; return {config.label}; }; // PDV Functions const cartSubtotal = cart.reduce((sum, item) => sum + (item.unitPrice * item.quantity), 0); const cartDiscountTotal = cart.reduce((sum, item) => sum + item.discount, 0) + pdvDiscount; const cartTotal = cartSubtotal - cartDiscountTotal - tradeInCredit - (useCredit ? creditAmountToUse : 0); const addDeviceToCart = (device: MobileDevice) => { setPendingDevice(device); setImeiInput(""); setShowImeiModal(true); }; const confirmImeiAndAdd = () => { if (!pendingDevice) return; if (imeiInput !== pendingDevice.imei) { toast({ title: "IMEI não confere!", description: "O IMEI digitado não corresponde ao dispositivo selecionado.", variant: "destructive" }); return; } const alreadyInCart = cart.find(item => item.imei === pendingDevice.imei); if (alreadyInCart) { toast({ title: "Dispositivo já no carrinho", variant: "destructive" }); return; } const newItem: CartItem = { id: `dev-${pendingDevice.id}-${Date.now()}`, type: "device", deviceId: pendingDevice.id, imei: pendingDevice.imei, name: `${pendingDevice.brand} ${pendingDevice.model}`, description: `${pendingDevice.storage || ""} ${pendingDevice.color || ""} - IMEI: ${pendingDevice.imei}`, quantity: 1, unitPrice: parseFloat(pendingDevice.sellingPrice || "0"), discount: 0 }; setCart([...cart, newItem]); setShowImeiModal(false); setPendingDevice(null); toast({ title: "Dispositivo adicionado ao carrinho!" }); }; const addServiceOrderToCart = (so: ServiceOrder) => { const alreadyInCart = cart.find(item => item.serviceOrderId === so.id); if (alreadyInCart) { toast({ title: "O.S. já está no carrinho", variant: "destructive" }); return; } const newItem: CartItem = { id: `os-${so.id}-${Date.now()}`, type: "service", serviceOrderId: so.id, name: `Serviço: ${so.orderNumber}`, description: `${so.issueDescription} - ${so.brand} ${so.model}`, quantity: 1, unitPrice: parseFloat(so.totalCost || "0"), discount: 0 }; setCart([...cart, newItem]); toast({ title: "O.S. adicionada ao carrinho!" }); }; const addProductToCart = (product: any, qty: number = 1) => { const existingItem = cart.find(item => item.type === "product" && item.productId === product.id); if (existingItem) { setCart(cart.map(item => item.id === existingItem.id ? { ...item, quantity: item.quantity + qty } : item )); toast({ title: "Quantidade atualizada no carrinho!" }); return; } const newItem: CartItem = { id: `prod-${product.id}-${Date.now()}`, type: "product", productId: product.id, name: product.name, code: product.code || "", ncm: product.ncm || "", description: `${product.code} - ${product.category || ""}`, quantity: qty, unitPrice: parseFloat(product.salePrice || product.sale_price || "0"), discount: 0 }; setCart([...cart, newItem]); toast({ title: "Produto adicionado ao carrinho!" }); }; const fetchPdvProducts = async () => { try { const res = await fetch("/api/retail/pdv-products", { credentials: "include" }); if (res.ok) { const data = await res.json(); setPdvProducts(data); } } catch (error) { console.error("Erro ao buscar produtos:", error); } }; const removeFromCart = (itemId: string) => { setCart(cart.filter(item => item.id !== itemId)); }; const updateItemDiscount = (itemId: string, discount: number) => { setCart(cart.map(item => item.id === itemId ? { ...item, discount } : item)); }; const selectCustomer = async (person: any) => { setPdvCustomer(person); setShowCustomerModal(false); toast({ title: `Cliente selecionado: ${person.fullName}` }); // Buscar créditos e trade-ins do cliente try { const [creditsRes, tradeInsRes] = await Promise.all([ fetch(`/api/retail/credits/by-person/${person.id}`, { credentials: "include" }), fetch(`/api/retail/customer-trade-ins/${person.id}`, { credentials: "include" }) ]); if (creditsRes.ok) { const data = await creditsRes.json(); setCustomerCredits(data.credits || []); setCustomerTotalCredit(data.totalAvailable || 0); } if (tradeInsRes.ok) { const data = await tradeInsRes.json(); setCustomerTradeIns(data.tradeIns || []); // Mostrar alerta se tiver trade-ins ou créditos const hasApproved = data.tradeIns?.some((t: any) => t.status === "approved"); const hasPending = data.tradeIns?.some((t: any) => t.status === "pending" || t.status === "analyzing"); if (hasApproved && data.totalCredit > 0) { setCustomerTotalCredit(data.totalCredit); setShowTradeInAlert(true); toast({ title: "Trade-In Aprovado!", description: `Cliente possui R$ ${data.totalCredit.toFixed(2)} em créditos disponíveis` }); } else if (hasPending) { setShowTradeInAlert(true); toast({ title: "Trade-In em Andamento", description: "Cliente possui avaliação(ões) pendente(s) ou em análise" }); } } } catch (error) { console.error("Erro ao buscar dados do cliente:", error); } }; // Função para verificar senha de gerente const verifyManagerPassword = async (password: string): Promise => { try { const res = await fetch("/api/retail/verify-manager-password", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ password, action: pendingManagerAction?.type }) }); return res.ok; } catch (error) { return false; } }; const handleManagerPasswordSubmit = async () => { const authorized = await verifyManagerPassword(managerPassword); if (authorized) { setShowManagerPasswordModal(false); setManagerPassword(""); if (pendingManagerAction?.type === "price_change" && pendingManagerAction.data) { // Aplicar alteração de preço const { itemId, newPrice } = pendingManagerAction.data; setCart(cart.map(item => item.id === itemId ? { ...item, unitPrice: newPrice } : item )); toast({ title: "Preço alterado com autorização do gerente" }); } else if (pendingManagerAction?.type === "discount" && pendingManagerAction.data) { // Aplicar desconto const { itemId, discount } = pendingManagerAction.data; setCart(cart.map(item => item.id === itemId ? { ...item, discount } : item )); toast({ title: "Desconto aplicado com autorização do gerente" }); } setPendingManagerAction(null); } else { toast({ title: "Senha incorreta", variant: "destructive" }); } }; const requestManagerAuthorization = (type: string, data?: any) => { setPendingManagerAction({ type, data }); setShowManagerPasswordModal(true); }; // Funções de Devolução const searchReturnSales = async () => { try { const params = new URLSearchParams(); if (pdvCustomer?.id) params.append("personId", pdvCustomer.id); if (returnSearch) params.append("search", returnSearch); const res = await fetch(`/api/retail/sales-for-return?${params.toString()}`, { credentials: "include" }); if (res.ok) { const data = await res.json(); setReturnSales(data); } } catch (error) { console.error("Erro ao buscar vendas:", error); } }; const processReturn = async () => { if (!selectedReturnSale || returnItems.length === 0) { toast({ title: "Selecione itens para devolução", variant: "destructive" }); return; } setProcessingReturn(true); try { // Preparar itens com campos corretos const formattedItems = returnItems.map(item => ({ itemCode: item.productCode || item.imei || "", itemName: item.itemName || item.productName || "Produto", quantity: item.quantity || 1, unitPrice: item.unitPrice || item.totalPrice || "0", imei: item.imei, deviceId: item.deviceId, reason: returnReason })); const res = await fetch("/api/retail/returns", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ originalSaleId: selectedReturnSale.id, customerId: pdvCustomer?.id || selectedReturnSale.customerId, customerName: pdvCustomer?.fullName || selectedReturnSale.customerName, reason: returnReason, returnType: "return", refundMethod: "credit", generateCredit: returnGenerateCredit, creditExpirationDays: returnCreditExpiration, items: formattedItems }) }); if (res.ok) { const data = await res.json(); const creditMsg = data.credit ? ` - Crédito de R$ ${parseFloat(data.credit.amount).toFixed(2)} gerado!` : ""; toast({ title: "Devolução processada!", description: `${data.return?.returnNumber || "Devolução registrada"}${creditMsg}` }); setShowReturnModal(false); setSelectedReturnSale(null); setReturnItems([]); setReturnReason(""); setReturnSales([]); // Atualizar créditos se gerou const customerId = pdvCustomer?.id || selectedReturnSale?.customerId; if (data.credit && customerId) { const creditsRes = await fetch(`/api/retail/customer-credits/${customerId}`, { credentials: "include" }); if (creditsRes.ok) { const creditsData = await creditsRes.json(); setCustomerCredits(creditsData.credits || []); setCustomerTotalCredit(creditsData.totalAvailable || 0); } } loadDevices(); } else { const error = await res.json(); toast({ title: error.error || "Erro ao processar devolução", variant: "destructive" }); } } catch (error) { toast({ title: "Erro ao processar devolução", variant: "destructive" }); } finally { setProcessingReturn(false); } }; const toggleReturnItem = (item: any) => { const exists = returnItems.find(i => i.id === item.id); if (exists) { setReturnItems(returnItems.filter(i => i.id !== item.id)); } else { setReturnItems([...returnItems, item]); } }; // Iniciar Análise de Trade-In const handleStartAnalysis = async (evaluationId: number) => { try { const res = await fetch(`/api/retail/evaluations/${evaluationId}/start-analysis`, { method: "PUT", credentials: "include" }); if (res.ok) { toast({ title: "Avaliação em análise!" }); loadEvaluations(); } } catch (error) { toast({ title: "Erro ao iniciar análise", variant: "destructive" }); } }; const clearCart = () => { setCart([]); setPdvCustomer(null); setTradeInCredit(0); setPdvDiscount(0); setCustomerCredits([]); setCustomerTotalCredit(0); setUseCredit(false); setCreditAmountToUse(0); setPaymentMethods([]); setCustomerTradeIns([]); setShowTradeInAlert(false); }; const loadAvailableOs = async () => { try { const res = await fetch("/api/retail/service-orders", { credentials: "include" }); if (res.ok) { const data = await res.json(); setAvailableOs(data.filter((so: ServiceOrder) => so.status === "awaiting_pickup" || so.status === "completed" || so.status === "ready_for_pickup" )); } } catch (error) { console.error("Error loading service orders:", error); } }; useEffect(() => { if (activeTab === "pdv") { loadDevices(); loadAvailableOs(); fetchPdvProducts(); } }, [activeTab]); const printSaleOrder = (saleResult: any, cartItems: CartItem[], customer: any, empresa: any, seller: any, payments: {method: string; amount: number}[]) => { const now = new Date(); const dateStr = now.toLocaleDateString('pt-BR'); const paymentLabels: Record = { cash: "Dinheiro", credit: "Crédito Loja", credit_card: "Cartão de Crédito", debit: "Débito", debit_card: "Cartão de Débito", pix: "PIX", combined: "Combinado", customer_credit: "Crédito Cliente" }; const itemsRows = cartItems.map((item, i) => ` ${item.code || item.productId || item.deviceId || i + 1} ${item.ncm || '-'} ${item.name} ${item.imei || '-'} ${item.quantity} UN ${formatCurrency(item.unitPrice)} ${formatCurrency((item.unitPrice * item.quantity) - item.discount)} `).join(''); const subtotal = cartItems.reduce((s, item) => s + (item.unitPrice * item.quantity), 0); const totalDiscount = cartItems.reduce((s, item) => s + item.discount, 0); const totalFinal = parseFloat(saleResult.totalAmount || "0"); const paymentRows = payments.length > 0 ? payments.map((pm, i) => ` ${i + 1} ${paymentLabels[pm.method] || pm.method} ${dateStr} ${formatCurrency(pm.amount)} `).join('') : ` 1 ${paymentLabels[saleResult.paymentMethod] || saleResult.paymentMethod || 'Dinheiro'} ${dateStr} ${formatCurrency(totalFinal)} `; const html = ` Pedido ${saleResult.saleNumber}

${empresa?.nomeFantasia || empresa?.razaoSocial || 'Loja'}

${empresa?.razaoSocial || ''}

CNPJ ${empresa?.cnpj || ''}

${empresa?.logradouro || ''}${empresa?.numero ? ', ' + empresa.numero : ''} ${empresa?.bairro || ''} - ${empresa?.cidade || ''}${empresa?.uf ? ' - ' + empresa.uf : ''}

Email: ${empresa?.email || ''}    Fone: ${empresa?.phone || ''}

${saleResult.saleNumber}
${dateStr}
${dateStr}
${customer?.fullName || customer?.name || 'Consumidor'}
${customer?.cpfCnpj || '-'}
${customer?.phone || customer?.celular || '-'}
${customer?.email || '-'}
${customer?.address || customer?.logradouro || '-'}
${customer?.city || customer?.cidade || '-'}${customer?.state || customer?.uf ? ' - ' + (customer.state || customer.uf) : ''}
${customer?.neighborhood || customer?.bairro || '-'}
${payments.length > 1 ? 'Combinado' : paymentLabels[payments[0]?.method || saleResult.paymentMethod] || 'A Vista'}
${seller?.name || '-'}
Do Emitente
${customer?.cep || customer?.zipCode || '-'}
Produtos e Serviços
${itemsRows}
Código NCM Descrição N. Série Qtd. Un Valor Unitário Valor Total
Total ${formatCurrency(subtotal - totalDiscount)}
Totais
${formatCurrency(0)}
${formatCurrency(totalDiscount)}
${formatCurrency(subtotal)}
${formatCurrency(totalFinal)}
Condições de Vendas
${paymentRows}
Nº Pagto Forma de Pagamento Vencimento Valor
Termos de Garantia
SOBRE A GARANTIA: Garantia de acessórios de 90 dias a contar a partir da data de compra. Garantia de aparelhos Seminovos a partir da data de compra tem prazo de 3 meses para modelos abaixo do iPhone X e de 12 meses para modelos acima do iPhone 11, para aparelhos Novos 1 ano de garantia pela Apple. GARANTIA É CANCELADA automaticamente nos seguintes casos: queda, esmagamentos, sobrecargas elétricas, exposição a altas temperaturas, umidade ou líquidos, exposição a poeira ou limalha de metais ou em casos que seja constatado mau uso do aparelho, instalações, modificações ou atualizações no sistema operacional; Abertura do equipamento ou tentativa de conserto destes por terceiros que não sejam os técnicos autorizados, mesmo que para realizações de outros serviços, bem como a violação do selo/lacre de garantia. AGRADECEMOS PELA CONFIANÇA.
Observações
Gerado automaticamente através do módulo PDV - Arcádia Retail
`; const printWindow = window.open('', '_blank'); if (printWindow) { printWindow.document.write(html); printWindow.document.close(); setTimeout(() => printWindow.print(), 500); } }; const finalizeSale = async () => { if (!requireSession()) return; if (!pdvCustomer) { toast({ title: "Selecione um cliente", variant: "destructive" }); return; } if (cart.length === 0) { toast({ title: "Carrinho vazio", variant: "destructive" }); return; } // Calcular totais com validação const totalCreditsUsed = tradeInCredit + (useCredit ? creditAmountToUse : 0); const baseTotal = cartSubtotal - cartDiscountTotal; const finalTotal = Math.max(0, baseTotal - totalCreditsUsed); // Validar: créditos não podem ser maiores que o valor da compra if (totalCreditsUsed > baseTotal) { toast({ title: "Créditos excedem o valor da compra", variant: "destructive" }); return; } // Permitir venda com valor zero se pago integralmente com créditos if (finalTotal < 0) { toast({ title: "Total da venda inválido", variant: "destructive" }); return; } try { // Determinar método de pagamento principal let paymentMethod = paymentMethods.length > 0 ? paymentMethods[0].method : "cash"; if (useCredit && creditAmountToUse > 0 && finalTotal === 0) { paymentMethod = "customer_credit"; // Pago 100% com crédito } const saleData = { sessionId: null, storeId: 1, empresaId: selectedEmpresaId, sellerId: selectedSellerId, sellerName: selectedSeller?.name || "", customerId: String(pdvCustomer.id), personId: pdvCustomer.id, customerName: pdvCustomer.fullName, customerCpf: pdvCustomer.cpfCnpj || "", saleType: "direct_sale", subtotal: String(cartSubtotal), totalAmount: String(finalTotal), discountAmount: String(cartDiscountTotal), tradeInValue: String(tradeInCredit), creditUsed: useCredit ? String(creditAmountToUse) : "0", paymentMethod: paymentMethod, paymentMethods: paymentMethods, status: "completed", items: cart.map(item => ({ itemType: item.type === "device" ? "device" : item.type === "service" ? "service" : "product", itemCode: item.deviceId ? `DEV-${item.deviceId}` : item.serviceOrderId ? `OS-${item.serviceOrderId}` : "", itemName: item.name, imei: item.imei || null, deviceId: item.deviceId || null, quantity: item.quantity, unitPrice: String(item.unitPrice), discountAmount: String(item.discount), totalPrice: String((item.unitPrice * item.quantity) - item.discount) })) }; console.log("[SALE] Enviando dados:", JSON.stringify(saleData, null, 2)); const res = await fetch("/api/retail/sales", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(saleData) }); if (res.ok) { const saleResult = await res.json(); // Créditos são processados atomicamente no servidor junto com a venda for (const item of cart) { if (item.serviceOrderId) { await fetch(`/api/retail/service-orders/${item.serviceOrderId}`, { method: "PUT", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ status: "billed" }) }); } } printSaleOrder(saleResult, cart, pdvCustomer, selectedEmpresa, selectedSeller, paymentMethods); toast({ title: "Venda finalizada com sucesso!" }); clearCart(); setShowPaymentModal(false); loadDevices(); loadDashboardStats(); } else { const errorData = await res.json().catch(() => ({})); throw new Error(errorData.message || errorData.error || "Failed to save sale"); } } catch (error: any) { console.error("Erro ao finalizar venda:", error); toast({ title: "Erro ao finalizar venda", description: error?.message || "Verifique os dados e tente novamente", variant: "destructive" }); } }; const handlePdvTradeIn = (tradeInData: any) => { setTradeInCredit(parseFloat(tradeInData.valor) || 0); setShowTradeInForm(false); toast({ title: `Trade-In de R$ ${tradeInData.valor} aplicado!` }); }; return (

Arcádia Retail

Loja e Assistência Técnica de Celulares

{(!selectedEmpresaId || !selectedSellerId) && (
Selecione a empresa e o vendedor para iniciar as operações.
)}
{ const operationalTabs = ["pdv", "servicos", "tradein", "compras"]; if (operationalTabs.includes(tab) && (!selectedEmpresaId || !selectedSellerId)) { if (!requireSession()) return; } setActiveTab(tab); if (tab === "estoque") { loadWarehouses(); loadProductTypes(); } }}> Dashboard PDV Pessoas Estoque Serviços Trade-In Compras Cadastros Relatórios Comissões Config
Dispositivos em Estoque
{stats?.devicesInStock || 0}
Vendas Hoje
{formatCurrency(stats?.todaySalesTotal)}

{stats?.todaySalesCount || 0} vendas

OS Abertas
{stats?.openServiceOrders || 0}
Trade-In Pendentes
{stats?.pendingEvaluations || 0}
Ticket Médio
{stats?.todaySalesCount && stats?.todaySalesCount > 0 && stats?.todaySalesTotal ? formatCurrency(stats.todaySalesTotal / stats.todaySalesCount) : "R$ 0,00"}
Últimas Ordens de Serviço
{serviceOrders.slice(0, 5).map((order) => (

{order.orderNumber}

{order.brand} {order.model}

{order.customerName}

{getStatusBadge(order.status)}

{formatCurrency(order.totalCost)}

))} {serviceOrders.length === 0 && (

Nenhuma ordem de serviço

)}
Avaliações de Trade-In
{evaluations.slice(0, 5).map((eval_) => (

{eval_.brand} {eval_.model}

IMEI: {eval_.imei}

{eval_.customerName}

{getStatusBadge(eval_.status)} {eval_.overallCondition && getConditionBadge(eval_.overallCondition)}

{formatCurrency(eval_.estimatedValue)}

))} {evaluations.length === 0 && (

Nenhuma avaliação pendente

)}
{stockAlerts.length > 0 && ( Produtos com Estoque Baixo {stockAlerts.length}
{stockAlerts.map((product: any) => (

{product.name}

{product.code} | {product.category || "Sem categoria"}

Estoque: {parseFloat(product.stockQty || product.stock_qty || "0").toFixed(0)}

Mín: {parseFloat(product.minStock || product.min_stock || "0").toFixed(0)}

))}
)}
Feed de Atividades {activities.filter(a => !a.isRead).length} não lidas Acompanhe todas as atividades em tempo real (atualiza a cada 30s)
{activities.map((activity) => (
{activity.activityType === 'evaluation' ? : activity.activityType === 'service_order' ? : activity.activityType === 'sale' ? : }

{activity.title}

{activity.description && (

{activity.description}

)}

{activity.createdAt ? new Date(activity.createdAt).toLocaleString('pt-BR') : 'Data não disponível'}

{!activity.isRead && (
)}
))} {activities.length === 0 && (

Nenhuma atividade registrada ainda

)}
Ponto de Venda {selectedEmpresa && ( {selectedEmpresa.nomeFantasia || selectedEmpresa.razaoSocial} )} {selectedSeller && ( Vendedor: {selectedSeller.name} )}
{cart.length > 0 && ( )}
Dispositivos Produtos Faturar O.S.
setPdvSearch(e.target.value)} data-testid="input-search-pdv" />
Dispositivos em Estoque ({devices.filter(d => d.status === "in_stock").length})
{devices.filter(d => d.status === "in_stock") .filter(d => !pdvSearch || d.imei?.toLowerCase().includes(pdvSearch.toLowerCase()) || d.brand?.toLowerCase().includes(pdvSearch.toLowerCase()) || d.model?.toLowerCase().includes(pdvSearch.toLowerCase()) || d.color?.toLowerCase().includes(pdvSearch.toLowerCase()) || d.storage?.toLowerCase().includes(pdvSearch.toLowerCase()) || String(d.sellingPrice || (d as any).selling_price || "").includes(pdvSearch) || String(d.id).includes(pdvSearch) ) .map((device) => (

{device.brand} {device.model}

{device.storage} | {device.color} | IMEI: {device.imei}

{getConditionBadge(device.condition)} {(device as any).warehouseId && ( {warehouses.find(w => w.id === (device as any).warehouseId)?.name || `Dep. ${(device as any).warehouseId}`} )}

{formatCurrency(device.sellingPrice)}

))} {devices.filter(d => d.status === "in_stock").length === 0 && (

Nenhum dispositivo em estoque

)}
setPdvProductSearch(e.target.value)} data-testid="input-search-products" />
Produtos Disponíveis ({pdvProducts.filter(p => p.status === "active").length})
{pdvProducts .filter(p => p.status === "active") .filter(p => !pdvProductSearch || p.name?.toLowerCase().includes(pdvProductSearch.toLowerCase()) || p.code?.toLowerCase().includes(pdvProductSearch.toLowerCase()) || p.category?.toLowerCase().includes(pdvProductSearch.toLowerCase()) || p.description?.toLowerCase().includes(pdvProductSearch.toLowerCase()) || p.barcode?.toLowerCase().includes(pdvProductSearch.toLowerCase()) || p.ncm?.toLowerCase().includes(pdvProductSearch.toLowerCase()) ) .map((product) => (

{product.name}

{product.code} | {product.category || "Sem categoria"}

Estoque: {parseFloat(product.stockQty || product.stock_qty || "0").toFixed(0)} {product.unit || "UN"}

{formatCurrency(product.salePrice || product.sale_price)}

))} {pdvProducts.filter(p => p.status === "active").length === 0 && (

Nenhum produto cadastrado

)}
setPdvOsSearch(e.target.value)} data-testid="input-search-os" />
O.S. Aguardando Faturamento ({availableOs.length})
{availableOs .filter(os => !pdvOsSearch || os.orderNumber.toLowerCase().includes(pdvOsSearch.toLowerCase()) || os.customerName.toLowerCase().includes(pdvOsSearch.toLowerCase()) ) .map((os) => (

{os.orderNumber}

{os.customerName}

{os.brand} {os.model} - {os.issueDescription}

{formatCurrency(os.totalCost)}

))} {availableOs.length === 0 && (

Nenhuma O.S. aguardando faturamento

)}

Carrinho ({cart.length} {cart.length === 1 ? "item" : "itens"})

{cart.length === 0 ? (

Carrinho vazio. Adicione produtos para iniciar a venda.

) : (
{cart.map((item) => (

{item.name}

{item.description}

{item.type === "device" && ( Celular )} {item.type === "service" && ( Serviço )}
{formatCurrency(item.unitPrice)}
))}
)}
Subtotal: {formatCurrency(cartSubtotal)}
Desconto: - {formatCurrency(cartDiscountTotal)}
{tradeInCredit > 0 && (
Trade-In: - {formatCurrency(tradeInCredit)}
)} {useCredit && creditAmountToUse > 0 && (
Crédito: - {formatCurrency(creditAmountToUse)}
)}
Total: {formatCurrency(cartTotal > 0 ? cartTotal : 0)}
{customerTotalCredit > 0 && !useCredit && (
Crédito disponível: {formatCurrency(customerTotalCredit)}
)}
{!pdvCustomer && cart.length > 0 && (

Selecione um cliente para finalizar

)}
Cadastro Unificado de Pessoas Gerencie clientes, fornecedores, colaboradores e técnicos em um só lugar. Uma pessoa pode ter múltiplos papéis no sistema.
setPersonSearch(e.target.value)} data-testid="input-person-search" />
{personsList.length > 0 ? personsList.map((person: any) => ( )) : ( )}
Nome CPF/CNPJ Contato Papéis Status Ações
{person.fullName}
{person.email}
{person.cpfCnpj || "-"}
{person.phone || "-"}
{person.whatsapp &&
WhatsApp: {person.whatsapp}
}
{person.roles?.map((role: any) => ( {role.roleType === "customer" ? "Cliente" : role.roleType === "supplier" ? "Fornecedor" : role.roleType === "employee" ? "Colaborador" : role.roleType === "technician" ? "Técnico" : role.roleType === "partner" ? "Parceiro" : role.roleType} ))}
{person.isActive ? "Ativo" : "Inativo"}
Nenhuma pessoa cadastrada. Clique em "Nova Pessoa" para começar.
{/* Depósitos */}
Depósitos
Gerenciamento de depósitos, saldos e transferências de estoque
{/* Lista de Depósitos */}

Depósitos Cadastrados

{warehouses.length > 0 ? warehouses.map((wh: any) => (
{ setSelectedWarehouse(wh); loadWarehouseStock(wh.id); loadStockMovements(wh.id); loadStockTransfers(); }} data-testid={`warehouse-card-${wh.id}`} >

{wh.name}

Código: {wh.code}

{wh.isDefault && Padrão}
{wh.type && {wh.type === "store" ? "Loja" : wh.type === "central" ? "Central" : wh.type}}
)) : (

Nenhum depósito cadastrado

)}
{/* Saldo do Depósito Selecionado */}
{selectedWarehouse ? (

Saldo em: {selectedWarehouse.name}

{warehouseStock.length > 0 ? ( Produto Qtd Reservado Disponível {warehouseStock.map((item: any) => ( ))}

{item.productName}

{item.productCode}

{parseFloat(item.quantity || 0).toFixed(2)} {parseFloat(item.reservedQuantity || 0).toFixed(2)} {parseFloat(item.availableQuantity || 0).toFixed(2)}
) : (

Nenhum produto em estoque neste depósito

)} {/* Últimas Movimentações */} {stockMovements.length > 0 && (
Últimas Movimentações
{stockMovements.slice(0, 10).map((mov: any) => (
{mov.movementType === "entry" ? "Entrada" : mov.movementType === "exit" ? "Saída" : mov.movementType === "transfer_in" ? "Transf. Entrada" : mov.movementType === "transfer_out" ? "Transf. Saída" : mov.movementType} {mov.operationType}
{mov.movementType === "entry" || mov.movementType === "transfer_in" ? "+" : "-"}{parseFloat(mov.quantity).toFixed(2)}
))}
)} {/* Histórico de Transferências */} {stockTransfers.length > 0 && (
Transferências
{stockTransfers.slice(0, 5).map((transfer: any) => (
{transfer.status === "completed" ? "Concluída" : transfer.status === "in_transit" ? "Em Trânsito" : transfer.status === "pending" ? "Pendente" : transfer.status === "cancelled" ? "Cancelada" : transfer.status} {transfer.transferNumber}
{new Date(transfer.createdAt).toLocaleDateString('pt-BR')}
))}
)}
) : (

Selecione um depósito para ver o saldo

)}
{/* Tipos de Dispositivos e Acessórios */}
{/* Tipos de Dispositivos */}
Tipos de Dispositivos
Produtos com IMEI/série (celulares, tablets, smartwatches)
{deviceTypes.length > 0 ? deviceTypes.map((type: any) => (

{type.name}

{type.ncm && NCM: {type.ncm}} {type.cfopVendaEstadual && CFOP: {type.cfopVendaEstadual}} {type.requiresImei && IMEI}
)) : (

Nenhum tipo cadastrado

)}
{/* Tipos de Acessórios */}
Tipos de Acessórios
Produtos sem série (capas, películas, cabos, carregadores)
{accessoryTypes.length > 0 ? accessoryTypes.map((type: any) => (

{type.name}

{type.ncm && NCM: {type.ncm}} {type.cfopVendaEstadual && CFOP: {type.cfopVendaEstadual}}
)) : (

Nenhum tipo cadastrado

)}
{/* Estoque de Dispositivos */}
Estoque de Dispositivos Gerencie o estoque por IMEI
setSearchTerm(e.target.value)} onKeyPress={(e) => e.key === "Enter" && handleSearch()} data-testid="input-search-estoque" />
{devices.map((device) => ( ))}
IMEI Dispositivo Condição Preço Status Ações
{device.imei}

{device.brand} {device.model}

{device.storage} | {device.color}

{getConditionBadge(device.condition)} {formatCurrency(device.sellingPrice)} {getStatusBadge(device.status)}
{devices.length === 0 && (

Nenhum dispositivo cadastrado

)}
Ordens de Serviço Gestão de assistência técnica
{serviceOrders.map((order) => ( { setEditingServiceOrder(order); setOsStatus(order.status || "open"); setOsEvaluationStatus((order as any).evaluationStatus || "pending"); setOsEstimatedValue(String((order as any).estimatedValue || order.totalCost || 0)); setOsEvaluatedValue(String((order as any).evaluatedValue || (order as any).estimatedValue || order.totalCost || 0)); setOsNotes((order as any).diagnosisNotes || ""); setOsChecklistData((order as any).checklistData || {}); loadOsItems(order.id); setShowOsDetailsModal(true); }} > ))}
Número Dispositivo Cliente Problema Valor Status Ações
{order.orderNumber} {order.isInternal && ( Trade-In )}

{order.brand} {order.model}

IMEI: {order.imei}

{order.imei && warrantyCache[order.imei] && ( Em Garantia )}

{order.customerName}

{order.customerPhone}

{order.issueDescription} {formatCurrency(order.totalCost)} {getStatusBadge(order.status)} e.stopPropagation()}> {order.isInternal && order.status !== "completed" && ( )}
{serviceOrders.length === 0 && (

Nenhuma ordem de serviço

)}
Avaliações de Trade-In Avalie dispositivos usados para troca
{evaluations.map((eval_) => ( handleOpenEvaluationDetails(eval_)} > ))}
IMEI Dispositivo Cliente Condição Valor Estimado Status Ações
{eval_.imei} {eval_.brand} {eval_.model} {eval_.customerName || "-"} {eval_.overallCondition && getConditionBadge(eval_.overallCondition)} {formatCurrency(eval_.estimatedValue)} {getStatusBadge(eval_.status)} e.stopPropagation()}> {eval_.status === "pending" && (
)}
{evaluations.length === 0 && (

Nenhuma avaliação de trade-in

)}
Transferências de Estoque Movimentação entre lojas e distribuidoras

Nenhuma transferência registrada

{/* Aba Compras/Aquisições */} {/* Ações de Compra */}
{/* Seção: OS Internas Concluídas - Prontas para Estoque */}
Manutenções Concluídas Dispositivos com manutenção interna concluída, prontos para precificação
{serviceOrders.filter(o => o.isInternal && o.status === "completed").length} pendentes
IMEI Dispositivo Custo Aquisição Custo Reparo Custo Total Preço Venda Margem Ações {serviceOrders.filter(o => o.isInternal && o.status === "completed").map((order) => { const acquisitionCost = parseFloat(String((order as any).estimatedValue || 0)); const repairCost = parseFloat(String((order as any).laborCost || 0)); const totalCost = acquisitionCost + repairCost; const suggestedPrice = totalCost * 1.5; return ( {order.imei} {order.brand} {order.model} {formatCurrency(acquisitionCost)} {formatCurrency(repairCost)} {formatCurrency(totalCost)} setComprasPrices(prev => ({ ...prev, [order.id]: e.target.value }))} /> {totalCost > 0 ? `${(((parseFloat(comprasPrices[order.id] || String(suggestedPrice)) - totalCost) / totalCost) * 100).toFixed(0)}%` : "N/A"} ); })}
{serviceOrders.filter(o => o.isInternal && o.status === "completed").length === 0 && (

Nenhuma manutenção concluída aguardando entrada em estoque

)}
{/* Seção: Avaliações Aprovadas - Aguardando Manutenção */}
Avaliações Aprovadas Trade-Ins aprovados aguardando criação de ordem de manutenção
{evaluations.filter(e => e.status === "approved" && !e.maintenanceOrderId).length} pendentes
IMEI Dispositivo Cliente Valor Estimado Data Avaliação Ações {evaluations.filter(e => e.status === "approved" && !e.maintenanceOrderId).map((evaluation) => ( {evaluation.imei} {evaluation.brand} {evaluation.model} {evaluation.customerName} {formatCurrency(evaluation.estimatedValue || 0)} {evaluation.evaluationDate ? new Date(evaluation.evaluationDate).toLocaleDateString("pt-BR") : "-"} ))}
{evaluations.filter(e => e.status === "approved" && !e.maintenanceOrderId).length === 0 && (

Nenhuma avaliação aprovada aguardando manutenção

)}
{/* Seção: Dispositivos Recém Adicionados ao Estoque */}
Entradas Recentes no Estoque Dispositivos adicionados ao estoque via Trade-In
IMEI Dispositivo Depósito Condição Custo Preço Venda Margem Status {devices.filter(d => d.acquisitionType === "trade_in").slice(0, 5).map((device) => { const cost = parseFloat(String(device.purchasePrice || 0)); const price = parseFloat(String(device.sellingPrice || 0)); const margin = cost > 0 ? ((price - cost) / cost * 100).toFixed(1) : "0"; return ( {device.imei} {device.brand} {device.model} {(device as any).warehouseId ? ( {warehouses.find(w => w.id === (device as any).warehouseId)?.name || "-"} ) : -} {device.condition === "refurbished" ? "Recondicionado" : device.condition} {formatCurrency(cost)} {formatCurrency(price)} = 30 ? "default" : "secondary"} className={parseFloat(margin) >= 30 ? "bg-green-600" : ""}> {margin}% {getStatusBadge(device.status)} ); })}
{devices.filter(d => d.acquisitionType === "trade_in").length === 0 && (

Nenhum dispositivo de Trade-In no estoque

)}
{/* Aba Cadastros */}
{ loadCadastroPaymentMethods(); setShowPaymentMethodsDialog(true); }} > Formas de Pagamento

Cartões, taxas e parcelas

{cadastroPaymentMethods.length} cadastrados
{ loadCadastroSellers(); setShowSellersDialog(true); }} > Vendedores

Equipe de vendas

{cadastroSellers.length} cadastrados
{ loadCadastroPriceTables(); setShowPriceTablesDialog(true); }} > Tabelas de Preço

Preços por tipo de cliente

{cadastroPriceTables.length} tabelas
{ loadCadastroPromotions(); setShowPromotionsDialog(true); }} > Promoções

Descontos e ofertas

{cadastroPromotions.length} ativas
{/* Aba Relatórios */} {/* Filtros de Relatórios */} Filtros
setReportDateFrom(e.target.value)} />
setReportDateTo(e.target.value)} />
setReportSubTab(v)} className="w-full"> OS por Status OS por Técnico Vendas por Vendedor Margem por IMEI Caixa Diário Giro de Estoque
OS por Status
Status Quantidade Valor Total {reportOsByStatus.length === 0 ? ( {reportLoading ? "Carregando..." : "Sem dados"} ) : reportOsByStatus.map((row: any, i: number) => ( {getStatusBadge(row.status)} {row.count} {formatCurrency(row.total_value)} ))}
OS por Técnico
Técnico Total OS Concluídas Em Andamento Receita {reportOsByTech.length === 0 ? ( {reportLoading ? "Carregando..." : "Sem dados"} ) : reportOsByTech.map((row: any, i: number) => ( {row.technician_name || "Não atribuído"} {row.total_os} {row.completed} {row.in_progress} {formatCurrency(row.total_revenue)} ))}
Vendas por Vendedor
Vendedor Total Vendas Receita Total Ticket Médio Dias Ativos {reportSalesBySeller.length === 0 ? ( {reportLoading ? "Carregando..." : "Sem dados"} ) : reportSalesBySeller.map((row: any, i: number) => ( {row.sold_by || "Sem vendedor"} {row.total_sales} {formatCurrency(row.total_revenue)} {formatCurrency(row.avg_ticket)} {row.active_days} ))}
Margem por IMEI
Dispositivo IMEI Condição Custo Venda Margem % Status {reportMarginByImei.length === 0 ? ( {reportLoading ? "Carregando..." : "Sem dados"} ) : reportMarginByImei.map((row: any, i: number) => ( {row.brand} {row.model} {row.imei} {getConditionBadge(row.condition)} {formatCurrency(row.cost)} {formatCurrency(row.sale_price)} = 0 ? 'text-green-600' : 'text-red-600'}`}>{formatCurrency(row.margin)} {parseFloat(row.margin_percent || 0).toFixed(1)}% {getStatusBadge(row.status)} ))}
setDailyCashDate(e.target.value)} className="w-44" data-testid="input-daily-cash-date" />
{reportDailyCash?.date && `Exibindo: ${new Date(reportDailyCash.date + 'T12:00:00').toLocaleDateString('pt-BR')}`}
{reportDailyCash ? ( <>

Total Vendas

{formatCurrency(reportDailyCash.total_sales)}

{reportDailyCash.sale_count} vendas

Dinheiro

{formatCurrency(reportDailyCash.cash_total)}

Cartão

{formatCurrency(reportDailyCash.card_total)}

PIX

{formatCurrency(reportDailyCash.pix_total)}

Sangrias

-{formatCurrency(reportDailyCash.withdrawals)}

Reforços

+{formatCurrency(reportDailyCash.reinforcements)}

Saldo Caixa (Dinheiro + Reforços - Sangrias)

{formatCurrency( parseFloat(reportDailyCash.cash_total || 0) + parseFloat(reportDailyCash.reinforcements || 0) - parseFloat(reportDailyCash.withdrawals || 0) )}

Vendas por Vendedor {reportDailyCash.bySeller?.length > 0 ? (
Vendedor Vendas Dinheiro Débito Crédito PIX Combinado Descontos Total {reportDailyCash.bySeller.map((seller: any, i: number) => ( {seller.sold_by || "Sem vendedor"} {seller.sale_count} {formatCurrency(seller.cash_total)} {formatCurrency(seller.debit_total)} {formatCurrency(seller.credit_total)} {formatCurrency(seller.pix_total)} {formatCurrency(seller.combined_total)} {parseFloat(seller.total_discount) > 0 ? `-${formatCurrency(seller.total_discount)}` : '-'} {formatCurrency(seller.total)} ))} TOTAL {reportDailyCash.sale_count} {formatCurrency(reportDailyCash.cash_total)} {formatCurrency(reportDailyCash.bySeller?.reduce((s: number, r: any) => s + parseFloat(r.debit_total || 0), 0))} {formatCurrency(reportDailyCash.bySeller?.reduce((s: number, r: any) => s + parseFloat(r.credit_total || 0), 0))} {formatCurrency(reportDailyCash.pix_total)} {formatCurrency(reportDailyCash.combined_total || 0)} {formatCurrency(reportDailyCash.bySeller?.reduce((s: number, r: any) => s + parseFloat(r.total_discount || 0), 0))} {formatCurrency(reportDailyCash.total_sales)}
) : (

Nenhuma venda no período

)}
Listagem de Vendas {reportDailyCash.sales?.length > 0 ? (
Venda Hora Cliente Vendedor Subtotal Desconto Total Pagamento {reportDailyCash.sales.map((sale: any, i: number) => ( {sale.sale_number} {sale.created_at ? new Date(sale.created_at).toLocaleTimeString('pt-BR', {hour: '2-digit', minute: '2-digit'}) : '-'} {sale.customer_name || "Consumidor"} {sale.sold_by || "-"} {formatCurrency(sale.subtotal)} {parseFloat(sale.discount_amount || 0) > 0 ? `-${formatCurrency(sale.discount_amount)}` : '-'} {formatCurrency(sale.total_amount)} {sale.payment_method === 'cash' ? 'Dinheiro' : sale.payment_method === 'credit' ? 'Crédito' : sale.payment_method === 'debit' ? 'Débito' : sale.payment_method === 'pix' ? 'PIX' : sale.payment_method === 'combined' ? 'Combinado' : sale.payment_method || '-'} ))}
) : (

Nenhuma venda registrada nesta data

)}
) : (

{reportLoading ? "Carregando..." : "Selecione uma data e clique em Carregar para ver o movimento do dia"}

)}
Giro de Estoque
Código Produto Categoria Estoque Atual Estoque Mínimo Vendas 30d Giro {reportStockTurnover.length === 0 ? ( {reportLoading ? "Carregando..." : "Sem dados"} ) : reportStockTurnover.map((row: any, i: number) => ( {row.code} {row.name} {row.category || "-"} {parseFloat(row.current_stock || 0).toFixed(0)} {parseFloat(row.min_stock || 0).toFixed(0)} {row.sales_30d} {parseFloat(row.turnover_ratio || 0).toFixed(2)} ))}
{/* ========== ABA COMISSÕES ========== */} {/* Período e Filtros */}
Gestão de Comissões
{/* KPIs da Loja */} {commissionDashboard && (
Total Vendas

{formatCurrency(commissionDashboard.store.totalSales)}

{commissionDashboard.store.salesCount} vendas

Devoluções

-{formatCurrency(commissionDashboard.store.totalReturns)}

{commissionDashboard.store.returnsCount} devoluções

Vendas Líquidas

{formatCurrency(commissionDashboard.store.netSales)}

Após devoluções

Meta Loja

{formatCurrency(commissionDashboard.store.goalAmount)}

{commissionDashboard.store.goalPercent}% atingido

Status Meta {commissionDashboard.store.metGoal ? "✓ Atingida" : "Em progresso"}
)}
{/* Metas por Vendedor */}
Metas por Vendedor
{commissionDashboard?.sellers?.length > 0 ? (
{commissionDashboard.sellers.map((seller: any) => (
{seller.sellerName} {seller.goalPercent}%

Vendas

{formatCurrency(seller.totalSales)}

Devoluções

-{formatCurrency(seller.totalReturns)}

Líquido

{formatCurrency(seller.netSales)}

Meta: {formatCurrency(seller.goalAmount)}
Comissão: {formatCurrency(seller.totalCommission)} {seller.bonus > 0 && ( +{formatCurrency(seller.bonus)} bônus )}
))}
) : (

Clique em "Carregar" para visualizar as metas

)}
{/* Planos de Comissão */}
Planos de Comissão
Nome Tipo Valor Base Status Ações {cadastroCommissionPlans.map((plan: any) => ( {plan.name} {plan.type === "percentage" ? "Percentual" : plan.type === "fixed" ? "Valor Fixo" : "Misto"} {plan.type === "percentage" ? `${plan.baseValue}%` : formatCurrency(plan.baseValue || 0)} {plan.isActive ? "Ativo" : "Inativo"}
))} {cadastroCommissionPlans.length === 0 && ( Nenhum plano de comissão cadastrado )}
{/* Fechamentos de Comissão */}
Fechamentos de Comissão
{commissionClosures.length > 0 ? ( Vendedor Período Líquido Comissão Status {commissionClosures.map((closure: any) => ( {cadastroSellers.find(s => s.id === closure.sellerId)?.name || "Geral"} {new Date(closure.periodStart).toLocaleDateString("pt-BR")} - {new Date(closure.periodEnd).toLocaleDateString("pt-BR")} {formatCurrency(closure.netSales)} {formatCurrency(closure.totalAmount)} {closure.status === "paid" ? "Pago" : closure.status === "closed" ? "Fechado" : "Aberto"} ))}
) : (

Nenhum fechamento registrado

)}
{/* Meta da Loja */}
Meta da Loja - {new Date(2024, commissionMonth - 1).toLocaleString("pt-BR", { month: "long" })} {commissionYear}
{storeGoals.length > 0 ? (
Progresso da Meta {commissionDashboard?.store?.goalPercent || 0}%
Atual: {formatCurrency(commissionDashboard?.store?.netSales || 0)} Meta: {formatCurrency(storeGoals[0]?.goalAmount || 0)}
) : (

Nenhuma meta definida para este período

)}
Modelos de Checklist
Selecione para editar os itens
{checklistTemplates.length === 0 ? (

Nenhum modelo cadastrado

) : ( checklistTemplates.map(template => (
loadTemplateDetails(template.id)} data-testid={`template-${template.id}`} >

{template.name}

{template.deviceCategory}

)) )}
{selectedTemplate ? `Itens: ${selectedTemplate.name}` : "Itens do Checklist"} {selectedTemplate ? `${selectedTemplate.items?.length || 0} itens configurados` : "Selecione um modelo à esquerda"}
{selectedTemplate && ( )}
{!selectedTemplate ? (

Selecione um modelo de checklist para visualizar e editar seus itens

) : selectedTemplate.items?.length === 0 ? (

Nenhum item cadastrado. Clique em "Novo Item" para começar.

) : (
{["visual", "funcional", "acessorios", "documentacao"].map(category => { const categoryItems = selectedTemplate.items?.filter(i => i.category === category) || []; if (categoryItems.length === 0) return null; return (

{getCategoryLabel(category)}

{categoryItems.map(item => (

{item.itemName}

{getEvalTypeLabel(item.evaluationType)} {item.isRequired && Obrigatório} {parseFloat(item.impactOnValue || "0") > 0 && ( -{item.impactOnValue}% se ruim )}
))}
); })}
)}
Novo Dispositivo
Nova Ordem de Serviço