// data.jsx — Moliterni Motos: config da loja, estoque, persistência + helpers const STORE = { nome: "Moliterni Motos", whatsapp: "5551999477573", // só dígitos, formato internacional whatsappDisplay: "(51) 99947-7573", instagram: "moliterni.motos", instagramUrl: "https://www.instagram.com/moliterni.motos/", endereco: "Porto Alegre · RS", // ajuste para o endereço exato da loja horario: "Seg a Sex 9h–18h30 · Sáb 9h–13h", diasAtendimento: [1, 2, 3, 4, 5, 6], // 0=Dom ... 6=Sáb horarios: ["09:00", "10:00", "11:00", "14:00", "15:00", "16:00", "17:00", "18:00"], }; const ADMIN_CRED = { user: "admin", pass: "demo123" }; const LS_KEY = "moliterni_motos_v2"; // Pasta da API PHP (relativa à página do cliente). Se existir e o banco estiver // configurado, o painel usa o MySQL da Hostinger; senão cai no modo demo (localStorage). const API_BASE = "api/"; // ---- helpers ---- const formatBRL = (n) => Number(n || 0).toLocaleString("pt-BR", { style: "currency", currency: "BRL", maximumFractionDigits: 0 }); const formatKM = (n) => Number(n || 0).toLocaleString("pt-BR") + " km"; const uid = () => "m" + Date.now().toString(36) + Math.random().toString(36).slice(2, 6); const STATUS_META = { disponivel: { label: "Disponível", badge: null }, oferta: { label: "Oferta", badge: "oferta" }, reservado: { label: "Reservado", badge: "reservado" }, vendido: { label: "Vendido", badge: "vendido" }, }; // segmentos de moto (substitui a antiga categoria carro/moto) const SEGMENTOS = [ { id: "street", label: "Street" }, { id: "esportiva", label: "Esportiva" }, { id: "trail", label: "Trail" }, { id: "scooter", label: "Scooter" }, { id: "custom", label: "Custom" }, ]; const segLabel = (id) => (SEGMENTOS.find((s) => s.id === id) || {}).label || "Moto"; const CAMBIOS = ["Manual", "Automático", "CVT"]; const COMBUSTIVEIS = ["Gasolina", "Flex", "Elétrica"]; const OPCIONAIS_COMUNS = [ "ABS", "Freio CBS", "Partida elétrica", "Painel digital", "Faróis de LED", "Controle de tração", "Quickshifter", "Manopla aquecida", "Bolha / Para-brisa", "Baú / Bauleto", "Protetor de motor", "Slider", "Escapamento esportivo", "Pneus novos", "Manual e chave reserva", "IPVA pago", ]; // ---- estoque inicial ---- // Fotos reais em fotos/ — nome do arquivo original = modelo · ano · valor const SEED = [ { id: "m_seed1", categoria: "street", marca: "Yamaha", modelo: "MT-09", ano: 2021, preco: 52900, precoAntigo: 0, km: 21800, cambio: "Manual", combustivel: "Gasolina", cor: "Preto", status: "disponivel", descricao: "MT-09 com o motor CP3 de 3 cilindros, 889cc de pura adrenalina e torque imediato. Naked agressiva, leve e absurdamente divertida. Revisada e impecável.", opcionais: ["ABS", "Controle de tração", "Quickshifter", "Painel digital", "Faróis de LED"], fotos: ["fotos/mt-09.png"], destaque: true, createdAt: 1718000000000, }, { id: "m_seed2", categoria: "trail", marca: "Yamaha", modelo: "XTZ 150 Crosser Z", ano: 2023, preco: 18900, precoAntigo: 0, km: 9400, cambio: "Manual", combustivel: "Flex", cor: "Vermelho", status: "disponivel", descricao: "Crosser 150 Z, a trail leve perfeita para cidade e estradas de terra. Econômica, robusta e com baixa quilometragem. Pronta para rodar.", opcionais: ["Freio CBS", "Partida elétrica", "Painel digital", "Faróis de LED"], fotos: ["fotos/xtz150-crosser-z.png"], destaque: false, createdAt: 1718100000000, }, { id: "m_seed3", categoria: "trail", marca: "BMW", modelo: "F 850 GS Premium", ano: 2019, preco: 50900, precoAntigo: 0, km: 31200, cambio: "Manual", combustivel: "Gasolina", cor: "Cinza", status: "disponivel", descricao: "F 850 GS na versão Premium, big trail bicilíndrica pronta para qualquer aventura. Pacote completo de eletrônicos e conforto. Conservadíssima.", opcionais: ["ABS", "Controle de tração", "Quickshifter", "Manopla aquecida", "Painel digital", "Faróis de LED"], fotos: ["fotos/bmw-f850gs-premium.png"], destaque: true, createdAt: 1718200000000, }, { id: "m_seed4", categoria: "scooter", marca: "Honda", modelo: "ADV 150", ano: 2024, preco: 23900, precoAntigo: 0, km: 4800, cambio: "CVT", combustivel: "Gasolina", cor: "Cinza", status: "disponivel", descricao: "ADV 150, o scooter aventureiro da Honda. Postura alta, suspensão reforçada e visual de mini big trail. Praticamente nova.", opcionais: ["ABS", "Partida elétrica", "Painel digital", "Faróis de LED"], fotos: ["fotos/adv-150.png"], destaque: false, createdAt: 1718300000000, }, { id: "m_seed5", categoria: "street", marca: "Kawasaki", modelo: "Z900", ano: 2022, preco: 53900, precoAntigo: 0, km: 13600, cambio: "Manual", combustivel: "Gasolina", cor: "Verde", status: "oferta", descricao: "Z900 Kawasaki, naked de 4 cilindros e 948cc com o verde icônico. Desempenho de sobra e visual Sugomi. Bem conservada e revisada.", opcionais: ["ABS", "Controle de tração", "Quickshifter", "Painel digital", "Faróis de LED"], fotos: ["fotos/z900.png"], destaque: true, createdAt: 1718400000000, }, { id: "m_seed6", categoria: "street", marca: "Suzuki", modelo: "GSR 150i", ano: 2016, preco: 8900, precoAntigo: 0, km: 37500, cambio: "Manual", combustivel: "Gasolina", cor: "Preto", status: "disponivel", descricao: "GSR 150i, naked leve e econômica, ótima primeira moto. Manutenção barata e documentação em dia. Custo-benefício imbatível.", opcionais: ["Partida elétrica", "Painel digital", "IPVA pago"], fotos: ["fotos/gsr-150i.png"], destaque: false, createdAt: 1718500000000, }, { id: "m_seed7", categoria: "street", marca: "Yamaha", modelo: "XJ6", ano: 2013, preco: 39900, precoAntigo: 0, km: 41800, cambio: "Manual", combustivel: "Gasolina", cor: "Branco", status: "disponivel", descricao: "XJ6 de 4 cilindros, naked equilibrada e confortável tanto na cidade quanto na estrada. Motor macio e confiável. Bem cuidada.", opcionais: ["ABS", "Painel digital", "Faróis de LED"], fotos: ["fotos/xj6.png"], destaque: false, createdAt: 1718600000000, }, { id: "m_seed8", categoria: "trail", marca: "Honda", modelo: "NXR 160 Bros", ano: 2020, preco: 17900, precoAntigo: 0, km: 23700, cambio: "Manual", combustivel: "Flex", cor: "Vermelho", status: "disponivel", descricao: "Bros 160 flex, a trail mais versátil do dia a dia. Aguenta o tranco no asfalto e na terra. Econômica e fácil de manter.", opcionais: ["Freio CBS", "Partida elétrica", "Painel digital"], fotos: ["fotos/bros-160.png"], destaque: false, createdAt: 1718700000000, }, { id: "m_seed9", categoria: "esportiva", marca: "Yamaha", modelo: "YZF-R3 Monster", ano: 2023, preco: 29900, precoAntigo: 0, km: 6900, cambio: "Manual", combustivel: "Gasolina", cor: "Preto", status: "disponivel", descricao: "R3 na pintura especial Monster Energy MotoGP. Esportiva leve, ágil e com posição de pilotagem amigável. Baixa quilometragem.", opcionais: ["ABS", "Faróis de LED", "Painel digital", "Bolha / Para-brisa"], fotos: ["fotos/yzf-r3-monster.png"], destaque: false, createdAt: 1718800000000, }, { id: "m_seed10", categoria: "street", marca: "Yamaha", modelo: "Fazer 150", ano: 2025, preco: 17900, precoAntigo: 0, km: 1900, cambio: "Manual", combustivel: "Flex", cor: "Azul", status: "disponivel", descricao: "Fazer 150 0km de garantia, a street mais querida do Brasil. Econômica, confortável e com painel digital. Zerada, ainda cheirando a nova.", opcionais: ["Freio CBS", "Partida elétrica", "Painel digital", "Faróis de LED"], fotos: ["fotos/fazer-150.png"], destaque: false, createdAt: 1718900000000, }, { id: "m_seed11", categoria: "custom", marca: "Royal Enfield", modelo: "Guerrilla 450", ano: 2026, preco: 25900, precoAntigo: 0, km: 800, cambio: "Manual", combustivel: "Gasolina", cor: "Amarelo", status: "oferta", descricao: "Guerrilla 450 novíssima, roadster de motor monocilíndrico Sherpa 450. Estilo retrô-moderno e pegada urbana. Praticamente 0km.", opcionais: ["ABS", "Painel digital", "Faróis de LED"], fotos: ["fotos/royal-enfield-guerrilla-450.png"], destaque: false, createdAt: 1719000000000, }, { id: "m_seed12", categoria: "esportiva", marca: "Honda", modelo: "CBR 600RR", ano: 2012, preco: 47900, precoAntigo: 0, km: 32900, cambio: "Manual", combustivel: "Gasolina", cor: "Vermelho", status: "disponivel", descricao: "CBR 600RR, supersport de 4 cilindros que é puro DNA de pista. Giro alto, ronco viciante e estabilidade absurda. Para apaixonados.", opcionais: ["ABS", "Faróis de LED", "Slider", "Escapamento esportivo"], fotos: ["fotos/cbr-600rr.png"], destaque: false, createdAt: 1719100000000, }, { id: "m_seed13", categoria: "street", marca: "Suzuki", modelo: "GSX-S750A", ano: 2020, preco: 46900, precoAntigo: 0, km: 18900, cambio: "Manual", combustivel: "Gasolina", cor: "Preto", status: "disponivel", descricao: "GSX-S750A, naked de 4 cilindros derivada da GSX-R. 114cv de potência e visual agressivo. Com controle de tração e ABS. Impecável.", opcionais: ["ABS", "Controle de tração", "Painel digital", "Faróis de LED"], fotos: ["fotos/gsx-s750a.png"], destaque: false, createdAt: 1719200000000, }, { id: "m_seed14", categoria: "trail", marca: "Yamaha", modelo: "XT 660R", ano: 2006, preco: 31900, precoAntigo: 0, km: 47600, cambio: "Manual", combustivel: "Gasolina", cor: "Azul", status: "disponivel", descricao: "XT 660R, trail monocilíndrica robusta e raiz, das mais desejadas para viagens. Mecânica indestrutível. Com pneus novos.", opcionais: ["Partida elétrica", "Protetor de motor", "Pneus novos"], fotos: ["fotos/xt-660r.png"], destaque: false, createdAt: 1719300000000, }, { id: "m_seed15", categoria: "street", marca: "Honda", modelo: "CB 650R", ano: 2023, preco: 52500, precoAntigo: 0, km: 7800, cambio: "Manual", combustivel: "Gasolina", cor: "Cinza", status: "disponivel", descricao: "CB 650R Neo Sports Café, naked de 4 cilindros com ronco inconfundível. Todas as revisões em dia, baixa quilometragem. Impecável.", opcionais: ["ABS", "Faróis de LED", "Painel digital", "Quickshifter"], fotos: ["fotos/cb-650r.png"], destaque: false, createdAt: 1719400000000, }, { id: "m_seed16", categoria: "scooter", marca: "Honda", modelo: "PCX 150", ano: 2019, preco: 16500, precoAntigo: 0, km: 25800, cambio: "Automático", combustivel: "Gasolina", cor: "Branco", status: "disponivel", descricao: "PCX 150, o scooter mais amado da cidade. Econômico, confortável e com porta-objetos generoso. Ótimo estado de conservação.", opcionais: ["Freio CBS", "Painel digital", "Faróis de LED"], fotos: ["fotos/pcx-150.png"], destaque: false, createdAt: 1719500000000, }, { id: "m_seed17", categoria: "street", marca: "Honda", modelo: "CB 500F", ano: 2018, preco: 30900, precoAntigo: 0, km: 28600, cambio: "Manual", combustivel: "Gasolina", cor: "Vermelho", status: "disponivel", descricao: "CB 500F naked, equilíbrio perfeito entre cidade e estrada. Motor bicilíndrico macio e econômico. Revisada e documentação em dia.", opcionais: ["ABS", "Faróis de LED", "Painel digital"], fotos: ["fotos/cb-500f.png"], destaque: false, createdAt: 1719600000000, }, ]; // ---- adaptadores entre o banco (snake_case) e o front (camelCase) ---- // A API devolve id numérico e preco_antigo/criado_em; o front trabalha com // id string (casa com a rota #/v/ID) e precoAntigo/createdAt. const fromApi = (r) => ({ id: String(r.id), categoria: r.categoria, marca: r.marca, modelo: r.modelo, ano: r.ano, preco: r.preco, precoAntigo: r.preco_antigo, km: r.km, cambio: r.cambio, combustivel: r.combustivel, cor: r.cor, status: r.status, descricao: r.descricao, opcionais: r.opcionais || [], fotos: r.fotos || [], destaque: !!r.destaque, createdAt: r.criado_em, }); const toApi = (v) => ({ marca: v.marca, modelo: v.modelo, ano: v.ano, preco: v.preco, preco_antigo: v.precoAntigo, km: v.km, categoria: v.categoria, status: v.status, cambio: v.cambio, combustivel: v.combustivel, cor: v.cor, descricao: v.descricao, opcionais: v.opcionais || [], fotos: v.fotos || [], destaque: v.destaque, }); // ---- chamadas à API ---- async function apiList() { const r = await fetch(API_BASE + "motos.php", { credentials: "include" }); if (!r.ok) throw new Error("api indisponível"); const d = await r.json(); if (!Array.isArray(d)) throw new Error("sem banco"); // ex: {erro:"sem config.php"} return d; } const apiSave = (payload) => fetch(API_BASE + "motos.php", { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }).then(async (r) => { const d = await r.json(); if (!r.ok) throw new Error(d.erro || "erro ao salvar"); return d; }); const apiDelete = (id) => fetch(API_BASE + "motos.php?id=" + encodeURIComponent(id), { method: "DELETE", credentials: "include" }) .then(async (r) => { const d = await r.json(); if (!r.ok) throw new Error(d.erro || "erro ao excluir"); return d; }); // ---- carga do modo demo (localStorage) ---- function loadDemo() { try { const raw = localStorage.getItem(LS_KEY); if (raw) return JSON.parse(raw); } catch (e) {} return SEED.map((v) => ({ ...v })); } // ---- store hook (híbrido: API quando há banco, senão demo) ---- function useVehicleStore() { const [vehicles, setVehicles] = React.useState([]); const [mode, setMode] = React.useState("loading"); // "loading" | "api" | "demo" // detecta a API uma vez e faz a carga inicial React.useEffect(() => { let alive = true; (async () => { try { const rows = await apiList(); if (!alive) return; setVehicles(rows.map(fromApi)); setMode("api"); } catch (e) { if (!alive) return; setVehicles(loadDemo()); setMode("demo"); } })(); return () => { alive = false; }; }, []); // no modo demo persiste no localStorage; no modo API o banco é a fonte da verdade React.useEffect(() => { if (mode === "demo") { try { localStorage.setItem(LS_KEY, JSON.stringify(vehicles)); } catch (e) {} } }, [vehicles, mode]); const refresh = async () => setVehicles((await apiList()).map(fromApi)); const addVehicle = async (v) => { if (mode === "api") { await apiSave(toApi(v)); await refresh(); } else setVehicles((prev) => [{ ...v, id: uid(), createdAt: Date.now() }, ...prev]); }; const updateVehicle = async (id, v) => { if (mode === "api") { await apiSave({ ...toApi(v), id }); await refresh(); } else setVehicles((prev) => prev.map((x) => (x.id === id ? { ...x, ...v } : x))); }; const deleteVehicle = async (id) => { if (mode === "api") { await apiDelete(id); await refresh(); } else setVehicles((prev) => prev.filter((x) => x.id !== id)); }; const resetVehicles = () => { if (mode === "demo") setVehicles(SEED.map((v) => ({ ...v }))); }; return { vehicles, mode, addVehicle, updateVehicle, deleteVehicle, resetVehicles }; } Object.assign(window, { STORE, ADMIN_CRED, STATUS_META, SEGMENTOS, segLabel, CAMBIOS, COMBUSTIVEIS, OPCIONAIS_COMUNS, formatBRL, formatKM, uid, useVehicleStore, API_BASE, });