Files
sfera/src/components/employees/employees-list.tsx

413 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState } from 'react'
import { Card } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
import { Badge } from '@/components/ui/badge'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import {
Edit,
Phone,
Mail,
Calendar,
MapPin,
User,
Briefcase,
Save,
X
} from 'lucide-react'
// Интерфейс сотрудника
interface Employee {
id: string
firstName: string
lastName: string
position: string
department: string
phone: string
email: string
avatar?: string
hireDate: string
status: 'active' | 'vacation' | 'sick' | 'inactive'
salary: number
address: string
}
// Моковые данные сотрудников
const mockEmployees: Employee[] = [
{
id: '1',
firstName: 'Александр',
lastName: 'Петров',
position: 'Менеджер склада',
department: 'Логистика',
phone: '+7 (999) 123-45-67',
email: 'a.petrov@company.com',
hireDate: '2023-01-15',
status: 'active',
salary: 80000,
address: 'Москва, ул. Ленина, 10'
},
{
id: '2',
firstName: 'Мария',
lastName: 'Иванова',
position: 'Кладовщик',
department: 'Логистика',
phone: '+7 (999) 234-56-78',
email: 'm.ivanova@company.com',
hireDate: '2023-03-20',
status: 'active',
salary: 60000,
address: 'Москва, ул. Советская, 25'
},
{
id: '3',
firstName: 'Дмитрий',
lastName: 'Сидоров',
position: 'Водитель',
department: 'Доставка',
phone: '+7 (999) 345-67-89',
email: 'd.sidorov@company.com',
hireDate: '2022-11-10',
status: 'vacation',
salary: 70000,
address: 'Москва, ул. Мира, 15'
},
{
id: '4',
firstName: 'Анна',
lastName: 'Козлова',
position: 'HR-специалист',
department: 'Кадры',
phone: '+7 (999) 456-78-90',
email: 'a.kozlova@company.com',
hireDate: '2023-02-05',
status: 'active',
salary: 75000,
address: 'Москва, пр. Победы, 8'
}
]
interface EmployeesListProps {
searchQuery: string
}
export function EmployeesList({ searchQuery }: EmployeesListProps) {
const [employees, setEmployees] = useState<Employee[]>(mockEmployees)
const [selectedEmployee, setSelectedEmployee] = useState<Employee | null>(null)
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
// Фильтрация сотрудников по поисковому запросу
const filteredEmployees = employees.filter(employee =>
`${employee.firstName} ${employee.lastName}`.toLowerCase().includes(searchQuery.toLowerCase()) ||
employee.position.toLowerCase().includes(searchQuery.toLowerCase()) ||
employee.department.toLowerCase().includes(searchQuery.toLowerCase())
)
const getStatusBadge = (status: Employee['status']) => {
switch (status) {
case 'active':
return <Badge className="bg-green-500/20 text-green-300 border-green-500/30">Активен</Badge>
case 'vacation':
return <Badge className="bg-blue-500/20 text-blue-300 border-blue-500/30">В отпуске</Badge>
case 'sick':
return <Badge className="bg-yellow-500/20 text-yellow-300 border-yellow-500/30">На больничном</Badge>
case 'inactive':
return <Badge className="bg-red-500/20 text-red-300 border-red-500/30">Неактивен</Badge>
default:
return <Badge>Неизвестно</Badge>
}
}
const getInitials = (firstName: string, lastName: string) => {
return `${firstName.charAt(0)}${lastName.charAt(0)}`
}
const formatSalary = (salary: number) => {
return new Intl.NumberFormat('ru-RU').format(salary) + ' ₽'
}
const handleEditEmployee = (employee: Employee) => {
setSelectedEmployee(employee)
setIsEditModalOpen(true)
}
const handleSaveEmployee = () => {
if (selectedEmployee) {
setEmployees(prev =>
prev.map(emp => emp.id === selectedEmployee.id ? selectedEmployee : emp)
)
setIsEditModalOpen(false)
setSelectedEmployee(null)
}
}
return (
<div className="space-y-4">
{/* Статистика */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<Card className="glass-card p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-white/70 text-sm">Всего сотрудников</p>
<p className="text-2xl font-bold text-white">{employees.length}</p>
</div>
<User className="h-8 w-8 text-purple-400" />
</div>
</Card>
<Card className="glass-card p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-white/70 text-sm">Активных</p>
<p className="text-2xl font-bold text-green-400">
{employees.filter(e => e.status === 'active').length}
</p>
</div>
<Briefcase className="h-8 w-8 text-green-400" />
</div>
</Card>
<Card className="glass-card p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-white/70 text-sm">В отпуске</p>
<p className="text-2xl font-bold text-blue-400">
{employees.filter(e => e.status === 'vacation').length}
</p>
</div>
<Calendar className="h-8 w-8 text-blue-400" />
</div>
</Card>
<Card className="glass-card p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-white/70 text-sm">Отделов</p>
<p className="text-2xl font-bold text-white">
{new Set(employees.map(e => e.department)).size}
</p>
</div>
<MapPin className="h-8 w-8 text-orange-400" />
</div>
</Card>
</div>
{/* Список сотрудников */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredEmployees.map((employee) => (
<Card key={employee.id} className="glass-card p-6">
<div className="flex items-start space-x-4">
<Avatar className="h-16 w-16 ring-2 ring-white/20">
{employee.avatar ? (
<AvatarImage src={employee.avatar} alt={`${employee.firstName} ${employee.lastName}`} />
) : null}
<AvatarFallback className="bg-gradient-to-br from-purple-500 to-purple-600 text-white font-semibold">
{getInitials(employee.firstName, employee.lastName)}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-2">
<h3 className="text-white font-semibold text-lg truncate">
{employee.firstName} {employee.lastName}
</h3>
<Button
size="sm"
variant="ghost"
className="text-white/60 hover:text-white hover:bg-white/10 h-8 w-8 p-0"
onClick={() => handleEditEmployee(employee)}
>
<Edit className="h-4 w-4" />
</Button>
</div>
<p className="text-purple-300 font-medium mb-1">{employee.position}</p>
<p className="text-white/60 text-sm mb-3">{employee.department}</p>
<div className="flex items-center justify-between mb-3">
{getStatusBadge(employee.status)}
<span className="text-white font-semibold">
{formatSalary(employee.salary)}
</span>
</div>
<div className="space-y-2">
<div className="flex items-center text-white/70 text-sm">
<Phone className="h-3 w-3 mr-2 flex-shrink-0" />
<span className="truncate">{employee.phone}</span>
</div>
<div className="flex items-center text-white/70 text-sm">
<Mail className="h-3 w-3 mr-2 flex-shrink-0" />
<span className="truncate">{employee.email}</span>
</div>
<div className="flex items-center text-white/70 text-sm">
<Calendar className="h-3 w-3 mr-2 flex-shrink-0" />
<span>С {new Date(employee.hireDate).toLocaleDateString('ru-RU')}</span>
</div>
</div>
</div>
</div>
</Card>
))}
</div>
{/* Модальное окно редактирования */}
<Dialog open={isEditModalOpen} onOpenChange={setIsEditModalOpen}>
<DialogContent className="glass-card max-w-md">
<DialogHeader>
<DialogTitle className="text-white flex items-center gap-2">
<Edit className="h-5 w-5" />
Редактировать сотрудника
</DialogTitle>
</DialogHeader>
{selectedEmployee && (
<div className="space-y-4 mt-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="text-white/80">Имя</Label>
<Input
value={selectedEmployee.firstName}
onChange={(e) => setSelectedEmployee({
...selectedEmployee,
firstName: e.target.value
})}
className="glass-input text-white"
/>
</div>
<div>
<Label className="text-white/80">Фамилия</Label>
<Input
value={selectedEmployee.lastName}
onChange={(e) => setSelectedEmployee({
...selectedEmployee,
lastName: e.target.value
})}
className="glass-input text-white"
/>
</div>
</div>
<div>
<Label className="text-white/80">Должность</Label>
<Input
value={selectedEmployee.position}
onChange={(e) => setSelectedEmployee({
...selectedEmployee,
position: e.target.value
})}
className="glass-input text-white"
/>
</div>
<div>
<Label className="text-white/80">Отдел</Label>
<Input
value={selectedEmployee.department}
onChange={(e) => setSelectedEmployee({
...selectedEmployee,
department: e.target.value
})}
className="glass-input text-white"
/>
</div>
<div>
<Label className="text-white/80">Статус</Label>
<Select
value={selectedEmployee.status}
onValueChange={(value: Employee['status']) => setSelectedEmployee({
...selectedEmployee,
status: value
})}
>
<SelectTrigger className="glass-input text-white">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-gray-900 border-white/20">
<SelectItem value="active" className="text-white hover:bg-white/10">Активен</SelectItem>
<SelectItem value="vacation" className="text-white hover:bg-white/10">В отпуске</SelectItem>
<SelectItem value="sick" className="text-white hover:bg-white/10">На больничном</SelectItem>
<SelectItem value="inactive" className="text-white hover:bg-white/10">Неактивен</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="text-white/80">Телефон</Label>
<Input
value={selectedEmployee.phone}
onChange={(e) => setSelectedEmployee({
...selectedEmployee,
phone: e.target.value
})}
className="glass-input text-white"
/>
</div>
<div>
<Label className="text-white/80">Зарплата</Label>
<Input
type="number"
value={selectedEmployee.salary}
onChange={(e) => setSelectedEmployee({
...selectedEmployee,
salary: Number(e.target.value)
})}
className="glass-input text-white"
/>
</div>
</div>
<div>
<Label className="text-white/80">Email</Label>
<Input
type="email"
value={selectedEmployee.email}
onChange={(e) => setSelectedEmployee({
...selectedEmployee,
email: e.target.value
})}
className="glass-input text-white"
/>
</div>
<div>
<Label className="text-white/80">Адрес</Label>
<Input
value={selectedEmployee.address}
onChange={(e) => setSelectedEmployee({
...selectedEmployee,
address: e.target.value
})}
className="glass-input text-white"
/>
</div>
<div className="flex gap-2 pt-4">
<Button
onClick={handleSaveEmployee}
className="glass-button flex-1"
>
<Save className="h-4 w-4 mr-2" />
Сохранить
</Button>
<Button
variant="outline"
onClick={() => setIsEditModalOpen(false)}
className="glass-secondary flex-1"
>
<X className="h-4 w-4 mr-2" />
Отмена
</Button>
</div>
</div>
)}
</DialogContent>
</Dialog>
</div>
)
}