From 58bc91b9666464b2096eb5eb48725510f31fdb43 Mon Sep 17 00:00:00 2001 From: Benoit Date: Mon, 1 Jun 2026 22:30:51 +0200 Subject: [PATCH 1/2] fix: UX interface admin et client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Settings : suppression paramètres inutiles (mode PTT continu, feedback audio non implémenté) - Settings : conservation uniquement du paramètre vibrations (fonctionnel) - PTTButton : suppression init mode continu par défaut (redondant avec geste verrouillage) - PWAInstallPrompt : ajout fond semi-transparent et couleurs hardcodées pour lisibilité - Admin : fix dropdowns audio qui se réinitialisaient (useRef au lieu de useState pour édition) --- client/src/Admin.jsx | 42 ++++++++++-------- client/src/components/PTTButton.jsx | 10 ----- client/src/components/PWAInstallPrompt.css | 44 +++++++++++++------ client/src/components/Settings.jsx | 51 +--------------------- 4 files changed, 56 insertions(+), 91 deletions(-) diff --git a/client/src/Admin.jsx b/client/src/Admin.jsx index def1f6f..bb285ee 100644 --- a/client/src/Admin.jsx +++ b/client/src/Admin.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import './Admin.css'; import AudioRoutingMatrix from './components/AudioRoutingMatrix'; @@ -19,11 +19,12 @@ function Admin() { const [selectedInputDevice, setSelectedInputDevice] = useState(null); const [selectedOutputDevice, setSelectedOutputDevice] = useState(null); const [selectedSampleRate, setSelectedSampleRate] = useState(48000); - const [isEditingAudio, setIsEditingAudio] = useState(false); + const isEditingAudioRef = useRef(false); // Channel names (Phase 2.5) const [channelNames, setChannelNames] = useState({ inputs: {}, outputs: {} }); - const [editingChannelNames, setEditingChannelNames] = useState(false); + const editingChannelNamesRef = useRef(false); + const [, forceUpdate] = useState({}); // Gestion formulaire nouveau groupe const [showGroupForm, setShowGroupForm] = useState(false); @@ -104,10 +105,14 @@ function Admin() { const device = currentData.device || { inputChannels: 8, outputChannels: 8 }; setCurrentDevice(device); - setChannelNames(channelNamesData.channelNames || { inputs: {}, outputs: {} }); - // Ne réinitialiser les sélections que si l'utilisateur n'est pas en train d'éditer - if (!isEditingAudio) { + // Ne pas écraser les noms de canaux pendant l'édition + if (!editingChannelNamesRef.current) { + setChannelNames(channelNamesData.channelNames || { inputs: {}, outputs: {} }); + } + + // Ne réinitialiser les sélections que lors du chargement initial (pas en train d'éditer) + if (!isEditingAudioRef.current) { setSelectedInputDevice(device.inputDeviceId ?? null); setSelectedOutputDevice(device.outputDeviceId ?? null); setSelectedSampleRate(device.sampleRate || 48000); @@ -216,7 +221,8 @@ function Admin() { if (res.ok) { alert('Noms de canaux sauvegardés avec succès!'); - setEditingChannelNames(false); + editingChannelNamesRef.current = false; + forceUpdate({}); await loadAudioDevices(); } else { const error = await res.json(); @@ -251,7 +257,7 @@ function Admin() { }); if (res.ok) { - setIsEditingAudio(false); // Désactiver le mode édition + isEditingAudioRef.current = false; // Désactiver le mode édition alert('Configuration audio sauvegardée avec succès!'); await loadAudioDevices(); } else { @@ -442,7 +448,7 @@ function Admin() { { - setIsEditingAudio(true); + isEditingAudioRef.current = true; setSelectedOutputDevice(e.target.value === '' ? null : e.target.value); }} className="device-select" @@ -494,7 +500,7 @@ function Admin() { handleChange('defaultPTTMode', 'normal')} - /> -
- Mode normal (Push-To-Talk) -

Maintenir le bouton pour parler, relâcher pour arrêter

-
- - - - -

Feedback

@@ -112,19 +77,7 @@ export default function Settings({ isOpen, onClose }) { />
Vibrations -

Activer le retour haptique (si disponible)

-
- - -
From 77bc36b765bc8d389e184e482a63bf04f04a4b9f Mon Sep 17 00:00:00 2001 From: Benoit Date: Mon, 1 Jun 2026 23:04:57 +0200 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20am=C3=A9lioration=20UX=20interface?= =?UTF-8?q?=20admin=20audio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Admin : regroupement des 3 dropdowns cartes son dans une seule section - Admin : suppression du mode édition pour noms de canaux (directement éditables) - Admin : unification des boutons de sauvegarde en bas de chaque section - Admin : routing par hash URL pour persistance des onglets (#groups, #audio, etc.) - AudioRoutingMatrix : bouton sauvegarde déplacé en bas de la matrice - AudioRoutingMatrix : dropdowns de gain en nuance de bleu (cohérence visuelle) --- client/src/Admin.jsx | 225 ++++++++++--------- client/src/components/AudioRoutingMatrix.css | 18 +- client/src/components/AudioRoutingMatrix.jsx | 27 +-- 3 files changed, 145 insertions(+), 125 deletions(-) diff --git a/client/src/Admin.jsx b/client/src/Admin.jsx index bb285ee..80b3f99 100644 --- a/client/src/Admin.jsx +++ b/client/src/Admin.jsx @@ -5,7 +5,13 @@ import AudioRoutingMatrix from './components/AudioRoutingMatrix'; const API_URL = import.meta.env.VITE_API_URL || '/api'; function Admin() { - const [activeTab, setActiveTab] = useState('groups'); + // Lire l'onglet depuis l'URL hash (ex: #audio) ou utiliser 'groups' par défaut + const getInitialTab = () => { + const hash = window.location.hash.slice(1); // Enlever le # + return ['groups', 'audio', 'users', 'stats', 'logs'].includes(hash) ? hash : 'groups'; + }; + + const [activeTab, setActiveTab] = useState(getInitialTab()); const [groups, setGroups] = useState([]); const [users, setUsers] = useState([]); const [stats, setStats] = useState(null); @@ -23,8 +29,6 @@ function Admin() { // Channel names (Phase 2.5) const [channelNames, setChannelNames] = useState({ inputs: {}, outputs: {} }); - const editingChannelNamesRef = useRef(false); - const [, forceUpdate] = useState({}); // Gestion formulaire nouveau groupe const [showGroupForm, setShowGroupForm] = useState(false); @@ -34,6 +38,19 @@ function Admin() { audioBitrate: 96 }); + // Synchroniser l'onglet avec l'URL hash + useEffect(() => { + const handleHashChange = () => { + const hash = window.location.hash.slice(1); + if (['groups', 'audio', 'users', 'stats', 'logs'].includes(hash)) { + setActiveTab(hash); + } + }; + + window.addEventListener('hashchange', handleHashChange); + return () => window.removeEventListener('hashchange', handleHashChange); + }, []); + // Rafraîchissement automatique useEffect(() => { loadData(); @@ -105,11 +122,7 @@ function Admin() { const device = currentData.device || { inputChannels: 8, outputChannels: 8 }; setCurrentDevice(device); - - // Ne pas écraser les noms de canaux pendant l'édition - if (!editingChannelNamesRef.current) { - setChannelNames(channelNamesData.channelNames || { inputs: {}, outputs: {} }); - } + setChannelNames(channelNamesData.channelNames || { inputs: {}, outputs: {} }); // Ne réinitialiser les sélections que lors du chargement initial (pas en train d'éditer) if (!isEditingAudioRef.current) { @@ -221,8 +234,6 @@ function Admin() { if (res.ok) { alert('Noms de canaux sauvegardés avec succès!'); - editingChannelNamesRef.current = false; - forceUpdate({}); await loadAudioDevices(); } else { const error = await res.json(); @@ -317,31 +328,31 @@ function Admin() {