new temporal folder to test
This commit is contained in:
323
frontend-web-free/src/components/events/ShiftRolesTable.jsx
Normal file
323
frontend-web-free/src/components/events/ShiftRolesTable.jsx
Normal file
@@ -0,0 +1,323 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user