631 lines
40 KiB
TypeScript
Executable File
631 lines
40 KiB
TypeScript
Executable File
|
||
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<React.SetStateAction<Building>>, isEditing: boolean }> = ({ building, setBuilding, isEditing }) => {
|
||
const accounts = building.accounts || [];
|
||
const [showSelectModal, setShowSelectModal] = useState(false);
|
||
const [selectedContacts, setSelectedContacts] = useState<Map<string, 'chairman' | 'activist' | 'resident'>>(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<string, { name: string; apartment: string; phone?: string; mood: 'happy' | 'angry' | 'neutral'; accountId?: string; personType?: 'owner' | 'registered'; personIndex?: number }>();
|
||
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<typeof item> => item !== null);
|
||
|
||
if (toAdd.length === 0) return prev;
|
||
setShowSelectModal(false);
|
||
setSelectedContacts(new Map());
|
||
return { ...prev, residents: [...toAdd, ...existing], isDirty: true };
|
||
});
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6 animate-fade-in">
|
||
<div className="flex justify-between items-center">
|
||
<h3 className="font-bold text-slate-700 text-sm uppercase">Актив дома (Жители)</h3>
|
||
<span className="text-xs bg-slate-100 px-2 py-1 rounded font-bold text-slate-500">NPS: {building.nps}</span>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 gap-3">
|
||
{building.residents.map(r => {
|
||
const residentData = findResidentData(r.name, r.apartment);
|
||
return (
|
||
<div
|
||
key={r.id}
|
||
className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm flex items-center justify-between cursor-pointer hover:border-primary-300 transition-colors"
|
||
onClick={() => {
|
||
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);
|
||
}}
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<div className={`w-10 h-10 rounded-full flex items-center justify-center text-white font-bold ${r.mood === 'happy' ? 'bg-emerald-400' : r.mood === 'angry' ? 'bg-red-400' : 'bg-slate-400'}`}>
|
||
{r.mood === 'happy' ? <Smile className="w-6 h-6"/> : r.mood === 'angry' ? <Frown className="w-6 h-6"/> : <Meh className="w-6 h-6"/>}
|
||
</div>
|
||
<div className="flex-1">
|
||
<h4 className="font-bold text-slate-800 text-sm">{r.name}</h4>
|
||
<div className="flex items-center gap-2 mt-1">
|
||
<p className="text-xs text-slate-500">
|
||
Кв. {r.apartment} •
|
||
</p>
|
||
{isEditing ? (
|
||
<select
|
||
value={r.role}
|
||
onChange={(e) => {
|
||
setBuilding(prev => ({
|
||
...prev,
|
||
residents: prev.residents.map(res =>
|
||
res.id === r.id
|
||
? { ...res, role: e.target.value as 'chairman' | 'activist' | 'resident' }
|
||
: res
|
||
),
|
||
isDirty: true
|
||
}));
|
||
}}
|
||
onClick={(e) => e.stopPropagation()}
|
||
className="text-xs font-bold px-2 py-0.5 rounded border border-slate-300 bg-white focus:ring-2 focus:ring-primary-500 outline-none"
|
||
>
|
||
<option value="resident">Житель</option>
|
||
<option value="activist">Активист</option>
|
||
<option value="chairman">Председатель</option>
|
||
</select>
|
||
) : (
|
||
<p className="text-xs text-slate-500">
|
||
{r.role === 'chairman' ? 'Председатель' : r.role === 'activist' ? 'Активист' : 'Житель'}
|
||
</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="text-right flex items-center gap-2">
|
||
{isEditing && (
|
||
<button
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
if (confirm('Удалить этого жителя из списка?')) {
|
||
setBuilding(prev => ({
|
||
...prev,
|
||
residents: prev.residents.filter(res => res.id !== r.id),
|
||
isDirty: true
|
||
}));
|
||
}
|
||
}}
|
||
className="p-2 bg-red-50 rounded-full text-red-400 hover:text-red-600 hover:bg-red-100 transition-colors"
|
||
>
|
||
<X className="w-4 h-4"/>
|
||
</button>
|
||
)}
|
||
<button
|
||
onClick={(e) => e.stopPropagation()}
|
||
className="p-2 bg-slate-50 rounded-full text-slate-400 hover:text-primary-600 hover:bg-primary-50 transition-colors"
|
||
>
|
||
<MessageCircle className="w-5 h-5"/>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
<button
|
||
type="button"
|
||
onClick={handleOpenSelectModal}
|
||
disabled={availableContacts.length === 0}
|
||
className="w-full py-3 border border-dashed border-slate-300 rounded-xl text-slate-400 text-sm font-bold hover:bg-slate-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
+ Добавить контакты из лицевых счетов
|
||
</button>
|
||
</div>
|
||
|
||
{/* Модальное окно выбора жителей */}
|
||
{showSelectModal && (
|
||
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm animate-fade-in" onClick={() => setShowSelectModal(false)}>
|
||
<div className="bg-white rounded-2xl w-full max-w-3xl max-h-[90vh] overflow-hidden shadow-2xl animate-slide-up flex flex-col" onClick={e => e.stopPropagation()}>
|
||
<div className="p-6 border-b border-slate-200 flex justify-between items-center">
|
||
<div>
|
||
<h3 className="text-lg font-bold text-slate-800">Выберите жителей для совета дома</h3>
|
||
<p className="text-sm text-slate-500 mt-1">Отметьте жителей и назначьте им роли</p>
|
||
</div>
|
||
<button onClick={() => {
|
||
setShowSelectModal(false);
|
||
setSearchTerm('');
|
||
}} className="p-2 hover:bg-slate-100 rounded-full">
|
||
<X className="w-5 h-5 text-slate-400"/>
|
||
</button>
|
||
</div>
|
||
|
||
{/* Поиск */}
|
||
<div className="px-6 pt-4 pb-2">
|
||
<div className="relative">
|
||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
||
<input
|
||
type="text"
|
||
placeholder="Поиск по имени или квартире..."
|
||
value={searchTerm}
|
||
onChange={(e) => 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"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex-1 overflow-auto p-6">
|
||
{availableContacts.length === 0 ? (
|
||
<div className="text-center py-12 text-slate-400">
|
||
<UserCheck className="w-12 h-12 mx-auto mb-3 opacity-20"/>
|
||
<p>Все жители уже добавлены</p>
|
||
</div>
|
||
) : filteredAvailableContacts.length === 0 ? (
|
||
<div className="text-center py-12 text-slate-400">
|
||
<Search className="w-12 h-12 mx-auto mb-3 opacity-20"/>
|
||
<p>Ничего не найдено</p>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-2">
|
||
{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 (
|
||
<div
|
||
key={key}
|
||
className={`p-4 border-2 rounded-xl transition-all ${
|
||
isSelected
|
||
? 'border-primary-500 bg-primary-50'
|
||
: 'border-slate-200 hover:border-slate-300'
|
||
}`}
|
||
>
|
||
<div className="flex items-start justify-between gap-4">
|
||
<div className="flex items-start gap-3 flex-1">
|
||
<div
|
||
onClick={() => 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 && (
|
||
<Check className="w-3 h-3 text-white"/>
|
||
)}
|
||
</div>
|
||
<div className="flex items-center gap-3 flex-1">
|
||
<div className={`w-10 h-10 rounded-full flex items-center justify-center text-white font-bold ${
|
||
contact.mood === 'happy' ? 'bg-emerald-400' : contact.mood === 'angry' ? 'bg-red-400' : 'bg-slate-400'
|
||
}`}>
|
||
{contact.mood === 'happy' ? <Smile className="w-6 h-6"/> : contact.mood === 'angry' ? <Frown className="w-6 h-6"/> : <Meh className="w-6 h-6"/>}
|
||
</div>
|
||
<div className="flex-1">
|
||
<div className="font-bold text-slate-800">{contact.name}</div>
|
||
<div className="text-sm text-slate-500">Кв. {contact.apartment}</div>
|
||
{contactData.phone && (
|
||
<div className="text-xs text-slate-400 flex items-center gap-1 mt-0.5">
|
||
<Phone className="w-3 h-3"/>
|
||
{contactData.phone}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{isSelected && (
|
||
<div className="flex items-center gap-2 flex-shrink-0" onClick={(e) => e.stopPropagation()}>
|
||
<label className="text-xs text-slate-500 font-bold whitespace-nowrap">Роль:</label>
|
||
<select
|
||
value={role}
|
||
onChange={(e) => setContactRole(key, e.target.value as 'chairman' | 'activist' | 'resident')}
|
||
onClick={(e) => e.stopPropagation()}
|
||
className="px-3 py-1.5 border border-slate-300 rounded-lg text-sm font-bold text-slate-800 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||
>
|
||
<option value="resident">Житель</option>
|
||
<option value="activist">Активист</option>
|
||
<option value="chairman">Председатель</option>
|
||
</select>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{selectedContacts.size > 0 && (
|
||
<div className="p-6 border-t border-slate-200 bg-slate-50 flex justify-between items-center">
|
||
<div className="text-sm text-slate-600">
|
||
Выбрано жителей: <span className="font-bold text-slate-800">{selectedContacts.size}</span>
|
||
</div>
|
||
<div className="flex gap-3">
|
||
<button
|
||
onClick={() => {
|
||
setShowSelectModal(false);
|
||
setSelectedContacts(new Map());
|
||
setSearchTerm('');
|
||
}}
|
||
className="px-4 py-2 text-slate-600 font-bold bg-white border border-slate-200 rounded-xl hover:bg-slate-50"
|
||
>
|
||
Отмена
|
||
</button>
|
||
<button
|
||
onClick={handleAddSelectedContacts}
|
||
className="px-4 py-2 bg-primary-600 text-white font-bold rounded-xl hover:bg-primary-700 flex items-center gap-2"
|
||
>
|
||
<UserCheck className="w-4 h-4"/> Добавить выбранных
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Модальное окно карточки жителя */}
|
||
{selectedResident && (
|
||
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm animate-fade-in" onClick={() => {
|
||
setSelectedResident(null);
|
||
setIsEditingResident(false);
|
||
}}>
|
||
<div className="bg-white rounded-2xl w-full max-w-md shadow-2xl animate-slide-up" onClick={e => e.stopPropagation()}>
|
||
<div className="p-6 border-b border-slate-200 flex justify-between items-center">
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-12 h-12 rounded-full bg-primary-100 flex items-center justify-center">
|
||
<User className="w-6 h-6 text-primary-600"/>
|
||
</div>
|
||
<div>
|
||
<h3 className="text-lg font-bold text-slate-800">Карточка жителя</h3>
|
||
<p className="text-xs text-slate-500">Кв. {selectedResident.apartment}</p>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => {
|
||
setSelectedResident(null);
|
||
setIsEditingResident(false);
|
||
}}
|
||
className="p-2 hover:bg-slate-100 rounded-full"
|
||
>
|
||
<X className="w-5 h-5 text-slate-400"/>
|
||
</button>
|
||
</div>
|
||
|
||
<div className="p-6 space-y-4">
|
||
{isEditingResident ? (
|
||
<>
|
||
<div>
|
||
<label className="block text-xs font-bold text-slate-500 uppercase mb-2">
|
||
ФИО *
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={editFormData.fullName}
|
||
onChange={(e) => 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="Иванов Иван Иванович"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-xs font-bold text-slate-500 uppercase mb-2">
|
||
Телефон
|
||
</label>
|
||
<input
|
||
type="tel"
|
||
value={editFormData.phone}
|
||
onChange={(e) => 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"
|
||
/>
|
||
</div>
|
||
<div className="flex gap-3 pt-2">
|
||
<button
|
||
onClick={() => {
|
||
// Сохраняем изменения
|
||
if (!editFormData.fullName.trim()) {
|
||
alert('ФИО обязательно для заполнения');
|
||
return;
|
||
}
|
||
|
||
// Обновляем данные в accounts
|
||
if (selectedResident.accountId && selectedResident.personType && selectedResident.personIndex !== undefined) {
|
||
const account = accounts.find(a => a.id === selectedResident.accountId);
|
||
if (account) {
|
||
if (selectedResident.personType === 'owner') {
|
||
const updatedOwners = [...account.owners];
|
||
updatedOwners[selectedResident.personIndex] = {
|
||
...updatedOwners[selectedResident.personIndex],
|
||
fullName: editFormData.fullName,
|
||
phone: editFormData.phone
|
||
};
|
||
setBuilding(prev => ({
|
||
...prev,
|
||
accounts: prev.accounts.map(acc =>
|
||
acc.id === account.id
|
||
? { ...acc, owners: updatedOwners }
|
||
: acc
|
||
),
|
||
residents: prev.residents.map(res =>
|
||
res.id === selectedResident.id
|
||
? { ...res, name: editFormData.fullName }
|
||
: res
|
||
),
|
||
isDirty: true
|
||
}));
|
||
} else if (selectedResident.personType === 'registered') {
|
||
const updatedRegistered = [...(account.registered || [])];
|
||
updatedRegistered[selectedResident.personIndex] = {
|
||
...updatedRegistered[selectedResident.personIndex],
|
||
fullName: editFormData.fullName,
|
||
phone: editFormData.phone
|
||
};
|
||
setBuilding(prev => ({
|
||
...prev,
|
||
accounts: prev.accounts.map(acc =>
|
||
acc.id === account.id
|
||
? { ...acc, registered: updatedRegistered }
|
||
: acc
|
||
),
|
||
residents: prev.residents.map(res =>
|
||
res.id === selectedResident.id
|
||
? { ...res, name: editFormData.fullName }
|
||
: res
|
||
),
|
||
isDirty: true
|
||
}));
|
||
}
|
||
}
|
||
} else {
|
||
// Если не найдено в accounts, просто обновляем residents
|
||
setBuilding(prev => ({
|
||
...prev,
|
||
residents: prev.residents.map(res =>
|
||
res.id === selectedResident.id
|
||
? { ...res, name: editFormData.fullName }
|
||
: res
|
||
),
|
||
isDirty: true
|
||
}));
|
||
}
|
||
|
||
setSelectedResident({
|
||
...selectedResident,
|
||
name: editFormData.fullName,
|
||
phone: editFormData.phone
|
||
});
|
||
setIsEditingResident(false);
|
||
}}
|
||
className="flex-1 bg-primary-600 text-white px-4 py-2.5 rounded-xl font-bold text-sm hover:bg-primary-700 transition-colors flex items-center justify-center gap-2"
|
||
>
|
||
<Save className="w-4 h-4"/> Сохранить
|
||
</button>
|
||
<button
|
||
onClick={() => {
|
||
setIsEditingResident(false);
|
||
setEditFormData({ fullName: selectedResident.name, phone: selectedResident.phone || '' });
|
||
}}
|
||
className="px-4 py-2.5 bg-slate-100 text-slate-600 rounded-xl font-bold text-sm hover:bg-slate-200 transition-colors"
|
||
>
|
||
Отмена
|
||
</button>
|
||
</div>
|
||
</>
|
||
) : (
|
||
<>
|
||
<div className="space-y-3">
|
||
<div>
|
||
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">
|
||
ФИО
|
||
</label>
|
||
<p className="text-sm font-bold text-slate-800">{selectedResident.name}</p>
|
||
</div>
|
||
<div>
|
||
<label className="block text-xs font-bold text-slate-500 uppercase mb-1 flex items-center gap-1">
|
||
<Phone className="w-3 h-3"/>
|
||
Телефон
|
||
</label>
|
||
<p className="text-sm text-slate-700">{selectedResident.phone || 'Не указан'}</p>
|
||
</div>
|
||
<div>
|
||
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">
|
||
Квартира
|
||
</label>
|
||
<p className="text-sm text-slate-700">Кв. {selectedResident.apartment}</p>
|
||
</div>
|
||
</div>
|
||
{isEditing && (
|
||
<button
|
||
onClick={() => setIsEditingResident(true)}
|
||
className="w-full mt-4 bg-primary-600 text-white px-4 py-2.5 rounded-xl font-bold text-sm hover:bg-primary-700 transition-colors flex items-center justify-center gap-2"
|
||
>
|
||
<Edit2 className="w-4 h-4"/> Редактировать
|
||
</button>
|
||
)}
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|