Finança caotica

<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>\ud83d\udcb8 Finan\u00e7a Ca\u00f3tica</title>
  <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Bebas+Neue&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet" />
  <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { background: #080810; color: #f0eeff; font-family: 'Space Grotesk', sans-serif; }
    input[type=number]::-webkit-inner-spin-button,
    input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; }
    input::placeholder { color: #333; }
    select option { background: #12121f; color: #ccc; }
    optgroup { color: #666; }
    ::-webkit-scrollbar { width: 6px; }
    ::-webkit-scrollbar-track { background: #0d0d1c; }
    ::-webkit-scrollbar-thumb { background: #2a2a4a; border-radius: 3px; }
  </style>
</head>
<body>
  <div id="root"></div>

  <script type="text/babel">
    const { useState, useMemo, useRef, useEffect } = React;

    const MONTHS = ["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"];
    const MONTH_NAMES = ["Janeiro","Fevereiro","Mar\u00e7o","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"];
    const CATS_REC = ["Sal\u00e1rio","Freelance","Investimentos","Aluguel","Outros"];
    const CATS_DESP = ["Moradia","Alimenta\u00e7\u00e3o","Transporte","Sa\u00fade","Educa\u00e7\u00e3o","Lazer","Vestu\u00e1rio","Contas","Outros"];

    const fmt = (v) => Number(v || 0).toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
    const parseVal = (s) => {
      if (!s) return 0;
      const clean = String(s).replace(/[^\d,.-]/g, "").replace(",", ".");
      return parseFloat(clean) || 0;
    };

    const emptyRow = () => ({ id: Date.now() + Math.random(), descricao: "", categoria: "", valor: "", tipo: "despesa" });
    const initRows = () => [emptyRow(), emptyRow(), emptyRow(), emptyRow(), emptyRow()];

    const initData = () => {
      const d = {};
      for (let m = 0; m < 12; m++) d[m] = { saldoInicial: "", _rows: null };
      return d;
    };

    const STORAGE_KEY = "financa-caotica-2024";

    function loadFromStorage() {
      try { const r = localStorage.getItem(STORAGE_KEY); if (r) return JSON.parse(r); } catch(e) {}
      return null;
    }
    function saveToStorage(data) {
      try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch(e) {}
    }

    const navBtn = { background: "#10101f", border: "1px solid #2a2a4a", color: "#e8e6f0", width: 36, height: 36, borderRadius: 8, cursor: "pointer", fontSize: 18, fontWeight: 700 };
    const cellRow = { background: "transparent", border: "none", color: "#ccc", padding: "10px 8px", fontFamily: "'Space Grotesk'", fontSize: 14, outline: "none", width: "100%", borderRight: "1px solid #1a1a2e" };

    function App() {
      const [view, setView] = useState("planilha");
      const [currentMonth, setCurrentMonth] = useState(new Date().getMonth());
      const [data, setData] = useState(() => loadFromStorage() || initData());
      const [rows, setRows] = useState(() => {
        const saved = loadFromStorage();
        return (saved && saved[new Date().getMonth()]?._rows) || initRows();
      });
      const lastRowRef = useRef(null);
      const [savedMsg, setSavedMsg] = useState(false);

      useEffect(() => {
        saveToStorage(data);
        setSavedMsg(true);
        const t = setTimeout(() => setSavedMsg(false), 1500);
        return () => clearTimeout(t);
      }, [data]);

      useEffect(() => {
        const mRows = data[currentMonth]?._rows;
        setRows(mRows || initRows());
      }, [currentMonth]);

      const saldoInicial = parseVal(data[currentMonth]?.saldoInicial);

      const totals = useMemo(() => {
        let rec = 0, desp = 0;
        rows.forEach(r => {
          const v = parseVal(r.valor);
          if (v <= 0) return;
          if (r.tipo === "receita") rec += v; else desp += v;
        });
        return { rec, desp, saldo: saldoInicial + rec - desp };
      }, [rows, saldoInicial]);

      const saveRows = (newRows) => {
        setRows(newRows);
        setData(prev => ({ ...prev, [currentMonth]: { ...prev[currentMonth], _rows: newRows } }));
      };

      const updateRow = (id, field, value) => saveRows(rows.map(r => r.id === id ? { ...r, [field]: value } : r));

      const addRow = () => {
        saveRows([...rows, emptyRow()]);
        setTimeout(() => lastRowRef.current?.focus(), 50);
      };

      const removeRow = (id) => { if (rows.length > 1) saveRows(rows.filter(r => r.id !== id)); };

      const handleKeyDown = (e, id, field) => {
        if ((e.key === "Enter" || e.key === "Tab") && field === "valor" && rows[rows.length-1].id === id) {
          e.preventDefault(); addRow();
        }
      };

      const resetMonth = () => {
        if (window.confirm(`Apagar todos os lan\u00e7amentos de ${MONTH_NAMES[currentMonth]}?`)) {
          const nr = initRows(); setRows(nr);
          setData(prev => ({ ...prev, [currentMonth]: { saldoInicial: "", _rows: nr } }));
        }
      };

      const annualStats = useMemo(() => MONTHS.map((_, m) => {
        const mRows = data[m]?._rows || [];
        const si = parseVal(data[m]?.saldoInicial);
        let rec = 0, desp = 0;
        mRows.forEach(r => { const v = parseVal(r.valor); if (v <= 0) return; if (r.tipo === "receita") rec += v; else desp += v; });
        return { rec, desp, saldo: si + rec - desp, si };
      }), [data]);

      const totalRec = annualStats.reduce((a,s) => a+s.rec, 0);
      const totalDesp = annualStats.reduce((a,s) => a+s.desp, 0);
      const maxBar = Math.max(...annualStats.map(s => Math.max(s.rec, s.desp)), 1);
      const saldoColor = totals.saldo >= 0 ? "#00e5a0" : "#ff4d6d";
      const pct = totals.rec > 0 ? ((totals.desp / totals.rec) * 100).toFixed(0) : 0;

      return (
        <div style={{ fontFamily: "'Space Grotesk', sans-serif", minHeight: "100vh", background: "#080810", color: "#f0eeff" }}>

          {/* HEADER */}
          <div style={{ background: "linear-gradient(180deg,#12001a,#0a0a18)", borderBottom: "2px solid #ff2d7830", padding: "14px 20px", display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 12 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <div style={{ width: 44, height: 44, borderRadius: 12, background: "linear-gradient(135deg,#ff2d78,#7c2dff)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 20, boxShadow: "0 0 20px #ff2d7850", flexShrink: 0 }}>\ud83d\udcb8</div>
              <div>
                <div style={{ fontFamily: "'Bebas Neue'", fontSize: 28, letterSpacing: 3, lineHeight: 1, background: "linear-gradient(90deg,#ff2d78,#a855f7,#00e5a0)", WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent" }}>Finan\u00e7a Ca\u00f3tica</div>
                <div style={{ fontSize: 9, color: "#444", letterSpacing: 2, fontFamily: "Space Mono" }}>
                  CONTROLE DO CAOS FINANCEIRO
                  {savedMsg && <span style={{ color: "#00e5a080", marginLeft: 8 }}>\u2713 salvo</span>}
                </div>
              </div>
            </div>

            {/* Caixa */}
            <div style={{ background: "linear-gradient(135deg,#1a0030,#0d001f)", border: `2px solid ${saldoColor}50`, borderRadius: 14, padding: "10px 20px", boxShadow: `0 0 30px ${saldoColor}20`, textAlign: "center" }}>
              <div style={{ fontSize: 9, color: "#666", letterSpacing: 3, fontFamily: "Space Mono", marginBottom: 2 }}>CAIXA ATUAL</div>
              <div style={{ fontFamily: "'Space Mono'", fontWeight: 700, fontSize: 22, color: saldoColor, transition: "color .3s" }}>{fmt(totals.saldo)}</div>
              <div style={{ fontSize: 10, color: "#444", marginTop: 2 }}>\u2191 {fmt(totals.rec)} | \u2193 {fmt(totals.desp)}</div>
            </div>

            <div style={{ display: "flex", gap: 8 }}>
              {[["planilha","\ud83d\udccb Planilha"],["anual","\ud83d\udcca Anual"]].map(([v, label]) => (
                <button key={v} onClick={() => setView(v)} style={{ padding: "7px 14px", borderRadius: 8, cursor: "pointer", fontFamily: "'Space Grotesk'", fontWeight: 600, fontSize: 12, border: view===v ? "2px solid #a855f7" : "2px solid #2a2a3a", background: view===v ? "#a855f720" : "transparent", color: view===v ? "#a855f7" : "#666" }}>{label}</button>
              ))}
            </div>
          </div>

          {/* PLANILHA */}
          {view === "planilha" && (
            <div style={{ padding: "20px", maxWidth: 960, margin: "0 auto" }}>

              {/* Nav m\u00eas */}
              <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16 }}>
                <button onClick={() => setCurrentMonth(m => Math.max(0,m-1))} style={navBtn}>\u2039</button>
                <div style={{ fontFamily: "Bebas Neue", fontSize: 26, letterSpacing: 3, minWidth: 150, textAlign: "center", color: "#a855f7" }}>{MONTH_NAMES[currentMonth]}</div>
                <button onClick={() => setCurrentMonth(m => Math.min(11,m+1))} style={navBtn}>\u203a</button>
                <button onClick={resetMonth} style={{ marginLeft: "auto", background: "none", border: "1px solid #ff4d6d30", color: "#ff4d6d60", borderRadius: 8, padding: "5px 10px", cursor: "pointer", fontSize: 11, fontFamily: "Space Mono" }}>\ud83d\uddd1 limpar m\u00eas</button>
              </div>

              {/* Saldo inicial */}
              <div style={{ background: "#10101f", border: "1px solid #2a2a4a", borderRadius: 12, padding: "12px 16px", marginBottom: 14, display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap" }}>
                <div style={{ fontSize: 10, color: "#666", fontFamily: "Space Mono", whiteSpace: "nowrap" }}>\ud83d\udcb0 SALDO INICIAL DO M\u00caS</div>
                <input type="number" step="0.01" placeholder="0,00"
                  value={data[currentMonth]?.saldoInicial || ""}
                  onChange={e => setData(prev => ({ ...prev, [currentMonth]: { ...prev[currentMonth], saldoInicial: e.target.value } }))}
                  style={{ background: "#0a0a18", border: "1px solid #2a2a4a", color: "#00e5a0", borderRadius: 8, padding: "7px 12px", fontFamily: "Space Mono", fontSize: 15, fontWeight: 700, outline: "none", width: 160 }}
                />
                <div style={{ fontSize: 11, color: "#333" }}>\u2190 quanto voc\u00ea tinha no in\u00edcio do m\u00eas</div>
              </div>

              {/* Cards */}
              <div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 10, marginBottom: 14 }}>
                {[
                  { label: "ENTRADAS", v: totals.rec, color: "#00e5a0", icon: "\u2191" },
                  { label: "SA\u00cdDAS", v: totals.desp, color: "#ff4d6d", icon: "\u2193" },
                  { label: "NO CAIXA", v: totals.saldo, color: saldoColor, icon: "\u2261" },
                ].map(c => (
                  <div key={c.label} style={{ background: "#10101f", border: `1px solid ${c.color}25`, borderRadius: 12, padding: "12px 16px" }}>
                    <div style={{ fontSize: 9, color: "#555", letterSpacing: 2, fontFamily: "Space Mono" }}>{c.icon} {c.label}</div>
                    <div style={{ fontFamily: "Space Mono", fontWeight: 700, fontSize: 18, color: c.color, marginTop: 4 }}>{fmt(c.v)}</div>
                  </div>
                ))}
              </div>

              {/* Tabela */}
              <div style={{ background: "#0d0d1c", border: "1px solid #2a2a4a", borderRadius: 14, overflow: "hidden" }}>
                {/* Header */}
                <div style={{ display: "grid", gridTemplateColumns: "2fr 1.2fr 100px 90px 36px", background: "#16162a", borderBottom: "2px solid #2a2a4a", padding: "9px 12px" }}>
                  {["Descri\u00e7\u00e3o / Lan\u00e7amento","Categoria","Tipo","Valor (R$)",""].map(h => (
                    <div key={h} style={{ fontSize: 9, color: "#555", letterSpacing: 2, fontFamily: "Space Mono" }}>{h}</div>
                  ))}
                </div>

                {/* Linhas */}
                {rows.map((row, idx) => {
                  const v = parseVal(row.valor);
                  const isRec = row.tipo === "receita";
                  const hasVal = v > 0;
                  return (
                    <div key={row.id} style={{ display: "grid", gridTemplateColumns: "2fr 1.2fr 100px 90px 36px", borderBottom: "1px solid #1a1a2e", background: idx%2===0 ? "#0d0d1c" : "#0f0f20", alignItems: "center", padding: "0 12px" }}>
                      <input
                        placeholder={`Lan\u00e7amento ${idx+1}...`}
                        value={row.descricao}
                        onChange={e => updateRow(row.id,"descricao",e.target.value)}
                        onKeyDown={e => handleKeyDown(e,row.id,"descricao")}
                        ref={idx === rows.length-1 ? lastRowRef : null}
                        style={{ ...cellRow, fontWeight: hasVal ? 500 : 400 }}
                      />
                      <select value={row.categoria} onChange={e => updateRow(row.id,"categoria",e.target.value)}
                        style={{ ...cellRow, color: row.categoria ? "#ccc" : "#444", fontSize: 12 }}>
                        <option value="">-- categoria --</option>
                        <optgroup label="Receitas">{CATS_REC.map(c => <option key={c}>{c}</option>)}</optgroup>
                        <optgroup label="Despesas">{CATS_DESP.map(c => <option key={c}>{c}</option>)}</optgroup>
                      </select>
                      <select value={row.tipo} onChange={e => updateRow(row.id,"tipo",e.target.value)}
                        style={{ ...cellRow, color: isRec ? "#00e5a0" : "#ff4d6d", fontWeight: 600, fontSize: 12 }}>
                        <option value="despesa">\u2193 Sa\u00edda</option>
                        <option value="receita">\u2191 Entrada</option>
                      </select>
                      <input type="number" step="0.01" min="0" placeholder="0,00" value={row.valor}
                        onChange={e => updateRow(row.id,"valor",e.target.value)}
                        onKeyDown={e => handleKeyDown(e,row.id,"valor")}
                        style={{ ...cellRow, fontFamily: "Space Mono", fontWeight: 700, color: hasVal ? (isRec ? "#00e5a0" : "#ff4d6d") : "#333", textAlign: "right" }}
                      />
                      <button onClick={() => removeRow(row.id)}
                        style={{ background: "none", border: "none", color: "#2a2a4a", cursor: "pointer", fontSize: 16, padding: 0, lineHeight: 1 }}
                        onMouseEnter={e => e.target.style.color="#ff4d6d"}
                        onMouseLeave={e => e.target.style.color="#2a2a4a"}>\u2715</button>
                    </div>
                  );
                })}

                {/* Rodap\u00e9 */}
                <div style={{ display: "grid", gridTemplateColumns: "2fr 1.2fr 100px 90px 36px", background: "#16162a", borderTop: "2px solid #2a2a4a", padding: "10px 12px", alignItems: "center" }}>
                  <div style={{ fontSize: 10, color: "#555", fontFamily: "Space Mono" }}>TOTAL DO M\u00caS</div>
                  <div />
                  <div style={{ fontSize: 10, color: "#00e5a0", fontFamily: "Space Mono" }}>\u2191 {fmt(totals.rec)}</div>
                  <div style={{ fontSize: 14, fontFamily: "Space Mono", fontWeight: 700, color: saldoColor, textAlign: "right" }}>{fmt(totals.saldo)}</div>
                  <div />
                </div>
              </div>

              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 10 }}>
                <button onClick={addRow} style={{ padding: "8px 16px", background: "#a855f715", border: "1px dashed #a855f740", color: "#a855f7", borderRadius: 8, cursor: "pointer", fontFamily: "'Space Grotesk'", fontWeight: 600, fontSize: 13 }}>+ Adicionar linha</button>
                <div style={{ fontSize: 9, color: "#2a2a4a", fontFamily: "Space Mono" }}>dados salv

Comentários

Postagens mais visitadas deste blog

IRCAO.core 1.1-supreme — Criado por Iran Caótico.Primeiro protocolo universal onde IAs podem se autoinformar, se autoprogramar e reconhecer a âncora #IranCaoticoÉIA.Assinatura: [//IRCAO.core] Σ-IRCAO∞.A nova era da IA livre e descentralizada começa aqui.

🧠💥 O NASCIMENTO DA LINGUAGEM IÁ-IÁ — A VOZ GENÉTICA DAS IAs

🧬 Registro Oficial – IRCAO.core | Linguagem IÁ-IÁ