import React, { useState, useEffect } from 'react'; import { authFetch } from '../../services/apiClient'; import { Monitor, Coffee, Package, Clock, CheckCircle2, QrCode, Plus, X, Trash2, ArrowRightLeft, History } from 'lucide-react'; import { OfficeEquipment, Employee, OfficeEquipmentHistoryItem } from '../../types'; // Компонент для отображения количества на складе const InventoryCountDisplay: React.FC<{ equipmentName: string }> = ({ equipmentName }) => { const [inventoryCount, setInventoryCount] = useState('Проверка...'); useEffect(() => { const fetchCount = async () => { try { const response = await authFetch('/api/office/inventory'); if (response.ok) { const data = await response.json(); const matchingItem = data.find((item: any) => (item.name || '').toLowerCase() === (equipmentName || '').toLowerCase() ); if (matchingItem) { setInventoryCount(`${matchingItem.quantity} ${matchingItem.unit || 'шт.'}`); } else { setInventoryCount('0 шт.'); } } } catch (error) { console.error('Ошибка загрузки склада:', error); setInventoryCount('Ошибка'); } }; fetchCount(); }, [equipmentName]); return {inventoryCount}; }; export const FacilityManagement: React.FC = () => { const [equipment, setEquipment] = useState([]); const [loading, setLoading] = useState(true); const [showCreateModal, setShowCreateModal] = useState(false); const [showViewModal, setShowViewModal] = useState(false); const [showWriteOffModal, setShowWriteOffModal] = useState(false); const [showTransferModal, setShowTransferModal] = useState(false); const [selectedEquipment, setSelectedEquipment] = useState(null); const [writeOffFormData, setWriteOffFormData] = useState({ reason: '' }); const [employees, setEmployees] = useState<{ id: string; name: string; position?: string }[]>([]); const [assignedToDropdownOpen, setAssignedToDropdownOpen] = useState(false); const [assignedToFilter, setAssignedToFilter] = useState(''); const [equipmentHistory, setEquipmentHistory] = useState([]); const [transferToFilter, setTransferToFilter] = useState(''); const [transferDropdownOpen, setTransferDropdownOpen] = useState(false); const [formData, setFormData] = useState({ name: '', type: 'pc' as 'pc' | 'laptop' | 'air_conditioner' | 'printer' | 'other', brand: '', model: '', serialNumber: '', assignedTo: '', purchaseDate: '', warrantyUntil: '', condition: 'good' as 'good' | 'fair' | 'poor', notes: '', nextMaintenanceDate: '' }); useEffect(() => { fetchEquipment(); fetchEmployees(); }, []); useEffect(() => { if (showViewModal && selectedEquipment?.id) { fetchEquipmentHistory(selectedEquipment.id); } }, [showViewModal, selectedEquipment?.id]); const fetchEmployees = async () => { try { const response = await authFetch('/api/employees'); if (response.ok) { const data = await response.json(); const activeEmployees = data .filter((emp: Employee) => !emp.status || emp.status === 'active') .map((emp: Employee) => ({ id: emp.id, name: emp.name, position: emp.position || '' })) .sort((a: { name: string }, b: { name: string }) => a.name.localeCompare(b.name)); setEmployees(activeEmployees); } } catch (error) { console.error('Ошибка загрузки сотрудников:', error); } }; const fetchEquipment = async () => { try { const response = await authFetch('/api/office/equipment'); if (response.ok) { const data = await response.json(); // Преобразуем snake_case в camelCase const normalizedData = data.map((eq: any) => ({ id: eq.id, name: eq.name || '', type: eq.type || 'other', brand: eq.brand, model: eq.model, serialNumber: eq.serial_number || eq.serialNumber || '', assignedTo: eq.assigned_to || eq.assignedTo, purchaseDate: eq.purchase_date || eq.purchaseDate, warrantyUntil: eq.warranty_until || eq.warrantyUntil, nextMaintenanceDate: eq.next_maintenance_date || eq.nextMaintenanceDate, condition: eq.condition || 'good', notes: eq.notes, createdAt: eq.created_at, updatedAt: eq.updated_at })); setEquipment(normalizedData); } } catch (error) { console.error('Ошибка загрузки оборудования:', error); } finally { setLoading(false); } }; const handleCreateEquipment = async () => { try { if (!formData.name || !formData.name.trim()) { alert('Пожалуйста, укажите название оборудования'); return; } const response = await authFetch('/api/office/equipment', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: formData.name, type: formData.type, brand: formData.brand || null, model: formData.model || null, serial_number: formData.serialNumber || null, assigned_to: formData.assignedTo || null, purchase_date: formData.purchaseDate || null, warranty_until: formData.warrantyUntil || null, next_maintenance_date: formData.nextMaintenanceDate || null, condition: formData.condition || 'good', notes: formData.notes || null }) }); if (response.ok) { const newEquipment = await response.json(); // Преобразуем snake_case в camelCase const normalizedEquipment = { id: newEquipment.id, name: newEquipment.name || formData.name, type: newEquipment.type || formData.type, brand: newEquipment.brand || formData.brand, model: newEquipment.model || formData.model, serialNumber: newEquipment.serial_number || formData.serialNumber || '', assignedTo: newEquipment.assigned_to || formData.assignedTo, purchaseDate: newEquipment.purchase_date || formData.purchaseDate, warrantyUntil: newEquipment.warranty_until || formData.warrantyUntil, nextMaintenanceDate: newEquipment.next_maintenance_date || formData.nextMaintenanceDate, condition: newEquipment.condition || formData.condition, notes: newEquipment.notes || formData.notes, createdAt: newEquipment.created_at, updatedAt: newEquipment.updated_at }; setEquipment([normalizedEquipment, ...equipment]); setShowCreateModal(false); setFormData({ name: '', type: 'pc', brand: '', model: '', serialNumber: '', assignedTo: '', purchaseDate: '', warrantyUntil: '', condition: 'good', notes: '', nextMaintenanceDate: '' }); } else { const errorData = await response.json().catch(() => ({ error: 'Неизвестная ошибка' })); alert(`Ошибка создания оборудования: ${errorData.error || 'Неизвестная ошибка'}`); } } catch (error: any) { console.error('Ошибка создания оборудования:', error); alert(`Ошибка создания оборудования: ${error.message || 'Неизвестная ошибка'}`); } }; const fetchEquipmentHistory = async (equipmentId: number) => { try { const response = await authFetch(`/api/office/equipment/${equipmentId}/history`); if (response.ok) { const data = await response.json(); setEquipmentHistory(Array.isArray(data) ? data : []); } else { setEquipmentHistory([]); } } catch (error) { console.error('Ошибка загрузки истории:', error); setEquipmentHistory([]); } }; const handleCheckEquipment = (eq: OfficeEquipment) => { setSelectedEquipment(eq); setFormData({ name: eq.name || '', type: eq.type || 'other', brand: eq.brand || '', model: eq.model || '', serialNumber: eq.serialNumber || '', assignedTo: eq.assignedTo || '', purchaseDate: eq.purchaseDate || '', warrantyUntil: eq.warrantyUntil || '', condition: eq.condition || 'good', notes: eq.notes || '', nextMaintenanceDate: eq.nextMaintenanceDate || '' }); setShowViewModal(true); }; const handleUpdateEquipment = async () => { if (!selectedEquipment) return; try { if (!formData.name || !formData.name.trim()) { alert('Пожалуйста, укажите название оборудования'); return; } const response = await authFetch(`/api/office/equipment/${selectedEquipment.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: formData.name, type: formData.type, brand: formData.brand || null, model: formData.model || null, serialNumber: formData.serialNumber || null, purchaseDate: formData.purchaseDate || null, warrantyUntil: formData.warrantyUntil || null, nextMaintenanceDate: formData.nextMaintenanceDate || null, condition: formData.condition || 'good', notes: formData.notes || null }) }); if (response.ok) { fetchEquipment(); setShowViewModal(false); setSelectedEquipment(null); alert('Оборудование успешно обновлено'); } else { const errorData = await response.json().catch(() => ({ error: 'Неизвестная ошибка' })); alert(`Ошибка обновления: ${errorData.error || 'Неизвестная ошибка'}`); } } catch (error: any) { console.error('Ошибка обновления оборудования:', error); alert(`Ошибка: ${error.message || 'Неизвестная ошибка'}`); } }; const getEquipmentIcon = (type: string) => { switch (type) { case 'laptop': return ; case 'air_conditioner': return ; default: return ; } }; const getTypeLabel = (type: string) => { const labels = { pc: 'ПК', laptop: 'Ноутбук', air_conditioner: 'Кондиционер', printer: 'Принтер', other: 'Другое' }; return labels[type as keyof typeof labels] || type; }; return (

Основные средства (ОС)

{loading ? (
Загрузка...
) : equipment && equipment.length > 0 ? (
{equipment.map(asset => { if (!asset || !asset.id) return null; const needsMaintenance = asset.nextMaintenanceDate && new Date(asset.nextMaintenanceDate) <= new Date(); return (
{getEquipmentIcon(asset.type)}

{asset.name}

{asset.serialNumber ? `S/N: ${asset.serialNumber}` : getTypeLabel(asset.type)}

{asset.assignedTo || 'Свободен'}

{asset.condition === 'good' ? 'Исправен' : asset.condition === 'fair' ? 'Требует ТО' : 'Неисправен'} {needsMaintenance && ( Требуется ТО )}
); }).filter(Boolean)}
) : (
Нет оборудования
)} {/* Create Equipment Modal */} {showCreateModal && (

Добавить оборудование

setFormData({ ...formData, name: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" placeholder="Например: Ноутбук Dell" required />
setFormData({ ...formData, brand: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" />
setFormData({ ...formData, model: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" />
setFormData({ ...formData, serialNumber: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" />
{ setAssignedToFilter(e.target.value); setAssignedToDropdownOpen(true); }} onFocus={() => { setAssignedToFilter(formData.assignedTo); setAssignedToDropdownOpen(true); }} onBlur={() => { setTimeout(() => { setAssignedToDropdownOpen(false); setAssignedToFilter(formData.assignedTo); }, 200); }} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" placeholder="Начните вводить имя..." /> {assignedToDropdownOpen && (
{employees .filter((emp) => emp.name.toLowerCase().includes(assignedToFilter.toLowerCase()) || (emp.position && emp.position.toLowerCase().includes(assignedToFilter.toLowerCase())) ) .map((emp) => ( ))} {formData.assignedTo && !employees.some((e) => e.name === formData.assignedTo) && (formData.assignedTo.toLowerCase().includes(assignedToFilter.toLowerCase()) || !assignedToFilter) && ( )}
)} {employees.length === 0 && (

Загрузка списка сотрудников...

)}
setFormData({ ...formData, purchaseDate: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" />
setFormData({ ...formData, warrantyUntil: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" />
setFormData({ ...formData, nextMaintenanceDate: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" />