
- ✨ Added compact employee forms (add/edit) with all fields visible - 🎯 Implemented expandable employee rows with timesheet integration - 📊 Added real KPI calculation based on work hours, sick days, and overtime - 📅 Added bulk date selection and editing in calendar - 🗓️ Implemented day-specific editing modal with hours and overtime tracking - 💾 Extended database schema with overtimeHours field - 🎨 Improved UI layout: tabs left, search right, real current date display - 🧹 Fixed spacing issues and removed unnecessary gaps - 🔧 Enhanced GraphQL mutations for employee schedule management
186 lines
6.2 KiB
TypeScript
186 lines
6.2 KiB
TypeScript
"use client"
|
||
|
||
import { useState } from 'react'
|
||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||
import { Button } from '@/components/ui/button'
|
||
import { Input } from '@/components/ui/input'
|
||
import { Label } from '@/components/ui/label'
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||
import { Calendar, Clock, Zap, FileText } from 'lucide-react'
|
||
|
||
interface DayEditModalProps {
|
||
isOpen: boolean
|
||
onClose: () => void
|
||
date: Date
|
||
employeeName: string
|
||
currentStatus?: string
|
||
currentHours?: number
|
||
currentOvertime?: number
|
||
currentNotes?: string
|
||
onSave: (data: {
|
||
status: string
|
||
hoursWorked?: number
|
||
overtimeHours?: number
|
||
notes?: string
|
||
}) => void
|
||
}
|
||
|
||
const statusOptions = [
|
||
{ value: 'WORK', label: 'Рабочий день', color: 'text-green-400' },
|
||
{ value: 'WEEKEND', label: 'Выходной', color: 'text-blue-400' },
|
||
{ value: 'VACATION', label: 'Отпуск', color: 'text-blue-400' },
|
||
{ value: 'SICK', label: 'Больничный', color: 'text-orange-400' },
|
||
{ value: 'ABSENT', label: 'Отсутствие', color: 'text-red-400' }
|
||
]
|
||
|
||
export function DayEditModal({
|
||
isOpen,
|
||
onClose,
|
||
date,
|
||
employeeName,
|
||
currentStatus = 'WORK',
|
||
currentHours = 8,
|
||
currentOvertime = 0,
|
||
currentNotes = '',
|
||
onSave
|
||
}: DayEditModalProps) {
|
||
const [status, setStatus] = useState(currentStatus)
|
||
const [hoursWorked, setHoursWorked] = useState(currentHours.toString())
|
||
const [overtimeHours, setOvertimeHours] = useState(currentOvertime.toString())
|
||
const [notes, setNotes] = useState(currentNotes)
|
||
|
||
const handleSave = () => {
|
||
const data = {
|
||
status,
|
||
hoursWorked: status === 'WORK' ? parseFloat(hoursWorked) || 0 : undefined,
|
||
overtimeHours: status === 'WORK' ? parseFloat(overtimeHours) || 0 : undefined,
|
||
notes: notes.trim() || undefined
|
||
}
|
||
onSave(data)
|
||
onClose()
|
||
}
|
||
|
||
const formatDate = (date: Date) => {
|
||
return date.toLocaleDateString('ru-RU', {
|
||
weekday: 'long',
|
||
year: 'numeric',
|
||
month: 'long',
|
||
day: 'numeric'
|
||
})
|
||
}
|
||
|
||
const isWorkDay = status === 'WORK'
|
||
const selectedStatus = statusOptions.find(opt => opt.value === status)
|
||
|
||
return (
|
||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||
<DialogContent className="glass-card border-white/10 max-w-md">
|
||
<DialogHeader>
|
||
<DialogTitle className="text-white flex items-center gap-2">
|
||
<Calendar className="h-5 w-5 text-purple-400" />
|
||
Редактирование дня
|
||
</DialogTitle>
|
||
</DialogHeader>
|
||
|
||
<div className="space-y-4">
|
||
{/* Информация о дне */}
|
||
<div className="bg-white/5 rounded-lg p-3 border border-white/10">
|
||
<div className="text-white font-medium">{employeeName}</div>
|
||
<div className="text-white/70 text-sm">{formatDate(date)}</div>
|
||
</div>
|
||
|
||
{/* Статус дня */}
|
||
<div className="space-y-2">
|
||
<Label className="text-white">Статус дня</Label>
|
||
<Select value={status} onValueChange={setStatus}>
|
||
<SelectTrigger className="glass-secondary border-white/20 text-white">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent className="glass-card border-white/10">
|
||
{statusOptions.map((option) => (
|
||
<SelectItem
|
||
key={option.value}
|
||
value={option.value}
|
||
className={`${option.color} hover:bg-white/10`}
|
||
>
|
||
{option.label}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
|
||
{/* Рабочие часы - только для рабочих дней */}
|
||
{isWorkDay && (
|
||
<>
|
||
<div className="space-y-2">
|
||
<Label className="text-white flex items-center gap-2">
|
||
<Clock className="h-4 w-4 text-green-400" />
|
||
Отработано часов
|
||
</Label>
|
||
<Input
|
||
type="number"
|
||
min="0"
|
||
max="24"
|
||
step="0.5"
|
||
value={hoursWorked}
|
||
onChange={(e) => setHoursWorked(e.target.value)}
|
||
className="glass-secondary border-white/20 text-white"
|
||
placeholder="8"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label className="text-white flex items-center gap-2">
|
||
<Zap className="h-4 w-4 text-yellow-400" />
|
||
Переработка (часов)
|
||
</Label>
|
||
<Input
|
||
type="number"
|
||
min="0"
|
||
max="12"
|
||
step="0.5"
|
||
value={overtimeHours}
|
||
onChange={(e) => setOvertimeHours(e.target.value)}
|
||
className="glass-secondary border-white/20 text-white"
|
||
placeholder="0"
|
||
/>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* Заметки */}
|
||
<div className="space-y-2">
|
||
<Label className="text-white flex items-center gap-2">
|
||
<FileText className="h-4 w-4 text-blue-400" />
|
||
Заметки (необязательно)
|
||
</Label>
|
||
<Input
|
||
value={notes}
|
||
onChange={(e) => setNotes(e.target.value)}
|
||
className="glass-secondary border-white/20 text-white"
|
||
placeholder="Дополнительная информация..."
|
||
/>
|
||
</div>
|
||
|
||
{/* Кнопки */}
|
||
<div className="flex gap-3 pt-4">
|
||
<Button
|
||
variant="ghost"
|
||
onClick={onClose}
|
||
className="flex-1 glass-secondary text-white hover:text-white"
|
||
>
|
||
Отмена
|
||
</Button>
|
||
<Button
|
||
onClick={handleSave}
|
||
className="flex-1 bg-purple-600 hover:bg-purple-700 text-white"
|
||
>
|
||
Сохранить
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
)
|
||
} |