Spaces:
Running
Running
| <html lang="ru"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Недвижимость - Объявления</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script> | |
| <script src="https://api-maps.yandex.ru/2.1/?apikey=ваш_api_ключ&lang=ru_RU" type="text/javascript"></script> | |
| <style> | |
| .marker-color-selector { | |
| display: flex; | |
| gap: 5px; | |
| margin-top: 5px; | |
| } | |
| .color-option { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| border: 1px solid #ccc; | |
| } | |
| .color-option.selected { | |
| border: 2px solid #000; | |
| } | |
| .map-container { | |
| height: 500px; | |
| width: 100%; | |
| } | |
| .object-image-thumbnail { | |
| width: 80px; | |
| height: 60px; | |
| object-fit: cover; | |
| margin-right: 5px; | |
| cursor: pointer; | |
| } | |
| .object-image-thumbnail:hover { | |
| opacity: 0.8; | |
| } | |
| .modal { | |
| display: none; | |
| position: fixed; | |
| z-index: 100; | |
| left: 0; | |
| top: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0,0,0,0.8); | |
| } | |
| .modal-content { | |
| margin: 5% auto; | |
| padding: 20px; | |
| width: 80%; | |
| max-width: 800px; | |
| background: white; | |
| border-radius: 8px; | |
| } | |
| .close { | |
| color: #aaa; | |
| float: right; | |
| font-size: 28px; | |
| font-weight: bold; | |
| cursor: pointer; | |
| } | |
| .close:hover { | |
| color: black; | |
| } | |
| @media (max-width: 768px) { | |
| .map-container { | |
| height: 300px; | |
| } | |
| .modal-content { | |
| width: 95%; | |
| margin: 10% auto; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gradient-to-r from-blue-50 to-indigo-50 min-h-screen"> | |
| <!-- Хранилище данных --> | |
| <script> | |
| // Инициализация хранилища объектов | |
| let objects = JSON.parse(localStorage.getItem('realtyObjects')) || []; | |
| let currentUser = null; | |
| let map = null; | |
| let objectMarkers = []; | |
| // Типы объектов | |
| const objectTypes = [ | |
| "Квартира", "Дом", "Офис", "Торговая площадь", | |
| "Склад", "Участок", "Гараж", "Другое" | |
| ]; | |
| // Цвета маркеров | |
| const markerColors = [ | |
| {name: "Красный", value: "#FF0000"}, | |
| {name: "Синий", value: "#0000FF"}, | |
| {name: "Зеленый", value: "#00FF00"}, | |
| {name: "Желтый", value: "#FFFF00"}, | |
| {name: "Фиолетовый", value: "#800080"}, | |
| {name: "Оранжевый", value: "#FFA500"}, | |
| {name: "Розовый", value: "#FFC0CB"}, | |
| {name: "Коричневый", value: "#A52A2A"} | |
| ]; | |
| // Иконки маркеров | |
| const markerIcons = [ | |
| {name: "Дом", value: "home"}, | |
| {name: "Здание", value: "building"}, | |
| {name: "Магазин", value: "store"}, | |
| {name: "Склад", value: "warehouse"}, | |
| {name: "Парковка", value: "parking"}, | |
| {name: "Офис", value: "briefcase"}, | |
| {name: "Участок", value: "tree"}, | |
| {name: "Другое", value: "map-marker-alt"} | |
| ]; | |
| </script> | |
| <!-- Шапка сайта --> | |
| <header class="bg-indigo-700 text-white shadow-lg"> | |
| <div class="container mx-auto px-4 py-4 flex justify-between items-center"> | |
| <h1 class="text-2xl font-bold">Недвижимость PRO</h1> | |
| <div class="flex space-x-4"> | |
| <button onclick="showTab('map')" class="px-4 py-2 rounded hover:bg-indigo-600">Карта</button> | |
| <button onclick="showTab('objects')" class="px-4 py-2 rounded hover:bg-indigo-600">Объекты</button> | |
| <button onclick="showTab('about')" class="px-4 py-2 rounded hover:bg-indigo-600">О компании</button> | |
| <button onclick="showAdminLogin()" id="adminBtn" class="px-4 py-2 rounded bg-yellow-500 hover:bg-yellow-600 text-white hidden">Админ</button> | |
| <button onclick="logout()" id="logoutBtn" class="px-4 py-2 rounded bg-red-500 hover:bg-red-600 text-white hidden">Выйти</button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Основное содержимое --> | |
| <main class="container mx-auto px-4 py-8"> | |
| <!-- Вкладка Карта --> | |
| <div id="map-tab" class="tab-content"> | |
| <div class="bg-white rounded-lg shadow-md p-6 mb-6"> | |
| <h2 class="text-xl font-semibold mb-4">Карта объектов</h2> | |
| <!-- Фильтры --> | |
| <div class="mb-4 flex flex-wrap gap-2"> | |
| <button onclick="toggleFilters()" class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"> | |
| Фильтры <i class="fas fa-filter"></i> | |
| </button> | |
| <div id="filtersPanel" class="hidden w-full bg-gray-100 p-4 rounded-lg mt-2"> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Тип объекта</label> | |
| <select id="filterType" class="w-full p-2 border rounded"> | |
| <option value="">Все типы</option> | |
| <script> | |
| objectTypes.forEach(type => { | |
| document.write(`<option value="${type}">${type}</option>`); | |
| }); | |
| </script> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Цена от</label> | |
| <input type="number" id="filterPriceMin" class="w-full p-2 border rounded" placeholder="Минимальная цена"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Цена до</label> | |
| <input type="number" id="filterPriceMax" class="w-full p-2 border rounded" placeholder="Максимальная цена"> | |
| </div> | |
| </div> | |
| <button onclick="applyFilters()" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"> | |
| Применить фильтры | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Карта --> | |
| <div id="map" class="map-container rounded-lg border border-gray-300"></div> | |
| </div> | |
| </div> | |
| <!-- Вкладка Объекты --> | |
| <div id="objects-tab" class="tab-content hidden"> | |
| <div class="bg-white rounded-lg shadow-md p-6"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-xl font-semibold">Список объектов</h2> | |
| <button onclick="showAddObjectForm()" id="addObjectBtn" class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 hidden"> | |
| <i class="fas fa-plus"></i> Добавить объект | |
| </button> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full bg-white border border-gray-200"> | |
| <thead> | |
| <tr class="bg-gray-100"> | |
| <th class="py-2 px-4 border">Фото</th> | |
| <th class="py-2 px-4 border">Тип</th> | |
| <th class="py-2 px-4 border">Адрес</th> | |
| <th class="py-2 px-4 border">Площадь</th> | |
| <th class="py-2 px-4 border">Цена</th> | |
| <th class="py-2 px-4 border">Контакты</th> | |
| <th class="py-2 px-4 border">Действия</th> | |
| </tr> | |
| </thead> | |
| <tbody id="objectsTableBody"> | |
| <!-- Объекты будут добавляться сюда --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Вкладка О компании --> | |
| <div id="about-tab" class="tab-content hidden"> | |
| <div class="bg-white rounded-lg shadow-md p-6"> | |
| <h2 class="text-xl font-semibold mb-4">О нашей компании</h2> | |
| <p class="mb-4">Мы - лидер на рынке недвижимости с 15-летним опытом работы. Наша компания специализируется на продаже и аренде коммерческой и жилой недвижимости.</p> | |
| <p class="mb-4">Наши преимущества:</p> | |
| <ul class="list-disc pl-6 mb-4"> | |
| <li>Профессиональные консультации</li> | |
| <li>Полное сопровождение сделки</li> | |
| <li>Юридическая проверка объектов</li> | |
| <li>Индивидуальный подход к каждому клиенту</li> | |
| </ul> | |
| <p>Свяжитесь с нами для получения дополнительной информации!</p> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Модальное окно входа в админку --> | |
| <div id="adminLoginModal" class="modal"> | |
| <div class="modal-content"> | |
| <span class="close" onclick="closeModal('adminLoginModal')">×</span> | |
| <h2 class="text-xl font-semibold mb-4">Вход в админ-панель</h2> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Пароль</label> | |
| <input type="password" id="adminPassword" class="w-full p-2 border rounded" placeholder="Введите пароль"> | |
| </div> | |
| <button onclick="adminLogin()" class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"> | |
| Войти | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Модальное окно добавления/редактирования объекта --> | |
| <div id="objectFormModal" class="modal"> | |
| <div class="modal-content"> | |
| <span class="close" onclick="closeModal('objectFormModal')">×</span> | |
| <h2 id="objectFormTitle" class="text-xl font-semibold mb-4">Добавить новый объект</h2> | |
| <form id="objectForm" class="space-y-4"> | |
| <input type="hidden" id="objectId"> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Тип объекта*</label> | |
| <select id="objectType" class="w-full p-2 border rounded" required> | |
| <option value="">Выберите тип</option> | |
| <script> | |
| objectTypes.forEach(type => { | |
| document.write(`<option value="${type}">${type}</option>`); | |
| }); | |
| </script> | |
| </select> | |
| <input type="text" id="customObjectType" class="w-full p-2 border rounded mt-2 hidden" placeholder="Укажите другой тип"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Цена*</label> | |
| <input type="number" id="objectPrice" class="w-full p-2 border rounded" placeholder="Укажите цену" required> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Адрес*</label> | |
| <input type="text" id="objectAddress" class="w-full p-2 border rounded" placeholder="Укажите адрес" required> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Площадь (м²)*</label> | |
| <input type="number" id="objectArea" class="w-full p-2 border rounded" placeholder="Площадь" required> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Представитель*</label> | |
| <input type="text" id="objectAgent" class="w-full p-2 border rounded" placeholder="ФИО представителя" required> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Телефон*</label> | |
| <input type="tel" id="objectPhone" class="w-full p-2 border rounded" placeholder="+7 (XXX) XXX-XX-XX" required> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Email</label> | |
| <input type="email" id="objectEmail" class="w-full p-2 border rounded" placeholder="email@example.com"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Описание объекта*</label> | |
| <textarea id="objectDescription" class="w-full p-2 border rounded" rows="3" placeholder="Подробное описание объекта" required></textarea> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Фотографии объекта (максимум 5)</label> | |
| <input type="file" id="objectImages" class="w-full p-2 border rounded" multiple accept="image/*"> | |
| <div id="imagePreviews" class="mt-2 flex flex-wrap gap-2"></div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Цвет маркера</label> | |
| <select id="markerColorSelect" class="w-full p-2 border rounded"> | |
| <option value="">Выберите цвет</option> | |
| <script> | |
| markerColors.forEach(color => { | |
| document.write(`<option value="${color.value}">${color.name}</option>`); | |
| }); | |
| </script> | |
| </select> | |
| <div class="marker-color-selector mt-2"> | |
| <script> | |
| markerColors.forEach(color => { | |
| document.write(`<div class="color-option" style="background-color:${color.value}" onclick="selectMarkerColor('${color.value}')"></div>`); | |
| }); | |
| </script> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Иконка маркера</label> | |
| <select id="markerIconSelect" class="w-full p-2 border rounded"> | |
| <option value="">Выберите иконку</option> | |
| <script> | |
| markerIcons.forEach(icon => { | |
| document.write(`<option value="${icon.value}">${icon.name}</option>`); | |
| }); | |
| </script> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Доп. поле 1</label> | |
| <input type="text" id="objectField1" class="w-full p-2 border rounded" placeholder="Дополнительная информация"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Доп. поле 2</label> | |
| <input type="text" id="objectField2" class="w-full p-2 border rounded" placeholder="Дополнительная информация"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Доп. поле 3</label> | |
| <input type="text" id="objectField3" class="w-full p-2 border rounded" placeholder="Дополнительная информация"> | |
| </div> | |
| </div> | |
| <div class="flex justify-end space-x-2"> | |
| <button type="button" onclick="closeModal('objectFormModal')" class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400"> | |
| Отмена | |
| </button> | |
| <button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"> | |
| Сохранить | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Модальное окно просмотра объекта --> | |
| <div id="viewObjectModal" class="modal"> | |
| <div class="modal-content"> | |
| <span class="close" onclick="closeModal('viewObjectModal')">×</span> | |
| <div id="objectViewContent"> | |
| <!-- Контент будет заполнен динамически --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Модальное окно просмотра изображения --> | |
| <div id="imageViewModal" class="modal"> | |
| <div class="modal-content"> | |
| <span class="close" onclick="closeModal('imageViewModal')">×</span> | |
| <img id="modalImageView" src="" alt="" class="w-full h-auto"> | |
| </div> | |
| </div> | |
| <script> | |
| // Инициализация при загрузке страницы | |
| document.addEventListener('DOMContentLoaded', function() { | |
| showTab('map'); | |
| initMap(); | |
| renderObjectsTable(); | |
| checkAdminStatus(); | |
| // Обработчик формы объекта | |
| document.getElementById('objectForm').addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| saveObject(); | |
| }); | |
| // Обработчик изменения типа объекта | |
| document.getElementById('objectType').addEventListener('change', function() { | |
| const customTypeField = document.getElementById('customObjectType'); | |
| if (this.value === 'Другое') { | |
| customTypeField.classList.remove('hidden'); | |
| customTypeField.required = true; | |
| } else { | |
| customTypeField.classList.add('hidden'); | |
| customTypeField.required = false; | |
| } | |
| }); | |
| // Обработчик загрузки изображений | |
| document.getElementById('objectImages').addEventListener('change', function() { | |
| const files = this.files; | |
| const previewsContainer = document.getElementById('imagePreviews'); | |
| previewsContainer.innerHTML = ''; | |
| if (files.length > 5) { | |
| alert('Максимальное количество фотографий - 5'); | |
| this.value = ''; | |
| return; | |
| } | |
| for (let i = 0; i < files.length; i++) { | |
| const file = files[i]; | |
| if (!file.type.match('image.*')) continue; | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| const img = document.createElement('img'); | |
| img.src = e.target.result; | |
| img.className = 'object-image-thumbnail'; | |
| img.onclick = function() { | |
| document.getElementById('modalImageView').src = e.target.result; | |
| document.getElementById('imageViewModal').style.display = 'block'; | |
| }; | |
| previewsContainer.appendChild(img); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| }); | |
| }); | |
| // Функции для работы с вкладками | |
| function showTab(tabName) { | |
| document.querySelectorAll('.tab-content').forEach(tab => { | |
| tab.classList.add('hidden'); | |
| }); | |
| document.getElementById(`${tabName}-tab`).classList.remove('hidden'); | |
| } | |
| // Функции для работы с картой | |
| function initMap() { | |
| ymaps.ready(function() { | |
| map = new ymaps.Map('map', { | |
| center: [55.76, 37.64], // Москва | |
| zoom: 10 | |
| }); | |
| updateMapMarkers(); | |
| }); | |
| } | |
| function updateMapMarkers() { | |
| // Удаляем старые маркеры | |
| objectMarkers.forEach(marker => { | |
| map.geoObjects.remove(marker); | |
| }); | |
| objectMarkers = []; | |
| // Добавляем новые маркеры | |
| objects.forEach(obj => { | |
| const marker = new ymaps.Placemark( | |
| [obj.lat, obj.lng], | |
| { | |
| balloonContentHeader: obj.type, | |
| balloonContentBody: ` | |
| <div> | |
| <p><strong>Адрес:</strong> ${obj.address}</p> | |
| <p><strong>Площадь:</strong> ${obj.area} м²</p> | |
| <p><strong>Цена:</strong> ${formatPrice(obj.price)}</p> | |
| <p><strong>Контакты:</strong> ${obj.agent}, ${obj.phone}</p> | |
| <p>${obj.description}</p> | |
| ${obj.images.length > 0 ? `<img src="${obj.images[0]}" style="max-width:200px; max-height:150px; margin-top:10px;">` : ''} | |
| </div> | |
| `, | |
| hintContent: obj.address | |
| }, | |
| { | |
| preset: obj.markerIcon ? `islands#${obj.markerIcon}Icon` : 'islands#icon', | |
| iconColor: obj.markerColor || '#0066ff' | |
| } | |
| ); | |
| objectMarkers.push(marker); | |
| map.geoObjects.add(marker); | |
| }); | |
| } | |
| function formatPrice(price) { | |
| return new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB' }).format(price); | |
| } | |
| // Функции для работы с фильтрами | |
| function toggleFilters() { | |
| const panel = document.getElementById('filtersPanel'); | |
| panel.classList.toggle('hidden'); | |
| } | |
| function applyFilters() { | |
| const typeFilter = document.getElementById('filterType').value; | |
| const priceMin = parseFloat(document.getElementById('filterPriceMin').value) || 0; | |
| const priceMax = parseFloat(document.getElementById('filterPriceMax').value) || Infinity; | |
| // Обновляем маркеры на карте | |
| objectMarkers.forEach(marker => { | |
| const obj = marker.properties.get('objectData'); | |
| const show = | |
| (typeFilter === '' || obj.type === typeFilter) && | |
| obj.price >= priceMin && | |
| obj.price <= priceMax; | |
| marker.options.set('visible', show); | |
| }); | |
| // Закрываем панель фильтров | |
| document.getElementById('filtersPanel').classList.add('hidden'); | |
| } | |
| // Функции для работы с объектами | |
| function renderObjectsTable() { | |
| const tbody = document.getElementById('objectsTableBody'); | |
| tbody.innerHTML = ''; | |
| objects.forEach((obj, index) => { | |
| const row = document.createElement('tr'); | |
| row.className = 'hover:bg-gray-50'; | |
| // Фото | |
| const photoCell = document.createElement('td'); | |
| photoCell.className = 'py-2 px-4 border'; | |
| if (obj.images && obj.images.length > 0) { | |
| const img = document.createElement('img'); | |
| img.src = obj.images[0]; | |
| img.className = 'object-image-thumbnail'; | |
| img.onclick = () => viewObject(index); | |
| photoCell.appendChild(img); | |
| } else { | |
| photoCell.textContent = '-'; | |
| } | |
| // Тип | |
| const typeCell = document.createElement('td'); | |
| typeCell.className = 'py-2 px-4 border'; | |
| typeCell.textContent = obj.type; | |
| // Адрес | |
| const addressCell = document.createElement('td'); | |
| addressCell.className = 'py-2 px-4 border'; | |
| addressCell.textContent = obj.address; | |
| // Площадь | |
| const areaCell = document.createElement('td'); | |
| areaCell.className = 'py-2 px-4 border'; | |
| areaCell.textContent = `${obj.area} м²`; | |
| // Цена | |
| const priceCell = document.createElement('td'); | |
| priceCell.className = 'py-2 px-4 border'; | |
| priceCell.textContent = formatPrice(obj.price); | |
| // Контакты | |
| const contactsCell = document.createElement('td'); | |
| contactsCell.className = 'py-2 px-4 border'; | |
| contactsCell.innerHTML = ` | |
| <p>${obj.agent}</p> | |
| <p>${obj.phone}</p> | |
| ${obj.email ? `<p>${obj.email}</p>` : ''} | |
| `; | |
| // Действия | |
| const actionsCell = document.createElement('td'); | |
| actionsCell.className = 'py-2 px-4 border'; | |
| const viewBtn = document.createElement('button'); | |
| viewBtn.className = 'px-2 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 mr-1'; | |
| viewBtn.innerHTML = '<i class="fas fa-eye"></i>'; | |
| viewBtn.title = 'Просмотр'; | |
| viewBtn.onclick = () => viewObject(index); | |
| actionsCell.appendChild(viewBtn); | |
| if (currentUser) { | |
| const editBtn = document.createElement('button'); | |
| editBtn.className = 'px-2 py-1 bg-yellow-500 text-white rounded hover:bg-yellow-600 mr-1'; | |
| editBtn.innerHTML = '<i class="fas fa-edit"></i>'; | |
| editBtn.title = 'Редактировать'; | |
| editBtn.onclick = () => editObject(index); | |
| const deleteBtn = document.createElement('button'); | |
| deleteBtn.className = 'px-2 py-1 bg-red-500 text-white rounded hover:bg-red-600'; | |
| deleteBtn.innerHTML = '<i class="fas fa-trash"></i>'; | |
| deleteBtn.title = 'Удалить'; | |
| deleteBtn.onclick = () => deleteObject(index); | |
| actionsCell.appendChild(editBtn); | |
| actionsCell.appendChild(deleteBtn); | |
| } | |
| row.appendChild(photoCell); | |
| row.appendChild(typeCell); | |
| row.appendChild(addressCell); | |
| row.appendChild(areaCell); | |
| row.appendChild(priceCell); | |
| row.appendChild(contactsCell); | |
| row.appendChild(actionsCell); | |
| tbody.appendChild(row); | |
| }); | |
| } | |
| function viewObject(index) { | |
| const obj = objects[index]; | |
| const content = document.getElementById('objectViewContent'); | |
| content.innerHTML = ` | |
| <h3 class="text-lg font-semibold mb-2">${obj.type} - ${obj.address}</h3> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> | |
| <div> | |
| <p><strong>Площадь:</strong> ${obj.area} м²</p> | |
| <p><strong>Цена:</strong> ${formatPrice(obj.price)}</p> | |
| <p><strong>Дата создания:</strong> ${new Date(obj.createdAt).toLocaleDateString()}</p> | |
| ${obj.field1 ? `<p><strong>Доп. поле 1:</strong> ${obj.field1}</p>` : ''} | |
| ${obj.field2 ? `<p><strong>Доп. поле 2:</strong> ${obj.field2}</p>` : ''} | |
| ${obj.field3 ? `<p><strong>Доп. поле 3:</strong> ${obj.field3}</p>` : ''} | |
| </div> | |
| <div> | |
| <p><strong>Представитель:</strong> ${obj.agent}</p> | |
| <p><strong>Телефон:</strong> ${obj.phone}</p> | |
| ${obj.email ? `<p><strong>Email:</strong> ${obj.email}</p>` : ''} | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <h4 class="font-medium mb-1">Описание:</h4> | |
| <p>${obj.description}</p> | |
| </div> | |
| ${obj.images && obj.images.length > 0 ? ` | |
| <div> | |
| <h4 class="font-medium mb-2">Фотографии:</h4> | |
| <div class="flex flex-wrap gap-2"> | |
| ${obj.images.map(img => ` | |
| <img src="${img}" class="object-image-thumbnail cursor-pointer" | |
| onclick="document.getElementById('modalImageView').src='${img}'; | |
| document.getElementById('imageViewModal').style.display='block'"> | |
| `).join('')} | |
| </div> | |
| </div> | |
| ` : ''} | |
| `; | |
| document.getElementById('viewObjectModal').style.display = 'block'; | |
| } | |
| function showAddObjectForm() { | |
| document.getElementById('objectFormTitle').textContent = 'Добавить новый объект'; | |
| document.getElementById('objectId').value = ''; | |
| document.getElementById('objectForm').reset(); | |
| document.getElementById('imagePreviews').innerHTML = ''; | |
| document.getElementById('customObjectType').classList.add('hidden'); | |
| document.getElementById('objectFormModal').style.display = 'block'; | |
| } | |
| function editObject(index) { | |
| const obj = objects[index]; | |
| document.getElementById('objectFormTitle').textContent = 'Редактировать объект'; | |
| document.getElementById('objectId').value = index; | |
| // Заполняем форму данными объекта | |
| document.getElementById('objectType').value = obj.type === 'Другое' ? 'Другое' : obj.type; | |
| if (obj.type === 'Другое') { | |
| document.getElementById('customObjectType').value = obj.type; | |
| document.getElementById('customObjectType').classList.remove('hidden'); | |
| } | |
| document.getElementById('objectPrice').value = obj.price; | |
| document.getElementById('objectAddress').value = obj.address; | |
| document.getElementById('objectArea').value = obj.area; | |
| document.getElementById('objectAgent').value = obj.agent; | |
| document.getElementById('objectPhone').value = obj.phone; | |
| document.getElementById('objectEmail').value = obj.email || ''; | |
| document.getElementById('objectDescription').value = obj.description; | |
| document.getElementById('markerColorSelect').value = obj.markerColor || ''; | |
| document.getElementById('markerIconSelect').value = obj.markerIcon || ''; | |
| document.getElementById('objectField1').value = obj.field1 || ''; | |
| document.getElementById('objectField2').value = obj.field2 || ''; | |
| document.getElementById('objectField3').value = obj.field3 || ''; | |
| // Превью изображений | |
| const previewsContainer = document.getElementById('imagePreviews'); | |
| previewsContainer.innerHTML = ''; | |
| if (obj.images && obj.images.length > 0) { | |
| obj.images.forEach(img => { | |
| const imgElement = document.createElement('img'); | |
| imgElement.src = img; | |
| imgElement.className = 'object-image-thumbnail'; | |
| imgElement.onclick = function() { | |
| document.getElementById('modalImageView').src = img; | |
| document.getElementById('imageViewModal').style.display = 'block'; | |
| }; | |
| previewsContainer.appendChild(imgElement); | |
| }); | |
| } | |
| document.getElementById('objectFormModal').style.display = 'block'; | |
| } | |
| function saveObject() { | |
| const id = document.getElementById('objectId').value; | |
| const type = document.getElementById('objectType').value === 'Другое' | |
| ? document.getElementById('customObjectType').value | |
| : document.getElementById('objectType').value; | |
| const price = parseFloat(document.getElementById('objectPrice').value); | |
| const address = document.getElementById('objectAddress').value; | |
| // Геокодирование адреса | |
| ymaps.geocode(address, { results: 1 }).then(function(res) { | |
| const firstGeoObject = res.geoObjects.get(0); | |
| const coords = firstGeoObject.geometry.getCoordinates(); | |
| const objectData = { | |
| type: type, | |
| price: price, | |
| address: address, | |
| lat: coords[0], | |
| lng: coords[1], | |
| area: parseFloat(document.getElementById('objectArea').value), | |
| agent: document.getElementById('objectAgent').value, | |
| phone: document.getElementById('objectPhone').value, | |
| email: document.getElementById('objectEmail').value || '', | |
| description: document.getElementById('objectDescription').value, | |
| markerColor: document.getElementById('markerColorSelect').value, | |
| markerIcon: document.getElementById('markerIconSelect').value, | |
| field1: document.getElementById('objectField1').value || '', | |
| field2: document.getElementById('objectField2').value || '', | |
| field3: document.getElementById('objectField3').value || '', | |
| createdAt: id === '' ? new Date().toISOString() : objects[id].createdAt, | |
| images: id !== '' ? objects[id].images : [] | |
| }; | |
| // Обработка загруженных изображений | |
| const files = document.getElementById('objectImages').files; | |
| if (files.length > 0) { | |
| objectData.images = []; | |
| for (let i = 0; i < files.length; i++) { | |
| const file = files[i]; | |
| if (!file.type.match('image.*')) continue; | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| objectData.images.push(e.target.result); | |
| // Когда все изображения загружены, сохраняем объект | |
| if (objectData.images.length === files.length) { | |
| saveObjectData(id, objectData); | |
| } | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| } else { | |
| saveObjectData(id, objectData); | |
| } | |
| }); | |
| } | |
| function saveObjectData(id, objectData) { | |
| if (id === '') { | |
| // Новый объект | |
| objects.push(objectData); | |
| } else { | |
| // Обновление существующего объекта | |
| objects[id] = objectData; | |
| } | |
| // Сохраняем в localStorage | |
| localStorage.setItem('realtyObjects', JSON.stringify(objects)); | |
| // Обновляем интерфейс | |
| updateMapMarkers(); | |
| renderObjectsTable(); | |
| closeModal('objectFormModal'); | |
| alert('Объект успешно сохранен!'); | |
| } | |
| function deleteObject(index) { | |
| if (confirm('Вы уверены, что хотите удалить этот объект?')) { | |
| objects.splice(index, 1); | |
| localStorage.setItem('realtyObjects', JSON.stringify(objects)); | |
| updateMapMarkers(); | |
| renderObjectsTable(); | |
| } | |
| } | |
| function selectMarkerColor(color) { | |
| document.getElementById('markerColorSelect').value = color; | |
| } | |
| // Функции для работы с админкой | |
| function checkAdminStatus() { | |
| const adminBtn = document.getElementById('adminBtn'); | |
| const logoutBtn = document.getElementById('logoutBtn'); | |
| const addObjectBtn = document.getElementById('addObjectBtn'); | |
| if (currentUser) { | |
| adminBtn.classList.add('hidden'); | |
| logoutBtn.classList.remove('hidden'); | |
| addObjectBtn.classList.remove('hidden'); | |
| } else { | |
| adminBtn.classList.remove('hidden'); | |
| logoutBtn.classList.add('hidden'); | |
| addObjectBtn.classList.add('hidden'); | |
| } | |
| } | |
| function showAdminLogin() { | |
| document.getElementById('adminPassword').value = ''; | |
| document.getElementById('adminLoginModal').style.display = 'block'; | |
| } | |
| function adminLogin() { | |
| const password = document.getElementById('adminPassword').value; | |
| if (password === 'i%UCLj2m2eg') { | |
| currentUser = { role: 'admin' }; | |
| checkAdminStatus(); | |
| closeModal('adminLoginModal'); | |
| alert('Вы успешно вошли в админ-панель!'); | |
| } else { | |
| alert('Неверный пароль!'); | |
| } | |
| } | |
| function logout() { | |
| currentUser = null; | |
| checkAdminStatus(); | |
| alert('Вы вышли из админ-панели'); | |
| } | |
| // Общие функции | |
| function closeModal(modalId) { | |
| document.getElementById(modalId).style.display = 'none'; | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=BIGKingAlex/pro" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |