Files
mkd/components/building/DynamicInspectionSections.tsx
2026-02-04 00:17:04 +05:00

796 lines
49 KiB
TypeScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from 'react';
import { InspectionSection, InspectionCommonSection, InspectionMKDElement, Building } from '../../types';
import {
FLOOR_ELEMENT_NAMES,
ENTRANCE_ELEMENT_NAMES,
COMMON_SECTION_ELEMENTS,
buildElementsFromNames
} from '../../inspectionElementTemplates';
import {
ChevronDown,
ChevronUp,
Plus,
X,
Trash2,
Camera,
Image as ImageIcon,
Gauge,
DoorClosed,
Building2,
Save,
Layers,
Paperclip,
ListPlus
} from 'lucide-react';
interface DynamicInspectionSectionsProps {
sections: InspectionSection[];
commonSections: InspectionCommonSection[];
building: Building;
onUpdateSections: (sections: InspectionSection[]) => void;
onUpdateCommonSections: (commonSections: InspectionCommonSection[]) => void;
onSaveToBuilding?: (entranceData: any, floorData: any) => void; // Сохранение данных в building
}
export const DynamicInspectionSections: React.FC<DynamicInspectionSectionsProps> = ({
sections,
commonSections,
building,
onUpdateSections,
onUpdateCommonSections,
onSaveToBuilding
}) => {
const [activeTab, setActiveTab] = useState<string | null>(sections.length > 0 ? sections[0].id : null);
const [editingElement, setEditingElement] = useState<{ sectionId: string; elementId: string | null } | null>(null);
const [editingEntranceTech, setEditingEntranceTech] = useState<number | null>(null); // Редактирование тех. характеристик подъезда
// Группируем секции по типу
const entranceSections = sections.filter(s => s.type === 'entrance');
const floorSections = sections.filter(s => s.type === 'floor');
const liftSections = sections.filter(s => s.type === 'lift');
const handleAddElement = (sectionId: string, isCommon: boolean = false) => {
const newElement: InspectionMKDElement = {
id: `element-${Date.now()}`,
name: '',
generalStatus: 'NOT_SELECTED',
electroStatus: 'NOT_SELECTED',
weldingStatus: 'NOT_SELECTED',
repairType: '',
mainPhoto: undefined,
additionalPhotos: []
};
if (isCommon) {
const updated = commonSections.map(s =>
s.id === sectionId
? { ...s, elements: [...s.elements, newElement] }
: s
);
onUpdateCommonSections(updated);
} else {
const updated = sections.map(s =>
s.id === sectionId
? { ...s, elements: [...s.elements, newElement] }
: s
);
onUpdateSections(updated);
// Сохраняем элементы этажа в building, если это секция этажа
const section = sections.find(s => s.id === sectionId);
if (section && section.type === 'floor') {
const match = sectionId.match(/entrance-(\d+)-floor-(\d+)/);
if (match && onSaveToBuilding) {
const entranceNum = parseInt(match[1]);
const floorNum = parseInt(match[2]);
const updatedSection = updated.find(s => s.id === sectionId);
if (updatedSection) {
onSaveToBuilding(null, {
entranceNumber: entranceNum,
floorNumber: floorNum,
elements: updatedSection.elements
});
}
}
}
}
setEditingElement({ sectionId, elementId: newElement.id });
};
const handleAddFromTemplate = (sectionId: string, isCommon: boolean = false) => {
const existingNames = new Set(
isCommon
? (commonSections.find(s => s.id === sectionId)?.elements || []).map(e => e.name)
: (sections.find(s => s.id === sectionId)?.elements || []).map(e => e.name)
);
let namesToAdd: string[] = [];
let prefix = sectionId;
if (isCommon) {
const section = commonSections.find(s => s.id === sectionId);
if (section) {
const templateNames = COMMON_SECTION_ELEMENTS[section.title];
if (templateNames) namesToAdd = templateNames.filter(n => !existingNames.has(n));
}
} else {
const section = sections.find(s => s.id === sectionId);
if (section) {
if (section.type === 'floor') {
const match = sectionId.match(/entrance-(\d+)-floor-(\d+)/);
if (match) {
namesToAdd = FLOOR_ELEMENT_NAMES.filter(n => !existingNames.has(n));
prefix = `entrance-${match[1]}-floor-${match[2]}`;
}
} else if (section.type === 'entrance') {
namesToAdd = ENTRANCE_ELEMENT_NAMES.filter(n => !existingNames.has(n));
prefix = sectionId;
}
}
}
if (namesToAdd.length === 0) return;
const newElements = buildElementsFromNames(namesToAdd, prefix);
if (isCommon) {
const updated = commonSections.map(s =>
s.id === sectionId ? { ...s, elements: [...s.elements, ...newElements] } : s
);
onUpdateCommonSections(updated);
} else {
const updated = sections.map(s =>
s.id === sectionId ? { ...s, elements: [...s.elements, ...newElements] } : s
);
onUpdateSections(updated);
const section = sections.find(s => s.id === sectionId);
if (section?.type === 'floor') {
const match = sectionId.match(/entrance-(\d+)-floor-(\d+)/);
if (match && onSaveToBuilding) {
const entranceNum = parseInt(match[1]);
const floorNum = parseInt(match[2]);
const updatedSection = updated.find(s => s.id === sectionId);
if (updatedSection) {
onSaveToBuilding(null, {
entranceNumber: entranceNum,
floorNumber: floorNum,
elements: updatedSection.elements
});
}
}
}
}
};
const handleUpdateElement = (sectionId: string, elementId: string, updates: Partial<InspectionMKDElement>, isCommon: boolean = false) => {
if (isCommon) {
const updated = commonSections.map(s =>
s.id === sectionId
? {
...s,
elements: s.elements.map(el =>
el.id === elementId ? { ...el, ...updates } : el
)
}
: s
);
onUpdateCommonSections(updated);
} else {
const updated = sections.map(s =>
s.id === sectionId
? {
...s,
elements: s.elements.map(el =>
el.id === elementId ? { ...el, ...updates } : el
)
}
: s
);
onUpdateSections(updated);
// Сохраняем элементы этажа в building, если это секция этажа
const section = sections.find(s => s.id === sectionId);
if (section && section.type === 'floor') {
// Извлекаем номер подъезда и этажа из sectionId (формат: entrance-{num}-floor-{floor})
const match = sectionId.match(/entrance-(\d+)-floor-(\d+)/);
if (match && onSaveToBuilding) {
const entranceNum = parseInt(match[1]);
const floorNum = parseInt(match[2]);
const updatedSection = updated.find(s => s.id === sectionId);
if (updatedSection) {
onSaveToBuilding(null, {
entranceNumber: entranceNum,
floorNumber: floorNum,
elements: updatedSection.elements
});
}
}
}
}
};
const handleDeleteElement = (sectionId: string, elementId: string, isCommon: boolean = false) => {
if (isCommon) {
const updated = commonSections.map(s =>
s.id === sectionId
? { ...s, elements: s.elements.filter(el => el.id !== elementId) }
: s
);
onUpdateCommonSections(updated);
} else {
const updated = sections.map(s =>
s.id === sectionId
? { ...s, elements: s.elements.filter(el => el.id !== elementId) }
: s
);
onUpdateSections(updated);
// Сохраняем элементы этажа в building, если это секция этажа
const section = sections.find(s => s.id === sectionId);
if (section && section.type === 'floor') {
const match = sectionId.match(/entrance-(\d+)-floor-(\d+)/);
if (match && onSaveToBuilding) {
const entranceNum = parseInt(match[1]);
const floorNum = parseInt(match[2]);
const updatedSection = updated.find(s => s.id === sectionId);
if (updatedSection) {
onSaveToBuilding(null, {
entranceNumber: entranceNum,
floorNumber: floorNum,
elements: updatedSection.elements
});
}
}
}
}
};
const handleToggleSection = (sectionId: string, isCommon: boolean = false) => {
if (isCommon) {
const updated = commonSections.map(s =>
s.id === sectionId ? { ...s, isCollapsed: !s.isCollapsed } : s
);
onUpdateCommonSections(updated);
} else {
const updated = sections.map(s =>
s.id === sectionId ? { ...s, isCollapsed: !s.isCollapsed } : s
);
onUpdateSections(updated);
}
};
const renderElement = (element: InspectionMKDElement, sectionId: string, isCommon: boolean = false) => {
const isEditing = editingElement?.sectionId === sectionId && editingElement?.elementId === element.id;
return (
<div key={element.id} className="bg-slate-50 p-4 rounded-xl border border-slate-200 shadow-sm transition-all mb-3">
<div className="flex justify-between items-start mb-3">
{isEditing ? (
<input
type="text"
value={element.name}
onChange={(e) => handleUpdateElement(sectionId, element.id, { name: e.target.value }, isCommon)}
placeholder="Название элемента МКД"
className="flex-1 px-3 py-2 bg-white border border-slate-200 rounded-xl text-sm font-bold focus:ring-2 focus:ring-primary-500 outline-none"
autoFocus
/>
) : (
<div className="flex items-center gap-2 flex-1">
<h4 className="font-bold text-sm text-slate-800 flex items-center gap-2">
<Layers className="w-4 h-4 text-slate-400"/>
{element.name || 'Новый элемент'}
</h4>
<button
onClick={() => setEditingElement({ sectionId, elementId: element.id })}
className="text-xs text-primary-600 hover:text-primary-700 font-bold px-2 py-1 hover:bg-primary-50 rounded-lg transition-colors"
>
+ Изменить
</button>
</div>
)}
<button
onClick={() => handleDeleteElement(sectionId, element.id, isCommon)}
className="text-red-500 hover:text-red-700 p-1 hover:bg-red-50 rounded-lg transition-colors"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
<div className="grid grid-cols-3 gap-3 mb-3">
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-1">Общий строй</label>
<select
value={element.generalStatus}
onChange={(e) => handleUpdateElement(sectionId, element.id, { generalStatus: e.target.value as any }, isCommon)}
className="w-full px-3 py-2 bg-white border border-slate-200 rounded-xl text-xs focus:ring-2 focus:ring-primary-500 outline-none"
>
<option value="NOT_SELECTED">Выбрать</option>
<option value="OK">Удовлет</option>
<option value="WARNING">Неуд</option>
<option value="CRITICAL">Критично</option>
</select>
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-1">Электроснабжение</label>
<select
value={element.electroStatus}
onChange={(e) => handleUpdateElement(sectionId, element.id, { electroStatus: e.target.value as any }, isCommon)}
className="w-full px-3 py-2 bg-white border border-slate-200 rounded-xl text-xs focus:ring-2 focus:ring-primary-500 outline-none"
>
<option value="NOT_SELECTED">Выбрать</option>
<option value="OK">Удовлет</option>
<option value="WARNING">Неуд</option>
<option value="CRITICAL">Критично</option>
</select>
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-1">Слесарные, сварочные</label>
<select
value={element.weldingStatus}
onChange={(e) => handleUpdateElement(sectionId, element.id, { weldingStatus: e.target.value as any }, isCommon)}
className="w-full px-3 py-2 bg-white border border-slate-200 rounded-xl text-xs focus:ring-2 focus:ring-primary-500 outline-none"
>
<option value="NOT_SELECTED">Выбрать</option>
<option value="OK">Удовлет</option>
<option value="WARNING">Неуд</option>
<option value="CRITICAL">Критично</option>
</select>
</div>
</div>
<div className="mb-3">
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-1">Вид требуемого ремонта</label>
<textarea
value={element.repairType || ''}
onChange={(e) => handleUpdateElement(sectionId, element.id, { repairType: e.target.value }, isCommon)}
placeholder="Опишите требуемый ремонт..."
className="w-full px-3 py-2 bg-white border border-slate-200 rounded-xl text-xs min-h-[60px] focus:ring-2 focus:ring-primary-500 outline-none"
/>
</div>
<div className="mt-2 space-y-2">
<div className="flex items-center justify-between">
<span className="text-[10px] text-slate-400 font-bold uppercase tracking-tight">Фотофиксация состояния:</span>
<div className="flex gap-2">
<button className="flex items-center gap-1.5 px-3 py-1 bg-white border border-slate-200 rounded-lg text-[10px] font-bold text-slate-600 hover:text-primary-600 hover:border-primary-200 transition-colors shadow-sm">
<Camera className="w-3.5 h-3.5 text-primary-500"/> Гл. фото
</button>
<button className="flex items-center gap-1.5 px-3 py-1 bg-white border border-slate-200 rounded-lg text-[10px] font-bold text-slate-600 hover:text-primary-600 hover:border-primary-200 transition-colors shadow-sm">
<Camera className="w-3.5 h-3.5 text-primary-500"/> Доп. фото
</button>
</div>
</div>
{(element.mainPhoto || (element.additionalPhotos && element.additionalPhotos.length > 0)) && (
<div className="flex gap-2 overflow-x-auto no-scrollbar py-1">
{element.mainPhoto && (
<div className="w-16 h-16 rounded-lg relative flex-shrink-0 group overflow-hidden border border-slate-200 bg-slate-200">
<img src={element.mainPhoto} alt="Main photo" className="w-full h-full object-cover" />
<button
onClick={() => handleUpdateElement(sectionId, element.id, { mainPhoto: undefined }, isCommon)}
className="absolute top-0.5 right-0.5 p-1 bg-red-500/80 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
>
<X className="w-2.5 h-2.5"/>
</button>
</div>
)}
{element.additionalPhotos?.map((photo, pIdx) => (
<div key={pIdx} className="w-16 h-16 rounded-lg relative flex-shrink-0 group overflow-hidden border border-slate-200 bg-slate-200">
<img src={photo} alt="Additional photo" className="w-full h-full object-cover" />
<button
onClick={() => {
const updated = element.additionalPhotos?.filter((_, i) => i !== pIdx) || [];
handleUpdateElement(sectionId, element.id, { additionalPhotos: updated }, isCommon);
}}
className="absolute top-0.5 right-0.5 p-1 bg-red-500/80 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
>
<X className="w-2.5 h-2.5"/>
</button>
</div>
))}
</div>
)}
</div>
</div>
);
};
return (
<div className="space-y-4">
{/* Табы для подъездов */}
{entranceSections.length > 0 && (
<div className="mb-6">
<h3 className="font-bold text-slate-800 text-sm mb-3 flex items-center gap-2">
<DoorClosed className="w-4 h-4 text-primary-500" /> Парадная
</h3>
<div className="flex gap-2 overflow-x-auto pb-2">
{entranceSections.map(section => (
<button
key={section.id}
onClick={() => setActiveTab(section.id)}
className={`px-4 py-2 rounded-lg font-bold text-sm whitespace-nowrap transition-all ${
activeTab === section.id
? 'bg-primary-600 text-white shadow-lg'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
{section.number}
</button>
))}
</div>
{activeTab && entranceSections.find(s => s.id === activeTab) && (() => {
const currentSection = entranceSections.find(s => s.id === activeTab)!;
const entrance = building.entrances?.find(e => e.number === currentSection.number);
const techChars = entrance?.technicalCharacteristics || {};
return (
<div className="mt-4 space-y-4">
{/* Технические характеристики подъезда */}
<div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm mb-4">
<div className="flex justify-between items-center mb-4">
<h4 className="font-bold text-slate-800 text-sm">
ТЕХНИЧЕСКИЕ ХАРАКТЕРИСТИКИ ПОДЪЕЗДА {currentSection.number}
</h4>
<button
onClick={() => setEditingEntranceTech(editingEntranceTech === currentSection.number ? null : currentSection.number)}
className="text-primary-600 hover:text-primary-700 text-xs font-bold"
>
{editingEntranceTech === currentSection.number ? 'Сохранить' : 'Редактировать'}
</button>
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-1">Наличие подвала</label>
{editingEntranceTech === currentSection.number ? (
<select
value={techChars.hasBasement || 'NOT_SELECTED'}
onChange={(e) => {
if (onSaveToBuilding) {
onSaveToBuilding({
entranceNumber: currentSection.number,
technicalCharacteristics: {
...techChars,
hasBasement: e.target.value as any
}
}, null);
}
}}
className="w-full px-2 py-1.5 border border-slate-300 rounded-lg text-xs"
>
<option value="NOT_SELECTED">Выбрать</option>
<option value="YES">Да</option>
<option value="NO">Нет</option>
</select>
) : (
<p className="text-sm font-bold text-slate-800">{techChars.hasBasement === 'YES' ? 'Да' : techChars.hasBasement === 'NO' ? 'Нет' : '-'}</p>
)}
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-1">Наличие тех. этажа</label>
{editingEntranceTech === currentSection.number ? (
<select
value={techChars.hasTechFloor || 'NOT_SELECTED'}
onChange={(e) => {
if (onSaveToBuilding) {
onSaveToBuilding({
entranceNumber: currentSection.number,
technicalCharacteristics: {
...techChars,
hasTechFloor: e.target.value as any
}
}, null);
}
}}
className="w-full px-2 py-1.5 border border-slate-300 rounded-lg text-xs"
>
<option value="NOT_SELECTED">Выбрать</option>
<option value="YES">Да</option>
<option value="NO">Нет</option>
</select>
) : (
<p className="text-sm font-bold text-slate-800">{techChars.hasTechFloor === 'YES' ? 'Да' : techChars.hasTechFloor === 'NO' ? 'Нет' : '-'}</p>
)}
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-1">Наличие паркинга</label>
{editingEntranceTech === currentSection.number ? (
<select
value={techChars.hasParking || 'NOT_SELECTED'}
onChange={(e) => {
if (onSaveToBuilding) {
onSaveToBuilding({
entranceNumber: currentSection.number,
technicalCharacteristics: {
...techChars,
hasParking: e.target.value as any
}
}, null);
}
}}
className="w-full px-2 py-1.5 border border-slate-300 rounded-lg text-xs"
>
<option value="NOT_SELECTED">Выбрать</option>
<option value="YES">Да</option>
<option value="NO">Нет</option>
</select>
) : (
<p className="text-sm font-bold text-slate-800">{techChars.hasParking === 'YES' ? 'Да' : techChars.hasParking === 'NO' ? 'Нет' : '-'}</p>
)}
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-1">Наличие чердака</label>
{editingEntranceTech === currentSection.number ? (
<select
value={techChars.hasAttic || 'NOT_SELECTED'}
onChange={(e) => {
if (onSaveToBuilding) {
onSaveToBuilding({
entranceNumber: currentSection.number,
technicalCharacteristics: {
...techChars,
hasAttic: e.target.value as any
}
}, null);
}
}}
className="w-full px-2 py-1.5 border border-slate-300 rounded-lg text-xs"
>
<option value="NOT_SELECTED">Выбрать</option>
<option value="YES">Да</option>
<option value="NO">Нет</option>
</select>
) : (
<p className="text-sm font-bold text-slate-800">{techChars.hasAttic === 'YES' ? 'Да' : techChars.hasAttic === 'NO' ? 'Нет' : '-'}</p>
)}
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-1">Количество этажей</label>
{editingEntranceTech === currentSection.number ? (
<input
type="number"
value={techChars.floorsCount || entrance?.floors || ''}
onChange={(e) => {
if (onSaveToBuilding) {
onSaveToBuilding({
entranceNumber: currentSection.number,
technicalCharacteristics: {
...techChars,
floorsCount: parseInt(e.target.value) || undefined
}
}, null);
}
}}
className="w-full px-2 py-1.5 border border-slate-300 rounded-lg text-xs"
/>
) : (
<p className="text-sm font-bold text-slate-800">{techChars.floorsCount || entrance?.floors || '-'}</p>
)}
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-1">Количество лифтов</label>
{editingEntranceTech === currentSection.number ? (
<input
type="number"
value={techChars.liftsCount || entrance?.liftsCount || ''}
onChange={(e) => {
if (onSaveToBuilding) {
onSaveToBuilding({
entranceNumber: currentSection.number,
technicalCharacteristics: {
...techChars,
liftsCount: parseInt(e.target.value) || undefined
}
}, null);
}
}}
className="w-full px-2 py-1.5 border border-slate-300 rounded-lg text-xs"
/>
) : (
<p className="text-sm font-bold text-slate-800">{techChars.liftsCount || entrance?.liftsCount || '-'}</p>
)}
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-1">Наличие мансарды</label>
{editingEntranceTech === currentSection.number ? (
<select
value={techChars.hasMansard || 'NOT_SELECTED'}
onChange={(e) => {
if (onSaveToBuilding) {
onSaveToBuilding({
entranceNumber: currentSection.number,
technicalCharacteristics: {
...techChars,
hasMansard: e.target.value as any
}
}, null);
}
}}
className="w-full px-2 py-1.5 border border-slate-300 rounded-lg text-xs"
>
<option value="NOT_SELECTED">Выбрать</option>
<option value="YES">Да</option>
<option value="NO">Нет</option>
</select>
) : (
<p className="text-sm font-bold text-slate-800">{techChars.hasMansard === 'YES' ? 'Да' : techChars.hasMansard === 'NO' ? 'Нет' : '-'}</p>
)}
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-1">Кол-во входных групп</label>
{editingEntranceTech === currentSection.number ? (
<input
type="number"
value={techChars.entranceGroupsCount || entrance?.entranceGroupsCount || ''}
onChange={(e) => {
if (onSaveToBuilding) {
onSaveToBuilding({
entranceNumber: currentSection.number,
technicalCharacteristics: {
...techChars,
entranceGroupsCount: parseInt(e.target.value) || undefined
}
}, null);
}
}}
className="w-full px-2 py-1.5 border border-slate-300 rounded-lg text-xs"
/>
) : (
<p className="text-sm font-bold text-slate-800">{techChars.entranceGroupsCount || entrance?.entranceGroupsCount || '-'}</p>
)}
</div>
</div>
</div>
{/* Элементы МКД подъезда */}
{currentSection.elements.map(el => renderElement(el, currentSection.id, false))}
<div className="flex gap-2">
<button
onClick={() => handleAddElement(currentSection.id, false)}
className="flex-1 py-3 bg-primary-600 text-white rounded-xl font-bold text-sm flex items-center justify-center gap-2 shadow-lg shadow-primary-500/20 hover:bg-primary-700 transition-all"
>
<Plus className="w-4 h-4" /> ДОБАВИТЬ ЭЛЕМЕНТ МКД
</button>
<button
type="button"
onClick={() => handleAddFromTemplate(currentSection.id, false)}
className="py-3 px-4 bg-white border border-slate-200 text-slate-700 rounded-xl font-bold text-sm flex items-center justify-center gap-2 hover:bg-slate-50 transition-all"
title="Добавить недостающие пункты из типового чек-листа парадной"
>
<ListPlus className="w-4 h-4" /> Из шаблона
</button>
</div>
</div>
);
})()}
</div>
)}
{/* Табы для этажей */}
{floorSections.length > 0 && (
<div className="mb-6">
<h3 className="font-bold text-slate-800 text-sm mb-3 flex items-center gap-2">
<Building2 className="w-4 h-4 text-primary-500" /> Этажи
</h3>
<div className="flex gap-2 overflow-x-auto pb-2 flex-wrap">
{floorSections.map(section => {
const match = section.id.match(/entrance-(\d+)-floor-(\d+)/);
const entranceNum = match ? match[1] : '';
const floorNum = section.number ?? '';
const label = entranceNum ? `П${entranceNum} Этаж ${floorNum}` : `Этаж ${floorNum}`;
return (
<button
key={section.id}
onClick={() => setActiveTab(section.id)}
className={`px-4 py-2 rounded-lg font-bold text-sm whitespace-nowrap transition-all ${
activeTab === section.id
? 'bg-primary-600 text-white shadow-lg'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
{label}
</button>
);
})}
</div>
{activeTab && floorSections.find(s => s.id === activeTab) && (() => {
const currentSection = floorSections.find(s => s.id === activeTab)!;
// Извлекаем номер подъезда из sectionId
const match = currentSection.id.match(/entrance-(\d+)-floor-(\d+)/);
const entranceNum = match ? parseInt(match[1]) : null;
const floorNum = currentSection.number;
return (
<div className="mt-4 space-y-4">
<h4 className="font-bold text-slate-800 text-sm mb-2">
ЭЛЕМЕНТЫ МКД ЭТАЖА {floorNum}{entranceNum ? ` (Подъезд ${entranceNum})` : ''}
</h4>
{currentSection.elements.map(el => renderElement(el, currentSection.id, false))}
<div className="flex gap-2">
<button
onClick={() => handleAddElement(currentSection.id, false)}
className="flex-1 py-3 bg-primary-600 text-white rounded-xl font-bold text-sm flex items-center justify-center gap-2 shadow-lg shadow-primary-500/20 hover:bg-primary-700 transition-all"
>
<Plus className="w-4 h-4" /> ДОБАВИТЬ ЭЛЕМЕНТ МКД
</button>
<button
type="button"
onClick={() => handleAddFromTemplate(currentSection.id, false)}
className="py-3 px-4 bg-white border border-slate-200 text-slate-700 rounded-xl font-bold text-sm flex items-center justify-center gap-2 hover:bg-slate-50 transition-all"
title="Добавить недостающие пункты из типового чек-листа этажа"
>
<ListPlus className="w-4 h-4" /> Из шаблона
</button>
</div>
</div>
);
})()}
</div>
)}
{/* Секции для лифтов */}
{liftSections.length > 0 && (
<div className="mb-6">
<h3 className="font-bold text-slate-800 text-sm mb-3 flex items-center gap-2">
<Gauge className="w-4 h-4 text-primary-500" /> Лифт
</h3>
{liftSections.map(section => (
<div key={section.id} className="mb-4 bg-slate-50 p-4 rounded-xl border border-slate-200 shadow-sm">
<div className="flex justify-between items-center mb-4">
<h4 className="font-bold text-slate-800 text-lg">Лифт {section.number}</h4>
<button
onClick={() => handleToggleSection(section.id, false)}
className="text-slate-500 hover:text-slate-700 transition-colors"
>
{section.isCollapsed ? <ChevronDown className="w-5 h-5" /> : <ChevronUp className="w-5 h-5" />}
</button>
</div>
{!section.isCollapsed && (
<div className="space-y-4">
{section.elements.map(el => renderElement(el, section.id, false))}
<button
onClick={() => handleAddElement(section.id, false)}
className="w-full py-3 bg-primary-600 text-white rounded-xl font-bold text-sm flex items-center justify-center gap-2 shadow-lg shadow-primary-500/20 hover:bg-primary-700 transition-all"
>
<Plus className="w-4 h-4" /> ДОБАВИТЬ ЭЛЕМЕНТ МКД
</button>
</div>
)}
</div>
))}
</div>
)}
{/* Общие секции */}
{commonSections.map(section => (
<div key={section.id} className="bg-slate-50 p-4 rounded-xl border border-slate-200 shadow-sm mb-4">
<div className="flex justify-between items-center mb-4">
<h3 className="font-bold text-slate-800 text-sm">{section.title}</h3>
<button
onClick={() => handleToggleSection(section.id, true)}
className="text-slate-500 hover:text-slate-700 transition-colors"
>
{section.isCollapsed ? <ChevronDown className="w-5 h-5" /> : <ChevronUp className="w-5 h-5" />}
</button>
</div>
{!section.isCollapsed && (
<div className="space-y-4">
{section.elements.map(el => renderElement(el, section.id, true))}
<div className="flex gap-2">
<button
onClick={() => handleAddElement(section.id, true)}
className="flex-1 py-3 bg-primary-600 text-white rounded-xl font-bold text-sm flex items-center justify-center gap-2 shadow-lg shadow-primary-500/20 hover:bg-primary-700 transition-all"
>
<Plus className="w-4 h-4" /> ДОБАВИТЬ ЭЛЕМЕНТ МКД
</button>
<button
type="button"
onClick={() => handleAddFromTemplate(section.id, true)}
className="py-3 px-4 bg-white border border-slate-200 text-slate-700 rounded-xl font-bold text-sm flex items-center justify-center gap-2 hover:bg-slate-50 transition-all"
title="Добавить недостающие пункты из типового чек-листа"
>
<ListPlus className="w-4 h-4" /> Из шаблона
</button>
</div>
</div>
)}
</div>
))}
</div>
);
};