commit de9bf86d94c0aaf368cb2fefab0c4391be0cdae8 Author: Marcio Bevervanso Date: Mon Jan 26 11:20:25 2026 -0300 Initial commit diff --git a/.env b/.env new file mode 100644 index 0000000..0a80675 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +VITE_SUPABASE_URL=https://cnattjitonpejcviwbdg.supabase.co +VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNuYXR0aml0b25wZWpjdml3YmRnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzOTY2MDMsImV4cCI6MjA4Mzk3MjYwM30.cLlDq2NowgeN-IN55E5Bq0ZM035DS6Vs4ICvFxMNSG8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..6fcee23 --- /dev/null +++ b/App.tsx @@ -0,0 +1,147 @@ +import React from 'react'; +import { BrowserRouter as Router, Routes, Route, Navigate, useLocation } from 'react-router-dom'; +import TopBar from './layouts/TopBar'; +import Dashboard from './pages/Dashboard'; +import Sourcing from './pages/Sourcing'; +import Orders from './pages/Orders'; +import Inventory from './pages/Inventory'; +import Financial from './pages/Financial'; +import Customers from './pages/Customers'; +import Sales from './pages/Sales'; +import Suppliers from './pages/Suppliers'; +import Users from './pages/Users'; +import Login from './pages/Login'; +import Products from './pages/Products'; // New +import Reports from './pages/Reports'; +import Settings from './pages/Settings'; +import { CRMProvider, useCRM } from './context/CRMContext'; + +import Sidebar from './layouts/Sidebar'; +import Header from './layouts/Header'; +import { useTheme } from './context/ThemeContext'; + +// Simple Layout Wrapper to handle Sidebar/Header display +const AppLayout: React.FC = () => { + const location = useLocation(); + const { session, authLoading } = useCRM(); + const { theme } = useTheme(); + const isLoginPage = location.pathname === '/login'; + + if (authLoading) { + return ( +
+
+
+ ); + } + + if (!session && !isLoginPage) { + return ; + } + + if (session && isLoginPage) { + return ; + } + + if (isLoginPage) { + return ( +
+ + } /> + +
+ ); + } + + // LAYOUT STRATEGIES + + // 1. SIMPLE / CLEAN (Light) -> Uses TopBar (ERP Style) + if (theme === 'light') { + return ( +
+ +
+
+ +
+
+
+ ); + } + + // 2. OCEAN / COMPLEX -> Uses Compact Sidebar (To be refined) or Modified TopBar + if (theme === 'ocean') { + return ( +
+ {/* Sidebar for Ocean */} + + +
+
+
+
+ +
+
+
+
+ ); + } + + // 3. DARK / PRO (Default) -> Sidebar + Header (Glassmorphism) + const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false); + + return ( +
+ setIsMobileMenuOpen(false)} /> +
+ {/* Background Blob Effect for Pro Theme */} +
+ +
setIsMobileMenuOpen(!isMobileMenuOpen)} /> +
+ +
+
+
+ ); +}; + +// Extracted Routes to avoid duplication +const AppRoutes = () => ( + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +); + +import ErrorBoundary from './components/ErrorBoundary'; +import { ThemeProvider } from './context/ThemeContext'; + +const App: React.FC = () => { + return ( + + + + {/* Main Router Setup */} + + + + + + + ); +}; + +export default App; diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f342db --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +
+GHBanner +
+ +# Run and deploy your AI Studio app + +This contains everything you need to run your app locally. + +View your app in AI Studio: https://ai.studio/apps/drive/1SvuBB4oPz2uAYIkGNsKO0Y44_Y_khSrI + +## Run Locally + +**Prerequisites:** Node.js + + +1. Install dependencies: + `npm install` +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: + `npm run dev` diff --git a/components/ErrorBoundary.tsx b/components/ErrorBoundary.tsx new file mode 100644 index 0000000..a062b1b --- /dev/null +++ b/components/ErrorBoundary.tsx @@ -0,0 +1,51 @@ +import React, { Component, ErrorInfo, ReactNode } from 'react'; + +interface Props { + children?: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +class ErrorBoundary extends Component { + public state: State = { + hasError: false, + error: null + }; + + public static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + public componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error("Uncaught error:", error, errorInfo); + } + + public render() { + if (this.state.hasError) { + return ( +
+

Software Crash Detected

+
+

{this.state.error?.name}: {this.state.error?.message}

+
+                            {this.state.error?.stack}
+                        
+
+ +
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/components/Logo.tsx b/components/Logo.tsx new file mode 100644 index 0000000..76769f7 --- /dev/null +++ b/components/Logo.tsx @@ -0,0 +1,34 @@ + +import React from 'react'; + +interface LogoProps { + className?: string; + showText?: boolean; + size?: 'sm' | 'md' | 'lg'; +} + +const Logo: React.FC = ({ className = "", showText = true, size = 'md' }) => { + // Sizes + const dim = size === 'sm' ? 24 : size === 'md' ? 32 : 48; + const textSize = size === 'sm' ? "text-lg" : size === 'md' ? "text-xl" : "text-3xl"; + + return ( +
+ {/* GEOMETRIC LOGO MARK - Abstract 'A' / Graph Up */} + + + + + + + {showText && ( +
+

ARBITRA

+ {size !== 'sm' &&

PRO SYSTEM

} +
+ )} +
+ ); +}; + +export default Logo; diff --git a/components/MarketplaceAnalytic.tsx b/components/MarketplaceAnalytic.tsx new file mode 100644 index 0000000..7fa8f32 --- /dev/null +++ b/components/MarketplaceAnalytic.tsx @@ -0,0 +1,156 @@ + +import React from 'react'; +import { Product, PLATFORMS, CalculationResult } from '../types'; +import { Info, Target, TrendingUp, AlertCircle, CheckCircle2, ExternalLink } from 'lucide-react'; + +interface Props { + product: Product; + overheadPercent: number; + useOverhead: boolean; +} + +const MarketplaceAnalytic: React.FC = ({ product, overheadPercent, useOverhead }) => { + const costParaguay = product.priceBRL; + const overhead = useOverhead ? (costParaguay * (overheadPercent / 100)) : 0; + const totalCost = costParaguay + overhead; + + const results: CalculationResult[] = PLATFORMS.map(platform => { + let platformPrice = product.marketPriceBRL || (totalCost * 1.5); + let platformUrl = ''; + + if (platform.name === 'Amazon' && product.amazonPrice) { + platformPrice = product.amazonPrice; + platformUrl = product.amazonUrl || ''; + } else if (platform.name === 'Mercado Livre' && product.mlPrice) { + platformPrice = product.mlPrice; + platformUrl = product.mlUrl || ''; + } else if (platform.name === 'Shopee' && product.shopeePrice) { + platformPrice = product.shopeePrice; + platformUrl = product.shopeeUrl || ''; + } else if (platform.name === 'Facebook') { + platformPrice = product.marketPriceBRL || (totalCost * 1.6); // Meta de venda + } + + const fees = (platformPrice * platform.commission) + platform.fixedFee; + const netProfit = platformPrice - fees - totalCost; + const margin = (netProfit / platformPrice) * 100; + + return { + platform: platform.name, + marketPrice: platformPrice, + totalCost, + fees, + netProfit, + margin, + url: platformUrl + }; + }); + + return ( +
+
+ {/* Header de Info do Produto */} +
+
+
+ Análise de Margem por Canal +

{product.name}

+

Referência Paraguay: {product.store}

+
+
+ Média Brasil + R$ {(product.marketPriceBRL || 0).toLocaleString('pt-BR')} +
+
+
+ +
+ {/* Breakdown de Custos */} +
+
+

Custo PY

+

R$ {costParaguay.toLocaleString('pt-BR')}

+
+
+

Custos Log/Fixo ({useOverhead ? `+${overheadPercent}%` : 'OFF'})

+

R$ {overhead.toLocaleString('pt-BR')}

+
+
+

Custo Final BR

+

R$ {totalCost.toLocaleString('pt-BR')}

+
+
+ +
+

Comparação em Tempo Real

+ CLIQUE NO PREÇO PARA VER O ANÚNCIO +
+ + +
+ +
+
+ +
+

+ As margens exibidas consideram seu custo operacional fixo de {useOverhead ? `${overheadPercent}%` : '0%'}. Os preços dos marketplaces são extraídos em tempo real via busca web. +

+
+
+
+ ); +}; + +export default MarketplaceAnalytic; diff --git a/context/CRMContext.tsx b/context/CRMContext.tsx new file mode 100644 index 0000000..13b38b9 --- /dev/null +++ b/context/CRMContext.tsx @@ -0,0 +1,849 @@ + +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { Product, ShoppingListItem, Order, InventoryItem, Supplier, User, PlatformFee, PLATFORMS, Customer, Transaction, FinancialSummary, AuctionLot, BiddingTender, Sale, SalesChannel, PaymentMethod, OrderStatus } from '../types'; +import { searchProducts } from '../services/geminiService'; +import { supabase } from '../services/supabase'; +import { Session, User as SupabaseUser } from '@supabase/supabase-js'; + +interface CRMContextType { + // Auth State + user: User | null; + session: Session | null; + authLoading: boolean; + isAdmin: boolean; // New + signIn: (email: string, password: string) => Promise<{ error: any }>; + signOut: () => Promise; + updateUserRole: (userId: string, role: 'admin' | 'user') => Promise; // New action + + // Data State + products: Product[]; + shoppingList: ShoppingListItem[]; + orders: Order[]; + inventory: InventoryItem[]; + suppliers: Supplier[]; + users: User[]; + customers: Customer[]; + transactions: Transaction[]; + searchTerm: string; + loading: boolean; + error: string | null; + searchLoading: boolean; // New separate loading state for search + searchError: string | null; // New separate error state for search + selectedProduct: Product | null; + searchType: 'specific' | 'opportunity'; + setSearchType: (type: 'specific' | 'opportunity') => void; + overheadPercent: number; + exchangeRate: number; + activeOrderId: string | null; + useOverhead: boolean; + setUseOverhead: (use: boolean) => void; + + // Actions + setSearchTerm: (term: string) => void; + setSelectedProduct: (product: Product | null) => void; + handleSearch: (e: React.FormEvent) => Promise; + handleOpportunitySearch: (category: string) => Promise; + addToShoppingList: (product: Product) => void; + removeFromShoppingList: (id: string) => void; + updateShoppingItemQuantity: (id: string, delta: number) => void; + updateOrderStatus: (orderId: string, newStatus: OrderStatus) => void; + saveOrderAsQuotation: () => void; + resumeOrder: (orderId: string) => void; + deleteOrder: (orderId: string) => Promise; + calculateShoppingTotals: () => { + totalParaguayBRL: number; + totalCostWithOverhead: number; + totalUSD: number; + totalApproxProfit: number; + }; + + // New Actions + addTransaction: (tx: Omit) => Promise; + updateTransaction: (id: string, updates: Partial) => Promise; + deleteTransaction: (id: string) => Promise; + addCustomer: (customer: Omit) => Promise; // Updated return type + updateCustomer: (id: string, updates: Partial) => Promise; + deleteCustomer: (id: string) => void; + getFinancialSummary: () => FinancialSummary; + + // Sales Management + sales: Sale[]; + importSales: (channel: SalesChannel) => Promise; + updateSale: (id: string, updates: Partial) => void; + + // Supplier Actions + addSupplier: (supplier: Omit) => void; + updateSupplier: (id: string, updates: Partial) => void; + deleteSupplier: (id: string) => void; + + registerSale: (items: { id: string, quantity: number, salePrice: number }[], customerId?: string, paymentMethod?: PaymentMethod) => Promise; + + // Inventory Management + addProduct: (product: Omit) => Promise; + updateProduct: (id: string, updates: Partial) => Promise; + deleteProduct: (id: string) => Promise; + + // Settings + settings: any; // We will define a stronger type later if needed + updateSettings: (newSettings: any) => Promise; +} + +const CRMContext = createContext(undefined); + +export const CRMProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + // --- AUTH STATE --- + const [user, setUser] = useState(null); + const [session, setSession] = useState(null); + const [authLoading, setAuthLoading] = useState(true); + const [isAdmin, setIsAdmin] = useState(false); // New State + + // --- APP STATE --- + const [searchTerm, setSearchTerm] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [searchLoading, setSearchLoading] = useState(false); + const [searchError, setSearchError] = useState(null); + const [products, setProducts] = useState([]); + const [selectedProduct, setSelectedProduct] = useState(null); + const [searchType, setSearchType] = useState<'specific' | 'opportunity'>('specific'); + const [shoppingList, setShoppingList] = useState([]); + const [activeOrderId, setActiveOrderId] = useState(null); + const [useOverhead, setUseOverhead] = useState(true); + + // SUPABASE DATA + const [orders, setOrders] = useState([]); + const [inventory, setInventory] = useState([]); + const [suppliers, setSuppliers] = useState([]); + const [customers, setCustomers] = useState([]); + const [transactions, setTransactions] = useState([]); + const [sales, setSales] = useState([]); + const [settings, setSettings] = useState({ + defaultOverhead: 20, + defaultExchange: 5.65, + companyName: 'Arbitra System' + }); + + // MOCKED DATA + const [users, setUsers] = useState([]); + + const overheadPercent = settings.defaultOverhead || 20; + const exchangeRate = settings.defaultExchange || 5.65; + + // --- AUTH & INITIAL LOAD --- + useEffect(() => { + const handleAuthSession = async (currentSession: Session | null) => { + setSession(currentSession); + if (currentSession?.user) { + // Fetch Role from profiles table + const { data: profile, error: profileError } = await supabase + .from('profiles') + .select('role') + .eq('id', currentSession.user.id) + .single(); + + if (profileError) { + console.error("Error fetching user profile:", profileError); + } + + const role = profile?.role || 'user'; + setIsAdmin(role === 'admin'); + + setUser({ + id: currentSession.user.id, + email: currentSession.user.email!, + name: currentSession.user.user_metadata?.full_name || currentSession.user.email!, + role: role as 'admin' | 'user', + status: 'Active', // Default or fetch from profile + avatar: currentSession.user.user_metadata?.avatar_url || "https://api.dicebear.com/7.x/avataaars/svg?seed=Felix" + }); + fetchData(); + } else { + setUser(null); + setIsAdmin(false); + // Optional: Clear state on logout + setOrders([]); + setInventory([]); + } + setAuthLoading(false); + }; + + // Check active session on mount + supabase.auth.getSession().then(({ data: { session } }) => { + handleAuthSession(session); + }); + + // Listen for auth changes + const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { + handleAuthSession(session); + }); + + return () => subscription.unsubscribe(); + }, []); + + const signIn = async (email: string, password: string) => { + const { error } = await supabase.auth.signInWithPassword({ email, password }); + return { error }; + }; + + const signOut = async () => { + await supabase.auth.signOut(); + setUser(null); + setIsAdmin(false); + }; + + const updateUserRole = async (userId: string, role: 'admin' | 'user') => { + if (!isAdmin) { + alert("Você não tem permissão para atualizar funções de usuário."); + return; + } + + const { error } = await supabase.from('profiles').update({ role }).eq('id', userId); + if (error) { + console.error("Error updating role:", error); + alert("Erro ao atualizar função do usuário."); + } else { + alert("Função atualizada com sucesso."); + // Update local state immediately + setUsers(prev => prev.map(u => u.id === userId ? { ...u, role } : u)); + } + }; + + // --- SUPABASE FETCH --- + const fetchData = async () => { + setLoading(true); + try { + // Orders + const { data: ordersData } = await supabase.from('orders').select('*').order('created_at', { ascending: false }); + if (ordersData) { + const parsedOrders = ordersData.map(o => ({ + id: o.id, + date: o.date || o.created_at, + items: typeof o.items === 'string' ? JSON.parse(o.items) : (o.items || []), + totalUSD: o.total_usd || 0, + totalBRL: o.total_brl || 0, + totalCostWithOverhead: o.total_cost_with_overhead || 0, + estimatedProfit: o.estimated_profit || 0, + status: o.status, + supplierName: o.supplier_name || 'Desconhecido' + })); + setOrders(parsedOrders || []); + } + + // Inventory + const { data: inventoryData } = await supabase.from('inventory').select('*').order('created_at', { ascending: false }); + if (inventoryData) { + setInventory(inventoryData.map((i: any) => ({ + id: i.id, + name: i.name, + sku: i.sku, + ean: i.ean, + quantity: i.quantity, + avgCostBRL: i.avg_cost_brl || 0, + marketPriceBRL: i.market_price_brl || 0, + lastSupplier: i.last_supplier + }))); + } + + // Users (Admin Only) - Moved logic inside check + if (isAdmin) { + const { data: usersData } = await supabase.from('profiles').select('*').order('created_at', { ascending: false }); + if (usersData) { + setUsers(usersData.map((u: any) => ({ + id: u.id, + name: u.full_name || u.email, // Fallback if full_name is empty + email: u.email, + role: u.role, + status: u.status, + avatar: u.avatar_url || `https://api.dicebear.com/7.x/initials/svg?seed=${u.email}`, + lastAccess: u.last_access + }))); + } + } + + // Settings + const { data: settingsData } = await supabase.from('settings').select('*').single(); + if (settingsData) { + setSettings({ + companyName: settingsData.company_name, + cnpj: settingsData.cnpj, + ie: settingsData.ie, + defaultOverhead: settingsData.default_overhead, + defaultExchange: settingsData.default_exchange, + geminiKey: settingsData.gemini_key, + melhorEnvioToken: settingsData.melhor_envio_token, + blingToken: settingsData.bling_token, + tinyToken: settingsData.tiny_token, + certificatePassword: settingsData.certificate_password, + nfeSerie: settingsData.nfe_serie, + nfeNumber: settingsData.nfe_number, + nfeEnvironment: settingsData.nfe_environment, + smtpHost: settingsData.smtp_host, + smtpPort: settingsData.smtp_port, + smtpUser: settingsData.smtp_user, + smtpPass: settingsData.smtp_pass, + autoSyncSales: settingsData.auto_sync_sales, + autoSyncStock: settingsData.auto_sync_stock, + }); + } + } catch (err) { + console.error("Error fetching data:", err); + } finally { + setLoading(false); + } + }; + + const updateSettings = async (newSettings: any) => { + // Determine if we need to insert or update (assuming single row for settings for now per user or global) + // For simplicity, we'll try to update the first row found, or insert if empty. + + const dbSettings = { + company_name: newSettings.companyName, + cnpj: newSettings.cnpj, + ie: newSettings.ie, + default_overhead: newSettings.defaultOverhead, + default_exchange: newSettings.defaultExchange, + gemini_key: newSettings.geminiKey, + melhor_envio_token: newSettings.melhorEnvioToken, + bling_token: newSettings.blingToken, + tiny_token: newSettings.tinyToken, + certificate_password: newSettings.certificatePassword, + nfe_serie: newSettings.nfeSerie, + nfe_number: newSettings.nfeNumber, + nfe_environment: newSettings.nfeEnvironment, + smtp_host: newSettings.smtpHost, + smtp_port: newSettings.smtpPort, + smtp_user: newSettings.smtpUser, + smtp_pass: newSettings.smtpPass, + auto_sync_sales: newSettings.autoSyncSales, + auto_sync_stock: newSettings.autoSyncStock + }; + + // Check if settings exist + const { data } = await supabase.from('settings').select('id').single(); + + if (data) { + // Update + await supabase.from('settings').update(dbSettings).eq('id', data.id); + } else { + // Insert + await supabase.from('settings').insert([dbSettings]); + } + + setSettings(newSettings); + }; + + // --- ACTIONS --- + const handleSearch = async (e: React.FormEvent) => { + e.preventDefault(); + if (!searchTerm.trim()) return; + setSearchLoading(true); + setSearchError(null); + setSelectedProduct(null); + setProducts([]); // Clear previous results + try { + const { products: result } = await searchProducts(searchTerm); + setProducts(result); + if (result.length === 0) { + setSearchError("Não encontramos nenhum produto com esse nome. Tente termos mais gerais (ex: 'iPhone' em vez de 'iPhone 15 Pro Max 256GB Azul')."); + } + } catch (err: any) { + console.error(err); + if (err.message && err.message.includes("VITE_GOOGLE_API_KEY")) { + setSearchError("Erro de Configuração: API Key do Google não encontrada. Verifique o arquivo .env"); + } else if (err.message && (err.message.includes("fetch") || err.message.includes("network"))) { + setSearchError("Erro de Conexão: Não foi possível conectar ao servidor. Verifique sua internet."); + } else { + setSearchError("Ocorreu um erro ao consultar a inteligência artificial. Por favor, tente novamente em alguns instantes."); + } + } finally { + setSearchLoading(false); + } + }; + + const handleOpportunitySearch = async (category: string) => { + if (!category.trim()) return; + setSearchLoading(true); + setSearchError(null); + setSelectedProduct(null); + setProducts([]); // Clear previous results + try { + const { searchOpportunities } = await import('../services/geminiService'); + const { products: result } = await searchOpportunities(category, useOverhead); + + setProducts(result); + if (result.length === 0) { + setSearchError("Nenhuma oportunidade encontrada com >25% de margem nesta categoria no momento. Tente outra categoria."); + } + } catch (err: any) { + console.error(err); + if (err.message && err.message.includes("VITE_GOOGLE_API_KEY")) { + setSearchError("Erro de Configuração: API Key do Google não encontrada. Verifique o arquivo .env"); + } else if (err.message && (err.message.includes("fetch") || err.message.includes("network"))) { + setSearchError("Erro de Conexão: Não foi possível conectar ao servidor. Verifique sua internet."); + } else { + setSearchError("A IA demorou muito para responder ou encontrou um erro. Tente novamente, geralmente funciona na segunda tentativa."); + } + } finally { + setSearchLoading(false); + } + }; + + const addToShoppingList = (p: Product) => { + setShoppingList(prev => { + const existing = prev.find(item => item.name === p.name && item.store === p.store); + if (existing) return prev.map(item => item.id === existing.id ? { ...item, quantity: item.quantity + 1 } : item); + return [...prev, { + id: crypto.randomUUID(), + name: p.name, + store: p.store, + priceUSD: p.priceUSD, + priceBRL: p.priceBRL, + quantity: 1, + marketPriceBRL: p.marketPriceBRL || (p.priceBRL * 1.6) + }]; + }); + }; + + const removeFromShoppingList = (id: string) => { + setShoppingList(prev => prev.filter(i => i.id !== id)); + }; + + const updateShoppingItemQuantity = (id: string, delta: number) => { + setShoppingList(prev => prev.map(i => i.id === id ? { ...i, quantity: Math.max(1, i.quantity + delta) } : i)); + }; + + const updateOrderStatus = async (orderId: string, newStatus: OrderStatus) => { + const order = orders.find(o => o.id === orderId); + if (!order) return; + + // Database Update + await supabase.from('orders').update({ status: newStatus }).eq('id', orderId); + + // Inventory Logic if Received + if (newStatus === 'Received' && order.status !== 'Received') { + const newInventoryItems: InventoryItem[] = order.items.map(item => ({ + id: crypto.randomUUID(), + name: item.name, + sku: `SKU-${Math.random().toString(36).substr(2, 4).toUpperCase()}`, + quantity: item.quantity, + avgCostBRL: item.priceBRL * (1 + overheadPercent / 100), + marketPriceBRL: item.marketPriceBRL, + lastSupplier: item.store + })); + + await supabase.from('inventory').insert(newInventoryItems.map(i => ({ + id: i.id, name: i.name, sku: i.sku, quantity: i.quantity, + avg_cost_brl: i.avgCostBRL, market_price_brl: i.marketPriceBRL, last_supplier: i.lastSupplier + }))); + + // Refresh Inventory + const { data } = await supabase.from('inventory').select('*').order('created_at', { ascending: false }); + if (data) setInventory(data); + } + + // Local Update + setOrders(prev => prev.map(o => o.id === orderId ? { ...o, status: newStatus } : o)); + }; + + const deleteOrder = async (orderId: string) => { + // Database Delete + const { error } = await supabase.from('orders').delete().eq('id', orderId); + if (error) { + console.error("Error deleting order:", error); + alert("Erro ao excluir pedido."); + } else { + // Local Update + setOrders(prev => prev.filter(o => o.id !== orderId)); + } + }; + + const calculateShoppingTotals = () => { + const totalParaguayBRL = shoppingList.reduce((acc, item) => acc + (item.priceBRL * item.quantity), 0); + const overheadMultiplier = useOverhead ? (1 + overheadPercent / 100) : 1; + const totalCostWithOverhead = totalParaguayBRL * overheadMultiplier; + const totalUSD = shoppingList.reduce((acc, item) => acc + (item.priceUSD * item.quantity), 0); + const totalApproxProfit = shoppingList.reduce((acc, item) => { + const costWithOverhead = item.priceBRL * overheadMultiplier; + const profitPerUnit = item.marketPriceBRL - costWithOverhead; + return acc + (profitPerUnit * item.quantity); + }, 0); + return { totalParaguayBRL, totalCostWithOverhead, totalUSD, totalApproxProfit }; + }; + + const resumeOrder = (orderId: string) => { + const order = orders.find(o => o.id === orderId); + if (!order) return; + setShoppingList(order.items); + setActiveOrderId(order.id); + }; + + const saveOrderAsQuotation = async () => { + if (shoppingList.length === 0) return; + const totals = calculateShoppingTotals(); + + if (activeOrderId) { + // Update + const updatePayload = { + items: shoppingList, // Helper handles jsonb + total_usd: totals.totalUSD, + total_brl: totals.totalParaguayBRL, + total_cost_with_overhead: totals.totalCostWithOverhead, + estimated_profit: totals.totalApproxProfit, + supplier_name: shoppingList[0]?.store || 'Múltiplos' + }; + + await supabase.from('orders').update(updatePayload).eq('id', activeOrderId); + + setOrders(prev => prev.map(o => o.id === activeOrderId ? { + ...o, items: shoppingList, totalUSD: totals.totalUSD, totalBRL: totals.totalParaguayBRL, + totalCostWithOverhead: totals.totalCostWithOverhead, estimatedProfit: totals.totalApproxProfit, + supplierName: updatePayload.supplier_name + } : o)); + setActiveOrderId(null); + + } else { + // Create + const newOrder: Order = { + id: crypto.randomUUID(), + date: new Date().toISOString(), + items: [...shoppingList], + totalUSD: totals.totalUSD, + totalBRL: totals.totalParaguayBRL, + totalCostWithOverhead: totals.totalCostWithOverhead, + estimatedProfit: totals.totalApproxProfit, + status: 'Pending', + supplierName: shoppingList[0]?.store || 'Múltiplos' + }; + + // Map to DB columns + const dbOrder = { + id: newOrder.id, + date: newOrder.date, + status: newOrder.status, + total_usd: newOrder.totalUSD, + total_brl: newOrder.totalBRL, + total_cost_with_overhead: newOrder.totalCostWithOverhead, + estimated_profit: newOrder.estimatedProfit, + items: newOrder.items, + supplier_name: newOrder.supplierName + }; + + const { error } = await supabase.from('orders').insert([dbOrder]); + + if (error) { + console.error("Error saving order:", error); + alert(`Erro ao salvar pedido: ${error.message} (${error.details || ''})`); + return; + } + + setOrders([newOrder, ...orders]); + alert("Pedido salvo com sucesso!"); + } + setShoppingList([]); + }; + + // --- FINANCIAL ACTIONS --- + const addTransaction = async (tx: Omit) => { + const id = crypto.randomUUID(); + const newTx = { + ...tx, + id, + user_id: user?.id, + status: tx.status || 'Pending', + payment_method: tx.paymentMethod || 'Cash', + due_date: tx.dueDate || new Date().toISOString() + }; + // Map camelCase to snake_case for DB if needed, but Supabase JS client handles it if keys match DB columns + // or if we map explicitly. Let's map explicitly to be safe. + const dbTx = { + id: newTx.id, + date: newTx.date, + type: newTx.type, + category: newTx.category, + amount: newTx.amount, + description: newTx.description, + status: newTx.status, + payment_method: newTx.paymentMethod, // DB column: payment_method + due_date: newTx.dueDate, // DB column: due_date + user_id: newTx.user_id + }; + + const { error } = await supabase.from('transactions').insert([dbTx]); + if (error) console.error("Error adding transaction:", error); + + // We update local state with the camelCase version for UI + setTransactions(prev => [{ ...tx, id: newTx.id, status: newTx.status, paymentMethod: newTx.paymentMethod, dueDate: newTx.dueDate }, ...prev]); + }; + + const updateTransaction = async (id: string, updates: Partial) => { + // Map updates + const dbUpdates: any = { ...updates }; + if (updates.paymentMethod) dbUpdates.payment_method = updates.paymentMethod; + if (updates.dueDate) dbUpdates.due_date = updates.dueDate; + + const { error } = await supabase.from('transactions').update(dbUpdates).eq('id', id); + if (error) { + console.error("Error updating transaction:", error); + alert("Erro ao atualizar transação"); + } else { + setTransactions(prev => prev.map(t => t.id === id ? { ...t, ...updates } : t)); + } + }; + + const deleteTransaction = async (id: string) => { + await supabase.from('transactions').delete().eq('id', id); + setTransactions(prev => prev.filter(t => t.id !== id)); + }; + + const addCustomer = async (customer: Omit): Promise => { + const id = crypto.randomUUID(); + const newCustomer = { ...customer, id, totalPurchased: 0 }; + + const dbCustomer = { + id: newCustomer.id, + name: newCustomer.name, + email: newCustomer.email, + phone: newCustomer.phone, + city: newCustomer.city, + status: newCustomer.status, + total_purchased: 0, + user_id: user?.id + }; + + const { data, error } = await supabase.from('customers').insert([dbCustomer]).select(); + + if (error) { + console.error("Error adding customer:", error); + return null; + } + + setCustomers(prev => [...prev, newCustomer]); + return newCustomer; // Return the object + }; + + const updateCustomer = async (id: string, updates: Partial) => { + // Map updates to snake_case if necessary, or pass mostly as is if Keys match + const dbUpdates: any = { ...updates }; + if (updates.totalPurchased) dbUpdates.total_purchased = updates.totalPurchased; + + await supabase.from('customers').update(dbUpdates).eq('id', id); + setCustomers(prev => prev.map(c => c.id === id ? { ...c, ...updates } : c)); + }; + + const deleteCustomer = async (id: string) => { + await supabase.from('customers').delete().eq('id', id); + setCustomers(prev => prev.filter(c => c.id !== id)); + }; + + const getFinancialSummary = () => { + const totalIncome = transactions.filter(t => t.type === 'Income').reduce((acc, t) => acc + t.amount, 0); + const totalExpense = transactions.filter(t => t.type === 'Expense').reduce((acc, t) => acc + t.amount, 0); + return { + totalIncome, + totalExpense, + balance: totalIncome - totalExpense, + recentTransactions: transactions.slice(0, 5) + }; + }; + + // --- SUPPLIER ACTIONS --- + const addSupplier = async (supplier: Omit) => { + const id = crypto.randomUUID(); + const newSupplier = { ...supplier, id, user_id: user?.id }; + await supabase.from('suppliers').insert([newSupplier]); + setSuppliers(prev => [...prev, newSupplier]); + }; + + const updateSupplier = async (id: string, updates: Partial) => { + await supabase.from('suppliers').update(updates).eq('id', id); + setSuppliers(prev => prev.map(s => s.id === id ? { ...s, ...updates } : s)); + }; + + const deleteSupplier = async (id: string) => { + await supabase.from('suppliers').delete().eq('id', id); + setSuppliers(prev => prev.filter(s => s.id !== id)); + }; + + // PRODUCT ACTIONS + const addProduct = async (product: Omit) => { + if (!user) { + alert("Erro: Usuário não autenticado. O sistema perdeu a conexão ou você não está logado."); + return; + } + console.log("Attempting to add product:", product); + const id = crypto.randomUUID(); + const newProduct = { ...product, id }; + + // In a real app, 'products' and 'inventory' might be separate tables. + // Here we treat 'inventory' as the master product list for simplicity as per current structure. + const { data, error } = await supabase.from('inventory').insert([{ + id: newProduct.id, + name: newProduct.name, + sku: newProduct.sku, + ean: newProduct.ean, + quantity: newProduct.quantity, + avg_cost_brl: newProduct.avgCostBRL, + market_price_brl: newProduct.marketPriceBRL, + last_supplier: newProduct.lastSupplier, + user_id: user?.id + }]).select(); + + if (error) { + console.error("Supabase Error adding product:", error); + throw error; + } + + console.log("Product added successfully, DB response:", data); + + setInventory(prev => [newProduct, ...prev]); + }; + + const updateProduct = async (id: string, updates: Partial) => { + // Map to snake_case for DB + const dbUpdates: any = { ...updates }; + if (updates.avgCostBRL) dbUpdates.avg_cost_brl = updates.avgCostBRL; + if (updates.marketPriceBRL) dbUpdates.market_price_brl = updates.marketPriceBRL; + if (updates.lastSupplier) dbUpdates.last_supplier = updates.lastSupplier; + + await supabase.from('inventory').update(dbUpdates).eq('id', id); + setInventory(prev => prev.map(p => p.id === id ? { ...p, ...updates } : p)); + }; + + const deleteProduct = async (id: string) => { + await supabase.from('inventory').delete().eq('id', id); + setInventory(prev => prev.filter(p => p.id !== id)); + }; + + // SALES LOGIC & MANAGEMENT + const importSales = async (channel: SalesChannel) => { + setLoading(true); + // MOCK DELAY & DATA + await new Promise(r => setTimeout(r, 1500)); + + const newSales: Sale[] = Array.from({ length: Math.floor(Math.random() * 3) + 2 }).map((_, i) => ({ + id: crypto.randomUUID(), + date: new Date().toISOString(), + customerName: `Cliente ${channel} ${Math.floor(Math.random() * 1000)}`, + items: [ + { id: 'mock-item', name: 'Produto Importado ' + (i + 1), quantity: 1, salePrice: Math.random() * 200 + 50 } + ], + total: 0, // Calculated below + status: 'Pending' as const, + channel: channel, + externalId: `MLB-${Math.floor(Math.random() * 1000000)}`, + isStockLaunched: false, + isFinancialLaunched: false + })).map(s => ({ ...s, total: s.items.reduce((acc, i) => acc + (i.quantity * i.salePrice), 0) })); + + setSales(prev => [...newSales, ...prev]); // Add to top + setLoading(false); + }; + + const updateSale = (id: string, updates: Partial) => { + setSales(prev => prev.map(s => { + if (s.id !== id) return s; + const updatedSale = { ...s, ...updates }; + + // Sincronização Automática (Mock logic linking checkboxes to actual actions) + if (updates.isStockLaunched && !s.isStockLaunched) { + // Trigger stock deduction logic theoretically + console.log("Stock launched for sale", id); + } + if (updates.isFinancialLaunched && !s.isFinancialLaunched) { + addTransaction({ + date: new Date().toISOString(), + type: 'Income', + category: 'Venda Marketplace', + amount: updatedSale.total, + description: `Venda ${updatedSale.channel} #${updatedSale.externalId || id}`, + status: 'Paid', + paymentMethod: 'Other' + }); + } + + return updatedSale; + })); + }; + + const registerSale = async (items: { id: string, quantity: number, salePrice: number }[], customerId?: string, paymentMethod: PaymentMethod = 'Cash') => { + // 1. Deduct from Inventory + const updatedInventory = inventory.map(invItem => { + const soldItem = items.find(i => i.id === invItem.id); + if (soldItem) { + return { ...invItem, quantity: Math.max(0, invItem.quantity - soldItem.quantity) }; + } + return invItem; + }); + setInventory(updatedInventory); + + // 2. Add Transaction (Income) + const totalAmount = items.reduce((acc, item) => acc + (item.quantity * item.salePrice), 0); + const cust = customers.find(c => c.id === customerId); + const transaction: Omit = { + date: new Date().toISOString(), + type: 'Income', + category: 'Venda de Produtos', + amount: totalAmount, + description: `Venda balcão - Cliente: ${cust?.name || 'Não Id.'}`, + status: 'Paid', + paymentMethod: paymentMethod, // Now correctly using the argument + dueDate: new Date().toISOString() + }; + + await addTransaction(transaction); // This is good, but Sales.tsx is calling registerSale which handles logic. + // ACTUALLY: registerSale needs to accept payment method or we need to update the transaction logic inside it. + // Modifying registerSale signature to accept PaymentMethod would be best practice. + + // 3. Register as a Sale Record + const newSale: Sale = { + id: crypto.randomUUID(), + date: new Date().toISOString(), + customerId: customerId, + customerName: cust ? cust.name : 'Cliente Balcão', + items: items.map(i => { + const inv = inventory.find(inv => inv.id === i.id); + return { + id: i.id, + name: inv?.name || 'Item', + quantity: i.quantity, + salePrice: i.salePrice, + costPrice: inv?.avgCostBRL || 0 // Capture cost at moment of sale + }; + }), + total: totalAmount, + status: 'Completed', + channel: 'Local', + isStockLaunched: true, // Auto-launched for POS + isFinancialLaunched: true // Auto-launched for POS + }; + setSales(prev => [newSale, ...prev]); + + // 4. Update Customer (if exists) - Mocked for now + if (customerId) { + console.log(`Updated customer ${customerId} total purchased`); + } + }; + + const value = { + // Auth + user, session, authLoading, signIn, signOut, isAdmin, updateUserRole, + // Data + products, shoppingList, orders, inventory, suppliers, users, searchTerm, loading, error, selectedProduct, overheadPercent, exchangeRate, activeOrderId, + searchLoading, searchError, searchType, setSearchType, + customers, transactions, + setSearchTerm, setSelectedProduct, handleSearch, handleOpportunitySearch, addToShoppingList, removeFromShoppingList, updateShoppingItemQuantity, updateOrderStatus, saveOrderAsQuotation, calculateShoppingTotals, resumeOrder, deleteOrder, + addTransaction, deleteTransaction, updateTransaction, addCustomer, updateCustomer, deleteCustomer, getFinancialSummary, + addSupplier, updateSupplier, deleteSupplier, registerSale, + addProduct, updateProduct, deleteProduct, settings, updateSettings, + sales, importSales, updateSale, + useOverhead, setUseOverhead + }; + + return {children}; +}; + +export const useCRM = () => { + const context = useContext(CRMContext); + if (!context) throw new Error('useCRM must be used within a CRMProvider'); + return context; +}; diff --git a/context/ThemeContext.tsx b/context/ThemeContext.tsx new file mode 100644 index 0000000..943009d --- /dev/null +++ b/context/ThemeContext.tsx @@ -0,0 +1,49 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; + +type Theme = 'dark' | 'light' | 'ocean'; + +interface ThemeContextType { + theme: Theme; + setTheme: (theme: Theme) => void; +} + +const ThemeContext = createContext(undefined); + +export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [theme, setTheme] = useState(() => { + // Check local storage or system preference + const saved = localStorage.getItem('theme'); + if (saved) return saved as Theme; + return 'dark'; // Default + }); + + useEffect(() => { + const root = window.document.documentElement; + + // Remove old classes + root.classList.remove('theme-light', 'theme-ocean', 'dark'); + + // Add new class + if (theme === 'dark') { + root.classList.add('dark'); + } else { + root.classList.add(`theme-${theme}`); + } + + localStorage.setItem('theme', theme); + }, [theme]); + + return ( + + {children} + + ); +}; + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (context === undefined) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +}; diff --git a/index.css b/index.css new file mode 100644 index 0000000..80559da --- /dev/null +++ b/index.css @@ -0,0 +1,164 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + + /* Dark Theme (Default) */ + :root { + --background: 240 10% 4%; + /* #0a0a0c - Deeper black/zinc */ + --foreground: 0 0% 98%; + + --card: 240 10% 6%; + /* Slightly lighter than bg */ + --card-foreground: 0 0% 98%; + + --popover: 240 10% 6%; + --popover-foreground: 0 0% 98%; + + --primary: 263 70% 50%; + /* Deep Purple/Indigo */ + --primary-foreground: 210 40% 98%; + + --secondary: 240 4% 16%; + --secondary-foreground: 0 0% 98%; + + --muted: 240 4% 16%; + --muted-foreground: 240 5% 65%; + + --accent: 240 4% 16%; + --accent-foreground: 0 0% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + + --border: 240 4% 16%; + --input: 240 4% 16%; + --ring: 240 4.9% 83.9%; + --radius: 1rem; + } + + /* Light Theme (Simple/Clean) */ + .theme-light { + --background: 210 40% 98%; + /* Soft blue-white */ + --foreground: 222 47% 11%; + /* Dark Navy Text */ + + --card: 0 0% 100%; + /* Pure White */ + --card-foreground: 222 47% 11%; + + --popover: 0 0% 100%; + --popover-foreground: 222 47% 11%; + + --primary: 221 83% 53%; + /* Classic Blue */ + --primary-foreground: 210 40% 98%; + + --secondary: 210 40% 96.1%; + --secondary-foreground: 222 47% 11%; + + --muted: 210 40% 96.1%; + --muted-foreground: 215 16% 47%; + + --accent: 210 40% 96.1%; + --accent-foreground: 222 47% 11%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + + --border: 214 32% 91%; + --input: 214 32% 91%; + --ring: 221 83% 53%; + } + + /* Ocean Theme (Deep Blue) */ + .theme-ocean { + --background: 222 47% 11%; + /* Navy BG */ + --foreground: 210 40% 98%; + + --card: 217 33% 17%; + /* Lighter Navy Card */ + --card-foreground: 210 40% 98%; + + --popover: 217 33% 17%; + --popover-foreground: 210 40% 98%; + + --primary: 199 89% 48%; + /* Cyan/Blue */ + --primary-foreground: 210 40% 98%; + + --secondary: 217 19% 27%; + --secondary-foreground: 210 40% 98%; + + --muted: 217 19% 27%; + --muted-foreground: 215 20% 65%; + + --accent: 217 19% 27%; + --accent-foreground: 210 40% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + + --border: 217 19% 27%; + --input: 217 19% 27%; + --ring: 199 89% 48%; + } +} + +@layer base { + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground; + } +} + +/* Custom Scrollbar for Premium Feel */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: hsl(var(--border)); + border-radius: 99px; +} + +::-webkit-scrollbar-thumb:hover { + background: hsl(var(--muted-foreground)); +} + +/* Glassmorphism Utilities */ +.glass-panel { + background: hsl(var(--background) / 0.6); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid hsl(var(--border) / 0.5); +} + +.glass-card { + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); +} + +/* Light Theme Overrides for Glass */ +.theme-light .glass-card { + background: white; + box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.05); + border: 1px solid #e2e8f0; +} + +.theme-light .glass-panel { + background: rgba(255, 255, 255, 0.8); + border: 1px solid #e2e8f0; +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..094cbfe --- /dev/null +++ b/index.html @@ -0,0 +1,83 @@ + + + + + + + Arbitra System + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/index.tsx b/index.tsx new file mode 100644 index 0000000..aaa0c6e --- /dev/null +++ b/index.tsx @@ -0,0 +1,16 @@ + +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +const rootElement = document.getElementById('root'); +if (!rootElement) { + throw new Error("Could not find root element to mount to"); +} + +const root = ReactDOM.createRoot(rootElement); +root.render( + + + +); diff --git a/layouts/Header.tsx b/layouts/Header.tsx new file mode 100644 index 0000000..adfe7ae --- /dev/null +++ b/layouts/Header.tsx @@ -0,0 +1,105 @@ + +import React from 'react'; +import { TrendingUp, Bell, Search, Command, Menu } from 'lucide-react'; +import { useCRM } from '../context/CRMContext'; +import { useLocation } from 'react-router-dom'; +import clsx from 'clsx'; + +interface HeaderProps { + onMenuClick?: () => void; +} + +const Header: React.FC = ({ onMenuClick }) => { + const { exchangeRate, orders } = useCRM(); + const location = useLocation(); + + // Mapping English paths to proper titles if needed, or keeping English for 'Premium' feel + const getTitle = () => { + const path = location.pathname.substring(1); + if (!path) return 'Dashboard'; + + const titles: Record = { + 'sales': 'Vendas', + 'sourcing': 'Sourcing', // or Arbitragem + 'products': 'Produtos', + 'orders': 'Pedidos', + 'financial': 'Financeiro', + 'customers': 'Clientes', + 'inventory': 'Estoque', + 'suppliers': 'Fornecedores', + 'reports': 'Relatórios', + 'users': 'Usuários', + 'settings': 'Configurações' + }; + + return titles[path] || path.charAt(0).toUpperCase() + path.slice(1); + }; + + const totalProfit = orders + .filter(o => o.status === 'Received') + .reduce((acc, o) => acc + o.estimatedProfit, 0); + + return ( +
+ {/* Left: Breadcrumb/Title */} +
+ +
+
+ App + / + {getTitle()} +
+

{getTitle()}

+
+
+ + {/* Right: Actions */} +
+ + {/* Search Bar (Visual Only) */} +
+ + Buscar... +
+
+ K +
+
+ +
+ + {/* Stats */} +
+
+ Dólar Hoje + R$ {exchangeRate.toFixed(2)} +
+ +
+
+ Lucro Total + 0 ? "text-emerald-400 bg-emerald-400/10" : "text-foreground" + )}> + R$ {totalProfit.toLocaleString('pt-BR')} + +
+
+
+ + +
+
+ ); +}; + +export default Header; diff --git a/layouts/Sidebar.tsx b/layouts/Sidebar.tsx new file mode 100644 index 0000000..0633547 --- /dev/null +++ b/layouts/Sidebar.tsx @@ -0,0 +1,109 @@ +import React from 'react'; +import { NavLink } from 'react-router-dom'; +import { LayoutDashboard, Search, History, Boxes, Store, Users, TrendingUp, LogOut, Wallet, ShoppingCart, Package, BarChart3, Settings } from 'lucide-react'; +import { useCRM } from '../context/CRMContext'; +import clsx from 'clsx'; + +interface SidebarProps { + isOpen?: boolean; + onClose?: () => void; +} + +const Sidebar: React.FC = ({ isOpen = false, onClose }) => { + const { user, isAdmin, signOut } = useCRM(); + const currentUser = user; + + const navItems = [ + { to: '/', icon: LayoutDashboard, label: 'Dashboard' }, + { to: '/sales', icon: ShoppingCart, label: 'Vendas' }, + { to: '/sourcing', icon: Search, label: 'Sourcing' }, + { to: '/products', icon: Package, label: 'Produtos' }, + { to: '/orders', icon: History, label: 'Pedidos' }, + { to: '/financial', icon: Wallet, label: 'Financeiro' }, + { to: '/customers', icon: Users, label: 'Clientes' }, + { to: '/inventory', icon: Boxes, label: 'Estoque' }, + { to: '/suppliers', icon: Store, label: 'Fornecedores' }, + { to: '/reports', icon: BarChart3, label: 'Relatórios' }, + { to: '/users', icon: Users, label: 'Usuários' }, + { to: '/settings', icon: Settings, label: 'Configurações' } + ]; + + return ( + <> + {/* Mobile Overlay */} + {isOpen && ( +
+ )} + + + + ); +}; + +export default Sidebar; diff --git a/layouts/TopBar.tsx b/layouts/TopBar.tsx new file mode 100644 index 0000000..56b2000 --- /dev/null +++ b/layouts/TopBar.tsx @@ -0,0 +1,175 @@ +import React, { useState } from 'react'; +import { NavLink, useNavigate } from 'react-router-dom'; +import { + LayoutDashboard, Search, History, Boxes, Store, Users, TrendingUp, LogOut, Wallet, + ChevronDown, Package, FileText, Factory, Menu, ShoppingCart, Settings, BarChart3 +} from 'lucide-react'; +import { useCRM } from '../context/CRMContext'; +import clsx from 'clsx'; +import Logo from '../components/Logo'; + +const TopBar: React.FC = () => { + const { users, signOut } = useCRM(); + const currentUser = users[0]; + const navigate = useNavigate(); + + // Dropdown States + const [openMenu, setOpenMenu] = useState(null); + + const handleSignOut = async () => { + await signOut(); + navigate('/login'); + }; + + const navStructure = [ + { + label: 'Dashboard', + to: '/', + icon: LayoutDashboard, + type: 'link' + }, + { + label: 'Vendas', + to: '/sales', + icon: ShoppingCart, + type: 'link' + }, + { + label: 'Arbitragem', + icon: Search, + type: 'dropdown', + items: [ + { label: 'Sourcing Intel', to: '/sourcing', icon: Search }, + { label: 'Minhas Ordens', to: '/orders', icon: History }, + ] + }, + { + label: 'Cadastros', + icon: FileText, + type: 'dropdown', + items: [ + { label: 'Clientes', to: '/customers', icon: Users }, + { label: 'Fornecedores', to: '/suppliers', icon: Store }, + { label: 'Produtos', to: '/products', icon: Package }, + { label: 'Estoque', to: '/inventory', icon: Boxes }, + { label: 'Usuários', to: '/users', icon: Users }, + ] + }, + { + label: 'Produção', + icon: Factory, + type: 'nav-link', // Future placeholder + to: '#', // Placeholder + items: [], // Empty for now or future items + disabled: true + }, + { + label: 'Relatórios', + to: '/reports', + icon: BarChart3, + type: 'link' + }, + { + label: 'Financeiro', + to: '/financial', + icon: Wallet, + type: 'link' + } + ]; + + return ( +
+ + {/* LOGO */} +
navigate('/')} className="cursor-pointer"> + +
+ + {/* NAV MENU CASCATA */} + + + {/* USER ACTIONS */} +
+ + +
+
+

{currentUser?.name || 'Admin'}

+

Online

+
+
+ Avatar +
+
+
+
+ ); +}; + +export default TopBar; diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..7532ee1 --- /dev/null +++ b/metadata.json @@ -0,0 +1,5 @@ +{ + "name": "Paraguay Arbitrage CRM", + "description": "Professional ERP/CRM solution for Paraguay arbitrage. Features real-time sourcing via Gemini, order tracking, profit history (Facebook-based), and supplier management.", + "requestFramePermissions": [] +} \ No newline at end of file diff --git a/migration.sql b/migration.sql new file mode 100644 index 0000000..27906f7 --- /dev/null +++ b/migration.sql @@ -0,0 +1,45 @@ +-- MIGRATION SCRIPT +-- Run this to update your existing tables with the new columns + +-- 1. Add 'ean' to inventory (This was the error you saw) +alter table public.inventory add column if not exists ean text; + +-- 2. Add 'user_id' to all key tables (For the authentication fix I made) +alter table public.inventory add column if not exists user_id uuid references auth.users; +alter table public.suppliers add column if not exists user_id uuid references auth.users; +alter table public.customers add column if not exists user_id uuid references auth.users; +alter table public.orders add column if not exists user_id uuid references auth.users; +alter table public.transactions add column if not exists user_id uuid references auth.users; + +-- 3. Ensure Settings table exists (in case it wasn't created yet) +create table if not exists public.settings ( + id uuid default uuid_generate_v4() primary key, + created_at timestamp with time zone default timezone('utc'::text, now()) not null, + updated_at timestamp with time zone default timezone('utc'::text, now()), + company_name text, + cnpj text, + ie text, + default_overhead numeric default 20, + default_exchange numeric default 5.65, + brazil_api_token text, + melhor_envio_token text, + bling_token text, + tiny_token text, + gemini_key text, + certificate_password text, + nfe_serie text default '1', + nfe_number text, + nfe_environment text default 'homologacao', + smtp_host text, + smtp_port text, + smtp_user text, + smtp_pass text, + auto_sync_sales boolean default true, + auto_sync_stock boolean default true, + user_id uuid references auth.users +); + +-- 4. Re-apply RLS policies just in case +alter table public.inventory enable row level security; +drop policy if exists "Enable all for authenticated users" on public.inventory; +create policy "Enable all for authenticated users" on public.inventory for all to authenticated using (true) with check (true); diff --git a/n8n-scraping-workflow.json b/n8n-scraping-workflow.json new file mode 100644 index 0000000..76b0c5b --- /dev/null +++ b/n8n-scraping-workflow.json @@ -0,0 +1,125 @@ +{ + "name": "Scrape Compras Paraguai", + "nodes": [ + { + "parameters": { + "path": "search", + "responseMode": "lastNode", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + 0, + 0 + ], + "id": "webhook-trigger", + "name": "Webhook" + }, + { + "parameters": { + "url": "=https://www.comprasparaguai.com.br/busca/?q={{ $json.query.query.replace(/ /g, '+') }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 220, + 0 + ], + "id": "http-request", + "name": "Fetch HTML" + }, + { + "parameters": { + "mode": "html", + "dataPropertyName": "data", + "selectors": [ + { + "name": "products", + "selector": ".product-item", + "returnArray": true, + "properties": [ + { + "name": "name", + "selector": ".product-title", + "value": "text" + }, + { + "name": "priceUSD", + "selector": ".price-dolar", + "value": "text" + }, + { + "name": "store", + "selector": ".store-name", + "value": "text" + }, + { + "name": "link", + "selector": "a", + "attribute": "href" + } + ] + } + ] + }, + "type": "n8n-nodes-base.htmlExtract", + "typeVersion": 1, + "position": [ + 440, + 0 + ], + "id": "html-extract", + "name": "Extract Data" + }, + { + "parameters": { + "jsCode": "const products = $input.all()[0].json.products || [];\n\nconst cleanProducts = products.map(p => ({\n name: p.name.trim(),\n priceUSD: parseFloat(p.priceUSD.replace('US$ ', '').replace(',', '.').trim()),\n priceBRL: parseFloat(p.priceUSD.replace('US$ ', '').replace(',', '.').trim()) * 5.75,\n store: p.store.trim(),\n url: 'https://www.comprasparaguai.com.br' + p.link\n})).filter(p => !isNaN(p.priceUSD));\n\nreturn cleanProducts;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 660, + 0 + ], + "id": "cleanup-code", + "name": "Clean Data" + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Fetch HTML", + "type": "main", + "index": 0 + } + ] + ] + }, + "Fetch HTML": { + "main": [ + [ + { + "node": "Extract Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract Data": { + "main": [ + [ + { + "node": "Clean Data", + "type": "main", + "index": 0 + } + ] + ] + } + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6c7025c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3255 @@ +{ + "name": "paraguay-arbitrage-crm", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "paraguay-arbitrage-crm", + "version": "0.0.0", + "dependencies": { + "@google/genai": "^1.35.0", + "@supabase/supabase-js": "^2.90.1", + "clsx": "^2.1.1", + "framer-motion": "^12.26.1", + "lucide-react": "^0.562.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-router-dom": "^7.12.0", + "recharts": "^3.6.0", + "tailwind-merge": "^3.4.0" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.35.0.tgz", + "integrity": "sha512-ZC1d0PSM5eS73BpbVIgL3ZsmXeMKLVJurxzww1Z9axy3B2eUB3ioEytbQt4Qu0Od6qPluKrTDew9pSi9kEuPaw==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.24.0" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz", + "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@supabase/auth-js": { + "version": "2.90.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.90.1.tgz", + "integrity": "sha512-vxb66dgo6h3yyPbR06735Ps+dK3hj0JwS8w9fdQPVZQmocSTlKUW5MfxSy99mN0XqCCuLMQ3jCEiIIUU23e9ng==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.90.1", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.90.1.tgz", + "integrity": "sha512-x9mV9dF1Lam9qL3zlpP6mSM5C9iqMPtF5B/tU1Jj/F0ufX5mjDf9ghVBaErVxmrQJRL4+iMKWKY2GnODkpS8tw==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.90.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.90.1.tgz", + "integrity": "sha512-jh6vqzaYzoFn3raaC0hcFt9h+Bt+uxNRBSdc7PfToQeRGk7PDPoweHsbdiPWREtDVTGKfu+PyPW9e2jbK+BCgQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.90.1", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.90.1.tgz", + "integrity": "sha512-PWbnEMkcQRuor8jhObp4+Snufkq8C6fBp+MchVp2qBPY1NXk/c3Iv3YyiFYVzo0Dzuw4nAlT4+ahuPggy4r32w==", + "license": "MIT", + "dependencies": { + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.90.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.90.1.tgz", + "integrity": "sha512-GHY+Ps/K/RBfRj7kwx+iVf2HIdqOS43rM2iDOIDpapyUnGA9CCBFzFV/XvfzznGykd//z2dkGZhlZZprsVFqGg==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.90.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.90.1.tgz", + "integrity": "sha512-U8KaKGLUgTIFHtwEW1dgw1gK7XrdpvvYo7nzzqPx721GqPe8WZbAiLh/hmyKLGBYQ/mmQNr20vU9tWSDZpii3w==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.90.1", + "@supabase/functions-js": "2.90.1", + "@supabase/postgrest-js": "2.90.1", + "@supabase/realtime-js": "2.90.1", + "@supabase/storage-js": "2.90.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.5.tgz", + "integrity": "sha512-HfF8+mYcHPcPypui3w3mvzuIErlNOh2OAG+BCeBZCEwyiD5ls2SiCwEyT47OELtf7M3nHxBdu0FsmzdKxkN52Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", + "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/es-toolkit": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.43.0.tgz", + "integrity": "sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/framer-motion": { + "version": "12.26.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.26.1.tgz", + "integrity": "sha512-Uzc8wGldU4FpmGotthjjcj0SZhigcODjqvKT7lzVZHsmYkzQMFfMIv0vHQoXCeoe/Ahxqp4by4A6QbzFA/lblw==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.24.11", + "motion-utils": "^12.24.10", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.562.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz", + "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/motion-dom": { + "version": "12.24.11", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.24.11.tgz", + "integrity": "sha512-DlWOmsXMJrV8lzZyd+LKjG2CXULUs++bkq8GZ2Sr0R0RRhs30K2wtY+LKiTjhmJU3W61HK+rB0GLz6XmPvTA1A==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.24.10" + } + }, + "node_modules/motion-utils": { + "version": "12.24.10", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.24.10.tgz", + "integrity": "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-is": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz", + "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==", + "license": "MIT", + "peer": true + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz", + "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz", + "integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==", + "license": "MIT", + "dependencies": { + "react-router": "7.12.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/recharts": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.6.0.tgz", + "integrity": "sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b3c9a9b --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "paraguay-arbitrage-crm", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@google/genai": "^1.35.0", + "@supabase/supabase-js": "^2.90.1", + "clsx": "^2.1.1", + "framer-motion": "^12.26.1", + "lucide-react": "^0.562.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-router-dom": "^7.12.0", + "recharts": "^3.6.0", + "tailwind-merge": "^3.4.0" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } +} diff --git a/pages/Customers.tsx b/pages/Customers.tsx new file mode 100644 index 0000000..a7fa2ac --- /dev/null +++ b/pages/Customers.tsx @@ -0,0 +1,167 @@ + +import React, { useState } from 'react'; +import { Users, Plus, Edit2, Trash2, Mail, Phone, MapPin } from 'lucide-react'; +import { useCRM } from '../context/CRMContext'; +import { Customer } from '../types'; + +const Customers: React.FC = () => { + const { customers, addCustomer, updateCustomer, deleteCustomer } = useCRM(); + const [isModalOpen, setIsModalOpen] = useState(false); + const [editingId, setEditingId] = useState(null); + const [formData, setFormData] = useState>({ + name: '', email: '', phone: '', city: '', status: 'Active' + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (editingId) { + updateCustomer(editingId, formData); + } else { + addCustomer(formData as any); + } + closeModal(); + }; + + const openModal = (customer?: Customer) => { + if (customer) { + setEditingId(customer.id); + setFormData(customer); + } else { + setEditingId(null); + setFormData({ name: '', email: '', phone: '', city: '', status: 'Active' }); + } + setIsModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + setEditingId(null); + }; + + return ( +
+ {/* HEADER */} +
+
+

Carteira de Clientes

+

Gestão de Relacionamento (CRM)

+
+ +
+ + {/* LIST */} +
+ + + + + + + + + + + + {customers.map(c => ( + + + + + + + + ))} + +
ClienteStatusContatoLTV (Total Pago)Ações
+
+
+ {c.name.charAt(0)} +
+
+

{c.name}

+

{c.city || 'Localização n/a'}

+
+
+
+ + {c.status === 'Active' ? 'ATIVO' : c.status === 'Prospect' ? 'PROSPECT' : 'INATIVO'} + + +
+ {c.email && ( +
+ {c.email} +
+ )} + {c.phone && ( +
+ {c.phone} +
+ )} +
+
+

R$ {c.totalPurchased?.toLocaleString('pt-BR') || '0,00'}

+
+
+ + +
+
+ {customers.length === 0 && ( +
NO CUSTOMERS FOUND.
+ )} +
+ + {/* MODAL */} + {isModalOpen && ( +
+
+

{editingId ? 'Editar Cliente' : 'Novo Cliente'}

+
+
+ + setFormData({ ...formData, name: e.target.value })} className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:border-indigo-500 outline-none transition-all" /> +
+
+
+ + setFormData({ ...formData, phone: e.target.value })} className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:border-indigo-500 outline-none transition-all" /> +
+
+ + +
+
+
+ + setFormData({ ...formData, email: e.target.value })} className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:border-indigo-500 outline-none transition-all" /> +
+
+ + setFormData({ ...formData, city: e.target.value })} className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:border-indigo-500 outline-none transition-all" /> +
+
+ + +
+
+
+
+ )} +
+ ); +}; + +export default Customers; diff --git a/pages/Dashboard.tsx b/pages/Dashboard.tsx new file mode 100644 index 0000000..4732ef3 --- /dev/null +++ b/pages/Dashboard.tsx @@ -0,0 +1,164 @@ + +import React from 'react'; +import { useCRM } from '../context/CRMContext'; +import { useNavigate } from 'react-router-dom'; +import clsx from 'clsx'; +import { TrendingUp, TrendingDown, DollarSign, Activity, ArrowRight, Package } from 'lucide-react'; + +const Dashboard: React.FC = () => { + const { orders, inventory, suppliers } = useCRM(); + const navigate = useNavigate(); + + // Financial Calculation + const activeOrders = orders.filter(o => o.status === 'Pending' || o.status === 'Paid'); + const totalInvested = activeOrders.reduce((acc, o) => acc + o.totalCostWithOverhead, 0); + // Mocking a growth metric for demonstration + const totalProfitHistory = orders.filter(o => o.status === 'Received').reduce((acc, o) => acc + o.estimatedProfit, 0); + const inventoryValue = inventory.reduce((acc, i) => acc + ((Number(i.marketPriceBRL) || 0) * (Number(i.quantity) || 0)), 0); + + // "Am I winning?" - Net Result (Mocked relative to investment for delta) + const netResultDelta = totalInvested > 0 ? (totalProfitHistory / totalInvested) * 100 : 0; + const isWinning = netResultDelta >= 0; + + return ( +
+ + {/* HERDER: "Am I winning?" Context */} +
+
+

Visão Geral

+

Performance financeira e operacional.

+
+
+

Resultado Líquido (YTD)

+
+ {isWinning ? : } + R$ {totalProfitHistory.toLocaleString('pt-BR')} +
+
+
+ + {/* HIGH PRECISION METRICS GRID */} +
+ {/* Metric 1: Capital Allocation */} +
+
+

Capital Alocado

+ +
+

R$ {totalInvested.toLocaleString('pt-BR')}

+
+ +12% vs. mês ant. +
+
+ + {/* Metric 2: Stock Value (Liquidity) */} +
+
+

Valor em Estoque

+ +
+

R$ {inventoryValue.toLocaleString('pt-BR')}

+
+ Giro: 15 dias +
+
+ + {/* Metric 3: Margin */} +
+
+

Margem Média

+ +
+

24.5%

+
+ -2.1% (Pressão de Custo) +
+
+ + {/* Metric 4: Active Orders */} +
+
+

Ordens Ativas

+ +
+

{activeOrders.length}

+
+ +
+
+
+ + {/* DATA DENSITY TABLE (Bloomberg Style) */} +
+ {/* ORDER FLOW */} +
+
+

Fluxo de Pedidos

+ +
+ + + + + + + + + + + {orders.length === 0 ? ( + + ) : ( + orders.slice(0, 8).map(o => ( + + + + + + + )) + )} + +
IDFornecedorValorStatus
Sem dados recentes.
{o.id.substring(0, 8)}...{o.supplierName}R$ {o.totalCostWithOverhead.toLocaleString('pt-BR')} + + {o.status.toUpperCase()} + +
+
+ + {/* SUPPLIER PERFORMANCE (Compact List) */} +
+
+

Top Fornecedores

+
+
+ {suppliers.slice(0, 6).map((s, i) => ( +
+
+ {i + 1} +
+

{s.name}

+

Eletrônicos • PY

+
+
+
+

+ R$ 45k

+
+
+ ))} +
+
+
+
+ ); +}; + +export default Dashboard; diff --git a/pages/Financial.tsx b/pages/Financial.tsx new file mode 100644 index 0000000..ca5afec --- /dev/null +++ b/pages/Financial.tsx @@ -0,0 +1,287 @@ +import React, { useState } from 'react'; +import { useCRM } from '../context/CRMContext'; +import { ArrowUpRight, ArrowDownRight, TrendingUp, DollarSign, Calendar, CheckCircle, AlertCircle, Plus } from 'lucide-react'; +import clsx from 'clsx'; +import { PaymentMethod, Transaction } from '../types'; + +const Financial: React.FC = () => { + const { transactions, getFinancialSummary, addTransaction, updateTransaction, loading } = useCRM(); + const { totalIncome, totalExpense, balance } = getFinancialSummary(); + + const [activeTab, setActiveTab] = useState<'payable' | 'receivable' | 'dashboard'>('dashboard'); + const [showAddModal, setShowAddModal] = useState(false); + + // New Bill State + const [newBill, setNewBill] = useState>({ + type: 'Expense', + status: 'Pending', + paymentMethod: 'Boleto', + date: new Date().toISOString().split('T')[0], + dueDate: new Date().toISOString().split('T')[0] + }); + + const pendingPayables = transactions.filter(t => t.type === 'Expense' && t.status === 'Pending').sort((a, b) => new Date(a.dueDate || '').getTime() - new Date(b.dueDate || '').getTime()); + const paidPayables = transactions.filter(t => t.type === 'Expense' && t.status === 'Paid'); + + // Sort Receivables by date desc + const receivables = transactions.filter(t => t.type === 'Income').sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());; + + const handleSaveBill = async () => { + if (!newBill.description || !newBill.amount) return alert("Preencha descrição e valor"); + + await addTransaction({ + type: 'Expense', + category: 'Contas', + description: newBill.description, + amount: Number(newBill.amount), + date: new Date(newBill.date!).toISOString(), + dueDate: new Date(newBill.dueDate!).toISOString(), + status: 'Pending', // Force pending + paymentMethod: newBill.paymentMethod as PaymentMethod || 'Boleto' + }); + + setShowAddModal(false); + setNewBill({ type: 'Expense', status: 'Pending', paymentMethod: 'Boleto', date: new Date().toISOString().split('T')[0], dueDate: new Date().toISOString().split('T')[0] }); + }; + + const togglePaid = async (t: Transaction) => { + const newStatus = t.status === 'Paid' ? 'Pending' : 'Paid'; + await updateTransaction(t.id, { status: newStatus }); + } + + const renderDashboard = () => ( +
+ {/* Summary Cards */} +
+
+
+ +
+
+

Entradas (Total)

+

R$ {totalIncome.toLocaleString('pt-BR')}

+
+ +12% este mês +
+
+
+ +
+
+ +
+
+

Saídas (Total)

+

R$ {totalExpense.toLocaleString('pt-BR')}

+
+ Estável +
+
+
+ +
+
+ +
+
+

Saldo Atual

+

= 0 ? "text-emerald-400" : "text-rose-400")}> + R$ {balance.toLocaleString('pt-BR')} +

+
+ Balanço Geral +
+
+
+
+ + {/* Upcoming Bills Alert */} + {pendingPayables.length > 0 && ( +
+
+ +
+
+

Contas a Pagar Pendentes

+

Você tem {pendingPayables.length} contas pendentes. Verifique a aba "A Pagar".

+
+ +
+ )} +
+ ); + + const renderPayables = () => ( +
+
+

Contas a Pagar

+ +
+ +
+ + + + + + + + + + + + + {pendingPayables.concat(paidPayables).map(t => { + const isOverdue = new Date(t.dueDate || '') < new Date() && t.status !== 'Paid'; + return ( + + + + + + + + + ); + })} + +
VencimentoDescriçãoValorViaStatusAção
+
+ + {t.dueDate ? new Date(t.dueDate).toLocaleDateString() : '-'} +
+
{t.description}- R$ {t.amount.toLocaleString('pt-BR')}{t.paymentMethod} + + {isOverdue ? 'Vencido' : t.status === 'Paid' ? 'Pago' : 'Pendente'} + + + +
+ {transactions.filter(t => t.type === 'Expense').length === 0 && ( +
Nenhuma conta registrada.
+ )} +
+
+ ); + + const renderReceivables = () => ( +
+

Contas a Receber (Vendas)

+
+ + + + + + + + + + + + {receivables.map(t => ( + + + + + + + + ))} + +
DataOrigemValorForma PagtoStatus
{new Date(t.date).toLocaleDateString()}{t.description}+ R$ {t.amount.toLocaleString('pt-BR')}{t.paymentMethod} + + {t.status || 'Paid'} + +
+ {receivables.length === 0 && ( +
Nenhuma venda registrada.
+ )} +
+
+ ); + + return ( +
+ {/* Tabs */} +
+ + + +
+ + {activeTab === 'dashboard' && renderDashboard()} + {activeTab === 'payable' && renderPayables()} + {activeTab === 'receivable' && renderReceivables()} + + {/* Add Bill Modal */} + {showAddModal && ( +
+
+

Agendar Pagamento

+
+ setNewBill({ ...newBill, description: e.target.value })} + className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-3 text-sm text-white focus:border-indigo-500 outline-none" + /> + setNewBill({ ...newBill, amount: e.target.value })} + className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-3 text-sm text-white focus:border-indigo-500 outline-none" + /> +
+
+ + setNewBill({ ...newBill, dueDate: e.target.value })} + className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-3 text-sm text-white focus:border-indigo-500 outline-none" + /> +
+
+ + +
+
+ + +
+
+
+ )} +
+ ); +}; + +export default Financial; diff --git a/pages/Inventory.tsx b/pages/Inventory.tsx new file mode 100644 index 0000000..e46af5d --- /dev/null +++ b/pages/Inventory.tsx @@ -0,0 +1,58 @@ + +import React from 'react'; +import { Boxes } from 'lucide-react'; +import { useCRM } from '../context/CRMContext'; + +const Inventory: React.FC = () => { + const { inventory } = useCRM(); + + return ( +
+
+
+

Estoque & Armazenamento

+

Visão geral dos ativos em custódia.

+
+
+ +
+ {inventory.length > 0 ? inventory.map((item, idx) => ( +
+
+ +
+ +
+ {item.sku} + QTD: {item.quantity} +
+ +

{item.name}

+ +
+
+ Custo Base + R$ {item.avgCostBRL.toFixed(2)} +
+
+ Preço Alvo + R$ {item.marketPriceBRL.toFixed(2)} +
+
+ + +
+ )) : ( +
+ +

Nenhum item em estoque

+
+ )} +
+
+ ); +}; + +export default Inventory; diff --git a/pages/Login.tsx b/pages/Login.tsx new file mode 100644 index 0000000..0ee4e28 --- /dev/null +++ b/pages/Login.tsx @@ -0,0 +1,106 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Hexagon, ArrowRight, Lock, Mail } from 'lucide-react'; +import clsx from 'clsx'; +import { useCRM } from '../context/CRMContext'; +import Logo from '../components/Logo'; + +const Login: React.FC = () => { + const navigate = useNavigate(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [loading, setLoading] = useState(false); + + const { signIn } = useCRM(); + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + const { error } = await signIn(email, password); + + if (error) { + alert("Erro ao entrar: " + error.message); + setLoading(false); + } else { + // Context logic handles redirect via Session state or we can force it here + navigate('/'); + } + }; + + return ( +
+ {/* Background Ambient Effects */} +
+
+ + {/* Login Card */} +
+
+ + {/* Header */} +
+ +
+ + {/* Form */} +
+
+ +
+
+ +
+ setEmail(e.target.value)} + className="w-full bg-secondary/30 border border-white/5 hover:border-white/10 focus:border-white/20 rounded-2xl py-4 pl-12 pr-4 text-sm text-foreground placeholder:text-muted-foreground/50 outline-none transition-all" + placeholder="admin@arbitra.com" + /> +
+
+ +
+ +
+
+ +
+ setPassword(e.target.value)} + className="w-full bg-secondary/30 border border-white/5 hover:border-white/10 focus:border-white/20 rounded-2xl py-4 pl-12 pr-4 text-sm text-foreground placeholder:text-muted-foreground/50 outline-none transition-all" + placeholder="••••••••" + /> +
+
+ + +
+ + {/* Footer */} +
+

+ Acesso Restrito • Segurança Criptografada +

+
+
+
+
+ ); +}; + +export default Login; diff --git a/pages/Orders.tsx b/pages/Orders.tsx new file mode 100644 index 0000000..b28b602 --- /dev/null +++ b/pages/Orders.tsx @@ -0,0 +1,270 @@ + +import React from 'react'; +import { Store, Trash2, FileEdit, FileCheck, Printer } from 'lucide-react'; +import { useCRM } from '../context/CRMContext'; +import { OrderStatus } from '../types'; + +const Orders: React.FC = () => { + const { orders, updateOrderStatus, resumeOrder, deleteOrder } = useCRM(); + + const handleDelete = async (id: string) => { + if (window.confirm("Tem certeza que deseja excluir esse pedido?")) { + await deleteOrder(id); + } + }; + + const handlePrint = (order: any) => { + const printWindow = window.open('', '_blank'); + if (printWindow) { + printWindow.document.write(` + + + Pedido #${order.id.substring(0, 8)} + + + +
+
+

Paraguai Imports

+

Relatório de Pedido

+
+
+

Data: ${new Date(order.date).toLocaleDateString()}

+

ID: #${order.id}

+

Status: ${order.status}

+
+
+ +
+

Fornecedor: ${order.supplierName}

+
+ + + + + + + + + + + + + ${order.items.map((item: any) => ` + + + + + + + + `).join('')} + +
ProdutoQtdPreço Unit. (USD)Total (USD)Total (BRL)
${item.name}${item.quantity}$ ${item.priceUSD.toFixed(2)}$ ${(item.priceUSD * item.quantity).toFixed(2)}R$ ${(item.priceBRL * item.quantity).toFixed(2)}
+ +
+

Total USD: $ ${order.totalUSD.toFixed(2)}

+

Total BRL (Sem Taxas): R$ ${order.totalBRL.toFixed(2)}

+

Custo Final Estimado: R$ ${order.totalCostWithOverhead.toLocaleString('pt-BR')}

+

Lucro Estimado: R$ ${order.estimatedProfit.toLocaleString('pt-BR')}

+
+ + + + + `); + printWindow.document.close(); + printWindow.print(); + } + }; + + return ( +
+
+
+

Fluxo de Pedidos (Order Flow)

+

Gestão de ciclo de vida e conciliação.

+
+
+ + {/* Desktop Table */} +
+ + + + + + + + + + + + + + {orders.map(o => ( + + + + + + + + + + ))} + +
ID / DataFornecedorCompra (USD)Custo BR (R$)Delta ($)StatusAções
+

{o.id.substring(0, 8)}...

+

{new Date(o.date).toLocaleDateString()}

+
+
+ + {o.supplierName} +
+
US$ {o.totalUSD.toFixed(2)}R$ {o.totalCostWithOverhead.toLocaleString('pt-BR')} + + {o.estimatedProfit.toLocaleString('pt-BR')} + + + +
+ + + + +
+
+
+ + {/* Mobile Card View */} +
+ {orders.map(o => ( +
+
+
+

Pedido

+

{o.id.substring(0, 8)}...

+

{new Date(o.date).toLocaleDateString()}

+
+
+ {o.status} +
+
+ +
+
+

Fornecedor

+
+ + {o.supplierName} +
+
+
+

Total (BRL)

+

R$ {o.totalCostWithOverhead.toLocaleString('pt-BR')}

+
+
+ +
+ + +
+ + + +
+
+
+ ))} +
+ + {orders.length === 0 && ( +
NO ACTIVE ORDERS.
+ )} +
+ ); +}; + +export default Orders; diff --git a/pages/Products.tsx b/pages/Products.tsx new file mode 100644 index 0000000..fb8b661 --- /dev/null +++ b/pages/Products.tsx @@ -0,0 +1,261 @@ + +import React, { useState } from 'react'; +import { useCRM } from '../context/CRMContext'; +import { Plus, Search, Edit2, Trash2, Save, X, ScanBarcode, Package } from 'lucide-react'; +import { InventoryItem } from '../types'; +import clsx from 'clsx'; + +const Products: React.FC = () => { + const { inventory, addProduct, updateProduct, deleteProduct } = useCRM(); + const [searchTerm, setSearchTerm] = useState(''); + const [isModalOpen, setIsModalOpen] = useState(false); + const [editingProduct, setEditingProduct] = useState(null); + + // Form State + const [formData, setFormData] = useState>({ + name: '', + sku: '', + ean: '', + quantity: 0, + avgCostBRL: 0, + marketPriceBRL: 0, // This is Sale Price + lastSupplier: 'Manual' + }); + + const filteredProducts = inventory.filter(p => + p.name.toLowerCase().includes(searchTerm.toLowerCase()) || + p.sku.toLowerCase().includes(searchTerm.toLowerCase()) || + (p.ean && p.ean.includes(searchTerm)) + ); + + const handleOpenModal = (product?: InventoryItem) => { + if (product) { + setEditingProduct(product); + setFormData(product); + } else { + setEditingProduct(null); + setFormData({ + name: '', + sku: '', + ean: '', + quantity: 0, + avgCostBRL: 0, + marketPriceBRL: 0, + lastSupplier: 'Manual' + }); + } + setIsModalOpen(true); + }; + + const handleSave = async () => { + if (!formData.name || !formData.sku) { + alert('Nome e SKU são obrigatórios.'); + return; + } + + try { + if (editingProduct) { + await updateProduct(editingProduct.id, formData); + } else { + await addProduct(formData as Omit); + } + setIsModalOpen(false); + } catch (error: any) { + console.error(error); + alert(`Erro ao salvar produto: ${error.message || JSON.stringify(error)}`); + } + }; + + const handleDelete = async (id: string) => { + if (confirm('Tem certeza que deseja excluir este produto?')) { + await deleteProduct(id); + } + }; + + return ( +
+ {/* Validating EAN logic exists in types from previous turn? Yes if applied. */} + +
+
+

Produtos

+

Gerencie o catálogo de produtos, SKUs e EANs.

+
+ +
+ + {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="bg-transparent border-none text-white text-sm focus:ring-0 w-full p-3 placeholder:text-slate-600" + /> +
+ + {/* Table */} +
+ + + + + + + + + + + + {filteredProducts.map((p) => ( + + + + + + + + ))} + +
ProdutoCódigosEstoquePreço Venda (BRL)Ações
+

{p.name}

+

{p.lastSupplier}

+
+
+
+ SKU + {p.sku} +
+ {p.ean && ( +
+ EAN + {p.ean} +
+ )} +
+
+ {p.quantity} + + R$ {p.marketPriceBRL?.toLocaleString('pt-BR', { minimumFractionDigits: 2 })} + +
+ + +
+
+ {filteredProducts.length === 0 && ( +
+ +

Nenhum produto encontrado.

+
+ )} +
+ + {/* Modal */} + {isModalOpen && ( +
+
+
+

{editingProduct ? 'Editar Produto' : 'Novo Produto'}

+ +
+ +
+ {/* Basic Info */} +
+ + setFormData({ ...formData, name: e.target.value })} + placeholder="Ex: iPhone 15 Pro Max 256GB" + /> +
+ +
+
+ + setFormData({ ...formData, sku: e.target.value })} + placeholder="Ex: IPH-15-PM-256" + /> +
+
+ +
+ + setFormData({ ...formData, ean: e.target.value })} + placeholder="Ex: 789..." + /> +
+
+
+ + {/* Prices & Stock */} +
+
+ + setFormData({ ...formData, quantity: parseInt(e.target.value) || 0 })} + /> +
+
+ + setFormData({ ...formData, avgCostBRL: parseFloat(e.target.value) || 0 })} + /> +
+
+ + setFormData({ ...formData, marketPriceBRL: parseFloat(e.target.value) || 0 })} + /> +
+
+ +
+ + setFormData({ ...formData, lastSupplier: e.target.value })} + placeholder="Ex: Apple Store PY" + /> +
+ + +
+
+
+ )} +
+ ); +}; + +export default Products; diff --git a/pages/Reports.tsx b/pages/Reports.tsx new file mode 100644 index 0000000..8d85016 --- /dev/null +++ b/pages/Reports.tsx @@ -0,0 +1,306 @@ + +import React, { useState, useMemo } from 'react'; +import { useCRM } from '../context/CRMContext'; +import { + BarChart3, TrendingUp, PieChart, Package, ArrowUpRight, ArrowDownRight, + Download, Calendar, Filter, DollarSign, Activity, AlertCircle, Boxes +} from 'lucide-react'; +import { + BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, + LineChart, Line, AreaChart, Area +} from 'recharts'; +import clsx from 'clsx'; + +const Reports: React.FC = () => { + const { orders, inventory, transactions, sales } = useCRM(); + const [period, setPeriod] = useState<'30d' | '3m' | '6m' | 'ytd' | '1y' | 'all'>('30d'); + const [activeTab, setActiveTab] = useState<'financial' | 'products' | 'inventory' | 'sales'>('financial'); + + // --- FILTERING LOGIC --- + const filteredSales = useMemo(() => { + const now = new Date(); + const cutoff = new Date(); + + if (period === '30d') cutoff.setDate(now.getDate() - 30); + else if (period === '3m') cutoff.setMonth(now.getMonth() - 3); + else if (period === '6m') cutoff.setMonth(now.getMonth() - 6); + else if (period === 'ytd') cutoff.setMonth(0, 1); // Jan 1st of current year + else if (period === '1y') cutoff.setFullYear(now.getFullYear() - 1); + else if (period === 'all') return sales; // No filter + + return sales.filter(s => new Date(s.date) >= cutoff); + }, [sales, period]); + + // 1. Financial Metrics + const financialStats = useMemo(() => { + // Revenue + const totalRevenue = filteredSales.reduce((acc, s) => acc + s.total, 0); + + // Cost of Goods Sold (COGS) based on captured costPrice at time of sale + const totalCost = filteredSales.reduce((acc, s) => { + const saleCost = s.items.reduce((iAcc, item) => iAcc + ((item.costPrice || 0) * item.quantity), 0); + return acc + saleCost; + }, 0); + + const totalProfit = totalRevenue - totalCost; + const margin = totalRevenue > 0 ? (totalProfit / totalRevenue) * 100 : 0; + + return { totalRevenue, totalCost, totalProfit, margin }; + }, [filteredSales]); + + // 2. Product/Arbitrage Metrics + const productStats = useMemo(() => { + const productPerformance: Record = {}; + + filteredSales.forEach(s => { + s.items.forEach(item => { + if (!productPerformance[item.name]) { + productPerformance[item.name] = { profit: 0, revenue: 0, quantity: 0 }; + } + const itemRevenue = item.salePrice; + const itemCost = item.costPrice || 0; + + productPerformance[item.name].profit += (itemRevenue - itemCost) * item.quantity; + productPerformance[item.name].revenue += itemRevenue * item.quantity; + productPerformance[item.name].quantity += item.quantity; + }); + }); + + const sortedProducts = Object.entries(productPerformance) + .map(([name, stats]) => ({ name, ...stats })) + .sort((a, b) => b.profit - a.profit); + + return { + topProducts: sortedProducts.slice(0, 5), + lowMarginProducts: sortedProducts.filter(p => p.revenue > 0 && (p.profit / p.revenue) < 0.10).slice(0, 5) + }; + }, [filteredSales]); + + // 3. Inventory Stats (Unchanged) + const inventoryStats = useMemo(() => { + const totalValue = inventory.reduce((acc, i) => acc + (i.avgCostBRL * i.quantity), 0); + const totalItems = inventory.reduce((acc, i) => acc + i.quantity, 0); + const deadStock = inventory.filter(i => i.quantity > 0).slice(0, 3); + return { totalValue, totalItems, deadStock }; + }, [inventory]); + + // 4. Chart Data - Responsive to Period + const chartData = useMemo(() => { + const dataMap: Record = {}; + + filteredSales.forEach(sale => { + if (sale.status === 'Cancelled' || sale.status === 'Returned') return; + const d = new Date(sale.date); + + // Dynamic grouping key + let key = ''; + if (period === '30d') { + key = d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' }); // Daily for 30d + } else { + key = d.toLocaleDateString('pt-BR', { month: 'short', year: '2-digit' }); // Monthly for others + } + + if (!dataMap[key]) dataMap[key] = { receita: 0, lucro: 0, date: d.getTime() }; + + const saleCost = sale.items.reduce((acc, item) => acc + ((item.costPrice || 0) * item.quantity), 0); + + dataMap[key].receita += sale.total; + dataMap[key].lucro += (sale.total - saleCost); + }); + + return Object.entries(dataMap) + .map(([name, vals]) => ({ name, ...vals })) + .sort((a, b) => a.date - b.date); // Sort chronologically + }, [filteredSales, period]); // Recalc on filter change + + + // --- EXPORT --- + const handleExport = () => { + const csvContent = "data:text/csv;charset=utf-8," + + "Categoria,Valor\n" + + `Receita Total,${financialStats.totalRevenue}\n` + + `Custo Total,${financialStats.totalCost}\n` + + `Lucro Liquido,${financialStats.totalProfit}\n`; + + const encodedUri = encodeURI(csvContent); + const link = document.createElement("a"); + link.setAttribute("href", encodedUri); + link.setAttribute("download", "relatorio_arbitra.csv"); + document.body.appendChild(link); + link.click(); + }; + + + return ( +
+ + {/* HEADER */} +
+
+

Relatórios & Analytics

+

Visão geral de performance, financeiro e estoque.

+
+ +
+
+ {[ + { id: '30d', label: '30D' }, + { id: '3m', label: '3M' }, + { id: '6m', label: '6M' }, + { id: 'ytd', label: 'YTD' }, + { id: '1y', label: '1 Ano' }, + { id: 'all', label: 'Tudo' } + ].map((p) => ( + + ))} +
+ +
+
+ + {/* KPI CARDS */} +
+ {[ + { label: 'Receita Total', value: `R$ ${financialStats.totalRevenue.toLocaleString('pt-BR')}`, icon: DollarSign, color: 'text-emerald-400', sub: '+12% vs. mês anterior' }, + { label: 'Lucro Líquido', value: `R$ ${financialStats.totalProfit.toLocaleString('pt-BR')}`, icon: TrendingUp, color: 'text-indigo-400', sub: `${financialStats.margin.toFixed(1)}% Margem` }, + { label: 'Valor em Estoque', value: `R$ ${inventoryStats.totalValue.toLocaleString('pt-BR')}`, icon: Package, color: 'text-blue-400', sub: `${inventoryStats.totalItems} iten(s)` }, + { label: 'Pedidos Realizados', value: orders.length, icon: Activity, color: 'text-amber-400', sub: 'Volume total' }, + ].map((stat, idx) => ( +
+
+
+ +
+ + +2.5% + +
+

{stat.value}

+

{stat.label}

+

{stat.sub}

+
+ ))} +
+ + {/* TABS */} +
+ {[ + { id: 'financial', label: 'Financeiro', icon: DollarSign }, + { id: 'products', label: 'Produtos', icon: Package }, + { id: 'inventory', label: 'Estoque', icon: Boxes }, + { id: 'sales', label: 'Vendas', icon: BarChart3 } + ].map(tab => ( + + ))} +
+ + {/* CONTENT AREA */} +
+ + {/* LEFT: MAIN CHART using Recharts */} +
+

Performance Financeira

+
+ + + + + + + + + + + + + + + `R$${value / 1000}k`} /> + + + + + +
+
+ + {/* RIGHT: METRIC LISTS */} +
+ {/* TOP PRODUCTS */} +
+

Produtos + Lucrativos

+
+ {productStats.topProducts.length > 0 ? productStats.topProducts.map((p, i) => ( +
+
+
+ {i + 1} +
+
+

{p.name}

+

{p.quantity} vendidos

+
+
+ R$ {p.profit.toLocaleString('pt-BR', { maximumFractionDigits: 0 })} +
+ )) : ( +

Sem dados de venda.

+ )} +
+
+ + {/* ALERTS */} +
+

Alertas de Estoque

+
+
+ +
+

Baixo Giro

+

3 produtos não vendem há 30 dias.

+
+
+
+ +
+

Reabastecer

+

5 SKUs abaixo do estoque mínimo.

+
+
+
+
+
+
+
+ ); +}; + +export default Reports; diff --git a/pages/Sales.tsx b/pages/Sales.tsx new file mode 100644 index 0000000..d2aa3f7 --- /dev/null +++ b/pages/Sales.tsx @@ -0,0 +1,558 @@ + +import React, { useState } from 'react'; +import { useCRM } from '../context/CRMContext'; +import { Search, ShoppingCart, Trash2, Plus, Minus, CreditCard, ChevronRight, Calculator, Package, AlertTriangle, Download, History, Store, CheckSquare, Square, UserPlus, Pencil, X, Banknote, CreditCard as CardIcon } from 'lucide-react'; +import { InventoryItem, Sale, SalesChannel, Customer, PaymentMethod } from '../types'; +import clsx from 'clsx'; + +const Sales: React.FC = () => { + const { inventory, customers, registerSale, sales, importSales, updateSale, loading, addCustomer, updateProduct, isAdmin } = useCRM(); + + // State + const [viewMode, setViewMode] = useState<'list' | 'pos'>('list'); // 'list' = Pedidos, 'pos' = Frente de Caixa + + // POS State + const [searchTerm, setSearchTerm] = useState(''); + const [cart, setCart] = useState<{ item: InventoryItem; quantity: number; price: number }[]>([]); + const [selectedCustomer, setSelectedCustomer] = useState(''); + const [paymentMethod, setPaymentMethod] = useState('Cash'); // New state + const [isFinalizing, setIsFinalizing] = useState(false); + + // Import Modal State + const [showImportModal, setShowImportModal] = useState(false); + const [importChannel, setImportChannel] = useState('Mercado Livre'); + + // Quick Customer Modal + const [showCustomerModal, setShowCustomerModal] = useState(false); + const [newCustomer, setNewCustomer] = useState({ name: '', email: '', phone: '' }); + + // Quick Edit Product Modal + const [editingProduct, setEditingProduct] = useState(null); + const [editForm, setEditForm] = useState({ price: 0, stock: 0 }); + + // Filter Inventory + const filteredInventory = inventory.filter(i => + i.name.toLowerCase().includes(searchTerm.toLowerCase()) || + i.sku.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + // Cart Actions + const addToCart = (item: InventoryItem) => { + const existing = cart.find(c => c.item.id === item.id); + if (existing) { + if (existing.quantity >= item.quantity) return; // Max stock reached + setCart(cart.map(c => c.item.id === item.id ? { ...c, quantity: c.quantity + 1 } : c)); + } else { + if (item.quantity <= 0) return; + setCart([...cart, { item, quantity: 1, price: Number(item.marketPriceBRL) || 0 }]); + } + }; + + const updateQuantity = (id: string, delta: number) => { + setCart(cart.map(c => { + if (c.item.id === id) { + const newQty = c.quantity + delta; + if (newQty <= 0) return c; // Don't remove here, need explicit remove + if (newQty > c.item.quantity) return c; + return { ...c, quantity: newQty }; + } + return c; + })); + }; + + const removeFromCart = (id: string) => { + setCart(cart.filter(c => c.item.id !== id)); + }; + + const updatePrice = (id: string, newPrice: number) => { + setCart(cart.map(c => c.item.id === id ? { ...c, price: newPrice } : c)); + }; + + // Totals + const totalAmount = cart.reduce((acc, c) => acc + (c.price * c.quantity), 0); + const totalItems = cart.reduce((acc, c) => acc + c.quantity, 0); + + const [showSuccessModal, setShowSuccessModal] = useState(false); + const [lastSaleTotal, setLastSaleTotal] = useState(0); + + const handleFinalize = async () => { + if (cart.length === 0) return; + setIsFinalizing(true); + + const saleTotal = cart.reduce((acc, c) => acc + (c.price * c.quantity), 0); + setLastSaleTotal(saleTotal); + + await registerSale( + cart.map(c => ({ id: c.item.id, quantity: c.quantity, salePrice: c.price })), + selectedCustomer || undefined, + paymentMethod // Pass new arg + ); + + setCart([]); + setSelectedCustomer(''); + setPaymentMethod('Cash'); // Reset + setIsFinalizing(false); + setShowSuccessModal(true); + }; + + const handleNewSale = () => { + setShowSuccessModal(false); + }; + + const handlePrintReceipt = () => { + alert("Impressão de Recibo iniciada... (Mock)"); + // window.print(); + }; + + const handleSendEmail = () => { + const email = customers.find(c => c.id === selectedCustomer)?.email || "cliente@exemplo.com"; + alert(`Comprovante enviado para ${email} (Mock)`); + }; + + // --- IMPORT HANDLERS --- + const handleImport = async () => { + await importSales(importChannel); + setShowImportModal(false); + }; + + // --- QUICK ACTIONS --- + const handleSaveCustomer = async () => { + if (!newCustomer.name) return alert("Nome é obrigatório"); + try { + const created = await addCustomer({ ...newCustomer, status: 'Active', city: 'Foz do Iguaçu' } as any); + if (created) { + setNewCustomer({ name: '', email: '', phone: '' }); + setShowCustomerModal(false); + setSelectedCustomer(created.id); // Auto-select + } + } catch (error) { + console.error(error); + alert("Erro ao cadastrar cliente. Verifique o console."); + } + }; + + const handleSaveProductEdit = async () => { + if (!editingProduct) return; + await updateProduct(editingProduct.id, { + marketPriceBRL: editForm.price, + quantity: editForm.stock + }); + setEditingProduct(null); + }; + + // --- UI RENDERERS --- + + // 1. SALES LIST VIEW + const renderSalesList = () => ( +
+
+
+

Histórico de Pedidos

+

Gerencie vendas locais e importadas.

+
+
+ + +
+
+ +
+ + + + + + + + + + + + + + {sales.map(sale => ( + + + + + + + + + + ))} + +
Data / IDCanalClienteTotalStatusBaixar EstoqueLançar Financeiro
+
{new Date(sale.date).toLocaleDateString()}
+
#{sale.externalId || sale.id.substring(0, 8)}
+
+
+ {sale.channel === 'Mercado Livre' && MeLi} + {sale.channel === 'Shopee' && Shopee} + {sale.channel === 'Amazon' && Amazon} + {sale.channel === 'Local' && Balcão} +
+
+ {sale.customerName || 'Cliente Consumidor'} + + R$ {sale.total.toLocaleString('pt-BR', { minimumFractionDigits: 2 })} + + + {sale.status} + + + + + +
+ {sales.length === 0 && ( +
+ +

Nenhuma venda registrada

+
+ )} +
+ + {/* IMPORT MODAL */} + {showImportModal && ( +
+
+

Importar Pedidos

+ +
+
+ + +
+ +
+
+ +
+
+ Simularemos a conexão com a API do {importChannel} para buscar novos pedidos pendentes. +
+
+
+ +
+ + +
+
+
+ )} +
+ ); + + + return ( +
+ {viewMode === 'list' ? renderSalesList() : ( +
+ +
+ {/* LEFT: POS / INVENTORY */} +
+ {/* ... Search Bar and Grid kept as children ... */} + {/* RE-INSERTING ORIGINAL POS CONTENT WRAPPED IN FRAGMENT OR DIV IF NEEDED */} + + {/* Search Bar */} +
+
+ + setSearchTerm(e.target.value)} + placeholder="Buscar produto por nome ou SKU..." + className="w-full bg-slate-900/50 border border-white/10 rounded-2xl py-4 pl-12 pr-4 text-white focus:border-indigo-500 outline-none transition-all placeholder:text-slate-600" + /> +
+
+ + {/* Grid */} +
+
+ {filteredInventory.map(item => ( +
addToCart(item)} + className={clsx( + "p-5 rounded-[24px] border transition-all cursor-pointer group relative overflow-hidden", + item.quantity === 0 ? "opacity-50 grayscale border-white/5 bg-white/5 cursor-not-allowed" : "bg-white/5 border-white/5 hover:border-indigo-500/50 hover:bg-white/10 active:scale-95" + )} + > +
+ {item.sku} + {item.quantity <= 3 && item.quantity > 0 && ( +
+ Baixo Estoque +
+ )} + {isAdmin && ( + + )} +
+

{item.name}

+
+
+

Preço Venda

+

R$ {(Number(item.marketPriceBRL) || 0).toLocaleString('pt-BR')}

+
+
+

Estoque

+

{item.quantity}

+
+
+
+ ))} +
+
+
+ + {/* RIGHT: CART / CHECKOUT */} +
+
+ + {/* Header */} +
+
+
+ +
+
+

Carrinho

+

Nova Venda

+
+
+ {totalItems} itens +
+ + {/* Customer Select (Optional) */} +
+ + +
+ + {/* Cart Items */} +
+ {cart.length === 0 ? ( +
+ +

Carrinho Vazio

+
+ ) : ( + cart.map((c, idx) => ( +
+
+

{c.item.name}

+ +
+ +
+
+ + {c.quantity} + +
+ +
+
+ R$ + updatePrice(c.item.id, parseFloat(e.target.value))} + className="w-full bg-black/30 border border-white/10 rounded-lg py-1.5 pl-8 pr-2 text-right text-xs font-bold text-white focus:border-indigo-500 outline-none" + /> +
+
+
+
+ Subtotal: + R$ {(c.price * c.quantity).toLocaleString('pt-BR')} +
+
+ )) + )} +
+ + {/* Summary & Action */} +
+
+
+ Itens + {totalItems} UN +
+
+ Total Geral + R$ {totalAmount.toLocaleString('pt-BR')} +
+
+ + {/* Payment Method Selector */} +
+

Forma de Pagamento

+
+ {['Cash', 'Pix', 'Credit Card'].map(method => ( + + ))} +
+
+ + +
+ +
+
+
+
+ )} + {/* SUCCESS MODAL */} + {showSuccessModal && ( +
+
+ {/* Confetti or success decoration could go here */} +
+ +
+
+ +
+

Venda Realizada!

+

Total processado com sucesso.

+
R$ {lastSaleTotal.toLocaleString('pt-BR')}
+
+ +
+ + + + + +
+
+
+ )} +
+ ); +}; + +export default Sales; diff --git a/pages/Settings.tsx b/pages/Settings.tsx new file mode 100644 index 0000000..1e2e954 --- /dev/null +++ b/pages/Settings.tsx @@ -0,0 +1,452 @@ + +import React, { useState } from 'react'; +import { Settings as SettingsIcon, Truck, Key, Building, Users, Save, Globe, Smartphone, Bell, Shield, FileCheck, ShoppingBag, RefreshCw, AlertCircle, Upload, Palette } from 'lucide-react'; +import clsx from 'clsx'; +import { useCRM } from '../context/CRMContext'; +import { useTheme } from '../context/ThemeContext'; + +const ThemeSelector = () => { + const { theme, setTheme } = useTheme(); + + return ( +
+

Tema do Sistema

+ +
+ {[ + { id: 'light', name: 'Simples (Clean)', color: 'bg-slate-50 border-2 border-slate-200', accent: 'bg-blue-600', text: 'Visual limpo e direto.' }, + { id: 'ocean', name: 'Complexo (Ocean)', color: 'bg-[#0f172a]', accent: 'bg-cyan-500', text: 'Alta densidade e contraste.' }, + { id: 'dark', name: 'Completo (Pro)', color: 'bg-[#0a0a0c]', accent: 'bg-indigo-600', text: 'Interface imersiva padrão.' }, + ].map(t => ( + + ))} +
+
+ ); +}; + +const Settings: React.FC = () => { + const { overheadPercent, exchangeRate, settings, updateSettings } = useCRM(); + const [activeTab, setActiveTab] = useState<'general' | 'fiscal' | 'marketplaces' | 'logistics' | 'email' | 'notifications'>('general'); + + // Local state for form, initialized with context settings + const [config, setConfig] = useState(settings || { + companyName: 'Arbitra System', + cnpj: '', + ie: '', + defaultOverhead: 20, + defaultExchange: 5.65, + certificateName: '', + certificatePassword: '', + geminiKey: 'sk-....................', + melhorEnvioToken: '', + blingToken: '', + tinyToken: '', + whatsappNumber: '', + nfeSerie: '1', + nfeNumber: '159', + autoSyncSales: true, + autoSyncStock: true, + smtpHost: '', + smtpPort: '587', + smtpUser: '', + smtpPass: '', + }); + + // Sync state when settings load from DB + React.useEffect(() => { + if (settings) { + setConfig(prev => ({ ...prev, ...settings })); + } + }, [settings]); + + const handleSave = async () => { + try { + await updateSettings(config); + alert('Configurações salvas com sucesso!'); + } catch (error) { + console.error(error); + alert('Erro ao salvar configurações.'); + } + }; + + const tabs = [ + { id: 'general', label: 'Geral', icon: Building, description: 'Dados cadastrais' }, + { id: 'fiscal', label: 'Fiscal & NFe', icon: FileCheck, description: 'Certificado e tributação' }, + { id: 'appearance', label: 'Aparência', icon: Palette, description: 'Temas e Cores' }, + { id: 'marketplaces', label: 'Marketplaces', icon: ShoppingBag, description: 'Integrações de vendas' }, + { id: 'logistics', label: 'Logística', icon: Truck, description: 'Fretes e entregas' }, + { id: 'email', label: 'Email SMTP', icon: Globe, description: 'Configuração de envio' }, + { id: 'notifications', label: 'Notificações', icon: Bell, description: 'Alertas e avisos' }, + ]; + + return ( +
+ +
+
+

Configurações do Sistema

+

Gerencie todos os parâmetros fiscais, integrações e regras de negócio.

+
+ +
+ +
+ + {/* Denser Sidebar */} +
+ {tabs.map(tab => ( + + ))} +
+ + {/* Content Area - Denser Forms */} +
+
+ + {activeTab === 'general' && ( +
+ {/* Section */} +
+

Identificação da Empresa

+
+
+ + setConfig({ ...config, cnpj: e.target.value })} + placeholder="00.000.000/0000-00" + className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all font-mono" + /> +
+
+ + setConfig({ ...config, ie: e.target.value })} + className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all" + /> +
+
+ + setConfig({ ...config, companyName: e.target.value })} + className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all" + /> +
+
+
+ + {/* Section */} +
+

Parâmetros Financeiros

+
+
+ +
+ R$ + setConfig({ ...config, defaultExchange: parseFloat(e.target.value) })} + className="w-full bg-black/20 border border-white/10 rounded-lg pl-8 pr-3 py-2 text-sm text-slate-200 focus:border-emerald-500 outline-none transition-all font-mono" + /> +
+
+
+ +
+ % + setConfig({ ...config, defaultOverhead: parseFloat(e.target.value) })} + className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all font-mono" + /> +
+
+
+
+
+ )} + + {activeTab === 'appearance' && ( +
+ +
+ )} + + {activeTab === 'fiscal' && ( +
+ {/* Certificate */} +
+

Certificado Digital (A1)

+ +
+ +

Clique para selecionar o Certificado .PFX

+

Sua senha será solicitada após o upload

+
+ +
+
+ + setConfig({ ...config, certificatePassword: e.target.value })} + className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all" + /> +
+
+ +
+ --/--/---- + +
+
+
+
+ + {/* NFe Config */} +
+

Configuração de Emissão (NFe 4.0)

+
+
+ + setConfig({ ...config, nfeNumber: e.target.value })} + className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all font-mono" + /> +
+
+ + setConfig({ ...config, nfeSerie: e.target.value })} + className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all font-mono" + /> +
+
+ + +
+
+
+
+ )} + + {activeTab === 'marketplaces' && ( +
+ {/* Major Marketplaces */} +
+

Hub de Integração (Mais de 30 canais)

+ +
+ {[ + { name: 'Mercado Livre', color: 'bg-yellow-500', status: 'connected', sales: 1240 }, + { name: 'Shopee', color: 'bg-orange-600', status: 'error', sales: 0 }, + { name: 'Amazon Seller', color: 'bg-slate-800', status: 'disconnected', sales: 0 }, + { name: 'Magalu', color: 'bg-blue-600', status: 'disconnected', sales: 0 }, + { name: 'Via Varejo', color: 'bg-green-600', status: 'disconnected', sales: 0 }, + { name: 'B2W (Americanas)', color: 'bg-red-600', status: 'disconnected', sales: 0 }, + { name: 'Shein', color: 'bg-black', status: 'disconnected', sales: 0 }, + { name: 'AliExpress', color: 'bg-red-500', status: 'disconnected', sales: 0 }, + ].map(mk => ( +
+
+
+
+ {mk.name.substring(0, 2)} +
+
+

{mk.name}

+ {mk.status === 'connected' &&

● Online

} + {mk.status === 'error' &&

● Erro

} + {mk.status === 'disconnected' &&

● Offline

} +
+
+ + {/* Toggle Switch Mockup */} +
+
+
+
+ +
+
+ {mk.status === 'connected' ? ( + <> +

Vendas Hoje

+

R$ {mk.sales.toLocaleString('pt-BR')}

+ + ) : ( +

Clique para configurar

+ )} +
+ +
+
+ ))} +
+
+ + {/* Custom Sites / E-commerce Platforms */} +
+

Lojas Virtuais & Sites Próprios

+
+ {[ + { name: 'WooCommerce', color: 'bg-purple-600' }, + { name: 'Shopify', color: 'bg-emerald-500' }, + { name: 'Nuvemshop', color: 'bg-blue-500' }, + { name: 'Vtex', color: 'bg-pink-600' }, + { name: 'API Personalizada', color: 'bg-slate-700', isCustom: true }, + ].map(platform => ( +
+
+
+ {platform.isCustom ? : platform.name.substring(0, 1)} +
+ {platform.name} +
+
+
+
+
+ ))} +
+

* Para API Personalizada, consulte a documentação /docs/api-v1

+
+ + {/* Automation Settings */} +
+
+

Regras de Sincronização

+
+
+
+

Importar Pedidos

+

Baixar vendas novas a cada 5 min.

+
+ setConfig({ ...config, autoSyncSales: !config.autoSyncSales })} className="accent-emerald-500 w-4 h-4" /> +
+
+
+

Atualizar Estoque

+

Enviar saldo local para os canais.

+
+ setConfig({ ...config, autoSyncStock: !config.autoSyncStock })} className="accent-emerald-500 w-4 h-4" /> +
+
+
+
+
+ )} + + {activeTab === 'logistics' && ( +
+
+

Hubs de Frete

+ +
+
+
+
+ + Melhor Envio +
+ Ativo +
+ setConfig({ ...config, melhorEnvioToken: e.target.value })} + placeholder="Token de Produção" + type="password" + className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-slate-400 text-xs focus:border-indigo-500 outline-none transition-all font-mono" + /> +
+
+
+
+ + Frenet +
+ Inativo +
+ +
+
+
+
+ )} +
+
+
+
+ ); +}; + +export default Settings; diff --git a/pages/Sourcing.tsx b/pages/Sourcing.tsx new file mode 100644 index 0000000..153e915 --- /dev/null +++ b/pages/Sourcing.tsx @@ -0,0 +1,234 @@ + +import React from 'react'; +import { Search, Package, Plus, Calculator, Trash2, Minus, ShoppingCart, CheckCircle2, Facebook, Tag, ToggleLeft, ToggleRight } from 'lucide-react'; +import { useCRM } from '../context/CRMContext'; +import MarketplaceAnalytic from '../components/MarketplaceAnalytic'; + +const Sourcing: React.FC = () => { + const { + searchTerm, setSearchTerm, handleSearch, handleOpportunitySearch, products, + selectedProduct, setSelectedProduct, addToShoppingList, + shoppingList, removeFromShoppingList, + updateShoppingItemQuantity, saveOrderAsQuotation, + calculateShoppingTotals, overheadPercent, + useOverhead, setUseOverhead, + searchLoading, searchError, searchType, setSearchType + } = useCRM(); + + const onSearchSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (searchType === 'specific') { + handleSearch(e); + } else { + handleOpportunitySearch(searchTerm); + } + }; + + const handleUpdateQuantity = (item: any, delta: number) => { + updateShoppingItemQuantity(item.id, delta); + }; + + const handleRemoveItem = (id: string) => { + removeFromShoppingList(id); + }; + + return ( +
+ {/* COLUNA 1: BUSCA */} +
+
+ {/* TABS */} +
+ + +
+ +
+ {searchType === 'specific' ? : } +

+ {searchType === 'specific' ? 'Sourcing Real-Time' : 'Caçador de Margem (>25%)'} +

+
+ +
+ setSearchTerm(e.target.value)} + placeholder={searchType === 'specific' ? "Ex: iPhone 16 Pro Max" : "Ex: Celulares, Drones, Games"} + className={`w-full bg-slate-900/50 border border-white/10 rounded-2xl py-4 pl-12 pr-4 text-sm font-semibold outline-none transition-all text-white placeholder-slate-600 shadow-inner ${searchType === 'specific' ? 'focus:border-indigo-500' : 'focus:border-emerald-500'}`} + /> + + + + {searchError && ( +
+
+

{searchError}

+
+ )} + +
+ {searchLoading ? ( +
+
+

Consultando Compras Paraguai...

+
+ ) : products.map((p, idx) => ( +
setSelectedProduct(p)} + className={`p-5 rounded-3xl border-2 cursor-pointer transition-all hover:scale-[1.02] active:scale-95 ${selectedProduct?.name === p.name + ? 'border-indigo-500 bg-indigo-500/10 shadow-md' + : 'border-white/5 bg-white/5 hover:border-white/10 hover:bg-white/10' + }`} + > +
+ {p.store} +
+

{p.name}

+
+
+

US$ {p.priceUSD.toFixed(2)}

+

R$ {p.priceBRL.toLocaleString('pt-BR')}

+
+
+ {p.salesVolume && ( + + {p.salesVolume} + + )} + +
+
+
+ ))} + {!searchLoading && products.length === 0 && ( +
+ +

Aguardando seu input

+
+ )} +
+
+
+ + {/* COLUNA 2: ANÁLISE DETALHADA */} +
+ {selectedProduct ? ( + + ) : ( +
+
+ +
+

Analítica de Arbitragem

+

Selecione um produto para ver a comparação de margem real entre os marketplaces do Brasil e sua venda direta no Facebook.

+
+ )} +
+ + {/* COLUNA 3: COTAÇÃO / CRM CHECKOUT */} +
+
+
+
+

Minha Cotação

+

Sourcing Ativo

+
+ {shoppingList.length} +
+ +
+ {shoppingList.length > 0 ? ( + shoppingList.map(item => ( +
+
+
+ {item.store} +

{item.name}

+
+ +
+
+

US$ {item.priceUSD.toFixed(2)}

+
+ + {item.quantity} + +
+
+
+ )) + ) : ( +
+ +

Carrinho Vazio

+
+ )} +
+ +
+
+
+ + + R$ {calculateShoppingTotals().totalCostWithOverhead.toLocaleString('pt-BR')} + +
+
+ Subtotal Paraguai + US$ {calculateShoppingTotals().totalUSD.toLocaleString('en-US')} +
+
+
+ Lucro Est. Final + +
+ R$ {calculateShoppingTotals().totalApproxProfit.toLocaleString('pt-BR')} +
+
+ +
+
+
+
+ ); +}; + +export default Sourcing; diff --git a/pages/Suppliers.tsx b/pages/Suppliers.tsx new file mode 100644 index 0000000..84e64c1 --- /dev/null +++ b/pages/Suppliers.tsx @@ -0,0 +1,143 @@ + +import React, { useState } from 'react'; +import { Store, Plus, Edit2, Trash2, Truck, Phone, Star } from 'lucide-react'; +import { useCRM } from '../context/CRMContext'; +import { Supplier } from '../types'; + +const Suppliers: React.FC = () => { + const { suppliers, addSupplier, updateSupplier, deleteSupplier } = useCRM(); + const [isModalOpen, setIsModalOpen] = useState(false); + const [editingId, setEditingId] = useState(null); + const [formData, setFormData] = useState>({ + name: '', contact: '', rating: 5 + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (editingId) { + updateSupplier(editingId, formData); + } else { + addSupplier(formData as any); + } + closeModal(); + }; + + const openModal = (supplier?: Supplier) => { + if (supplier) { + setEditingId(supplier.id); + setFormData(supplier); + } else { + setEditingId(null); + setFormData({ name: '', contact: '', rating: 5 }); + } + setIsModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + setEditingId(null); + }; + + return ( +
+ {/* HEADER */} +
+
+

Parceiros Logísticos

+

Gestão de Transporte e Fornecedores

+
+ +
+ + {/* LIST */} +
+ {suppliers.map(s => ( +
+ + {/* Watermark Icon */} +
+ +
+ +
+
+
+ +
+
+

{s.name}

+

Parceiro

+
+
+
+ {[1, 2, 3, 4, 5].map(star => ( +
+ ))} +
+
+ +
+ {s.contact && ( +
+ + {s.contact} +
+ )} +
+ +
+ + +
+
+ ))} +
+ + {/* MODAL */} + {isModalOpen && ( +
+
+

{editingId ? 'Editar Parceiro' : 'Novo Parceiro'}

+
+
+ + setFormData({ ...formData, name: e.target.value })} className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:border-indigo-500 outline-none transition-all" placeholder="Ex: João Freteiro" /> +
+
+ + setFormData({ ...formData, contact: e.target.value })} className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:border-indigo-500 outline-none transition-all" placeholder="+55 45 9..." /> +
+
+ +
+ {[1, 2, 3, 4, 5].map(star => ( + + ))} +
+
+ +
+ + +
+
+
+
+ )} +
+ ); +}; + +export default Suppliers; diff --git a/pages/Users.tsx b/pages/Users.tsx new file mode 100644 index 0000000..29d79d9 --- /dev/null +++ b/pages/Users.tsx @@ -0,0 +1,167 @@ +import React, { useState } from 'react'; +import { useCRM } from '../context/CRMContext'; +import { Shield, ShieldAlert, User as UserIcon, Check, X, MoreVertical, Edit2 } from 'lucide-react'; +import { User } from '../types'; + +const Users: React.FC = () => { + const { users, isAdmin, updateUserRole } = useCRM(); + const [searchTerm, setSearchTerm] = useState(''); + const [showRoleModal, setShowRoleModal] = useState(false); + const [selectedUser, setSelectedUser] = useState(null); + const [newRole, setNewRole] = useState<'admin' | 'user'>('user'); + + // Filter users + const filteredUsers = users.filter(user => + (user.name?.toLowerCase().includes(searchTerm.toLowerCase()) || + user.email.toLowerCase().includes(searchTerm.toLowerCase())) + ); + + const handleRoleUpdateClick = (user: User) => { + setSelectedUser(user); + setNewRole(user.role === 'admin' ? 'user' : 'admin'); + setShowRoleModal(true); + }; + + const confirmRoleUpdate = async () => { + if (selectedUser) { + await updateUserRole(selectedUser.id, newRole); + setShowRoleModal(false); + setSelectedUser(null); + } + }; + + if (!isAdmin) { + return ( +
+
+ +
+

Acesso Restrito

+

+ Você precisa de privilégios de administrador para gerenciar usuários e permissões do sistema. +

+
+ ); + } + + return ( +
+
+
+

Gerenciamento de Usuários

+

Controle total sobre acesso e permissões da equipe.

+
+
+ setSearchTerm(e.target.value)} + className="w-full md:w-80 bg-black/20 border border-white/10 rounded-xl px-4 py-3 text-sm text-slate-300 focus:border-indigo-500 outline-none transition-all placeholder:text-slate-600" + /> +
+
+ +
+
+ + + + + + + + + + + + + {filteredUsers.length > 0 ? filteredUsers.map(user => ( + + + + + + + + + )) : ( + + + + )} + +
UsuárioEmailStatusPermissãoÚltimo AcessoAções
+
+ {user.avatar ? ( + {user.name} + ) : ( +
+ +
+ )} +
+
{user.name || 'Sem Nome'}
+
ID: {user.id.substring(0, 8)}
+
+
+
{user.email} + + {user.status || 'Active'} + + + + {user.role === 'admin' && } + {user.role || 'User'} + + + {user.lastAccess ? new Date(user.lastAccess).toLocaleDateString() : '-'} + + +
+ +

Nenhum usuário encontrado

+
+
+
+ + {/* Role Update Modal */} + {showRoleModal && selectedUser && ( +
+
+
+ +

Alterar Permissão

+

+ Você tem certeza que deseja alterar o nível de acesso de {selectedUser.name} para {newRole}? +

+ +
+ + +
+
+
+ )} +
+ ); +}; + +export default Users; diff --git a/paraguai.rar b/paraguai.rar new file mode 100644 index 0000000..24f722f Binary files /dev/null and b/paraguai.rar differ diff --git a/policies.sql b/policies.sql new file mode 100644 index 0000000..8b32362 --- /dev/null +++ b/policies.sql @@ -0,0 +1,26 @@ +-- 1. Ensure RLS is enabled (consistency) +alter table public.inventory enable row level security; +alter table public.suppliers enable row level security; +alter table public.customers enable row level security; +alter table public.orders enable row level security; +alter table public.transactions enable row level security; +alter table public.settings enable row level security; + +-- 2. Drop existing policies to avoid conflicts if re-run +drop policy if exists "Enable all for authenticated users" on public.inventory; +drop policy if exists "Enable all for authenticated users" on public.suppliers; +drop policy if exists "Enable all for authenticated users" on public.customers; +drop policy if exists "Enable all for authenticated users" on public.orders; +drop policy if exists "Enable all for authenticated users" on public.transactions; +drop policy if exists "Enable all for authenticated users" on public.settings; + +-- 3. Create Permissive Policies for Authenticated Users +-- This allows any logged-in user to Select, Insert, Update, Delete ONLY if they are authenticated. +-- In a SaaS, you would restrict 'using (user_id = auth.uid())', but for this internal tool, we allow all authenticated access. + +create policy "Enable all for authenticated users" on public.inventory for all to authenticated using (true) with check (true); +create policy "Enable all for authenticated users" on public.suppliers for all to authenticated using (true) with check (true); +create policy "Enable all for authenticated users" on public.customers for all to authenticated using (true) with check (true); +create policy "Enable all for authenticated users" on public.orders for all to authenticated using (true) with check (true); +create policy "Enable all for authenticated users" on public.transactions for all to authenticated using (true) with check (true); +create policy "Enable all for authenticated users" on public.settings for all to authenticated using (true) with check (true); diff --git a/services/geminiService.ts b/services/geminiService.ts new file mode 100644 index 0000000..424e246 --- /dev/null +++ b/services/geminiService.ts @@ -0,0 +1,314 @@ +import { GoogleGenAI, Type } from "@google/genai"; +import { Product, AuctionLot, BiddingTender } from "../types"; + +// Vite: variáveis precisam começar com VITE_ +const API_KEY = import.meta.env.VITE_GOOGLE_API_KEY as string | undefined; + +if (!API_KEY) { + // Erro explícito pra não “falhar silencioso” + throw new Error( + "Missing VITE_GOOGLE_API_KEY. Add it to your .env.local (VITE_GOOGLE_API_KEY=...) and restart the dev server." + ); +} + +const ai = new GoogleGenAI({ apiKey: API_KEY }); + +/** + * Helper: converte File em Part (inlineData) para o Gemini + */ +async function fileToPart(file: File) { + return new Promise<{ inlineData: { data: string; mimeType: string } }>((resolve, reject) => { + const reader = new FileReader(); + reader.onerror = () => reject(new Error("Failed to read file")); + reader.onloadend = () => { + const result = reader.result as string | null; + if (!result) return reject(new Error("Empty file result")); + const base64Data = result.split(",")[1] || ""; + resolve({ + inlineData: { + data: base64Data, + mimeType: file.type || "application/octet-stream", + }, + }); + }; + reader.readAsDataURL(file); + }); +} + +/** + * Sourcing Otimizado: Foco no MENOR PREÇO de compra (PY) + * e MENOR PREÇO de venda competitiva (BR - "Mais Vendidos") + */ +export async function searchProducts( + query: string +): Promise<{ products: Product[]; sources: any[] }> { + // try { // Removed to propagate errors + const prompt = `OBJETIVO: Análise de Arbitragem Profissional. + +1. BUSCA REAL OBRIGATÓRIA: +Você DEVE usar a tool googleSearch para buscar dados REAIS e ATUAIS no site www.comprasparaguai.com.br. +Busque os 20 itens com o MENOR PREÇO ABSOLUTO para "${query}". +Foque em lojas de grande renome (Nissei, Cellshop, Atacado Games, Mega Eletronicos). + +2. BUSCA BRASIL (REFERÊNCIA): +Para cada item, encontre o MENOR PREÇO de venda no Mercado Livre e Amazon Brasil. + +3. REGRAS CRÍTICAS (ANTI-ALUCINAÇÃO): +- JAMAIS invente produtos ou preços. +- JAMAIS retorne itens com "(Exemplo)", "(Example)" ou dados fictícios. +- Se não encontrar dados REAIS, retorne lista vazia. +- Use a cotação de dólar turismo/paralelo atual (~5.75 BRL/USD). + +4. RETORNO: +Apenas JSON conforme o schema.`; + + const response = await ai.models.generateContent({ + model: "gemini-3-pro-preview", // Stable fast model + contents: prompt, + config: { + tools: [{ googleSearch: {} }], + responseMimeType: "application/json", + responseSchema: { + type: Type.ARRAY, + items: { + type: Type.OBJECT, + properties: { + name: { type: Type.STRING, description: "Nome técnico do produto" }, + priceUSD: { type: Type.NUMBER, description: "Menor preço em USD no Paraguai" }, + priceBRL: { type: Type.NUMBER, description: "Preço USD convertido para BRL" }, + store: { type: Type.STRING, description: "Loja do Paraguai (Nissei, Cellshop, etc)" }, + url: { type: Type.STRING, description: "Link da oferta no Paraguai" }, + marketPriceBRL: { + type: Type.NUMBER, + description: "Menor preço encontrado entre os MAIS VENDIDOS no Brasil", + }, + amazonPrice: { type: Type.NUMBER, description: "Menor preço Amazon (Best Seller)" }, + amazonUrl: { type: Type.STRING }, + mlPrice: { type: Type.NUMBER, description: "Menor preço Mercado Livre (Mais Vendido)" }, + mlUrl: { type: Type.STRING }, + }, + required: ["name", "priceUSD", "priceBRL", "store", "url", "marketPriceBRL"], + }, + }, + }, + }); + + const text = response.text || "[]"; + const products = JSON.parse(text); + const sources = response.candidates?.[0]?.groundingMetadata?.groundingChunks || []; + + // Ordena para mostrar primeiro as melhores oportunidades (menor custo em BRL) + const sortedProducts = Array.isArray(products) + ? products.sort((a: any, b: any) => (a.priceBRL ?? 0) - (b.priceBRL ?? 0)) + : []; + + return { products: sortedProducts as Product[], sources }; + // } catch (error) { + // console.error("Erro na busca de arbitrage:", error); + // return { products: [], sources: [] }; + // } +} + +/** + * Busca Oportunidades (>25% Margem) por Categoria + */ +export async function searchOpportunities(category: string, includeOverhead: boolean = true): Promise<{ products: Product[]; sources: any[] }> { + // try { + const prompt = `OBJETIVO: Encontrar Oportunidades de Arbitragem (>25% Lucro) na categoria "${category}". + +1. BUSCA PARAGUAI: +Vasculhe as principais lojas do Paraguai (Nissei, Cellshop, Mega, Atacado Games) em busca de produtos da categoria "${category}" que estejam com preços muito atrativos ou em oferta. +Identifique pelo menos 20 produtos promissores. + +2. CÁLCULO DE MARGEM: +Para cada produto, estime o custo total em BRL (Preço PY * 5.75 * ${includeOverhead ? '1.20' : '1.00'} de taxas). +Compare com o MENOR preço de venda real no Brasil (Mercado Livre/Amazon - Filtro "Mais Vendidos"). +Margem = (Preço Venda BR - Custo Total) / Preço Venda BR. + +3. FILTRO RIGOROSO: +Retorne APENAS produtos onde a Margem estimada seja SUPERIOR a 20-25%. +Se não encontrar nada com 25%, mostre os que tiverem a maior margem possível. + +4. RETORNO: +Apenas JSON conforme o schema.`; + + const response = await ai.models.generateContent({ + model: "gemini-3-pro-preview", + contents: prompt, + config: { + tools: [{ googleSearch: {} }], + responseMimeType: "application/json", + responseSchema: { + type: Type.ARRAY, + items: { + type: Type.OBJECT, + properties: { + name: { type: Type.STRING, description: "Nome técnico do produto" }, + priceUSD: { type: Type.NUMBER, description: "Menor preço em USD no Paraguai" }, + priceBRL: { type: Type.NUMBER, description: "Preço USD convertido para BRL" }, + store: { type: Type.STRING, description: "Loja do Paraguai (Nissei, Cellshop, etc)" }, + url: { type: Type.STRING, description: "Link da oferta no Paraguai" }, + marketPriceBRL: { + type: Type.NUMBER, + description: "Menor preço encontrado entre os MAIS VENDIDOS no Brasil", + }, + amazonPrice: { type: Type.NUMBER, description: "Menor preço Amazon (Best Seller)" }, + amazonUrl: { type: Type.STRING }, + mlPrice: { type: Type.NUMBER, description: "Menor preço Mercado Livre (Mais Vendido)" }, + mlUrl: { type: Type.STRING }, + }, + required: ["name", "priceUSD", "priceBRL", "store", "url", "marketPriceBRL"], + }, + }, + }, + }); + + const text = response.text || "[]"; + const products = JSON.parse(text); + const sources = response.candidates?.[0]?.groundingMetadata?.groundingChunks || []; + + // Ordena por maior margem (diferença entre venda BR e custo PY) + const sortedProducts = Array.isArray(products) + ? products.sort((a: any, b: any) => { + const marginA = (a.marketPriceBRL - a.priceBRL) / a.marketPriceBRL; + const marginB = (b.marketPriceBRL - b.priceBRL) / b.marketPriceBRL; + return marginB - marginA; // Maior margem primeiro + }) + : []; + + return { products: sortedProducts as Product[], sources }; + // } catch (error) { + // console.error("Erro na busca de oportunidades:", error); + // return { products: [], sources: [] }; + // } +} + +/** + * Leilões: Analisa texto, link ou ARQUIVO PDF + */ +export async function analyzeAuctionData( + input: string | File +): Promise<{ lot: AuctionLot | null; sources: any[] }> { + try { + const promptPrefix = "Analise este lote de leilão da Receita Federal."; + + const parts: any[] = []; + + if (input instanceof File) { + const filePart = await fileToPart(input); + parts.push(filePart); + parts.push({ + text: `${promptPrefix} +Leia o documento PDF anexo e extraia TODOS os itens da tabela de mercadorias. + +REGRAS: +1. Identifique os itens reais. +2. Descubra o MENOR valor real de mercado no Brasil (baseado nos itens mais vendidos) via Google Search. +3. Use URLs REAIS dos sites de busca.`, + }); + } else { + parts.push({ + text: `${promptPrefix} +Analise este conteúdo: "${input}". + +REGRAS: +1. Identifique os itens reais. +2. Descubra o MENOR valor real de mercado no Brasil (baseado nos itens mais vendidos) via Google Search. +3. Use URLs REAIS dos sites de busca.`, + }); + } + + const analysisResponse = await ai.models.generateContent({ + model: "gemini-3-pro-preview", + contents: { parts }, // padronizado + config: { tools: [{ googleSearch: {} }] }, + }); + + const analysisText = analysisResponse.text || ""; + const sources = analysisResponse.candidates?.[0]?.groundingMetadata?.groundingChunks || []; + + // Segunda passada: força JSON limpo + const extraction = await ai.models.generateContent({ + model: "gemini-3-pro-preview", + contents: `Transforme em JSON conforme o schema. Texto base: ${analysisText}`, + config: { + responseMimeType: "application/json", + responseSchema: { + type: Type.OBJECT, + properties: { + id: { type: Type.STRING }, + title: { type: Type.STRING }, + location: { type: Type.STRING }, + items: { type: Type.ARRAY, items: { type: Type.STRING } }, + currentBid: { type: Type.NUMBER }, + minBid: { type: Type.NUMBER }, + mlMarketPrice: { type: Type.NUMBER }, + }, + required: ["id", "title", "items", "minBid", "mlMarketPrice"], + }, + }, + }); + + const lotData = JSON.parse(extraction.text || "{}"); + + return { + lot: { + ...lotData, + status: "Analyzing" as any, + url: typeof input === "string" && input.startsWith("http") ? input : "", + }, + sources, + }; + } catch (error) { + console.error("Erro ao analisar leilão:", error); + return { lot: null, sources: [] }; + } +} + +/** + * Licitações Otimizadas + */ +export async function searchBiddings( + query: string +): Promise<{ tenders: BiddingTender[]; sources: any[] }> { + try { + const prompt = `Pesquise licitações ativas para "${query}" no Brasil. +Priorize PNCP e Compras.gov.br. +Retorne apenas JSON conforme o schema.`; + + const searchResponse = await ai.models.generateContent({ + model: "gemini-3-pro-preview", + contents: prompt, + config: { + tools: [{ googleSearch: {} }], + responseMimeType: "application/json", + responseSchema: { + type: Type.ARRAY, + items: { + type: Type.OBJECT, + properties: { + id: { type: Type.STRING }, + org: { type: Type.STRING }, + object: { type: Type.STRING }, + estimatedValue: { type: Type.NUMBER }, + deadline: { type: Type.STRING }, + items: { type: Type.ARRAY, items: { type: Type.STRING } }, + requirements: { type: Type.ARRAY, items: { type: Type.STRING } }, + marketReferencePrice: { type: Type.NUMBER }, + url: { type: Type.STRING }, + }, + required: ["id", "org", "object", "url"], + }, + }, + }, + }); + + const tenders = JSON.parse(searchResponse.text || "[]"); + const sources = searchResponse.candidates?.[0]?.groundingMetadata?.groundingChunks || []; + + return { tenders: (Array.isArray(tenders) ? tenders : []) as BiddingTender[], sources }; + } catch (error) { + console.error("Erro na busca de licitações:", error); + return { tenders: [], sources: [] }; + } +} diff --git a/services/supabase.ts b/services/supabase.ts new file mode 100644 index 0000000..4e5714b --- /dev/null +++ b/services/supabase.ts @@ -0,0 +1,11 @@ + +import { createClient } from '@supabase/supabase-js'; + +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; +const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; + +if (!supabaseUrl || !supabaseAnonKey) { + console.error('Missing Supabase environment variables'); +} + +export const supabase = createClient(supabaseUrl, supabaseAnonKey); diff --git a/supabase_schema.sql b/supabase_schema.sql new file mode 100644 index 0000000..d0c8073 --- /dev/null +++ b/supabase_schema.sql @@ -0,0 +1,126 @@ +-- Enable UUID extension +create extension if not exists "uuid-ossp"; + +-- 1. USERS & PROFILES (Managed by Supabase Auth, but we can have a public profile) +create table if not exists public.profiles ( + id uuid references auth.users not null primary key, + email text, + full_name text, + role text default 'Buyer', + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +-- 2. INVENTORY (Products) +create table if not exists public.inventory ( + id uuid default uuid_generate_v4() primary key, + created_at timestamp with time zone default timezone('utc'::text, now()) not null, + name text not null, + sku text, + ean text, + quantity integer default 0, + avg_cost_brl numeric default 0, + market_price_brl numeric default 0, + last_supplier text, + user_id uuid references auth.users +); + +-- 3. SUPPLIERS +create table if not exists public.suppliers ( + id uuid default uuid_generate_v4() primary key, + created_at timestamp with time zone default timezone('utc'::text, now()) not null, + name text not null, + contact text, + rating integer default 0, + last_purchase timestamp with time zone, + user_id uuid references auth.users +); + +-- 4. CUSTOMERS +create table if not exists public.customers ( + id uuid default uuid_generate_v4() primary key, + created_at timestamp with time zone default timezone('utc'::text, now()) not null, + name text not null, + email text, + phone text, + city text, + status text default 'Active', -- 'Active', 'Inactive', 'Prospect' + total_purchased numeric default 0, + user_id uuid references auth.users +); + +-- 5. ORDERS +create table if not exists public.orders ( + id uuid default uuid_generate_v4() primary key, + created_at timestamp with time zone default timezone('utc'::text, now()) not null, + date timestamp with time zone, + status text default 'Pending', -- 'Pending', 'Paid', 'Received', 'Cancelled' + total_usd numeric default 0, + total_brl numeric default 0, + total_cost_with_overhead numeric default 0, + estimated_profit numeric default 0, + items jsonb, -- Stores the JSON array of items + supplier_name text, + user_id uuid references auth.users +); + +-- 6. TRANSACTIONS (Financial) +create table if not exists public.transactions ( + id uuid default uuid_generate_v4() primary key, + created_at timestamp with time zone default timezone('utc'::text, now()) not null, + date timestamp with time zone, + type text, -- 'Income', 'Expense' + category text, + amount numeric default 0, + description text, + user_id uuid references auth.users +); + +-- 7. SETTINGS (Global Config) +create table if not exists public.settings ( + id uuid default uuid_generate_v4() primary key, + created_at timestamp with time zone default timezone('utc'::text, now()) not null, + updated_at timestamp with time zone default timezone('utc'::text, now()), + + -- Company Info + company_name text, + cnpj text, + ie text, + + -- Financial Defaults + default_overhead numeric default 20, + default_exchange numeric default 5.65, + + -- Integration Tokens + brazil_api_token text, + melhor_envio_token text, + bling_token text, + tiny_token text, + gemini_key text, + + -- Fiscal / NFe + certificate_password text, -- CAUTION: Storing plain text/base64? Ideally encrypted. + nfe_serie text default '1', + nfe_number text, + nfe_environment text default 'homologacao', + + -- Email SMTP + smtp_host text, + smtp_port text, + smtp_user text, + smtp_pass text, + + -- Automation + auto_sync_sales boolean default true, + auto_sync_stock boolean default true, + + user_id uuid references auth.users +); + +-- RLS POLICIES (Optional but recommended) +alter table public.inventory enable row level security; +alter table public.suppliers enable row level security; +alter table public.customers enable row level security; +alter table public.orders enable row level security; +alter table public.transactions enable row level security; +alter table public.settings enable row level security; +-- (Policies would need to be added to allow read/write for authenticated users) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2c6eed5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2022", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "types": [ + "node" + ], + "moduleResolution": "bundler", + "isolatedModules": true, + "moduleDetection": "force", + "allowJs": true, + "jsx": "react-jsx", + "paths": { + "@/*": [ + "./*" + ] + }, + "allowImportingTsExtensions": true, + "noEmit": true + } +} \ No newline at end of file diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..6273b72 --- /dev/null +++ b/types.ts @@ -0,0 +1,167 @@ + +export interface Product { + name: string; + priceUSD: number; + priceBRL: number; + store: string; + url: string; + marketPriceBRL?: number; + salesVolume?: string; // New: e.g. "10k+ vendidos" + rating?: number; // New: 0-5 + amazonPrice?: number; + amazonUrl?: string; + mlPrice?: number; + mlUrl?: string; + shopeePrice?: number; + shopeeUrl?: string; +} + +export interface ShoppingListItem { + id: string; + name: string; + store: string; + priceUSD: number; + priceBRL: number; + quantity: number; + marketPriceBRL: number; +} + +export type OrderStatus = 'Quotation' | 'Pending' | 'Paid' | 'Received' | 'Cancelled'; +export type SalesChannel = 'Local' | 'Mercado Livre' | 'Shopee' | 'Amazon' | 'Facebook'; + +export interface Sale { + id: string; + date: string; + customerId?: string; // Optional linkage + customerName?: string; // Flattened for easier display + items: { id: string; name: string; quantity: number; salePrice: number; costPrice?: number }[]; + total: number; + status: 'Pending' | 'Paid' | 'Shipped' | 'Completed' | 'Cancelled' | 'Returned'; + channel: SalesChannel; + externalId?: string; // Mercado Livre ID, etc. + isStockLaunched: boolean; + isFinancialLaunched: boolean; +} + +export interface Order { + id: string; + date: string; + items: ShoppingListItem[]; + totalUSD: number; + totalBRL: number; + totalCostWithOverhead: number; + estimatedProfit: number; + status: OrderStatus; + supplierName: string; +} + +export interface InventoryItem { + id: string; + name: string; + sku: string; + ean?: string; + quantity: number; + avgCostBRL: number; + marketPriceBRL: number; + lastSupplier: string; +} + +export interface Supplier { + id: string; + name: string; + contact?: string; + rating: number; + lastPurchase?: string; +} + +export interface User { + id: string; + name?: string; + email: string; + phone?: string; + role?: 'admin' | 'user'; // New + status: 'Active' | 'Inactive'; + lastAccess?: string; + avatar: string; +} + +export interface PlatformFee { + name: string; + commission: number; + fixedFee: number; +} + +export interface CalculationResult { + platform: string; + marketPrice: number; + totalCost: number; + fees: number; + netProfit: number; + margin: number; + url?: string; +} + +export const PLATFORMS: PlatformFee[] = [ + { name: 'Amazon', commission: 0.15, fixedFee: 0 }, + { name: 'Mercado Livre', commission: 0.18, fixedFee: 6 }, + { name: 'Shopee', commission: 0.20, fixedFee: 3 }, + { name: 'Facebook', commission: 0.0, fixedFee: 0 }, +]; + +export interface Customer { + id: string; + name: string; + email?: string; + phone?: string; + city?: string; + status: 'Active' | 'Inactive' | 'Prospect'; + totalPurchased: number; +} + +export type TransactionType = 'Income' | 'Expense'; + +export interface Transaction { + id: string; + date: string; + type: TransactionType; + category: string; // Hotel, Combustível, Alimentação, Venda, etc. + amount: number; + description: string; + status: 'Pending' | 'Paid' | 'Overdue' | 'Cancelled'; + paymentMethod: PaymentMethod; + dueDate?: string; + relatedSaleId?: string; +} + +export type PaymentMethod = 'Pix' | 'Credit Card' | 'Debit Card' | 'Cash' | 'Boleto' | 'Transfer' | 'Other'; + +export interface FinancialSummary { + totalIncome: number; + totalExpense: number; + balance: number; + recentTransactions: Transaction[]; +} + +export interface AuctionLot { + id: string; + title: string; + location: string; + items: string[]; + currentBid: number; + minBid: number; + mlMarketPrice: number; + status: 'Analyzing' | 'Open' | 'Closed'; + url: string; +} + +export interface BiddingTender { + id: string; + org: string; + object: string; + estimatedValue: number; + deadline: string; + items: string[]; + requirements: string[]; + marketReferencePrice: number; + url: string; +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..e3611db --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,23 @@ +import path from 'path'; +import { defineConfig, loadEnv } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, '.', ''); + return { + server: { + port: 3000, + host: '0.0.0.0', + }, + plugins: [react()], + define: { + 'process.env.API_KEY': JSON.stringify(env.API_KEY || env.VITE_GOOGLE_API_KEY || env.GEMINI_API_KEY), + 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '.'), + } + } + }; +});