<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
<title>Visitas</title>
<!-- Tailwind -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: { sans: ['Inter','system-ui','ui-sans-serif','Segoe UI','Roboto','Arial'] },
colors: {
base: { 900:'#0b0f14', 850:'#0f141b', 800:'#121922', 700:'#1a2330' },
brand:{ 500:'#2563eb', 400:'#3b82f6' },
ok: { 500:'#16a34a' },
warn: { 500:'#f59e0b' },
info: { 500:'#1d4ed8' }
},
boxShadow:{ card:'0 1px 1px rgba(0,0,0,.25), 0 12px 28px rgba(0,0,0,.35)' }
}
}
}
</script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
*{ -webkit-tap-highlight-color: transparent; }
.no-scrollbar::-webkit-scrollbar{ display:none; }
.no-scrollbar{ -ms-overflow-style: none; scrollbar-width: none; }
.glass{ background: rgba(20,24,33,.55); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
.map-stub{ background: radial-gradient(1200px 600px at 50% -10%, #1b2740 0%, #0f141b 45%, #0b0f14 100%); }
</style>
</head>
<body class="bg-base-900 text-zinc-100 font-sans">
<div id="app" class="mx-auto max-w-md min-h-[100dvh] flex flex-col">
<!-- HEADER -->
<link rel="icon" href="/favicon.ico">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
<meta name="theme-color" content="#111111">
<!-- melhora a experiência no iPhone -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Nome do App">
<header class="sticky top-0 z-40 bg-base-900/90 backdrop-blur border-b border-white/5">
<div class="px-5 py-4 flex items-center justify-between">
<h1 class="text-xl font-semibold tracking-tight">Visitas</h1>
<button id="btnAdd" class="h-10 w-10 grid place-items-center rounded-2xl bg-brand-500 text-white shadow-card active:scale-95">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-width="1.8" d="M12 5v14M5 12h14"/></svg>
</button>
</div>
<!-- Filtros (pílulas) -->
<div class="px-4 pb-3">
<div class="flex gap-2 overflow-x-auto no-scrollbar">
<button id="pillDate" class="px-4 py-2 rounded-full bg-base-800 border border-white/10 text-sm flex items-center gap-2">
Data
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 opacity-70" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m6 9 6 6 6-6"/></svg>
</button>
<button id="pillCity" class="px-4 py-2 rounded-full bg-base-800 border border-white/10 text-sm flex items-center gap-2">
Cidade
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 opacity-70" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m6 9 6 6 6-6"/></svg>
</button>
<button id="pillStatus" class="px-4 py-2 rounded-full bg-base-800 border border-white/10 text-sm flex items-center gap-2">
Status
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 opacity-70" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m6 9 6 6 6-6"/></svg>
</button>
</div>
</div>
</header>
<!-- CONTENT -->
<main class="flex-1 px-4 pb-28 pt-2">
<!-- LISTA -->
<section id="tab-list" class="space-y-4"></section>
<!-- FORMULÁRIO -->
<section id="tab-form" class="hidden">
<form id="quickForm" class="space-y-3 glass rounded-2xl p-4 border border-white/10">
<div>
<label class="text-xs text-zinc-400">Data</label>
<input id="f_date" type="date" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm">
</div>
<div>
<label class="text-xs text-zinc-400">Cidade</label>
<input id="f_city" placeholder="Ex: São Paulo" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm">
</div>
<div>
<label class="text-xs text-zinc-400">Cliente/Empresa</label>
<input id="f_client" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm">
</div>
<div>
<label class="text-xs text-zinc-400">Status</label>
<select id="f_status" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm">
<option>Agendado</option>
<option>Pendente</option>
<option>Concluído</option>
</select>
</div>
<div>
<label class="text-xs text-zinc-400">Endereço</label>
<input id="f_address" placeholder="Rua, número, bairro" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm">
<div class="mt-2 h-32 w-full rounded-xl overflow-hidden border border-white/10 map-stub"></div>
</div>
<div>
<label class="text-xs text-zinc-400">Objetivo da Visita</label>
<textarea id="f_goal" rows="3" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm"></textarea>
</div>
<div>
<label class="text-xs text-zinc-400">Observações Internas</label>
<textarea id="f_notes" rows="3" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm"></textarea>
</div>
<div class="flex items-center justify-between">
<label class="flex items-center gap-2 text-sm text-zinc-300"><input id="f_priority" type="checkbox" class="rounded"> Prioridade</label>
<button class="px-4 py-2 rounded-xl bg-brand-500 text-white font-medium">Salvar</button>
</div>
</form>
</section>
<!-- DETALHES -->
<section id="tab-details" class="hidden space-y-3">
<div class="glass rounded-2xl p-4 border border-white/10">
<div class="text-xs text-brand-400">Cliente</div>
<div id="d_client" class="text-base font-semibold">—</div>
</div>
<div class="glass rounded-2xl p-4 border border-white/10">
<div class="text-xs text-brand-400">Data</div>
<div id="d_date" class="text-base">—</div>
</div>
<div class="glass rounded-2xl p-4 border border-white/10">
<div class="text-xs text-brand-400">Status</div>
<div id="d_status" class="text-base mt-1">—</div>
</div>
<div class="glass rounded-2xl p-4 border border-white/10">
<div class="text-xs text-brand-400">Endereço</div>
<div id="d_address" class="text-base mb-2">—</div>
<div class="h-40 w-full rounded-xl overflow-hidden border border-white/10 map-stub"></div>
</div>
<div class="glass rounded-2xl p-4 border border-white/10">
<div class="text-xs text-brand-400">Objetivo da visita</div>
<div id="d_goal" class="text-base">—</div>
</div>
<div class="glass rounded-2xl p-4 border border-white/10">
<div class="text-xs text-brand-400">Observações</div>
<div id="d_notes" class="text-base">—</div>
</div>
<div class="flex gap-2">
<button id="btnEdit" class="flex-1 h-11 rounded-xl border border-white/10 bg-white/5">Editar</button>
<button id="btnFinish" class="flex-1 h-11 rounded-xl bg-brand-500 text-white">Finalizar</button>
</div>
<button id="btnBack" class="w-full h-11 rounded-xl border border-white/10 bg-white/5">Voltar</button>
</section>
</main>
<!-- TAB BAR -->
<nav class="fixed bottom-0 left-0 right-0 mx-auto max-w-md bg-base-900/95 backdrop-blur border-t border-white/5">
<div class="grid grid-cols-3 px-4 py-2">
<button data-tab="form" class="tab h-14 rounded-xl flex flex-col items-center justify-center gap-1 text-zinc-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M4 7h16M4 12h16M4 17h10"/></svg>
<span class="text-[11px]">Formulário</span>
</button>
<button data-tab="list" class="tab h-14 rounded-xl flex flex-col items-center justify-center gap-1 text-brand-400 bg-white/5">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
<span class="text-[11px]">Lista</span>
</button>
<button data-tab="details" class="tab h-14 rounded-xl flex flex-col items-center justify-center gap-1 text-zinc-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="9"/><path d="M12 8v8M8 12h8"/></svg>
<span class="text-[11px]">Detalhes</span>
</button>
</div>
</nav>
</div>
<!-- MODAL NOVA VISITA -->
<div id="modal" class="fixed inset-0 hidden z-50">
<div class="absolute inset-0 bg-black/50"></div>
<div class="absolute inset-x-0 bottom-0 mx-auto max-w-md rounded-t-3xl bg-base-900 border-t border-white/10 p-5">
<div class="flex items-center justify-between mb-3">
<h3 class="text-lg font-semibold">Nova Visita</h3>
<button id="mClose" class="h-10 w-10 grid place-items-center rounded-xl bg-white/5">✕</button>
</div>
<form id="modalForm" class="space-y-3">
<div>
<label class="text-xs text-zinc-400">Data</label>
<input id="m_date" type="date" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm">
</div>
<div>
<label class="text-xs text-zinc-400">Cidade</label>
<input id="m_city" placeholder="Ex: São Paulo" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm">
</div>
<div>
<label class="text-xs text-zinc-400">Cliente/Empresa</label>
<input id="m_client" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm">
</div>
<div>
<label class="text-xs text-zinc-400">Status</label>
<select id="m_status" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm">
<option>Agendado</option>
<option>Pendente</option>
<option>Concluído</option>
</select>
</div>
<div>
<label class="text-xs text-zinc-400">Local (Endereço)</label>
<input id="m_address" placeholder="Rua, número, bairro..." class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm">
<div class="mt-2 h-36 w-full rounded-xl overflow-hidden border border-white/10 map-stub"></div>
</div>
<div>
<label class="text-xs text-zinc-400">Objetivo da Visita</label>
<textarea id="m_goal" rows="3" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm" placeholder="Descreva o propósito principal da visita..."></textarea>
</div>
<div>
<label class="text-xs text-zinc-400">Observações Internas</label>
<textarea id="m_notes" rows="2" class="w-full mt-1 px-3 py-2 rounded-xl bg-base-850 border border-white/10 text-sm" placeholder="Notas para uso interno da equipe..."></textarea>
</div>
<div class="flex items-center justify-between pt-1">
<label class="flex items-center gap-2 text-sm text-zinc-300"><input id="m_priority" type="checkbox" class="rounded"> Prioridade</label>
<button class="px-4 py-2 rounded-xl bg-brand-500 text-white font-medium">Finalizar</button>
</div>
</form>
</div>
</div>
<script>
// --------- STATE / STORAGE ----------
const STORAGE_KEY = 'visitas_mobile_protov1';
const $ = (s, r=document) => r.querySelector(s);
const $$= (s, r=document) => [...r.querySelectorAll(s)];
const state = { tab:'list', selectedId:null, filters:{ date:'', city:'', status:'' } };
const sample = [
{id:uid(), name:'Lucas Oliveira', city:'Porto', date:'2024-03-20', status:'Agendado', address:'Rua das Flores, 123, Centro', goal:'Apresentar novo produto', notes:'Levar amostras', priority:false},
{id:uid(), name:'Isabela Santos', city:'Porto', date:'2024-03-20', status:'Concluído', address:'Av. Principal, 456, Bairro Novo', goal:'Reunião de acompanhamento', notes:'', priority:false},
{id:uid(), name:'Rafael Costa', city:'Valbom', date:'2024-03-21', status:'Pendente', address:'Rua da Paz, 789, Vila Verde', goal:'Negociação de contrato', notes:'', priority:true},
];
function uid(){ return 'id-'+Math.random().toString(36).slice(2,10); }
function load(){ try{ return JSON.parse(localStorage.getItem(STORAGE_KEY)||'[]'); }catch{ return []; } }
function save(v){ localStorage.setItem(STORAGE_KEY, JSON.stringify(v)); }
if(load().length===0) save(sample);
// --------- RENDER ----------
function render(){
// tabs
$$('.tab').forEach(el=>{
const active = el.getAttribute('data-tab')===state.tab;
el.classList.toggle('bg-white/5', active);
el.classList.toggle('text-brand-400', active);
el.classList.toggle('text-zinc-400', !active);
});
$('#tab-list').classList.toggle('hidden', state.tab!=='list');
$('#tab-form').classList.toggle('hidden', state.tab!=='form');
$('#tab-details').classList.toggle('hidden', state.tab!=='details');
if(state.tab==='list') renderList();
if(state.tab==='details') renderDetails();
}
function badge(status){
const map = {
'Agendado' : 'bg-info-500/15 text-info-500 border-info-500/30',
'Concluído': 'bg-ok-500/15 text-ok-500 border-ok-500/30',
'Pendente' : 'bg-warn-500/15 text-warn-500 border-warn-500/30'
};
const cls = map[status] || 'bg-white/10 text-zinc-300 border-white/10';
return `<span class="inline-block text-[13px] px-3 py-1 rounded-full border ${cls}">${status}</span>`;
}
function toBR(iso){ if(!iso) return '—'; const d = new Date(iso+'T00:00:00'); return isNaN(d)?iso:d.toLocaleDateString('pt-BR'); }
function esc(s){ return String(s).replaceAll('&','&').replaceAll('<','<').replaceAll('>','>'); }
function applyFilters(list){
const f = state.filters;
return list.filter(i=>{
if(f.date && i.date !== f.date) return false;
if(f.city && i.city.toLowerCase() !== f.city.toLowerCase()) return false;
if(f.status && i.status !== f.status) return false;
return true;
});
}
function renderList(){
const wrap = $('#tab-list');
wrap.innerHTML = '';
const items = applyFilters(load());
items.forEach(item=>{
const card = document.createElement('button');
card.className = 'w-full text-left rounded-3xl bg-base-800/70 border border-white/10 shadow-card px-5 py-4 active:scale-[.995] transition';
card.innerHTML = `
<div class="text-[17px] font-semibold">${esc(item.name)}</div>
<div class="mt-0.5 text-sm text-zinc-400">${esc(item.address)}</div>
<div class="mt-4 text-xs text-zinc-400">Objetivo</div>
<div class="text-base font-semibold">${esc(item.goal||'—')}</div>
<div class="mt-3">${badge(item.status)}</div>
`;
card.addEventListener('click', ()=>{ state.selectedId=item.id; state.tab='details'; render(); });
wrap.appendChild(card);
});
if(items.length===0){
wrap.innerHTML = `<div class="text-center text-zinc-400 py-8">Sem registros para o filtro atual.</div>`;
}
}
function renderDetails(){
const list = load();
const it = list.find(x=>x.id===state.selectedId) || list[0];
if(!it) return;
$('#d_client').textContent = it.name;
$('#d_date').textContent = toBR(it.date);
$('#d_status').innerHTML = badge(it.status);
$('#d_address').textContent = it.address || '—';
$('#d_goal').textContent = it.goal || '—';
$('#d_notes').textContent = it.notes || '—';
}
// --------- EVENTS ----------
// Tabs
$$('.tab').forEach(b=> b.addEventListener('click',()=>{ state.tab = b.getAttribute('data-tab'); render(); }));
// Quick form
$('#quickForm')?.addEventListener('submit', (e)=>{
e.preventDefault();
const list = load();
list.unshift({
id: uid(),
name: $('#f_client').value || '—',
city: $('#f_city').value || '',
date: $('#f_date').value || new Date().toISOString().slice(0,10),
status: $('#f_status').value || 'Pendente',
address: $('#f_address').value || '',
goal: $('#f_goal').value || '',
notes: $('#f_notes').value || '',
priority: $('#f_priority').checked
});
save(list);
e.target.reset();
state.tab='list'; render();
});
// Details buttons
$('#btnBack').addEventListener('click', ()=>{ state.tab='list'; render(); });
$('#btnFinish').addEventListener('click', ()=>{
const list = load();
const i = list.findIndex(x=>x.id===state.selectedId);
if(i>-1){ list[i].status='Concluído'; save(list); }
renderDetails();
});
$('#btnEdit').addEventListener('click', ()=>{
const it = load().find(x=>x.id===state.selectedId);
if(!it) return;
// Preenche modal para editar duplicando (rápido)
$('#m_client').value = it.name;
$('#m_city').value = it.city;
$('#m_date').value = it.date;
$('#m_status').value = it.status;
$('#m_address').value = it.address;
$('#m_goal').value = it.goal;
$('#m_notes').value = it.notes;
$('#m_priority').checked = !!it.priority;
$('#modal').classList.remove('hidden');
});
// Modal + / salvar
$('#btnAdd').addEventListener('click', ()=> $('#modal').classList.remove('hidden'));
$('#mClose').addEventListener('click', ()=> $('#modal').classList.add('hidden'));
$('#modalForm').addEventListener('submit', (e)=>{
e.preventDefault();
const list = load();
list.unshift({
id: uid(),
name: $('#m_client').value || '—',
city: $('#m_city').value || '',
date: $('#m_date').value || new Date().toISOString().slice(0,10),
status: $('#m_status').value || 'Pendente',
address: $('#m_address').value || '',
goal: $('#m_goal').value || '',
notes: $('#m_notes').value || '',
priority: $('#m_priority').checked
});
save(list);
$('#modal').classList.add('hidden');
state.tab='list'; render();
});
// Filtros (pílulas simples)
$('#pillDate').addEventListener('click', async ()=>{
const v = prompt('Filtrar por data (AAAA-MM-DD). Deixe vazio para limpar:', state.filters.date || '');
state.filters.date = (v||'').trim();
render();
});
$('#pillCity').addEventListener('click', async ()=>{
const v = prompt('Filtrar por cidade. Deixe vazio para limpar:', state.filters.city || '');
state.filters.city = (v||'').trim();
render();
});
$('#pillStatus').addEventListener('click', async ()=>{
const v = prompt('Status: Agendado | Pendente | Concluído. Deixe vazio para limpar:', state.filters.status || '');
const ok = ['Agendado','Pendente','Concluído',''].includes((v||'').trim());
if(ok){ state.filters.status = (v||'').trim(); render(); }
else alert('Valor inválido.');
});
// start
render();
</script>
</body>
</html>