Config admin : - Tarification activable avec paliers (ex: 1-5 = 0.50€, 6-10 = 0.40€) - Fonction calculate_price() utilitaire Galerie : - Affiche "X impressions — Y.YY €" quand tarification activée Écran impression/récap : - Galerie des photos sélectionnées avec miniatures + quantité (xN) - Prix total en haut - Choix du format d'impression - Bouton IMPRIMER Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
383 lines
14 KiB
Python
383 lines
14 KiB
Python
"""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
|