Przejdź do głównej treści

Kalendarz Miesięczny

Grafika Kalendarz

Poznaj prosty, elegancki i niezwykle wygodny kalendarz, który działa w całości w Twojej przeglądarce — bez logowania, bez internetu, bez utraty danych. Aplikacja pozwala szybko planować dzień, przeglądać najbliższe wydarzenia, sprawdzać polskie święta i dni wolne, a także tworzyć własne notatki i zapisywać je lokalnie na urządzeniu.

To idealne narzędzie dla osób, które chcą mieć przejrzysty kalendarz zawsze pod ręką — lekki, bezpieczny i w pełni prywatny. Zacznij korzystać już teraz i zobacz, jak wygodne może być planowanie!

Opis działania kalendarza

Aplikacja wyświetla kalendarz miesięczny z możliwością przechodzenia między miesiącami oraz szybkiego powrotu do bieżącej daty. Najważniejsze elementy działania:

1. Kalendarz miesięczny

  • Aktualny miesiąc wyświetlany jest z podziałem na tygodnie.
  • Niedziele i święta oznaczone są czerwonym kolorem, aby były lepiej widoczne.
  • Dzisiejszy dzień jest dodatkowo wyróżniony, dzięki czemu z łatwością go odnajdziesz.
  • Możesz swobodnie przełączać miesiące i przeglądać rok wstecz lub naprzód.

2. Automatyczne zaznaczanie świąt

Kalendarz rozpoznaje wszystkie najważniejsze polskie święta, zarówno stałe, jak i ruchome (np. Wielkanoc, Boże Ciało). Po wskazaniu święta wyświetlany jest jego opis.

3. Dodawanie i edycja wydarzeń

  • Kliknięcie dowolnego dnia otwiera panel z listą wydarzeń.
  • Możesz szybko dodać nowe zadanie, notatkę lub przypomnienie.
  • Każde wydarzenie można łatwo edytować lub usunąć.
  • Wydarzenia zapisywane są automatycznie w localStorage, dzięki czemu pozostają na urządzeniu nawet po zamknięciu przeglądarki.

4. Import i eksport danych

  • Eksport wszystkich wydarzeń do pliku .json jednym kliknięciem.
  • Import pozwala przywrócić lub przenieść dane na inne urządzenie.

5. Prywatność

Wszystkie dane pozostają wyłącznie u Ciebie — nic nie jest wysyłane na zewnętrzne serwery.

Kalendarz

PnWtŚrCzPtSoNd

Szczegóły dnia

Wybierz dzień

Dane astronomiczne

Warszawa (52.2297° N, 21.0122° E)

Zdarzenia

    Funkcje

    • Podświetla niedziele i święta na czerwono.
    • Dodawanie / edycja / usuwanie zdarzeń (LocalStorage).
    • Eksport i import JSON z zachowaniem zdarzeń.
    • Szczegółowa lista świąt (stałe i ruchome dla Polski).

    Kod po stronie przeglądarki

    <link type="text/css" href="http://www.dariuszrorat.ugu.pl/assets/css/bootstrap/wcag-outline.min.css" rel="stylesheet">   
    <style>
        
        /* Drobne poprawki wyglądu */
        .calendar-table td { height: 84px; vertical-align: top; padding: .35rem; cursor: pointer; }
        .day-number { font-size: .95rem; }
        .events-list { font-size: .85rem; max-height: 136px; overflow:auto; }    
        .table-active { outline: 2px solid rgba(13,110,253,0.12); }
        
        [data-bs-theme=golden] .text-danger {color: #d13241 !important;}
        [data-bs-theme=twilight] .text-danger {color: #ff4c65 !important;}
        [data-bs-theme=dark] .text-danger {color: #ff4359 !important;}
      </style>
      <div id="app">
        <div class="d-flex justify-content-between align-items-center mb-3">
          <h2>
          Kalendarz   
          </h2>  
          <div>
            <div class="btn-group me-2" role="group">
              <button id="prevMonth" class="btn btn-outline-secondary btn-sm">&lt;</button>
              <button id="todayBtn" class="btn btn-outline-secondary btn-sm">Dziś</button>
              <button id="nextMonth" class="btn btn-outline-secondary btn-sm">&gt;</button>
            </div>
            <div class="btn-group ms-2" role="group">
              <button id="exportBtn" class="btn btn-sm btn-outline-primary">Eksportuj</button>
              <button id="importBtn" class="btn btn-sm btn-outline-secondary">Importuj</button>
              <input id="importFile" type="file" accept="application/json" class="d-none" aria-label="Wybierz plik">
            </div>
          </div>
        </div>
    
        <div class="row g-3">
          <div class="col-lg-8">
            <div class="card">
              <div class="card-body">
                <div class="d-flex justify-content-between align-items-center mb-2">
                  <div>
                    <div id="monthTitle" class="fw-bold text-capitalize"></div>
                    <div id="yearTitle" class="small text-muted"></div>
                  </div>
                  <div>
                    <button id="addEventGlobal" class="btn btn-sm btn-success" data-bs-toggle="modal" data-bs-target="#eventModal">Dodaj zdarzenie</button>
                  </div>
                </div>
    
                <div class="table-responsive">
                  <table class="table table-sm table-bordered text-center calendar-table mb-0">
                    <thead>
                      <tr class="table-light">
                        <th>Pn</th><th>Wt</th><th>Śr</th><th>Cz</th><th>Pt</th><th>So</th><th>Nd</th>
                      </tr>
                    </thead>
                    <tbody id="calendarBody"></tbody>
                  </table>
                </div>    
              </div>
            </div>
          </div>
    
          <div class="col-lg-4">
            <div class="card">
              <div class="card-body">
                <h3 class="h5 card-title">Szczegóły dnia</h3>
                <p id="selectedDateText" class="mb-1">Wybierz dzień</p>            
                <div id="holidaysList" class="mb-2 small"></div>
                <h4 class="h6 mb-1">Dane astronomiczne</h4>
                <div class="small text-muted">Warszawa (52.2297° N, 21.0122° E)</div>  
                <div id="suninfo" class="mb-3"></div>    
                <h4 class="h6 mb-1">Zdarzenia</h4>
                <ul id="eventsList" class="list-group mb-2 events-list"></ul>
                <div class="d-flex gap-2">
                  <button id="addEventBtn" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#eventModal">Dodaj</button>
                  <button id="clearStorageBtn" class="btn btn-outline-danger btn-sm">Wyczyść lokalne</button>
                </div>
              </div>
            </div>
    
            <div class="card mt-3">
              <div class="card-body small">
                <h4 class="h6">Funkcje</h4>
                <ul>
                  <li>Podświetla niedziele i święta na czerwono.</li>
                  <li>Dodawanie / edycja / usuwanie zdarzeń (LocalStorage).</li>
                  <li>Eksport i import JSON z zachowaniem zdarzeń.</li>
                  <li>Szczegółowa lista świąt (stałe i ruchome dla Polski).</li>
                </ul>
              </div>
            </div>
          </div>
        </div>
    
        <!-- Event modal -->
        <div class="modal fade" id="eventModal" tabindex="-1" aria-hidden="true">
          <div class="modal-dialog">
            <form id="eventForm" class="modal-content">
              <div class="modal-header">
                <h3 class="h5 modal-title">Zdarzenie</h3>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zamknij"></button>
              </div>
              <div class="modal-body">
                <input type="hidden" id="eventId">
                <div class="mb-3">
                  <label for="eventDate" class="form-label">Data</label>
                  <input type="date" id="eventDate" class="form-control" required>
                </div>
                <div class="mb-3">
                  <label for="eventTime" class="form-label">Godzina (opcjonalnie)</label>
                  <input type="time" id="eventTime" class="form-control">
                </div>
                <div class="mb-3">
                  <label for="eventTitle" class="form-label">Tytuł</label>
                  <input type="text" id="eventTitle" class="form-control" required>
                </div>
                <div class="mb-3">
                  <label for="eventDesc" class="form-label">Opis</label>
                  <textarea id="eventDesc" rows="3" class="form-control"></textarea>
                </div>
              </div>
              <div class="modal-footer">
                <button type="button" id="deleteEventBtn" class="btn btn-outline-danger me-auto d-none">Usuń</button>
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Anuluj</button>
                <button type="submit" class="btn btn-primary">Zapisz</button>
              </div>
            </form>
          </div>
        </div>
    
      </div>
      <script>
      // =====================
      // Prosty kalendarz z obsługą świąt i LocalStorage
      // =====================
    
      // --- Stan aplikacji ---
      const state = {
        cursor: new Date(), // aktywny miesiąc
        selectedDate: null, // YYYY-MM-DD
        eventsKey: 'calendarEvents_v1'
      };
    
      // --- Utility ---
      const fmtDate = (d) => d.toISOString().slice(0,10);
      const pad = (n)=> n<10? '0'+n: ''+n;
    
      // --- LocalStorage events ---
      function loadEvents(){
        try{
          const raw = localStorage.getItem(state.eventsKey);
          return raw ? JSON.parse(raw) : [];
        }catch(e){ console.error(e); return []; }
      }
      function saveEvents(list){ localStorage.setItem(state.eventsKey, JSON.stringify(list)); }
    
      function addEvent(evt){
        const list = loadEvents();
        list.push(evt);
        saveEvents(list);
      }
      function updateEvent(updated){
        const list = loadEvents().map(e=> e.id===updated.id? updated: e);
        saveEvents(list);
      }
      function removeEvent(id){
        const list = loadEvents().filter(e=> e.id!==id);
        saveEvents(list);
      }
    
      function eventsForDate(dateStr){
        return loadEvents().filter(e=> e.date===dateStr).sort((a,b)=> (a.time||'')> (b.time||'')?1:-1);
      }
    
      // --- Holiday calculations (Poland) ---
      function getEasterDate(y){
        // Gauss algorithm
        const a = y % 19;
        const b = Math.floor(y/100);
        const c = y % 100;
        const d = Math.floor(b/4);
        const e = b % 4;
        const f = Math.floor((b+8)/25);
        const g = Math.floor((b-f+1)/3);
        const h = (19*a + b - d - g + 15) % 30;
        const i = Math.floor(c/4);
        const k = c % 4;
        const l = (32 + 2*e + 2*i - h - k) % 7;
        const m = Math.floor((a + 11*h + 22*l)/451);
        const month = Math.floor((h + l - 7*m + 114)/31) -1;
        const day = ((h + l -7*m +114) % 31) +1;
        return new Date(y, month, day);
      }
    
      function holidaysForYear(y){
        const fixed = [
          {m:0,d:1, name: 'Nowy Rok'},                                // 01.01
          {m:0,d:6, name: 'Trzech Króli'},                            // 06.01
          {m:4,d:1, name: 'Święto Pracy'},                            // 01.05
          {m:4,d:3, name: 'Święto Konstytucji 3 Maja'},               // 03.05
          {m:7,d:15, name: 'Wniebowzięcie Najświętszej Maryi Panny'},  // 15.08
          {m:10,d:1, name: 'Wszystkich Świętych'},                    // 01.11 (poprawione)
          {m:10,d:11, name: 'Święto Niepodległości'},                 // 11.11 (poprawione)
          {m:11,d:25, name: 'Boże Narodzenie (1)'},                   // 25.12
          {m:11,d:26, name: 'Boże Narodzenie (2)'}                    // 26.12
        ];
    
        const easter = getEasterDate(y);
        const easterMon = new Date(easter); easterMon.setDate(easter.getDate()+1);
        const pentecost = new Date(easter); pentecost.setDate(easter.getDate()+49);
        const corpus = new Date(easter); corpus.setDate(easter.getDate()+60);
    
        const mov = [
          {d: easter.getDate(), m: easter.getMonth(), name: 'Wielkanoc'},
          {d: easterMon.getDate(), m: easterMon.getMonth(), name: 'Poniedziałek Wielkanocny'},
          {d: pentecost.getDate(), m: pentecost.getMonth(), name: 'Zielone Świątki'},
          {d: corpus.getDate(), m: corpus.getMonth(), name: 'Boże Ciało'}
        ];
    
        const combined = fixed.concat(mov);
        // map to date string keys
        return combined.map(h=> ({ date: y+'-'+pad(h.m+1)+'-'+pad(h.d), name: h.name }));
      }
    
      // cache holidays per year
      const holidaysCache = {};
      function getHolidaysForYear(y){ if(!holidaysCache[y]) holidaysCache[y]= holidaysForYear(y); return holidaysCache[y]; }
      
    // Dokładna funkcja Sunrise / Sunset (algorytm NOAA)
      function noaaGetSunTimes(date, lat, lon) {
        const rad = Math.PI / 180;
        const deg = 180 / Math.PI;
    
        // Helpers
        function toJulian(d) {
            return d.valueOf() / 86400000 + 2440587.5; // JS Date -> Julian day
        }
    
        function fromJulian(j) {
            return new Date((j - 2440587.5) * 86400000);
        }
    
        function julianCentury(jd) {
            return (jd - 2451545.0) / 36525.0;
        }
    
        function geomMeanLongSun(T) {
            let L = 280.46646 + T * (36000.76983 + T * 0.0003032);
            L = L % 360;
            return (L < 0) ? L + 360 : L;
        }
    
        function geomMeanAnomalySun(T) {
            return 357.52911 + T * (35999.05029 - 0.0001537 * T);
        }
    
        function eccentEarthOrbit(T) {
            return 0.016708634 - T * (0.000042037 + 0.0000001267 * T);
        }
    
        function sunEqOfCenter(T, M) {
            const Mrad = M * rad;
            return Math.sin(Mrad) * (1.914602 - T * (0.004817 + 0.000014 * T)) +
                Math.sin(2 * Mrad) * (0.019993 - 0.000101 * T) +
                Math.sin(3 * Mrad) * 0.000289;
        }
    
        function sunTrueLong(L0, C) {
            return L0 + C;
        }
    
        function sunApparentLong(T, trueLong) {
            const omega = 125.04 - 1934.136 * T;
            return trueLong - 0.00569 - 0.00478 * Math.sin(omega * rad);
        }
    
        function meanObliqEcliptic(T) {
            return 23 + (26 + ((21.448 - T * (46.815 + T * (0.00059 - T * 0.001813)))) / 60) / 60;
        }
    
        function obliqCorr(T, eps0) {
            const omega = 125.04 - 1934.136 * T;
            return eps0 + 0.00256 * Math.cos(omega * rad);
        }
    
        function sunDeclination(eps, lambda) {
            return Math.asin(Math.sin(eps * rad) * Math.sin(lambda * rad)) * deg;
        }
    
        function equationOfTime(T, L0, e, M, eps) {
            // in minutes
            const y = Math.tan((eps / 2) * rad);
            const y2 = y * y;
            const Mrad = M * rad;
            const L0rad = L0 * rad;
    
            const Etime = 4 * deg * (y2 * Math.sin(2 * L0rad) -
                2 * e * Math.sin(Mrad) +
                4 * e * y2 * Math.sin(Mrad) * Math.cos(2 * L0rad) -
                0.5 * y2 * y2 * Math.sin(4 * L0rad) -
                1.25 * e * e * Math.sin(2 * Mrad));
            return Etime; // minutes
        }
    
        // ---- main calculation ----
        const jd = toJulian(new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0)));
        const T = julianCentury(jd);
    
        const L0 = geomMeanLongSun(T); // degrees
        const M = geomMeanAnomalySun(T); // degrees
        const e = eccentEarthOrbit(T);
        const C = sunEqOfCenter(T, M);
        const trueLong = sunTrueLong(L0, C);
        const apparentLong = sunApparentLong(T, trueLong);
        const eps0 = meanObliqEcliptic(T);
        const eps = obliqCorr(T, eps0);
        const decl = sunDeclination(eps, apparentLong);
        const eqTime = equationOfTime(T, L0, e, M, eps); // minutes
    
        // solar noon (in Julian days)
        // longitude to minutes: lon * 4 (deg -> minutes)
        const timezoneOffsetMinutes = new Date().getTimezoneOffset(); // in minutes (local)
        // solarNoon in minutes relative to UTC midnight = 720 - 4*lon - eqTime
        const solarNoonMinutesUTC = 720 - (4 * lon) - eqTime;
    
        // Hour angle for the sun altitude -0.83 deg for sunrise/sunset (refraction + sun radius)
        const ha = Math.acos(
            (Math.cos((90.833) * rad) / (Math.cos(lat * rad) * Math.cos(decl * rad)) - Math.tan(lat * rad) * Math.tan(decl * rad))
        ) * deg; // degrees
    
        // sunrise and sunset in minutes UTC
        const sunriseMinutesUTC = solarNoonMinutesUTC - (ha * 4);
        const sunsetMinutesUTC = solarNoonMinutesUTC + (ha * 4);
    
        // Build Date objects (convert minutes UTC of the day to Date in local timezone)
        function minutesUTCToLocalDate(minutesUTC) {
            // minutesUTC is minutes since 00:00 UTC of that date
            const ms = Math.round(minutesUTC * 60000);
            // base: UTC midnight of the given date
            const utcMidnight = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
            // UTC timestamp
            const utcTimestamp = utcMidnight + ms;
            return new Date(utcTimestamp); // JS Date will represent local time when formatted
        }
    
        const sunrise = minutesUTCToLocalDate(sunriseMinutesUTC);
        const sunset = minutesUTCToLocalDate(sunsetMinutesUTC);
    
        return {
            sunrise,
            sunset
        };
      }
                
      // --- Rendering calendar ---
      function buildCalendar(){
        const now = state.cursor;
        const year = now.getFullYear();
        const month = now.getMonth();
    
        const firstDay = new Date(year, month, 1).getDay() || 7; // Monday=1 .. Sunday=7
        const daysInMonth = new Date(year, month+1, 0).getDate();
    
        document.getElementById('monthTitle').textContent = now.toLocaleDateString('pl-PL',{ month:'long'});
        document.getElementById('yearTitle').textContent = year;
    
        const holidays = getHolidaysForYear(year).reduce((acc,h)=>{ acc[h.date]=h.name; return acc; }, {});
    
        let html = '';
        let day = 1;
        for(let row=0; row<6; row++){
          html += '<tr>';
          for(let col=1; col<=7; col++){
            if((row===0 && col<firstDay) || day>daysInMonth){ html += '<td></td>'; }
            else{
              const dateStr = `${year}-${pad(month+1)}-${pad(day)}`;
              const isToday = dateStr === fmtDate(new Date());
              const isSunday = col===7;
              const isHoliday = !!holidays[dateStr];
    
              let classes = [];
              if(isToday) classes.push('border border-primary border-2 fw-bold');
              if(isHoliday || isSunday) classes.push('text-danger fw-bold');
    
              const evs = eventsForDate(dateStr);
    
              html += `<td class="${classes.join(' ')}" data-date="${dateStr}">`;
              html += `<div class="d-flex justify-content-between">`;
              html += `<div class="day-number">${day}</div>`;
              if(evs.length) html += `<span class="badge bg-primary rounded-pill">${evs.length}</span>`;
              html += `</div>`;
    
              if(isHoliday) html += `<div class="small">${holidays[dateStr]}</div>`;
    
              // show up to 2 events
              html += '<div class="events-list mt-1">';
              evs.slice(0,2).forEach(e=>{
                html += `<div class="small text-truncate">${escapeHtml(e.time? e.time + ' ' : '')}${escapeHtml(e.title)}</div>`;
              });
              html += '</div>';
    
              html += '</td>';
              day++;
            }
          }
          html += '</tr>';
        }
    
        document.getElementById('calendarBody').innerHTML = html;
    
        // attach click handlers
        document.querySelectorAll('[data-date]').forEach(td=> td.addEventListener('click', ()=> selectDate(td.getAttribute('data-date'))));
      }
    
      function escapeHtml(s){ return (s||'').replace(/[&<>\\\"]/g, c=> ({'&':'&amp;','<':'&lt;','>':'&gt;','\\':'\\\\','"':'&quot;'}[c])); }
    
      function parseYMD(ymd) {
        const [y, m, d] = ymd.split('-').map(Number);
        return new Date(y, m - 1, d, 12); // południe = brak przesunięć
      }
          
      function selectDate(dateStr){
        state.selectedDate = dateStr;
        document.getElementById('selectedDateText').textContent = new Date(dateStr).toLocaleDateString('pl-PL', { weekday:'long', year:'numeric', month:'long', day:'numeric' });
    
        const d = parseYMD(dateStr); 
        const LAT = 52.2297;
        const LNG = 21.0122;  
        const times = noaaGetSunTimes(d, LAT, LNG);
        const diffMs = times.sunset - times.sunrise;
        const hrs = Math.floor(diffMs / 3600000);
        const mins = Math.floor((diffMs % 3600000) / 60000);      
        const html = `
        <div class="small text-muted mt-2">
            <div>Wschód słońca: <strong>${times.sunrise.toLocaleTimeString("pl-PL") ?? "n/a"}</strong></div>
            <div>Zachód słońca: <strong>${times.sunset.toLocaleTimeString("pl-PL") ?? "n/a"}</strong></div>
            <div>Długość dnia: <strong>${hrs}h ${mins}min</strong></div>
        </div>
        `;
        const suninfo = document.getElementById('suninfo');
        suninfo.innerHTML = html;  
          
        const holidays = getHolidaysForYear(new Date(dateStr).getFullYear()).filter(h=> h.date===dateStr);
        const holEl = document.getElementById('holidaysList');
        holEl.innerHTML = holidays.map(h=> `<div class="badge bg-danger me-1">${h.name}</div>`).join('') || '<span class="small-quiet">Brak świąt</span>';
    
        renderEventsList();
    
        // highlight selected cell
        document.querySelectorAll('[data-date]').forEach(td=> td.classList.remove('table-active'));
        const sel = document.querySelector(`[data-date="${dateStr}"]`);
        if(sel) sel.classList.add('table-active');
      }
    
      function renderEventsList(){
        const ul = document.getElementById('eventsList'); ul.innerHTML='';
        if(!state.selectedDate) return;
        const evs = eventsForDate(state.selectedDate);
        if(!evs.length){ ul.innerHTML = '<li class="list-group-item small-quiet">Brak zdarzeń</li>'; return; }
        evs.forEach(e=>{
          const li = document.createElement('li'); li.className='list-group-item d-flex justify-content-between align-items-start';
          const div = document.createElement('div');
          div.innerHTML = `<div class="fw-bold">${escapeHtml(e.title)}</div><div class="small">${escapeHtml(e.time||'')} ${escapeHtml(e.description||'')}</div>`;
          const btns = document.createElement('div');
          btns.innerHTML = `<button class="btn btn-sm btn-outline-secondary me-1 edit">Edytuj</button><button class="btn btn-sm btn-outline-danger delete">Usuń</button>`;
          li.appendChild(div); li.appendChild(btns);
          ul.appendChild(li);
    
          btns.querySelector('.edit').addEventListener('click', ()=> openEditModal(e));
          btns.querySelector('.delete').addEventListener('click', ()=>{ if(confirm('Usuń zdarzenie?')){ removeEvent(e.id); buildCalendar(); renderEventsList(); }});
        });
      }
    
      // --- Modal handling (add/edit) ---
      const eventModalEl = document.getElementById('eventModal');
      let eventModal = null;
      document.addEventListener('DOMContentLoaded', function() {
        eventModal = new bootstrap.Modal(eventModalEl);  
      }) ;   
      
      const eventForm = document.getElementById('eventForm');
    
      document.getElementById('addEventBtn').addEventListener('click', ()=> openAddModal());
      document.getElementById('addEventGlobal').addEventListener('click', ()=> openAddModal());
    
      function openAddModal(){
        document.getElementById('eventId').value = '';
        document.getElementById('eventDate').value = state.selectedDate || fmtDate(new Date());
        document.getElementById('eventTime').value = '';
        document.getElementById('eventTitle').value = '';
        document.getElementById('eventDesc').value = '';
        document.getElementById('deleteEventBtn').classList.add('d-none');
        eventModal.show();
      }
    
      function openEditModal(e){
        document.getElementById('eventId').value = e.id;
        document.getElementById('eventDate').value = e.date;
        document.getElementById('eventTime').value = e.time || '';
        document.getElementById('eventTitle').value = e.title;
        document.getElementById('eventDesc').value = e.description || '';
        document.getElementById('deleteEventBtn').classList.remove('d-none');
        eventModal.show();
      }
    
      document.getElementById('deleteEventBtn').addEventListener('click', ()=>{
        const id = document.getElementById('eventId').value;
        if(!id) return;
        if(confirm('Na pewno usunąć zdarzenie?')){ removeEvent(id); eventModal.hide(); buildCalendar(); renderEventsList(); }
      });
    
      eventForm.addEventListener('submit', (ev)=>{
        ev.preventDefault();
        const id = document.getElementById('eventId').value || ('e_'+Date.now());
        const data = {
          id,
          date: document.getElementById('eventDate').value,
          time: document.getElementById('eventTime').value,
          title: document.getElementById('eventTitle').value.trim(),
          description: document.getElementById('eventDesc').value.trim()
        };
        if(document.getElementById('eventId').value) updateEvent(data);
        else addEvent(data);
        eventModal.hide();
        buildCalendar();
        if(state.selectedDate === data.date) renderEventsList();
      });
    
      // --- Navigation ---
      document.getElementById('prevMonth').addEventListener('click', ()=>{ state.cursor.setMonth(state.cursor.getMonth()-1); buildCalendar(); });
      document.getElementById('nextMonth').addEventListener('click', ()=>{ state.cursor.setMonth(state.cursor.getMonth()+1); buildCalendar(); });
      document.getElementById('todayBtn').addEventListener('click', ()=>{ state.cursor = new Date(); state.selectedDate = fmtDate(state.cursor); selectDate(state.selectedDate); buildCalendar(); });
    
      // --- Export / Import ---
      document.getElementById('exportBtn').addEventListener('click', ()=>{
        const data = { exportedAt: new Date().toISOString(), events: loadEvents() };
        const blob = new Blob([JSON.stringify(data, null, 2)], {type:'application/json'});
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a'); a.href = url; a.download = 'calendar-events.json'; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
      });
    
      document.getElementById('importBtn').addEventListener('click', ()=> document.getElementById('importFile').click());
      document.getElementById('importFile').addEventListener('change', (e)=>{
        const f = e.target.files[0]; if(!f) return; const reader = new FileReader();
        reader.onload = function(){
          try{
            const parsed = JSON.parse(reader.result);
            if(!parsed.events) throw new Error('Brak pola events w pliku');
            // merge uniquely by id
            const existing = loadEvents();
            const map = new Map(existing.map(x=> [x.id,x]));
            parsed.events.forEach(x=> map.set(x.id,x));
            saveEvents(Array.from(map.values()));
            alert('Import zakończony');
            buildCalendar(); if(state.selectedDate) renderEventsList();
          }catch(err){ alert('Błąd importu: '+ err.message); }
        };
        reader.readAsText(f);
        e.target.value = '';
      });
    
      document.getElementById('clearStorageBtn').addEventListener('click', ()=>{
        if(confirm('Usunąć wszystkie zdarzenia z LocalStorage?')){ localStorage.removeItem(state.eventsKey); buildCalendar(); renderEventsList(); }
      });
    
      // --- Init ---
      (function init(){
        // select today by default
        state.selectedDate = fmtDate(new Date());
        selectDate(state.selectedDate);
        buildCalendar();
      })();
    
      </script>
    

    Kod po stronie serwera

    Brak kodu serwera

    Ta aplikacja działa wyłącznie w przeglądarce i nie korzysta z kodu po stronie serwera.

    16 listopada 2025 2

    Kategorie

    Technologie

    Dziękujemy!
    ()

    Informacja o cookies

    Moja strona internetowa wykorzystuje wyłącznie niezbędne pliki cookies, które są wymagane do jej prawidłowego działania. Nie używam ciasteczek w celach marketingowych ani analitycznych. Korzystając z mojej strony, wyrażasz zgodę na stosowanie tych plików. Możesz dowiedzieć się więcej w mojej polityce prywatności.