323 lines
11 KiB
JavaScript
323 lines
11 KiB
JavaScript
import React from "react";
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { Plus, Minus, Pencil, Trash2, Search } from "lucide-react";
|
|
import {
|
|
Command,
|
|
CommandEmpty,
|
|
CommandGroup,
|
|
CommandInput,
|
|
CommandItem,
|
|
} from "@/components/ui/command";
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from "@/components/ui/popover";
|
|
|
|
const ROLES = [
|
|
"Front Desk",
|
|
"Finance",
|
|
"Hospitality",
|
|
"Recruiter",
|
|
"Server",
|
|
"Bartender",
|
|
"Cook",
|
|
"Dishwasher",
|
|
"Security",
|
|
"Janitor"
|
|
];
|
|
|
|
const DEPARTMENTS = [
|
|
"Accounting",
|
|
"Operations",
|
|
"Sales",
|
|
"HR",
|
|
"Finance",
|
|
"IT",
|
|
"Marketing",
|
|
"Customer Service",
|
|
"Logistics"
|
|
];
|
|
|
|
const UNIFORMS = ["Type 1", "Type 2", "Type 3", "Casual", "Formal"];
|
|
|
|
const TIME_OPTIONS = [];
|
|
for (let h = 1; h <= 12; h++) {
|
|
for (let m of ['00', '30']) {
|
|
TIME_OPTIONS.push(`${h.toString().padStart(2, '0')}:${m} AM`);
|
|
}
|
|
}
|
|
for (let h = 1; h <= 12; h++) {
|
|
for (let m of ['00', '30']) {
|
|
TIME_OPTIONS.push(`${h.toString().padStart(2, '0')}:${m} PM`);
|
|
}
|
|
}
|
|
|
|
export default function ShiftRolesTable({ roles, onChange }) {
|
|
const handleAddRole = () => {
|
|
onChange([...roles, {
|
|
role: "",
|
|
department: "",
|
|
count: 1,
|
|
start_time: "12:00 PM",
|
|
end_time: "05:00 PM",
|
|
hours: 5,
|
|
uniform: "Type 1",
|
|
break_minutes: 30,
|
|
cost_per_hour: 45,
|
|
total_value: 0
|
|
}]);
|
|
};
|
|
|
|
const handleDeleteRole = (index) => {
|
|
onChange(roles.filter((_, i) => i !== index));
|
|
};
|
|
|
|
const handleRoleChange = (index, field, value) => {
|
|
const newRoles = [...roles];
|
|
newRoles[index] = { ...newRoles[index], [field]: value };
|
|
|
|
// Calculate hours if times changed
|
|
if (field === 'start_time' || field === 'end_time') {
|
|
const start = newRoles[index].start_time;
|
|
const end = newRoles[index].end_time;
|
|
const hours = calculateHours(start, end);
|
|
newRoles[index].hours = hours;
|
|
}
|
|
|
|
// Calculate total value
|
|
const count = newRoles[index].count || 0;
|
|
const hours = newRoles[index].hours || 0;
|
|
const cost = newRoles[index].cost_per_hour || 0;
|
|
newRoles[index].total_value = count * hours * cost;
|
|
|
|
onChange(newRoles);
|
|
};
|
|
|
|
const calculateHours = (start, end) => {
|
|
// Simple calculation - in production, use proper time library
|
|
const startHour = parseInt(start.split(':')[0]) + (start.includes('PM') && !start.startsWith('12') ? 12 : 0);
|
|
const endHour = parseInt(end.split(':')[0]) + (end.includes('PM') && !end.startsWith('12') ? 12 : 0);
|
|
return Math.max(0, endHour - startHour);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="border border-slate-200 rounded-lg overflow-hidden">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow className="bg-slate-50">
|
|
<TableHead className="w-12 text-center">#</TableHead>
|
|
<TableHead className="min-w-[150px]">Role</TableHead>
|
|
<TableHead className="min-w-[130px]">Department</TableHead>
|
|
<TableHead className="w-24 text-center">Count</TableHead>
|
|
<TableHead className="min-w-[120px]">Start Date</TableHead>
|
|
<TableHead className="min-w-[120px]">End Date</TableHead>
|
|
<TableHead className="w-20 text-center">Hours</TableHead>
|
|
<TableHead className="min-w-[100px]">Uniform</TableHead>
|
|
<TableHead className="w-24">Break</TableHead>
|
|
<TableHead className="w-20">Cost</TableHead>
|
|
<TableHead className="w-28 text-right">Value</TableHead>
|
|
<TableHead className="w-24 text-center">Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{roles.map((role, index) => (
|
|
<TableRow key={index} className="hover:bg-slate-50">
|
|
<TableCell className="text-center font-medium text-slate-600">{index + 1}</TableCell>
|
|
|
|
{/* Role */}
|
|
<TableCell>
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button variant="outline" className="w-full justify-between">
|
|
{role.role || "Role"}
|
|
<Search className="w-4 h-4 ml-2 text-slate-400" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-64 p-0" align="start">
|
|
<Command>
|
|
<CommandInput placeholder="Search role..." />
|
|
<CommandEmpty>No role found.</CommandEmpty>
|
|
<CommandGroup className="max-h-64 overflow-auto">
|
|
{ROLES.map((r) => (
|
|
<CommandItem
|
|
key={r}
|
|
onSelect={() => handleRoleChange(index, 'role', r)}
|
|
>
|
|
{r}
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</TableCell>
|
|
|
|
{/* Department */}
|
|
<TableCell>
|
|
<Select
|
|
value={role.department}
|
|
onValueChange={(value) => handleRoleChange(index, 'department', value)}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Department" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{DEPARTMENTS.map(dept => (
|
|
<SelectItem key={dept} value={dept}>{dept}</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</TableCell>
|
|
|
|
{/* Count */}
|
|
<TableCell>
|
|
<div className="flex items-center gap-1">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="icon"
|
|
className="h-8 w-8"
|
|
onClick={() => handleRoleChange(index, 'count', Math.max(1, role.count - 1))}
|
|
>
|
|
<Minus className="w-3 h-3" />
|
|
</Button>
|
|
<Input
|
|
type="number"
|
|
value={role.count}
|
|
onChange={(e) => handleRoleChange(index, 'count', parseInt(e.target.value) || 1)}
|
|
className="w-12 h-8 text-center p-0"
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="icon"
|
|
className="h-8 w-8"
|
|
onClick={() => handleRoleChange(index, 'count', role.count + 1)}
|
|
>
|
|
<Plus className="w-3 h-3" />
|
|
</Button>
|
|
</div>
|
|
</TableCell>
|
|
|
|
{/* Start Time */}
|
|
<TableCell>
|
|
<Select
|
|
value={role.start_time}
|
|
onValueChange={(value) => handleRoleChange(index, 'start_time', value)}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent className="max-h-64">
|
|
{TIME_OPTIONS.map(time => (
|
|
<SelectItem key={time} value={time}>{time}</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</TableCell>
|
|
|
|
{/* End Time */}
|
|
<TableCell>
|
|
<Select
|
|
value={role.end_time}
|
|
onValueChange={(value) => handleRoleChange(index, 'end_time', value)}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent className="max-h-64">
|
|
{TIME_OPTIONS.map(time => (
|
|
<SelectItem key={time} value={time}>{time}</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</TableCell>
|
|
|
|
{/* Hours */}
|
|
<TableCell className="text-center font-semibold">{role.hours}</TableCell>
|
|
|
|
{/* Uniform */}
|
|
<TableCell>
|
|
<Select
|
|
value={role.uniform}
|
|
onValueChange={(value) => handleRoleChange(index, 'uniform', value)}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{UNIFORMS.map(u => (
|
|
<SelectItem key={u} value={u}>{u}</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</TableCell>
|
|
|
|
{/* Break */}
|
|
<TableCell>
|
|
<Input
|
|
type="number"
|
|
value={role.break_minutes}
|
|
onChange={(e) => handleRoleChange(index, 'break_minutes', parseInt(e.target.value) || 0)}
|
|
className="w-20 text-center"
|
|
placeholder="30"
|
|
/>
|
|
</TableCell>
|
|
|
|
{/* Cost */}
|
|
<TableCell>
|
|
<Input
|
|
type="number"
|
|
value={role.cost_per_hour}
|
|
onChange={(e) => handleRoleChange(index, 'cost_per_hour', parseFloat(e.target.value) || 0)}
|
|
className="w-20 text-center"
|
|
placeholder="45"
|
|
/>
|
|
</TableCell>
|
|
|
|
{/* Value */}
|
|
<TableCell className="text-right font-bold text-[#0A39DF]">
|
|
${role.total_value?.toFixed(2) || '0.00'}
|
|
</TableCell>
|
|
|
|
{/* Actions */}
|
|
<TableCell>
|
|
<div className="flex items-center justify-center gap-1">
|
|
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
<Pencil className="w-4 h-4 text-slate-600" />
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-8 w-8"
|
|
onClick={() => handleDeleteRole(index)}
|
|
>
|
|
<Trash2 className="w-4 h-4 text-red-600" />
|
|
</Button>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
|
|
{/* Add Role Button */}
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={handleAddRole}
|
|
className="border-dashed border-slate-300 hover:border-[#0A39DF] hover:text-[#0A39DF]"
|
|
>
|
|
<Plus className="w-4 h-4 mr-2" />
|
|
Add Role
|
|
</Button>
|
|
</div>
|
|
);
|
|
} |