La Cooptation par
les Pairs
L'excellence médicale se bâtit par la reconnaissance mutuelle. Participez à l'élaboration d'un réseau de
soin d'élite en recommandant vos confrères les plus talentueux.
Tableau
d'Excellence
Découvrez les praticiens les plus plébiscités par la communauté AksantiDoc.
// API Config (Pure JS)
const API_URL = 'http://localhost:3000/api';
// Session Mock (Pure JS - Replace with real Auth if needed)
async function getSession() {
// Pour la démo : On simule une session avec un ID fixe ou stocké en local
let userId = localStorage.getItem('user_id') || 'demo-doctor-uuid';
return { user: { id: userId, email: 'docteur@exemple.com' } };
}
// Auth Guard Simplified
async function checkSession() {
const session = await getSession();
if (!session) {
window.location.href = '/login.html';
return;
}
const profile = document.getElementById('user-profile');
if (profile) {
profile.classList.remove('hidden');
profile.classList.add('flex');
}
const mobileProfile = document.getElementById('mobile-user-profile');
if (mobileProfile) {
mobileProfile.classList.remove('hidden');
mobileProfile.classList.add('flex');
}
const heroActions = document.getElementById('hero-user-actions');
if (heroActions) {
heroActions.classList.remove('hidden');
heroActions.classList.add('flex');
}
}
// Fees Management (Pure JS)
let currentFeeEditingId = null;
let myFeesData = [];
let hospitalData = [];
window.openFeesModal = async () => {
const modal = document.getElementById('fees-modal');
if (modal) {
modal.classList.remove('hidden');
modal.classList.add('flex');
}
await loadHospitals();
await loadMyFees();
};
window.closeFeesModal = () => {
const modal = document.getElementById('fees-modal');
if (modal) modal.classList.add('hidden');
};
async function loadHospitals() {
const res = await fetch(`${API_URL}/etablissements`);
hospitalData = await res.json();
const select = document.getElementById('fee_hospital_id');
if (select) {
select.innerHTML = hospitalData.map(h => `${h.nom} (${h.ville}) `).join('');
}
}
async function loadMyFees() {
// Route à ajouter au serveur si nécessaire, ou filtrage côté client
const res = await fetch(`${API_URL}/tarifs`); // Supposant que cette route existe
myFeesData = await res.json();
// ... rendu de la liste des tarifs ...
}
// Load Leaderboard (Pure JS)
async function loadLeaderboard() {
try {
const response = await fetch(`${API_URL}/leaderboard`);
const mergedData = await response.json();
renderLeaderboard(mergedData);
} catch (err) {
console.error('Leaderboard error:', err);
}
}
function renderLeaderboard(data) {
const body = document.getElementById('leaderboard-body');
body.innerHTML = data.length > 0 ? '' : '
Aucune donnée disponible.
';
const lang = localStorage.getItem('language') || 'fr';
const labels = {
photo: window.translations[lang]?.cooptation?.table_photo || "Photo",
name: window.translations[lang]?.cooptation?.table_name || "Nom",
specialty: window.translations[lang]?.cooptation?.table_specialty || "Spécialité",
graduation: window.translations[lang]?.cooptation?.table_graduation || "Promotion",
count: window.translations[lang]?.cooptation?.table_count || "Cooptations"
};
data.forEach((item, index) => {
const row = document.createElement('tr');
row.className = 'block md:table-row border-b border-gray-100 hover:bg-gray-50 transition-colors mb-4 md:mb-0
bg-white md:bg-transparent rounded-xl md:rounded-none shadow-sm md:shadow-none p-4 md:p-0 border md:border-b';
// DIAGNOSTIC LOG
if (index < 3) console.log(`Rendering Row ${index}: ${item.full_name}, Promo: ${item.graduation_year}`); const
initials=item.initials || 'MD' ; const currentYear=2026; let levelBadge='' ; if (item.graduation_year) { const
exp=currentYear - item.graduation_year; if (exp>= 12) {
levelBadge = 'Senior ';
} else if (exp >= 5) {
levelBadge = 'Medior ';
} else {
levelBadge = 'Junior ';
}
}
row.innerHTML = `
${labels.photo}
${item.avatar_url ? `
` : `
${initials} `}
${labels.name}
${item.full_name || '-'}
${item.hasHigherCooptation ?
` ` :
''
}
${labels.specialty}
${item.specialty || '-'}
${labels.graduation}
${item.graduation_year ? item.graduation_year : '-'}
${levelBadge}
${labels.count}
${item.nombre_de_cooptations}
S:${item.breakdown?.senior || 0}
M:${item.breakdown?.medior || 0}
J:${item.breakdown?.junior || 0}
`;
body.appendChild(row);
});
}
// Load Consultants for Select
let allConsultants = [];
async function loadConsultants() {
try {
const res = await fetch(`${API_URL}/praticiens`);
const data = await res.json();
const session = await getSession();
allConsultants = data.filter(c => c.id !== session?.user?.id);
renderConsultants(allConsultants);
} catch (err) {
console.error('Load consultants error:', err);
}
}
function renderConsultants(list) {
const select = document.getElementById('nominee_id');
const placeholder = select.querySelector('option[disabled]');
select.innerHTML = '';
if (placeholder) select.appendChild(placeholder);
list.forEach(c => {
const opt = document.createElement('option');
opt.value = c.id;
const year = c.graduation_year ? ` - ${c.graduation_year}` : '';
opt.textContent = `${c.full_name} (${c.specialty || 'Généraliste'}${year})`;
select.appendChild(opt);
});
}
window.filterConsultants = (val) => {
const term = val.toLowerCase();
const filtered = allConsultants.filter(c =>
c.full_name.toLowerCase().includes(term) ||
(c.specialty && c.specialty.toLowerCase().includes(term))
);
renderConsultants(filtered);
};
// Wizard Navigation
window.nextStep = () => {
const nomineeId = document.getElementById('nominee_id').value;
if (!nomineeId) {
alert('Veuillez sélectionner un praticien.');
return;
}
document.getElementById('form-step-1').classList.add('hidden');
document.getElementById('form-step-2').classList.remove('hidden');
document.getElementById('step-2-indicator').classList.remove('bg-gray-200', 'text-gray-500');
document.getElementById('step-2-indicator').classList.add('bg-primary', 'text-white');
};
window.prevStep = () => {
document.getElementById('form-step-2').classList.add('hidden');
document.getElementById('form-step-1').classList.remove('hidden');
document.getElementById('step-2-indicator').classList.add('bg-gray-200', 'text-gray-500');
document.getElementById('step-2-indicator').classList.remove('bg-primary', 'text-white');
};
// Character counter
const textarea = document.getElementById('justification');
const charCount = document.getElementById('char-count');
textarea.addEventListener('input', () => {
const len = textarea.value.length;
charCount.textContent = `${len} / 100 caractères min.`;
charCount.className = len >= 100 ? 'text-xs text-green-500' : 'text-xs text-red-400';
});
// Form Submission
document.getElementById('cooptation-form').addEventListener('submit', async (e) => {
e.preventDefault();
const feedback = document.getElementById('form-feedback');
const submitBtn = document.getElementById('submit-btn');
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations?.[lang]?.cooptation || window.translations?.['fr']?.cooptation;
submitBtn.disabled = true;
submitBtn.innerHTML = ' ';
try {
const session = await getSession();
if (!session?.user) {
throw new Error('User not logged in');
}
const nomineeId = document.getElementById('nominee_id').value;
if (nomineeId === session.user.id) {
throw new Error("Vous ne pouvez pas vous coopter vous-même.");
}
const res = await fetch(`${API_URL}/cooptations`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
referrer_id: session.user.id,
nominee_id: nomineeId,
justification: document.getElementById('justification').value
})
});
if (!res.ok) {
const errorData = await res.json();
if (errorData.error && errorData.error.includes('Cooptation circulaire')) {
throw new Error(trans.error_circular);
}
throw new Error(errorData.error || 'Erreur lors de la soumission');
}
feedback.textContent = trans.success_msg;
feedback.className = 'block p-4 rounded-lg text-center font-semibold bg-green-100 text-green-700 mt-4';
document.getElementById('cooptation-form').reset();
charCount.textContent = '0 / 100 caractères min.';
loadLeaderboard();
} catch (err) {
feedback.textContent = err.message;
feedback.className = 'block p-4 rounded-lg text-center font-semibold bg-red-100 text-red-700 mt-4';
} finally {
submitBtn.disabled = false;
submitBtn.innerHTML = trans.btn_submit;
feedback.classList.remove('hidden');
}
});
// Init
window.addEventListener('translationsLoaded', applyTranslations);
// Fallback if script already loaded
if (window.translations) applyTranslations();
loadLeaderboard();
loadConsultants();
checkSession();
// Profile Completion Submission
document.getElementById('profile-completion-form').addEventListener('submit', async (e) => {
e.preventDefault();
const btn = document.getElementById('comp-submit-btn');
btn.disabled = true;
btn.innerHTML = ' Initialisation...';
try {
const { data: { user } } = await supabase.auth.getUser();
const fullName = document.getElementById('comp_full_name').value;
const rpps = document.getElementById('comp_rpps').value;
const specialty = document.getElementById('comp_specialty').value;
const graduation = document.getElementById('comp_graduation').value;
const { error } = await supabase
.from('consultants')
.insert([{
id: user.id,
full_name: fullName,
rpps_number: rpps,
// Profile Completion (Pure JS)
document.getElementById('profile-completion-form')?.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = e.target.querySelector('button');
btn.disabled = true;
const formData = new FormData(e.target);
const profileData = Object.fromEntries(formData.entries());
try {
const session = await getSession();
const res = await fetch(`${API_URL}/praticiens`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...profileData, id: session.user.id })
});
if (res.ok) {
document.getElementById('profile-completion-section').classList.add('hidden');
document.getElementById('cooptation-form').closest('div').classList.remove('hidden');
loadLeaderboard();
}
} catch (err) {
alert("Erreur : " + err.message);
} finally {
btn.disabled = false;
}
});
// Curriculum Modal Logic
let currentEditingId = null;
let myCurriculumData = [];
window.openCurriculumModal = async () => {
try {
currentEditingId = null;
const form = document.getElementById('curriculum-form');
if (form) form.reset();
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations?.[lang]?.cooptation?.curriculum || {};
const submitBtn = document.getElementById('curr-submit-btn');
if (submitBtn) {
submitBtn.innerHTML = trans.btn_save || 'Enregistrer';
}
const modal = document.getElementById('curriculum-modal');
if (modal) {
modal.classList.remove('hidden');
modal.classList.add('flex');
document.body.style.overflow = 'hidden';
}
await loadMyCurriculum();
} catch (err) {
console.error("Error opening Curriculum Modal:", err);
}
};
window.loadMyCurriculum = async () => {
const listContainer = document.getElementById('my-curriculum-list');
if (!listContainer) return;
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations[lang]?.cooptation?.curriculum || {};
listContainer.innerHTML = ' Chargement...
';
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
const { data, error } = await supabase
.from('consultant_curriculum')
.select('*')
.eq('consultant_id', user.id)
.order('end_year', { ascending: false });
if (error) throw error;
myCurriculumData = data || [];
// Update badge
const badge = document.getElementById('curr-count-badge');
if (badge) badge.textContent = myCurriculumData.length;
if (myCurriculumData.length === 0) {
listContainer.innerHTML = `
Aucune expérience enregistrée.
`;
return;
}
const categoryIcons = {
university: 'fa-university',
publication: 'fa-book',
award: 'fa-award',
training: 'fa-briefcase'
};
listContainer.innerHTML = myCurriculumData.map(item => `
${item.title}
${item.organization} • ${item.end_year}
`).join('');
} catch (err) {
listContainer.innerHTML = `Erreur: ${err.message}
`;
}
};
window.editCurriculumEntry = (id) => {
const entry = myCurriculumData.find(e => e.id === id);
if (!entry) return;
currentEditingId = id;
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations[lang]?.cooptation?.curriculum || {};
// Populate form
document.querySelector(`input[name="category_type"][value="${entry.category_type}"]`).checked = true;
document.getElementById('curr_title').value = entry.title || '';
document.getElementById('curr_organization').value = entry.organization || '';
document.getElementById('curr_city').value = entry.city || '';
document.getElementById('curr_country').value = entry.location_country || '';
document.getElementById('curr_start_year').value = entry.start_year || '';
document.getElementById('curr_end_year').value = entry.end_year || '';
document.getElementById('curr_description').value = entry.description || '';
document.getElementById('curr_url').value = entry.url_reference || '';
// Update button text
document.getElementById('curr-submit-btn').innerHTML = trans.btn_update || 'Mettre à jour';
// Scroll form into view if needed (it's inside the modal, so just scroll the container)
document.querySelector('.p-6.overflow-y-auto').scrollTo({ top: 0, behavior: 'smooth' });
};
window.deleteCurriculumEntry = async (id) => {
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations[lang]?.cooptation?.curriculum || {};
if (!confirm(trans.confirm_delete || "Voulez-vous supprimer cette entrée ?")) return;
try {
const { error } = await supabase
.from('consultant_curriculum')
.delete()
.eq('id', id);
if (error) throw error;
// Show floating success message (we can use the feedback div if it's visible)
const feedback = document.getElementById('curriculum-feedback');
feedback.textContent = trans.feedback_delete_success || "Supprimé !";
feedback.className = "p-4 rounded-xl text-sm font-semibold text-center bg-green-100 text-green-700";
feedback.classList.remove('hidden');
setTimeout(() => feedback.classList.add('hidden'), 3000);
await loadMyCurriculum();
} catch (err) {
alert("Erreur: " + err.message);
}
};
window.closeCurriculumModal = () => {
document.getElementById('curriculum-modal').classList.add('hidden');
document.getElementById('curriculum-modal').classList.remove('flex');
document.body.style.overflow = '';
};
document.getElementById('curriculum-form').addEventListener('submit', async (e) => {
e.preventDefault();
const btn = document.getElementById('curr-submit-btn');
const feedback = document.getElementById('curriculum-feedback');
btn.disabled = true;
btn.innerHTML = ' Enregistrement...';
feedback.classList.add('hidden');
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) throw new Error("Session expirée. Veuillez vous reconnecter.");
const category = document.querySelector('input[name="category_type"]:checked').value;
const title = document.getElementById('curr_title').value;
const organization = document.getElementById('curr_organization').value;
const city = document.getElementById('curr_city').value;
const country = document.getElementById('curr_country').value;
const startYear = document.getElementById('curr_start_year').value ?
parseInt(document.getElementById('curr_start_year').value) : null;
const endYear = parseInt(document.getElementById('curr_end_year').value);
const description = document.getElementById('curr_description').value;
const url = document.getElementById('curr_url').value;
const payload = {
consultant_id: user.id,
category_type: category,
title: title,
organization: organization,
city: city,
location_country: country,
start_year: startYear,
end_year: endYear,
description: description,
url_reference: url
};
let error;
if (currentEditingId) {
const { error: err } = await supabase
.from('consultant_curriculum')
.update(payload)
.eq('id', currentEditingId);
error = err;
} else {
const { error: err } = await supabase
.from('consultant_curriculum')
.insert([payload]);
error = err;
}
if (error) throw error;
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations[lang]?.cooptation?.curriculum || {};
feedback.innerText = currentEditingId ? (trans.feedback_update_success || "Mis à jour !") :
(trans.feedback_success || "Ajouté !");
feedback.className = 'block p-4 rounded-xl text-sm font-semibold text-center bg-green-50 text-green-700 border
border-green-100 mb-4';
feedback.classList.remove('hidden');
document.getElementById('curriculum-form').reset();
currentEditingId = null;
await loadMyCurriculum();
setTimeout(() => feedback.classList.add('hidden'), 2000);
} catch (err) {
feedback.innerText = "Erreur : " + err.message;
feedback.className = 'block p-4 rounded-xl text-sm font-semibold text-center bg-red-50 text-red-700 border
border-red-100 mb-4';
feedback.classList.remove('hidden');
} finally {
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations[lang]?.cooptation?.curriculum || {};
btn.disabled = false;
btn.innerHTML = currentEditingId ? `${trans.btn_update ||
'Mettre à jour'} ` : `${trans.btn_save ||
'Enregistrer'} `;
}
});
async function loadHospitals() {
const select = document.getElementById('fee_hospital_id');
if (!select) return;
if (hospitalData.length > 0) {
// Already loaded, just ensure placeholder is translated
return;
}
try {
const { data, error } = await supabase
.from('hospitals')
.select('id, name')
.eq('is_active', true)
.order('name');
if (error) throw error;
hospitalData = data || [];
const lang = localStorage.getItem('language') || 'fr';
const placeholder = window.translations[lang]?.cooptation?.fees?.hospital_placeholder || "Sélectionnez un
établissement...";
select.innerHTML = `${placeholder} ` +
hospitalData.map(h => `${h.name} `).join('');
} catch (err) {
console.error('Error loading hospitals:', err);
}
}
window.loadMyFees = async () => {
const listContainer = document.getElementById('my-fees-list');
if (!listContainer) return;
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations?.[lang]?.cooptation?.fees || {};
listContainer.innerHTML = ` ${trans.loading || 'Chargement...'}
`;
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
// Manual Join Fallback: Fetch fees and hospitals separately to avoid 400 error from missing FK
const { data: fees, error: feesError } = await supabase
.from('consultant_fees')
.select('*')
.eq('consultant_id', user.id)
.order('created_at', { ascending: false });
if (feesError) throw feesError;
// Use globally cached hospitalData or fetch if empty
if (hospitalData.length === 0) {
await loadHospitals();
}
// Merge data in JS
myFeesData = (fees || []).map(fee => ({
...fee,
hospitals: hospitalData.find(h => h.id === fee.hospital_id) || { name: 'Établissement' }
}));
const badge = document.getElementById('fees-count-badge');
if (badge) {
badge.textContent = myFeesData.length;
badge.classList.toggle('hidden', myFeesData.length === 0);
}
if (myFeesData.length === 0) {
listContainer.innerHTML = `
${trans.no_fees || 'Aucun tarif enregistré.'}
`;
return;
}
listContainer.innerHTML = myFeesData.map(item => `
${item.currency}
${item.hospitals?.name || 'Établissement'}
${item.service_name}
Montant à afficher aux patients : ${(parseFloat(item.price_amount) +
parseFloat(item.hospital_fees || 0)).toFixed(2)} ${item.currency}
Acte : ${item.price_amount} ${item.currency}
${item.hospital_fees ? `• Frais Hôp. : ${item.hospital_fees} ${item.currency} ` :
''}
${item.payto ? `
Payé à : ${item.payto}
` : ''}
`).join('');
} catch (err) {
listContainer.innerHTML = `Erreur: ${err.message}
`;
}
};
window.editFee = (id) => {
const fee = myFeesData.find(f => f.id === id);
if (!fee) return;
currentFeeEditingId = id;
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations[lang]?.cooptation?.fees || {};
document.getElementById('fee_hospital_id').value = fee.hospital_id;
document.getElementById('fee_service_name').value = fee.service_name;
document.getElementById('fee_price_amount').value = fee.price_amount;
document.getElementById('fee_currency').value = fee.currency;
document.getElementById('fee_hospital_fees').value = fee.hospital_fees || '';
document.getElementById('fee_payto').value = fee.payto || '';
document.getElementById('fee_confirmation').checked = fee.confirmation || false;
document.getElementById('fee-submit-btn').innerHTML = trans.btn_update || 'Mettre à jour';
if (typeof updateFeeTotal === 'function') updateFeeTotal();
document.getElementById('fee-form').scrollIntoView({ behavior: 'smooth' });
};
window.deleteFee = async (id) => {
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations[lang]?.cooptation?.fees || {};
if (!confirm(trans.confirm_delete || "Voulez-vous supprimer ce tarif ?")) return;
try {
const { error } = await supabase.from('consultant_fees').delete().eq('id', id);
if (error) throw error;
const feedback = document.getElementById('fee-feedback');
feedback.textContent = trans.feedback_delete_success || "Supprimé !";
feedback.className = "p-4 rounded-xl text-sm font-semibold text-center bg-green-100 text-green-700 mb-4";
feedback.classList.remove('hidden');
setTimeout(() => feedback.classList.add('hidden'), 3000);
await loadMyFees();
} catch (err) {
alert("Erreur: " + err.message);
}
};
document.getElementById('fee-form').addEventListener('submit', async (e) => {
e.preventDefault();
const btn = document.getElementById('fee-submit-btn');
const feedback = document.getElementById('fee-feedback');
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations[lang]?.cooptation?.fees || {};
btn.disabled = true;
btn.innerHTML = ' ...';
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) throw new Error("Session expirée");
const payload = {
consultant_id: user.id,
hospital_id: document.getElementById('fee_hospital_id').value,
service_name: document.getElementById('fee_service_name').value,
price_amount: parseFloat(document.getElementById('fee_price_amount').value),
currency: document.getElementById('fee_currency').value,
hospital_fees: parseFloat(document.getElementById('fee_hospital_fees').value) || null,
payto: document.getElementById('fee_payto').value || null,
confirmation: document.getElementById('fee_confirmation').checked
};
let error;
if (currentFeeEditingId) {
const { error: err } = await supabase.from('consultant_fees').update(payload).eq('id', currentFeeEditingId);
error = err;
} else {
const { error: err } = await supabase.from('consultant_fees').insert([payload]);
error = err;
}
if (error) throw error;
feedback.innerText = currentFeeEditingId ? (trans.feedback_update_success || "Mis à jour !") :
(trans.feedback_success || "Ajouté !");
feedback.className = 'block p-4 rounded-xl text-sm font-semibold text-center bg-green-50 text-green-700 border
border-green-100 mb-4';
feedback.classList.remove('hidden');
document.getElementById('fee-form').reset();
if (typeof updateFeeTotal === 'function') updateFeeTotal();
currentFeeEditingId = null;
await loadMyFees();
setTimeout(() => feedback.classList.add('hidden'), 2000);
} catch (err) {
feedback.innerText = "Erreur : " + err.message;
feedback.className = 'block p-4 rounded-xl text-sm font-semibold text-center bg-red-50 text-red-700 border
border-red-100 mb-4';
feedback.classList.remove('hidden');
} finally {
btn.disabled = false;
btn.innerHTML = currentFeeEditingId ? trans.btn_update : trans.btn_save;
}
});
// CV Viewer Logic
window.viewCV = async (consultantId) => {
if (!consultantId || consultantId === 'undefined' || consultantId === 'null' || consultantId === '') {
console.warn("Invalid consultantId passed to viewCV:", consultantId);
return;
}
const modal = document.getElementById('cv-viewer-modal');
const content = document.getElementById('cv-viewer-content');
const lang = localStorage.getItem('language') || 'fr';
modal.classList.remove('hidden');
modal.classList.add('flex');
document.body.style.overflow = 'hidden';
content.innerHTML = '
Chargement du curriculum...
';
try {
// Fetch consultant details first for the title
const { data: consultant, error: cErr } = await supabase
.from('consultants')
.select('full_name, specialty')
.eq('id', consultantId)
.single();
if (consultant) {
document.getElementById('cv-consultant-name').textContent = consultant.full_name;
document.getElementById('cv-consultant-specialty').textContent = consultant.specialty || '';
}
const { data: curriculum, error: err } = await supabase
.from('consultant_curriculum')
.select('*')
.eq('consultant_id', consultantId)
.order('end_year', { ascending: false });
if (err) throw err;
if (!curriculum || curriculum.length === 0) {
content.innerHTML = `
${window.translations[lang]?.cooptation?.no_cv_data || 'Aucune information disponible.'}
`;
return;
}
// Group by category
const groups = {
university: [],
publication: [],
award: [],
training: []
};
curriculum.forEach(item => {
if (groups[item.category_type]) {
groups[item.category_type].push(item);
}
});
let html = '';
const categoryIcons = {
university: 'fa-university',
publication: 'fa-book',
award: 'fa-award',
training: 'fa-briefcase'
};
const categoryLabels = {
university: window.translations[lang]?.cooptation?.curriculum?.university || 'Formation',
publication: window.translations[lang]?.cooptation?.curriculum?.publication || 'Publications',
award: window.translations[lang]?.cooptation?.curriculum?.award || 'Prix & Distinctions',
training: window.translations[lang]?.cooptation?.curriculum?.training || 'Stages'
};
Object.keys(groups).forEach(cat => {
if (groups[cat].length > 0) {
html += `
${groups[cat].map(item => `
${item.title}
${item.organization}
${item.city || item.location_country ? `
${[item.city,
item.location_country].filter(Boolean).join(', ')}
` : ''}
${item.start_year ? item.start_year + ' - ' : ''}${item.end_year}
${item.description ? `
${item.description}
` : ''}
${item.url_reference ? `
${window.translations[lang]?.cooptation?.curriculum?.url || 'Référence'}
` : ''}
`).join('')}
`;
}
});
html += '
';
content.innerHTML = html;
} catch (err) {
content.innerHTML = `
Erreur de chargement: ${err.message}
`;
}
};
window.closeCVViewer = () => {
document.getElementById('cv-viewer-modal').classList.add('hidden');
document.getElementById('cv-viewer-modal').classList.remove('flex');
document.body.style.overflow = '';
};
// Scroll Logic
window.addEventListener('scroll', () => {
const topBtn = document.getElementById('scroll-top-btn');
const bottomBtn = document.getElementById('scroll-bottom-btn');
if (!topBtn || !bottomBtn) return;
const scrollPos = window.scrollY;
const totalHeight = document.documentElement.scrollHeight - window.innerHeight;
if (scrollPos > 300) {
topBtn.style.opacity = '1';
topBtn.style.visibility = 'visible';
} else {
topBtn.style.opacity = '0';
topBtn.style.visibility = 'hidden';
}
if (scrollPos < totalHeight - 300) { bottomBtn.style.opacity='1' ; bottomBtn.style.visibility='visible' ; } else
{ bottomBtn.style.opacity='0' ; bottomBtn.style.visibility='hidden' ; } }); // --- Second Opinion Fees
Management --- let currentSoFeeEditingId=null; let mySoFeesData=[]; window.openSecondOpinionFeesModal=async
()=> {
try {
currentSoFeeEditingId = null;
const form = document.getElementById('so-fee-form');
if (form) form.reset();
const { data: { session } } = await supabase.auth.getSession();
if (!session) return;
// Get current doctor info
const { data: consultant } = await supabase
.from('consultants')
.select('full_name, specialty')
.eq('id', session.user.id)
.single();
if (consultant) {
document.getElementById('so_fee_specialty').value = consultant.specialty || '';
}
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations?.[lang]?.cooptation?.second_opinion_fees || {};
const submitBtn = document.getElementById('so-fee-submit-btn');
if (submitBtn) {
submitBtn.innerHTML = trans.btn_save || 'Enregistrer';
}
const modal = document.getElementById('second-opinion-fees-modal');
if (modal) {
modal.classList.remove('hidden');
modal.classList.add('flex');
document.body.style.overflow = 'hidden';
}
await loadMySoFees(consultant?.full_name);
} catch (err) {
console.error("Error opening Second Opinion Fees Modal:", err);
}
};
window.closeSecondOpinionFeesModal = () => {
const modal = document.getElementById('second-opinion-fees-modal');
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('flex');
document.body.style.overflow = '';
}
};
window.loadMySoFees = async () => {
const listContainer = document.getElementById('my-so-fees-list');
if (!listContainer) return;
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations?.[lang]?.cooptation?.second_opinion_fees || {};
listContainer.innerHTML = ` Chargement...
`;
try {
const { data: { session } } = await supabase.auth.getSession();
if (!session) return;
const { data, error } = await supabase
.from('prix_second_avis')
.select('*')
.eq('consultant_id', session.user.id)
.order('created_at', { ascending: false });
if (error) throw error;
mySoFeesData = data || [];
const badge = document.getElementById('so-fees-count-badge');
if (badge) {
badge.textContent = mySoFeesData.length;
badge.classList.toggle('hidden', mySoFeesData.length === 0);
}
if (mySoFeesData.length === 0) {
listContainer.innerHTML = `
${trans.no_fees || 'Aucun tarif enregistré.'}
`;
return;
}
listContainer.innerHTML = mySoFeesData.map(item => `
${item.devise}
${item.specialite}
${item.prix}
${item.devise}
`).join('');
} catch (err) {
listContainer.innerHTML = `Erreur: ${err.message}
`;
}
};
window.editSoFee = (id) => {
const fee = mySoFeesData.find(f => f.id === id);
if (!fee) return;
currentSoFeeEditingId = id;
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations[lang]?.cooptation?.second_opinion_fees || {};
document.getElementById('so_fee_specialty').value = fee.specialite;
document.getElementById('so_fee_price').value = fee.prix;
document.getElementById('so_fee_currency').value = fee.devise;
document.getElementById('so-fee-submit-btn').innerHTML = trans.btn_update || 'Mettre à jour';
document.getElementById('so-fee-form').scrollIntoView({ behavior: 'smooth' });
};
window.deleteSoFee = async (id) => {
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations[lang]?.cooptation?.second_opinion_fees || {};
if (!confirm(trans.confirm_delete || "Supprimer ?")) return;
try {
const { error } = await supabase.from('prix_second_avis').delete().eq('id', id);
if (error) throw error;
const feedback = document.getElementById('so-fee-feedback');
feedback.textContent = trans.feedback_delete_success || "Supprimé !";
feedback.className = "p-4 rounded-xl text-sm font-semibold text-center bg-green-100 text-green-700 mb-4";
feedback.classList.remove('hidden');
setTimeout(() => feedback.classList.add('hidden'), 3000);
await loadMySoFees();
} catch (err) {
alert("Erreur: " + err.message);
}
};
document.getElementById('so-fee-form').addEventListener('submit', async (e) => {
e.preventDefault();
const btn = document.getElementById('so-fee-submit-btn');
const feedback = document.getElementById('so-fee-feedback');
const lang = localStorage.getItem('language') || 'fr';
const trans = window.translations[lang]?.cooptation?.second_opinion_fees || {};
btn.disabled = true;
btn.innerHTML = ' ...';
try {
const { data: { session } } = await supabase.auth.getSession();
if (!session) throw new Error("Session expirée.");
const payload = {
consultant_id: session.user.id,
specialite: document.getElementById('so_fee_specialty').value,
prix: parseFloat(document.getElementById('so_fee_price').value),
devise: document.getElementById('so_fee_currency').value
};
let error;
if (currentSoFeeEditingId) {
const { error: err } = await supabase.from('prix_second_avis').update(payload).eq('id',
currentSoFeeEditingId);
error = err;
} else {
const { error: err } = await supabase.from('prix_second_avis').insert([payload]);
error = err;
}
if (error) throw error;
feedback.innerText = currentSoFeeEditingId ? (trans.feedback_update_success || "Mis à jour !") :
(trans.feedback_success || "Ajouté !");
feedback.className = 'block p-4 rounded-xl text-sm font-semibold text-center bg-green-50 text-green-700
border border-green-100 mb-4';
feedback.classList.remove('hidden');
if (!currentSoFeeEditingId) {
document.getElementById('so_fee_price').value = '';
}
currentSoFeeEditingId = null;
await loadMySoFees();
setTimeout(() => feedback.classList.add('hidden'), 2000);
} catch (err) {
feedback.innerText = "Erreur : " + err.message;
feedback.className = 'block p-4 rounded-xl text-sm font-semibold text-center bg-red-50 text-red-700 border
border-red-100 mb-4';
feedback.classList.remove('hidden');
} finally {
btn.disabled = false;
btn.innerHTML = currentSoFeeEditingId ? trans.btn_update : trans.btn_save;
}
});
// --- End Second Opinion Fees ---
Mon Curriculum
Vitae
Ajoutez vos
diplômes,
publications et distinctions
Mes Tarifs de Consultation
Définissez vos honoraires selon les établissements
Mes Tarifs de Second Avis
Définissez vos honoraires pour les consultations de second avis