zhurnal-vydachi-spetsodezhdy/script.js

329 lines
11 KiB
JavaScript
Raw Permalink 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.

// Демо-данные: 10 записей с реалистичными именами и датами
const DEMO_DATA = [
{ id: 1, fio: "Алиев А.С.", item: "Каска защитная", issueDate: "2025-01-15", returnDate: "2025-07-15", status: "В носке" },
{ id: 2, fio: "Смирнова Е.В.", item: "Ботинки кожаные", issueDate: "2025-02-01", returnDate: "2025-06-01", status: "Просрочено" },
{ id: 3, fio: "Ибрагимов Б.К.", item: "Костюм х/б", issueDate: "2025-03-10", returnDate: "2025-09-10", status: "В носке" },
{ id: 4, fio: "Петров И.Д.", item: "Перчатки рабочие", issueDate: "2025-01-20", returnDate: "2025-04-20", status: "Просрочено" },
{ id: 5, fio: "Ахметова Г.Т.", item: "Очки защитные", issueDate: "2025-04-05", returnDate: "2025-10-05", status: "В носке" },
{ id: 6, fio: "Козлов М.А.", item: "Респиратор", issueDate: "2025-02-15", returnDate: "2025-05-15", status: "Сдано" },
{ id: 7, fio: "Нурланова А.Е.", item: "Каска защитная", issueDate: "2025-05-01", returnDate: "2025-11-01", status: "В носке" },
{ id: 8, fio: "Васильченко П.П.", item: "Наушники противошумные", issueDate: "2025-03-20", returnDate: "2025-08-20", status: "Просрочено" },
{ id: 9, fio: "Омаров Д.Ж.", item: "Жилет сигнальный", issueDate: "2025-06-01", returnDate: "2025-12-01", status: "В носке" },
{ id: 10, fio: "Тимофеева О.Л.", item: "Сапоги резиновые", issueDate: "2025-01-10", returnDate: "2025-03-10", status: "Сдано" }
];
// Ключи для localStorage
const STORAGE_KEY = "specod_storage_v1";
const LOG_KEY = "specod_log_v1";
// Текущие данные
let items = [];
let logs = [];
let currentFilter = "Все";
// Инициализация при загрузке
document.addEventListener("DOMContentLoaded", () => {
loadData();
renderFilters();
renderTable();
renderLog();
updateCounts();
});
// Загрузка данных из localStorage или демо
function loadData() {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
items = JSON.parse(stored);
} else {
items = [...DEMO_DATA];
saveToStorage();
}
const storedLog = localStorage.getItem(LOG_KEY);
if (storedLog) {
logs = JSON.parse(storedLog);
} else {
logs = [{ type: "add", message: "Система инициализирована с демо-данными", timestamp: new Date().toISOString() }];
saveLogToStorage();
}
}
function saveToStorage() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
}
function saveLogToStorage() {
localStorage.setItem(LOG_KEY, JSON.stringify(logs));
}
// Фильтры
function renderFilters() {
const filters = ["Все", "В носке", "Сдано", "Просрочено"];
const container = document.getElementById("filters");
container.innerHTML = filters.map(f =>
`<button class="filter-chip ${f === currentFilter ? "active" : ""}" onclick="setFilter('${f}')">${f}</button>`
).join("");
}
function setFilter(filter) {
currentFilter = filter;
renderFilters();
renderTable();
}
// Таблица
function renderTable() {
const tbody = document.getElementById("table-body");
const search = document.getElementById("search").value.toLowerCase();
let filtered = items.filter(item => {
// Обновляем статус просрочки
item = updateItemStatus(item);
// Фильтр по статусу
if (currentFilter !== "Все" && item.status !== currentFilter) return false;
// Поиск
if (search) {
return item.fio.toLowerCase().includes(search) || item.item.toLowerCase().includes(search);
}
return true;
});
if (filtered.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="7" class="empty-state">
<div class="empty-state-icon">📋</div>
<p>Нет записей для отображения</p>
</td>
</tr>
`;
updateCounts();
return;
}
tbody.innerHTML = filtered.map((item, idx) => {
const isOverdue = item.status === "Просрочено";
return `
<tr class="${isOverdue ? "row-overdue" : ""}">
<td class="col-num">${idx + 1}</td>
<td class="col-fio">${escapeHtml(item.fio)}</td>
<td class="col-item">${escapeHtml(item.item)}</td>
<td class="col-date">${formatDate(item.issueDate)}</td>
<td class="col-date">${formatDate(item.returnDate)}</td>
<td class="col-status">
<span class="status-badge ${getStatusClass(item.status)}">${item.status}</span>
</td>
<td class="col-actions">
<button class="action-btn edit" onclick="editItem(${item.id})">✏️ Изменить</button>
<button class="action-btn delete" onclick="deleteItem(${item.id})">🗑️ Удалить</button>
</td>
</tr>
`;
}).join("");
updateCounts();
}
// Обновление статуса на основе даты
function updateItemStatus(item) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const returnDate = new Date(item.returnDate);
if (item.status !== "Сдано" && returnDate < today) {
item.status = "Просрочено";
}
return item;
}
function getStatusClass(status) {
switch (status) {
case "В носке": return "status-worn";
case "Сдано": return "status-returned";
case "Просрочено": return "status-overdue";
default: return "status-worn";
}
}
// Счётчики
function updateCounts() {
// Пересчитываем статусы перед подсчётом
items = items.map(updateItemStatus);
saveToStorage();
const total = items.length;
const worn = items.filter(i => i.status === "В носке").length;
const returned = items.filter(i => i.status === "Сдано").length;
const overdue = items.filter(i => i.status === "Просрочено").length;
document.getElementById("count-total").textContent = total;
document.getElementById("count-worn").textContent = worn;
document.getElementById("count-returned").textContent = returned;
document.getElementById("count-overdue").textContent = overdue;
}
// Модальное окно
function openModal(isEdit = false) {
document.getElementById("modal").classList.remove("hidden");
document.getElementById("modal-title").textContent = isEdit ? "Редактирование выдачи" : "Новая выдача";
if (!isEdit) {
document.getElementById("form").reset();
document.getElementById("edit-id").value = "";
// Устанавливаем сегодняшнюю дату по умолчанию
document.getElementById("form-issue-date").value = new Date().toISOString().split("T")[0];
}
}
function closeModal() {
document.getElementById("modal").classList.add("hidden");
}
// Сохранение (добавление/редактирование)
function saveItem(event) {
event.preventDefault();
const id = document.getElementById("edit-id").value;
const fio = document.getElementById("form-fio").value.trim();
const item = document.getElementById("form-item").value.trim();
const issueDate = document.getElementById("form-issue-date").value;
const returnDate = document.getElementById("form-return-date").value;
if (id) {
// Редактирование
const idx = items.findIndex(i => i.id == id);
if (idx !== -1) {
const oldItem = { ...items[idx] };
items[idx] = { ...items[idx], fio, item, issueDate, returnDate };
logs.unshift({
type: "edit",
message: `Изменено: ${oldItem.fio} — ${oldItem.item}`,
timestamp: new Date().toISOString()
});
}
} else {
// Добавление
const newId = Date.now();
let status = "В носке";
const today = new Date();
today.setHours(0, 0, 0, 0);
const retDate = new Date(returnDate);
if (retDate < today) status = "Просрочено";
items.unshift({ id: newId, fio, item, issueDate, returnDate, status });
logs.unshift({
type: "add",
message: `Добавлено: ${fio} — ${item}`,
timestamp: new Date().toISOString()
});
}
saveToStorage();
saveLogToStorage();
closeModal();
renderTable();
renderLog();
}
// Редактирование
function editItem(id) {
const item = items.find(i => i.id === id);
if (!item) return;
document.getElementById("edit-id").value = item.id;
document.getElementById("form-fio").value = item.fio;
document.getElementById("form-item").value = item.item;
document.getElementById("form-issue-date").value = item.issueDate;
document.getElementById("form-return-date").value = item.returnDate;
openModal(true);
}
// Удаление
function deleteItem(id) {
const item = items.find(i => i.id === id);
if (!item) return;
if (confirm(`Вы уверены, что хотите удалить запись:\n${item.fio} — ${item.item}?`)) {
items = items.filter(i => i.id !== id);
logs.unshift({
type: "delete",
message: `Удалено: ${item.fio} — ${item.item}`,
timestamp: new Date().toISOString()
});
saveToStorage();
saveLogToStorage();
renderTable();
renderLog();
}
}
// Журнал изменений
function renderLog() {
const container = document.getElementById("log-container");
if (logs.length === 0) {
container.innerHTML = '<div class="text-gray-500 text-sm">История пуста</div>';
return;
}
container.innerHTML = logs.map(log => {
const date = new Date(log.timestamp);
const dateStr = date.toLocaleDateString("ru-RU") + " " + date.toLocaleTimeString("ru-RU", { hour: "2-digit", minute: "2-digit" });
return `
<div class="log-entry ${log.type}">
<span class="font-medium">${log.message}</span>
<span class="text-gray-500 text-xs ml-2">(${dateStr})</span>
</div>
`;
}).join("");
}
// Выгрузка в Excel (CSV с BOM)
function exportToExcel() {
const head = ["№", "ФИО", "Наименование", "Дата выдачи", "Дата возврата", "Статус"];
const rows = items.map((item, idx) => [
idx + 1,
item.fio,
item.item,
formatDate(item.issueDate),
formatDate(item.returnDate),
item.status
]);
const csvContent = "\uFEFF" + [
head.join(";"),
...rows.map(r => r.map(v => `"${String(v).replace(/"/g, '""')}"`).join(";"))
].join("\n");
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = `zhurnal_${new Date().toISOString().slice(0, 10)}.csv`;
link.click();
}
// Утилиты
function formatDate(dateStr) {
if (!dateStr) return "";
const [year, month, day] = dateStr.split("-");
return `${day}.${month}.${year}`;
}
function escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
// Закрытие модального окна по клику вне
document.getElementById("modal").addEventListener("click", (e) => {
if (e.target.id === "modal") closeModal();
});