Refactor Restaurant POS module

This commit is contained in:
gpt-engineer-app[bot]
2025-10-10 23:27:31 +00:00
parent 957c95e59c
commit 73f9b1ab95
7 changed files with 1660 additions and 8 deletions

View File

@@ -0,0 +1,371 @@
import { useState } from 'react';
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 { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Plus, Users, Clock, Calendar, Phone, Mail, Shield } from 'lucide-react';
import { toast } from 'sonner';
interface HotelStaff {
id: string;
name: string;
role: string;
department: string;
email: string;
phone: string;
status: 'active' | 'inactive' | 'on_leave';
shift: string;
hiredDate: Date;
hourlyRate: number;
accessLevel: string;
}
const StaffManagement = () => {
const [staff, setStaff] = useState<HotelStaff[]>([
{
id: '1',
name: 'María González',
role: 'Recepcionista',
department: 'Recepción',
email: 'maria@hotel.com',
phone: '+34 600 111 222',
status: 'active',
shift: 'Mañana',
hiredDate: new Date('2022-05-10'),
hourlyRate: 13.00,
accessLevel: 'Alto'
},
{
id: '2',
name: 'Pedro Sánchez',
role: 'Conserje',
department: 'Recepción',
email: 'pedro@hotel.com',
phone: '+34 600 222 333',
status: 'active',
shift: 'Tarde',
hiredDate: new Date('2023-01-15'),
hourlyRate: 12.00,
accessLevel: 'Medio'
},
{
id: '3',
name: 'Carmen Ruiz',
role: 'Supervisora',
department: 'Limpieza',
email: 'carmen@hotel.com',
phone: '+34 600 333 444',
status: 'active',
shift: 'Mañana',
hiredDate: new Date('2021-08-20'),
hourlyRate: 14.50,
accessLevel: 'Alto'
},
{
id: '4',
name: 'José Martín',
role: 'Camarero',
department: 'Limpieza',
email: 'jose@hotel.com',
phone: '+34 600 444 555',
status: 'active',
shift: 'Mañana',
hiredDate: new Date('2023-06-01'),
hourlyRate: 11.00,
accessLevel: 'Bajo'
},
{
id: '5',
name: 'Isabel Torres',
role: 'Técnico',
department: 'Mantenimiento',
email: 'isabel@hotel.com',
phone: '+34 600 555 666',
status: 'on_leave',
shift: 'Completo',
hiredDate: new Date('2022-11-12'),
hourlyRate: 15.00,
accessLevel: 'Alto'
}
]);
const roles = [
'Recepcionista', 'Conserje', 'Supervisor', 'Camarero',
'Técnico de Mantenimiento', 'Gerente', 'Botones'
];
const departments = ['Recepción', 'Limpieza', 'Mantenimiento', 'Seguridad', 'Administración'];
const shifts = ['Mañana', 'Tarde', 'Noche', 'Completo'];
const accessLevels = ['Bajo', 'Medio', 'Alto'];
const activeStaff = staff.filter(s => s.status === 'active').length;
const onLeave = staff.filter(s => s.status === 'on_leave').length;
const getStatusColor = (status: HotelStaff['status']) => {
switch (status) {
case 'active': return 'default';
case 'inactive': return 'secondary';
case 'on_leave': return 'outline';
default: return 'secondary';
}
};
const getStatusLabel = (status: HotelStaff['status']) => {
switch (status) {
case 'active': return 'Activo';
case 'inactive': return 'Inactivo';
case 'on_leave': return 'De Permiso';
default: return status;
}
};
const updateStatus = (staffId: string, newStatus: HotelStaff['status']) => {
setStaff(staff.map(s =>
s.id === staffId ? { ...s, status: newStatus } : s
));
toast.success('Estado actualizado');
};
const getMonthsEmployed = (hiredDate: Date) => {
const months = Math.floor((Date.now() - hiredDate.getTime()) / (1000 * 60 * 60 * 24 * 30));
return months;
};
const staffByDepartment = departments.map(dept => ({
department: dept,
count: staff.filter(s => s.department === dept).length
}));
return (
<div className="space-y-6">
{/* Stats */}
<div className="grid gap-4 md:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Personal</CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{staff.length}</div>
<p className="text-xs text-muted-foreground">empleados totales</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Activos</CardTitle>
<Users className="h-4 w-4 text-green-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-500">{activeStaff}</div>
<p className="text-xs text-muted-foreground">trabajando hoy</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">De Permiso</CardTitle>
<Clock className="h-4 w-4 text-orange-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-orange-500">{onLeave}</div>
<p className="text-xs text-muted-foreground">ausentes hoy</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Departamentos</CardTitle>
<Calendar className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{departments.length}</div>
<p className="text-xs text-muted-foreground">áreas operativas</p>
</CardContent>
</Card>
</div>
{/* Department Distribution */}
<Card>
<CardHeader>
<CardTitle>Distribución por Departamento</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
{staffByDepartment.map(dept => (
<div key={dept.department} className="text-center p-3 bg-muted rounded-lg">
<div className="text-2xl font-bold text-primary">{dept.count}</div>
<div className="text-xs text-muted-foreground">{dept.department}</div>
</div>
))}
</div>
</CardContent>
</Card>
{/* Actions */}
<div className="flex justify-between items-center">
<div>
<h3 className="text-lg font-semibold">Equipo del Hotel</h3>
<p className="text-sm text-muted-foreground">Gestiona tu personal hotelero</p>
</div>
<Dialog>
<DialogTrigger asChild>
<Button className="gap-2">
<Plus className="h-4 w-4" />
Nuevo Empleado
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Agregar Nuevo Empleado</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="staff-name">Nombre Completo</Label>
<Input id="staff-name" placeholder="Ej: María González" />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="staff-role">Puesto</Label>
<Select>
<SelectTrigger id="staff-role">
<SelectValue placeholder="Seleccionar puesto" />
</SelectTrigger>
<SelectContent>
{roles.map(role => (
<SelectItem key={role} value={role}>{role}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="staff-department">Departamento</Label>
<Select>
<SelectTrigger id="staff-department">
<SelectValue placeholder="Departamento" />
</SelectTrigger>
<SelectContent>
{departments.map(dept => (
<SelectItem key={dept} value={dept}>{dept}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="staff-email">Email</Label>
<Input id="staff-email" type="email" placeholder="email@hotel.com" />
</div>
<div>
<Label htmlFor="staff-phone">Teléfono</Label>
<Input id="staff-phone" placeholder="+34 600 123 456" />
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<Label htmlFor="staff-shift">Turno</Label>
<Select>
<SelectTrigger id="staff-shift">
<SelectValue placeholder="Turno" />
</SelectTrigger>
<SelectContent>
{shifts.map(shift => (
<SelectItem key={shift} value={shift}>{shift}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="staff-access">Nivel de Acceso</Label>
<Select>
<SelectTrigger id="staff-access">
<SelectValue placeholder="Acceso" />
</SelectTrigger>
<SelectContent>
{accessLevels.map(level => (
<SelectItem key={level} value={level}>{level}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="staff-rate">Tarifa/Hora ()</Label>
<Input id="staff-rate" type="number" step="0.50" placeholder="13.00" />
</div>
</div>
<Button className="w-full">Agregar Empleado</Button>
</div>
</DialogContent>
</Dialog>
</div>
{/* Staff List */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{staff.map((member) => (
<Card key={member.id}>
<CardHeader>
<div className="flex justify-between items-start">
<div className="flex-1">
<CardTitle className="text-lg">{member.name}</CardTitle>
<p className="text-sm text-muted-foreground">{member.role}</p>
<Badge variant="outline" className="mt-1">{member.department}</Badge>
</div>
<Badge variant={getStatusColor(member.status)}>
{getStatusLabel(member.status)}
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-3">
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2">
<Mail className="h-3 w-3 text-muted-foreground" />
<span className="text-xs truncate">{member.email}</span>
</div>
<div className="flex items-center gap-2">
<Phone className="h-3 w-3 text-muted-foreground" />
<span className="text-xs">{member.phone}</span>
</div>
<div className="flex items-center gap-2">
<Clock className="h-3 w-3 text-muted-foreground" />
<span>Turno: {member.shift}</span>
</div>
<div className="flex items-center gap-2">
<Shield className="h-3 w-3 text-muted-foreground" />
<span>Acceso: {member.accessLevel}</span>
</div>
<div className="flex items-center gap-2">
<Calendar className="h-3 w-3 text-muted-foreground" />
<span>{getMonthsEmployed(member.hiredDate)} meses en el hotel</span>
</div>
</div>
<div className="p-3 bg-muted rounded-lg">
<div className="text-xs text-muted-foreground">Tarifa por Hora</div>
<div className="text-xl font-bold text-primary">{member.hourlyRate.toFixed(2)}</div>
</div>
<Select
value={member.status}
onValueChange={(value) => updateStatus(member.id, value as HotelStaff['status'])}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="active">Activo</SelectItem>
<SelectItem value="on_leave">De Permiso</SelectItem>
<SelectItem value="inactive">Inactivo</SelectItem>
</SelectContent>
</Select>
</CardContent>
</Card>
))}
</div>
</div>
);
};
export default StaffManagement;

View File

@@ -0,0 +1,364 @@
import { useState } from 'react';
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 { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Plus, Package, AlertTriangle, TrendingUp, Search } from 'lucide-react';
import { toast } from 'sonner';
interface InventoryItem {
id: string;
name: string;
category: string;
quantity: number;
unit: string;
minStock: number;
supplier: string;
lastRestocked: Date;
cost: number;
}
const InventoryManagement = () => {
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('all');
const [inventory, setInventory] = useState<InventoryItem[]>([
{
id: '1',
name: 'Arroz Bomba',
category: 'Ingredientes',
quantity: 25,
unit: 'kg',
minStock: 10,
supplier: 'Distribuciones García',
lastRestocked: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000),
cost: 3.50
},
{
id: '2',
name: 'Gambas Frescas',
category: 'Mariscos',
quantity: 5,
unit: 'kg',
minStock: 8,
supplier: 'Pescadería El Mar',
lastRestocked: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000),
cost: 18.00
},
{
id: '3',
name: 'Vino Tinto Reserva',
category: 'Bebidas',
quantity: 45,
unit: 'botellas',
minStock: 20,
supplier: 'Bodegas Rioja',
lastRestocked: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000),
cost: 8.50
},
{
id: '4',
name: 'Aceite de Oliva Virgen Extra',
category: 'Ingredientes',
quantity: 8,
unit: 'litros',
minStock: 5,
supplier: 'Aceites del Sur',
lastRestocked: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000),
cost: 12.00
}
]);
const categories = ['all', 'Ingredientes', 'Mariscos', 'Bebidas', 'Carnes', 'Verduras'];
const lowStockItems = inventory.filter(item => item.quantity <= item.minStock);
const filteredInventory = inventory.filter(item => {
const matchesSearch = item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.supplier.toLowerCase().includes(searchTerm.toLowerCase());
const matchesCategory = selectedCategory === 'all' || item.category === selectedCategory;
return matchesSearch && matchesCategory;
});
const addStock = (itemId: string, quantity: number) => {
setInventory(inventory.map(item =>
item.id === itemId
? { ...item, quantity: item.quantity + quantity, lastRestocked: new Date() }
: item
));
toast.success('Stock actualizado correctamente');
};
const getDaysAgo = (date: Date) => {
const days = Math.floor((Date.now() - date.getTime()) / (1000 * 60 * 60 * 24));
return days === 0 ? 'Hoy' : days === 1 ? 'Ayer' : `Hace ${days} días`;
};
return (
<div className="space-y-6">
{/* Stats */}
<div className="grid gap-4 md:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Items</CardTitle>
<Package className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{inventory.length}</div>
<p className="text-xs text-muted-foreground">productos en inventario</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Stock Bajo</CardTitle>
<AlertTriangle className="h-4 w-4 text-orange-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-orange-500">{lowStockItems.length}</div>
<p className="text-xs text-muted-foreground">items necesitan reposición</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Valor Total</CardTitle>
<TrendingUp className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{inventory.reduce((sum, item) => sum + (item.quantity * item.cost), 0).toFixed(2)}
</div>
<p className="text-xs text-muted-foreground">valor del inventario</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Categorías</CardTitle>
<Package className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{categories.length - 1}</div>
<p className="text-xs text-muted-foreground">categorías activas</p>
</CardContent>
</Card>
</div>
{/* Low Stock Alert */}
{lowStockItems.length > 0 && (
<Card className="border-orange-500 bg-orange-50">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-orange-700">
<AlertTriangle className="h-5 w-5" />
Alertas de Stock Bajo
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
{lowStockItems.map(item => (
<div key={item.id} className="flex justify-between items-center p-2 bg-white rounded">
<div>
<span className="font-medium">{item.name}</span>
<span className="text-sm text-muted-foreground ml-2">
Stock: {item.quantity} {item.unit}
</span>
</div>
<Button size="sm" onClick={() => addStock(item.id, item.minStock)}>
Reponer
</Button>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* Filters and Actions */}
<div className="flex flex-wrap gap-4 items-end">
<div className="flex-1 min-w-[200px] relative">
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Buscar productos o proveedores..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
<SelectTrigger className="w-[200px]">
<SelectValue placeholder="Categoría" />
</SelectTrigger>
<SelectContent>
{categories.map(cat => (
<SelectItem key={cat} value={cat}>
{cat === 'all' ? 'Todas las categorías' : cat}
</SelectItem>
))}
</SelectContent>
</Select>
<Dialog>
<DialogTrigger asChild>
<Button className="gap-2">
<Plus className="h-4 w-4" />
Nuevo Item
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Agregar Item al Inventario</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="item-name">Nombre del Producto</Label>
<Input id="item-name" placeholder="Ej: Arroz Bomba" />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="item-quantity">Cantidad</Label>
<Input id="item-quantity" type="number" placeholder="25" />
</div>
<div>
<Label htmlFor="item-unit">Unidad</Label>
<Select>
<SelectTrigger id="item-unit">
<SelectValue placeholder="Unidad" />
</SelectTrigger>
<SelectContent>
<SelectItem value="kg">Kilogramos</SelectItem>
<SelectItem value="litros">Litros</SelectItem>
<SelectItem value="unidades">Unidades</SelectItem>
<SelectItem value="botellas">Botellas</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<Label htmlFor="item-category">Categoría</Label>
<Select>
<SelectTrigger id="item-category">
<SelectValue placeholder="Seleccionar categoría" />
</SelectTrigger>
<SelectContent>
{categories.slice(1).map(cat => (
<SelectItem key={cat} value={cat}>{cat}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="item-min">Stock Mínimo</Label>
<Input id="item-min" type="number" placeholder="10" />
</div>
<div>
<Label htmlFor="item-cost">Costo Unitario ()</Label>
<Input id="item-cost" type="number" step="0.01" placeholder="3.50" />
</div>
</div>
<div>
<Label htmlFor="item-supplier">Proveedor</Label>
<Input id="item-supplier" placeholder="Nombre del proveedor" />
</div>
<Button className="w-full">Agregar al Inventario</Button>
</div>
</DialogContent>
</Dialog>
</div>
{/* Inventory List */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{filteredInventory.map((item) => (
<Card key={item.id}>
<CardContent className="p-4">
<div className="space-y-3">
<div className="flex justify-between items-start">
<div className="flex-1">
<h4 className="font-semibold">{item.name}</h4>
<Badge variant="outline" className="mt-1">{item.category}</Badge>
</div>
{item.quantity <= item.minStock && (
<AlertTriangle className="h-5 w-5 text-orange-500" />
)}
</div>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Stock actual:</span>
<span className="font-medium">
{item.quantity} {item.unit}
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Stock mínimo:</span>
<span>{item.minStock} {item.unit}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Costo:</span>
<span>{item.cost.toFixed(2)}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Valor total:</span>
<span className="font-medium">{(item.quantity * item.cost).toFixed(2)}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Proveedor:</span>
<span className="text-xs">{item.supplier}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Última reposición:</span>
<span className="text-xs">{getDaysAgo(item.lastRestocked)}</span>
</div>
</div>
<div className="flex gap-2 pt-2">
<Dialog>
<DialogTrigger asChild>
<Button size="sm" variant="outline" className="flex-1">
Reponer
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Reponer Stock - {item.name}</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label>Cantidad a agregar ({item.unit})</Label>
<Input type="number" defaultValue={item.minStock} />
</div>
<div className="p-4 bg-muted rounded-lg">
<div className="text-sm text-muted-foreground">Nuevo stock será:</div>
<div className="text-2xl font-bold text-primary">
{item.quantity + item.minStock} {item.unit}
</div>
</div>
<Button
className="w-full"
onClick={() => addStock(item.id, item.minStock)}
>
Confirmar Reposición
</Button>
</div>
</DialogContent>
</Dialog>
</div>
</div>
</CardContent>
</Card>
))}
</div>
{filteredInventory.length === 0 && (
<div className="text-center py-12 text-muted-foreground">
No se encontraron productos
</div>
)}
</div>
);
};
export default InventoryManagement;

View File

@@ -0,0 +1,290 @@
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { Search, Plus, Minus, Trash2, CreditCard, DollarSign, Printer } from 'lucide-react';
import { toast } from 'sonner';
interface MenuItem {
id: string;
name: string;
price: number;
category: string;
}
interface CartItem extends MenuItem {
quantity: number;
notes?: string;
}
const POSTerminal = () => {
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('all');
const [selectedTable, setSelectedTable] = useState<number | null>(null);
const [cart, setCart] = useState<CartItem[]>([]);
const menuItems: MenuItem[] = [
{ id: '1', name: 'Paella Valenciana', price: 18.50, category: 'Platos Principales' },
{ id: '2', name: 'Gazpacho Andaluz', price: 6.50, category: 'Entrantes' },
{ id: '3', name: 'Pulpo a la Gallega', price: 16.00, category: 'Platos Principales' },
{ id: '4', name: 'Tarta de Santiago', price: 5.50, category: 'Postres' },
{ id: '5', name: 'Vino Tinto Reserva', price: 12.00, category: 'Bebidas' },
{ id: '6', name: 'Ensalada Mixta', price: 7.50, category: 'Entrantes' },
{ id: '7', name: 'Café Espresso', price: 2.00, category: 'Bebidas' },
{ id: '8', name: 'Crema Catalana', price: 4.50, category: 'Postres' }
];
const categories = ['all', 'Entrantes', 'Platos Principales', 'Postres', 'Bebidas'];
const filteredItems = menuItems.filter(item => {
const matchesSearch = item.name.toLowerCase().includes(searchTerm.toLowerCase());
const matchesCategory = selectedCategory === 'all' || item.category === selectedCategory;
return matchesSearch && matchesCategory;
});
const addToCart = (item: MenuItem) => {
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 }]);
}
toast.success(`${item.name} agregado`);
};
const updateQuantity = (itemId: string, 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: string) => {
setCart(cart.filter(item => item.id !== itemId));
};
const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
const tax = subtotal * 0.10; // 10% IVA
const total = subtotal + tax;
const processPayment = (method: 'cash' | 'card') => {
if (!selectedTable) {
toast.error('Por favor selecciona una mesa');
return;
}
if (cart.length === 0) {
toast.error('El carrito está vacío');
return;
}
toast.success(`Pago procesado - Mesa ${selectedTable}`);
setCart([]);
setSelectedTable(null);
};
const printReceipt = () => {
toast.success('Imprimiendo recibo...');
};
return (
<div className="grid lg:grid-cols-3 gap-6 h-[calc(100vh-300px)]">
{/* Left: Menu Items */}
<div className="lg:col-span-2 space-y-4 overflow-y-auto">
{/* Table Selection */}
<Card>
<CardContent className="p-4">
<div className="flex flex-wrap gap-2">
<span className="text-sm font-medium self-center">Mesa:</span>
{[1, 2, 3, 4, 5, 6, 7, 8].map(num => (
<Button
key={num}
size="sm"
variant={selectedTable === num ? 'default' : 'outline'}
onClick={() => setSelectedTable(num)}
>
{num}
</Button>
))}
</div>
</CardContent>
</Card>
{/* Search and Categories */}
<div className="space-y-3">
<div className="relative">
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Buscar productos..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
<div className="flex flex-wrap gap-2">
{categories.map(cat => (
<Button
key={cat}
size="sm"
variant={selectedCategory === cat ? 'default' : 'outline'}
onClick={() => setSelectedCategory(cat)}
>
{cat === 'all' ? 'Todo' : cat}
</Button>
))}
</div>
</div>
{/* Menu Grid */}
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
{filteredItems.map(item => (
<Card
key={item.id}
className="cursor-pointer hover:border-primary transition-colors"
onClick={() => addToCart(item)}
>
<CardContent className="p-4">
<div className="space-y-2">
<h4 className="font-medium text-sm leading-tight">{item.name}</h4>
<Badge variant="outline" className="text-xs">{item.category}</Badge>
<div className="text-lg font-bold text-primary">
{item.price.toFixed(2)}
</div>
</div>
</CardContent>
</Card>
))}
</div>
</div>
{/* Right: Cart and Checkout */}
<div className="space-y-4">
<Card className="h-full flex flex-col">
<CardHeader>
<CardTitle className="flex justify-between items-center">
<span>Pedido Actual</span>
{selectedTable && (
<Badge>Mesa {selectedTable}</Badge>
)}
</CardTitle>
</CardHeader>
<CardContent className="flex-1 flex flex-col space-y-4">
{/* Cart Items */}
<div className="flex-1 overflow-y-auto space-y-2">
{cart.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">
Carrito vacío
</div>
) : (
cart.map(item => (
<div key={item.id} className="p-2 border rounded-lg space-y-2">
<div className="flex justify-between items-start">
<div className="flex-1">
<div className="font-medium text-sm">{item.name}</div>
<div className="text-xs text-muted-foreground">
{item.price.toFixed(2)} × {item.quantity}
</div>
</div>
<div className="font-bold">
{(item.price * item.quantity).toFixed(2)}
</div>
</div>
<div className="flex gap-1">
<Button
size="icon"
variant="outline"
className="h-7 w-7"
onClick={() => updateQuantity(item.id, -1)}
>
<Minus className="h-3 w-3" />
</Button>
<Button
size="icon"
variant="outline"
className="h-7 w-7"
onClick={() => updateQuantity(item.id, 1)}
>
<Plus className="h-3 w-3" />
</Button>
<Button
size="icon"
variant="ghost"
className="h-7 w-7 ml-auto"
onClick={() => removeFromCart(item.id)}
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
</div>
))
)}
</div>
<Separator />
{/* Totals */}
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Subtotal:</span>
<span>{subtotal.toFixed(2)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">IVA (10%):</span>
<span>{tax.toFixed(2)}</span>
</div>
<Separator />
<div className="flex justify-between text-lg font-bold">
<span>Total:</span>
<span className="text-primary">{total.toFixed(2)}</span>
</div>
</div>
{/* Payment Buttons */}
<div className="space-y-2">
<Button
className="w-full gap-2"
size="lg"
onClick={() => processPayment('card')}
disabled={cart.length === 0 || !selectedTable}
>
<CreditCard className="h-5 w-5" />
Pagar con Tarjeta
</Button>
<Button
className="w-full gap-2"
size="lg"
variant="outline"
onClick={() => processPayment('cash')}
disabled={cart.length === 0 || !selectedTable}
>
<DollarSign className="h-5 w-5" />
Pagar en Efectivo
</Button>
<Button
className="w-full gap-2"
size="sm"
variant="ghost"
onClick={printReceipt}
disabled={cart.length === 0}
>
<Printer className="h-4 w-4" />
Imprimir Recibo
</Button>
</div>
</CardContent>
</Card>
</div>
</div>
);
};
export default POSTerminal;

View File

@@ -0,0 +1,292 @@
import { useState } from 'react';
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 { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Plus, Users, Clock, Calendar, Phone, Mail } from 'lucide-react';
import { toast } from 'sonner';
interface Staff {
id: string;
name: string;
role: string;
email: string;
phone: string;
status: 'active' | 'inactive' | 'on_leave';
shift: string;
hiredDate: Date;
hourlyRate: number;
}
const StaffManagement = () => {
const [staff, setStaff] = useState<Staff[]>([
{
id: '1',
name: 'Carlos Martínez',
role: 'Mesero',
email: 'carlos@restaurant.com',
phone: '+34 600 123 456',
status: 'active',
shift: 'Mañana',
hiredDate: new Date('2023-01-15'),
hourlyRate: 12.50
},
{
id: '2',
name: 'Ana López',
role: 'Mesera',
email: 'ana@restaurant.com',
phone: '+34 600 234 567',
status: 'active',
shift: 'Tarde',
hiredDate: new Date('2023-03-20'),
hourlyRate: 12.50
},
{
id: '3',
name: 'Miguel Rodríguez',
role: 'Chef',
email: 'miguel@restaurant.com',
phone: '+34 600 345 678',
status: 'active',
shift: 'Completo',
hiredDate: new Date('2022-06-10'),
hourlyRate: 18.00
},
{
id: '4',
name: 'Laura García',
role: 'Ayudante de Cocina',
email: 'laura@restaurant.com',
phone: '+34 600 456 789',
status: 'on_leave',
shift: 'Mañana',
hiredDate: new Date('2023-09-05'),
hourlyRate: 10.50
}
]);
const roles = ['Mesero', 'Chef', 'Ayudante de Cocina', 'Bartender', 'Host', 'Gerente'];
const shifts = ['Mañana', 'Tarde', 'Noche', 'Completo'];
const activeStaff = staff.filter(s => s.status === 'active').length;
const onLeave = staff.filter(s => s.status === 'on_leave').length;
const getStatusColor = (status: Staff['status']) => {
switch (status) {
case 'active': return 'default';
case 'inactive': return 'secondary';
case 'on_leave': return 'outline';
default: return 'secondary';
}
};
const getStatusLabel = (status: Staff['status']) => {
switch (status) {
case 'active': return 'Activo';
case 'inactive': return 'Inactivo';
case 'on_leave': return 'De Permiso';
default: return status;
}
};
const updateStatus = (staffId: string, newStatus: Staff['status']) => {
setStaff(staff.map(s =>
s.id === staffId ? { ...s, status: newStatus } : s
));
toast.success('Estado actualizado');
};
const getMonthsEmployed = (hiredDate: Date) => {
const months = Math.floor((Date.now() - hiredDate.getTime()) / (1000 * 60 * 60 * 24 * 30));
return months;
};
return (
<div className="space-y-6">
{/* Stats */}
<div className="grid gap-4 md:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Personal</CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{staff.length}</div>
<p className="text-xs text-muted-foreground">empleados totales</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Activos</CardTitle>
<Users className="h-4 w-4 text-green-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-500">{activeStaff}</div>
<p className="text-xs text-muted-foreground">trabajando hoy</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">De Permiso</CardTitle>
<Clock className="h-4 w-4 text-orange-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-orange-500">{onLeave}</div>
<p className="text-xs text-muted-foreground">ausentes hoy</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Costo Hora</CardTitle>
<Calendar className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{staff.reduce((sum, s) => sum + s.hourlyRate, 0).toFixed(2)}
</div>
<p className="text-xs text-muted-foreground">por hora total</p>
</CardContent>
</Card>
</div>
{/* Actions */}
<div className="flex justify-between items-center">
<div>
<h3 className="text-lg font-semibold">Equipo de Trabajo</h3>
<p className="text-sm text-muted-foreground">Gestiona tu personal del restaurante</p>
</div>
<Dialog>
<DialogTrigger asChild>
<Button className="gap-2">
<Plus className="h-4 w-4" />
Nuevo Empleado
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Agregar Nuevo Empleado</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="staff-name">Nombre Completo</Label>
<Input id="staff-name" placeholder="Ej: Carlos Martínez" />
</div>
<div>
<Label htmlFor="staff-role">Puesto</Label>
<Select>
<SelectTrigger id="staff-role">
<SelectValue placeholder="Seleccionar puesto" />
</SelectTrigger>
<SelectContent>
{roles.map(role => (
<SelectItem key={role} value={role}>{role}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="staff-email">Email</Label>
<Input id="staff-email" type="email" placeholder="email@ejemplo.com" />
</div>
<div>
<Label htmlFor="staff-phone">Teléfono</Label>
<Input id="staff-phone" placeholder="+34 600 123 456" />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="staff-shift">Turno</Label>
<Select>
<SelectTrigger id="staff-shift">
<SelectValue placeholder="Turno" />
</SelectTrigger>
<SelectContent>
{shifts.map(shift => (
<SelectItem key={shift} value={shift}>{shift}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="staff-rate">Tarifa por Hora ()</Label>
<Input id="staff-rate" type="number" step="0.50" placeholder="12.50" />
</div>
</div>
<Button className="w-full">Agregar Empleado</Button>
</div>
</DialogContent>
</Dialog>
</div>
{/* Staff List */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{staff.map((member) => (
<Card key={member.id}>
<CardHeader>
<div className="flex justify-between items-start">
<div>
<CardTitle className="text-lg">{member.name}</CardTitle>
<p className="text-sm text-muted-foreground">{member.role}</p>
</div>
<Badge variant={getStatusColor(member.status)}>
{getStatusLabel(member.status)}
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-3">
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2">
<Mail className="h-3 w-3 text-muted-foreground" />
<span className="text-xs">{member.email}</span>
</div>
<div className="flex items-center gap-2">
<Phone className="h-3 w-3 text-muted-foreground" />
<span className="text-xs">{member.phone}</span>
</div>
<div className="flex items-center gap-2">
<Clock className="h-3 w-3 text-muted-foreground" />
<span>Turno: {member.shift}</span>
</div>
<div className="flex items-center gap-2">
<Calendar className="h-3 w-3 text-muted-foreground" />
<span>{getMonthsEmployed(member.hiredDate)} meses en la empresa</span>
</div>
</div>
<div className="p-3 bg-muted rounded-lg">
<div className="text-xs text-muted-foreground">Tarifa por Hora</div>
<div className="text-xl font-bold text-primary">{member.hourlyRate.toFixed(2)}</div>
</div>
<div className="flex gap-2">
<Select
value={member.status}
onValueChange={(value) => updateStatus(member.id, value as Staff['status'])}
>
<SelectTrigger className="flex-1">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="active">Activo</SelectItem>
<SelectItem value="on_leave">De Permiso</SelectItem>
<SelectItem value="inactive">Inactivo</SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
))}
</div>
</div>
);
};
export default StaffManagement;

View File

@@ -0,0 +1,262 @@
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent } from '@/components/ui/card';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Plus, Edit, Trash2, Grid3x3 } from 'lucide-react';
import { toast } from 'sonner';
interface Table {
id: string;
number: number;
seats: number;
section: string;
status: 'available' | 'occupied' | 'reserved' | 'maintenance';
position: { x: number; y: number };
}
const TableConfiguration = () => {
const [tables, setTables] = useState<Table[]>([
{ id: '1', number: 1, seats: 4, section: 'Interior', status: 'available', position: { x: 50, y: 50 } },
{ id: '2', number: 2, seats: 2, section: 'Interior', status: 'occupied', position: { x: 200, y: 50 } },
{ id: '3', number: 3, seats: 6, section: 'Terraza', status: 'available', position: { x: 50, y: 200 } },
{ id: '4', number: 4, seats: 4, section: 'Terraza', status: 'reserved', position: { x: 200, y: 200 } },
{ id: '5', number: 5, seats: 8, section: 'VIP', status: 'available', position: { x: 350, y: 125 } }
]);
const [editingTable, setEditingTable] = useState<Table | null>(null);
const getStatusColor = (status: Table['status']) => {
switch (status) {
case 'available': return 'bg-green-500';
case 'occupied': return 'bg-red-500';
case 'reserved': return 'bg-yellow-500';
case 'maintenance': return 'bg-gray-500';
default: return 'bg-gray-500';
}
};
const getStatusLabel = (status: Table['status']) => {
switch (status) {
case 'available': return 'Disponible';
case 'occupied': return 'Ocupada';
case 'reserved': return 'Reservada';
case 'maintenance': return 'Mantenimiento';
default: return status;
}
};
const addTable = () => {
const newTable: Table = {
id: String(tables.length + 1),
number: tables.length + 1,
seats: 4,
section: 'Interior',
status: 'available',
position: { x: 100, y: 100 }
};
setTables([...tables, newTable]);
toast.success('Mesa agregada correctamente');
};
const updateTable = (updatedTable: Table) => {
setTables(tables.map(t => t.id === updatedTable.id ? updatedTable : t));
setEditingTable(null);
toast.success('Mesa actualizada correctamente');
};
const deleteTable = (tableId: string) => {
setTables(tables.filter(t => t.id !== tableId));
toast.success('Mesa eliminada correctamente');
};
const changeStatus = (tableId: string, newStatus: Table['status']) => {
setTables(tables.map(t =>
t.id === tableId ? { ...t, status: newStatus } : t
));
toast.success('Estado actualizado');
};
const sections = ['Interior', 'Terraza', 'VIP', 'Bar'];
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h3 className="text-lg font-semibold">Layout del Restaurante</h3>
<p className="text-sm text-muted-foreground">
{tables.length} mesas configuradas
</p>
</div>
<Dialog>
<DialogTrigger asChild>
<Button className="gap-2">
<Plus className="h-4 w-4" />
Nueva Mesa
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Agregar Nueva Mesa</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="table-number">Número de Mesa</Label>
<Input id="table-number" type="number" defaultValue={tables.length + 1} />
</div>
<div>
<Label htmlFor="table-seats">Capacidad (personas)</Label>
<Input id="table-seats" type="number" defaultValue={4} />
</div>
<div>
<Label htmlFor="table-section">Sección</Label>
<Select defaultValue="Interior">
<SelectTrigger id="table-section">
<SelectValue placeholder="Seleccionar sección" />
</SelectTrigger>
<SelectContent>
{sections.map(section => (
<SelectItem key={section} value={section}>{section}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Button onClick={addTable} className="w-full">Agregar Mesa</Button>
</div>
</DialogContent>
</Dialog>
</div>
{/* Visual Layout */}
<Card>
<CardContent className="p-6">
<div className="relative bg-muted/30 rounded-lg" style={{ height: '400px' }}>
<div className="absolute top-2 left-2 flex gap-2 text-xs">
<div className="flex items-center gap-1">
<div className="w-3 h-3 rounded bg-green-500"></div>
<span>Disponible</span>
</div>
<div className="flex items-center gap-1">
<div className="w-3 h-3 rounded bg-red-500"></div>
<span>Ocupada</span>
</div>
<div className="flex items-center gap-1">
<div className="w-3 h-3 rounded bg-yellow-500"></div>
<span>Reservada</span>
</div>
</div>
{tables.map((table) => (
<div
key={table.id}
className="absolute cursor-pointer hover:scale-110 transition-transform"
style={{
left: `${table.position.x}px`,
top: `${table.position.y}px`
}}
>
<div className={`${getStatusColor(table.status)} text-white rounded-lg p-3 shadow-lg`}>
<div className="text-center">
<div className="font-bold">#{table.number}</div>
<div className="text-xs">{table.seats} personas</div>
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
{/* Tables List */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{tables.map((table) => (
<Card key={table.id}>
<CardContent className="p-4">
<div className="flex justify-between items-start mb-3">
<div>
<h4 className="font-semibold text-lg">Mesa #{table.number}</h4>
<p className="text-sm text-muted-foreground">{table.section}</p>
</div>
<div className="flex gap-1">
<Dialog>
<DialogTrigger asChild>
<Button size="icon" variant="ghost" onClick={() => setEditingTable(table)}>
<Edit className="h-4 w-4" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Editar Mesa #{table.number}</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label>Capacidad</Label>
<Input type="number" defaultValue={table.seats} />
</div>
<div>
<Label>Sección</Label>
<Select defaultValue={table.section}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{sections.map(section => (
<SelectItem key={section} value={section}>{section}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label>Estado</Label>
<Select
defaultValue={table.status}
onValueChange={(value) => changeStatus(table.id, value as Table['status'])}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="available">Disponible</SelectItem>
<SelectItem value="occupied">Ocupada</SelectItem>
<SelectItem value="reserved">Reservada</SelectItem>
<SelectItem value="maintenance">Mantenimiento</SelectItem>
</SelectContent>
</Select>
</div>
<Button className="w-full">Guardar Cambios</Button>
</div>
</DialogContent>
</Dialog>
<Button
size="icon"
variant="ghost"
onClick={() => deleteTable(table.id)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Capacidad:</span>
<span className="font-medium">{table.seats} personas</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Estado:</span>
<span className={`font-medium ${getStatusColor(table.status)} text-white px-2 py-0.5 rounded text-xs`}>
{getStatusLabel(table.status)}
</span>
</div>
</div>
</CardContent>
</Card>
))}
</div>
</div>
);
};
export default TableConfiguration;

View File

@@ -22,6 +22,7 @@ import RoomManagement from '@/components/hotel/RoomManagement';
import CheckInSystem from '@/components/hotel/CheckInSystem'; import CheckInSystem from '@/components/hotel/CheckInSystem';
import RoomService from '@/components/hotel/RoomService'; import RoomService from '@/components/hotel/RoomService';
import KeylessEntry from '@/components/hotel/KeylessEntry'; import KeylessEntry from '@/components/hotel/KeylessEntry';
import StaffManagement from '@/components/hotel/StaffManagement';
const HotelManagement = () => { const HotelManagement = () => {
const [activeTab, setActiveTab] = useState('overview'); const [activeTab, setActiveTab] = useState('overview');
@@ -122,7 +123,7 @@ const HotelManagement = () => {
{/* Main Content Tabs */} {/* Main Content Tabs */}
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6"> <Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
<TabsList className="grid w-full grid-cols-5 lg:w-auto lg:inline-grid"> <TabsList className="grid w-full grid-cols-6 lg:w-auto lg:inline-grid">
<TabsTrigger value="overview" className="flex items-center gap-2"> <TabsTrigger value="overview" className="flex items-center gap-2">
<Hotel className="w-4 h-4" /> <Hotel className="w-4 h-4" />
<span className="hidden sm:inline">Resumen</span> <span className="hidden sm:inline">Resumen</span>
@@ -143,6 +144,10 @@ const HotelManagement = () => {
<Key className="w-4 h-4" /> <Key className="w-4 h-4" />
<span className="hidden sm:inline">Acceso</span> <span className="hidden sm:inline">Acceso</span>
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="staff" className="flex items-center gap-2">
<Users className="w-4 h-4" />
<span className="hidden sm:inline">Personal</span>
</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="overview" className="space-y-6"> <TabsContent value="overview" className="space-y-6">
@@ -271,6 +276,10 @@ const HotelManagement = () => {
<TabsContent value="keyless"> <TabsContent value="keyless">
<KeylessEntry /> <KeylessEntry />
</TabsContent> </TabsContent>
<TabsContent value="staff">
<StaffManagement />
</TabsContent>
</Tabs> </Tabs>
</div> </div>
); );

View File

@@ -6,6 +6,10 @@ import DigitalMenu from '@/components/restaurant/DigitalMenu';
import TableOrders from '@/components/restaurant/TableOrders'; import TableOrders from '@/components/restaurant/TableOrders';
import KitchenDisplay from '@/components/restaurant/KitchenDisplay'; import KitchenDisplay from '@/components/restaurant/KitchenDisplay';
import BillManagement from '@/components/restaurant/BillManagement'; import BillManagement from '@/components/restaurant/BillManagement';
import TableConfiguration from '@/components/restaurant/TableConfiguration';
import InventoryManagement from '@/components/restaurant/InventoryManagement';
import POSTerminal from '@/components/restaurant/POSTerminal';
import StaffManagement from '@/components/restaurant/StaffManagement';
const RestaurantPOS = () => { const RestaurantPOS = () => {
const [activeOrders] = useState(12); const [activeOrders] = useState(12);
@@ -63,20 +67,38 @@ const RestaurantPOS = () => {
</div> </div>
{/* Main Content */} {/* Main Content */}
<Tabs defaultValue="menu" className="space-y-4"> <Tabs defaultValue="pos" className="space-y-4">
<TabsList className="grid w-full grid-cols-4"> <TabsList className="grid w-full grid-cols-8">
<TabsTrigger value="menu">Menú Digital</TabsTrigger> <TabsTrigger value="pos">POS</TabsTrigger>
<TabsTrigger value="orders">Pedidos en Mesa</TabsTrigger> <TabsTrigger value="orders">Pedidos</TabsTrigger>
<TabsTrigger value="kitchen">Cocina</TabsTrigger> <TabsTrigger value="kitchen">Cocina</TabsTrigger>
<TabsTrigger value="bills">Facturación</TabsTrigger> <TabsTrigger value="bills">Cuentas</TabsTrigger>
<TabsTrigger value="tables">Mesas</TabsTrigger>
<TabsTrigger value="menu">Menú</TabsTrigger>
<TabsTrigger value="inventory">Inventario</TabsTrigger>
<TabsTrigger value="staff">Personal</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="pos" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Terminal Punto de Venta</CardTitle>
<CardDescription>
Sistema POS completo conectado con inventario
</CardDescription>
</CardHeader>
<CardContent>
<POSTerminal />
</CardContent>
</Card>
</TabsContent>
<TabsContent value="menu" className="space-y-4"> <TabsContent value="menu" className="space-y-4">
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Menú Digital con QR</CardTitle> <CardTitle>Gestión de Menú y Platos</CardTitle>
<CardDescription> <CardDescription>
Gestiona tu menú y genera códigos QR para las mesas Configura tu menú y genera códigos QR para las mesas
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -126,6 +148,48 @@ const RestaurantPOS = () => {
</CardContent> </CardContent>
</Card> </Card>
</TabsContent> </TabsContent>
<TabsContent value="tables" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Configuración de Mesas</CardTitle>
<CardDescription>
Gestiona el layout y estado de las mesas del restaurante
</CardDescription>
</CardHeader>
<CardContent>
<TableConfiguration />
</CardContent>
</Card>
</TabsContent>
<TabsContent value="inventory" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Gestión de Inventario</CardTitle>
<CardDescription>
Control de stock, alertas y reposición de productos
</CardDescription>
</CardHeader>
<CardContent>
<InventoryManagement />
</CardContent>
</Card>
</TabsContent>
<TabsContent value="staff" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Gestión de Personal</CardTitle>
<CardDescription>
Administra tu equipo de restaurante
</CardDescription>
</CardHeader>
<CardContent>
<StaffManagement />
</CardContent>
</Card>
</TabsContent>
</Tabs> </Tabs>
</div> </div>
); );