🔥 Firebase-integration – Bjerreds Saltsjöbad

Arbetsdokument för backend-kopplingen av inpasseringsstatistiken

🏠 Startsidan ← Statistik 📊 Datatabell
📋 Bakgrund och mål

Idag lagras inpasseringsdata hårdkodat i BASE_DATA-objektet i index.html och data.html. Det fungerar bra, men för att uppdatera siffrorna måste Kent redigera koden och publicera via GitHub – varje gång.

Målet var att koppla sidan till Firebase Realtime Database så att behöriga föreningsmedlemmar kan skriva in siffror direkt i webbläsaren, och alla ser ändringen direkt – utan att Kent behöver röra koden.

BASE_DATA behålls alltid i koden som reserv, ifall Firebase inte går att nå.
✅ Klart – 2026-05-15
Alla 8 steg är genomförda och systemet är i produktion på GitHub Pages. Föreningsmedlemmar loggar in direkt på data.html och uppdaterar siffror utan att Kent behöver göra något. Nya användare bjuds in via "✉ Bjud in"-knappen i nedre vänstra hörnet av data.html – administratören anger namn, e-post och tillfälligt lösenord, och ett färdigt välkomstbrev genereras att kopiera och skicka.

Aktiva användare (2026-05-15):
lundgren.kent@gmail.com – administratör
bjorn.syren1@gmail.com – Björn Syren, inbjuden 2026-05-15
🔥 Firebase har två olika databaser – vilken väljer vi?

Firebase är Googles backend-plattform, men den innehåller faktiskt två helt skilda databasprodukter. Det är lätt att blanda ihop dem – de heter nästan likadant och lever i samma Firebase-projekt. Här är skillnaden:

Egenskap ☁️ Firestore ⚡ Realtime Database
Hur data lagras Dokument i samlingar (som mappar med JSON-filer) Ett enda stort JSON-träd (som ett JavaScript-objekt)
Struktur Hierarkisk: samlingar → dokument → fält Platt träd: nod/nod/nod → värde
Passar bäst för Komplex data, relationer, avancerade sökfrågor Enkel tabelldata, realtidssynk, enkla strukturer
Realtidsuppdatering Ja, men kräver lite mer kod Ja, inbyggt och mycket enkelt
SDK-exempel firebase.firestore() getDatabase(app) + ref()
Används i projektet Bjerred-skylt (trivselregler, röster) Quiz 16B (resultat, namn, poäng)
Varför vi väljer Realtime Database för inpasseringsdata:

Titta på hur BASE_DATA ser ut i koden: BASE_DATA[2026]['Medlemmar – armband'][3] = 90.
Det är exakt samma struktur som ett JSON-träd: data/2026/Medlemmar – armband/3 = 90.

Realtime Database är ett JSON-träd. Vi kan kopiera BASE_DATA rakt in utan att ändra strukturen. Firestore hade krävt att vi omstrukturerar data till dokument och samlingar – onödigt jobb.
Båda kan leva i samma Firebase-projekt: Firestore (används av Bjerred-skylt) och Realtime Database (som vi aktiverar för inpasseringsdata) är separata produkter men kan köras parallellt i projektet "skylt" (skylt-e0c45) utan att störa varandra.
⚙️ Firebase-projekt och konfiguration

Vi använder det befintliga Firebase-projektet "skylt" (skylt-e0c45), som redan är kopplat till Bjerreds Saltsjöbad via appen "Bjerreds-skylt". Inpasseringsdata sparas under en egen nod i databasen så att den inte krockar med annan data i projektet.

🔗 Firebase Console för projektet "skylt":
https://console.firebase.google.com/project/skylt-e0c45

ℹ️ Länken är inte hemlig – den kräver att du är inloggad med rätt Google-konto för att visa något. Den långa URL som Firebase Console ibland genererar (med web:ZmU3... i slutet) är bara App-ID:t kodat och behövs inte – den kortare versionen ovan fungerar lika bra och är enklare att läsa.

Nedanför visas firebaseConfig för projektet. Notera att koden är anpassad för CDN-laddning (direkt i HTML utan byggverktyg), inte npm-versionen som visas i Firebase Console.

// ============================================================
// FIREBASE-KONFIGURATION – projekt "skylt" (skylt-e0c45)
// Används för Bjerreds-skylt-appen (Web App)
//
// OBS: Konfigurationen nedan är för CDN-varianten (ES-moduler
// från gstatic.com), inte npm-versionen. Det är det format
// som fungerar direkt i en HTML-fil utan byggverktyg.
//
// databaseURL saknas ännu – läggs till när Realtime Database
// har aktiverats i Firebase Console (se steg 3 nedan).
// ============================================================

import { initializeApp, getApps }
  from 'https://www.gstatic.com/firebasejs/11.0.0/firebase-app.js';
import { getDatabase, ref, set, onValue }
  from 'https://www.gstatic.com/firebasejs/11.0.0/firebase-database.js';

const firebaseConfig = {
  apiKey:            "AIzaSyBbAX6EFxbW1VIcSYQ3Zqbr3v733sGMYD8",
  authDomain:        "skylt-e0c45.firebaseapp.com",
  projectId:         "skylt-e0c45",
  storageBucket:     "skylt-e0c45.firebasestorage.app",
  messagingSenderId: "653653642598",
  appId:             "1:653653642598:web:29e332c87fed1cd9f34ca7",
  // databaseURL – tillagd efter att Realtime Database aktiverades (steg 3 klart):
  databaseURL: "https://skylt-e0c45-default-rtdb.europe-west1.firebasedatabase.app"
};
databaseURL är inlagd. Realtime Database är aktiverad och konfigurationen är komplett. Anslutningstest-knappen längre ned på sidan kan nu användas för att verifiera att anslutningen fungerar.
Vad är databas.html och vad är data.html?

databas.html (den här sidan) är ett levande arbetsdokument – en loggbok över vad vi gör, varför vi gör det, och hur långt vi har kommit. databaseURL:en finns här för att anslutningstest-knappen ska kunna testa om Firebase svarar. Den här sidan påverkar inte produktionsdatan.

data.html är produktionsfilen – den som besökare använder för att se och (när Firebase är klart) uppdatera inpasseringssiffror. Firebase-koden läggs in i data.html i steg 7, inte förrän allt är testat och klart.
📦 Vad är ett SDK – och vilken version ska vi använda?

SDK = Software Development Kit

Ett SDK (uttalas "es-dee-kay") är ett färdigt verktygslåda av kod som ett företag paketerar och ger bort till utvecklare, så att de slipper uppfinna hjulet på nytt. I stället för att skriva all kommunikation med Firebase-servrar från grunden laddar vi in Googles SDK – och sedan räcker det att anropa enkla funktioner som set() och onValue().

Är SDK:er mest för databaser?

Nej – SDK:er finns för i princip allt inom modern mjukvaruutveckling. Några exempel på vad SDK:er används till:
  • Databaser och molntjänster – Firebase, AWS, Azure (det vi använder här)
  • Betalningar – Stripe SDK, Klarna SDK, Swish
  • Kartor – Google Maps SDK, Mapbox
  • Inloggning – BankID SDK, Google Sign-In
  • Mobilappar – Android SDK, iOS SDK (hela plattformen)
  • AI-tjänster – OpenAI SDK, Anthropic SDK
  • Statistik – Google Analytics SDK
Mönstret är alltid detsamma: ett företag eller en community erbjuder en tjänst, och SDK:t är den "bro" som gör att din kod kan prata med tjänsten utan att du behöver förstå hur den fungerar inuti.

Firebase JS SDK – versioner

Google släpper kontinuerligt nya versioner av Firebase JavaScript SDK. Varje version kan innehålla buggfixar, prestandaförbättringar, nya funktioner – och ibland ändringar som kan bryta befintlig kod (breaking changes). Versionen anges i CDN-länken:

// Versionen styr vilken kod som laddas från Googles servrar:
import { initializeApp }
  from 'https://www.gstatic.com/firebasejs/11.0.0/firebase-app.js';
Version Status Används i projektet Notering
v10.7.0 Äldre Bjerred-skylt (Firestore, compat-läge) Äldre compat-API – fortfarande vanlig
v11.0.0 ✅ Stabil – används nu data.html + databas.html (Realtime DB) ES-moduler, vältestad, rekommenderas
v12.13.0 🆕 Senaste (maj 2026) Nyast men mindre beprövad i produktion
🔧 Välj SDK-version – se hur import-raderna ser ut:


        
        
⚠️ Rekommendation: Håll dig till v11.0.0 tills v12 är mer beprövad i produktion. Uppgradera aldrig en SDK-version mitt i en deploy-cykel – testa alltid lokalt (Cursor + Live Preview) innan du pushar till GitHub Pages. Versionsbytet görs på tre ställen i data.html (firebase-app, firebase-database, firebase-auth) och tre ställen i databas.html.
🗂️ Datastruktur i Firebase

Firebase Realtime Database lagrar data som ett JSON-träd. Vi speglar strukturen i BASE_DATA-objektet rakt av, men lägger allt under noden bjerred-inpasseringar/ för att inte krocka med annan data i projektet.

Sökväg i Firebase Innehåll Exempel
bjerred-inpasseringar/data Rotnod för all inpasseringsdata
.../data/2026 Årsdata, ett objekt per år
.../data/2026/Medlemmar – armband Kategori, ett objekt per kategori
.../data/2026/Medlemmar – armband/3 Månadsindex (0 = jan, 3 = apr) 90
bjerred-inpasseringar/senast-uppdaterad Datum för senaste ändring "2026-05-15"
Säkerhetsregler – följande regler sätts i Firebase Console under Realtime Database → Rules. Alla kan läsa, men bara inloggade kan skriva:
{
  "rules": {
    "bjerred-inpasseringar": {
      ".read":  "true",
      ".write": "auth != null"
    }
  }
}
✅ Checklista – 8 steg mot en fungerande databas (= backend)
💡 "Databas" och "backend" – vad är skillnaden?

I vardagligt tal används orden ibland som synonymer, och i det här projektet betyder de i praktiken samma sak:

Backend är den del av ett system som användaren inte ser – servern, databasen, logiken som körs bakom kulisserna. Motsatsen är frontend, det vill säga det som visas i webbläsaren (HTML, CSS, JavaScript).

Databas är den specifika del av backend där data lagras permanent. En backend kan innehålla mycket mer (beräkningar, autentisering, e-post osv.), men för det här projektet är det databasen som är det viktiga.

Firebase är en backend som en tjänst (BaaS – Backend as a Service): Google sköter servern och infrastrukturen, vi sköter bara vår data och våra regler. Det är därför Firebase är ett bra val för ett litet projekt som det här – vi slipper sätta upp en egen server.

Kort sagt: när vi säger "koppla data.html till en backend" menar vi "lagra inpasseringssiffrorna i Firebase-databasen istället för bara lokalt i webbläsaren".
  • Steg 1 – Firebase-projekt valt Använder befintliga projektet "skylt" (skylt-e0c45) med appen "Bjerreds-skylt". Inget nytt projekt behövs.
  • Steg 2 – firebaseConfig hämtad Konfigurationen är kopierad från Firebase Console och dokumenterad ovan i det här dokumentet.
  • Steg 3 – Aktivera Realtime Database i Firebase Console Klart! Realtime Database är aktiverad i projektet "skylt" (europe-west1). Databasen är tom men redo att ta emot data.
    databaseURL: https://skylt-e0c45-default-rtdb.europe-west1.firebasedatabase.app
    Obs: URL:en slutar på /:null i Firebase Console – det betyder bara att databasen är tom. Det är korrekt.
  • Steg 4 – Säkerhetsregler publicerade Klart! Reglerna är publicerade i Firebase Console. Noden bjerred-inpasseringar kan läsas av alla, men bara skriva av inloggade användare (auth != null). De gamla testlägesreglerna (med utgångsdatum 2026-06-14) är borttagna.
  • Steg 5 – Firebase Authentication aktiverad Klart! Inloggningsmetoden E-post/lösenord är aktiverad. Användarkonto skapat: lundgren.kent@gmail.com (2026-05-15). Det är det kontot som används för att logga in och få skriva i databasen.
  • Steg 6 – Engångsmigration klar (2026-05-15) All historisk data är uppskriven till Firebase Realtime Database. 11 kategorier skrivna, 0 fel. Data finns under bjerred-inpasseringar/data/ i Firebase Console. Migrationen kördes inloggad som lundgren.kent@gmail.com.
  • Steg 7 – data.html omskriven för Firebase (2026-05-15) Hela JavaScript-blocket skrevs om med <script type="module"> och Firebase SDK v11.0.0 (ES-moduler via CDN).
    onValue() prenumererar på bjerred-inpasseringar/data – tabellen uppdateras live.
    set() sparar cellredigering direkt till Firebase.
    BASE_DATA renderas omedelbart vid sidladdning som fallback.
    • Inloggningsmodal visas automatiskt vid cellklick om ej inloggad.
    • Grön/röd statusdot i headern visar om Firebase är uppkopplad.
    • CSP-metatag tillåter Firebase WebSocket från GitHub Pages.
    tolkFirebaseSnapshot() hanterar kategorier med snedstreck (nästlad sökväg i Firebase).
  • Steg 8 – Testat och publicerat på GitHub Pages (2026-05-15) Pusha direkt till main via Cursor – säkerhetskopior (index copy.html, data copy.html) fungerade som skyddsnät i stället för separat branch.
    Bekräftat på live-URL: data.html:
    • Tabellen laddas från Firebase ✓
    • Live-synk bekräftad: ändring i Chrome syntes direkt i Firefox ✓
    • Inloggning och utloggning fungerar från kentlundgren.github.io

    Tillägg efter steg 8: "Bjud in"-knapp (✉ Bjud in) lagd till i nedre vänstra hörnet av data.html. Knappen öppnar en modal där administratören fyller i namn, e-post och tillfälligt lösenord – ett färdigt välkomstbrev genereras automatiskt och kan kopieras med ett klick. Inbjudan skickad till Björn Syren (bjorn.syren1@gmail.com) 2026-05-15.
🚀 Steg 6 – Engångsmigration: kopiera all data till Firebase
Varför körs skriptet härifrån och inte från data.html?

Skriptet behöver Firebase SDK för att kunna skriva till databasen. Firebase SDK är inläst på den här sidan (databas.html) – det lade vi in tidigare. data.html har ingen Firebase-kod än (det kommer i steg 7). Därför kör vi migrationen härifrån.

Är BASE_DATA samma i data.html och databas.html?
BASE_DATA finns i data.html och inpasseringar/index.html – de är identiska och hålls alltid synkade. Den här sidan (databas.html) har ingen BASE_DATA. Därför är all historisk data inbakad direkt i skriptet nedan – en kopia av BASE_DATA från data.html.

Så här gör du:

  1. Se till att du är på sidan databas.html i webbläsaren
  2. Tryck F12 för att öppna utvecklarverktyget
  3. Klicka på fliken Console
  4. Kopiera hela skriptet nedan och klistra in det i konsolen
  5. Tryck Enter
  6. Vänta på bekräftelsemeddelandena – hela BASE_DATA skrivs till Firebase
⚠️ Kör skriptet bara en gång. Kör du det flera gånger skrivs samma data igen (det skadar ingenting, men är onödigt). När konsolen visar "✅ Migration klar!" är all data uppe i Firebase.
✅ Skriptet är kopierat! Klistra in det i konsolen (F12 → Console → >) och tryck Enter.
// ============================================================
// ENGÅNGSMIGRATION – BASE_DATA → Firebase Realtime Database
// ============================================================
// Kör detta skript EN GÅNG i konsolen (F12) på databas.html.
// Skriptet använder Firebase SDK som redan är inläst på sidan.
//
// Data skrivs till: bjerred-inpasseringar/data/{år}/{kategori}/{månadsindex}
// ============================================================

(async () => {

  // ============================================================
  // STEG 0 – Logga in med ditt Firebase-konto
  // Skriptet måste autentisera sig innan det kan skriva,
  // eftersom säkerhetsreglerna kräver auth != null.
  // Fyll i ditt lösenord på raden nedan innan du kör skriptet.
  // ============================================================
  const EPOST    = 'lundgren.kent@gmail.com';
  const LÖSENORD = 'FYLL_IN_DITT_LÖSENORD_HÄR'; // ← ändra detta

  const { getAuth, signInWithEmailAndPassword } =
    await import('https://www.gstatic.com/firebasejs/11.0.0/firebase-auth.js');
  const auth = getAuth();

  try {
    await signInWithEmailAndPassword(auth, EPOST, LÖSENORD);
    console.log('✅ Inloggad som', EPOST);
  } catch (e) {
    console.error('❌ Inloggning misslyckades:', e.message);
    return; // avbryt om inloggningen misslyckas
  }

  // ---- BASE_DATA – identisk kopia från data.html och index.html ----
  const BASE_DATA = {

    /* ---- 2024 -------------------------------------------------- */
    2024: {
      'Restaurangen':         [754,696,772,656,841, 654,696,678,725,607,795,620],
      'SMS-biljetter':        [557,458,484,331,265, 184,143,197,240,484,593,827],
      'Medlemmar – armband': [2949,2788,3096,2251,3388,3130,3581,3362,4210,3146,3299,3255]
    },

    /* ---- 2025 – 0-värden mars–juli = ombyggnad av bastun ------- */
    2025: {
      'Restaurangen/badbiljetter': [737,764,  0,  0,  0,  0, 344, 891, 991, 667, 794, 621],
      'SMS/Swish-biljett':         [801,619,  0,  0,  0,  0,   4, 140, 285, 726,1052,1373],
      'Medlemmar – armband':       [3231,2555,  0,  0,  0,  0,1524,2134,2339,1552,1400,1000],
      'Medlemmar – Wonder':        [  0,  0,  0,  0,  0,  0, 120,1359,1729,1993,2443,2513]
    },

    /* ---- 2026 – null = saknas (ännu ej rapporterat) ------------ */
    2026: {
      'Restaurangen/badbiljetter': [643, 665, 600,null,null,null,null,null,null,null,null,null],
      'SMS/Swish-biljetter':       [1114, 751,1006, 913,null,null,null,null,null,null,null,null],
      'Medlemmar – armband':       [ 492, 119,  95,  90,null,null,null,null,null,null,null,null],
      'Medlemmar – Wonder':        [2627,2445,3663,3293,null,null,null,null,null,null,null,null]
    }
  };

  // ---- Hämta Firebase-instansen som redan är initierad på sidan ----
  const { getDatabase, ref, set } =
    await import('https://www.gstatic.com/firebasejs/11.0.0/firebase-database.js');
  const db  = getDatabase();
  let   ok  = 0;
  let   fel = 0;

  // ---- Skriv år för år, kategori för kategori till Firebase --------
  for (const [år, kategorier] of Object.entries(BASE_DATA)) {
    for (const [kategori, månader] of Object.entries(kategorier)) {

      // Bygg ett objekt {0: val, 1: val, ...} – null-värden inkluderas
      const månadsObj = {};
      månader.forEach((v, i) => { månadsObj[i] = v ?? 'null'; });

      try {
        await set(
          ref(db, `bjerred-inpasseringar/data/${år}/${kategori}`),
          månadsObj
        );
        console.log(`✅ ${år} / ${kategori}`);
        ok++;
      } catch (e) {
        console.error(`❌ ${år} / ${kategori}: ${e.message}`);
        fel++;
      }
    }
  }

  // ---- Spara tidsstämpel för migrationen ---------------------------
  await set(ref(db, 'bjerred-inpasseringar/senast-uppdaterad'),
    new Date().toLocaleDateString('sv-SE'));

  console.log(`\n✅ Migration klar! ${ok} kategorier skrivna, ${fel} fel.`);
  if (fel > 0) console.warn('Kontrollera felen ovan och kör om vid behov.');

})();
🔌 Anslutningstest mot Firebase

Knappen nedan försöker ansluta till Firebase och skriva ett testmeddelande under noden bjerred-inpasseringar/anslutningstest. Testet fungerar inte förrän Realtime Database är aktiverad och databaseURL är inlagd.

✅ Realtime Database är aktiverad, databaseURL är inlagd och säkerhetsreglerna är i produktion (read=true, write=auth!=null).
⚠️ OBS – testet läser, skriver inte.
Tidigare försökte det här testet skriva ett testmeddelande till Firebase. Det gav felet PERMISSION_DENIED – och det är korrekt beteende: säkerhetsreglerna kräver numera inloggning för all skrivning, och anslutningstest-knappen är inte inloggad.

Testet är nu uppdaterat och gör istället en läsning av riktig produktionsdata (bjerred-inpasseringar/senast-uppdaterad). Läsning är öppen för alla – det bekräftar att Firebase är nådd och svarar. Vill du testa skrivning loggar du in i data.html och redigerar en cell.
📚 Referens – tidigare Firebase-implementation

Kent har arbetat med Firebase i två olika projekt som använder olika Firebase-tekniker. Det är viktigt att förstå skillnaden inför arbetet med inpasseringsdata.

Projekt Teknik SDK Passar för
Bjerred-skylt
skylt-e0c45
Firestore
Dokumentdatabas
v10.7.0 compat
script-taggar
Komplex data, avancerade queries, röster och förslag
Quiz 16B
borgholm-registration
Realtime Database
JSON-träd
v11.0.0 ES-moduler
import-satser
Enkel tabelldata, realtidssynk, passar BASE_DATA-strukturen
☁️ Projekt 1 – Bjerred-skylt: kentlundgren.github.io/Bjerred-skylt/ ↗

Vad lagras? Trivselregler för bastun, med möjlighet att rösta (👍/👎) och föreslå ändringar av enskilda regler. Varje regel är ett eget objekt med svenska och engelska texter, ett röstningsresultat och en lista med användarförslag.

Var Firestore ett bra val här?

✅ Ja – Firestore var ett bra val för detta projekt. Här är varför:
  • Varje regel är ett dokument med flera fält (textSv, textEn, votes.up, votes.down, suggestions). Det passar Firestores dokumentstruktur perfekt.
  • Röstning och förslag är oberoende av varandra – Firestore hanterar det bra med subcollections.
  • Firestore har inbyggt stöd för att lyssna på ändringar i enskilda dokument (t.ex. bara regel nr 3), vilket är effektivt när man bara vill uppdatera en reglad i taget.
  • Compat-SDK v10.7.0 (script-taggar) är enkel att komma igång med – rätt nivå för ett lärande-projekt.

Kort sagt: Data med relationer (regel → röster → förslag) = Firestore. Det är precis det Bjerred-skylt har.

⚠️ En sak att tänka på: Compat-SDK (v10.7.0) är äldre och lite tyngre än modern ES-modulvariant. Det fungerar utmärkt, men om projektet byggs om idag skulle man troligen välja ES-moduler (v11+) för ett lite smidigare flöde. Det är inte fel – bara en finputsning.
⚡ Projekt 2 – Quiz 16B: kentlundgren.se/program/quiz/16B/ ↗

Vad lagras? Quizresultat – varje deltagare skickar in sitt namn, sin poäng och ett svårighetsbetyg (1–10). Allt sparas i en enkel lista som sedan kan visas för alla.

Var Realtime Database ett bra val här?

✅ Ja – Realtime Database var ett bra val för detta projekt. Här är varför:
  • Quizresultat är en enkel lista av poster (namn + poäng + betyg + datum). Det är ett platt JSON-träd – exakt vad Realtime Database är optimerat för.
  • Med push() läggs varje ny post till med ett unikt nyckel-ID automatiskt. Enkelt och felfritt.
  • onValue() lyssnar i realtid – om en ny person skickar in sitt resultat syns det direkt för alla utan att sidan behöver laddas om.
  • Modern ES-modul-SDK (v11.0.0) med import-satser är det senaste och mest underhållna alternativet.

Kort sagt: Enkel lista med poster som skapas och läses i realtid = Realtime Database. Perfekt match.

⚠️ En sak att tänka på: Projektet använder projektet borgholm-registration – ett namn som inte har med quiz att göra. Det funkar tekniskt, men om man bygger om det idag skulle man skapa ett nytt Firebase-projekt med ett tydligare namn. Återigen: inte fel, bara lite otydligt i efterhand.
Slutsats och koppling till inpasseringsdata:

Båda tidigare val var välmotiverade och tekniskt korrekta. Det visar att du redan har en god intuition för när man väljer vad. För inpasseringsdata är valet lika tydligt: BASE_DATA[år][kategori][månadsindex] är ett JSON-träd → Realtime Database. Samma teknik som i quiz-projektet, fast nu i projektet "skylt" (skylt-e0c45) istället för borgholm-registration.

Länk till datatabellen som ska få Firebase-koppling: 📊 data.html
💡 Lärdom 2026-06-03 – BASE_DATA och Firebase är två separata saker

I samband med att en ny datakategori (Engångsbadare – Wondr) lades till i data.html uppstod en förvirring som ledde till en viktig insikt om hur systemet verkligen fungerar.

Kärnan i insikten:
Det finns två separata lagringsplatser för inpasseringsdata. De synkroniseras inte automatiskt med varandra.
Lagring Vad är det? Hur uppdateras det? Grön prick?
BASE_DATA JavaScript-konstant hårdkodad direkt i HTML-filerna (data.html, index.html). Det är vanlig kod – inte browser-localStorage och inte Firebase. Redigera filen i Cursor → commit → push ❌ Nej (orange)
Firebase Realtidsdatabas online. Alla sidor med grön prick läser härifrån. Firebase-data har alltid företräde över BASE_DATA. Öppna data.html → logga in → klicka en cell → skriv värdet → tryck Enter. Ändringen syns direkt för alla. ✅ Ja

🔶 Vad händer när en ny kategori läggs till i BASE_DATA?

När en ny rad läggs till i koden (t.ex. 'Engångsbadare – Wondr') syns värden direkt i tabellen – men de kommer från BASE_DATA, inte Firebase. Indikatorn i sidhuvudet visar då ● orange: Lokal fallbackdata tills data aktivt sparas till Firebase.

✅ Hur sparar man data till Firebase?

Metod 1 – Manuell cell-klick (alltid tillgänglig):

  1. Öppna data.html och logga in
  2. Klicka på en cell i den nya kategorins rad
  3. Skriv in värdet i rutan som dyker upp
  4. Tryck Enter – värdet sparas direkt i Firebase
  5. Upprepa för varje månad

Metod 2 – Synkroniseringsknapp (tillagd 2026-06-03):

När man är inloggad i data.html visas en grön knapp 📥 Synka till Firebase i sidhuvudet. Den läser igenom hela BASE_DATA, jämför med vad Firebase redan har, och skickar enbart de värden som saknas. Befintliga Firebase-värden skrivs aldrig över. Perfekt för att initiera en ny kategori med ett klick.

🔑 Nyckelinsikt att komma ihåg:
Att värden visas i tabellen på data.html betyder inte att de är sparade i Firebase. De kan komma från BASE_DATA. Kontrollera alltid indikatorn i sidhuvudet: grön prick = Firebase, orange prick = lokal fallback.

📊 Konkret exempel – Engångsbadare Wondr, 3 juni 2026

Kategorin lades till i BASE_DATA med värden jan=17, feb=13, mar=38, apr=31, maj=173. Trots att tabellen visade dessa siffror var de inte i Firebase. Indikatorn visade orange. Problemet löstes när ett värde (1 173 som testvalue) skrevs in manuellt via cell-klick i data.html – då bytte indikatorn till grön och engangsintraden.html uppdaterades direkt.

Dokumenterat: 2026-06-03

🐛 Lärdom 2026-06-03 – loadData()-buggen: BASE_DATA som månadsvis fallback

Strax efter att kategori-synken fungerade (se lärdomen ovan) uppstod ett nytt problem: Jan–apr försvann ur tabellen för Engångsbadare – Wondr direkt efter att maj sparats i Firebase. Det såg ut som att siffror gick förlorade, men egentligen var det en bugg i hur loadData() blandade ihop Firebase och BASE_DATA.

Buggens kärna:
loadData() ersatte hela månadsarryen för en kategori med Firebase-arrayen. Men Firebase-arrayen innehöll bara maj (ett verkligt värde) och null för jan–apr. Dessa null skrev över BASE_DATA:s jan–apr-värden.

Hur det såg ut före fixen

// GAMMAL kod – ersätter hela arrayen med Firebase
for (const [kategori, månader] of Object.entries(kategorier)) {
  data[year][kategori] = månader;   // null-värden från Firebase skriver över BASE_DATA
}

Hur det fungerar efter fixen (2026-06-03)

// NY kod – merge på månads-nivå
for (const [kategori, månader] of Object.entries(kategorier)) {
  if (!data[year][kategori]) {
    // Kategorin saknas i BASE_DATA – använd Firebase-arrayen direkt
    data[year][kategori] = månader;
  } else {
    // Kategorin finns i BASE_DATA – slå ihop månad för månad
    for (let m = 0; m < 12; m++) {
      if (månader[m] !== null) {
        data[year][kategori][m] = månader[m]; // Firebase vinner
      }
      // månader[m] === null: behåll BASE_DATA (gör ingenting)
    }
  }
}

Konkret exempel – januari till maj

Källa JanFebMarAprMaj
BASE_DATA 17133831173
Firebase (bara maj sparad) null null null null 1173
Resultat efter fix 171338311173

Firebase-värdet 1173 används för maj. BASE_DATA 173 används ej för maj. Jan–apr hämtas från BASE_DATA eftersom Firebase har null för dem.

Principen: Firebase har alltid företräde – men bara där Firebase faktiskt har ett värde (inte null). BASE_DATA fungerar som en per-månad-fallback, inte som en allt-eller-inget-reserv.

Dokumenterat och fixat: 2026-06-03