Kalendarz Miesięczny
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
.jsonjednym 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
| Pn | Wt | Śr | Cz | Pt | So | Nd |
|---|
Szczegóły dnia
Wybierz dzień
Dane astronomiczne
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"><</button>
<button id="todayBtn" class="btn btn-outline-secondary btn-sm">Dziś</button>
<button id="nextMonth" class="btn btn-outline-secondary btn-sm">></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=> ({'&':'&','<':'<','>':'>','\\':'\\\\','"':'"'}[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.
Licencja
## BSD-3-Clause License Agreement
BSD-3-Clause
Сopyright (c) 2026 Dariusz Rorat
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.