// ============================================================ // PSIS ALTO TIETÊ — App.jsx // Dados: Google Sheets reais (psicólogos + sublocação) // ============================================================ // // ⚠️ AÇÃO NECESSÁRIA — PLANILHA DE PSICÓLOGOS: // A planilha de psicólogos precisa estar publicada publicamente. // Abra: https://docs.google.com/spreadsheets/d/1n8cmjG7piny-8qwd50uKt0idw2o4ewKSi4PJA0Bx3hI // Depois: Arquivo → Compartilhar → Publicar na web → CSV → Publicar // // A planilha de sublocação já está publicada ✓ // // ============================================================ import { useState, useEffect, useCallback } from "react"; // ── URLs DAS PLANILHAS ────────────────────────────────────── const SHEET_PSICOLOGOS_URL = "https://docs.google.com/spreadsheets/d/1n8cmjG7piny-8qwd50uKt0idw2o4ewKSi4PJA0Bx3hI/export?format=csv&gid=1960401861"; const SHEET_SUBLOCACAO_URL = "https://docs.google.com/spreadsheets/d/e/2PACX-1vSl-NGbpmFQ37slkRG4xyIAnwOS_G7b2vvrWwe601S5MWt7Y4Ff9re8E5eZ8YqGhyQhJYZHrZhTjNJd/pub?output=csv"; // ── NORMALIZAÇÃO ──────────────────────────────────────────── function norm(s) { return (s || "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").trim(); } // Encontra coluna pelo nome parcial function findCol(headers, keyword) { return headers.find((h) => norm(h).includes(norm(keyword))) || ""; } // Limpa telefone → só dígitos com DDI 55 function cleanPhone(raw) { const digits = (raw || "").replace(/\D/g, ""); if (!digits) return ""; if (digits.startsWith("55") && digits.length >= 12) return digits; if (digits.length >= 10) return "55" + digits; return digits; } // Formata para exibição: 11 99999-9999 function formatPhone(raw) { const digits = (raw || "").replace(/\D/g, ""); if (digits.length === 11) return `${digits.slice(0,2)} ${digits.slice(2,7)}-${digits.slice(7)}`; if (digits.length === 10) return `${digits.slice(0,2)} ${digits.slice(2,6)}-${digits.slice(6)}`; return raw || ""; } // Normaliza público: Ambos → Adulto e Infantil function normPublico(raw) { const v = norm(raw); if (!v) return ""; if (v === "ambos" || v.includes("ambos")) return "Adulto e Infantil"; if (v.includes("adulto") && v.includes("infantil")) return "Adulto e Infantil"; if (v.includes("infantil")) return "Infantil"; if (v.includes("adulto")) return "Adulto"; return raw; } // ── CSV PARSER ────────────────────────────────────────────── function parseCSV(text) { // Divide respeitando campos entre aspas function splitLine(line) { const result = []; let cur = "", inQuote = false; for (let i = 0; i < line.length; i++) { const ch = line[i]; if (ch === '"') { inQuote = !inQuote; continue; } if (ch === "," && !inQuote) { result.push(cur.trim()); cur = ""; continue; } cur += ch; } result.push(cur.trim()); return result; } const lines = text.trim().split(/\r?\n/); if (lines.length < 2) return []; const headers = splitLine(lines[0]); return lines.slice(1) .filter((l) => l.trim()) .map((line, idx) => { const cols = splitLine(line); const obj = { _id: idx + 1 }; headers.forEach((h, i) => { obj[h] = (cols[i] || "").trim(); }); return obj; }); } // ── MAPEADOR DE PSICÓLOGOS ────────────────────────────────── // Colunas reais: Nome | Telefone de Contato | Abordagem | Cidade | Público function mapPsicologos(rows) { if (!rows.length) return []; const headers = Object.keys(rows[0]).filter((k) => k !== "_id"); const colNome = findCol(headers, "nome"); const colTel = findCol(headers, "telefone"); const colAbord = findCol(headers, "abordagem"); const colCidade = findCol(headers, "cidade"); const colPublico = findCol(headers, "publico") || findCol(headers, "publico"); const colOnline = findCol(headers, "online"); const colSocial = findCol(headers, "social"); const colSuperv = findCol(headers, "supervis"); return rows.map((row, i) => { const telRaw = row[colTel] || ""; return { id: i + 1, nome: (row[colNome] || "").trim(), abordagem: (row[colAbord] || "").trim(), publico: normPublico(row[colPublico] || ""), cidade: (row[colCidade] || "").trim(), telefone: formatPhone(telRaw), whatsapp: cleanPhone(telRaw), online: (row[colOnline] || ""), social: (row[colSocial] || ""), supervisao:(row[colSuperv] || ""), }; }).filter((p) => p.nome); } // ── MAPEADOR DE SUBLOCAÇÃO ────────────────────────────────── // Colunas esperadas: nome | referencia | endereco | contato | whatsapp | modalidade | maps function mapSalas(rows) { if (!rows.length) return []; const headers = Object.keys(rows[0]).filter((k) => k !== "_id"); const colNome = findCol(headers, "nome") || headers[0]; const colRef = findCol(headers, "referencia") || findCol(headers, "ref"); const colEnd = findCol(headers, "endereco") || findCol(headers, "end"); const colCont = findCol(headers, "contato") || findCol(headers, "telefone"); const colWa = findCol(headers, "whatsapp") || findCol(headers, "wa"); const colMod = findCol(headers, "modalidade") || findCol(headers, "mod"); const colMaps = findCol(headers, "maps") || findCol(headers, "map"); return rows.map((row, i) => { const contRaw = row[colCont] || row[colWa] || ""; return { id: i + 1, nome: (row[colNome] || "").trim(), referencia: (row[colRef] || "").trim(), endereco: (row[colEnd] || "").trim(), contato: formatPhone(contRaw), whatsapp: cleanPhone(row[colWa] || contRaw), modalidade: (row[colMod] || "Hora Avulsa").trim(), maps: (row[colMaps] || "").trim(), }; }).filter((s) => s.nome); } // ── SINÔNIMOS ──────────────────────────────────────────────── const SYNONYMS = { tcc: ["tcc","cognitivo","cognitivo-comportamental","terapia cognitiva","comportamental","comportamental cognitiva"], act: ["act","aceitacao","compromisso","contextual","contextuais"], dbt: ["dbt","dialetica","dialetico"], fap: ["fap","analitica funcional","psicoterapia analitica"], psic: ["psicanalise","psicanalitica","lacaniana"], esquema: ["esquema","young"], fenomeno: ["fenomenologica","fenomenologia","existencial","existencial"], humanista:["humanista","humanismo"], mogi: ["mogi","mogi das cruzes"], suzano: ["suzano"], poa: ["poa"], itaqua: ["itaquaquecetuba","itaqua"], adulto: ["adulto","adultos"], infantil: ["infantil","crianca","criancas"], online: ["online"], social: ["valor social","social"], superv: ["supervisao","supervisão"], }; function matches(item, query) { const q = norm(query); if (!q) return false; const haystack = [item.nome, item.abordagem, item.publico, item.cidade, item.online, item.social].map(norm).join(" "); if (haystack.includes(q)) return true; for (const group of Object.values(SYNONYMS)) { if (group.some((s) => s.includes(q) || q.includes(s))) { if (group.some((s) => haystack.includes(s))) return true; } } return false; } function initials(nome) { const parts = (nome || "").replace(/^(Dr|Dra|Dr\.|Dra\.)\.?\s+/i,"").split(" "); return ((parts[0]?.[0]||"")+(parts[1]?.[0]||"")).toUpperCase(); } // ── ESTILOS ────────────────────────────────────────────────── const CSS = ` @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap'); *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} html{scroll-behavior:smooth} body{font-family:'Plus Jakarta Sans',sans-serif;background:#faf8ff;color:#1e0a3c;font-size:15px;line-height:1.6} :root{ --v950:#1e0a3c;--v900:#2d1260;--v800:#3d1a7e;--v700:#5b21b6;--v600:#7c3aed; --v500:#8b5cf6;--v400:#a78bfa;--v300:#c4b5fd;--v200:#ddd6fe;--v100:#ede9fe;--v50:#f5f3ff; --bg:#faf8ff;--text:#1e0a3c;--sec:#6b4f8a;--muted:#9e86b8; --green:#22c55e;--green-d:#16a34a; --border:rgba(124,58,237,0.12); --sh-sm:0 1px 4px rgba(93,30,180,0.08); --sh-md:0 4px 16px rgba(93,30,180,0.12); --sh-lg:0 12px 40px rgba(93,30,180,0.18); } /* NAV */ .nav{position:sticky;top:0;z-index:100;background:rgba(255,255,255,0.9);backdrop-filter:blur(16px);border-bottom:1px solid var(--border);padding:0 24px;display:flex;align-items:center;justify-content:space-between;height:64px} .nav-logo{display:flex;align-items:center;gap:10px;cursor:pointer} .nav-logo-icon{width:36px;height:36px;border-radius:10px;background:linear-gradient(135deg,var(--v700),var(--v500));display:flex;align-items:center;justify-content:center;font-size:18px} .nav-logo-text{font-weight:800;font-size:17px;letter-spacing:-0.3px;color:var(--text)} .nav-logo-text span{color:var(--v600)} .nav-links{display:flex;align-items:center;gap:4px} .nav-link{padding:7px 14px;border-radius:8px;font-weight:600;font-size:14px;cursor:pointer;border:none;background:none;color:var(--sec);transition:all .18s;font-family:inherit} .nav-link:hover{background:var(--v100);color:var(--v700)} .nav-link.active{background:var(--v600);color:#fff} .hamburger{display:none;background:none;border:none;cursor:pointer;padding:6px} .mob-menu{display:none;flex-direction:column;position:absolute;top:64px;left:0;right:0;background:#fff;border-bottom:1px solid var(--border);padding:12px 16px;gap:4px;z-index:99} .mob-menu.open{display:flex} /* HERO */ .hero{background:linear-gradient(145deg,var(--v950) 0%,var(--v800) 55%,var(--v600) 100%);position:relative;overflow:hidden;padding:88px 24px 80px;text-align:center} .blob{position:absolute;border-radius:50%;filter:blur(60px);opacity:.22} .b1{width:420px;height:420px;background:#a78bfa;top:-120px;left:-80px} .b2{width:320px;height:320px;background:#c4b5fd;bottom:-80px;right:-60px} .hero-badge{display:inline-flex;align-items:center;gap:6px;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.2);padding:5px 14px;border-radius:999px;font-size:12px;font-weight:600;color:var(--v200);letter-spacing:.5px;text-transform:uppercase;margin-bottom:22px} .hero-title{font-size:clamp(36px,6vw,64px);font-weight:800;line-height:1.08;letter-spacing:-1.5px;color:#fff;margin-bottom:20px} .hero-title em{font-style:normal;color:var(--v300)} .hero-sub{font-size:clamp(15px,2vw,18px);color:rgba(255,255,255,.72);max-width:540px;margin:0 auto 36px;line-height:1.65} .hero-btns{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;position:relative} .btn-hp{background:#fff;color:var(--v800);font-weight:700;font-size:15px;padding:13px 28px;border-radius:14px;border:none;cursor:pointer;box-shadow:0 4px 20px rgba(0,0,0,.2);transition:all .2s;display:flex;align-items:center;gap:8px;font-family:inherit} .btn-hp:hover{transform:translateY(-2px);box-shadow:0 8px 28px rgba(0,0,0,.25)} .btn-hs{background:rgba(255,255,255,.12);color:#fff;border:1px solid rgba(255,255,255,.25);font-weight:600;font-size:15px;padding:13px 28px;border-radius:14px;cursor:pointer;transition:all .2s;font-family:inherit} .btn-hs:hover{background:rgba(255,255,255,.2)} /* FEATURES */ .features{padding:64px 24px;max-width:1000px;margin:0 auto} .feat-label{text-align:center;font-size:13px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:var(--v500);margin-bottom:10px} .feat-title{text-align:center;font-size:clamp(24px,4vw,36px);font-weight:800;letter-spacing:-0.8px;margin-bottom:40px} .feat-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px} .feat-card{background:#fff;border:1px solid var(--border);border-radius:20px;padding:28px 24px;box-shadow:var(--sh-sm);transition:box-shadow .2s,transform .2s} .feat-card:hover{box-shadow:var(--sh-md);transform:translateY(-3px)} .feat-icon{width:48px;height:48px;border-radius:14px;margin-bottom:16px;display:flex;align-items:center;justify-content:center;font-size:22px} .fi-p{background:var(--v100)}.fi-g{background:#dcfce7}.fi-l{background:var(--v50)} .feat-card h3{font-size:16px;font-weight:700;margin-bottom:8px} .feat-card p{font-size:14px;color:var(--sec);line-height:1.6} /* INNER */ .inner{max-width:860px;margin:0 auto;padding:48px 24px} .ph{margin-bottom:32px} .ph h1{font-size:clamp(22px,4vw,32px);font-weight:800;letter-spacing:-0.6px;margin-bottom:8px} .ph p{color:var(--sec);font-size:15px} /* SEARCH */ .sbox{background:#fff;border:2px solid var(--border);border-radius:28px;padding:6px 6px 6px 20px;display:flex;align-items:center;gap:10px;box-shadow:var(--sh-md);margin-bottom:16px;transition:border-color .2s} .sbox:focus-within{border-color:var(--v500)} .sbox input{flex:1;border:none;outline:none;font-family:inherit;font-size:15px;color:var(--text);background:transparent;padding:10px 0} .sbox input::placeholder{color:var(--muted)} .btn-search{background:linear-gradient(135deg,var(--v700),var(--v500));color:#fff;border:none;border-radius:20px;padding:12px 24px;font-family:inherit;font-size:14px;font-weight:700;cursor:pointer;transition:opacity .2s;white-space:nowrap;display:flex;align-items:center;gap:8px} .btn-search:hover{opacity:.88} .chips{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:28px} .chip{background:var(--v100);color:var(--v700);border:1px solid var(--v200);border-radius:999px;padding:5px 14px;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s;border:none;font-family:inherit} .chip:hover{background:var(--v200)} /* TOOLBAR */ .toolbar{background:#fff;border:1px solid var(--border);border-radius:20px;padding:12px 16px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;box-shadow:var(--sh-sm);margin-bottom:20px} .tb-l{display:flex;align-items:center;gap:10px;flex:1} .tb-r{display:flex;gap:8px;flex-wrap:wrap} .badge{background:var(--v100);color:var(--v700);font-size:13px;font-weight:700;padding:4px 12px;border-radius:999px;white-space:nowrap} .bsm{font-family:inherit;font-size:13px;font-weight:600;padding:7px 14px;border-radius:8px;cursor:pointer;border:1px solid transparent;transition:all .15s;display:flex;align-items:center;gap:6px;white-space:nowrap} .bo{background:none;border-color:var(--border);color:var(--sec)}.bo:hover{border-color:var(--v400);color:var(--v700);background:var(--v50)} .bg{background:#dcfce7;color:#15803d;border-color:#bbf7d0}.bg:hover{background:#bbf7d0} /* CHECKBOX */ .cb{width:18px;height:18px;border:2px solid var(--v400);border-radius:5px;display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;transition:all .15s;background:#fff} .cb.on{background:var(--v600);border-color:var(--v600)} /* PSI CARD */ .cl{display:flex;flex-direction:column;gap:14px} .pc{background:#fff;border:1px solid var(--border);border-radius:20px;padding:20px;box-shadow:var(--sh-sm);display:flex;gap:16px;align-items:flex-start;transition:box-shadow .2s} .pc:hover{box-shadow:var(--sh-md)} .pc.sel{border-color:var(--v400);background:var(--v50)} .av{width:52px;height:52px;border-radius:14px;flex-shrink:0;background:linear-gradient(135deg,var(--v700),var(--v400));display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;color:#fff;letter-spacing:-1px} .pb{flex:1;min-width:0} .pn{font-size:16px;font-weight:800;margin-bottom:10px;letter-spacing:-0.2px} .ptags{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px} .tag{display:flex;align-items:center;gap:5px;font-size:12px;font-weight:600;padding:4px 10px;border-radius:999px;background:var(--v50);color:var(--v700);border:1px solid var(--v100)} .tag-g{background:#f0fdf4;color:#16a34a;border-color:#bbf7d0} .pinfo{display:flex;flex-wrap:wrap;gap:10px;font-size:13px;color:var(--sec)} .ii{display:flex;align-items:center;gap:5px} .pa{display:flex;flex-direction:column;align-items:flex-end;gap:8px;flex-shrink:0} /* BUTTONS */ .bwa{background:var(--green);color:#fff;font-family:inherit;font-size:13px;font-weight:700;padding:9px 16px;border-radius:8px;border:none;cursor:pointer;display:flex;align-items:center;gap:7px;box-shadow:0 2px 8px rgba(34,197,94,.28);transition:all .2s;white-space:nowrap} .bwa:hover{background:var(--green-d);transform:translateY(-1px)} .bmaps{background:var(--v100);color:var(--v700);font-family:inherit;font-size:12px;font-weight:700;padding:8px 14px;border-radius:8px;border:1px solid var(--v200);cursor:pointer;display:flex;align-items:center;gap:6px;transition:all .2s;white-space:nowrap;text-decoration:none} .bmaps:hover{background:var(--v200)} /* SUB */ .sc{background:#fff;border:1px solid var(--border);border-radius:20px;padding:20px;box-shadow:var(--sh-sm);display:flex;gap:16px;align-items:flex-start;transition:box-shadow .2s} .sc:hover{box-shadow:var(--sh-md)} .sc.sel{border-color:var(--v400);background:var(--v50)} .si{width:48px;height:48px;border-radius:12px;flex-shrink:0;background:var(--v100);display:flex;align-items:center;justify-content:center;font-size:20px} .sb{flex:1;min-width:0} .sn{font-size:16px;font-weight:800;margin-bottom:3px} .sr{font-size:12px;color:var(--muted);margin-bottom:10px} .sinfo{display:flex;flex-direction:column;gap:5px;font-size:13px;color:var(--sec)} .sbadge{display:inline-flex;align-items:center;background:var(--v100);color:var(--v700);font-size:12px;font-weight:600;padding:3px 10px;border-radius:999px;border:1px solid var(--v200);margin-top:8px} .sa{display:flex;flex-direction:column;gap:8px;align-items:flex-end;flex-shrink:0} /* EMPTY / SKELETON / TOAST */ .empty{text-align:center;padding:72px 24px} .ei{font-size:56px;margin-bottom:16px;opacity:.6} .empty h3{font-size:20px;font-weight:700;margin-bottom:8px} .empty p{color:var(--sec);font-size:15px;max-width:360px;margin:0 auto} .skel{background:linear-gradient(90deg,#f0ebff 25%,#e6dfff 50%,#f0ebff 75%);background-size:200% 100%;animation:shimmer 1.4s infinite;border-radius:12px;height:100px;margin-bottom:14px} @keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}} .toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:var(--v900);color:#fff;padding:12px 24px;border-radius:14px;font-size:14px;font-weight:600;box-shadow:var(--sh-lg);z-index:999;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .3s} .toast.show{opacity:1} /* FOOTER */ .footer{background:var(--v950);color:rgba(255,255,255,.5);text-align:center;padding:28px 24px;font-size:13px;margin-top:80px} .footer a{color:var(--v300)} /* ALERT */ .alert{background:#fef9c3;border:1px solid #fde047;border-radius:12px;padding:14px 18px;font-size:13px;color:#713f12;margin-bottom:24px;display:flex;align-items:flex-start;gap:10px} /* RESPONSIVE */ @media(max-width:680px){ .feat-grid{grid-template-columns:1fr} .pc{flex-direction:column} .pa{flex-direction:row;align-items:flex-start;width:100%} .bwa{flex:1;justify-content:center} .sc{flex-direction:column} .sa{flex-direction:row;width:100%} .bmaps{flex:1;justify-content:center} .nav-links{display:none} .hamburger{display:flex} .toolbar{flex-direction:column;align-items:flex-start} .tb-r{width:100%} } `; // ── ICONS ──────────────────────────────────────────────────── const ISearch = () => ; const IPhone = () => ; const IPin = () => ; const ICopy = () => ; const ISend = () => ; const IWa = () => ; // ── CHECKBOX ───────────────────────────────────────────────── function CB({ checked, onClick }) { return (
Conectamos pessoas da região com profissionais de psicologia qualificados. Também divulgamos salas para sublocação clínica.
Como funciona
{f.desc}
Busque por abordagem, cidade, público atendido ou nome.
Tente "TCC", "Mogi", "Psicanálise" ou "Online".
Digite uma abordagem, cidade ou público para encontrar psicólogas(os).
Selecione uma ou mais salas e envie mensagem pelo WhatsApp.
As salas aparecerão aqui assim que forem adicionadas à planilha.