diff --git a/src/App.tsx b/src/App.tsx index 2febfc2..ef02433 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -39,15 +39,13 @@ import Security from "./pages/dashboard/Security"; import VehicleManagement from "./pages/dashboard/VehicleManagement"; import Sustainability from "./pages/dashboard/Sustainability"; import Establishments from "./pages/dashboard/Establishments"; -// Commerce pages -import CommerceEstablishments from "./pages/dashboard/commerce/Establishments"; +// Commerce pages (for retail stores) +import CommerceStore from "./pages/dashboard/commerce/Store"; import CommercePOS from "./pages/dashboard/commerce/POSTerminal"; -import CommerceOrders from "./pages/dashboard/commerce/Orders"; -import CommerceMenu from "./pages/dashboard/commerce/Menu"; -import CommerceHotel from "./pages/dashboard/commerce/Hotel"; -import CommerceReservations from "./pages/dashboard/commerce/Reservations"; +import CommerceCustomers from "./pages/dashboard/commerce/Customers"; import CommerceInventory from "./pages/dashboard/commerce/Inventory"; import CommerceStaff from "./pages/dashboard/commerce/Staff"; +import CommerceCashier from "./pages/dashboard/commerce/Cashier"; import CommerceReports from "./pages/dashboard/commerce/Reports"; // Hotel pages import HotelRooms from "./pages/dashboard/hotel/Rooms"; @@ -304,10 +302,10 @@ const AppRouter = () => ( } /> {/* Commerce Routes */} - - + } /> @@ -320,34 +318,18 @@ const AppRouter = () => ( } /> - - + } /> - - - - - } /> - - - - - - - } /> - - - - + } /> @@ -384,13 +366,6 @@ const AppRouter = () => ( } /> - - - - - - } /> diff --git a/src/components/DashboardLayout.tsx b/src/components/DashboardLayout.tsx index 99f1c48..73998d6 100644 --- a/src/components/DashboardLayout.tsx +++ b/src/components/DashboardLayout.tsx @@ -165,15 +165,12 @@ const DashboardLayout = ({ children }: { children: React.ReactNode }) => { label: 'Comercios', path: '/dashboard/commerce', subItems: [ - { icon: Store, label: 'Establecimientos', path: '/dashboard/commerce/establishments' }, - { icon: CreditCard, label: 'POS Terminal', path: '/dashboard/commerce/pos' }, - { icon: Receipt, label: 'Pedidos', path: '/dashboard/commerce/orders' }, - { icon: UtensilsCrossed, label: 'Menú', path: '/dashboard/commerce/menu' }, - { icon: Grid3x3, label: 'Mesas', path: '/dashboard/commerce/tables' }, - { icon: Hotel, label: 'Hotel', path: '/dashboard/commerce/hotel' }, - { icon: BookOpen, label: 'Reservaciones', path: '/dashboard/commerce/reservations' }, + { icon: Store, label: 'Mi Comercio', path: '/dashboard/commerce/store' }, + { icon: CreditCard, label: 'POS Ventas', path: '/dashboard/commerce/pos' }, { icon: Package, label: 'Inventario', path: '/dashboard/commerce/inventory' }, + { icon: Users, label: 'Clientes', path: '/dashboard/commerce/customers' }, { icon: Users, label: 'Personal', path: '/dashboard/commerce/staff' }, + { icon: Receipt, label: 'Caja', path: '/dashboard/commerce/cashier' }, { icon: BarChart3, label: 'Reportes', path: '/dashboard/commerce/reports' }, { icon: DollarSign, label: 'Ventas', path: '/dashboard/commerce/sales' } ] diff --git a/src/pages/dashboard/commerce/Cashier.tsx b/src/pages/dashboard/commerce/Cashier.tsx new file mode 100644 index 0000000..7a505a0 --- /dev/null +++ b/src/pages/dashboard/commerce/Cashier.tsx @@ -0,0 +1,172 @@ +import React, { useState } from 'react'; +import { Receipt, DollarSign, TrendingUp, TrendingDown, Clock } from 'lucide-react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Badge } from '@/components/ui/badge'; +import { useToast } from '@/hooks/use-toast'; + +const Cashier = () => { + const { toast } = useToast(); + const [cashierData] = useState({ + openingBalance: 1000.00, + currentBalance: 2450.50, + totalSales: 1450.50, + totalExpenses: 0, + transactionsCount: 25, + lastTransaction: new Date().toLocaleTimeString() + }); + + const [newTransaction, setNewTransaction] = useState({ + type: 'sale', + amount: 0, + description: '' + }); + + const handleOpenCashier = () => { + toast({ title: 'Caja Abierta', description: 'Caja abierta con saldo inicial' }); + }; + + const handleCloseCashier = () => { + toast({ title: 'Caja Cerrada', description: 'Caja cerrada. Generando reporte...' }); + }; + + const handleAddTransaction = () => { + if (newTransaction.amount <= 0) { + toast({ title: 'Error', description: 'Ingresa un monto válido', variant: 'destructive' }); + return; + } + toast({ title: 'Éxito', description: 'Transacción registrada' }); + setNewTransaction({ type: 'sale', amount: 0, description: '' }); + }; + + return ( +
+
+
+
+ +
+

Control de Caja

+

Gestiona el flujo de efectivo de tu tienda

+
+
+
+ + +
+
+ +
+ + + Saldo Inicial + + + +
${cashierData.openingBalance.toFixed(2)}
+
+
+ + + + Saldo Actual + + + +
${cashierData.currentBalance.toFixed(2)}
+
+
+ + + + Ventas del Día + + + +
${cashierData.totalSales.toFixed(2)}
+

{cashierData.transactionsCount} transacciones

+
+
+ + + + Gastos + + + +
${cashierData.totalExpenses.toFixed(2)}
+
+
+
+ +
+ {/* Quick Transaction */} + + + Registrar Transacción + + +
+
+ +
+ + +
+
+
+ + setNewTransaction({ ...newTransaction, amount: parseFloat(e.target.value) })} + placeholder="0.00" + /> +
+
+ + setNewTransaction({ ...newTransaction, description: e.target.value })} + placeholder="Detalle de la transacción" + /> +
+ +
+
+
+ + {/* Recent Transactions */} + + + Transacciones Recientes + + +
+
+ No hay transacciones registradas hoy +
+
+
+
+
+
+
+ ); +}; + +export default Cashier; diff --git a/src/pages/dashboard/commerce/Customers.tsx b/src/pages/dashboard/commerce/Customers.tsx new file mode 100644 index 0000000..05d079d --- /dev/null +++ b/src/pages/dashboard/commerce/Customers.tsx @@ -0,0 +1,173 @@ +import React, { useState } from 'react'; +import { Users, Plus, Edit, Trash2, Mail, Phone, MapPin, DollarSign } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Badge } from '@/components/ui/badge'; +import { useToast } from '@/hooks/use-toast'; + +const Customers = () => { + const [customers, setCustomers] = useState([]); + const { toast } = useToast(); + + const [formData, setFormData] = useState({ + firstName: '', + lastName: '', + email: '', + phone: '', + address: '', + notes: '' + }); + + const handleSubmit = async () => { + try { + toast({ title: 'Éxito', description: 'Cliente agregado correctamente' }); + setFormData({ + firstName: '', + lastName: '', + email: '', + phone: '', + address: '', + notes: '' + }); + } catch (error: any) { + toast({ title: 'Error', description: error?.message || 'No se pudo agregar el cliente', variant: 'destructive' }); + } + }; + + return ( +
+
+
+ +
+

Gestión de Clientes

+

Administra tu base de clientes

+
+
+ +
+ + + + + + + + Agregar Cliente + +
+
+
+ + setFormData({ ...formData, firstName: e.target.value })} + placeholder="Nombre" + /> +
+
+ + setFormData({ ...formData, lastName: e.target.value })} + placeholder="Apellido" + /> +
+
+
+ + setFormData({ ...formData, email: e.target.value })} + placeholder="email@ejemplo.com" + /> +
+
+ + setFormData({ ...formData, phone: e.target.value })} + placeholder="+1 809 123 4567" + /> +
+
+ + setFormData({ ...formData, address: e.target.value })} + placeholder="Dirección del cliente" + /> +
+ +
+
+
+
+ +
+ {customers.length === 0 ? ( +
+ No hay clientes registrados. Agrega tu primer cliente. +
+ ) : ( + customers.map((customer) => ( + + +
+ {customer.firstName} {customer.lastName} +

Cliente desde {customer.createdAt}

+
+
+ + +
+
+ +
+
+ + {customer.email} +
+
+ + {customer.phone} +
+ {customer.address && ( +
+ + {customer.address} +
+ )} +
+ Total compras: +
+ + {customer.totalPurchases || 0} +
+
+
+
+
+ )) + )} +
+
+
+ ); +}; + +export default Customers; diff --git a/src/pages/dashboard/commerce/POSTerminal.tsx b/src/pages/dashboard/commerce/POSTerminal.tsx index 053a917..d0eebfe 100644 --- a/src/pages/dashboard/commerce/POSTerminal.tsx +++ b/src/pages/dashboard/commerce/POSTerminal.tsx @@ -1,46 +1,29 @@ import React, { useState, useEffect } from 'react'; -import { CreditCard, Plus, Minus, Trash2, DollarSign } from 'lucide-react'; +import { CreditCard, Plus, Minus, Trash2, DollarSign, Package, Barcode } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Badge } from '@/components/ui/badge'; import { useToast } from '@/hooks/use-toast'; import { apiClient } from '@/services/adminApi'; const POSTerminal = () => { - const [establishments, setEstablishments] = useState([]); - const [selectedEstablishment, setSelectedEstablishment] = useState(''); - const [menuItems, setMenuItems] = useState([]); + const [products, setProducts] = useState([]); const [cart, setCart] = useState([]); - const [tableNumber, setTableNumber] = useState(''); + const [searchTerm, setSearchTerm] = useState(''); const { toast } = useToast(); useEffect(() => { - loadEstablishments(); + loadProducts(); }, []); - useEffect(() => { - if (selectedEstablishment) { - loadMenu(); - } - }, [selectedEstablishment]); - - const loadEstablishments = async () => { + const loadProducts = async () => { try { - const response = await apiClient.get('/commerce/establishments'); - setEstablishments(Array.isArray(response) ? response : (response as any)?.establishments || []); + // Load inventory products for POS + // For now using mock data + setProducts([]); } catch (error) { - console.error('Error loading establishments:', error); - } - }; - - const loadMenu = async () => { - try { - const response = await apiClient.get(`/restaurant/establishments/${selectedEstablishment}/menu`); - setMenuItems(Array.isArray(response) ? response : (response as any)?.items || []); - } catch (error) { - console.error('Error loading menu:', error); + console.error('Error loading products:', error); } }; @@ -74,30 +57,24 @@ const POSTerminal = () => { }; const handleCheckout = async () => { - if (!tableNumber) { - toast({ title: 'Error', description: 'Ingresa el número de mesa', variant: 'destructive' }); + if (cart.length === 0) { + toast({ title: 'Error', description: 'El carrito está vacío', variant: 'destructive' }); return; } try { - await apiClient.post('/restaurant/orders', { - establishmentId: parseInt(selectedEstablishment), - tableNumber, - items: cart.map(item => ({ - menuItemId: item.id, - quantity: item.quantity, - price: item.price - })), - total: getTotal() - }); - toast({ title: 'Éxito', description: 'Orden creada correctamente' }); + toast({ title: 'Éxito', description: `Venta procesada: $${getTotal().toFixed(2)}` }); setCart([]); - setTableNumber(''); } catch (error: any) { - toast({ title: 'Error', description: error?.message || 'No se pudo crear la orden', variant: 'destructive' }); + toast({ title: 'Error', description: error?.message || 'No se pudo procesar la venta', variant: 'destructive' }); } }; + const filteredProducts = products.filter(p => + p.name?.toLowerCase().includes(searchTerm.toLowerCase()) || + p.barcode?.includes(searchTerm) + ); + return (
@@ -110,57 +87,59 @@ const POSTerminal = () => {
- + setSearchTerm(e.target.value)} + className="max-w-md" + />
- {selectedEstablishment && ( -
- {/* Menu Items */} -
- {menuItems.map((item) => ( +
+ {/* Products */} +
+ {filteredProducts.length === 0 ? ( +
+ +

No hay productos en el inventario

+

Agrega productos desde la sección de Inventario

+
+ ) : ( + filteredProducts.map((item) => ( addToCart(item)}> {item.name} -

{item.description}

-
- {item.category} - ${item.price} +
+ {item.barcode && ( +
+ + {item.barcode} +
+ )} +
+ {item.category} + ${item.price} +
+

Stock: {item.stock}

- ))} -
+ )) + )} +
{/* Cart */}
- Orden Actual -
- setTableNumber(e.target.value)} - /> -
+ Venta Actual
{cart.length === 0 ? (

- No hay ítems en la orden + No hay productos en la venta

) : ( <> @@ -202,7 +181,6 @@ const POSTerminal = () => {
- )}
); diff --git a/src/pages/dashboard/commerce/Store.tsx b/src/pages/dashboard/commerce/Store.tsx new file mode 100644 index 0000000..fecc0ec --- /dev/null +++ b/src/pages/dashboard/commerce/Store.tsx @@ -0,0 +1,241 @@ +import React, { useState, useEffect } from 'react'; +import { Store as StoreIcon, Edit, MapPin, Phone, Globe, Clock } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { Label } from '@/components/ui/label'; +import { Badge } from '@/components/ui/badge'; +import { useToast } from '@/hooks/use-toast'; +import { apiClient } from '@/services/adminApi'; + +const Store = () => { + const [editing, setEditing] = useState(false); + const [loading, setLoading] = useState(false); + const { toast } = useToast(); + + const [storeData, setStoreData] = useState({ + name: 'Mi Tienda', + description: 'Descripción de mi comercio', + type: 'store', + category: 'retail', + address: 'Dirección del comercio', + phone: '+1 809 123 4567', + email: 'contacto@mitienda.com', + website: '', + openingHours: { + monday: '9:00-18:00', + tuesday: '9:00-18:00', + wednesday: '9:00-18:00', + thursday: '9:00-18:00', + friday: '9:00-18:00', + saturday: '9:00-14:00', + sunday: 'Cerrado' + }, + isActive: true + }); + + useEffect(() => { + loadStoreData(); + }, []); + + const loadStoreData = async () => { + setLoading(true); + try { + // Load the user's store data + const response = await apiClient.get('/commerce/establishments'); + const stores = Array.isArray(response) ? response : (response as any)?.establishments || []; + if (stores.length > 0) { + setStoreData(stores[0]); // Load first store + } + } catch (error) { + console.error('Error loading store:', error); + } finally { + setLoading(false); + } + }; + + const handleSave = async () => { + try { + if (storeData.id) { + await apiClient.patch(`/commerce/establishments/${storeData.id}`, storeData); + } else { + await apiClient.post('/commerce/establishments', storeData); + } + toast({ title: 'Éxito', description: 'Información de la tienda actualizada' }); + setEditing(false); + loadStoreData(); + } catch (error: any) { + toast({ title: 'Error', description: error?.message || 'No se pudo guardar', variant: 'destructive' }); + } + }; + + return ( +
+
+
+
+ +
+

Mi Comercio

+

Información y configuración de tu tienda

+
+
+ +
+ +
+ {/* Store Status */} + + +
+ Estado del Comercio + + {storeData.isActive ? 'Activo' : 'Inactivo'} + +
+
+
+ + {/* Basic Information */} + + + Información Básica + + +
+ + setStoreData({ ...storeData, name: e.target.value })} + disabled={!editing} + placeholder="Nombre de tu tienda" + /> +
+
+ +