import React, { useState, useEffect } from 'react'; import Sidebar from './components/Sidebar'; import SearchFilters from './components/SearchFilters'; import EmployeeTable from './components/EmployeeTable'; import DetailModal from './components/DetailModal'; import AdminPanel from './components/AdminPanel'; import { SunIcon, MoonIcon, LoginIcon, LogoutIcon } from './utils/svgs'; // Normalize Russian phone numbers to 10 digits (strip +7, 7, 8, spaces, dashes, brackets) const getPhoneDigits = (phone) => { if (!phone) return ''; let digits = phone.replace(/\D/g, ''); // If it starts with 7 or 8 and is 11 digits, strip the first digit (country code) if (digits.length === 11 && (digits.startsWith('7') || digits.startsWith('8'))) { return digits.substring(1); } return digits; }; // Normalize search query for phone matching const getQueryPhoneDigits = (q) => { let digits = q.replace(/\D/g, ''); // Strip leading 7 or 8 if the query starts with them to allow matching the 10-digit local format if (digits.startsWith('7') || digits.startsWith('8')) { return digits.substring(1); } return digits; }; const App = () => { const [employees, setEmployees] = useState([]); const [departments, setDepartments] = useState([]); const [companies, setCompanies] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [selectedDept, setSelectedDept] = useState(null); const [selectedCompany, setSelectedCompany] = useState(null); const [activeView, setActiveView] = useState('directory'); // 'directory', 'login', 'admin' const [selectedEmployee, setSelectedEmployee] = useState(null); // Auth state const [token, setToken] = useState(localStorage.getItem('token') || ''); const [username, setUsername] = useState(localStorage.getItem('username') || ''); // Login form state const [loginForm, setLoginForm] = useState({ username: '', password: '' }); const [loginError, setLoginError] = useState(''); // Theme state const [theme, setTheme] = useState(localStorage.getItem('theme') || 'light'); // Fetch initial data from server const fetchData = async () => { try { const empRes = await fetch('/api/employees'); const empData = await empRes.json(); if (empRes.ok) setEmployees(empData); const deptRes = await fetch('/api/departments'); const deptData = await deptRes.json(); if (deptRes.ok) setDepartments(deptData); const compRes = await fetch('/api/companies'); const compData = await compRes.json(); if (compRes.ok) setCompanies(compData); } catch (error) { console.error('Error fetching data:', error); } }; useEffect(() => { fetchData(); }, []); // Update HTML data-theme attribute for CSS styling useEffect(() => { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); }, [theme]); const toggleTheme = () => { setTheme(prev => prev === 'light' ? 'dark' : 'light'); }; // Auth Handlers const handleLoginSubmit = async (e) => { e.preventDefault(); setLoginError(''); try { const res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(loginForm) }); const data = await res.json(); if (res.ok) { setToken(data.token); setUsername(data.username); localStorage.setItem('token', data.token); localStorage.setItem('username', data.username); setLoginForm({ username: '', password: '' }); setActiveView('admin'); } else { setLoginError(data.error || 'Ошибка входа'); } } catch (err) { setLoginError('Сетевая ошибка при авторизации'); } }; const handleLogout = () => { setToken(''); setUsername(''); localStorage.removeItem('token'); localStorage.removeItem('username'); setActiveView('directory'); }; // Client-side search and filtering const filteredEmployees = employees.filter((emp) => { // 1. Company filter if (selectedCompany && emp.company_id !== selectedCompany) return false; // 2. Department filter if (selectedDept && emp.department_id !== selectedDept) return false; // 3. Search query filter (Case-insensitive, fragments, normalized phone) if (searchQuery.trim()) { const queryClean = searchQuery.toLowerCase().trim(); const queryDigits = getQueryPhoneDigits(queryClean); // Match FIO fragments (full name parts) const nameMatch = emp.name.toLowerCase().includes(queryClean); // Match phone digits normalized const empPhoneDigits = getPhoneDigits(emp.phone); const phoneMatch = queryDigits.length > 0 && empPhoneDigits.includes(queryDigits); // Match internal phone const empInternalPhoneDigits = getPhoneDigits(emp.internal_phone); const internalPhoneMatch = queryDigits.length > 0 && empInternalPhoneDigits.includes(queryDigits); const internalPhoneStringMatch = emp.internal_phone && emp.internal_phone.toLowerCase().includes(queryClean); // Secondary match parameters (Job title, Company, Location) const titleMatch = emp.job_title.toLowerCase().includes(queryClean); const companyMatch = emp.company_name && emp.company_name.toLowerCase().includes(queryClean); const deptMatch = emp.department_name && emp.department_name.toLowerCase().includes(queryClean); const buildingMatch = emp.building && emp.building.toLowerCase().includes(queryClean); // Match email fragment (by start, middle, domain, part before @) const emailMatch = emp.email && emp.email.toLowerCase().includes(queryClean); return nameMatch || phoneMatch || internalPhoneMatch || internalPhoneStringMatch || titleMatch || companyMatch || deptMatch || buildingMatch || emailMatch; } return true; }); return (
{/* Header bar */}
setActiveView('directory')}>

ГИДРОМОНТАЖ

Адресная книга
{/* Theme Toggle Button */} {/* Admin Portal Toggle */} {token ? ( <> {activeView === 'admin' ? ( ) : ( )} ) : ( activeView === 'login' ? ( ) : ( ) )}
{/* Main View Area */}
{/* VIEW 1: Public Directory Search */} {activeView === 'directory' && (
)} {/* VIEW 2: Admin Authentication Form */} {activeView === 'login' && (

Вход в панель управления

Доступ только для администраторов сети

{loginError && (
{loginError}
)}
setLoginForm({ ...loginForm, username: e.target.value })} required />
setLoginForm({ ...loginForm, password: e.target.value })} required />
)} {/* VIEW 3: Admin CRUD Dashboard */} {activeView === 'admin' && token && ( )}
{/* Details Profile View Modal */} {selectedEmployee && ( setSelectedEmployee(null)} /> )} {/* Corporate Footer */}
); }; export default App;