Icone app

This commit is contained in:
2026-05-01 19:41:41 +02:00
parent af07b6f266
commit 2c6ecf574c
10 changed files with 85 additions and 19 deletions

View File

@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#2563eb" />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/png" sizes="192x192" href="/icons/professeur-top-192.png" />
<link rel="apple-touch-icon" href="/icons/professeur-top-192.png" />
<link rel="icon" type="image/png" sizes="192x192" href="/icons/professeur-top-install-v2-192.png" />
<link rel="apple-touch-icon" href="/icons/professeur-top-install-v2-192.png" />
<title>Professeur virtuel</title>
<script type="module" src="/src/main.jsx"></script>
</head>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

View File

@@ -9,13 +9,13 @@
"theme_color": "#2563eb",
"icons": [
{
"src": "/icons/professeur-top-192.png",
"src": "/icons/professeur-top-install-v2-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/professeur-top-512.png",
"src": "/icons/professeur-top-install-v2-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"

9
frontend/public/sw.js Normal file
View File

@@ -0,0 +1,9 @@
self.addEventListener('install', () => {
self.skipWaiting()
})
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim())
})
self.addEventListener('fetch', () => {})

View File

@@ -209,6 +209,7 @@ function TutorApp({ currentUser, onLogout }) {
const [voiceProfiles, setVoiceProfiles] = useState([])
const [selectedVoiceProfileId, setSelectedVoiceProfileId] = useState('')
const [progressPanelOpen, setProgressPanelOpen] = useState(false)
const [settingsPanelOpen, setSettingsPanelOpen] = useState(false)
const mediaRecorderRef = useRef(null)
const mediaStreamRef = useRef(null)
const recordedChunksRef = useRef([])
@@ -493,6 +494,7 @@ function TutorApp({ currentUser, onLogout }) {
deactivateAutoListening(false)
setSessionActive(false)
setProgressPanelOpen(false)
setSettingsPanelOpen(false)
setCurrentInstruction('')
setAssessment(null)
setAssessmentAnswer('')
@@ -942,6 +944,15 @@ function TutorApp({ currentUser, onLogout }) {
<h1>{lessonFocus?.label || 'Cours du jour'}</h1>
</div>
<div className="lesson-actions">
<button
type="button"
className="icon-button"
aria-label="Parametres"
title="Parametres"
onClick={() => setSettingsPanelOpen(true)}
>
...
</button>
<button type="button" className="secondary-button" onClick={() => setProgressPanelOpen(true)}>
Progression
</button>
@@ -973,18 +984,6 @@ function TutorApp({ currentUser, onLogout }) {
}
placeholder={assessment ? 'Ta reponse au mini-test...' : 'Ecris ta reponse...'}
/>
<select
value={selectedVoiceProfileId}
onChange={(event) => setSelectedVoiceProfileId(event.target.value)}
title="Choisir la voix de Professeur TOP"
>
<option value="">Voix</option>
{voiceProfiles.map((profile) => (
<option key={profile.id} value={profile.id}>
{formatVoiceLabel(profile)}
</option>
))}
</select>
<button type="button" onClick={startVoiceInput} disabled={isTranscribing}>
{isAutoListening ? 'Stop micro' : isTranscribing ? 'Transcription...' : 'Dicter'}
</button>
@@ -1012,6 +1011,38 @@ function TutorApp({ currentUser, onLogout }) {
</aside>
</div>
)}
{settingsPanelOpen && (
<div className="progress-panel-backdrop" role="presentation" onClick={() => setSettingsPanelOpen(false)}>
<aside className="progress-panel card" role="dialog" aria-label="Parametres" onClick={(event) => event.stopPropagation()}>
<div className="progress-panel-head">
<h2>Parametres</h2>
<button type="button" className="secondary-button" onClick={() => setSettingsPanelOpen(false)}>
Fermer
</button>
</div>
<label className="settings-field">
<span>Voix de Professeur TOP</span>
<select
value={selectedVoiceProfileId}
onChange={(event) => setSelectedVoiceProfileId(event.target.value)}
>
<option value="">Voix de Professeur TOP</option>
{voiceProfiles.map((profile) => (
<option key={profile.id} value={profile.id}>
{formatVoiceLabel(profile)}
</option>
))}
</select>
</label>
{selectedVoiceProfile && (
<p className="muted voice-note visible-status">
Voix IA non humaine: {selectedVoiceProfile.label} - {selectedVoiceProfile.description}
</p>
)}
</aside>
</div>
)}
</main>
)}
</div>

View File

@@ -8,3 +8,9 @@ ReactDOM.createRoot(document.getElementById('root')).render(
<App />
</React.StrictMode>
)
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').catch(() => {})
})
}

View File

@@ -307,6 +307,14 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
display: flex;
gap: 0.75rem;
}
.icon-button {
width: 44px;
min-width: 44px;
padding: 0.8rem 0;
background: #e2e8f0;
color: #14213d;
font-weight: 800;
}
.student-home-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -379,7 +387,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
}
.lesson-composer {
display: grid;
grid-template-columns: minmax(0, 1fr) 150px 120px 110px;
grid-template-columns: minmax(0, 1fr) 120px 110px;
gap: 0.6rem;
}
.lesson-status {
@@ -410,6 +418,14 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
gap: 1rem;
margin-bottom: 1rem;
}
.settings-field {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.settings-field span {
font-weight: 700;
}
@keyframes pulse {
from { transform: scale(1); }
@@ -538,6 +554,11 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
width: 100%;
}
.icon-button {
width: 44px;
min-width: 44px;
}
.student-home {
padding: 0.5rem;
}
@@ -594,8 +615,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
gap: 0.5rem;
}
.lesson-composer input,
.lesson-composer select {
.lesson-composer input {
grid-column: 1 / -1;
}