const STORAGE_KEY = "kindergarten_crm_children_v1";
const STATUS_LABELS = {
studying: "Учится",
sick: "Больничный",
vacation: "Отпуск"
};
const PAYMENT_LABELS = {
paid: "Оплачено",
current: "Актуально",
overdue: "Долг"
};
const DEMO_DATA = [
{ id: 1, name: "Айгерим Ахметов", group: "Солнышко", birth: "2021-04-12", parent: "Ахметова Л.К.", phone: "+7 700 111 22 33", status: "studying", payment: "paid", note: "Аллергия на цитрусовые" },
{ id: 2, name: "Бекзат Сауыт", group: "Солнышко", birth: "2021-07-30", parent: "Сауытов Д.Б.", phone: "+7 701 222 33 44", status: "sick", payment: "current", note: "Частые ОРВИ, нужен щадящий режим" },
{ id: 3, name: "Виктория Мельник", group: "Любимчики", birth: "2020-01-25", parent: "Мельник А.С.", phone: "+7 702 333 44 55", status: "studying", payment: "overdue", note: "Долг за сентябрь" },
{ id: 4, name: "Гульнара Сейитова", group: "Любимчики", birth: "2020-09-08", parent: "Сейитова Н.Ж.", phone: "+7 703 444 55 66", status: "vacation", payment: "paid", note: "Отпуск до 15 ноября" },
{ id: 5, name: "Дамир Тлеуов", group: "Ромашки", birth: "2019-11-17", parent: "Тлеуов Б.С.", phone: "+7 704 555 66 77", status: "studying", payment: "paid", note: "" },
{ id: 6, name: "Ева Соколова", group: "Ромашки", birth: "2019-03-04", parent: "Соколова И.А.", phone: "+7 705 666 77 88", status: "studying", payment: "current", note: "Нужен контроль приёма лекарств в 14:00" },
{ id: 7, name: "Жанибек Нурланов", group: "Солнышко", birth: "2021-12-21", parent: "Нурланова С.Б.", phone: "+7 706 777 88 99", status: "studying", payment: "paid", note: "" },
{ id: 8, name: "Катя Иванова", group: "Любимчики", birth: "2020-06-14", parent: "Иванов П.И.", phone: "+7 707 888 99 00", status: "sick", payment: "paid", note: "Больничный до 25 сентября" }
];
let children = [];
let activeStatusFilter = "all";
let searchQuery = "";
function loadChildren() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed) && parsed.length > 0) {
children = parsed;
return;
}
}
} catch (e) {
console.warn("Не удалось загрузить данные, используется демо", e);
}
children = JSON.parse(JSON.stringify(DEMO_DATA));
saveChildren();
}
function saveChildren() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(children));
}
function getFilteredChildren() {
const q = searchQuery.toLowerCase().trim();
return children.filter((c) => {
const matchesStatus = activeStatusFilter === "all" || c.status === activeStatusFilter;
if (!matchesStatus) return false;
if (!q) return true;
const haystack = [c.name, c.group, c.parent, c.phone, c.note].filter(Boolean).join(" ").toLowerCase();
return haystack.includes(q);
});
}
function formatDate(iso) {
if (!iso) return "";
const [y, m, d] = iso.split("-");
return `${d}.${m}.${y}`;
}
function renderKpis() {
const total = children.length;
const studying = children.filter((c) => c.status === "studying").length;
const sick = children.filter((c) => c.status === "sick").length;
const overdue = children.filter((c) => c.payment === "overdue").length;
const attendancePct = total ? Math.round((studying / total) * 100) : 0;
document.getElementById("kpis").innerHTML = `
Всего детей
${total}
в саду
Сегодня в группе
${studying}
${attendancePct}% посещаемость
На больничном
${sick}
отсутствуют
Долги по оплате
${overdue}
запросить родителю
`;
}
function renderFilters() {
const statuses = ["all", "studying", "sick", "vacation"];
const labels = { all: "Все", studying: "Учатся", sick: "Больничный", vacation: "Отпуск" };
document.getElementById("status-filters").innerHTML = statuses
.map((s) => {
const count = s === "all" ? children.length : children.filter((c) => c.status === s).length;
const active = activeStatusFilter === s ? "active" : "";
return ``;
})
.join("");
document.getElementById("status-filters").querySelectorAll("[data-status]").forEach((btn) => {
btn.addEventListener("click", () => {
activeStatusFilter = btn.dataset.status;
renderAll();
});
});
}
function renderTable() {
const list = getFilteredChildren();
const tbody = document.getElementById("tbody");
const empty = document.getElementById("empty");
if (list.length === 0) {
tbody.innerHTML = "";
empty.hidden = false;
} else {
empty.hidden = true;
tbody.innerHTML = list
.map((c) => {
const birthDate = c.birth ? formatDate(c.birth) : "";
return `
|
${c.name}
${c.birth ? `р. ${birthDate} ` : ""}
|
${c.group} |
${c.parent || ""}
${c.phone ? `${c.phone} ` : ""}
|
${STATUS_LABELS[c.status] || c.status} |
${PAYMENT_LABELS[c.payment] || c.payment} |
|
`;
})
.join("");
}
document.getElementById("count-label").textContent = `Показано ${list.length} из ${children.length}`;
tbody.querySelectorAll("[data-action]").forEach((btn) => {
const id = Number(btn.closest("tr").dataset.id);
btn.addEventListener("click", () => {
if (btn.dataset.action === "edit") openDrawer(id);
if (btn.dataset.action === "delete") removeChild(id);
});
});
}
function renderAll() {
renderKpis();
renderFilters();
renderTable();
}
function openDrawer(id = null) {
const drawer = document.getElementById("drawer");
const title = document.getElementById("drawer-title");
const form = document.getElementById("child-form");
form.reset();
document.getElementById("field-id").value = "";
if (id !== null) {
const child = children.find((c) => c.id === id);
if (!child) return;
title.textContent = "Ребёнок";
document.getElementById("field-id").value = child.id;
document.getElementById("field-name").value = child.name;
document.getElementById("field-group").value = child.group;
document.getElementById("field-birth").value = child.birth || "";
document.getElementById("field-parent").value = child.parent || "";
document.getElementById("field-phone").value = child.phone || "";
document.getElementById("field-status").value = child.status;
document.getElementById("field-payment").value = child.payment;
document.getElementById("field-note").value = child.note || "";
} else {
title.textContent = "Новый ребёнок";
}
drawer.hidden = false;
document.getElementById("field-name").focus();
}
function closeDrawer() {
document.getElementById("drawer").hidden = true;
}
function saveChild(e) {
e.preventDefault();
const id = document.getElementById("field-id").value;
const data = {
name: document.getElementById("field-name").value.trim(),
group: document.getElementById("field-group").value,
birth: document.getElementById("field-birth").value,
parent: document.getElementById("field-parent").value.trim(),
phone: document.getElementById("field-phone").value.trim(),
status: document.getElementById("field-status").value,
payment: document.getElementById("field-payment").value,
note: document.getElementById("field-note").value.trim()
};
if (!data.name) return;
if (id) {
const idx = children.findIndex((c) => c.id === Number(id));
if (idx !== -1) {
children[idx] = { ...children[idx], ...data };
showToast("Данные обновлены");
}
} else {
children.push({ id: Date.now(), ...data });
showToast("Ребёнок добавлен");
}
saveChildren();
renderAll();
closeDrawer();
}
function removeChild(id) {
const child = children.find((c) => c.id === id);
if (!child) return;
if (!confirm(`Удалить ${child.name}?`)) return;
children = children.filter((c) => c.id !== id);
saveChildren();
renderAll();
showToast("Запись удалена");
}
function exportCsv() {
const list = getFilteredChildren();
if (list.length === 0) {
showToast("Нет данных для выгрузки");
return;
}
const head = ["Имя ребёнка", "Группа", "Дата рождения", "Родитель", "Телефон", "Статус", "Оплата", "Заметка"];
const body = list.map((c) =>
[
c.name,
c.group,
c.birth ? formatDate(c.birth) : "",
c.parent,
c.phone,
STATUS_LABELS[c.status] || c.status,
PAYMENT_LABELS[c.payment] || c.payment,
c.note
]
.map((v) => '"' + String(v || "").replace(/"/g, '""') + '"')
.join(";")
);
const csv = "\uFEFF" + [head.join(";"), ...body].join("\n");
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([csv], { type: "text/csv" }));
a.download = "deti-detsad.csv";
a.click();
showToast("Файл сформирован");
}
function showToast(message) {
const stack = document.getElementById("toasts");
const el = document.createElement("div");
el.className = "kt-ai-toast";
el.textContent = message;
stack.appendChild(el);
setTimeout(() => el.remove(), 2600);
}
function init() {
loadChildren();
renderAll();
document.getElementById("btn-new").addEventListener("click", () => openDrawer());
document.getElementById("btn-drawer-close").addEventListener("click", closeDrawer);
document.getElementById("btn-cancel").addEventListener("click", closeDrawer);
document.getElementById("child-form").addEventListener("submit", saveChild);
document.getElementById("btn-export").addEventListener("click", exportCsv);
document.getElementById("btn-clear-filters").addEventListener("click", () => {
activeStatusFilter = "all";
searchQuery = "";
document.getElementById("search").value = "";
renderAll();
});
document.getElementById("search").addEventListener("input", (e) => {
searchQuery = e.target.value;
renderTable();
});
}
document.addEventListener("DOMContentLoaded", init);