Добавлена возможность загрузки фото паспорта сотрудников через API. Реализована валидация загружаемых файлов, включая проверку типа и размера. Обновлены компоненты формы для отображения и загрузки аватара и паспорта. Оптимизирована логика обработки ошибок при загрузке.у
This commit is contained in:
@ -22,7 +22,10 @@ import {
|
||||
DollarSign,
|
||||
FileText,
|
||||
MessageCircle,
|
||||
AlertCircle
|
||||
AlertCircle,
|
||||
Calendar,
|
||||
RefreshCw,
|
||||
FileImage
|
||||
} from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
@ -214,8 +217,8 @@ export function EmployeeInlineForm({ onSave, onCancel, isLoading = false }: Empl
|
||||
let endpoint: string
|
||||
|
||||
if (type === 'avatar') {
|
||||
// Для аватара используем upload-avatar API
|
||||
formDataUpload.append('key', `avatars/employees/${Date.now()}-${file.name}`)
|
||||
// Для аватара используем upload-avatar API и добавляем временный userId
|
||||
formDataUpload.append('userId', `temp_${Date.now()}`)
|
||||
endpoint = '/api/upload-avatar'
|
||||
} else {
|
||||
// Для паспорта используем специальный API для документов сотрудников
|
||||
@ -268,6 +271,12 @@ export function EmployeeInlineForm({ onSave, onCancel, isLoading = false }: Empl
|
||||
})
|
||||
|
||||
setErrors(newErrors)
|
||||
|
||||
// Дебаг: показываем все ошибки в консоли
|
||||
if (Object.keys(newErrors).filter(key => newErrors[key]).length > 0) {
|
||||
console.log('Ошибки валидации:', newErrors)
|
||||
}
|
||||
|
||||
return Object.keys(newErrors).filter(key => newErrors[key]).length === 0
|
||||
}
|
||||
|
||||
@ -317,333 +326,342 @@ export function EmployeeInlineForm({ onSave, onCancel, isLoading = false }: Empl
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="glass-card mb-6">
|
||||
<CardHeader className="pb-4">
|
||||
<CardTitle className="text-white text-xl flex items-center gap-3">
|
||||
<UserPlus className="h-6 w-6 text-purple-400" />
|
||||
Добавить нового сотрудника
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Фотографии */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Фото сотрудника */}
|
||||
<div className="space-y-3">
|
||||
<Label className="text-white/80 font-medium flex items-center gap-2">
|
||||
<Camera className="h-4 w-4" />
|
||||
Фото сотрудника
|
||||
</Label>
|
||||
<>
|
||||
<Card className="glass-card p-6 mb-6">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col lg:flex-row gap-6">
|
||||
{/* Информация о сотруднике - точно как в карточке */}
|
||||
<div className="lg:w-80 flex-shrink-0">
|
||||
<div className="flex items-start space-x-4 mb-4">
|
||||
{/* Блок с аватаром и фото паспорта вертикально */}
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
{/* Аватар с иконкой камеры */}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="relative">
|
||||
<Avatar className="h-16 w-16 ring-2 ring-white/20">
|
||||
{formData.avatar && formData.avatar.trim() !== '' ? (
|
||||
<AvatarImage src={formData.avatar} alt="Фото сотрудника" />
|
||||
) : null}
|
||||
<AvatarFallback className="bg-gradient-to-br from-purple-500 to-purple-600 text-white font-semibold text-lg">
|
||||
{getInitials() || <User className="h-8 w-8" />}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="absolute -bottom-1 -right-1">
|
||||
<label htmlFor="avatar-upload-inline" className="cursor-pointer">
|
||||
<div className="w-5 h-5 bg-purple-600 rounded-full flex items-center justify-center hover:bg-purple-700 transition-colors">
|
||||
{isUploadingAvatar ? (
|
||||
<RefreshCw className="h-2.5 w-2.5 text-white animate-spin" />
|
||||
) : (
|
||||
<Camera className="h-2.5 w-2.5 text-white" />
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-white/60 text-xs text-center">Аватар</span>
|
||||
</div>
|
||||
|
||||
{/* Фото паспорта */}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="relative">
|
||||
<div className="w-16 h-16 rounded-lg ring-2 ring-white/20 bg-white/5 flex items-center justify-center overflow-hidden">
|
||||
{formData.passportPhoto && formData.passportPhoto.trim() !== '' ? (
|
||||
<img
|
||||
src={formData.passportPhoto}
|
||||
alt="Фото паспорта"
|
||||
className="w-full h-full object-cover cursor-pointer"
|
||||
onClick={() => setShowPassportPreview(true)}
|
||||
/>
|
||||
) : (
|
||||
<FileImage className="h-6 w-6 text-white/40" />
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute -bottom-1 -right-1">
|
||||
<label htmlFor="passport-upload-inline" className="cursor-pointer">
|
||||
<div className="w-5 h-5 bg-blue-600 rounded-full flex items-center justify-center hover:bg-blue-700 transition-colors">
|
||||
{isUploadingPassport ? (
|
||||
<RefreshCw className="h-2.5 w-2.5 text-white animate-spin" />
|
||||
) : (
|
||||
<Camera className="h-2.5 w-2.5 text-white" />
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-white/60 text-xs text-center">Паспорт</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<Avatar className="h-20 w-20 ring-2 ring-white/20">
|
||||
{formData.avatar && formData.avatar.trim() !== '' ? (
|
||||
<AvatarImage src={formData.avatar} alt="Фото сотрудника" />
|
||||
) : null}
|
||||
<AvatarFallback className="bg-gradient-to-br from-purple-500 to-purple-600 text-white text-lg font-semibold">
|
||||
{getInitials() || <User className="h-8 w-8" />}
|
||||
</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">
|
||||
<UserPlus className="h-5 w-5 text-purple-400 inline mr-2" />
|
||||
Новый сотрудник
|
||||
</h3>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={onCancel}
|
||||
className="text-red-400/60 hover:text-red-300 hover:bg-red-500/10 h-8 w-8 p-0"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="mb-4">
|
||||
<div className="space-y-3">
|
||||
{/* Имя */}
|
||||
<div className="flex items-center text-white/70">
|
||||
<User className="h-4 w-4 mr-3 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
value={formData.firstName}
|
||||
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
||||
placeholder="Имя *"
|
||||
className={`glass-input text-white placeholder:text-white/40 h-9 ${errors.firstName ? 'border-red-400' : ''}`}
|
||||
required
|
||||
/>
|
||||
<ErrorMessage error={errors.firstName} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Фамилия */}
|
||||
<div className="flex items-center text-white/70">
|
||||
<User className="h-4 w-4 mr-3 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
value={formData.lastName}
|
||||
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
||||
placeholder="Фамилия *"
|
||||
className={`glass-input text-white placeholder:text-white/40 h-9 ${errors.lastName ? 'border-red-400' : ''}`}
|
||||
required
|
||||
/>
|
||||
<ErrorMessage error={errors.lastName} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Отчество */}
|
||||
<div className="flex items-center text-white/70">
|
||||
<User className="h-4 w-4 mr-3 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
value={formData.middleName}
|
||||
onChange={(e) => handleInputChange('middleName', e.target.value)}
|
||||
placeholder="Отчество"
|
||||
className={`glass-input text-white placeholder:text-white/40 h-9 ${errors.middleName ? 'border-red-400' : ''}`}
|
||||
/>
|
||||
<ErrorMessage error={errors.middleName} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Должность */}
|
||||
<div className="flex items-center text-white/70">
|
||||
<Briefcase className="h-4 w-4 mr-3 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
value={formData.position}
|
||||
onChange={(e) => handleInputChange('position', e.target.value)}
|
||||
placeholder="Должность *"
|
||||
className={`glass-input text-white placeholder:text-white/40 h-9 ${errors.position ? 'border-red-400' : ''}`}
|
||||
required
|
||||
/>
|
||||
<ErrorMessage error={errors.position} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Телефон */}
|
||||
<div className="flex items-center text-white/70">
|
||||
<Phone className="h-4 w-4 mr-3 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
value={formData.phone}
|
||||
onChange={(e) => handleInputChange('phone', e.target.value)}
|
||||
placeholder="Телефон *"
|
||||
className={`glass-input text-white placeholder:text-white/40 h-9 ${errors.phone ? 'border-red-400' : ''}`}
|
||||
required
|
||||
/>
|
||||
<ErrorMessage error={errors.phone} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div className="flex items-center text-white/70">
|
||||
<Mail className="h-4 w-4 mr-3 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleInputChange('email', e.target.value)}
|
||||
placeholder="Email"
|
||||
className={`glass-input text-white placeholder:text-white/40 h-9 ${errors.email ? 'border-red-400' : ''}`}
|
||||
/>
|
||||
<ErrorMessage error={errors.email} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Дата рождения */}
|
||||
<div className="flex items-center text-white/70">
|
||||
<Calendar className="h-4 w-4 mr-3 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
type="date"
|
||||
value={formData.birthDate}
|
||||
onChange={(e) => handleInputChange('birthDate', e.target.value)}
|
||||
placeholder="Дата рождения"
|
||||
className={`glass-input text-white placeholder:text-white/40 h-9 ${errors.birthDate ? 'border-red-400' : ''}`}
|
||||
/>
|
||||
<ErrorMessage error={errors.birthDate} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Зарплата */}
|
||||
<div className="flex items-center text-white/70">
|
||||
<DollarSign className="h-4 w-4 mr-3 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
value={formData.salary ? formatSalary(formData.salary.toString()) : ''}
|
||||
onChange={(e) => handleSalaryChange(e.target.value)}
|
||||
placeholder="Зарплата"
|
||||
className={`glass-input text-white placeholder:text-white/40 h-9 ${errors.salary ? 'border-red-400' : ''}`}
|
||||
/>
|
||||
<ErrorMessage error={errors.salary} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Telegram */}
|
||||
<div className="flex items-center text-white/70">
|
||||
<MessageCircle className="h-4 w-4 mr-3 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
value={formData.telegram}
|
||||
onChange={(e) => handleInputChange('telegram', e.target.value)}
|
||||
placeholder="@telegram"
|
||||
className={`glass-input text-white placeholder:text-white/40 h-9 ${errors.telegram ? 'border-red-400' : ''}`}
|
||||
/>
|
||||
<ErrorMessage error={errors.telegram} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* WhatsApp */}
|
||||
<div className="flex items-center text-white/70">
|
||||
<Phone className="h-4 w-4 mr-3 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
value={formData.whatsapp}
|
||||
onChange={(e) => handleInputChange('whatsapp', e.target.value)}
|
||||
placeholder="WhatsApp"
|
||||
className={`glass-input text-white placeholder:text-white/40 h-9 ${errors.whatsapp ? 'border-red-400' : ''}`}
|
||||
/>
|
||||
<ErrorMessage error={errors.whatsapp} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-sm">
|
||||
|
||||
{/* Скрытые input элементы для загрузки файлов */}
|
||||
<input
|
||||
id="avatar-upload-inline"
|
||||
ref={avatarInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={(e) => e.target.files?.[0] && handleFileUpload(e.target.files[0], 'avatar')}
|
||||
className="hidden"
|
||||
disabled={isUploadingAvatar}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => avatarInputRef.current?.click()}
|
||||
disabled={isUploadingAvatar}
|
||||
className="glass-secondary text-white hover:text-white"
|
||||
>
|
||||
<Camera className="h-4 w-4 mr-2" />
|
||||
{isUploadingAvatar ? 'Загрузка...' : 'Загрузить фото'}
|
||||
</Button>
|
||||
|
||||
{formData.avatar && (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => setFormData(prev => ({ ...prev, avatar: '' }))}
|
||||
className="text-red-400 hover:text-red-300 hover:bg-red-500/10"
|
||||
>
|
||||
<X className="h-4 w-4 mr-2" />
|
||||
Удалить
|
||||
</Button>
|
||||
)}
|
||||
<input
|
||||
id="passport-upload-inline"
|
||||
ref={passportInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={(e) => e.target.files?.[0] && handleFileUpload(e.target.files[0], 'passport')}
|
||||
className="hidden"
|
||||
disabled={isUploadingPassport}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Фото паспорта */}
|
||||
<div className="space-y-3">
|
||||
<Label className="text-white/80 font-medium flex items-center gap-2">
|
||||
<FileText className="h-4 w-4" />
|
||||
Фото паспорта
|
||||
</Label>
|
||||
{/* Табель работы - точно как в карточке но пустой */}
|
||||
<div className="flex-1 space-y-4">
|
||||
<h4 className="text-white/80 font-medium mb-3 flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4" />
|
||||
Табель работы (будет доступен после создания)
|
||||
</h4>
|
||||
|
||||
{/* Пустая сетка календаря */}
|
||||
<div className="grid grid-cols-7 gap-2 opacity-50">
|
||||
{/* Заголовки дней недели */}
|
||||
{['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'].map(day => (
|
||||
<div key={day} className="p-2 text-center text-white/70 font-medium text-sm">
|
||||
{day}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="space-y-3">
|
||||
{formData.passportPhoto && formData.passportPhoto.trim() !== '' ? (
|
||||
<div className="relative">
|
||||
<Image
|
||||
src={formData.passportPhoto}
|
||||
alt="Паспорт"
|
||||
width={400}
|
||||
height={300}
|
||||
className="w-full h-auto max-h-48 object-contain rounded-lg border border-white/20 bg-white/5 cursor-pointer hover:opacity-80 transition-opacity"
|
||||
onClick={() => setShowPassportPreview(true)}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => setFormData(prev => ({ ...prev, passportPhoto: '' }))}
|
||||
className="absolute top-2 right-2 text-red-400 hover:text-red-300 hover:bg-red-500/10 h-8 w-8 p-0"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="absolute bottom-2 left-2 bg-black/50 text-white text-xs px-2 py-1 rounded">
|
||||
Нажмите для увеличения
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-48 border-2 border-dashed border-white/20 rounded-lg flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<FileText className="h-8 w-8 text-white/40 mx-auto mb-2" />
|
||||
<p className="text-white/60 text-sm">Паспорт не загружен</p>
|
||||
<p className="text-white/40 text-xs mt-1">Рекомендуемый формат: JPG, PNG</p>
|
||||
{/* Пустые дни месяца */}
|
||||
{Array.from({ length: 35 }, (_, i) => {
|
||||
const day = i + 1
|
||||
if (day > 31) return <div key={i} className="p-2"></div>
|
||||
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className="relative p-2 min-h-[60px] border rounded-lg bg-white/5 border-white/10"
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
<span className="font-semibold text-sm text-white/40">{day <= 31 ? day : ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Статистика - пустая */}
|
||||
<div className="grid grid-cols-4 gap-3 mt-4 opacity-50">
|
||||
<div className="text-center p-3 bg-white/10 rounded-lg">
|
||||
<p className="text-white/40 font-semibold text-lg">0</p>
|
||||
<p className="text-white/40 text-xs">Рабочих дней</p>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-white/10 rounded-lg">
|
||||
<p className="text-white/40 font-semibold text-lg">0</p>
|
||||
<p className="text-white/40 text-xs">Отпуск</p>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-white/10 rounded-lg">
|
||||
<p className="text-white/40 font-semibold text-lg">0</p>
|
||||
<p className="text-white/40 text-xs">Больничный</p>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-white/5 rounded-lg">
|
||||
<p className="text-white/40 font-semibold text-lg">0ч</p>
|
||||
<p className="text-white/40 text-xs">Всего часов</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Кнопка сохранения */}
|
||||
<div className="flex justify-end pt-4">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isLoading || isUploadingAvatar || isUploadingPassport}
|
||||
className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-500 hover:to-pink-500 text-white border-0 shadow-lg shadow-purple-500/25 hover:shadow-purple-500/40 transition-all duration-300"
|
||||
size="lg"
|
||||
>
|
||||
{isLoading ? 'Создание сотрудника...' : (
|
||||
<>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
Создать сотрудника
|
||||
</>
|
||||
)}
|
||||
|
||||
<input
|
||||
ref={passportInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={(e) => e.target.files?.[0] && handleFileUpload(e.target.files[0], 'passport')}
|
||||
className="hidden"
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => passportInputRef.current?.click()}
|
||||
disabled={isUploadingPassport}
|
||||
className="w-full glass-secondary text-white hover:text-white"
|
||||
>
|
||||
<FileText className="h-4 w-4 mr-2" />
|
||||
{isUploadingPassport ? 'Загрузка...' : 'Загрузить паспорт'}
|
||||
</Button>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator className="bg-white/10" />
|
||||
|
||||
{/* Основная информация */}
|
||||
<div className="space-y-4">
|
||||
<Label className="text-white font-medium flex items-center gap-2">
|
||||
<User className="h-4 w-4" />
|
||||
Личные данные
|
||||
</Label>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">
|
||||
Имя <span className="text-red-400">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
value={formData.firstName}
|
||||
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
||||
placeholder="Александр"
|
||||
className={`glass-input text-white placeholder:text-white/40 ${errors.firstName ? 'border-red-400' : ''}`}
|
||||
required
|
||||
/>
|
||||
<ErrorMessage error={errors.firstName} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">
|
||||
Фамилия <span className="text-red-400">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
value={formData.lastName}
|
||||
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
||||
placeholder="Петров"
|
||||
className={`glass-input text-white placeholder:text-white/40 ${errors.lastName ? 'border-red-400' : ''}`}
|
||||
required
|
||||
/>
|
||||
<ErrorMessage error={errors.lastName} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">Отчество</Label>
|
||||
<Input
|
||||
value={formData.middleName}
|
||||
onChange={(e) => handleInputChange('middleName', e.target.value)}
|
||||
placeholder="Иванович"
|
||||
className={`glass-input text-white placeholder:text-white/40 ${errors.middleName ? 'border-red-400' : ''}`}
|
||||
/>
|
||||
<ErrorMessage error={errors.middleName} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">
|
||||
Дата рождения
|
||||
</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={formData.birthDate}
|
||||
onChange={(e) => handleInputChange('birthDate', e.target.value)}
|
||||
className="glass-input text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator className="bg-white/10" />
|
||||
|
||||
{/* Контактная информация */}
|
||||
<div className="space-y-4">
|
||||
<Label className="text-white font-medium flex items-center gap-2">
|
||||
<Phone className="h-4 w-4" />
|
||||
Контактная информация
|
||||
</Label>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">
|
||||
Телефон <span className="text-red-400">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
value={formData.phone}
|
||||
onChange={(e) => handleInputChange('phone', e.target.value)}
|
||||
placeholder="+7 (999) 123-45-67"
|
||||
className={`glass-input text-white placeholder:text-white/40 ${errors.phone ? 'border-red-400' : ''}`}
|
||||
required
|
||||
/>
|
||||
<ErrorMessage error={errors.phone} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block flex items-center gap-2">
|
||||
<MessageCircle className="h-3 w-3" />
|
||||
Telegram
|
||||
</Label>
|
||||
<Input
|
||||
value={formData.telegram}
|
||||
onChange={(e) => handleInputChange('telegram', e.target.value)}
|
||||
placeholder="@username"
|
||||
className="glass-input text-white placeholder:text-white/40"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block flex items-center gap-2">
|
||||
<MessageCircle className="h-3 w-3" />
|
||||
WhatsApp
|
||||
</Label>
|
||||
<Input
|
||||
value={formData.whatsapp}
|
||||
onChange={(e) => handleInputChange('whatsapp', e.target.value)}
|
||||
placeholder="+7 (999) 123-45-67"
|
||||
className={`glass-input text-white placeholder:text-white/40 ${errors.whatsapp ? 'border-red-400' : ''}`}
|
||||
/>
|
||||
<ErrorMessage error={errors.whatsapp} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block flex items-center gap-2">
|
||||
<Mail className="h-3 w-3" />
|
||||
Email
|
||||
</Label>
|
||||
<Input
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleInputChange('email', e.target.value)}
|
||||
placeholder="a.petrov@company.com"
|
||||
className={`glass-input text-white placeholder:text-white/40 ${errors.email ? 'border-red-400' : ''}`}
|
||||
/>
|
||||
<ErrorMessage error={errors.email} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator className="bg-white/10" />
|
||||
|
||||
{/* Рабочая информация */}
|
||||
<div className="space-y-4">
|
||||
<Label className="text-white font-medium flex items-center gap-2">
|
||||
<Briefcase className="h-4 w-4" />
|
||||
Рабочая информация
|
||||
</Label>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">
|
||||
Должность <span className="text-red-400">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
value={formData.position}
|
||||
onChange={(e) => handleInputChange('position', e.target.value)}
|
||||
placeholder="Менеджер склада"
|
||||
className={`glass-input text-white placeholder:text-white/40 ${errors.position ? 'border-red-400' : ''}`}
|
||||
required
|
||||
/>
|
||||
<ErrorMessage error={errors.position} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block flex items-center gap-2">
|
||||
<DollarSign className="h-3 w-3" />
|
||||
Зарплата
|
||||
</Label>
|
||||
<Input
|
||||
value={formData.salary ? formatSalary(formData.salary.toString()) : ''}
|
||||
onChange={(e) => handleSalaryChange(e.target.value)}
|
||||
placeholder="80 000"
|
||||
className={`glass-input text-white placeholder:text-white/40 ${errors.salary ? 'border-red-400' : ''}`}
|
||||
/>
|
||||
<ErrorMessage error={errors.salary} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Кнопки управления */}
|
||||
<div className="flex gap-3 pt-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={onCancel}
|
||||
className="flex-1 border-red-400/30 text-red-200 hover:bg-red-500/10 hover:border-red-300 transition-all duration-300"
|
||||
>
|
||||
<X className="h-4 w-4 mr-2" />
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isLoading || isUploadingAvatar || isUploadingPassport}
|
||||
className="flex-1 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-500 hover:to-pink-500 text-white border-0 shadow-lg shadow-purple-500/25 hover:shadow-purple-500/40 transition-all duration-300"
|
||||
>
|
||||
{isLoading ? 'Сохранение...' : (
|
||||
<>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
Добавить сотрудника
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Превью паспорта */}
|
||||
<Dialog open={showPassportPreview} onOpenChange={setShowPassportPreview}>
|
||||
@ -664,6 +682,6 @@ export function EmployeeInlineForm({ onSave, onCancel, isLoading = false }: Empl
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user