195 lines
9.3 KiB
TypeScript
195 lines
9.3 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Layout } from '../components/Layout/Layout';
|
|
import { Calendar, Plus, Copy, Zap, Send, Edit, Trash2 } from 'lucide-react';
|
|
import { format, startOfWeek, addDays } from 'date-fns';
|
|
|
|
export const TeamSchedules: React.FC = () => {
|
|
const [currentWeek] = useState(startOfWeek(new Date()));
|
|
const [selectedShift, setSelectedShift] = useState<string | null>(null);
|
|
|
|
const employees = ['John Doe', 'Jane Smith', 'Bob Johnson', 'Alice Brown', 'Charlie Wilson'];
|
|
const weekDays = Array.from({ length: 7 }, (_, i) => addDays(currentWeek, i));
|
|
|
|
const shifts: Record<string, Record<string, { start: string; end: string; role: string }>> = {
|
|
'John Doe': {
|
|
'0': { start: '8:00 AM', end: '5:00 PM', role: 'Cashier' },
|
|
'1': { start: '8:00 AM', end: '5:00 PM', role: 'Cashier' },
|
|
'3': { start: '9:00 AM', end: '6:00 PM', role: 'Manager' },
|
|
},
|
|
'Jane Smith': {
|
|
'1': { start: '9:00 AM', end: '6:00 PM', role: 'Stock' },
|
|
'2': { start: '8:00 AM', end: '5:00 PM', role: 'Cashier' },
|
|
'4': { start: '8:00 AM', end: '5:00 PM', role: 'Cashier' },
|
|
},
|
|
};
|
|
|
|
return (
|
|
<Layout>
|
|
<div>
|
|
<div className="flex items-center justify-between mb-6">
|
|
<h1 className="text-3xl font-bold text-gray-900">Team Schedules</h1>
|
|
<div className="flex gap-2">
|
|
<button className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg font-medium hover:bg-gray-300 flex items-center gap-2">
|
|
<Calendar className="w-4 h-4" />
|
|
Week Selector
|
|
</button>
|
|
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg font-medium hover:bg-primary-700 flex items-center gap-2">
|
|
<Send className="w-4 h-4" />
|
|
Publish Week
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Top Controls */}
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6">
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg font-medium hover:bg-primary-700 flex items-center gap-2">
|
|
<Plus className="w-4 h-4" />
|
|
Add Shift
|
|
</button>
|
|
<button className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg font-medium hover:bg-gray-300 flex items-center gap-2">
|
|
<Copy className="w-4 h-4" />
|
|
Copy Last Week
|
|
</button>
|
|
<button className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg font-medium hover:bg-gray-300 flex items-center gap-2">
|
|
<Zap className="w-4 h-4" />
|
|
Auto-Generate
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-6">
|
|
{/* Main Grid */}
|
|
<div className="flex-1 bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead className="bg-gray-50 border-b border-gray-200">
|
|
<tr>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider sticky left-0 bg-gray-50 z-10 border-r border-gray-200">
|
|
Employee
|
|
</th>
|
|
{weekDays.map((day, idx) => (
|
|
<th key={idx} className="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider min-w-[120px]">
|
|
<div>{format(day, 'EEE')}</div>
|
|
<div className="text-xs text-gray-400">{format(day, 'MMM d')}</div>
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
|
{employees.map((emp, empIdx) => (
|
|
<tr key={empIdx}>
|
|
<td className="px-4 py-3 font-medium text-gray-900 sticky left-0 bg-white z-10 border-r border-gray-200">
|
|
{emp}
|
|
</td>
|
|
{weekDays.map((day, dayIdx) => {
|
|
const shift = shifts[emp]?.[dayIdx.toString()];
|
|
return (
|
|
<td key={dayIdx} className="px-2 py-2">
|
|
{shift ? (
|
|
<div
|
|
className="p-2 bg-primary-100 border border-primary-300 rounded cursor-pointer hover:bg-primary-200 transition-colors"
|
|
onClick={() => setSelectedShift(`${emp}-${dayIdx}`)}
|
|
>
|
|
<div className="text-xs font-medium text-primary-900">{shift.start} - {shift.end}</div>
|
|
<div className="text-xs text-primary-700">{shift.role}</div>
|
|
</div>
|
|
) : (
|
|
<div className="p-2 border-2 border-dashed border-gray-200 rounded cursor-pointer hover:border-primary-300 transition-colors text-center text-xs text-gray-400">
|
|
Click to add
|
|
</div>
|
|
)}
|
|
</td>
|
|
);
|
|
})}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Sidebar */}
|
|
<div className="w-64 bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
<h3 className="font-semibold text-gray-900 mb-4">Unassigned Shifts</h3>
|
|
<div className="space-y-2">
|
|
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
|
<p className="text-sm font-medium">Monday 8:00 AM</p>
|
|
<p className="text-xs text-gray-600">Cashier needed</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-6">
|
|
<h3 className="font-semibold text-gray-900 mb-4">Coverage</h3>
|
|
<div className="space-y-2">
|
|
{weekDays.map((day, idx) => (
|
|
<div key={idx} className="flex items-center justify-between p-2 bg-gray-50 rounded">
|
|
<span className="text-xs text-gray-600">{format(day, 'EEE')}</span>
|
|
<span className="text-xs font-medium text-green-600">✓ Covered</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Shift Edit Drawer */}
|
|
{selectedShift && (
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
<div className="bg-white rounded-lg shadow-xl p-6 w-full max-w-md">
|
|
<h2 className="text-xl font-bold text-gray-900 mb-4">Edit Shift</h2>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Start Time</label>
|
|
<input type="time" className="w-full px-3 py-2 border border-gray-300 rounded-lg" defaultValue="08:00" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">End Time</label>
|
|
<input type="time" className="w-full px-3 py-2 border border-gray-300 rounded-lg" defaultValue="17:00" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Role</label>
|
|
<select className="w-full px-3 py-2 border border-gray-300 rounded-lg">
|
|
<option>Cashier</option>
|
|
<option>Stock Associate</option>
|
|
<option>Manager</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Location</label>
|
|
<input type="text" className="w-full px-3 py-2 border border-gray-300 rounded-lg" defaultValue="Main Store" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Notes</label>
|
|
<textarea className="w-full px-3 py-2 border border-gray-300 rounded-lg" rows={3}></textarea>
|
|
</div>
|
|
<div>
|
|
<label className="flex items-center gap-2">
|
|
<input type="checkbox" className="rounded" />
|
|
<span className="text-sm text-gray-700">Repeat weekly</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2 mt-6">
|
|
<button
|
|
onClick={() => setSelectedShift(null)}
|
|
className="flex-1 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg font-medium hover:bg-gray-300"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button className="flex-1 px-4 py-2 bg-primary-600 text-white rounded-lg font-medium hover:bg-primary-700">
|
|
Save
|
|
</button>
|
|
<button className="px-4 py-2 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700">
|
|
<Trash2 className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Layout>
|
|
);
|
|
};
|
|
|