/* Calendrier */ .cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:3px;margin-bottom:12px;} .cal-head{text-align:center;font-size:10px;font-weight:700;color:var(--g400);padding:4px 0;} .cal-day{aspect-ratio:1;display:flex;align-items:center;justify-content:center;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;transition:all .15s;position:relative;} .cal-day.empty{cursor:default;} .cal-day.past{color:var(--g300);cursor:not-allowed;} .cal-day.available{color:var(--ink);background:var(--g50);border:1px solid var(--g200);} .cal-day.available:hover{background:var(--blueL);border-color:var(--blue);color:var(--blue);} .cal-day.selected{background:var(--blue);color:#fff;border-color:var(--blue);} .cal-day.in-range{background:var(--blueL);color:var(--blue);border-radius:0;border:none;} .cal-day.range-start{border-radius:8px 0 0 8px;} .cal-day.range-end{border-radius:0 8px 8px 0;} .cal-day.booked{background:var(--rdL);color:var(--rd);cursor:not-allowed;text-decoration:line-through;} .cal-day.blocked{background:var(--g100);color:var(--g300);cursor:not-allowed;} .cal-nav{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;} .cal-nav-btn{width:32px;height:32px;border-radius:8px;border:1px solid var(--g200);background:var(--white);cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center;} .cal-nav-title{font-size:14px;font-weight:700;color:var(--ink);} /* Booking card */ .book-summary{background:var(--g50);border:1px solid var(--g200);border-radius:12px;padding:14px;margin-bottom:14px;} .book-row{display:flex;justify-content:space-between;align-items:center;font-size:13px;padding:4px 0;} .book-row.total{border-top:1px solid var(--g200);margin-top:6px;padding-top:10px;font-weight:800;font-size:15px;} .book-badge{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:50px;font-size:11px;font-weight:700;} .book-b-inst{background:var(--gnL);color:var(--gn);} .book-b-req{background:#FEF3C7;color:#92400E;} .book-b-pend{background:#FEF3C7;color:#92400E;} .book-b-conf{background:var(--gnL);color:var(--gn);} .book-b-canc{background:var(--rdL);color:var(--rd);} .book-b-comp{background:var(--blueL);color:var(--blue);} .book-b-rej{background:var(--rdL);color:var(--rd);} /* Section Séjours */ #sec-sejours{display:none;} .sejour-card{background:var(--white);border-radius:var(--r);overflow:hidden;cursor:pointer;transition:transform .2s,box-shadow .2s;border:1px solid var(--g200);position:relative;} .sejour-card:hover{transform:translateY(-3px);box-shadow:var(--sh2);} .sejour-img{height:200px;position:relative;overflow:hidden;background:var(--g100);} .sejour-img img{width:100%;height:100%;object-fit:cover;} .sejour-badge{position:absolute;top:8px;left:8px;display:flex;gap:4px;} .sejour-price-night{position:absolute;bottom:8px;left:8px;background:rgba(0,0,0,.7);color:#fff;border-radius:8px;padding:4px 10px;font-size:13px;font-weight:800;} .sejour-body{padding:12px;} .sejour-name{font-size:14px;font-weight:700;color:var(--ink);margin-bottom:3px;} .sejour-loc{font-size:11px;color:var(--g400);margin-bottom:8px;} .sejour-feats{display:flex;gap:6px;flex-wrap:wrap;} .sejour-feat{font-size:10px;padding:2px 7px;background:var(--g100);border-radius:50px;color:var(--g500);} /* Dash réservations */ .bk-card{background:var(--white);border:1px solid var(--g200);border-radius:12px;padding:14px;margin-bottom:10px;box-shadow:var(--sh);} .bk-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:8px;} .bk-card-title{font-size:14px;font-weight:700;color:var(--ink);} .bk-card-dates{font-size:12px;color:var(--g400);margin-top:2px;} .bk-card-acts{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px;} /* Proprio calendar manager */ .cal-manager-day{aspect-ratio:1;display:flex;align-items:center;justify-content:center;border-radius:8px;font-size:11px;font-weight:600;cursor:pointer;transition:all .15s;border:1.5px solid transparent;} .cal-manager-day.available{background:var(--gnL);color:var(--gn);border-color:#BBF7D0;} .cal-manager-day.available:hover{background:#BBF7D0;} .cal-manager-day.blocked{background:var(--rdL);color:var(--rd);border-color:#FECACA;} .cal-manager-day.booked{background:var(--blueL);color:var(--blue);border-color:var(--blueS);cursor:not-allowed;} .cal-manager-day.past{background:var(--g100);color:var(--g300);cursor:not-allowed;} .cal-manager-day.empty{cursor:default;}
🏠 Lokas · Sénégal

Trouve le logement
que tu mérites

Publie tes biens, vends sans bouger de chez toi.

Mise en ligne rapidePublie en moins de 2 minutes
💳
Wave & Orange MoneyPaiement 100% mobile, sans carte
📍
Toutes les villesDakar, Thiès, Saint-Louis et plus
🇸🇳 Marketplace immobilière — Sénégal

Publiez votre bien
dès 2 000 F CFA

Particuliers et agences — publiez vos annonces en 5 minutes. Payez via Wave, Orange Money ou PayTech.

🆓 Gratuit
0 F
Pour tester
✅ 1 annonce
✅ 3 photos
❌ Badge Vérifié
❌ WhatsApp direct
🏢 Pro
10 000 F / mois
Agences & pros actifs
✅ Annonces illimitées
✅ 20 photos / annonce
✅ Badge 🟣 Pro
✅ Stats & leads
Annonces actives
Villes couvertes
2 000 F
Pour commencer
<24h
Mise en ligne
Pub
🖼️
Espace publicitaire — Bannière 728×90
Comment ça marche ?
Publiez en moins de 5 minutes
📝
1. Remplissez le formulaire
Titre, type, ville, loyer, photos. Simple et guidé pas à pas.
💰
2. Choisissez votre plan
Gratuit, 2 000 F / 30 jours, ou Pro 10 000 F/mois. Wave ou Orange Money.
3. En ligne sous 24h
Votre annonce est validée et visible par tous les chercheurs de logement.
Dernières annonces
Biens récemment publiés
🚗 Louer ce véhicule
✈️ Réserver ce transfert
🚗 Proposer mon véhicule
✈️ Proposer un transfert
📅 Réserver
📅 Gérer mes disponibilités
💬 Messages
📊 Mes statistiques
🎟 Code promo

Entrez votre code promo pour obtenir une réduction sur votre réservation.

⭐ Laisser un avis

Partagez votre expérience pour aider les autres voyageurs.

+ Créer une annonce
⚖️ Comparer :
⚖️ Comparaison
🔔 Créer une alerte

Recevez un WhatsApp dès qu'une nouvelle annonce correspond à vos critères.

⭐ Avis
Laisser un avis
📄 Conditions d'utilisation
Dernière mise à jour : Janvier 2025
1. Présentation

Lokas est une marketplace immobilière indépendante opérant au Sénégal, permettant à des particuliers et professionnels de publier et consulter des annonces de location, vente et location courte durée de biens immobiliers.

2. Conditions de publication

En publiant une annonce sur Lokas, vous certifiez :

3. Paiement et publication

Le paiement de 2 000 FCFA (plan Annonce) ou 10 000 FCFA (plan Pro) est requis pour la publication d'annonces payantes. Le paiement doit être effectué via Wave ou Orange Money au numéro indiqué, avec communication de la référence de transaction. Lokas se réserve le droit de vérifier chaque paiement avant validation. Aucun remboursement ne sera effectué après validation de l'annonce.

4. Durée des annonces

Les annonces payantes sont publiées pour une durée de 30 jours à compter de la date de validation. À l'expiration, l'annonce est automatiquement désactivée. Le renouvellement est possible en effectuant un nouveau paiement.

5. Responsabilité

Lokas agit en tant qu'intermédiaire technique. Nous ne sommes pas partie aux transactions immobilières entre annonceurs et locataires/acheteurs. Nous déclinons toute responsabilité en cas de litige entre les parties, d'inexactitude des annonces ou de fraude commise par un utilisateur.

6. Contenu interdit

Sont strictement interdits sur la plateforme :

7. Données personnelles

Les données collectées (nom, téléphone, email) sont utilisées uniquement pour le fonctionnement de la plateforme et la mise en relation entre annonceurs et chercheurs. Elles ne sont pas vendues à des tiers. Vous pouvez demander la suppression de vos données à tout moment en nous contactant.

8. Modération

Lokas se réserve le droit de supprimer sans préavis toute annonce ne respectant pas les présentes conditions, de bloquer tout utilisateur frauduleux et de signaler les cas graves aux autorités compétentes.

9. Contact

Pour toute question, litige ou signalement, contactez-nous via WhatsApp au numéro indiqué sur la plateforme ou par email.

Détail
📢 Publier une annonce
Contact
Bien
Photos
Plan
Vos coordonnées
Comment les candidats vous contacteront
Votre bien
Décrivez votre propriété
Photos
+5× plus de contacts avec des photos 📷
⚠️ Minimum 1 photo obligatoire
📷
Glissez vos photos ici
ou cliquez pour sélectionner · max 20 photos · JPG/PNG
☁️ Stockage sécurisé Supabase
Filmez votre bien et collez le lien YouTube — +300% de contacts
Plan & Paiement
Choisissez et payez pour publier
🆓
Gratuit
1 annonce, 3 photos, sans badge
2K
Annonce — 2 000 F
30 jours, badge 🔵 Vérifié, WA direct
PRO
Pro — 10 000 F/mois
Illimité, stats, badge 🟣 Pro
💙🟠 Wave / Orange Money
Paiement sécurisé · Votre annonce est activée automatiquement après confirmation
💙 Wave🟠 Orange Money💳 Carte bancaire
Vous serez redirigé vers la page de paiement sécurisée
Se connecter

Pas de compte ? S'inscrire

// ════════════════════════════════════════ var sjMapVisible = false; function toggleSejourMap() { sjMapVisible = !sjMapVisible; var btn = document.getElementById("sj-map-btn"); var container = document.getElementById("sj-map-container"); if (btn) btn.classList.toggle("on", sjMapVisible); if (container) container.style.display = sjMapVisible ? "block" : "none"; if (sjMapVisible) renderSejourMap(); } function renderSejourMap() { var frame = document.getElementById("sj-map-frame"); if (!frame) return; var q = (document.getElementById("sj-q")||{}).value || "Dakar"; var city = q || "Dakar"; frame.innerHTML = ''; } // ════════════════════════════════════════ // ══ 7. MESSAGERIE IN-APP ══ // ════════════════════════════════════════ var curMsgBookingId = null; var curMsgListingTitle = ""; var msgPollingInterval = null; async function openMsg(bookingId, listingTitle) { curMsgBookingId = bookingId; curMsgListingTitle = listingTitle || "Réservation"; var titleEl = document.getElementById("sh-msg-title"); if (titleEl) titleEl.textContent = "💬 " + curMsgListingTitle; document.getElementById("ov-msg").classList.add("show"); document.getElementById("sh-msg").classList.add("show"); await loadMsgThread(); // Polling toutes les 10s if (msgPollingInterval) clearInterval(msgPollingInterval); msgPollingInterval = setInterval(loadMsgThread, 10000); } function closeMsg() { document.getElementById("ov-msg").classList.remove("show"); document.getElementById("sh-msg").classList.remove("show"); if (msgPollingInterval) { clearInterval(msgPollingInterval); msgPollingInterval = null; } } async function loadMsgThread() { if (!curMsgBookingId) return; var thread = document.getElementById("msg-thread"); if (!thread) return; try { var msgs = await api("/rest/v1/lokas_messages?booking_id=eq."+curMsgBookingId+"&order=created_at.asc&select=*") || []; if (!msgs.length) { thread.innerHTML = '
Aucun message — démarrez la conversation
'; return; } thread.innerHTML = msgs.map(function(m) { var isMine = curUser && m.sender_id === curUser.id; var dt = m.created_at ? new Date(m.created_at).toLocaleTimeString("fr-FR",{hour:"2-digit",minute:"2-digit"}) : ""; return '
'+ '
'+ escHtml(m.content||"")+ '
'+ '
'+escHtml(m.sender_name||"")+' · '+dt+'
'+ '
'; }).join(""); thread.scrollTop = thread.scrollHeight; } catch(e) {} } async function sendMsg() { if (!curMsgBookingId || !curUser) { showToast("Connectez-vous pour envoyer un message","error"); return; } var inp = document.getElementById("msg-inp"); var content = (inp||{}).value || ""; if (!content.trim()) return; inp.value = ""; try { await api("/rest/v1/lokas_messages", { method:"POST", headers:{"Prefer":"return=minimal"}, body:JSON.stringify({ booking_id: curMsgBookingId, sender_id: curUser.id, sender_name: curUser.email ? curUser.email.split("@")[0] : "Moi", content: content.trim() }) }); await loadMsgThread(); } catch(e) { showToast("Erreur envoi message","error"); } } // ════════════════════════════════════════ // ══ 8. STATS PROPRIO ══ // ════════════════════════════════════════ async function openStats() { document.getElementById("ov-stats").classList.add("show"); document.getElementById("sh-stats").classList.add("show"); var body = document.getElementById("sh-stats-body"); if (body) body.innerHTML = '
Chargement...
'; if (!curUser) { if (body) body.innerHTML = '
Connectez-vous d\'abord
'; return; } try { var listings = await api("/rest/v1/marketplace_listings?user_id=eq."+curUser.id+"&select=*") || []; var myIds = listings.map(function(l){ return l.id; }); var bookings = myIds.length ? await api("/rest/v1/lokas_bookings?listing_id=in.("+myIds.join(",")+")"+"&select=*") || [] : []; var leads = myIds.length ? await api("/rest/v1/marketplace_leads?listing_id=in.("+myIds.join(",")+")"+"&select=*") || [] : []; var totalViews = listings.reduce(function(t,l){ return t+(l.views||0); }, 0); var totalLeads = leads.length; var totalBkConf = bookings.filter(function(b){ return b.statut==="confirmed"||b.statut==="completed"; }).length; var totalRev = bookings.filter(function(b){ return b.statut==="confirmed"||b.statut==="completed"; }).reduce(function(t,b){ return t+(b.acompte||0); }, 0); var sejours = listings.filter(function(l){ return l.freq==="/nuit"||l.type==="airbnb"||l.booking_type==="airbnb"||l.booking_type==="hotel"; }); var occupancyDays = bookings.filter(function(b){ return b.statut==="confirmed"||b.statut==="completed"; }).reduce(function(t,b){ return t+(b.nights||0); }, 0); // Revenus par mois (6 derniers mois) var monthRevMap = {}; bookings.forEach(function(b) { if (b.statut!=="confirmed"&&b.statut!=="completed") return; var m = b.created_at ? b.created_at.slice(0,7) : "—"; monthRevMap[m] = (monthRevMap[m]||0) + (b.acompte||0); }); var months = Object.keys(monthRevMap).sort().slice(-6); body.innerHTML = '
'+ kpiBox("👁","Vues totales",totalViews,"var(--blue)")+ kpiBox("📩","Leads reçus",totalLeads,"var(--indigo)")+ kpiBox("📅","Réservations",totalBkConf,"var(--gn)")+ kpiBox("💰","Acomptes (F)",fmtPrice(totalRev),"var(--om)")+ '
'+ (sejours.length ? '
'+ '
🏨 Taux d\'occupation
'+ '
'+occupancyDays+' nuits
'+ '
réservées sur vos biens courte durée
'+ '
' : '')+ (months.length ? '
'+ '
📈 Revenus par mois
'+ months.map(function(m){ var rev = monthRevMap[m]||0; var maxRev = Math.max.apply(null, months.map(function(x){ return monthRevMap[x]||0; })); var pct = maxRev ? Math.round((rev/maxRev)*100) : 0; var label = m.slice(5)+"/"+m.slice(2,4); return '
'+ '
'+label+''+fmtPrice(rev)+'
'+ '
'+ '
'; }).join("")+ '
' : '')+ '
📋 Mes annonces
'+ listings.map(function(l){ return '
'+ '
'+escHtml(l.title||"")+'
'+ '
👁 '+(l.views||0)+'
'+ '
'; }).join(""); } catch(e) { if (body) body.innerHTML = '
Erreur chargement stats
'; } } function kpiBox(ico, label, val, color) { return '
'+ '
'+ico+'
'+ '
'+val+'
'+ '
'+label+'
'+ '
'; } function closeStats() { document.getElementById("ov-stats").classList.remove("show"); document.getElementById("sh-stats").classList.remove("show"); } // ════════════════════════════════════════ // ══ 9. CODES PROMO ══ // ════════════════════════════════════════ var activePromo = null; // {code, discount_pct, discount_flat} function openPromo() { document.getElementById("ov-promo").classList.add("show"); document.getElementById("sh-promo").classList.add("show"); var res = document.getElementById("promo-res"); if (res) res.innerHTML = ""; } function closePromo() { document.getElementById("ov-promo").classList.remove("show"); document.getElementById("sh-promo").classList.remove("show"); } async function applyPromo() { var code = (document.getElementById("promo-inp")||{}).value || ""; var res = document.getElementById("promo-res"); if (!code.trim()) return; try { var rows = await api("/rest/v1/lokas_promos?code=eq."+code.trim().toUpperCase()+"&select=*") || []; var promo = rows[0]; if (!promo) { res.innerHTML = '
❌ Code invalide ou expiré
'; return; } if (promo.expires_at && new Date(promo.expires_at) < new Date()) { res.innerHTML = '
❌ Ce code a expiré
'; return; } if (promo.max_uses && (promo.uses_count||0) >= promo.max_uses) { res.innerHTML = '
❌ Ce code a atteint sa limite d\'utilisation
'; return; } activePromo = promo; var discountTxt = promo.discount_pct ? "-"+promo.discount_pct+"%" : "-"+fmtPrice(promo.discount_flat||0); res.innerHTML = '
✅ Code appliqué ! Réduction : '+discountTxt+'
'; showToast("Code promo appliqué 🎟","success"); closePromo(); // Re-render booking sheet si ouvert if (BOOKING_STATE.listing) renderBookingSheet(); } catch(e) { res.innerHTML = '
Erreur — réessayez
'; } } function calcPromoPrice(total) { if (!activePromo) return total; if (activePromo.discount_pct) return Math.round(total * (1 - activePromo.discount_pct/100)); if (activePromo.discount_flat) return Math.max(0, total - activePromo.discount_flat); return total; } // ════════════════════════════════════════ // ══ 10. REVIEWS SUR RÉSERVATIONS TERMINÉES ══ // ════════════════════════════════════════ var curBkRevStar = 0; function openBkReview(bookingId, listingId) { curBkRevStar = 0; var listingIdEl = document.getElementById("bkrev-listing-id"); var bookingIdEl = document.getElementById("bkrev-booking-id"); if (listingIdEl) listingIdEl.value = listingId; if (bookingIdEl) bookingIdEl.value = bookingId; setBkRevStar(0); var res = document.getElementById("bkrev-res"); if (res) res.innerHTML = ""; document.getElementById("ov-bk-review").classList.add("show"); document.getElementById("sh-bk-review").classList.add("show"); } function closeBkReview() { document.getElementById("ov-bk-review").classList.remove("show"); document.getElementById("sh-bk-review").classList.remove("show"); } function setBkRevStar(n) { curBkRevStar = n; var stars = document.querySelectorAll("#bkrev-stars span"); stars.forEach(function(s, i) { s.textContent = i < n ? "⭐" : "☆"; }); } async function submitBkReview() { var listingId = (document.getElementById("bkrev-listing-id")||{}).value; var bookingId = (document.getElementById("bkrev-booking-id")||{}).value; var name = (document.getElementById("bkrev-name")||{}).value.trim(); var comment = (document.getElementById("bkrev-comment")||{}).value.trim(); var res = document.getElementById("bkrev-res"); if (!curBkRevStar) { res.innerHTML = '
Choisissez une note
'; return; } if (!name) { res.innerHTML = '
Votre nom est requis
'; return; } try { await api("/rest/v1/marketplace_reviews", { method:"POST", headers:{"Prefer":"return=minimal"}, body:JSON.stringify({ listing_id:listingId, rating:curBkRevStar, author_name:name, comment:comment, user_id:curUser?curUser.id:null, booking_id:bookingId }) }); // Marquer la réservation comme reviewée await api("/rest/v1/lokas_bookings?id=eq."+bookingId, {method:"PATCH",headers:{"Prefer":"return=minimal"},body:JSON.stringify({reviewed:true})}).catch(function(){}); res.innerHTML = '
✅ Avis publié — merci !
'; showToast("Avis publié ⭐","success"); setTimeout(function(){ closeBkReview(); loadDashBookings(); }, 1500); } catch(e) { res.innerHTML = '
Erreur — réessayez
'; } } // ════════════════════════════════════════ // ══ 11. MULTI-LANGUE ══ // ════════════════════════════════════════ var LANGS = { fr: { "nav.home":"Accueil","nav.listings":"Annonces","nav.sejours":"🏨 Séjours","nav.dash":"Mon espace", "nav.login":"Connexion","nav.publish":"+ Publier", "hero.title":"Publiez votre bien
dès 2 000 F CFA", "hero.sub":"Particuliers et agences — publiez vos annonces en 5 minutes.", "listings.title":"Toutes les annonces","sejours.title":"🏨 Séjours & Airbnb", "sejours.sub":"Hôtels, Airbnb et locations courte durée au Sénégal", "dash.title":"Mon espace","dash.sub":"Gérez vos annonces", "btn.publish":"+ Nouvelle annonce","btn.stats":"📊 Mes stats", "splash.title":"Trouve le logement
que tu mérites", "splash.sub":"Publie tes biens, vends sans bouger de chez toi.", "splash.cta":"Voir les annonces →","splash.cta2":"+ Publier mon bien — dès 2 000 F" }, en: { "nav.home":"Home","nav.listings":"Listings","nav.sejours":"🏨 Stays","nav.dash":"My Space", "nav.login":"Login","nav.publish":"+ Post", "hero.title":"List your property
from 2,000 F CFA", "hero.sub":"Individuals & agencies — post your listings in 5 minutes.", "listings.title":"All listings","sejours.title":"🏨 Stays & Airbnb", "sejours.sub":"Hotels, Airbnb and short-term rentals in Senegal", "dash.title":"My Space","dash.sub":"Manage your listings", "btn.publish":"+ New listing","btn.stats":"📊 My stats", "splash.title":"Find the home
you deserve", "splash.sub":"List your property, sell without leaving home.", "splash.cta":"Browse listings →","splash.cta2":"+ List my property — from 2,000 F" }, wo: { "nav.home":"Kanam","nav.listings":"Jëfandikoo","nav.sejours":"🏨 Tànn","nav.dash":"Sa Yoon", "nav.login":"Dugg","nav.publish":"+ Bind", "hero.title":"Bindal sa kër
ci 2 000 F CFA", "hero.sub":"Tëralkat ak société — bindal sa kër ci 5 minit.", "listings.title":"Jëfandikoo yépp","sejours.title":"🏨 Tànn & Airbnb", "sejours.sub":"Ótéel, Airbnb ak yoon bu gudd ci Senegaal", "dash.title":"Sa Yoon","dash.sub":"Tëral sa jëfandikoo", "btn.publish":"+ Jëfandikoo bu bees","btn.stats":"📊 Sa statistik", "splash.title":"Seet kër bu baax
bu mel ni sa", "splash.sub":"Bindal sa kër, jaay bu dem dëkk.", "splash.cta":"Xool jëfandikoo →","splash.cta2":"+ Bindal sa kër — ci 2 000 F" } }; var currentLang = localStorage.getItem("lokas_lang") || "fr"; function setLang(lang) { currentLang = lang; localStorage.setItem("lokas_lang", lang); applyLang(); } function t(key) { return (LANGS[currentLang]||LANGS.fr)[key] || (LANGS.fr)[key] || key; } function applyLang() { // Nav links var nlHome = document.getElementById("nl-home"); var nlList = document.getElementById("nl-listings"); var nlSej = document.getElementById("nl-sejours"); var nlDash = document.getElementById("nl-dash"); if (nlHome) nlHome.textContent = t("nav.home"); if (nlList) nlList.textContent = t("nav.listings"); if (nlSej) nlSej.textContent = t("nav.sejours"); if (nlDash) nlDash.textContent = t("nav.dash"); // Séjours section title/sub var sjTitle = document.querySelector("#sec-sejours .sec-title"); var sjSub = document.querySelector("#sec-sejours .sec-sub"); if (sjTitle) sjTitle.textContent = t("sejours.title"); if (sjSub) sjSub.textContent = t("sejours.sub"); // Splash var splTitle = document.querySelector(".splash-title"); var splSub = document.querySelector(".splash-sub"); var splCta = document.querySelector(".splash-cta-main"); var splCta2 = document.querySelector(".splash-cta-pub"); if (splTitle) splTitle.innerHTML = t("splash.title"); if (splSub) splSub.textContent = t("splash.sub"); if (splCta) splCta.textContent = t("splash.cta"); if (splCta2) splCta2.textContent= t("splash.cta2"); // Lang selector sync var sel = document.getElementById("lang-sel"); if (sel) sel.value = currentLang; } // Init langue au chargement document.addEventListener("DOMContentLoaded", function(){ applyLang(); }); // ════════════════════════════════════════ // ══ DASHBOARD CHAUFFEUR / LOUEUR ══ // ════════════════════════════════════════ async function loadDriverDash() { if (!curUser) return; var section = document.getElementById("my-driver-section"); if (!section) return; var myCars = [], myTransfers = [], carBks = [], trBks = []; try { myCars = await api("/rest/v1/lokas_cars?user_id=eq."+curUser.id+"&select=*&order=created_at.desc") || []; myTransfers = await api("/rest/v1/lokas_transfers?user_id=eq."+curUser.id+"&select=*&order=created_at.desc") || []; } catch(e) {} section.style.display = "block"; if (myCars.length) { try { var carIds = myCars.map(function(c){ return c.id; }); carBks = await api("/rest/v1/lokas_car_bookings?car_id=in.("+carIds.join(",")+")"+"&select=*&order=created_at.desc&limit=30") || []; } catch(e) {} } if (myTransfers.length) { try { var trIds = myTransfers.map(function(t){ return t.id; }); trBks = await api("/rest/v1/lokas_transfer_bookings?transfer_id=in.("+trIds.join(",")+")"+"&select=*&order=created_at.desc&limit=30") || []; } catch(e) {} } // KPIs var totalRev = carBks.filter(function(b){ return b.statut==="confirmed"||b.statut==="completed"; }).reduce(function(t,b){ return t+(b.acompte||0); },0) + trBks.filter(function(b){ return b.statut==="confirmed"||b.statut==="completed"; }).reduce(function(t,b){ return t+(b.total_price||0); },0); var pending = carBks.filter(function(b){ return b.statut==="pending"; }).length + trBks.filter(function(b){ return b.statut==="pending"; }).length; var dkEl = document.getElementById("driver-kpis"); if (dkEl) dkEl.innerHTML = '
'+(myCars.length+myTransfers.length)+'
Services
'+ '
'+pending+'
En attente
'+ '
'+fmtPrice(totalRev)+'
Revenus
'; // Mes véhicules var catIco = { berline:"🚗", suv:"🚙", minibus:"🚐", luxe:"💎", utilitaire:"🚚" }; var carsEl = document.getElementById("my-cars-list"); if (carsEl) carsEl.innerHTML = myCars.length ? myCars.map(function(c) { return '
'+ '
'+(catIco[c.category]||"🚗")+'
'+ '
'+ '
'+escHtml(c.name||"")+'
'+ '
'+escHtml(c.city||"")+'
'+ '
'+fmtPrice(c.price_per_day||0)+'/jour
'+ '
'+ '
'+ ''+(c.statut==="active"?"✅ Actif":"⏸ Pause")+''+ ''+ ''+ '
'+ '
'; }).join("") : '
Aucun véhicule —
'; // Mes transferts var routeL = { aeroport:"Aéroport", dakar:"Dakar", saly:"Saly", "saint-louis":"St-Louis", mbour:"Mbour", thies:"Thiès" }; var trEl = document.getElementById("my-transfers-list"); if (trEl) trEl.innerHTML = myTransfers.length ? myTransfers.map(function(t) { return '
'+ '
✈️
'+ '
'+ '
'+(routeL[t.route_from]||t.route_from)+' → '+(routeL[t.route_to]||t.route_to)+'
'+ '
'+(t.max_pax||"—")+' pax · '+(t.duration||"—")+'
'+ '
'+fmtPrice(t.price||0)+' fixe
'+ '
'+ '
'+ ''+(t.statut==="active"?"✅ Actif":"⏸ Pause")+''+ ''+ ''+ '
'+ '
'; }).join("") : '
Aucun service —
'; // Réservations voitures var cbEl = document.getElementById("my-car-bookings"); if (cbEl) cbEl.innerHTML = carBks.length ? carBks.map(function(b){ return renderDriverBkRow(b,"car"); }).join("") : '
📭 Aucune réservation voiture
'; // Réservations transferts var tbEl = document.getElementById("my-tr-bookings"); if (tbEl) tbEl.innerHTML = trBks.length ? trBks.map(function(b){ return renderDriverBkRow(b,"transfer"); }).join("") : '
📭 Aucune réservation transfert
'; } function renderDriverBkRow(b, type) { var sBg = { pending:"#FEF3C7", confirmed:"var(--gnL)", cancelled:"var(--rdL)", completed:"var(--blueL)", rejected:"var(--rdL)" }; var sLbl = { pending:"⏳ En attente", confirmed:"✅ Confirmée", cancelled:"❌ Annulée", completed:"🏁 Terminée", rejected:"🚫 Refusée" }; var dateInfo = type === "car" ? "📅 "+b.pickup_date+" → "+b.return_date+" ("+b.days+" j)" : "📅 "+b.pickup_date+" "+(b.pickup_time||"")+(b.flight_number?" · ✈️ "+b.flight_number:""); var priceInfo = type === "car" ? fmtPrice(b.total_price||0)+" · Acompte: "+fmtPrice(b.acompte||0) : fmtPrice(b.total_price||0); var acts = b.statut==="pending" ? ''+ '' : b.statut==="confirmed" ? '' : ''; var waUrl = b.guest_phone ? "https://wa.me/"+b.guest_phone.replace(/\D/g,"") : ""; return '
'+ '
'+ '
'+ '
'+escHtml(b.guest_name||"Client")+'
'+ '
📞 '+(b.guest_phone||"—")+'
'+ '
'+dateInfo+'
'+ '
💰 '+priceInfo+'
'+ (b.message?'
"'+escHtml(b.message)+'"
':'')+ (b.passengers?'
👥 '+b.passengers+' passager'+(b.passengers>1?"s":"")+'
':'')+ '
'+ ''+(sLbl[b.statut]||b.statut)+''+ '
'+ '
'+ acts+(waUrl?'':'')+ '
'+ '
'; } async function handleDriverBooking(id, statut, type) { var table = type==="car" ? "lokas_car_bookings" : "lokas_transfer_bookings"; try { await api("/rest/v1/"+table+"?id=eq."+id, {method:"PATCH",headers:{"Prefer":"return=minimal"},body:JSON.stringify({statut:statut})}); var labels = {confirmed:"✅ Acceptée",rejected:"🚫 Refusée",completed:"🏁 Terminée"}; showToast(labels[statut]||statut,"success"); loadDriverDash(); } catch(e) { showToast("Erreur","error"); } } async function toggleCarStatut(id, cur) { var next = cur==="active" ? "inactive" : "active"; try { await api("/rest/v1/lokas_cars?id=eq."+id,{method:"PATCH",headers:{"Prefer":"return=minimal"},body:JSON.stringify({statut:next})}); CAR_DATA=[]; showToast(next==="active"?"Activé ✅":"Mis en pause","success"); loadDriverDash(); } catch(e){ showToast("Erreur","error"); } } async function deleteCar(id) { if (!confirm("Supprimer ce véhicule ?")) return; try { await api("/rest/v1/lokas_cars?id=eq."+id,{method:"DELETE"}); CAR_DATA=[]; showToast("Supprimé","success"); loadDriverDash(); } catch(e){ showToast("Erreur","error"); } } async function toggleTransferStatut(id, cur) { var next = cur==="active" ? "inactive" : "active"; try { await api("/rest/v1/lokas_transfers?id=eq."+id,{method:"PATCH",headers:{"Prefer":"return=minimal"},body:JSON.stringify({statut:next})}); TRANSFER_DATA=[]; showToast(next==="active"?"Activé ✅":"Mis en pause","success"); loadDriverDash(); } catch(e){ showToast("Erreur","error"); } } async function deleteTransfer(id) { if (!confirm("Supprimer ce service ?")) return; try { await api("/rest/v1/lokas_transfers?id=eq."+id,{method:"DELETE"}); TRANSFER_DATA=[]; showToast("Supprimé","success"); loadDriverDash(); } catch(e){ showToast("Erreur","error"); } } // ════════════════════════════════════════