Refactor commerce module
This commit is contained in:
99
src/App.tsx
99
src/App.tsx
@@ -39,6 +39,16 @@ import Security from "./pages/dashboard/Security";
|
|||||||
import VehicleManagement from "./pages/dashboard/VehicleManagement";
|
import VehicleManagement from "./pages/dashboard/VehicleManagement";
|
||||||
import Sustainability from "./pages/dashboard/Sustainability";
|
import Sustainability from "./pages/dashboard/Sustainability";
|
||||||
import Establishments from "./pages/dashboard/Establishments";
|
import Establishments from "./pages/dashboard/Establishments";
|
||||||
|
// Commerce pages
|
||||||
|
import CommerceEstablishments from "./pages/dashboard/commerce/Establishments";
|
||||||
|
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 CommerceInventory from "./pages/dashboard/commerce/Inventory";
|
||||||
|
import CommerceStaff from "./pages/dashboard/commerce/Staff";
|
||||||
|
import CommerceReports from "./pages/dashboard/commerce/Reports";
|
||||||
// Hotel pages
|
// Hotel pages
|
||||||
import HotelRooms from "./pages/dashboard/hotel/Rooms";
|
import HotelRooms from "./pages/dashboard/hotel/Rooms";
|
||||||
import HotelCheckIn from "./pages/dashboard/hotel/CheckIn";
|
import HotelCheckIn from "./pages/dashboard/hotel/CheckIn";
|
||||||
@@ -293,6 +303,95 @@ const AppRouter = () => (
|
|||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
} />
|
} />
|
||||||
|
|
||||||
|
{/* Commerce Routes */}
|
||||||
|
<Route path="/dashboard/commerce/establishments" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardLayout>
|
||||||
|
<CommerceEstablishments />
|
||||||
|
</DashboardLayout>
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
|
||||||
|
<Route path="/dashboard/commerce/pos" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardLayout>
|
||||||
|
<CommercePOS />
|
||||||
|
</DashboardLayout>
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
|
||||||
|
<Route path="/dashboard/commerce/orders" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardLayout>
|
||||||
|
<CommerceOrders />
|
||||||
|
</DashboardLayout>
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
|
||||||
|
<Route path="/dashboard/commerce/menu" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardLayout>
|
||||||
|
<CommerceMenu />
|
||||||
|
</DashboardLayout>
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
|
||||||
|
<Route path="/dashboard/commerce/hotel" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardLayout>
|
||||||
|
<CommerceHotel />
|
||||||
|
</DashboardLayout>
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
|
||||||
|
<Route path="/dashboard/commerce/reservations" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardLayout>
|
||||||
|
<CommerceReservations />
|
||||||
|
</DashboardLayout>
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
|
||||||
|
<Route path="/dashboard/commerce/inventory" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardLayout>
|
||||||
|
<CommerceInventory />
|
||||||
|
</DashboardLayout>
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
|
||||||
|
<Route path="/dashboard/commerce/staff" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardLayout>
|
||||||
|
<CommerceStaff />
|
||||||
|
</DashboardLayout>
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
|
||||||
|
<Route path="/dashboard/commerce/reports" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardLayout>
|
||||||
|
<CommerceReports />
|
||||||
|
</DashboardLayout>
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
|
||||||
|
<Route path="/dashboard/commerce/sales" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardLayout>
|
||||||
|
<CommerceReports />
|
||||||
|
</DashboardLayout>
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
|
||||||
|
<Route path="/dashboard/commerce/tables" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardLayout>
|
||||||
|
<CommerceMenu />
|
||||||
|
</DashboardLayout>
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
|
||||||
<Route path="/dashboard/hotel-management" element={
|
<Route path="/dashboard/hotel-management" element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
|
|||||||
@@ -160,7 +160,24 @@ const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
{ icon: Shield, label: 'Security', path: '/dashboard/security' },
|
{ icon: Shield, label: 'Security', path: '/dashboard/security' },
|
||||||
{ icon: Car, label: 'Vehicle Management', path: '/dashboard/vehicle-management' },
|
{ icon: Car, label: 'Vehicle Management', path: '/dashboard/vehicle-management' },
|
||||||
{ icon: Leaf, label: 'Sustainability', path: '/dashboard/sustainability' },
|
{ icon: Leaf, label: 'Sustainability', path: '/dashboard/sustainability' },
|
||||||
{ icon: Store, label: 'Comercios', path: '/dashboard/establishments' },
|
{
|
||||||
|
icon: Store,
|
||||||
|
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: Package, label: 'Inventario', path: '/dashboard/commerce/inventory' },
|
||||||
|
{ icon: Users, label: 'Personal', path: '/dashboard/commerce/staff' },
|
||||||
|
{ icon: BarChart3, label: 'Reportes', path: '/dashboard/commerce/reports' },
|
||||||
|
{ icon: DollarSign, label: 'Ventas', path: '/dashboard/commerce/sales' }
|
||||||
|
]
|
||||||
|
},
|
||||||
{ icon: Wallet, label: 'Wallet', path: '/dashboard/wallet' },
|
{ icon: Wallet, label: 'Wallet', path: '/dashboard/wallet' },
|
||||||
{ icon: MessageSquare, label: 'Message', path: '/dashboard/messages', badge: '2' },
|
{ icon: MessageSquare, label: 'Message', path: '/dashboard/messages', badge: '2' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -22,6 +22,27 @@ export const API_CONFIG = {
|
|||||||
// Wallet
|
// Wallet
|
||||||
WALLET: '/wallet',
|
WALLET: '/wallet',
|
||||||
TRANSACTIONS: '/wallet/transactions',
|
TRANSACTIONS: '/wallet/transactions',
|
||||||
|
|
||||||
|
// Commerce
|
||||||
|
ESTABLISHMENTS: '/commerce/establishments',
|
||||||
|
ESTABLISHMENT_DETAIL: '/commerce/establishments/:id',
|
||||||
|
RESERVATIONS: '/commerce/reservations',
|
||||||
|
RESERVATION_DETAIL: '/commerce/reservations/:id',
|
||||||
|
COMMERCE_STATS: '/commerce/stats',
|
||||||
|
|
||||||
|
// Restaurant
|
||||||
|
MENU_ITEMS: '/restaurant/menu-items',
|
||||||
|
RESTAURANT_MENU: '/restaurant/establishments/:id/menu',
|
||||||
|
RESTAURANT_TABLES: '/restaurant/establishments/:id/tables',
|
||||||
|
RESTAURANT_ORDERS: '/restaurant/establishments/:id/orders',
|
||||||
|
RESTAURANT_STATS: '/restaurant/establishments/:id/stats',
|
||||||
|
|
||||||
|
// Hotel
|
||||||
|
HOTEL_ROOMS: '/hotel/establishments/:id/rooms',
|
||||||
|
HOTEL_CHECKIN: '/hotel/checkin',
|
||||||
|
HOTEL_ROOM_SERVICE: '/hotel/room-service',
|
||||||
|
HOTEL_STATS: '/hotel/establishments/:id/stats',
|
||||||
|
HOTEL_HOUSEKEEPING: '/hotel/establishments/:id/housekeeping',
|
||||||
},
|
},
|
||||||
|
|
||||||
// External Assets
|
// External Assets
|
||||||
|
|||||||
23
src/pages/dashboard/commerce/Establishments.tsx
Normal file
23
src/pages/dashboard/commerce/Establishments.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Store } from 'lucide-react';
|
||||||
|
import EstablishmentsList from '@/components/establishments/EstablishmentsList';
|
||||||
|
|
||||||
|
const Establishments = () => {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<Store className="w-8 h-8 text-orange-600" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900">Establecimientos</h1>
|
||||||
|
<p className="text-gray-600">Gestiona todos los comercios del sistema</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<EstablishmentsList />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Establishments;
|
||||||
23
src/pages/dashboard/commerce/Hotel.tsx
Normal file
23
src/pages/dashboard/commerce/Hotel.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Hotel as HotelIcon } from 'lucide-react';
|
||||||
|
import HotelManagement from '@/components/establishments/HotelManagement';
|
||||||
|
|
||||||
|
const Hotel = () => {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<HotelIcon className="w-8 h-8 text-orange-600" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900">Gestión de Hotel</h1>
|
||||||
|
<p className="text-gray-600">Administra habitaciones y servicios</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<HotelManagement />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Hotel;
|
||||||
170
src/pages/dashboard/commerce/Inventory.tsx
Normal file
170
src/pages/dashboard/commerce/Inventory.tsx
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Package, Plus, Edit, Trash2, AlertTriangle } 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 Inventory = () => {
|
||||||
|
const [inventory, setInventory] = useState<any[]>([]);
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
category: '',
|
||||||
|
quantity: 0,
|
||||||
|
unit: 'kg',
|
||||||
|
minStock: 0,
|
||||||
|
supplier: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
toast({ title: 'Éxito', description: 'Producto agregado al inventario' });
|
||||||
|
setFormData({
|
||||||
|
name: '',
|
||||||
|
category: '',
|
||||||
|
quantity: 0,
|
||||||
|
unit: 'kg',
|
||||||
|
minStock: 0,
|
||||||
|
supplier: ''
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
toast({ title: 'Error', description: error?.message || 'No se pudo agregar el producto', variant: 'destructive' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<Package className="w-8 h-8 text-orange-600" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900">Gestión de Inventario</h1>
|
||||||
|
<p className="text-gray-600">Controla el stock de productos</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end mb-4">
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button>
|
||||||
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
|
Nuevo Producto
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Agregar Producto</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<div>
|
||||||
|
<Label>Nombre del Producto</Label>
|
||||||
|
<Input
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
placeholder="Ej: Tomates"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Categoría</Label>
|
||||||
|
<Input
|
||||||
|
value={formData.category}
|
||||||
|
onChange={(e) => setFormData({ ...formData, category: e.target.value })}
|
||||||
|
placeholder="Ej: Vegetales"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label>Cantidad</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={formData.quantity}
|
||||||
|
onChange={(e) => setFormData({ ...formData, quantity: parseFloat(e.target.value) })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Unidad</Label>
|
||||||
|
<Input
|
||||||
|
value={formData.unit}
|
||||||
|
onChange={(e) => setFormData({ ...formData, unit: e.target.value })}
|
||||||
|
placeholder="kg, lb, unidades"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Stock Mínimo</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={formData.minStock}
|
||||||
|
onChange={(e) => setFormData({ ...formData, minStock: parseFloat(e.target.value) })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Proveedor</Label>
|
||||||
|
<Input
|
||||||
|
value={formData.supplier}
|
||||||
|
onChange={(e) => setFormData({ ...formData, supplier: e.target.value })}
|
||||||
|
placeholder="Nombre del proveedor"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button onClick={handleSubmit}>Agregar Producto</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{inventory.length === 0 ? (
|
||||||
|
<div className="col-span-full text-center py-12 text-muted-foreground">
|
||||||
|
No hay productos en el inventario. Agrega el primer producto.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
inventory.map((item) => (
|
||||||
|
<Card key={item.id}>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
|
<div>
|
||||||
|
<CardTitle className="text-base">{item.name}</CardTitle>
|
||||||
|
<p className="text-sm text-muted-foreground">{item.category}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<Edit className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-muted-foreground">Stock actual:</span>
|
||||||
|
<span className="font-bold">{item.quantity} {item.unit}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-muted-foreground">Stock mínimo:</span>
|
||||||
|
<span>{item.minStock} {item.unit}</span>
|
||||||
|
</div>
|
||||||
|
{item.quantity <= item.minStock && (
|
||||||
|
<Badge variant="destructive" className="w-full justify-center">
|
||||||
|
<AlertTriangle className="w-3 h-3 mr-1" />
|
||||||
|
Stock Bajo
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
<p className="text-xs text-muted-foreground">Proveedor: {item.supplier}</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Inventory;
|
||||||
23
src/pages/dashboard/commerce/Menu.tsx
Normal file
23
src/pages/dashboard/commerce/Menu.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { UtensilsCrossed } from 'lucide-react';
|
||||||
|
import RestaurantPOS from '@/components/establishments/RestaurantPOS';
|
||||||
|
|
||||||
|
const Menu = () => {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<UtensilsCrossed className="w-8 h-8 text-orange-600" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900">Gestión de Menú</h1>
|
||||||
|
<p className="text-gray-600">Administra el menú del restaurante</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RestaurantPOS />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Menu;
|
||||||
142
src/pages/dashboard/commerce/Orders.tsx
Normal file
142
src/pages/dashboard/commerce/Orders.tsx
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { ClipboardList, Clock } from 'lucide-react';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
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 Orders = () => {
|
||||||
|
const [establishments, setEstablishments] = useState<any[]>([]);
|
||||||
|
const [selectedEstablishment, setSelectedEstablishment] = useState('');
|
||||||
|
const [orders, setOrders] = useState<any[]>([]);
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadEstablishments();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedEstablishment) {
|
||||||
|
loadOrders();
|
||||||
|
}
|
||||||
|
}, [selectedEstablishment]);
|
||||||
|
|
||||||
|
const loadEstablishments = async () => {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get('/commerce/establishments');
|
||||||
|
setEstablishments(Array.isArray(response) ? response : (response as any)?.establishments || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading establishments:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadOrders = async () => {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get(`/restaurant/establishments/${selectedEstablishment}/orders`);
|
||||||
|
setOrders(Array.isArray(response) ? response : (response as any)?.orders || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading orders:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateStatus = async (orderId: number, status: string) => {
|
||||||
|
try {
|
||||||
|
await apiClient.patch(`/restaurant/orders/${orderId}/status`, { status });
|
||||||
|
toast({ title: 'Éxito', description: 'Estado actualizado' });
|
||||||
|
loadOrders();
|
||||||
|
} catch (error) {
|
||||||
|
toast({ title: 'Error', description: 'No se pudo actualizar', variant: 'destructive' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusVariant = (status: string) => {
|
||||||
|
const variants: Record<string, any> = {
|
||||||
|
pending: 'secondary',
|
||||||
|
preparing: 'default',
|
||||||
|
ready: 'outline',
|
||||||
|
delivered: 'default',
|
||||||
|
paid: 'outline'
|
||||||
|
};
|
||||||
|
return variants[status] || 'default';
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<ClipboardList className="w-8 h-8 text-orange-600" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900">Gestión de Pedidos</h1>
|
||||||
|
<p className="text-gray-600">Administra todas las órdenes</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<Select value={selectedEstablishment} onValueChange={setSelectedEstablishment}>
|
||||||
|
<SelectTrigger className="w-64">
|
||||||
|
<SelectValue placeholder="Selecciona establecimiento" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{establishments.map((est) => (
|
||||||
|
<SelectItem key={est.id} value={est.id.toString()}>
|
||||||
|
{est.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4">
|
||||||
|
{orders.map((order) => (
|
||||||
|
<Card key={order.id}>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<ClipboardList className="w-8 h-8 text-primary" />
|
||||||
|
<div>
|
||||||
|
<CardTitle>Orden #{order.id}</CardTitle>
|
||||||
|
<p className="text-sm text-muted-foreground">Mesa {order.tableNumber}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
|
<Clock className="w-4 h-4" />
|
||||||
|
{new Date(order.createdAt).toLocaleTimeString()}
|
||||||
|
</div>
|
||||||
|
<span className="text-lg font-bold">${order.total?.toFixed(2)}</span>
|
||||||
|
<Select
|
||||||
|
value={order.status}
|
||||||
|
onValueChange={(value) => handleUpdateStatus(order.id, value)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-32">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="pending">Pendiente</SelectItem>
|
||||||
|
<SelectItem value="preparing">Preparando</SelectItem>
|
||||||
|
<SelectItem value="ready">Lista</SelectItem>
|
||||||
|
<SelectItem value="delivered">Entregada</SelectItem>
|
||||||
|
<SelectItem value="paid">Pagada</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{order.items?.map((item: any, idx: number) => (
|
||||||
|
<div key={idx} className="flex justify-between text-sm">
|
||||||
|
<span>{item.quantity}x {item.menuItem?.name || 'Item'}</span>
|
||||||
|
<span>${(item.quantity * item.price).toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Orders;
|
||||||
211
src/pages/dashboard/commerce/POSTerminal.tsx
Normal file
211
src/pages/dashboard/commerce/POSTerminal.tsx
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { CreditCard, Plus, Minus, Trash2, DollarSign } 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<any[]>([]);
|
||||||
|
const [selectedEstablishment, setSelectedEstablishment] = useState('');
|
||||||
|
const [menuItems, setMenuItems] = useState<any[]>([]);
|
||||||
|
const [cart, setCart] = useState<any[]>([]);
|
||||||
|
const [tableNumber, setTableNumber] = useState('');
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadEstablishments();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedEstablishment) {
|
||||||
|
loadMenu();
|
||||||
|
}
|
||||||
|
}, [selectedEstablishment]);
|
||||||
|
|
||||||
|
const loadEstablishments = async () => {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get('/commerce/establishments');
|
||||||
|
setEstablishments(Array.isArray(response) ? response : (response as any)?.establishments || []);
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addToCart = (item: any) => {
|
||||||
|
const existingItem = cart.find(i => i.id === item.id);
|
||||||
|
if (existingItem) {
|
||||||
|
setCart(cart.map(i =>
|
||||||
|
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
setCart([...cart, { ...item, quantity: 1 }]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateQuantity = (itemId: number, delta: number) => {
|
||||||
|
setCart(cart.map(item => {
|
||||||
|
if (item.id === itemId) {
|
||||||
|
const newQuantity = item.quantity + delta;
|
||||||
|
return newQuantity > 0 ? { ...item, quantity: newQuantity } : item;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}).filter(item => item.quantity > 0));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeFromCart = (itemId: number) => {
|
||||||
|
setCart(cart.filter(item => item.id !== itemId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTotal = () => {
|
||||||
|
return cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckout = async () => {
|
||||||
|
if (!tableNumber) {
|
||||||
|
toast({ title: 'Error', description: 'Ingresa el número de mesa', 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' });
|
||||||
|
setCart([]);
|
||||||
|
setTableNumber('');
|
||||||
|
} catch (error: any) {
|
||||||
|
toast({ title: 'Error', description: error?.message || 'No se pudo crear la orden', variant: 'destructive' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<CreditCard className="w-8 h-8 text-orange-600" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900">POS Terminal</h1>
|
||||||
|
<p className="text-gray-600">Sistema punto de venta</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<Select value={selectedEstablishment} onValueChange={setSelectedEstablishment}>
|
||||||
|
<SelectTrigger className="w-64">
|
||||||
|
<SelectValue placeholder="Selecciona establecimiento" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{establishments.map((est) => (
|
||||||
|
<SelectItem key={est.id} value={est.id.toString()}>
|
||||||
|
{est.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedEstablishment && (
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
{/* Menu Items */}
|
||||||
|
<div className="lg:col-span-2 grid gap-4 md:grid-cols-2">
|
||||||
|
{menuItems.map((item) => (
|
||||||
|
<Card key={item.id} className="cursor-pointer hover:shadow-lg transition-shadow" onClick={() => addToCart(item)}>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-base">{item.name}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-sm text-muted-foreground mb-2">{item.description}</p>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Badge variant="outline">{item.category}</Badge>
|
||||||
|
<span className="text-lg font-bold">${item.price}</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cart */}
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<Card className="sticky top-6">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Orden Actual</CardTitle>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Input
|
||||||
|
placeholder="Número de mesa"
|
||||||
|
value={tableNumber}
|
||||||
|
onChange={(e) => setTableNumber(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{cart.length === 0 ? (
|
||||||
|
<p className="text-sm text-muted-foreground text-center py-8">
|
||||||
|
No hay ítems en la orden
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="space-y-2 mb-4">
|
||||||
|
{cart.map((item) => (
|
||||||
|
<div key={item.id} className="flex items-center justify-between border-b pb-2">
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="font-medium text-sm">{item.name}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">${item.price}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button size="sm" variant="outline" onClick={() => updateQuantity(item.id, -1)}>
|
||||||
|
<Minus className="w-3 h-3" />
|
||||||
|
</Button>
|
||||||
|
<span className="w-8 text-center">{item.quantity}</span>
|
||||||
|
<Button size="sm" variant="outline" onClick={() => updateQuantity(item.id, 1)}>
|
||||||
|
<Plus className="w-3 h-3" />
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" variant="ghost" onClick={() => removeFromCart(item.id)}>
|
||||||
|
<Trash2 className="w-4 h-4 text-red-500" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="border-t pt-4 space-y-2">
|
||||||
|
<div className="flex justify-between text-lg font-bold">
|
||||||
|
<span>Total:</span>
|
||||||
|
<span>${getTotal().toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<Button className="w-full" onClick={handleCheckout}>
|
||||||
|
<DollarSign className="w-4 h-4 mr-2" />
|
||||||
|
Procesar Pago
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default POSTerminal;
|
||||||
23
src/pages/dashboard/commerce/Reports.tsx
Normal file
23
src/pages/dashboard/commerce/Reports.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { BarChart3 } from 'lucide-react';
|
||||||
|
import BusinessAnalytics from '@/components/establishments/BusinessAnalytics';
|
||||||
|
|
||||||
|
const Reports = () => {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<BarChart3 className="w-8 h-8 text-orange-600" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900">Reportes y Analytics</h1>
|
||||||
|
<p className="text-gray-600">Visualiza métricas y estadísticas</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BusinessAnalytics />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Reports;
|
||||||
23
src/pages/dashboard/commerce/Reservations.tsx
Normal file
23
src/pages/dashboard/commerce/Reservations.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { BookOpen } from 'lucide-react';
|
||||||
|
import CommerceReservations from '@/components/establishments/CommerceReservations';
|
||||||
|
|
||||||
|
const Reservations = () => {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<BookOpen className="w-8 h-8 text-orange-600" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900">Reservaciones</h1>
|
||||||
|
<p className="text-gray-600">Gestiona todas las reservaciones</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CommerceReservations />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Reservations;
|
||||||
219
src/pages/dashboard/commerce/Staff.tsx
Normal file
219
src/pages/dashboard/commerce/Staff.tsx
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Users, Plus, Edit, Trash2, Phone, Mail } 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 { 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 Staff = () => {
|
||||||
|
const [staff, setStaff] = useState<any[]>([]);
|
||||||
|
const [establishments, setEstablishments] = useState<any[]>([]);
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
role: 'waiter',
|
||||||
|
establishmentId: '',
|
||||||
|
salary: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
const [estResponse] = await Promise.all([
|
||||||
|
apiClient.get('/commerce/establishments')
|
||||||
|
]);
|
||||||
|
setEstablishments(Array.isArray(estResponse) ? estResponse : (estResponse as any)?.establishments || []);
|
||||||
|
// Mock staff data for now
|
||||||
|
setStaff([]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
// This would be a call to create staff member
|
||||||
|
toast({ title: 'Éxito', description: 'Empleado agregado correctamente' });
|
||||||
|
setFormData({
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
role: 'waiter',
|
||||||
|
establishmentId: '',
|
||||||
|
salary: 0
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
toast({ title: 'Error', description: error?.message || 'No se pudo agregar el empleado', variant: 'destructive' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<Users className="w-8 h-8 text-orange-600" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900">Gestión de Personal</h1>
|
||||||
|
<p className="text-gray-600">Administra empleados y roles</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end mb-4">
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button>
|
||||||
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
|
Nuevo Empleado
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-w-2xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Agregar Empleado</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label>Nombre</Label>
|
||||||
|
<Input
|
||||||
|
value={formData.firstName}
|
||||||
|
onChange={(e) => setFormData({ ...formData, firstName: e.target.value })}
|
||||||
|
placeholder="Nombre"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Apellido</Label>
|
||||||
|
<Input
|
||||||
|
value={formData.lastName}
|
||||||
|
onChange={(e) => setFormData({ ...formData, lastName: e.target.value })}
|
||||||
|
placeholder="Apellido"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label>Email</Label>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||||||
|
placeholder="email@ejemplo.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Teléfono</Label>
|
||||||
|
<Input
|
||||||
|
value={formData.phone}
|
||||||
|
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
|
||||||
|
placeholder="+1 809 123 4567"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label>Rol</Label>
|
||||||
|
<Select value={formData.role} onValueChange={(value) => setFormData({ ...formData, role: value })}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="waiter">Mesero</SelectItem>
|
||||||
|
<SelectItem value="chef">Chef</SelectItem>
|
||||||
|
<SelectItem value="bartender">Bartender</SelectItem>
|
||||||
|
<SelectItem value="manager">Gerente</SelectItem>
|
||||||
|
<SelectItem value="receptionist">Recepcionista</SelectItem>
|
||||||
|
<SelectItem value="housekeeping">Limpieza</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Establecimiento</Label>
|
||||||
|
<Select value={formData.establishmentId} onValueChange={(value) => setFormData({ ...formData, establishmentId: value })}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecciona establecimiento" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{establishments.map((est) => (
|
||||||
|
<SelectItem key={est.id} value={est.id.toString()}>
|
||||||
|
{est.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Salario</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={formData.salary}
|
||||||
|
onChange={(e) => setFormData({ ...formData, salary: parseFloat(e.target.value) })}
|
||||||
|
placeholder="0.00"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button onClick={handleSubmit}>Agregar Empleado</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{staff.length === 0 ? (
|
||||||
|
<div className="col-span-full text-center py-12 text-muted-foreground">
|
||||||
|
No hay empleados registrados. Agrega el primer empleado.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
staff.map((member) => (
|
||||||
|
<Card key={member.id}>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
|
<div>
|
||||||
|
<CardTitle className="text-base">{member.firstName} {member.lastName}</CardTitle>
|
||||||
|
<p className="text-sm text-muted-foreground">{member.role}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<Edit className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Mail className="w-4 h-4 text-muted-foreground" />
|
||||||
|
<span>{member.email}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Phone className="w-4 h-4 text-muted-foreground" />
|
||||||
|
<span>{member.phone}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center pt-2">
|
||||||
|
<Badge>{member.establishment?.name}</Badge>
|
||||||
|
<span className="font-bold">${member.salary}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Staff;
|
||||||
Reference in New Issue
Block a user