"""Dialogue de configuration admin — accessible par appui long sur le titre.""" import json import os from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QGridLayout, QLineEdit, QPushButton, QLabel, QComboBox, QScrollArea, QWidget, QSpinBox, QCheckBox ) from PyQt6.QtCore import Qt from PyQt6.QtGui import QFont CONFIG_PATH = os.path.join( os.path.dirname(__file__), "..", "..", "..", "config", "settings.json" ) DEFAULT_CONFIG = { # WiFi hotspot "wifi_ap_name": "Photostation", "wifi_ap_password": "photo1234", # Serveur upload "server_ip": "0.0.0.0", "server_port": 8080, "upload_url": "https://photo.copydev.fr/upload", # Email IMAP "email_address": "photo@photostation.local", "imap_enabled": False, "imap_server": "", "imap_port": 993, "imap_user": "", "imap_password": "", "imap_folder": "INBOX", "imap_interval": 30, # Formats d'impression (activable + imprimante par format) "formats": { "10x15": {"enabled": True, "printer": "", "ratio": 1.5}, "13x18": {"enabled": False, "printer": "", "ratio": 1.385}, "15x20": {"enabled": False, "printer": "", "ratio": 1.333}, }, # Tarification "pricing": { "enabled": True, "currency": "€", "tiers": [ {"from": 1, "to": 5, "price": 0.50}, {"from": 6, "to": 10, "price": 0.40}, {"from": 11, "to": 999, "price": 0.30}, ], }, } def load_config(): try: with open(CONFIG_PATH, "r") as f: saved = json.load(f) config = dict(DEFAULT_CONFIG) config.update(saved) return config except (FileNotFoundError, json.JSONDecodeError): return dict(DEFAULT_CONFIG) def calculate_price(quantity, config=None): """Calcule le prix total pour une quantité donnée selon les paliers.""" if config is None: config = load_config() pricing = config.get("pricing", DEFAULT_CONFIG.get("pricing", {})) if not pricing.get("enabled", False) or quantity <= 0: return 0.0 tiers = pricing.get("tiers", []) # Trouver le palier applicable unit_price = 0.0 for tier in tiers: if tier["from"] <= quantity <= tier["to"]: unit_price = tier["price"] break if unit_price == 0.0 and tiers: unit_price = tiers[-1]["price"] return quantity * unit_price def save_config(config): os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) with open(CONFIG_PATH, "w") as f: json.dump(config, f, indent=2, ensure_ascii=False) class ConfigDialog(QDialog): """Panneau de configuration admin — plein écran, responsive.""" def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Configuration — Admin") self.config = load_config() self._setup_ui() def showEvent(self, event): super().showEvent(event) if self.parent(): self.setGeometry(self.parent().rect()) self.setStyleSheet("ConfigDialog { background-color: #f0f0f0; }") def _section_title(self, text): lbl = QLabel(text) lbl.setFont(QFont("", 13, QFont.Weight.Bold)) lbl.setStyleSheet("color: #2196F3; padding-top: 5px;") return lbl def _label(self, text): lbl = QLabel(text) lbl.setFont(QFont("", 12)) lbl.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) return lbl def _input(self, value, placeholder=""): inp = QLineEdit(str(value)) inp.setFont(QFont("", 12)) inp.setMinimumHeight(35) inp.setStyleSheet(""" QLineEdit { border: 2px solid #ccc; border-radius: 6px; padding: 5px 10px; background: white; } QLineEdit:focus { border-color: #2196F3; } """) if placeholder: inp.setPlaceholderText(placeholder) return inp def _password_input(self, value): inp = self._input(value) inp.setEchoMode(QLineEdit.EchoMode.Password) return inp def _spinbox(self, value, min_val, max_val): sb = QSpinBox() sb.setFont(QFont("", 12)) sb.setMinimumHeight(35) sb.setRange(min_val, max_val) sb.setValue(value) return sb def _setup_ui(self): layout = QVBoxLayout(self) layout.setContentsMargins(20, 10, 20, 10) layout.setSpacing(8) title = QLabel("Configuration Photostation") title.setFont(QFont("", 18, QFont.Weight.Bold)) title.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(title) scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) scroll.setStyleSheet("QScrollArea { border: none; }") content = QWidget() grid = QGridLayout(content) grid.setSpacing(8) grid.setColumnStretch(0, 1) grid.setColumnStretch(1, 2) row = 0 # ── WiFi ── grid.addWidget(self._section_title("WiFi (Hotspot)"), row, 0, 1, 2) row += 1 grid.addWidget(self._label("Nom du réseau :"), row, 0) self.wifi_name_input = self._input(self.config["wifi_ap_name"]) grid.addWidget(self.wifi_name_input, row, 1) row += 1 grid.addWidget(self._label("Mot de passe :"), row, 0) self.wifi_pass_input = self._input(self.config["wifi_ap_password"]) grid.addWidget(self.wifi_pass_input, row, 1) row += 1 # ── Serveur upload ── grid.addWidget(self._section_title("Serveur upload (QR Code / WiFi)"), row, 0, 1, 2) row += 1 grid.addWidget(self._label("IP d'écoute :"), row, 0) self.server_ip_input = self._input(self.config["server_ip"], "0.0.0.0 = toutes les interfaces") grid.addWidget(self.server_ip_input, row, 1) row += 1 grid.addWidget(self._label("Port :"), row, 0) self.server_port_input = self._spinbox(self.config["server_port"], 80, 65535) grid.addWidget(self.server_port_input, row, 1) row += 1 grid.addWidget(self._label("URL publique (QR) :"), row, 0) self.upload_url_input = self._input(self.config["upload_url"], "https://photo.exemple.fr/upload") grid.addWidget(self.upload_url_input, row, 1) row += 1 # ── Email IMAP ── grid.addWidget(self._section_title("Email (réception IMAP)"), row, 0, 1, 2) row += 1 grid.addWidget(self._label("Activer :"), row, 0) self.imap_enabled_cb = QCheckBox("Consulter la boîte mail") self.imap_enabled_cb.setFont(QFont("", 12)) self.imap_enabled_cb.setChecked(self.config["imap_enabled"]) grid.addWidget(self.imap_enabled_cb, row, 1) row += 1 grid.addWidget(self._label("Adresse affichée :"), row, 0) self.email_input = self._input(self.config["email_address"]) grid.addWidget(self.email_input, row, 1) row += 1 grid.addWidget(self._label("Serveur IMAP :"), row, 0) self.imap_server_input = self._input(self.config["imap_server"], "imap.gmail.com") grid.addWidget(self.imap_server_input, row, 1) row += 1 grid.addWidget(self._label("Port IMAP :"), row, 0) self.imap_port_input = self._spinbox(self.config["imap_port"], 1, 65535) grid.addWidget(self.imap_port_input, row, 1) row += 1 grid.addWidget(self._label("Utilisateur :"), row, 0) self.imap_user_input = self._input(self.config["imap_user"]) grid.addWidget(self.imap_user_input, row, 1) row += 1 grid.addWidget(self._label("Mot de passe :"), row, 0) self.imap_pass_input = self._password_input(self.config["imap_password"]) grid.addWidget(self.imap_pass_input, row, 1) row += 1 grid.addWidget(self._label("Dossier :"), row, 0) self.imap_folder_input = self._input(self.config["imap_folder"], "INBOX") grid.addWidget(self.imap_folder_input, row, 1) row += 1 grid.addWidget(self._label("Intervalle (sec) :"), row, 0) self.imap_interval_input = self._spinbox(self.config["imap_interval"], 10, 300) grid.addWidget(self.imap_interval_input, row, 1) row += 1 # ── Formats d'impression ── grid.addWidget(self._section_title("Formats d'impression"), row, 0, 1, 2) row += 1 formats = self.config.get("formats", DEFAULT_CONFIG["formats"]) self.format_widgets = {} for fmt_name, fmt_conf in formats.items(): # Checkbox activé cb = QCheckBox(f"{fmt_name} cm") cb.setFont(QFont("", 12)) cb.setChecked(fmt_conf.get("enabled", False)) grid.addWidget(cb, row, 0) # Imprimante associée printer = self._input(fmt_conf.get("printer", ""), "Imprimante CUPS (vide = défaut)") grid.addWidget(printer, row, 1) self.format_widgets[fmt_name] = {"checkbox": cb, "printer": printer} row += 1 # ── Tarification ── grid.addWidget(self._section_title("Tarification"), row, 0, 1, 2) row += 1 pricing = self.config.get("pricing", DEFAULT_CONFIG["pricing"]) grid.addWidget(self._label("Activer :"), row, 0) self.pricing_enabled_cb = QCheckBox("Afficher les prix") self.pricing_enabled_cb.setFont(QFont("", 12)) self.pricing_enabled_cb.setChecked(pricing.get("enabled", True)) grid.addWidget(self.pricing_enabled_cb, row, 1) row += 1 self.tier_widgets = [] tiers = pricing.get("tiers", []) for i, tier in enumerate(tiers): lbl = QLabel(f"De {tier['from']} à {tier['to']} :") lbl.setFont(QFont("", 12)) lbl.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) grid.addWidget(lbl, row, 0) price_row = QHBoxLayout() from_sb = self._spinbox(tier["from"], 1, 999) to_sb = self._spinbox(tier["to"], 1, 999) price_input = self._input(f"{tier['price']:.2f}", "0.50") price_input.setMaximumWidth(80) price_row.addWidget(QLabel("de")) price_row.addWidget(from_sb) price_row.addWidget(QLabel("à")) price_row.addWidget(to_sb) price_row.addWidget(QLabel("=")) price_row.addWidget(price_input) price_row.addWidget(QLabel("€/photo")) tier_container = QWidget() tier_container.setLayout(price_row) grid.addWidget(tier_container, row, 1) self.tier_widgets.append({"from": from_sb, "to": to_sb, "price": price_input}) row += 1 scroll.setWidget(content) layout.addWidget(scroll, 1) # Boutons buttons = QHBoxLayout() buttons.setSpacing(15) cancel_btn = QPushButton("Annuler") cancel_btn.setFont(QFont("", 13)) cancel_btn.setMinimumHeight(45) cancel_btn.setStyleSheet(""" QPushButton { background-color: #757575; color: white; border-radius: 8px; padding: 8px 25px; } QPushButton:pressed { background-color: #616161; } """) cancel_btn.clicked.connect(self.reject) buttons.addWidget(cancel_btn) save_btn = QPushButton("Enregistrer") save_btn.setFont(QFont("", 13, QFont.Weight.Bold)) save_btn.setMinimumHeight(45) save_btn.setStyleSheet(""" QPushButton { background-color: #4CAF50; color: white; border-radius: 8px; padding: 8px 25px; } QPushButton:pressed { background-color: #388E3C; } """) save_btn.clicked.connect(self._save) buttons.addWidget(save_btn) layout.addLayout(buttons) def _save(self): self.config["wifi_ap_name"] = self.wifi_name_input.text() self.config["wifi_ap_password"] = self.wifi_pass_input.text() self.config["server_ip"] = self.server_ip_input.text() self.config["server_port"] = self.server_port_input.value() self.config["upload_url"] = self.upload_url_input.text() self.config["email_address"] = self.email_input.text() self.config["imap_enabled"] = self.imap_enabled_cb.isChecked() self.config["imap_server"] = self.imap_server_input.text() self.config["imap_port"] = self.imap_port_input.value() self.config["imap_user"] = self.imap_user_input.text() self.config["imap_password"] = self.imap_pass_input.text() self.config["imap_folder"] = self.imap_folder_input.text() self.config["imap_interval"] = self.imap_interval_input.value() # Formats d'impression formats = self.config.get("formats", dict(DEFAULT_CONFIG["formats"])) for fmt_name, widgets in self.format_widgets.items(): if fmt_name not in formats: formats[fmt_name] = {} formats[fmt_name]["enabled"] = widgets["checkbox"].isChecked() formats[fmt_name]["printer"] = widgets["printer"].text() self.config["formats"] = formats # Tarification tiers = [] for tw in self.tier_widgets: try: price = float(tw["price"].text().replace(",", ".")) except ValueError: price = 0.0 tiers.append({ "from": tw["from"].value(), "to": tw["to"].value(), "price": price, }) self.config["pricing"] = { "enabled": self.pricing_enabled_cb.isChecked(), "currency": "€", "tiers": tiers, } save_config(self.config) self.accept() @staticmethod def open_config(parent=None): dialog = ConfigDialog(parent) return dialog.exec() == QDialog.DialogCode.Accepted