import React, { useState, useEffect, useRef } from 'react'; import { X, User, Mail, Phone, Calendar, Lock, Settings, Image as ImageIcon, Loader2, MessageCircle } from 'lucide-react'; import { User as UserType, UserRole } from '../types'; import { backendApi } from '../services/apiClient'; const ROLE_NAMES: Record = { DIRECTOR: 'Директор', ENGINEER: 'Гл. Инженер', MASTER: 'Мастер', LAWYER: 'Юрист', FINANCIER: 'Финансист', HR_MANAGER: 'HR-менеджер', PR_MANAGER: 'PR-менеджер' }; const DEFAULT_AVATAR = 'https://api.dicebear.com/7.x/avataaars/svg?seed=default'; type TabId = 'profile' | 'photo' | 'security' | 'preferences' | 'messengers'; interface ProfileSettingsModalProps { user: UserType; onClose: () => void; onSave: (user: UserType) => void; } export const ProfileSettingsModal: React.FC = ({ user, onClose, onSave }) => { const [activeTab, setActiveTab] = useState('profile'); const [form, setForm] = useState({ name: user.name || '', email: user.email || '', phone: user.phone || '', birthDate: user.birthDate || '', language: user.language || 'ru', theme: user.theme || 'light', notificationEmail: user.notificationEmail !== false, notificationPush: user.notificationPush !== false, }); const [passwordForm, setPasswordForm] = useState({ oldPassword: '', newPassword: '', confirmPassword: '', }); const [messengers, setMessengers] = useState>( user.messengerLogins || [] ); const [newMessenger, setNewMessenger] = useState<{ messenger: 'Max' | 'Telegram'; login: string }>({ messenger: 'Telegram', login: '' }); const [photoFile, setPhotoFile] = useState(null); const [photoPreview, setPhotoPreview] = useState(null); const [isDragging, setIsDragging] = useState(false); const [isSaving, setIsSaving] = useState(false); const [isUploadingPhoto, setIsUploadingPhoto] = useState(false); const [isChangingPassword, setIsChangingPassword] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const fileInputRef = useRef(null); const getAvatarUrl = () => { if (photoPreview) return photoPreview; if (!user.avatar) return DEFAULT_AVATAR; if (user.avatar.startsWith('http')) return user.avatar; const base = (import.meta.env.VITE_API_BASE_URL || '').replace(/\/api\/?$/, '') || window.location.origin; return `${base}${user.avatar.startsWith('/') ? '' : '/'}${user.avatar}`; }; const avatarUrl = getAvatarUrl(); useEffect(() => { setForm({ name: user.name || '', email: user.email || '', phone: user.phone || '', birthDate: user.birthDate || '', language: user.language || 'ru', theme: user.theme || 'light', notificationEmail: user.notificationEmail !== false, notificationPush: user.notificationPush !== false, }); setMessengers(user.messengerLogins || []); }, [user]); const tabs: { id: TabId; label: string; icon: React.ReactNode }[] = [ { id: 'profile', label: 'Основная информация', icon: }, { id: 'photo', label: 'Фото профиля', icon: }, { id: 'security', label: 'Безопасность', icon: }, { id: 'preferences', label: 'Настройки', icon: }, { id: 'messengers', label: 'Мессенджеры', icon: }, ]; const handleSaveProfile = async () => { try { setIsSaving(true); setError(null); const updated = await backendApi.updateProfile({ name: form.name.trim(), email: form.email.trim() || undefined, phone: form.phone.trim() || undefined, birthDate: form.birthDate || undefined, }); onSave(updated); setSuccess('Профиль сохранён'); setTimeout(() => setSuccess(null), 3000); } catch (err: any) { setError(err.message || 'Ошибка сохранения'); } finally { setIsSaving(false); } }; const handleSavePreferences = async () => { try { setIsSaving(true); setError(null); await backendApi.updatePreferences({ language: form.language, theme: form.theme, notificationEmail: form.notificationEmail, notificationPush: form.notificationPush, }); const updated = await backendApi.getMe(); onSave(updated); setSuccess('Настройки сохранены'); setTimeout(() => setSuccess(null), 3000); } catch (err: any) { setError(err.message || 'Ошибка сохранения'); } finally { setIsSaving(false); } }; const handlePhotoSelect = (file: File | null) => { if (!file) { setPhotoFile(null); setPhotoPreview(null); return; } if (!file.type.startsWith('image/')) { setError('Выберите изображение (JPG, PNG, GIF, WEBP)'); return; } if (file.size > 5 * 1024 * 1024) { setError('Размер файла не должен превышать 5MB'); return; } setError(null); setPhotoFile(file); const reader = new FileReader(); reader.onloadend = () => setPhotoPreview(reader.result as string); reader.readAsDataURL(file); }; const handlePhotoUpload = async () => { if (!photoFile) return; try { setIsUploadingPhoto(true); setError(null); const { photoUrl } = await backendApi.uploadProfilePhoto(photoFile); const updated = await backendApi.getMe(); onSave({ ...updated, avatar: photoUrl }); setPhotoFile(null); setPhotoPreview(null); setSuccess('Фото загружено'); setTimeout(() => setSuccess(null), 3000); } catch (err: any) { setError(err.message || 'Ошибка загрузки фото'); } finally { setIsUploadingPhoto(false); } }; const handleDeletePhoto = async () => { try { setIsUploadingPhoto(true); setError(null); await backendApi.deleteProfilePhoto(); const updated = await backendApi.getMe(); onSave(updated); setPhotoFile(null); setPhotoPreview(null); setSuccess('Фото удалено'); setTimeout(() => setSuccess(null), 3000); } catch (err: any) { setError(err.message || 'Ошибка удаления'); } finally { setIsUploadingPhoto(false); } }; const handleChangePassword = async () => { if (passwordForm.newPassword !== passwordForm.confirmPassword) { setError('Пароли не совпадают'); return; } if (passwordForm.newPassword.length < 8) { setError('Пароль должен содержать не менее 8 символов'); return; } if (!/^(?=.*[a-zA-Z])(?=.*\d)/.test(passwordForm.newPassword)) { setError('Пароль должен содержать буквы и цифры'); return; } try { setIsChangingPassword(true); setError(null); await backendApi.changePassword(passwordForm.oldPassword, passwordForm.newPassword); setPasswordForm({ oldPassword: '', newPassword: '', confirmPassword: '' }); setSuccess('Пароль успешно изменён'); setTimeout(() => setSuccess(null), 3000); } catch (err: any) { setError(err.message || 'Ошибка смены пароля'); } finally { setIsChangingPassword(false); } }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); const file = e.dataTransfer.files?.[0]; if (file) handlePhotoSelect(file); }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }; const handleDragLeave = () => setIsDragging(false); const addMessenger = () => { if (!newMessenger.login.trim()) return; if (messengers.some(m => m.messenger === newMessenger.messenger)) { setError('Этот мессенджер уже добавлен'); return; } setMessengers([...messengers, { ...newMessenger }]); setNewMessenger({ messenger: 'Telegram', login: '' }); }; const removeMessenger = (idx: number) => { setMessengers(messengers.filter((_, i) => i !== idx)); }; return (
e.stopPropagation()}>

Профиль и настройки

Управление вашим аккаунтом

{tabs.map((tab) => ( ))}
{error && (
{error}
)} {success && (
{success}
)} {activeTab === 'profile' && (
setForm(f => ({ ...f, name: e.target.value }))} className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" placeholder="Иванов Иван Иванович" />
setForm(f => ({ ...f, email: e.target.value }))} className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" placeholder="email@example.com" />
setForm(f => ({ ...f, phone: e.target.value }))} className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" placeholder="+7 (999) 123-45-67" />
setForm(f => ({ ...f, birthDate: e.target.value }))} className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" />

Роль: {ROLE_NAMES[user.role]}

)} {activeTab === 'photo' && (
handlePhotoSelect(e.target.files?.[0] || null)} />
{photoFile && ( )} {(user.avatar || photoPreview) && ( )}

JPG, PNG, GIF, WEBP до 5MB

)} {activeTab === 'security' && (
setPasswordForm(f => ({ ...f, oldPassword: e.target.value }))} className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" placeholder="••••••••" />
setPasswordForm(f => ({ ...f, newPassword: e.target.value }))} className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" placeholder="Минимум 8 символов, буквы и цифры" />
setPasswordForm(f => ({ ...f, confirmPassword: e.target.value }))} className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" placeholder="••••••••" />
)} {activeTab === 'preferences' && (
Email уведомления
Push уведомления
)} {activeTab === 'messengers' && (

Логины мессенджеров для связи (информационно)

{messengers.map((m, idx) => (
{m.messenger}: {m.login}
))}
setNewMessenger(m => ({ ...m, login: e.target.value }))} placeholder="@username" className="flex-1 px-3 py-2 border border-slate-200 rounded-xl text-sm" />

Мессенджеры пока только отображаются. Сохранение в профиль будет добавлено.

)}
); };