kalendar/script.js
2026-07-31 10:09:36 +00:00

202 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var WEEKDAYS = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"];
var MONTHS = [
"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь",
"Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"
];
var now = new Date();
var view = { year: now.getFullYear(), month: now.getMonth() };
var openDate = null;
var allByDate = {};
var gridEl = document.getElementById("grid");
var monthNameEl = document.getElementById("monthName");
var panelBody = document.getElementById("panelBody");
var panelEmpty = document.getElementById("panelEmpty");
var panelDateEl = document.getElementById("panelDate");
var notesEl = document.getElementById("notes");
var countEl = document.getElementById("count");
function pad(n) { return (n < 10 ? "0" : "") + n; }
function fmt(d) {
return pad(d.getDate()) + "." + pad(d.getMonth() + 1) + "." + d.getFullYear();
}
function iso(date) {
return date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate());
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, function (c) {
return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
});
}
function renderWeekdays() {
document.getElementById("weekdays").innerHTML =
WEEKDAYS.map(function (w) { return "<span>" + w + "</span>"; }).join("");
}
function render() {
monthNameEl.textContent = MONTHS[view.month] + " " + view.year;
var first = new Date(view.year, view.month, 1);
var startPad = (first.getDay() + 6) % 7;
var daysInMonth = new Date(view.year, view.month + 1, 0).getDate();
var prevDays = new Date(view.year, view.month, 0).getDate();
var html = "";
for (var i = startPad - 1; i >= 0; i--) {
html += '<div class="day empty">' + (prevDays - i) + "</div>";
}
for (var d = 1; d <= daysInMonth; d++) {
var date = new Date(view.year, view.month, d);
var key = iso(date);
var cls = "day";
var isToday =
date.getFullYear() === now.getFullYear() &&
date.getMonth() === now.getMonth() &&
date.getDate() === now.getDate();
if (isToday) cls += " today";
if (openDate === key) cls += " selected";
var has = (allByDate[key] || []).length > 0;
var dot = has ? '<span class="dot"></span>' : '<span class="dot" style="visibility:hidden"></span>';
html += '<div class="' + cls + '" data-key="' + key + '">' + d + dot + "</div>";
}
var endPad = 7 - ((startPad + daysInMonth) % 7);
if (endPad < 7) {
for (var j = 1; j <= endPad; j++) {
html += '<div class="day empty">' + j + "</div>";
}
}
gridEl.innerHTML = html;
}
function changeMonth(delta) {
view.month += delta;
if (view.month < 0) { view.month = 11; view.year--; }
if (view.month > 11) { view.month = 0; view.year++; }
render();
}
function goToday() {
now = new Date();
view = { year: now.getFullYear(), month: now.getMonth() };
render();
}
function api(url, opts) {
return fetch(url, opts).then(function (r) {
if (!r.ok) throw new Error("Ошибка сервера " + r.status);
return r.json();
});
}
function loadAll() {
return api("/api/notes").then(function (list) {
allByDate = {};
list.forEach(function (n) {
(allByDate[n.date] = allByDate[n.date] || []).push(n);
});
render();
if (openDate) renderPanel();
});
}
function renderPanel() {
var list = allByDate[openDate] || [];
notesEl.innerHTML = list.map(function (n) {
return (
'<li class="note' + (n.done ? " done" : "") + '" data-id="' + n.id + '">' +
'<button class="chk' + (n.done ? " on" : "") + '" title="Отметить">' + (n.done ? "✓" : "") + "</button>" +
'<span class="note-text">' + escapeHtml(n.text) + "</span>" +
'<button class="del" title="Удалить">×</button>' +
"</li>"
);
}).join("");
countEl.textContent = list.length ? "Дела: " + list.length : "";
}
function openDay(key) {
openDate = key;
panelEmpty.classList.add("hidden");
panelBody.classList.remove("hidden");
var parts = key.split("-");
var date = new Date(parts[0], +parts[1] - 1, +parts[2]);
panelDateEl.textContent = fmt(date);
render();
renderPanel();
}
function closePanel() {
openDate = null;
panelBody.classList.add("hidden");
panelEmpty.classList.remove("hidden");
render();
}
function addNote(text) {
return api("/api/notes", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ date: openDate, text: text })
}).then(loadAll);
}
function toggleNote(id, done) {
return api("/api/notes", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: id, done: done })
}).then(loadAll);
}
function deleteNote(id) {
return api("/api/notes/" + id, { method: "DELETE" }).then(loadAll);
}
gridEl.addEventListener("click", function (e) {
var day = e.target.closest(".day");
if (!day || day.classList.contains("empty")) return;
openDay(day.dataset.key);
});
notesEl.addEventListener("click", function (e) {
var li = e.target.closest(".note");
if (!li) return;
var id = +li.dataset.id;
var item = (allByDate[openDate] || []).find(function (n) { return n.id === id; });
if (!item) return;
if (e.target.classList.contains("del")) {
if (confirm("Удалить это дело?")) deleteNote(id);
return;
}
if (e.target.classList.contains("chk")) {
toggleNote(id, !item.done);
}
});
document.getElementById("addForm").addEventListener("submit", function (e) {
e.preventDefault();
var input = document.getElementById("noteText");
var text = input.value.trim();
if (!text || !openDate) return;
addNote(text);
input.value = "";
});
document.getElementById("prev").addEventListener("click", function () { changeMonth(-1); });
document.getElementById("next").addEventListener("click", function () { changeMonth(1); });
document.getElementById("today").addEventListener("click", goToday);
document.getElementById("closePanel").addEventListener("click", closePanel);
renderWeekdays();
render();
loadAll();