Spaces:
Running
Running
| import os, time, shutil | |
| import gradio as gr | |
| from utils import get_models_data, save_config, get_contacts, save_contacts | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| def build_admin_app(): | |
| initial_models = get_models_data(BASE_DIR) | |
| with gr.Blocks(title="Unstop Admin", analytics_enabled=False) as app_admin: | |
| with gr.Tabs(): | |
| # ── ВКЛ 1: ТОВАРИ ────────────────────────────────────────────── | |
| with gr.Tab("🛒 Товари"): | |
| with gr.Row(): | |
| model_selector = gr.Dropdown( | |
| choices=[m['name'] for m in initial_models], | |
| label="Оберіть товар для редагування", | |
| value=initial_models[0]['name'] if initial_models else None, | |
| scale=4 | |
| ) | |
| add_btn = gr.Button("➕ Створити новий", variant="primary", scale=1) | |
| del_btn = gr.Button("🗑 Видалити товар", variant="stop", scale=1) | |
| status_msg = gr.Markdown("") | |
| with gr.Group(): | |
| gr.Markdown("## 🗂 Структура картки товару") | |
| with gr.Row(): | |
| with gr.Column(scale=1, variant="panel"): | |
| gr.Markdown("### 🖼 Фотографії") | |
| m_folder = gr.Textbox(label="Назва папки (у /models/)", info="Порожнє = автостворення") | |
| m_images = gr.File(label="Завантажити нові фото", file_count="multiple", type="filepath") | |
| with gr.Row(): | |
| upload_btn = gr.Button("✅ Завантажити") | |
| clear_img_btn = gr.Button("🗑 Очистити папку") | |
| with gr.Column(scale=1, variant="panel"): | |
| gr.Markdown("### 📄 Основна інформація") | |
| m_name = gr.Textbox(label="Назва (вкладка)", placeholder="Unstop Retail 4032W") | |
| m_price = gr.Textbox(label="💵 Ціна", placeholder="49 000 грн") | |
| m_title = gr.Textbox(label="⚡ Заголовок H1") | |
| m_subtitle = gr.Textbox(label="🔋 Підзаголовок") | |
| with gr.Row(): | |
| with gr.Column(variant="panel"): | |
| gr.Markdown("### 📝 Детальний опис (Markdown)") | |
| m_opis = gr.Textbox(label="Текст опису", lines=12, show_label=False) | |
| with gr.Row(): | |
| save_btn = gr.Button("💾 ЗБЕРЕГТИ ЗМІНИ ТОВАРУ", variant="primary", size="lg") | |
| # ── ВКЛ 2: НАЛАШТУВАННЯ САЙТУ ────────────────────────────────── | |
| with gr.Tab("⚙️ Налаштування сайту"): | |
| gr.Markdown("## 📞 Глобальні контакти") | |
| _c = get_contacts(BASE_DIR) | |
| c_phone = gr.Textbox(label="Телефон (для tel:)", value=_c.get("phone", ""), placeholder="+380675745662") | |
| c_phone_display = gr.Textbox(label="Телефон (відображення)", value=_c.get("phone_display", ""), placeholder="+38 067 574 56 62") | |
| c_tg_link = gr.Textbox(label="Telegram посилання", value=_c.get("tg_link", ""), placeholder="https://t.me/...") | |
| c_tg_display = gr.Textbox(label="Telegram підпис кнопки", value=_c.get("tg_display", ""), placeholder="Написати в Telegram") | |
| c_address = gr.Textbox(label="Адреса (HTML дозволено)", value=_c.get("address", ""), placeholder="м. Харків, ...") | |
| c_map_src = gr.Textbox(label="Src для <iframe> карти", value=_c.get("map_iframe", ""), lines=2) | |
| contacts_status = gr.Markdown("") | |
| save_contacts_btn = gr.Button("💾 ЗБЕРЕГТИ КОНТАКТИ", variant="primary", size="lg") | |
| # ── ОБРОБНИКИ: ТОВАРИ ────────────────────────────────────────────── | |
| def load_model_data(sel_name): | |
| if not sel_name: | |
| return "", "", "", "", "", "", "" | |
| for m in get_models_data(BASE_DIR): | |
| if m['name'] == sel_name: | |
| return m['name'], m['price'], m['title'], m['subtitle'], m.get('folder', ''), m['opis_raw'], "" | |
| return "", "", "", "", "", "", "" | |
| model_selector.change( | |
| load_model_data, inputs=model_selector, | |
| outputs=[m_name, m_price, m_title, m_subtitle, m_folder, m_opis, status_msg] | |
| ) | |
| def save_model_data(sel_name, name, price, title, sub, folder, opis): | |
| if not sel_name: | |
| return gr.update(), "⚠️ Немає моделі для збереження" | |
| models = get_models_data(BASE_DIR) | |
| for m in models: | |
| if m['name'] == sel_name: | |
| m['name'], m['price'], m['title'], m['subtitle'], m['folder'], m['opis_raw'] = \ | |
| name, price, title, sub, folder, opis | |
| break | |
| save_config(BASE_DIR, models) | |
| return gr.update(choices=[x['name'] for x in models], value=name), "✅ Збережено! Оновіть вітрину (F5)." | |
| save_btn.click( | |
| save_model_data, | |
| inputs=[model_selector, m_name, m_price, m_title, m_subtitle, m_folder, m_opis], | |
| outputs=[model_selector, status_msg] | |
| ) | |
| def add_new_model(): | |
| models = get_models_data(BASE_DIR) | |
| new_name = f"Новий товар {len(models) + 1}" | |
| models.append({"id": f"m_{int(time.time())}", "folder": "", "name": new_name, | |
| "title": "Новий заголовок", "subtitle": "Короткий опис", | |
| "price": "0 грн", "opis_raw": ""}) | |
| save_config(BASE_DIR, models) | |
| return gr.update(choices=[x['name'] for x in models], value=new_name), f"✅ Додано «{new_name}»!" | |
| add_btn.click(add_new_model, outputs=[model_selector, status_msg]) | |
| def delete_selected_model(sel_name): | |
| if not sel_name: | |
| return gr.update(), "⚠️ Не обрано товар" | |
| models = [m for m in get_models_data(BASE_DIR) if m['name'] != sel_name] | |
| save_config(BASE_DIR, models) | |
| new_sel = models[0]['name'] if models else None | |
| return gr.update(choices=[x['name'] for x in models], value=new_sel), f"🗑 «{sel_name}» видалено!" | |
| del_btn.click(delete_selected_model, inputs=[model_selector], outputs=[model_selector, status_msg]) | |
| def handle_upload(sel_name, current_folder, files): | |
| if not files: | |
| return current_folder, "⚠️ Виберіть файли." | |
| target_folder = current_folder.strip() or "".join(c if c.isalnum() else "_" for c in sel_name) | |
| target_dir = os.path.join(BASE_DIR, "models", target_folder) | |
| os.makedirs(target_dir, exist_ok=True) | |
| count = 0 | |
| for f in files: | |
| shutil.copy(f, os.path.join(target_dir, os.path.basename(f))) | |
| count += 1 | |
| models = get_models_data(BASE_DIR) | |
| for m in models: | |
| if m['name'] == sel_name: | |
| m['folder'] = target_folder | |
| save_config(BASE_DIR, models) | |
| return gr.update(value=target_folder), f"✅ Завантажено {count} фото у '{target_folder}'!" | |
| upload_btn.click(handle_upload, inputs=[model_selector, m_folder, m_images], outputs=[m_folder, status_msg]) | |
| def handle_clear_images(current_folder): | |
| target_folder = current_folder.strip() | |
| if not target_folder: | |
| return "⚠️ Папка не вказана." | |
| target_dir = os.path.join(BASE_DIR, "models", target_folder) | |
| count = 0 | |
| if os.path.exists(target_dir): | |
| for f in os.listdir(target_dir): | |
| if f.lower().endswith(('.webp', '.png', '.jpg', '.jpeg')): | |
| try: | |
| os.remove(os.path.join(target_dir, f)); count += 1 | |
| except Exception: | |
| pass | |
| return f"🗑 Видалено {count} фото!" | |
| clear_img_btn.click(handle_clear_images, inputs=[m_folder], outputs=[status_msg]) | |
| # ── ОБРОБНИКИ: КОНТАКТИ ──────────────────────────────────────────── | |
| def do_save_contacts(phone, phone_display, tg_link, tg_display, address, map_src): | |
| save_contacts(BASE_DIR, { | |
| "phone": phone.strip(), | |
| "phone_display": phone_display.strip(), | |
| "tg_link": tg_link.strip(), | |
| "tg_display": tg_display.strip(), | |
| "address": address.strip(), | |
| "map_iframe": map_src.strip(), | |
| }) | |
| return "✅ Контакти збережено! Оновіть вітрину (F5)." | |
| save_contacts_btn.click( | |
| do_save_contacts, | |
| inputs=[c_phone, c_phone_display, c_tg_link, c_tg_display, c_address, c_map_src], | |
| outputs=[contacts_status] | |
| ) | |
| return app_admin | |