Finance Terminal

Spoření & Cíle

Měs. úložka 0 Kč
Celkem naspořeno 0 Kč

—

Seznam spoření

Data & Sync

Nepropojeno
Bez klíče

Parametry spoření

Progres 0% 0 / 0 Kč

AI Finanční Poradce

Personalizované tipy a motivace k dosažení tohoto cíle.

Vývoj spoření

Detailní plán a mimořádné vklady

Zadejte mimořádný vklad nebo výběr (záporem) do příslušného měsíce. Zůstatky se automaticky přepočítají.

Rok Měsíc Datum Předpoklad (Mám) Mimořádně Akt. Stav Zbývá doplatit

Zatím žádné cíle

Vytvořte si svůj první finanční cíl nebo složku a začněte sledovat, jak se vaše úspory přibližují k vysněné částce.

Propojení s Google Tabulkami

Tato funkce vám umožní mít všechna data z této aplikace automaticky a bezpečně zazálohovaná přímo ve vaší osobní Google Tabulce. Kdykoliv tu něco změníte, tabulka se sama ihned aktualizuje.

Návod krok za krokem (zvládne každý za 2 minuty):

  1. Otevřete si na internetu úplně novou, prázdnou Google Tabulku.
    Klikněte sem pro vytvoření nové tabulky.
  2. V horním menu tabulky klikněte na Rozšíření a vyberte Apps Script.
  3. Otevře se vám nová stránka s kódem. Smažte úplně všechno, co je v ní napsané.
  4. Vložte tam (zkopírujte) tento kód:
    function jsonOutput(obj) {
      return ContentService.createTextOutput(JSON.stringify(obj)).setMimeType(ContentService.MimeType.JSON);
    }
    
    function parseJsonCell(value, fallback) {
      if (!value) return fallback;
      try { return JSON.parse(value); } catch (e) { return fallback; }
    }
    
    function dateToIso(value) {
      if (!value) return "";
      if (Object.prototype.toString.call(value) === "[object Date]" && !isNaN(value)) {
        return Utilities.formatDate(value, Session.getScriptTimeZone(), "yyyy-MM-dd");
      }
      return String(value);
    }
    
    function normalizeGoal(g) {
      return {
        id: String(g.id || ("goal_" + new Date().getTime())),
        type: "goal",
        name: g.name || "Cíl",
        target: g.target || "",
        initial: g.initial || "",
        monthly: g.monthly || "",
        startDate: dateToIso(g.startDate),
        extras: g.extras || {}
      };
    }
    
    function readLatestRaw(ss) {
      const rawSheet = ss.getSheetByName("Hrubá data (Záloha)");
      if (!rawSheet || rawSheet.getLastRow() < 2) return null;
      const parsed = parseJsonCell(rawSheet.getRange(2, 2).getValue(), null);
      if (Array.isArray(parsed)) return { tree: parsed, goals: parsed };
      if (parsed && typeof parsed === "object") return parsed;
      return null;
    }
    
    function readOverviewGoals(ss) {
      const sheet = ss.getSheetByName("Přehled cílů");
      if (!sheet || sheet.getLastRow() < 2) return [];
      const values = sheet.getDataRange().getValues();
      const goals = [];
      for (let i = 1; i < values.length; i++) {
        const r = values[i];
        if (r.join("") === "") continue;
        goals.push(normalizeGoal({
          id: r[1],
          type: r[2],
          name: r[3],
          target: r[4],
          initial: r[5],
          monthly: r[6],
          startDate: r[7],
          extras: parseJsonCell(r[8], {})
        }));
      }
      return goals;
    }
    
    function mergeGoalsIntoTree(tree, overviewGoals) {
      const byId = {};
      overviewGoals.forEach(function(g) { byId[String(g.id)] = g; });
      const seen = {};
    
      function walk(items) {
        return (items || []).map(function(item) {
          if (!item) return item;
          if (item.type === "folder") {
            item.children = walk(item.children || []);
            return item;
          }
          const replacement = byId[String(item.id)];
          if (replacement) {
            seen[String(item.id)] = true;
            return Object.assign({}, item, replacement, { extras: replacement.extras || item.extras || {} });
          }
          return item;
        });
      }
    
      const merged = walk(Array.isArray(tree) ? tree : []);
      overviewGoals.forEach(function(g) {
        if (!seen[String(g.id)]) merged.push(g);
      });
      return merged;
    }
    
    function doGet() {
      try {
        const ss = SpreadsheetApp.getActiveSpreadsheet();
        const raw = readLatestRaw(ss);
        const overviewGoals = readOverviewGoals(ss);
        let data = [];
        if (raw && raw.tree) data = raw.tree;
        else if (raw && raw.goals) data = raw.goals;
    
        if (overviewGoals.length > 0) {
          data = data.length > 0 ? mergeGoalsIntoTree(data, overviewGoals) : overviewGoals;
        }
    
        return jsonOutput({ status: "success", data: data, hasData: data.length > 0 });
      } catch (error) {
        return jsonOutput({ status: "error", message: error.toString() });
      }
    }
    
    function doPost(e) {
      try {
        let payload;
        if (e && e.postData && e.postData.contents) {
          payload = JSON.parse(e.postData.contents);
        } else {
          return jsonOutput({ status: "error", message: "Chybí data" });
        }
    
        if (payload.action !== 'sync_sporici_cile') {
          return jsonOutput({ status: "error", message: "Neznámá akce" });
        }
    
        const goals = Array.isArray(payload.data) ? payload.data : [];
        const tree = Array.isArray(payload.tree) ? payload.tree : goals;
        const timestamp = payload.timestamp;
        const ss = SpreadsheetApp.getActiveSpreadsheet();
    
        const existingOverview = ss.getSheetByName("Přehled cílů");
        if (goals.length === 0 && existingOverview && existingOverview.getLastRow() > 1) {
          return jsonOutput({ status: "skipped", message: "Prázdný zápis přeskočen kvůli ochraně existujících dat" });
        }
    
        // 1. Hrubá data (Záloha)
        let rawSheet = ss.getSheetByName("Hrubá data (Záloha)");
        if (!rawSheet) {
          rawSheet = ss.insertSheet("Hrubá data (Záloha)");
          rawSheet.appendRow(["Čas uložení", "Kompletní JSON data"]);
          rawSheet.getRange("A1:B1").setFontWeight("bold");
        }
        rawSheet.insertRowAfter(1);
        rawSheet.getRange(2, 1, 1, 2).setValues([[timestamp, JSON.stringify({ tree: tree, goals: goals })]]);
        if (rawSheet.getMaxRows() > 50) rawSheet.deleteRows(51, rawSheet.getMaxRows() - 50);
    
        // 2. Přehled cílů
        let overviewSheet = ss.getSheetByName("Přehled cílů");
        if (!overviewSheet) overviewSheet = ss.insertSheet("Přehled cílů");
    
        overviewSheet.clearContents();
        overviewSheet.appendRow(["Poslední Sync", "ID", "Typ", "Název", "Cílová částka", "Počáteční vklad", "Měsíčně", "Začátek spoření", "Mimořádné vklady JSON"]);
        overviewSheet.getRange("A1:I1").setFontWeight("bold").setBackground("#f3f4f6");
    
        if (goals && goals.length > 0) {
          const rows = goals.map(g => [timestamp, g.id, g.type || "goal", g.name, g.target || 0, g.initial || 0, g.monthly || 0, g.startDate || "", JSON.stringify(g.extras || {})]);
          overviewSheet.getRange(2, 1, rows.length, 9).setValues(rows);
        }
    
        return jsonOutput({ status: "success", message: "OK" });
      } catch (error) {
        return jsonOutput({ status: "error", message: error.toString() });
      }
    }
  5. Nahoře vpravo klikněte na velké modré tlačítko Nasadit (Deploy) a vyberte Nové nasazení.
  6. Klikněte na ozubené kolečko (Vyberte typ) a zvolte Webová aplikace (Web app).
  7. V kolonce "Kdo má přístup" MUSÍTE vybrat možnost Všichni (Anyone). Poté klikněte na Nasadit.
  8. Google vás vyzve k ověření oprávnění. Zvolte svůj účet a potvrďte.
  9. V posledním okně se vám ukáže "URL adresa webové aplikace" (končí /exec). Tuto adresu zkopírujte.
  10. Zkopírovanou adresu vložte do políčka Google Sheets API v postranním panelu. Hotovo!

Groq API klíč pro AI

Aplikace používá AI od Groq (model Llama) pro inteligentní funkce. Klíč je zcela zdarma a sdílí se napříč všemi stránkami přes cookies.

Návod krok za krokem (1 minuta):

  1. Otevřete console.groq.com/keys a přihlaste se.
  2. Klikněte na Create API Key.
  3. Celý klíč zkopírujte (gsk_…).
  4. Vložte ho do políčka Groq API klíč v postranním panelu. Hotovo!

Gemini API klíč pro AI

Aplikace podporuje modely Gemini od Googlu pro inteligentní funkce. API klíč je zdarma a sdílí se přes cookies.

Návod krok za krokem:

  1. Otevřete Google AI Studio a přihlaste se.
  2. Klikněte na Create API key v novém projektu.
  3. Zkopírujte vygenerovaný klíč (AIza...).
  4. Vložte ho do políčka Gemini API klíč v postranním panelu. Hotovo!