Refactor Restaurant POS module
This commit is contained in:
371
src/components/hotel/StaffManagement.tsx
Normal file
371
src/components/hotel/StaffManagement.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user