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;