import React, { useMemo, useState } from 'react'; import { Building, PersonalAccount } from '../../types'; import { Smile, Frown, Meh, MessageCircle, X, Check, UserCheck, Search, User, Phone, Edit2, Save } from 'lucide-react'; export const ResidentsView: React.FC<{ building: Building, setBuilding: React.Dispatch>, isEditing: boolean }> = ({ building, setBuilding, isEditing }) => { const accounts = building.accounts || []; const [showSelectModal, setShowSelectModal] = useState(false); const [selectedContacts, setSelectedContacts] = useState>(new Map()); const [searchTerm, setSearchTerm] = useState(''); const [selectedResident, setSelectedResident] = useState<{ id: string; name: string; apartment: string; phone?: string; accountId?: string; personType?: 'owner' | 'registered'; personIndex?: number } | null>(null); const [isEditingResident, setIsEditingResident] = useState(false); const [editFormData, setEditFormData] = useState({ fullName: '', phone: '' }); const contactsFromAccounts = useMemo(() => { const res: Array<{ name: string; apartment: string; phone?: string; mood: 'happy' | 'angry' | 'neutral'; accountId?: string; personType?: 'owner' | 'registered'; personIndex?: number }> = []; const sentimentToMood = (sentiment?: string): 'happy' | 'angry' | 'neutral' => { if (!sentiment) return 'neutral'; if (sentiment === 'positive' || sentiment === 'loyal') return 'happy'; if (sentiment === 'negative' || sentiment === 'toxic') return 'angry'; return 'neutral'; }; for (const acc of accounts) { const apt = acc.apartmentNumber || ''; // Owners for (let i = 0; i < (acc.owners || []).length; i++) { const owner = acc.owners[i]; if (!owner?.fullName) continue; res.push({ name: owner.fullName, apartment: apt, phone: owner.phone, mood: sentimentToMood(owner.residentProfile?.sentiment), accountId: acc.id, personType: 'owner', personIndex: i }); } // Registered for (let i = 0; i < (acc.registered || []).length; i++) { const reg = acc.registered[i]; if (!reg?.fullName) continue; res.push({ name: reg.fullName, apartment: apt, phone: reg.phone, mood: sentimentToMood(reg.residentProfile?.sentiment), accountId: acc.id, personType: 'registered', personIndex: i }); } } // unique by name+apartment (берем первый найденный) const uniq = new Map(); for (const c of res) { const key = `${c.apartment}::${c.name}`.toLowerCase(); if (!uniq.has(key)) uniq.set(key, c); } return Array.from(uniq.values()); }, [accounts]); // Получаем список жителей, которых еще нет в списке const availableContacts = useMemo(() => { const existing = building.residents || []; const existingKeys = new Set(existing.map(r => `${r.apartment}::${r.name}`.toLowerCase())); return contactsFromAccounts.filter(c => c.name && !existingKeys.has(`${c.apartment}::${c.name}`.toLowerCase())); }, [contactsFromAccounts, building.residents]); // Фильтруем контакты по поисковому запросу const filteredAvailableContacts = useMemo(() => { if (!searchTerm.trim()) return availableContacts; const term = searchTerm.toLowerCase(); return availableContacts.filter(c => c.name.toLowerCase().includes(term) || c.apartment.toLowerCase().includes(term) ); }, [availableContacts, searchTerm]); // Находим данные жителя из лицевых счетов (для получения телефона) const findResidentData = (residentName: string, apartment: string): { phone?: string; accountId?: string; personType?: 'owner' | 'registered'; personIndex?: number } => { // Сначала ищем в contactsFromAccounts (быстрее) const contact = contactsFromAccounts.find(c => c.name === residentName && c.apartment === apartment ); if (contact) { return { phone: contact.phone, accountId: contact.accountId, personType: contact.personType, personIndex: contact.personIndex }; } // Если не нашли, ищем напрямую в accounts for (const acc of accounts) { if (acc.apartmentNumber !== apartment) continue; // Проверяем собственников for (let i = 0; i < (acc.owners || []).length; i++) { const owner = acc.owners[i]; if (owner?.fullName === residentName) { return { phone: owner.phone, accountId: acc.id, personType: 'owner', personIndex: i }; } } // Проверяем прописанных for (let i = 0; i < (acc.registered || []).length; i++) { const reg = acc.registered[i]; if (reg?.fullName === residentName) { return { phone: reg.phone, accountId: acc.id, personType: 'registered', personIndex: i }; } } } return {}; }; const handleOpenSelectModal = () => { setSelectedContacts(new Map()); setShowSelectModal(true); }; const toggleContactSelection = (key: string) => { setSelectedContacts(prev => { const newMap = new Map(prev); if (newMap.has(key)) { newMap.delete(key); } else { newMap.set(key, 'resident'); // По умолчанию житель } return newMap; }); }; const setContactRole = (key: string, role: 'chairman' | 'activist' | 'resident') => { setSelectedContacts(prev => { const newMap = new Map(prev); if (newMap.has(key)) { newMap.set(key, role); } return newMap; }); }; const handleAddSelectedContacts = () => { if (selectedContacts.size === 0) { alert('Выберите хотя бы одного жителя'); return; } setBuilding((prev) => { const existing = prev.residents || []; const existingKeys = new Set(existing.map(r => `${r.apartment}::${r.name}`.toLowerCase())); const toAdd = Array.from(selectedContacts.entries()) .map(([key, role], index) => { const contact = availableContacts.find(c => `${c.apartment}::${c.name}`.toLowerCase() === key ); if (!contact || existingKeys.has(key)) return null; return { id: `acc-${Date.now()}-${index}-${Math.random().toString(36).slice(2, 8)}`, name: contact.name, role: role, apartment: contact.apartment, mood: contact.mood, lastContact: '—', }; }) .filter((item): item is NonNullable => item !== null); if (toAdd.length === 0) return prev; setShowSelectModal(false); setSelectedContacts(new Map()); return { ...prev, residents: [...toAdd, ...existing], isDirty: true }; }); }; return (

Актив дома (Жители)

NPS: {building.nps}
{building.residents.map(r => { const residentData = findResidentData(r.name, r.apartment); return (
{ const data = findResidentData(r.name, r.apartment); setSelectedResident({ id: r.id, name: r.name, apartment: r.apartment, phone: data.phone, accountId: data.accountId, personType: data.personType, personIndex: data.personIndex }); setEditFormData({ fullName: r.name, phone: data.phone || '' }); setIsEditingResident(false); }} >
{r.mood === 'happy' ? : r.mood === 'angry' ? : }

{r.name}

Кв. {r.apartment} •

{isEditing ? ( ) : (

{r.role === 'chairman' ? 'Председатель' : r.role === 'activist' ? 'Активист' : 'Житель'}

)}
{isEditing && ( )}
); })}
{/* Модальное окно выбора жителей */} {showSelectModal && (
setShowSelectModal(false)}>
e.stopPropagation()}>

Выберите жителей для совета дома

Отметьте жителей и назначьте им роли

{/* Поиск */}
setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2.5 rounded-xl border border-slate-200 focus:outline-none focus:ring-2 focus:ring-primary-500 text-sm bg-white" />
{availableContacts.length === 0 ? (

Все жители уже добавлены

) : filteredAvailableContacts.length === 0 ? (

Ничего не найдено

) : (
{filteredAvailableContacts.map((contact) => { const contactData = findResidentData(contact.name, contact.apartment); const key = `${contact.apartment}::${contact.name}`.toLowerCase(); const isSelected = selectedContacts.has(key); const role = selectedContacts.get(key) || 'resident'; return (
toggleContactSelection(key)} className={`w-5 h-5 rounded border-2 flex items-center justify-center flex-shrink-0 mt-0.5 cursor-pointer ${ isSelected ? 'bg-primary-500 border-primary-500' : 'border-slate-300' }`} > {isSelected && ( )}
{contact.mood === 'happy' ? : contact.mood === 'angry' ? : }
{contact.name}
Кв. {contact.apartment}
{contactData.phone && (
{contactData.phone}
)}
{isSelected && (
e.stopPropagation()}>
)}
); })}
)}
{selectedContacts.size > 0 && (
Выбрано жителей: {selectedContacts.size}
)}
)} {/* Модальное окно карточки жителя */} {selectedResident && (
{ setSelectedResident(null); setIsEditingResident(false); }}>
e.stopPropagation()}>

Карточка жителя

Кв. {selectedResident.apartment}

{isEditingResident ? ( <>
setEditFormData({ ...editFormData, fullName: e.target.value })} className="w-full p-3 rounded-xl border border-slate-200 text-sm focus:ring-2 focus:ring-primary-500 outline-none" placeholder="Иванов Иван Иванович" />
setEditFormData({ ...editFormData, phone: e.target.value })} className="w-full p-3 rounded-xl border border-slate-200 text-sm focus:ring-2 focus:ring-primary-500 outline-none" placeholder="+7 (999) 123-45-67" />
) : ( <>

{selectedResident.name}

{selectedResident.phone || 'Не указан'}

Кв. {selectedResident.apartment}

{isEditing && ( )} )}
)}
); };