MedicalFlow
Application Mobile Docteur
Dr.
🗓 Planning
Jour
Semaine
Mois
🗓
Chargement...
📷 Scanner Patient
Scannez le QR code du patient pour ouvrir son dossier
🩺 Consultation
📋 Antécédents
🧪 Examens
🔬 Résultats
💊 Traitement & Plan
📅 Suivi
🗂 Historique
📹 Téléconsultation
Consultations vidéo — Jitsi Meet
📹
Chargement...
🔐 Gestion des accès
Contrôle des accès Secrétaire et Admin
🔐
Chargement...
'); w.document.close(); w.print(); } function _sendConsultPatient(id) { var c = window._lastConsultData && window._lastConsultData.find(function(x){ return x.id===id; }); if (!c) return; var pat = PATIENTS.find(function(x){ return x.id===c.patient_id; }); var phone = pat ? (pat.phone||'') : ''; var actes = []; try { actes = JSON.parse(c.actes||'[]'); } catch(e) {} var msg = 'Consultation du ' + (c.date||'') + '\n' + 'Motif: ' + (c.motif||'') + '\n' + (actes.length ? 'Actes: ' + actes.join(', ') + '\n' : '') + (c.diagnostic ? 'Diagnostic: ' + c.diagnostic + '\n' : '') + (c.prescription ? 'Prescription: ' + c.prescription + '\n' : '') + 'Dr. ' + (c.doctor||''); var waUrl = 'https://wa.me/' + phone.replace(/[^0-9]/g,'') + '?text=' + encodeURIComponent(msg); window.open(waUrl, '_blank'); } async function ouvrirHistoriqueMobile() { if (!currentPatientId) { toast('⚠️ Aucun patient sélectionné'); return; } var p = PATIENTS.find(function(x){ return x.id === currentPatientId; }); var patName = p ? p.full_name : 'Patient'; var patId = currentPatientId; openModal( '
' + '
🗂️ Historique consultations
' + '' + '
' + '
' + patName + '
' + '
Chargement...
' ); await _chargerHistoriqueConsult(patId); // Polling toutes les 4s tant que modal ouvert if (_modalPollInterval) clearInterval(_modalPollInterval); _modalPollInterval = setInterval(async function() { var list = document.getElementById('hist-consult-list'); if (!list) { clearInterval(_modalPollInterval); _modalPollInterval = null; return; } await _chargerHistoriqueConsult(patId); }, 4000); } async function _chargerHistoriqueConsult(patId) { var list = document.getElementById('hist-consult-list'); if (!list) return; try { var { data, error } = await db.from('consultations') .select('*') .eq('patient_id', patId) .eq('cabinet_id', CABINET_ID) .order('date', { ascending: false }) .limit(50); if (error) { list.innerHTML = '
⚠️
Erreur: ' + error.message + '
'; return; } if (!data || !data.length) { // Essayer sans filtre cabinet_id pour debug var { data: data2 } = await db.from('consultations').select('id,patient_id,cabinet_id,motif,date').eq('patient_id', patId).limit(5); var debugMsg = data2 && data2.length ? 'Trouvé ' + data2.length + ' sans cabinet_id filter. cabinet_id=' + (data2[0].cabinet_id||'null') + ' vs ' + CABINET_ID : 'Aucune consultation pour patient ' + patId; list.innerHTML = '
🗂️
' + debugMsg + '
'; return; } window._lastConsultData = data; list.innerHTML = renderConsultationsList(data); } catch(e) { list.innerHTML = '
⚠️
Erreur: ' + e.message + '
'; } } function switchSection(section) { currentSection = section; var sections = ['motif','antecedents','examens_prescrits','examens','plans','suivi','historique']; document.querySelectorAll('.stab').forEach(function(t, i) { t.classList.toggle('active', sections[i] === section); }); renderSection(section); } function renderSection(section) { console.log('[DEBUG] renderSection called:', section, 'patient:', currentPatientId); var content = document.getElementById('section-content'); if (!content) { console.log('[DEBUG] section-content NOT FOUND'); return; } if (!currentPatientId) { content.innerHTML = '
👤
Aucun patient sélectionné
Scannez un QR ou cherchez un patient
'; return; } if (!DOSSIERS[currentPatientId]) DOSSIERS[currentPatientId] = _blankDossier(currentPatientId); var dos = DOSSIERS[currentPatientId]; // Convert single objects to arrays — never reset to [] if data exists if (!dos.motif) dos.motif = []; else if (!Array.isArray(dos.motif) && dos.motif.raison) dos.motif = [dos.motif]; if (!dos.traitements_cours) dos.traitements_cours = []; else if (!Array.isArray(dos.traitements_cours) && dos.traitements_cours.medicaments) dos.traitements_cours = [dos.traitements_cours]; if (!dos.examens_prescrits) dos.examens_prescrits = []; else if (!Array.isArray(dos.examens_prescrits)) dos.examens_prescrits = []; if (!dos.examens) dos.examens = []; else if (!Array.isArray(dos.examens)) dos.examens = []; if (!dos.diagnostics) dos.diagnostics = []; else if (!Array.isArray(dos.diagnostics) && dos.diagnostics.diagnostic) dos.diagnostics = [dos.diagnostics]; if (!dos.plans) dos.plans = []; else if (!Array.isArray(dos.plans) && (dos.plans.prescription||dos.plans.traitements_proposes)) dos.plans = [dos.plans]; if (!dos.suivi) dos.suivi = []; else if (!Array.isArray(dos.suivi)) dos.suivi = []; if (!dos.antecedents) dos.antecedents = {maladies:[],chirurgies:[],hospitalisations:[],famille:[],allergies:[]}; var html = ''; if (section === 'traitements') { var trtsRaw = dos.traitements_cours; var trts = Array.isArray(trtsRaw) ? trtsRaw : (trtsRaw && trtsRaw.medicaments ? [trtsRaw] : []); var lastTrt = trts[0] || {}; var lastMeds = Array.isArray(lastTrt.medicaments) ? lastTrt.medicaments : []; var lastMed = lastMeds[0] || {}; html = '
' + '
💊 Prescription
' + '
' + '' + '' + '
' + '
' + '
' + '
' + '
' + '
' + '' + '
'; } else if (section === 'examens_prescrits') { var epArr = Array.isArray(dos.examens_prescrits) ? dos.examens_prescrits : []; var epHtml = epArr.length ? epArr.map(function(e, i) { var urgColor = e.urgence==='tres_urgent'?'var(--danger)':e.urgence==='urgent'?'var(--warn)':'var(--accent)'; return '
' + '
' + '' + '' + (e.urgence==='tres_urgent'?'🔴 Très urgent':e.urgence==='urgent'?'🟠 Urgent':'🟢 Normal') + '' + '
' + '
' + (e.type||'') + '
' + (e.instructions ? '
' + e.instructions + '
' : '') + (e.statut==='fait' ? '
✅ Résultat reçu
' : '
⏳ En attente
') + '
'; }).join('') + (epArr.length ? '
' : '') : '
Aucun examen prescrit
'; html = '
' + '
' + '
🧪 Examens prescrits
' + '' + '
' + epHtml + '
'; } else if (section === 'motif') { var motifRaw = dos.motif; var motifEntries = Array.isArray(motifRaw) ? motifRaw : (motifRaw && motifRaw.raison ? [motifRaw] : []); var timeSlots = ['08:00','08:30','09:00','09:30','10:00','10:30','11:00','11:30','12:00','14:00','14:30','15:00','15:30','16:00','16:30','17:00','17:30','18:00']; var slotsHtml = '' + timeSlots.map(function(h){ return ''; }).join(''); var histHtml = motifEntries.length ? motifEntries.map(function(e,i){ return '
' + '
' + '' + (e.date||'') + ' � ' + (e.medecin||'Dr.') + '' + '
' + '
' + (e.raison||'') + '
' + (e.symptomes ? '
' + e.symptomes + '
' : '') + (e.diagnostic ? '
\ud83c\udfe5 ' + e.diagnostic + '
' : '') + (e.prescription ? '
\ud83d\udc8a ' + e.prescription + '
' : '') + (e.rdv ? '
\ud83d\udcc5 RDV: ' + e.rdv + '
' : '') + '
'; }).join('') : '
Aucune consultation enregistr�e
'; html = '
' + '
' + '
\ud83e\ude7a Consultations
' + '
' + '' + motifEntries.length + ' visite(s)' + '' + '
' + '
' + histHtml + '
'; } else if (section === 'antecedents') { var ant = dos.antecedents || {}; html = '
' + '
📋 Antécédents médicaux
' + renderAntGroup('Maladies passées', 'maladies', ant.maladies||[], '🏥') + renderAntGroup('Chirurgies', 'chirurgies', ant.chirurgies||[], '🔧') + renderAntGroup('Hospitalisations', 'hospitalisations', ant.hospitalisations||[], '🏨') + renderAntGroup('Antécédents familiaux', 'famille', ant.famille||[], '👨‍👩‍👧') + renderAntGroup('Allergies', 'allergies', ant.allergies||[], '⚠️') + '
'; } else if (section === 'examens') { var examEntries = Array.isArray(dos.examens) ? dos.examens : []; var examHtml = examEntries.length ? examEntries.map(function(e) { return '
' + '
' + (e.date||'') + (e.medecin ? ' · ' + e.medecin : '') + (e.type ? ' · ' + e.type + '' : '') + '
' + (e.resultat ? '
' + e.resultat + '
' : '') + '
'; }).join('') : '
Aucun examen enregistré
'; html = '
' + '
' + '
🔬 Résultats examens
' + '' + '
' + examHtml + '
'; } else if (section === 'plans') { var planRaw = dos.plans; var planEntries = Array.isArray(planRaw) ? planRaw : (planRaw && (planRaw.prescription||planRaw.traitements_proposes) ? [planRaw] : []); var lastPlan = planEntries[0] || {}; var lastRx = Array.isArray(lastPlan.prescription) ? lastPlan.prescription.join('\n') : ''; var lastTrtP = Array.isArray(lastPlan.traitements_proposes) ? lastPlan.traitements_proposes.join('\n') : ''; var lastReco = Array.isArray(lastPlan.recommandations) ? lastPlan.recommandations.join('\n') : ''; var trtsRaw = dos.traitements_cours; var trts = Array.isArray(trtsRaw) ? trtsRaw : []; var lastTrt = trts[0] || {}; var lastMeds = Array.isArray(lastTrt.medicaments) ? lastTrt.medicaments : []; var lastMed = lastMeds[0] || {}; var planHtml = planEntries.length ? planEntries.map(function(p) { var meds = Array.isArray(p.medicaments) ? p.medicaments : (dos.traitements_cours && Array.isArray(dos.traitements_cours[0] && dos.traitements_cours[0].medicaments) ? dos.traitements_cours[0].medicaments : []); var rx = Array.isArray(p.prescription) ? p.prescription : []; var trts = Array.isArray(p.traitements_proposes) ? p.traitements_proposes : []; var recos = Array.isArray(p.recommandations) ? p.recommandations : []; return '
' + '
' + (p.date||'') + (p.medecin ? ' · ' + p.medecin : '') + '
' + (meds.length ? '
💊 ' + meds.map(function(m){ return (m.nom||m) + (m.posologie ? ' — ' + m.posologie : '') + (m.duree ? ' / ' + m.duree : ''); }).join(', ') + '
' : '') + (rx.length ? '
📋 ' + rx.join(', ') + '
' : '') + (trts.length ? '
🔧 ' + trts.join(', ') + '
' : '') + (recos.length ? '
💡 ' + recos.join(', ') + '
' : '') + '
'; }).join('') : '
Aucun traitement enregistré
'; html = '
' + '
' + '
💊 Traitement & Plan
' + '' + '
' + planHtml + '
'; } else if (section === 'suivi') { var suiviRaw = dos.suivi; var suiviEntries = Array.isArray(suiviRaw) ? suiviRaw : (suiviRaw && suiviRaw.resume ? [suiviRaw] : []); var lastSuivi = suiviEntries[0] || {}; var suiviHtml = suiviEntries.length ? suiviEntries.map(function(s) { return '
' + '
' + (s.date||'') + (s.medecin ? ' · ' + s.medecin : '') + (s.type ? ' · ' + s.type + '' : '') + '
' + (s.resume ? '
' + s.resume + '
' : '') + '
' + (s.evolution ? '📈 ' + s.evolution + '' : '') + (s.reaction_traitement ? '💊 ' + s.reaction_traitement + '' : '') + '
' + '
'; }).join('') : '
Aucun suivi enregistré
'; html = '
' + '
' + '
📅 Suivi
' + '' + '
' + suiviHtml + '
'; } else if (section === 'historique') { html = renderHistorique(dos); } else { html = '
🔍
Section inconnue: ' + section + '
'; } console.log('[DEBUG] html length for', section, ':', html.length); content.innerHTML = html; } function renderEntries(arr, section, labelFn) { if (!Array.isArray(arr) || !arr.length) return ''; return '
' + '
📂 Historique (' + arr.length + ')
' + arr.map(function(e, i) { return '
' + '' + '
' + (labelFn(e)||'') + '
' + '
'; }).join('') + '
'; } function renderAntGroup(label, key, items, icon) { return '
' + '
' + icon + ' ' + label + '
' + items.map(function(item, i) { return '
' + '' + item + '' + '
'; }).join('') + '
' + '' + '' + '
' + '
'; } function renderHistorique(dos) { var all = []; var push = function(arr, section, labelFn) { if (!Array.isArray(arr)) return; arr.forEach(function(e) { all.push({ date:e.date||'', section:section, label:labelFn(e) }); }); }; push(dos.motif, 'Motif', function(e){ return e.raison||''; }); push(dos.examens, 'Examen', function(e){ return e.type+' — '+(e.resultat||'').substring(0,50); }); push(dos.diagnostics, 'Diagnostic', function(e){ return (e.certitude||'')+' — '+(e.diagnostic||''); }); push(dos.plans, 'Plan', function(e){ return (Array.isArray(e.prescription)?e.prescription:[]).join(', ').substring(0,60); }); push(dos.suivi, 'Suivi', function(e){ return (e.type||'')+' — '+(e.resume||'').substring(0,50); }); all.sort(function(a,b){ return b.date.localeCompare(a.date); }); if (!all.length) return '
📂
Dossier vide
'; return '
🗂 Historique dossier (' + all.length + ' entrées)
' + all.map(function(e) { return '
' + '' + '
' + e.label + '
' + '
'; }).join('') + '
'; } // ── SAVE FUNCTIONS ── function openNewConsultDocModal() { var timeSlots = ['08:00','08:30','09:00','09:30','10:00','10:30','11:00','11:30','12:00','14:00','14:30','15:00','15:30','16:00','16:30','17:00','17:30','18:00']; var slotsHtml = '' + timeSlots.map(function(h){ return ''; }).join(''); openModal( '
' + '
🩺 Nouvelle consultation
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '' ); } async function saveConsultDocModal() { var motif = (document.getElementById('m-cons-motif')||{value:''}).value.trim(); if (!motif) { toast('⚠️ Motif requis'); return; } var symptomes = (document.getElementById('m-cons-symptomes')||{value:''}).value.trim(); var diagnostic = (document.getElementById('m-cons-diagnostic')||{value:''}).value.trim(); var prescription = (document.getElementById('m-cons-prescription')||{value:''}).value.trim(); var rdvDate = (document.getElementById('m-cons-rdv-date')||{value:''}).value; var rdvHeure = (document.getElementById('m-cons-rdv-heure')||{value:''}).value; var now = new Date(); var today = now.toISOString().split('T')[0]; var heure = now.getHours().toString().padStart(2,'0') + ':' + now.getMinutes().toString().padStart(2,'0'); if (!DOSSIERS[currentPatientId]) DOSSIERS[currentPatientId] = _blankDossier(currentPatientId); var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.motif)) dos.motif = []; var entry = { date: today, medecin: _doctorName||'Dr. Diallo', raison: motif, symptomes: symptomes, diagnostic: diagnostic, prescription: prescription, rdv: rdvDate && rdvHeure ? rdvDate + ' a ' + rdvHeure : null }; dos.motif.unshift(entry); saveDossierSection(currentPatientId, 'motif', dos.motif); db.from('consultations').insert({ id: crypto.randomUUID(), cabinet_id: CABINET_ID, patient_id: currentPatientId, doctor: entry.medecin, type: 'Consultation', date: today, heure: heure, motif: motif, notes: symptomes, diagnostic: diagnostic||null, prescription: prescription||null, actes: '[]', suivant: entry.rdv }); if (rdvDate && rdvHeure) { var pat = PATIENTS.find(function(x){ return x.id===currentPatientId; }); db.from('bookings').insert({ id: crypto.randomUUID(), cabinet_id: CABINET_ID, patient_id: currentPatientId, patient_name: pat?pat.full_name:'', date: rdvDate, time: rdvHeure, duration: 30, reason: 'Suivi - ' + motif.substring(0,40), doctor: entry.medecin, status: 'scheduled' }); } toast('✅ Consultation enregistrée'); closeModal(); renderSection('motif'); } function openNewExamenPrescritModal() { openModal( '
' + '
🧪 Prescrire un examen
' + '
' + '
' + '
' + '
' + '' ); } function saveExamenPrescritModal() { var type = (document.getElementById('m-ep-type')||{value:'Autre'}).value; var urgence = (document.getElementById('m-ep-urgence')||{value:'normal'}).value; var instructions = (document.getElementById('m-ep-instructions')||{value:''}).value.trim(); if (!DOSSIERS[currentPatientId]) DOSSIERS[currentPatientId] = _blankDossier(currentPatientId); var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.examens_prescrits)) dos.examens_prescrits = []; dos.examens_prescrits.unshift({ type: type, urgence: urgence, instructions: instructions, date: new Date().toISOString().split('T')[0], statut: 'en_attente' }); saveDossierSection(currentPatientId, 'examens_prescrits', dos.examens_prescrits); toast('✅ Examen prescrit'); closeModal(); renderSection('examens_prescrits'); } function openNewExamenResultatModal() { openModal( '
' + '
🔬 Résultat examen
' + '
' + '
' + '
' + '
' + '' ); } function saveExamenModal() { var type = (document.getElementById('m-ex-type')||{value:'Autre'}).value; var resultat = (document.getElementById('m-ex-resultat')||{value:''}).value.trim(); var date = (document.getElementById('m-ex-date')||{value:''}).value; if (!resultat) { toast('⚠️ Entrez le résultat'); return; } if (!DOSSIERS[currentPatientId]) DOSSIERS[currentPatientId] = _blankDossier(currentPatientId); var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.examens)) dos.examens = []; dos.examens.unshift({ type: type, resultat: resultat, date: date, medecin: _doctorName || 'Dr. Diallo' }); saveDossierSection(currentPatientId, 'examens', dos.examens); toast('✅ Résultat enregistré'); closeModal(); renderSection('examens'); } function openNewPlanDocModal() { openModal( '
' + '
💊 Nouveau traitement / Plan
' + '
' + '
' + '
' + '
' + '
' + '' ); } function savePlanDocModal() { if (!DOSSIERS[currentPatientId]) DOSSIERS[currentPatientId] = _blankDossier(currentPatientId); var dos = DOSSIERS[currentPatientId]; var med = (document.getElementById('m-trt-med')||{value:''}).value.trim(); var posologie = (document.getElementById('m-trt-posologie')||{value:''}).value.trim(); var duree = (document.getElementById('m-trt-duree')||{value:''}).value.trim(); var rx = (document.getElementById('m-plan-rx')||{value:''}).value.trim(); var reco = (document.getElementById('m-plan-reco')||{value:''}).value.trim(); var today = new Date().toISOString().split('T')[0]; if (med) { if (!Array.isArray(dos.traitements_cours)) dos.traitements_cours = []; dos.traitements_cours.unshift({ date: today, medecin: _doctorName||'Dr. Diallo', medicaments: [{nom:med,dosage:posologie,duree:duree}], note: '' }); saveDossierSection(currentPatientId, 'traitements_cours', dos.traitements_cours); } if (rx) { if (!Array.isArray(dos.plans)) dos.plans = []; dos.plans.unshift({ date: today, medecin: _doctorName||'Dr. Diallo', prescription: rx.split('\n').filter(Boolean), traitements_proposes: [], recommandations: reco ? reco.split('\n').filter(Boolean) : [] }); saveDossierSection(currentPatientId, 'plans', dos.plans); } toast('✅ Traitement enregistré'); closeModal(); renderSection('plans'); } function openNewSuiviDocModal() { openModal( '
' + '
📅 Nouveau suivi
' + '
' + '
' + '
' + '
' + '
' + '' ); } function saveSuiviDocModal() { var condition = (document.getElementById('m-suivi-condition')||{value:''}).value.trim(); if (!condition) { toast('⚠️ Entrez la condition'); return; } if (!DOSSIERS[currentPatientId]) DOSSIERS[currentPatientId] = _blankDossier(currentPatientId); var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.suivi)) dos.suivi = []; dos.suivi.unshift({ condition: condition, type: (document.getElementById('m-suivi-type')||{value:'Consultation'}).value, date: (document.getElementById('m-suivi-date')||{value:new Date().toISOString().split('T')[0]}).value, medecin: _doctorName || 'Dr. Diallo', resume: (document.getElementById('m-suivi-resume')||{value:''}).value.trim(), evolution: (document.getElementById('m-suivi-evolution')||{value:'Stable'}).value, reaction_traitement: (document.getElementById('m-suivi-reaction')||{value:'Bonne'}).value }); saveDossierSection(currentPatientId, 'suivi', dos.suivi); toast('✅ Suivi enregistré'); closeModal(); renderSection('suivi'); } function saveConsultationDoc() { var motif = (document.getElementById('s-motif-raison')||{value:''}).value.trim(); if (!motif) { toast('\u26a0\ufe0f Motif requis'); return; } var symptomes = (document.getElementById('s-motif-symptomes')||{value:''}).value.trim(); var diagnostic = (document.getElementById('s-motif-diagnostic')||{value:''}).value.trim(); var prescription = (document.getElementById('s-motif-prescription')||{value:''}).value.trim(); var rdvDate = (document.getElementById('s-motif-rdv-date')||{value:''}).value; var rdvHeure = (document.getElementById('s-motif-rdv-heure')||{value:''}).value; var now = new Date(); var today = now.toISOString().split('T')[0]; var heure = now.getHours().toString().padStart(2,'0') + ':' + now.getMinutes().toString().padStart(2,'0'); if (!DOSSIERS[currentPatientId]) DOSSIERS[currentPatientId] = _blankDossier(currentPatientId); var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.motif)) dos.motif = []; var entry = { date: today, medecin: _doctorName || 'Dr. Diallo', raison: motif, symptomes: symptomes, diagnostic: diagnostic, prescription: prescription, rdv: rdvDate && rdvHeure ? rdvDate + ' a ' + rdvHeure : null }; dos.motif.unshift(entry); saveDossierSection(currentPatientId, 'motif', dos.motif); // Sauvegarder dans table consultations Supabase db.from('consultations').insert({ id: crypto.randomUUID(), cabinet_id: CABINET_ID, patient_id: currentPatientId, doctor: entry.medecin, type: 'Consultation', date: today, heure: heure, motif: motif, notes: symptomes, diagnostic: diagnostic || null, prescription: prescription || null, actes: '[]', suivant: entry.rdv }).then(function(r){ if(r.error) console.warn('consult insert:', r.error.message); }); // Creer booking si RDV if (rdvDate && rdvHeure) { var pat = PATIENTS.find(function(x){ return x.id===currentPatientId; }); db.from('bookings').insert({ id: crypto.randomUUID(), cabinet_id: CABINET_ID, patient_id: currentPatientId, patient_name: pat ? pat.full_name : '', date: rdvDate, time: rdvHeure, duration: 30, reason: 'Suivi - ' + motif.substring(0,40), doctor: entry.medecin, status: 'scheduled' }).then(function(r){ if(r.error) console.warn('booking insert:', r.error.message); }); } toast('\u2705 Consultation enregistree'); renderSection('motif'); } function removeMotifEntryDoc(i) { if (!DOSSIERS[currentPatientId]) return; var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.motif)) return; dos.motif.splice(i, 1); saveDossierSection(currentPatientId, 'motif', dos.motif); renderSection('motif'); } function saveMotif() { var raison = (document.getElementById('s-motif-raison')||{value:''}).value.trim(); if (!raison) { toast('⚠️ Entrez le motif'); return; } var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.motif)) dos.motif = []; dos.motif.unshift({ date: new Date().toISOString().split('T')[0], medecin: _doctorName, raison: raison, symptomes: (document.getElementById('s-motif-symptomes')||{value:''}).value.trim(), duree: '', evolution: (document.getElementById('s-motif-evolution')||{value:'Stable'}).value }); saveDossierSection(currentPatientId, 'motif', dos.motif); toast('✅ Motif enregistré — synchronisé'); renderSection('motif'); } function saveExamen() { var resultat = (document.getElementById('s-exam-resultat')||{value:''}).value.trim(); if (!resultat) { toast('⚠️ Entrez le résultat'); return; } var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.examens)) dos.examens = []; dos.examens.unshift({ date: (document.getElementById('s-exam-date')||{value:''}).value, medecin: _doctorName, type: (document.getElementById('s-exam-type')||{value:'Autre'}).value, resultat: resultat }); saveDossierSection(currentPatientId, 'examens', dos.examens); toast('✅ Examen enregistré — synchronisé'); renderSection('examens'); } function saveDiagnostic() { var text = (document.getElementById('s-diag-text')||{value:''}).value.trim(); if (!text) { toast('⚠️ Entrez le diagnostic'); return; } var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.diagnostics)) dos.diagnostics = []; dos.diagnostics.unshift({ date: (document.getElementById('s-diag-date')||{value:''}).value, medecin: _doctorName, diagnostic: text, certitude: (document.getElementById('s-diag-certitude')||{value:'Confirmé'}).value }); saveDossierSection(currentPatientId, 'diagnostics', dos.diagnostics); toast('✅ Diagnostic enregistré — synchronisé'); renderSection('diagnostics'); } function savePlan() { var rx = (document.getElementById('s-plan-rx')||{value:''}).value.trim().split('\n').filter(Boolean); var trt = (document.getElementById('s-plan-trt')||{value:''}).value.trim().split('\n').filter(Boolean); var reco = (document.getElementById('s-plan-reco')||{value:''}).value.trim().split('\n').filter(Boolean); if (!rx.length && !trt.length) { toast('⚠️ Entrez au moins une prescription ou soin'); return; } var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.plans)) dos.plans = []; dos.plans.unshift({ date: new Date().toISOString().split('T')[0], medecin: _doctorName, prescription: rx, traitements_proposes: trt, recommandations: reco }); saveDossierSection(currentPatientId, 'plans', dos.plans); toast('✅ Plan enregistré — synchronisé'); renderSection('plans'); } function saveSuivi() { var resume = (document.getElementById('s-suivi-resume')||{value:''}).value.trim(); if (!resume) { toast('⚠️ Entrez le compte rendu'); return; } var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.suivi)) dos.suivi = []; dos.suivi.unshift({ date: (document.getElementById('s-suivi-date')||{value:''}).value, medecin: _doctorName, type: (document.getElementById('s-suivi-type')||{value:'Consultation'}).value, resume: resume, evolution: (document.getElementById('s-suivi-evolution')||{value:'Stable'}).value, reaction_traitement: (document.getElementById('s-suivi-reaction')||{value:'Bonne'}).value }); saveDossierSection(currentPatientId, 'suivi', dos.suivi); toast('✅ Suivi enregistré — synchronisé'); renderSection('suivi'); } function addAntecedent(key) { var input = document.getElementById('ant-input-' + key); if (!input || !input.value.trim()) return; var dos = DOSSIERS[currentPatientId]; if (!dos.antecedents[key]) dos.antecedents[key] = []; dos.antecedents[key].push(input.value.trim()); input.value = ''; saveDossierSection(currentPatientId, 'antecedents', dos.antecedents); renderSection('antecedents'); } function removeAntecedent(key, idx) { var dos = DOSSIERS[currentPatientId]; if (dos.antecedents[key]) dos.antecedents[key].splice(idx, 1); saveDossierSection(currentPatientId, 'antecedents', dos.antecedents); renderSection('antecedents'); } function savePrescription() { var med = (document.getElementById('s-trt-med')||{value:''}).value.trim(); if (!med) { toast('⚠️ Entrez le médicament'); return; } var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.traitements_cours)) dos.traitements_cours = []; // Chercher une visite du jour existante ou créer une nouvelle var today = new Date().toISOString().split('T')[0]; var visite = dos.traitements_cours.find(function(v){ return v.date === today && v.medecin === _doctorName; }); if (!visite) { visite = { date: today, medecin: _doctorName, medicaments: [], note: '' }; dos.traitements_cours.unshift(visite); } visite.medicaments.push({ nom: med, posologie: (document.getElementById('s-trt-posologie')||{value:''}).value.trim(), duree: (document.getElementById('s-trt-duree')||{value:''}).value.trim() }); var note = (document.getElementById('s-trt-note')||{value:''}).value.trim(); if (note) visite.note = note; saveDossierSection(currentPatientId, 'traitements_cours', dos.traitements_cours); document.getElementById('s-trt-med').value = ''; document.getElementById('s-trt-posologie').value = ''; document.getElementById('s-trt-duree').value = ''; toast('✅ ' + med + ' ajouté — synchronisé'); renderSection('traitements'); } function saveExamenPrescrit() { var type = (document.getElementById('s-ep-type')||{value:''}).value; if (!type) { toast('⚠️ Sélectionnez un type'); return; } var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.examens_prescrits)) dos.examens_prescrits = []; dos.examens_prescrits.unshift({ id: crypto.randomUUID(), date: new Date().toISOString().split('T')[0], medecin: _doctorName, type: type, urgence: (document.getElementById('s-ep-urgence')||{value:'normal'}).value, instructions: (document.getElementById('s-ep-instructions')||{value:''}).value.trim(), statut: 'en_attente' }); saveDossierSection(currentPatientId, 'examens_prescrits', dos.examens_prescrits); toast('✅ Examen prescrit: ' + type + ' — synchronisé'); renderSection('examens_prescrits'); } function sendExamensWhatsApp() { var p = PATIENTS.find(function(x){ return x.id === currentPatientId; }); if (!p) { toast('⚠️ Aucun patient'); return; } var dos = DOSSIERS[currentPatientId]; var examens = (dos.examens_prescrits || []).filter(function(e){ return e.statut !== 'fait'; }); if (!examens.length) { toast('⚠️ Aucun examen en attente'); return; } var phone = (p.phone||'').replace(/[^0-9+]/g,''); var waPhone = phone.startsWith('+') ? phone.replace('+','') : '221'+phone.replace(/^0/,''); var _nl = '%0A'; var list = examens.map(function(e){ return '- '+e.type+(e.urgence==='tres_urgent'?' (URGENT)':e.urgence==='urgent'?' (Urgent)':'')+(e.instructions?' : '+e.instructions:''); }).join(_nl); var waText = 'Ordres+examens+MedicalFlow'+_nl+ 'Patient:+'+encodeURIComponent(p.full_name)+_nl+ 'Date:+'+new Date().toISOString().split('T')[0]+_nl+ 'Docteur:+'+encodeURIComponent(_doctorName)+_nl+_nl+ 'Examens+a+realiser:'+_nl+encodeURIComponent(list)+_nl+_nl+ 'Cabinet+Dr.+Diallo,+Dakar'; if (waPhone.length > 6) window.open('https://wa.me/'+waPhone+'?text='+waText, '_blank'); else toast('⚠️ Numéro introuvable'); } // ══════════════════════════════════════════ // ORDONNANCE // ══════════════════════════════════════════ function populatePatientSelects() { var sel = document.getElementById('ord-patient-sel'); if (!sel) return; var current = sel.value; sel.innerHTML = '' + PATIENTS.map(function(p) { return ''; }).join(''); if (currentPatientId) { sel.value = currentPatientId; updateOrdPatient(); } } function updateOrdPatient() { var sel = document.getElementById('ord-patient-sel'); var infoEl = document.getElementById('ord-patient-info'); if (!sel || !infoEl) return; var p = PATIENTS.find(function(x){ return x.id === sel.value; }); if (!p) { infoEl.style.display = 'none'; return; } infoEl.style.display = 'block'; infoEl.innerHTML = '
' + '🩸 ' + (p.blood_group||'?') + '' + (p.allergies && p.allergies !== 'Aucune' ? '⚠️ ' + p.allergies + '' : '') + '' + (p.phone||'') + '' + '
'; } function addMedToOrd() { var name = (document.getElementById('ord-med-name')||{value:''}).value.trim(); var posologie = (document.getElementById('ord-posologie')||{value:''}).value.trim(); var duree = (document.getElementById('ord-duree')||{value:''}).value.trim(); if (!name) { toast('⚠️ Entrez le médicament'); return; } _ordDrugs.push({ nom: name, posologie: posologie, duree: duree }); document.getElementById('ord-med-name').value = ''; document.getElementById('ord-posologie').value = ''; document.getElementById('ord-duree').value = ''; renderOrdDrugs(); } function renderOrdDrugs() { var list = document.getElementById('ord-drugs-list'); if (!list) return; if (!_ordDrugs.length) { list.innerHTML = '
Aucun médicament ajouté
'; return; } list.innerHTML = _ordDrugs.map(function(d, i) { return '
' + '
' + d.nom + '
' + '
' + (d.posologie||'') + (d.duree ? ' · ' + d.duree : '') + '
' + '
'; }).join(''); } function removeDrug(i) { _ordDrugs.splice(i, 1); renderOrdDrugs(); } async function saveOrdonnance() { var sel = document.getElementById('ord-patient-sel'); var patId = sel ? sel.value : (currentPatientId || ''); if (!patId) { toast('⚠️ Sélectionnez un patient'); return; } if (!_ordDrugs.length) { toast('⚠️ Ajoutez au moins un médicament'); return; } var p = PATIENTS.find(function(x){ return x.id === patId; }); var note = (document.getElementById('ord-note')||{value:''}).value.trim(); var ord = { id: crypto.randomUUID(), patient_id: patId, medecin: _doctorName, date: new Date().toISOString().split('T')[0], medicaments: JSON.stringify(_ordDrugs), note: note, actif: true }; await saveOrdonnanceDB(ord); ORDONNANCES.push(ord); toast('✅ Ordonnance enregistrée — synchronisée'); _ordDrugs = []; renderOrdDrugs(); if (document.getElementById('ord-note')) document.getElementById('ord-note').value = ''; } function sendOrdWhatsApp() { var sel = document.getElementById('ord-patient-sel'); var patId = sel ? sel.value : (currentPatientId || ''); var p = PATIENTS.find(function(x){ return x.id === patId; }); if (!p) { toast('⚠️ Sélectionnez un patient'); return; } if (!_ordDrugs.length) { toast('⚠️ Ajoutez des médicaments d\'abord'); return; } var phone = (p.phone||'').replace(/[^0-9+]/g,''); var waPhone = phone.startsWith('+') ? phone.replace('+','') : '221'+phone.replace(/^0/,''); var _nl = '%0A'; var meds = _ordDrugs.map(function(d){ return '- '+d.nom+(d.posologie?' : '+d.posologie:'')+(d.duree?' ('+d.duree+')':''); }).join(_nl); var waText = 'Ordonnance+MedicalFlow'+_nl+ 'Patient:+'+encodeURIComponent(p.full_name)+_nl+ 'Date:+'+new Date().toISOString().split('T')[0]+_nl+ 'Docteur:+'+encodeURIComponent(_doctorName)+_nl+_nl+ 'Prescriptions:'+_nl+encodeURIComponent(meds)+_nl+_nl+ 'Cabinet+Dr.+Diallo,+Dakar'; if (waPhone.length > 6) window.open('https://wa.me/'+waPhone+'?text='+waText, '_blank'); else toast('⚠️ Numéro patient introuvable'); } // ══════════════════════════════════════════ // PRESCRIRE — SOUS-ONGLETS // ══════════════════════════════════════════ function switchPrescrire(tab) { var ord = document.getElementById('sub-panel-ord'); var exam = document.getElementById('sub-panel-exam'); var btnOrd = document.getElementById('sub-ord'); var btnExam = document.getElementById('sub-exam'); if (!ord || !exam) return; if (tab === 'ord') { ord.style.display = 'block'; exam.style.display = 'none'; if (btnOrd) { btnOrd.style.background='var(--al)'; btnOrd.style.color='var(--accent)'; btnOrd.style.borderColor='var(--accent)30'; } if (btnExam) { btnExam.style.background='var(--s2)'; btnExam.style.color='var(--text2)'; btnExam.style.borderColor='var(--border)'; } } else { ord.style.display = 'none'; exam.style.display = 'block'; if (btnExam) { btnExam.style.background='var(--al)'; btnExam.style.color='var(--accent)'; btnExam.style.borderColor='var(--accent)30'; } if (btnOrd) { btnOrd.style.background='var(--s2)'; btnOrd.style.color='var(--text2)'; btnOrd.style.borderColor='var(--border)'; } mRenderExamList(); } } function mRenderExamList() { var list = document.getElementById('m-ep-list'); if (!list || !currentPatientId) return; var dos = DOSSIERS[currentPatientId]; var exams = dos && Array.isArray(dos.examens_prescrits) ? dos.examens_prescrits : []; if (!exams.length) { list.innerHTML = ''; return; } var urgColor = { tres_urgent:'var(--danger)', urgent:'var(--warn)', normal:'var(--accent)' }; var urgLabel = { tres_urgent:'🔴 Très urgent', urgent:'🟠 Urgent', normal:'🟢 Normal' }; list.innerHTML = '
📋 Prescrits (' + exams.length + ')
' + exams.map(function(e, i) { return '
' + '
' + '
' + (e.type||'') + '
' + '' + (urgLabel[e.urgence]||'Normal') + '' + '
' + '' + '
' + (e.statut==='fait' ? '✅ Résultat reçu' : '⏳ En attente') + '
' + '
'; }).join('') + '
'; } async function mSaveExamenPrescrit() { if (!currentPatientId) { toast('⚠️ Aucun patient sélectionné'); return; } var type = (document.getElementById('m-ep-type')||{value:''}).value; if (!type) { toast('⚠️ Sélectionnez un type'); return; } var dos = DOSSIERS[currentPatientId]; if (!Array.isArray(dos.examens_prescrits)) dos.examens_prescrits = []; dos.examens_prescrits.unshift({ id: crypto.randomUUID(), date: new Date().toISOString().split('T')[0], medecin: _doctorName, type: type, urgence: (document.getElementById('m-ep-urgence')||{value:'normal'}).value, instructions: (document.getElementById('m-ep-instructions')||{value:''}).value.trim(), statut: 'en_attente' }); await saveDossierSection(currentPatientId, 'examens_prescrits', dos.examens_prescrits); if (document.getElementById('m-ep-instructions')) document.getElementById('m-ep-instructions').value = ''; mRenderExamList(); toast('✅ Examen prescrit: ' + type + ' — synchronisé'); } function mSendExamensWA() { if (!currentPatientId) { toast('⚠️ Aucun patient'); return; } var p = PATIENTS.find(function(x){ return x.id === currentPatientId; }); if (!p) return; var dos = DOSSIERS[currentPatientId]; var examens = (dos.examens_prescrits || []).filter(function(e){ return e.statut !== 'fait'; }); if (!examens.length) { toast('⚠️ Aucun examen en attente'); return; } var phone = (p.phone||'').replace(/[^0-9+]/g,''); var waPhone = phone.startsWith('+') ? phone.replace('+','') : '221'+phone.replace(/^0/,''); var list = examens.map(function(e){ return '- '+e.type+(e.urgence==='tres_urgent'?' (URGENT)':e.urgence==='urgent'?' (Urgent)':'')+(e.instructions?' : '+e.instructions:''); }).join('%0A'); var waText = 'Ordres+examens+MedicalFlow%0APatient:+'+encodeURIComponent(p.full_name)+'%0A%0A'+encodeURIComponent(list)+'%0A%0ACabinet+Dr.+Diallo'; if (waPhone.length > 6) window.open('https://wa.me/'+waPhone+'?text='+waText, '_blank'); else toast('⚠️ Numéro introuvable'); } // ══════════════════════════════════════════ // TÉLÉCONSULTATION MOBILE DOCTEUR // ══════════════════════════════════════════ async function loadDocTeleconsult() { var list = document.getElementById('teleconsult-list'); if (!list) return; list.innerHTML = '
Chargement...
'; var today = new Date().toISOString().split('T')[0]; // Charger les RDV du jour depuis Supabase try { var { data } = await db.from('bookings').select('*') .eq('cabinet_id', CABINET_ID).eq('date', today).order('time'); if (data) BOOKINGS = data; } catch(e) { console.warn(e); } if (!BOOKINGS.length) { list.innerHTML = '
🗓
Aucun RDV aujourd'hui
'; return; } list.innerHTML = BOOKINGS.map(function(r) { var p = PATIENTS.find(function(x){ return x.id === r.patient_id; }); var nom = p ? p.full_name : (r.patient_name || 'Patient'); var phone = p ? (p.phone || '') : ''; var room = r.teleconsult_room || ('medicalflow-' + today.replace(/-/g,'') + '-' + (r.id||'').replace(/[^a-zA-Z0-9]/g,'').substring(0,8)); var enCours = r.status === 'in_progress'; return '
' + '
' + '
' + (p ? p.emoji||'👤' : '👤') + '
' + '
' + '
' + nom + '
' + '
' + (r.time||'').substring(0,5) + ' · ' + (r.reason||'Consultation') + '
' + '
' + '' + (enCours ? '● En cours' : '⏳ Planifié') + '' + '
' + // Boutons action '
' + '' + '' + '
' + // Salle urgente — ligne séparée si en cours (enCours ? '
● Salle active — patient peut rejoindre
' : '') + '
'; }).join('') + // Salle ad-hoc urgente '
' + '
🔗 Salle urgente (sans RDV)
' + '
Consultation immédiate — lien envoyé par WhatsApp
' + '
' + '' + '' + '
'; } async function docDemarrerVideo(rdvId, patientId, room) { var url = 'https://meet.jit.si/' + room; window.open(url, '_blank'); // Marquer en cours + sauvegarder room try { await db.from('bookings').update({ status:'in_progress', teleconsult_room: room }).eq('id', rdvId); var bk = BOOKINGS.find(function(x){ return x.id===rdvId; }); if (bk) { bk.status = 'in_progress'; bk.teleconsult_room = room; } loadDocTeleconsult(); toast('📹 Salle ouverte — patient notifié en temps réel'); } catch(e) { toast('⚠️ Erreur: ' + e.message); } } function docEnvoyerLienWA(nom, phone, room) { if (!phone) { toast('⚠️ Numéro introuvable'); return; } var waPhone = phone.replace(/[^0-9+]/g,''); waPhone = waPhone.startsWith('+') ? waPhone.replace('+','') : '221'+waPhone.replace(/^0/,''); var prenom = nom.split(' ')[0]; var url = 'https://meet.jit.si/' + room; var waText = encodeURIComponent('Bonjour ' + prenom + ' ! Votre teleconsultation MedicalFlow est prete. Rejoignez ici : ' + url + ' Tarif : 15 000 F CFA (apres consultation). Cabinet Dr. Diallo'); window.open('https://wa.me/'+waPhone+'?text='+waText, '_blank'); toast('💬 Lien envoyé à ' + prenom); } function docSalleAdhoc() { var phone = (document.getElementById('doc-adhoc-phone')||{value:''}).value.trim(); var room = 'medicalflow-urgent-' + Date.now().toString(36); var url = 'https://meet.jit.si/' + room; window.open(url, '_blank'); if (phone) docEnvoyerLienWA('Patient', phone, room); toast('📹 Salle urgente créée'); } // ══════════════════════════════════════════ // GESTION DES ACCÈS // ══════════════════════════════════════════ var PERSONNEL_LIST = []; async function loadAccesList() { var list = document.getElementById('acces-list'); if (!list) return; list.innerHTML = '
Chargement...
'; try { var { data, error } = await db.from('personnel') .select('*') .eq('cabinet_id', CABINET_ID) .in('role_systeme', ['secretaire','admin','infirmier','laborantin','accueil','hygiene','securite','personnel']) .order('prenom'); if (error) throw error; PERSONNEL_LIST = data || []; renderAccesList(); } catch(e) { console.warn('loadAccesList:', e); list.innerHTML = '
⚠️
Erreur de chargement
'; } } function renderAccesList() { var list = document.getElementById('acces-list'); if (!list) return; var roleLabels = { secretaire:'💼 Secrétaire', admin:'🏢 Admin', infirmier:'💉 Infirmier', laborantin:'🧪 Laborantin', accueil:'🛎 Réceptionniste', hygiene:'🧹 Hygiène', securite:'🛡️ Sécurité', personnel:'👥 Personnel' }; var roleColors = { secretaire:'var(--purple)', admin:'var(--warn)', infirmier:'var(--accent2)', laborantin:'var(--accent2)', accueil:'var(--accent2)', hygiene:'var(--text2)', securite:'var(--text2)', personnel:'var(--text2)' }; // Carte docteur (son propre code) var docCard = '
' + '
' + '
🩺
' + '
' + _doctorName + '
🩺 Docteur — Votre compte
' + '
' + '
' + '
Votre code acces
' + '
' + (SYSTEM_CODES.docteur||'DOC-2025') + '
' + '
' + '' + '' + '
'; // Cartes codes Secrétaire et Admin (system_codes) var roleCards = [ { role:'secretaire', icon:'💼', label:'Secrétaire', prefix:'SEC', color:'var(--purple)' }, { role:'admin', icon:'🏢', label:'Admin', prefix:'ADM', color:'var(--warn)' }, { role:'infirmier', icon:'💉', label:'Infirmier', prefix:'INF', color:'var(--accent2)' }, { role:'laborantin', icon:'🧪', label:'Laborantin', prefix:'LAB', color:'var(--accent2)' }, { role:'accueil', icon:'🛎', label:'Réceptionniste', prefix:'ACC', color:'var(--accent2)' }, { role:'hygiene', icon:'🧹', label:'Hygiène', prefix:'HYG', color:'var(--text2)' }, { role:'securite', icon:'🛡️', label:'Sécurité', prefix:'SEC2', color:'var(--text2)' }, { role:'personnel', icon:'👥', label:'Personnel', prefix:'PER', color:'var(--text2)' }, ].map(function(r) { var code = SYSTEM_CODES[r.role]; return '
' + '
' + '
' + r.icon + '
' + '
' + r.label + '
' + '
Code partagé par tous les ' + r.label.toLowerCase() + 's
' + '
' + '
' + '
Code d\'accès
' + '
' + (code || '— Non créé —') + '
' + '
' + '' + '
'; }).join(''); if (!PERSONNEL_LIST.length) { list.innerHTML = docCard + roleCards + '
👥
Aucun personnel secrétaire/admin enregistré
'; return; } list.innerHTML = docCard + roleCards + PERSONNEL_LIST.map(function(p) { var isActif = p.statut === 'actif'; return '
' + // Header '
' + '
' + (p.emoji||'👤') + '
' + '
' + '
' + (p.prenom||'') + ' ' + (p.nom||'') + '
' + '
' + (roleLabels[p.role_systeme]||p.role_systeme) + '
' + '
' + '
' + '
' + (isActif ? '✓ Actif' : '✗ Bloqué') + '
' + '
' + '
' + // Code accès '
' + '
Code acces
' + '
' + (p.code_acces||'—') + '
' + '
' + // Actions '
' + '' + (isActif ? '' : '' ) + '
' + '
'; }).join(''); } function changerMonCodeDoc() { var chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; var part = ''; for (var i=0; i<4; i++) part += chars[Math.floor(Math.random()*chars.length)]; var suggestion = 'DOC-' + part; openModal( '' + '
🔄 Changer mon code docteur
' + '
Notez le nouveau code — il sera demandé à la prochaine connexion
' + '
' + '' + '' + '
' + '' + '' ); setTimeout(function() { var btnGen = document.getElementById('btn-gen-doc'); var btnSave = document.getElementById('btn-save-doc'); if (btnGen) btnGen.onclick = function() { var c = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; var p = ''; for (var i=0; i<4; i++) p += c[Math.floor(Math.random()*c.length)]; var el = document.getElementById('new-doc-code'); if (el) el.value = 'DOC-' + p; }; if (btnSave) btnSave.onclick = async function() { var code = (document.getElementById('new-doc-code')||{value:''}).value.trim().toUpperCase(); if (!code || code.length < 4) { toast('⚠️ Code trop court'); return; } try { await db.from('system_codes').upsert({ cabinet_id: CABINET_ID, role: 'docteur', code: code }, { onConflict: 'cabinet_id,role' }); closeModal(); var el = document.getElementById('doc-own-code'); if (el) el.textContent = code; // Alerte importante openModal( '
' + '
⚠️
' + '
Nouveau code actif
' + '
Ce code sera demandé à votre prochaine connexion
' + '
' + '
' + code + '
' + '
' + '' + '' + '
' ); setTimeout(function() { var btn = document.getElementById('btn-copy-new-doc'); if (btn) btn.onclick = function(){ navigator.clipboard && navigator.clipboard.writeText(code); toast('Code copié'); }; }, 50); } catch(e) { toast('❌ Erreur: ' + e.message); } }; }, 50); } function docDeconnexion() { if (!confirm('Se déconnecter ?')) return; localStorage.removeItem('df_doctor_session'); _doctorName = null; _loggedIn = false; document.getElementById('app').style.display = 'none'; document.getElementById('login-screen').style.display = 'flex'; toast('👋 Déconnecté'); } function gererCodeSysteme(role, prefix, label) { var chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; var part = ''; for (var i=0; i<4; i++) part += chars[Math.floor(Math.random()*chars.length)]; var suggestion = prefix + '-' + part; var existingCode = SYSTEM_CODES[role]; openModal( '' + '
' + (existingCode ? '🔄 Changer' : '✨ Créer') + ' le code ' + label + '
' + '
Code partagé par tous les ' + label.toLowerCase() + 's du cabinet
' + (existingCode ? '
Code actuel
' + existingCode + '
' : '') + '
' + '' + '' + '
' + '' + '' ); setTimeout(function() { var btnGen = document.getElementById('btn-gen-sys'); var btnSave = document.getElementById('btn-save-sys'); if (btnGen) btnGen.onclick = function() { var c = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; var p = ''; for (var i=0; i<4; i++) p += c[Math.floor(Math.random()*c.length)]; var el = document.getElementById('new-sys-code'); if (el) el.value = prefix + '-' + p; }; if (btnSave) btnSave.onclick = async function() { var code = (document.getElementById('new-sys-code')||{value:''}).value.trim().toUpperCase(); if (!code || code.length < 4) { toast('⚠️ Code trop court'); return; } try { var { error } = await db.from('system_codes') .upsert({ cabinet_id: CABINET_ID, role: role, code: code }, { onConflict: 'cabinet_id,role' }); if (error) throw error; SYSTEM_CODES[role] = code; closeModal(); toast('✅ Code ' + label + ' ' + (existingCode ? 'changé' : 'créé') + ' : ' + code); renderAccesList(); } catch(e) { SYSTEM_CODES[role] = code; closeModal(); toast('✅ Code ' + label + ' ' + (existingCode ? 'changé' : 'créé') + ' : ' + code); renderAccesList(); } }; }, 50); } function generateNewCode(prefix) { var chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; var part = ''; for (var i = 0; i < 4; i++) part += chars[Math.floor(Math.random() * chars.length)]; return prefix + '-' + part; } function changerCode(personnelId, prenom) { var p = PERSONNEL_LIST.find(function(x){ return x.id === personnelId; }); if (!p) return; var prefix = p.role_systeme === 'secretaire' ? 'SEC' : 'ADM'; var suggestion = generateNewCode(prefix); openModal( '
🔄 Changer le code
' + '
' + prenom + ' — code actuel: ' + (p.code_acces||'—') + '
' + '
' + '' + '' + '
' + '' + '' ); setTimeout(function() { var btnGen = document.getElementById('btn-gen-code'); var btnConf = document.getElementById('btn-confirm-code'); if (btnGen) btnGen.onclick = function(){ genererCode(prefix); }; if (btnConf) btnConf.onclick = function(){ confirmerChangementCode(personnelId, prenom); }; }, 50); } function genererCode(prefix) { var input = document.getElementById('new-code-input'); if (input) input.value = generateNewCode(prefix); } async function confirmerChangementCode(personnelId, prenom) { var newCode = (document.getElementById('new-code-input')||{value:''}).value.trim().toUpperCase(); if (!newCode || newCode.length < 4) { toast('⚠️ Code trop court'); return; } try { var { error } = await db.from('personnel').update({ code_acces: newCode }).eq('id', personnelId); if (error) throw error; // Mettre à jour local var p = PERSONNEL_LIST.find(function(x){ return x.id === personnelId; }); if (p) p.code_acces = newCode; closeModal(); renderAccesList(); toast('✅ Code de ' + prenom + ' changé: ' + newCode); // Envoyer WhatsApp si téléphone disponible setTimeout(function() { var pers = PERSONNEL_LIST.find(function(x){ return x.id === personnelId; }); if (pers && pers.telephone) { var phone = pers.telephone.replace(/[^0-9+]/g,''); var waPhone = phone.startsWith('+') ? phone.replace('+','') : '221'+phone.replace(/^0/,''); var waText = 'Bonjour+' + encodeURIComponent(prenom) + '+!%0AVotre+nouveau+code+MedicalFlow+:%0A%0A' + encodeURIComponent(newCode) + '%0A%0ACabinet+Dr.+Diallo'; if (waPhone.length > 6) window.open('https://wa.me/'+waPhone+'?text='+waText, '_blank'); } }, 500); } catch(e) { toast('❌ Erreur: ' + (e.message||e)); } } async function bloquerAcces(personnelId, prenom) { if (!confirm('Bloquer acces de ' + prenom + ' ?')) return; try { var { error } = await db.from('personnel').update({ statut: 'inactif' }).eq('id', personnelId); if (error) throw error; var p = PERSONNEL_LIST.find(function(x){ return x.id === personnelId; }); if (p) p.statut = 'inactif'; renderAccesList(); toast('🔒 Accès de ' + prenom + ' bloqué'); } catch(e) { toast('❌ Erreur: ' + (e.message||e)); } } async function debloquerAcces(personnelId, prenom) { try { var { error } = await db.from('personnel').update({ statut: 'actif' }).eq('id', personnelId); if (error) throw error; var p = PERSONNEL_LIST.find(function(x){ return x.id === personnelId; }); if (p) p.statut = 'actif'; renderAccesList(); toast('🔓 Accès de ' + prenom + ' rétabli'); } catch(e) { toast('❌ Erreur: ' + (e.message||e)); } } // ══════════════════════════════════════════ // MODAL // ══════════════════════════════════════════ var _modalPollInterval = null; // ── VOICE TO NOTE (Claude AI) ───────────────────────────────── var _voiceRecog = null; var _voiceActive = null; var _voiceRawText = {}; var _voiceContexts = { 'm-cons-symptomes': 'Symptômes cliniques dentaires dictés par le médecin', 'm-cons-prescription': 'Prescription médicale dentaire (médicaments, posologie)', 'm-ex-resultat': 'Résultat d’examen dentaire (radio, bilan, biopsie...)', 'm-ep-instructions': 'Instructions pré-examen pour le patient', 'm-plan-rx': 'Plan de traitement dentaire - actes et soins planifiés', 'm-plan-reco': 'Recommandations post-soin pour le patient', 'm-suivi-resume': 'Note de suivi clinique dentaire' }; async function _claudeFormatNote(rawText, fieldId) { var context = _voiceContexts[fieldId] || 'Note médicale dentaire'; var prompt = 'Tu es assistant médical pour MedicalFlow, un logiciel de cabinet dentaire à Dakar, Sénégal. ' + 'Le médecin a dicté oralement : "' + rawText.trim() + '". ' + 'Contexte : ' + context + '. ' + 'Reformate cette dictee en note clinique propre en français : corrige les erreurs de transcription vocale, ' + 'ajoute la ponctuation, structure le texte si nécessaire. ' + 'Sois concis et professionnel. Réponds uniquement avec la note reformulée, sans introduction ni explication.'; try { var res = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: 500, messages: [{ role: 'user', content: prompt }] }) }); var data = await res.json(); return (data.content && data.content[0] && data.content[0].text) || rawText; } catch(e) { console.warn('Claude API error:', e); return rawText; } } function voiceInput(targetId) { var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; if (!SpeechRecognition) { toast('🎙️ Non supporté sur ce navigateur'); return; } var btn = document.getElementById('voice-btn-' + targetId); var field = document.getElementById(targetId); if (_voiceActive === targetId && _voiceRecog) { _voiceRecog.stop(); return; } if (_voiceRecog) { try { _voiceRecog.stop(); } catch(e){} } _voiceActive = targetId; _voiceRawText[targetId] = ''; _voiceRecog = new SpeechRecognition(); _voiceRecog.lang = 'fr-FR'; _voiceRecog.continuous = false; _voiceRecog.interimResults = false; if (btn) { btn.style.background = 'var(--danger)'; btn.style.animation = 'voicePulse 1s infinite'; btn.textContent = '⏹️'; btn.title = 'Arrêter'; } if (field) field.placeholder = '🎙️ Écoute...'; _voiceRecog.onresult = function(e) { for (var i = e.resultIndex; i < e.results.length; i++) { if (e.results[i].isFinal) _voiceRawText[targetId] += e.results[i][0].transcript + ' '; } }; _voiceRecog.onend = function() { _voiceActive = null; _voiceRecog = null; var raw = (_voiceRawText[targetId] || '').trim(); if (btn) { btn.style.background = 'var(--s2)'; btn.style.animation = ''; } if (field) field.placeholder = field.getAttribute('data-placeholder') || ''; if (!raw) { if (btn) btn.textContent = '🎙️'; return; } // Show raw text immediately if (field) field.value = raw; // Then send to Claude for reformatting if (btn) { btn.textContent = '⏳'; btn.title = 'IA en cours...'; btn.style.background = 'var(--accent2)'; btn.style.animation = 'voicePulse .8s infinite'; } _claudeFormatNote(raw, targetId).then(function(formatted) { if (field) field.value = formatted; if (btn) { btn.textContent = '🎙️'; btn.title = 'Dicter'; btn.style.background = 'var(--s2)'; btn.style.animation = ''; } toast('✅ Note reformulée par IA'); }).catch(function() { if (btn) { btn.textContent = '🎙️'; btn.title = 'Dicter'; btn.style.background = 'var(--s2)'; btn.style.animation = ''; } }); }; _voiceRecog.onerror = function(e) { toast('🎙️ Erreur: ' + e.error); if (btn) { btn.style.background = 'var(--s2)'; btn.style.animation = ''; btn.textContent = '🎙️'; btn.title = 'Dicter'; } if (field) field.placeholder = field.getAttribute('data-placeholder') || ''; _voiceActive = null; _voiceRecog = null; }; _voiceRecog.start(); } function _voiceBtn(targetId) { return ''; } function _voiceLabel(label, targetId, placeholder) { return '' + '