cash-report-system/frontend/script.js
2025-07-25 01:24:14 +02:00

1925 lines
70 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.

// Глобальные переменные
let currentUser = null;
let editingReportId = null;
let editingUserId = null;
let editingStoreId = null;
let editingTodoId = null;
// База данных (симуляция)
let database = {
users: [
{
id: 1,
username: "admin",
password: "admin123",
role: "admin",
stores: [],
},
{
id: 2,
username: "employee",
password: "password123",
role: "employee",
stores: [1, 2],
},
{
id: 3,
username: "cashier1",
password: "password123",
role: "employee",
stores: [1, 2],
},
{
id: 4,
username: "cashier2",
password: "password123",
role: "employee",
stores: [3],
},
],
stores: [
{ id: 1, name: "Магазин 1" },
{ id: 2, name: "Магазин 2" },
{ id: 3, name: "Магазин 3" },
{ id: 4, name: "Магазин 4" },
],
reports: [],
todos: [
{
id: 1,
title: "Исправить модальные окна",
description: "Исправить прокрутку и видимость кнопок в модальных окнах",
completed: true,
priority: "high",
createdAt: "2024-01-15",
},
{
id: 2,
title: "Добавить экспорт в PDF",
description: "Реализовать функцию экспорта отчетов в PDF формат",
completed: false,
priority: "medium",
createdAt: "2024-01-16",
},
{
id: 3,
title: "Улучшить графики",
description: "Добавить больше интерактивности в графики Dashboard",
completed: false,
priority: "low",
createdAt: "2024-01-17",
},
],
};
// Генерация тестовых данных для отчетов
function generateTestData() {
const reports = [];
const today = new Date();
for (let i = 0; i < 30; i++) {
const date = new Date(today);
date.setDate(date.getDate() - i);
const storeId = Math.floor(Math.random() * 4) + 1;
const userId = Math.floor(Math.random() * 3) + 2; // employee пользователи
const income = Math.floor(Math.random() * 2000) + 500;
const cajaInicial = Math.floor(Math.random() * 300) + 100;
const totalIncome = income + cajaInicial;
const wages = Math.floor(Math.random() * 400) + 100;
const expenses = Math.floor(Math.random() * 200) + 50;
const totalExpenses = wages + expenses;
const envelope = Math.floor(Math.random() * 200) + 100;
const cajaFinal = totalIncome - totalExpenses - envelope;
reports.push({
id: i + 1,
date: date.toISOString().split("T")[0],
storeId: storeId,
userId: userId,
income: income,
cajaInicial: cajaInicial,
totalIncome: totalIncome,
wages: [
{
name: "Сотрудник " + (Math.floor(Math.random() * 3) + 1),
amount: wages,
},
],
expenses: [
{
name: "Расходы " + (Math.floor(Math.random() * 3) + 1),
amount: expenses,
},
],
totalWages: wages,
totalExpensesInternal: expenses,
totalExpenses: totalExpenses,
envelope: envelope,
cajaFinal: cajaFinal,
verified: Math.random() > 0.3,
createdAt: date.toISOString(),
});
}
database.reports = reports;
}
// Инициализация тестовых данных
generateTestData();
// Система уведомлений
function showNotification(message, type = "success") {
const notification = document.getElementById("notification");
const notificationText = document.getElementById("notificationText");
if (!notification || !notificationText) return;
notificationText.textContent = message;
notification.className = `fixed top-4 right-4 z-50 animate-fade-in`;
if (type === "error") {
notification.innerHTML = `
<div class="bg-red-500 text-white px-6 py-4 rounded-lg shadow-lg">
<div class="flex items-center">
<i class="fas fa-exclamation-triangle mr-2"></i>
<span>${message}</span>
</div>
</div>
`;
} else {
notification.innerHTML = `
<div class="bg-green-500 text-white px-6 py-4 rounded-lg shadow-lg">
<div class="flex items-center">
<i class="fas fa-check-circle mr-2"></i>
<span>${message}</span>
</div>
</div>
`;
}
notification.classList.remove("hidden");
setTimeout(() => {
notification.classList.add("hidden");
}, 3000);
}
// Управление модальными окнами с исправленной прокруткой
function showModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.add("show");
// Блокируем прокрутку body
document.body.classList.add("modal-open");
}
}
function hideModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.remove("show");
// Разблокируем прокрутку body
document.body.classList.remove("modal-open");
}
}
// Закрытие модального окна при клике вне его
document.addEventListener("click", (e) => {
if (e.target.classList.contains("modal")) {
hideModal(e.target.id);
}
});
// Обработчики авторизации
// document.getElementById("loginForm").addEventListener("submit", (e) => {
// e.preventDefault();
// const username = document.getElementById("username").value;
// const password = document.getElementById("password").value;
// const user = database.users.find(
// (u) => u.username === username && u.password === password
// );
// if (user) {
// currentUser = user;
// document.getElementById("loginScreen").classList.add("hidden");
// if (user.role === "admin") {
// showAdminInterface();
// } else {
// showUserInterface();
// }
// showNotification("Успешная авторизация!");
// } else {
// const errorDiv = document.getElementById("loginError");
// errorDiv.textContent = "Неверный логин или пароль";
// errorDiv.classList.remove("hidden");
// }
// });
// Показать интерфейс пользователя
function showUserInterface() {
document.getElementById("userInterface").classList.remove("hidden");
document.getElementById(
"userWelcome"
).textContent = `Добро пожаловать, ${currentUser.username}!`;
loadUserStores();
setupFormCalculations();
}
// Показать интерфейс администратора
function showAdminInterface() {
document.getElementById("adminInterface").classList.remove("hidden");
document.getElementById(
"adminWelcome"
).textContent = `Добро пожаловать, ${currentUser.username}!`;
updateDashboard();
loadReports();
loadUsers();
loadStores();
loadTodos();
setupAdminTabs();
}
// Загрузка магазинов для пользователя
function loadUserStores() {
const select = document.getElementById("storeSelect");
select.innerHTML = '<option value="">Выберите магазин</option>';
let userStores = [];
if (currentUser.role === "admin") {
userStores = database.stores;
} else {
userStores = database.stores.filter((store) =>
currentUser.stores.includes(store.id)
);
}
userStores.forEach((store) => {
const option = document.createElement("option");
option.value = store.id;
option.textContent = store.name;
select.appendChild(option);
});
}
// Настройка автоматических расчетов в форме
function setupFormCalculations() {
const incomeInput = document.getElementById("income");
const cajaInicialInput = document.getElementById("cajaInicial");
const envelopeInput = document.getElementById("envelope");
function updateCalculations() {
const income = parseFloat(incomeInput.value) || 0;
const cajaInicial = parseFloat(cajaInicialInput.value) || 0;
const envelope = parseFloat(envelopeInput.value) || 0;
const totalIncome = income + cajaInicial;
document.getElementById("totalIncome").value = totalIncome.toFixed(2);
document.getElementById("displayTotalIncome").value =
totalIncome.toFixed(2);
const totalWages = calculateTotalWages();
const totalExpensesInternal = calculateTotalExpenses();
const totalExpenses = totalWages + totalExpensesInternal;
document.getElementById("totalExpenses").value = totalExpenses.toFixed(2);
const cajaFinal = totalIncome - totalExpenses - envelope;
document.getElementById("cajaFinal").textContent = cajaFinal.toFixed(2);
}
incomeInput.addEventListener("input", updateCalculations);
cajaInicialInput.addEventListener("input", updateCalculations);
envelopeInput.addEventListener("input", updateCalculations);
setupDynamicRows();
}
// Настройка динамических строк
function setupDynamicRows() {
document.getElementById("addWage").addEventListener("click", () => {
addWageRow();
});
document.getElementById("addExpense").addEventListener("click", () => {
addExpenseRow();
});
// Обновление при изменении значений
document.addEventListener("input", (e) => {
if (
e.target.classList.contains("wage-amount") ||
e.target.classList.contains("expense-amount")
) {
updateTotals();
}
});
// Удаление строк
document.addEventListener("click", (e) => {
if (e.target.classList.contains("remove-wage")) {
e.target.closest(".wage-row").remove();
updateTotals();
} else if (e.target.classList.contains("remove-expense")) {
e.target.closest(".expense-row").remove();
updateTotals();
}
});
}
function addWageRow() {
const container = document.getElementById("wagesContainer");
const row = document.createElement("div");
row.className = "wage-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3";
row.innerHTML = `
<input type="text" placeholder="Имя сотрудника" class="wage-name form-input px-3 py-2 rounded-lg">
<input type="number" step="0.01" placeholder="Сумма €" class="wage-amount form-input px-3 py-2 rounded-lg">
<button type="button" class="remove-wage bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(row);
}
function addExpenseRow() {
const container = document.getElementById("expensesContainer");
const row = document.createElement("div");
row.className = "expense-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3";
row.innerHTML = `
<input type="text" placeholder="Название расхода" class="expense-name form-input px-3 py-2 rounded-lg">
<input type="number" step="0.01" placeholder="Сумма €" class="expense-amount form-input px-3 py-2 rounded-lg">
<button type="button" class="remove-expense bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
<i class="fas fa-times"></i>
</button>
`;
container.appendChild(row);
}
function calculateTotalWages() {
const amounts = document.querySelectorAll(".wage-amount");
let total = 0;
amounts.forEach((amount) => {
total += parseFloat(amount.value) || 0;
});
return total;
}
function calculateTotalExpenses() {
const amounts = document.querySelectorAll(".expense-amount");
let total = 0;
amounts.forEach((amount) => {
total += parseFloat(amount.value) || 0;
});
return total;
}
function updateTotals() {
const totalWages = calculateTotalWages();
const totalExpensesInternal = calculateTotalExpenses();
document.getElementById("totalWages").textContent = totalWages.toFixed(2);
document.getElementById("totalExpensesInternal").textContent =
totalExpensesInternal.toFixed(2);
const totalExpenses = totalWages + totalExpensesInternal;
document.getElementById("totalExpenses").value = totalExpenses.toFixed(2);
// Обновить Caja Final
const income = parseFloat(document.getElementById("income").value) || 0;
const cajaInicial =
parseFloat(document.getElementById("cajaInicial").value) || 0;
const envelope = parseFloat(document.getElementById("envelope").value) || 0;
const totalIncome = income + cajaInicial;
const cajaFinal = totalIncome - totalExpenses - envelope;
document.getElementById("cajaFinal").textContent = cajaFinal.toFixed(2);
}
// Отправка формы отчета
document.getElementById("reportForm").addEventListener("submit", (e) => {
e.preventDefault();
const formData = {
id: editingReportId || Date.now(),
date: new Date().toISOString().split("T")[0],
storeId: parseInt(document.getElementById("storeSelect").value),
userId: currentUser.id,
income: parseFloat(document.getElementById("income").value),
cajaInicial: parseFloat(document.getElementById("cajaInicial").value),
totalIncome: parseFloat(document.getElementById("totalIncome").value),
envelope: parseFloat(document.getElementById("envelope").value),
verified: false,
createdAt: new Date().toISOString(),
};
// Сбор зарплат
const wages = [];
const wageRows = document.querySelectorAll(".wage-row");
wageRows.forEach((row) => {
const name = row.querySelector(".wage-name").value;
const amount = parseFloat(row.querySelector(".wage-amount").value) || 0;
if (name && amount > 0) {
wages.push({ name, amount });
}
});
// Сбор расходов
const expenses = [];
const expenseRows = document.querySelectorAll(".expense-row");
expenseRows.forEach((row) => {
const name = row.querySelector(".expense-name").value;
const amount = parseFloat(row.querySelector(".expense-amount").value) || 0;
if (name && amount > 0) {
expenses.push({ name, amount });
}
});
formData.wages = wages;
formData.expenses = expenses;
formData.totalWages = calculateTotalWages();
formData.totalExpensesInternal = calculateTotalExpenses();
formData.totalExpenses = formData.totalWages + formData.totalExpensesInternal;
formData.cajaFinal =
formData.totalIncome - formData.totalExpenses - formData.envelope;
if (editingReportId) {
const index = database.reports.findIndex((r) => r.id === editingReportId);
database.reports[index] = formData;
editingReportId = null;
showNotification("Отчет успешно обновлен!");
} else {
database.reports.push(formData);
showNotification("Отчет успешно сохранен!");
}
// Очистка формы
document.getElementById("reportForm").reset();
document.getElementById("wagesContainer").innerHTML = `
<div class="wage-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3">
<input type="text" placeholder="Имя сотрудника" class="wage-name form-input px-3 py-2 rounded-lg">
<input type="number" step="0.01" placeholder="Сумма €" class="wage-amount form-input px-3 py-2 rounded-lg">
<button type="button" class="remove-wage bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
<i class="fas fa-times"></i>
</button>
</div>
`;
document.getElementById("expensesContainer").innerHTML = `
<div class="expense-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3">
<input type="text" placeholder="Название расхода" class="expense-name form-input px-3 py-2 rounded-lg">
<input type="number" step="0.01" placeholder="Сумма €" class="expense-amount form-input px-3 py-2 rounded-lg">
<button type="button" class="remove-expense bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
<i class="fas fa-times"></i>
</button>
</div>
`;
updateTotals();
});
// Отчет за сегодня для пользователя
document.getElementById("todayReportBtn").addEventListener("click", () => {
const today = new Date().toISOString().split("T")[0];
const todayReport = database.reports.find(
(r) => r.date === today && r.userId === currentUser.id
);
if (todayReport) {
showReportModal(todayReport, false); // false = не админ режим
} else {
showNotification("Отчет за сегодня не найден", "error");
}
});
// Показ модального окна отчета с исправленной прокруткой
function showReportModal(report, isAdmin = false) {
const modal = document.getElementById("reportViewModal");
const content = document.getElementById("reportViewContent");
const buttons = document.getElementById("reportModalButtons");
const title = document.getElementById("reportModalTitle");
const store = database.stores.find((s) => s.id === report.storeId);
const user = database.users.find((u) => u.id === report.userId);
title.textContent = `Отчет от ${report.date} - ${
store ? store.name : "Неизвестный магазин"
}`;
// Формирование содержимого с красивыми стилями
content.innerHTML = `
<div class="space-y-6">
<!-- Основная информация -->
<div class="report-section">
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-info-circle mr-2"></i>Основная информация</h4>
<div class="grid grid-cols-2 gap-4 text-sm">
<div><strong>Дата:</strong> ${report.date}</div>
<div><strong>Магазин:</strong> ${
store ? store.name : "Неизвестно"
}</div>
<div><strong>Пользователь:</strong> ${
user ? user.username : "Неизвестно"
}</div>
<div><strong>Статус:</strong>
<span class="px-2 py-1 rounded text-xs ${
report.verified
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}">
${
report.verified
? "Проверен"
: "Не проверен"
}
</span>
</div>
</div>
</div>
<!-- Доходы -->
<div class="report-section income">
<h4 class="font-bold text-green-700 mb-3"><i class="fas fa-arrow-up mr-2"></i>Доходы (Ingresos)</h4>
<div class="grid grid-cols-3 gap-4 text-sm">
<div><strong>Income:</strong> €${report.income.toFixed(
2
)}</div>
<div><strong>Caja inicial:</strong> €${report.cajaInicial.toFixed(
2
)}</div>
<div><strong>Total income:</strong> €${report.totalIncome.toFixed(
2
)}</div>
</div>
</div>
<!-- Зарплаты -->
<div class="report-section wages">
<h4 class="font-bold text-yellow-700 mb-3"><i class="fas fa-users mr-2"></i>Зарплаты (Wages)</h4>
${
report.wages && report.wages.length > 0
? `
<div class="space-y-2">
${report.wages
.map(
(w) => `
<div class="flex justify-between text-sm">
<span>${w.name}</span>
<span>€${w.amount.toFixed(2)}</span>
</div>
`
)
.join("")}
<div class="border-t pt-2 font-bold">
<div class="flex justify-between">
<span>Total wages:</span>
<span>€${report.totalWages.toFixed(
2
)}</span>
</div>
</div>
</div>
`
: '<p class="text-gray-500 text-sm">Нет данных о зарплатах</p>'
}
</div>
<!-- Расходы -->
<div class="report-section expenses">
<h4 class="font-bold text-red-700 mb-3"><i class="fas fa-arrow-down mr-2"></i>Расходы (Expenses)</h4>
${
report.expenses && report.expenses.length > 0
? `
<div class="space-y-2">
${report.expenses
.map(
(e) => `
<div class="flex justify-between text-sm">
<span>${e.name}</span>
<span>€${e.amount.toFixed(2)}</span>
</div>
`
)
.join("")}
<div class="border-t pt-2 font-bold">
<div class="flex justify-between">
<span>Total expenses internal:</span>
<span>€${report.totalExpensesInternal.toFixed(
2
)}</span>
</div>
</div>
</div>
`
: '<p class="text-gray-500 text-sm">Нет данных о расходах</p>'
}
</div>
<!-- Итоговые расчеты -->
<div class="report-section">
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-calculator mr-2"></i>Итоговые расчеты</h4>
<div class="space-y-2">
<div class="flex justify-between"><strong>Total income:</strong> <span>€${report.totalIncome.toFixed(
2
)}</span></div>
<div class="flex justify-between"><strong>Total expenses:</strong> <span>€${report.totalExpenses.toFixed(
2
)}</span></div>
<div class="flex justify-between"><strong>Envelope:</strong> <span>€${report.envelope.toFixed(
2
)}</span></div>
<div class="flex justify-between border-t pt-2 text-lg font-bold text-blue-700">
<strong>Caja final:</strong> <span>€${report.cajaFinal.toFixed(
2
)}</span>
</div>
</div>
</div>
</div>
`;
// Настройка кнопок в зависимости от роли и статуса
buttons.innerHTML = "";
if (isAdmin) {
// Кнопки для администратора
buttons.innerHTML = `
<button id="editReportBtn" class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
<i class="fas fa-edit mr-2"></i>Редактировать
</button>
<button id="verifyReportBtn" class="btn-success text-white px-4 py-2 rounded-lg">
<i class="fas fa-check mr-2"></i>Подтвердить отчет
</button>
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
<i class="fas fa-times mr-2"></i>Закрыть
</button>
`;
document.getElementById("editReportBtn").addEventListener("click", () => {
editReport(report);
});
document.getElementById("verifyReportBtn").addEventListener("click", () => {
verifyReport(report.id);
});
} else {
// Кнопки для пользователя
if (!report.verified) {
buttons.innerHTML = `
<button id="editReportUserBtn" class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
<i class="fas fa-edit mr-2"></i>Редактировать
</button>
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
<i class="fas fa-times mr-2"></i>Закрыть
</button>
`;
document
.getElementById("editReportUserBtn")
.addEventListener("click", () => {
fillFormWithReport(report);
hideModal("reportViewModal");
});
} else {
buttons.innerHTML = `
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
<i class="fas fa-times mr-2"></i>Закрыть
</button>
`;
}
}
document
.getElementById("closeReportModalBtn")
.addEventListener("click", () => {
hideModal("reportViewModal");
});
showModal("reportViewModal");
}
// Редактирование отчета администратором
function editReport(report) {
const content = document.getElementById("reportViewContent");
const buttons = document.getElementById("reportModalButtons");
// Преобразуем просмотр в форму редактирования
content.innerHTML = `
<form id="editReportForm" class="space-y-6">
<!-- Основная информация -->
<div class="report-section">
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-info-circle mr-2"></i>Основная информация</h4>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-bold text-gray-700 mb-1">Магазин</label>
<select id="editStoreSelect" class="form-input w-full px-3 py-2 rounded-lg">
${database.stores
.map(
(s) => `
<option value="${s.id}" ${
s.id === report.storeId
? "selected"
: ""
}>${s.name}</option>
`
)
.join("")}
</select>
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-1">Дата</label>
<input type="date" id="editDate" value="${
report.date
}" class="form-input w-full px-3 py-2 rounded-lg">
</div>
</div>
</div>
<!-- Доходы -->
<div class="report-section income">
<h4 class="font-bold text-green-700 mb-3"><i class="fas fa-arrow-up mr-2"></i>Доходы</h4>
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block text-sm font-bold text-gray-700 mb-1">Income</label>
<input type="number" id="editIncome" value="${
report.income
}" step="0.01" class="form-input w-full px-3 py-2 rounded-lg">
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-1">Caja inicial</label>
<input type="number" id="editCajaInicial" value="${
report.cajaInicial
}" step="0.01" class="form-input w-full px-3 py-2 rounded-lg">
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-1">Envelope</label>
<input type="number" id="editEnvelope" value="${
report.envelope
}" step="0.01" class="form-input w-full px-3 py-2 rounded-lg">
</div>
</div>
</div>
<!-- Статус проверки -->
<div class="report-section">
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-check mr-2"></i>Статус</h4>
<label class="flex items-center">
<input type="checkbox" id="editVerified" ${
report.verified ? "checked" : ""
} class="mr-2">
<span>Отчет проверен</span>
</label>
</div>
</form>
`;
// Изменяем кнопки на режим редактирования
buttons.innerHTML = `
<button id="saveReportBtn" class="btn-success text-white px-4 py-2 rounded-lg">
<i class="fas fa-save mr-2"></i>Сохранить
</button>
<button id="cancelEditBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
<i class="fas fa-times mr-2"></i>Отмена
</button>
`;
document.getElementById("saveReportBtn").addEventListener("click", () => {
saveEditedReport(report.id);
});
document.getElementById("cancelEditBtn").addEventListener("click", () => {
showReportModal(report, true); // Вернуться к просмотру
});
}
// Сохранение отредактированного отчета
function saveEditedReport(reportId) {
const reportIndex = database.reports.findIndex((r) => r.id === reportId);
if (reportIndex === -1) return;
const report = database.reports[reportIndex];
// Обновляем данные
report.storeId = parseInt(document.getElementById("editStoreSelect").value);
report.date = document.getElementById("editDate").value;
report.income = parseFloat(document.getElementById("editIncome").value);
report.cajaInicial = parseFloat(
document.getElementById("editCajaInicial").value
);
report.envelope = parseFloat(document.getElementById("editEnvelope").value);
report.verified = document.getElementById("editVerified").checked;
// Пересчитываем итоги
report.totalIncome = report.income + report.cajaInicial;
report.cajaFinal =
report.totalIncome - report.totalExpenses - report.envelope;
database.reports[reportIndex] = report;
showNotification("Отчет успешно обновлен!");
hideModal("reportViewModal");
// Обновляем таблицу отчетов если мы в админке
if (currentUser.role === "admin") {
loadReports();
updateDashboard();
}
}
// Подтверждение отчета
function verifyReport(reportId) {
const reportIndex = database.reports.findIndex((r) => r.id === reportId);
if (reportIndex !== -1) {
database.reports[reportIndex].verified = true;
showNotification("Отчет подтвержден!");
hideModal("reportViewModal");
loadReports();
updateDashboard();
}
}
// Заполнение формы данными отчета (для пользователя)
function fillFormWithReport(report) {
editingReportId = report.id;
document.getElementById("storeSelect").value = report.storeId;
document.getElementById("income").value = report.income;
document.getElementById("cajaInicial").value = report.cajaInicial;
document.getElementById("envelope").value = report.envelope;
// Заполнение зарплат
const wagesContainer = document.getElementById("wagesContainer");
wagesContainer.innerHTML = "";
if (report.wages && report.wages.length > 0) {
report.wages.forEach((wage) => {
const row = document.createElement("div");
row.className = "wage-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3";
row.innerHTML = `
<input type="text" placeholder="Имя сотрудника" value="${wage.name}" class="wage-name form-input px-3 py-2 rounded-lg">
<input type="number" step="0.01" placeholder="Сумма €" value="${wage.amount}" class="wage-amount form-input px-3 py-2 rounded-lg">
<button type="button" class="remove-wage bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
<i class="fas fa-times"></i>
</button>
`;
wagesContainer.appendChild(row);
});
} else {
addWageRow();
}
// Заполнение расходов
const expensesContainer = document.getElementById("expensesContainer");
expensesContainer.innerHTML = "";
if (report.expenses && report.expenses.length > 0) {
report.expenses.forEach((expense) => {
const row = document.createElement("div");
row.className = "expense-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3";
row.innerHTML = `
<input type="text" placeholder="Название расхода" value="${expense.name}" class="expense-name form-input px-3 py-2 rounded-lg">
<input type="number" step="0.01" placeholder="Сумма €" value="${expense.amount}" class="expense-amount form-input px-3 py-2 rounded-lg">
<button type="button" class="remove-expense bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
<i class="fas fa-times"></i>
</button>
`;
expensesContainer.appendChild(row);
});
} else {
addExpenseRow();
}
updateTotals();
}
// === АДМИН ПАНЕЛЬ ===
// Настройка вкладок администратора
function setupAdminTabs() {
const tabButtons = document.querySelectorAll(".admin-tab-btn");
const tabContents = document.querySelectorAll(".admin-tab-content");
tabButtons.forEach((button) => {
button.addEventListener("click", () => {
const tabId = button.dataset.tab;
// Переключение активной вкладки
tabButtons.forEach((btn) => {
btn.className =
"admin-tab-btn px-6 py-3 font-medium text-gray-600 hover:text-blue-600 transition-colors border-b-2 border-transparent hover:border-blue-500";
});
button.className =
"admin-tab-btn px-6 py-3 font-medium transition-colors border-b-2 border-blue-500 text-blue-600";
// Показ/скрытие содержимого
tabContents.forEach((content) => {
content.classList.add("hidden");
});
document.getElementById(tabId + "Tab").classList.remove("hidden");
// Загрузка данных при переключении
switch (tabId) {
case "dashboard":
updateDashboard();
break;
case "reports":
loadReports();
break;
case "users":
loadUsers();
break;
case "stores":
loadStores();
break;
case "todo":
loadTodos();
break;
}
});
});
}
// Обновление дашборда
function updateDashboard() {
const reports = database.reports;
// Расчет статистики
const totalRevenue = reports.reduce((sum, r) => sum + r.totalIncome, 0);
const totalExpenses = reports.reduce((sum, r) => sum + r.totalExpenses, 0);
const totalReports = reports.length;
const totalUsers = database.users.filter((u) => u.role === "employee").length;
// Обновление карточек
document.getElementById(
"totalRevenueCard"
).textContent = `${totalRevenue.toFixed(2)}`;
document.getElementById(
"totalExpensesCard"
).textContent = `${totalExpenses.toFixed(2)}`;
document.getElementById("totalReportsCard").textContent = totalReports;
document.getElementById("totalUsersCard").textContent = totalUsers;
// Создание графиков
createCharts();
}
// Создание графиков
function createCharts() {
// График доходов по дням
const revenueCtx = document.getElementById("revenueChart");
if (revenueCtx) {
const last7Days = [];
const revenueData = [];
for (let i = 6; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split("T")[0];
last7Days.push(dateStr);
const dayReports = database.reports.filter((r) => r.date === dateStr);
const dayRevenue = dayReports.reduce((sum, r) => sum + r.totalIncome, 0);
revenueData.push(dayRevenue);
}
new Chart(revenueCtx, {
type: "line",
data: {
labels: last7Days,
datasets: [
{
label: "Доходы",
data: revenueData,
borderColor: "rgb(59, 130, 246)",
backgroundColor: "rgba(59, 130, 246, 0.1)",
tension: 0.4,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function (value) {
return "€" + value.toFixed(0);
},
},
},
},
},
});
}
// Круговая диаграмма расходов
const expensesCtx = document.getElementById("expensesChart");
if (expensesCtx) {
const totalWages = database.reports.reduce(
(sum, r) => sum + r.totalWages,
0
);
const totalInternal = database.reports.reduce(
(sum, r) => sum + r.totalExpensesInternal,
0
);
new Chart(expensesCtx, {
type: "doughnut",
data: {
labels: ["Зарплаты", "Прочие расходы"],
datasets: [
{
data: [totalWages, totalInternal],
backgroundColor: ["#F59E0B", "#EF4444"],
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom",
},
},
},
});
}
// График по магазинам
const storesCtx = document.getElementById("storesChart");
if (storesCtx) {
const storeData = database.stores.map((store) => {
const storeReports = database.reports.filter(
(r) => r.storeId === store.id
);
const revenue = storeReports.reduce((sum, r) => sum + r.totalIncome, 0);
return { name: store.name, revenue };
});
new Chart(storesCtx, {
type: "bar",
data: {
labels: storeData.map((s) => s.name),
datasets: [
{
label: "Доходы по магазинам",
data: storeData.map((s) => s.revenue),
backgroundColor: "#10B981",
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function (value) {
return "€" + value.toFixed(0);
},
},
},
},
},
});
}
// Тренды продаж
const trendsCtx = document.getElementById("trendsChart");
if (trendsCtx) {
const last30Days = [];
const profitData = [];
for (let i = 29; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split("T")[0];
last30Days.push(dateStr);
const dayReports = database.reports.filter((r) => r.date === dateStr);
const dayProfit = dayReports.reduce(
(sum, r) => sum + (r.totalIncome - r.totalExpenses),
0
);
profitData.push(dayProfit);
}
new Chart(trendsCtx, {
type: "line",
data: {
labels: last30Days,
datasets: [
{
label: "Прибыль",
data: profitData,
borderColor: "#8B5CF6",
backgroundColor: "rgba(139, 92, 246, 0.1)",
tension: 0.4,
fill: true,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function (value) {
return "€" + value.toFixed(0);
},
},
},
},
},
});
}
}
// Загрузка отчетов в админке
function loadReports() {
const tbody = document.getElementById("reportsTableBody");
const filterStore = document.getElementById("filterStore");
// Заполнение фильтра магазинов
filterStore.innerHTML = '<option value="">Все магазины</option>';
database.stores.forEach((store) => {
const option = document.createElement("option");
option.value = store.id;
option.textContent = store.name;
filterStore.appendChild(option);
});
// Отображение отчетов
tbody.innerHTML = "";
database.reports.forEach((report) => {
const store = database.stores.find((s) => s.id === report.storeId);
const user = database.users.find((u) => u.id === report.userId);
const profit = report.totalIncome - report.totalExpenses;
const row = document.createElement("tr");
row.className = "hover:bg-gray-50";
row.innerHTML = `
<td class="px-6 py-4 text-sm text-gray-900">${
report.date
}</td>
<td class="px-6 py-4 text-sm text-gray-900">${
store ? store.name : "Неизвестно"
}</td>
<td class="px-6 py-4 text-sm text-gray-900">€${report.totalIncome.toFixed(
2
)}</td>
<td class="px-6 py-4 text-sm text-gray-900">€${report.totalExpenses.toFixed(
2
)}</td>
<td class="px-6 py-4 text-sm ${
profit >= 0 ? "text-green-600" : "text-red-600"
}">€${profit.toFixed(2)}</td>
<td class="px-6 py-4 text-sm text-gray-900">${
user ? user.username : "Неизвестно"
}</td>
<td class="px-6 py-4">
<span class="px-2 py-1 text-xs rounded-full ${
report.verified
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}">
${report.verified ? "Проверен" : "Не проверен"}
</span>
</td>
<td class="px-6 py-4 text-sm">
<button class="text-blue-600 hover:text-blue-900 mr-2" onclick="viewReport(${
report.id
})">
<i class="fas fa-eye"></i> Просмотр
</button>
<button class="text-red-600 hover:text-red-900" onclick="deleteReport(${
report.id
})">
<i class="fas fa-trash"></i> Удалить
</button>
</td>
`;
tbody.appendChild(row);
});
// Настройка фильтров и экспорта
setupReportsFilters();
}
// Просмотр отчета
function viewReport(reportId) {
const report = database.reports.find((r) => r.id === reportId);
if (report) {
showReportModal(report, true); // true = админ режим
}
}
// Удаление отчета
function deleteReport(reportId) {
if (confirm("Вы уверены, что хотите удалить этот отчет?")) {
database.reports = database.reports.filter((r) => r.id !== reportId);
loadReports();
updateDashboard();
showNotification("Отчет удален!");
}
}
// Настройка фильтров отчетов
function setupReportsFilters() {
document
.getElementById("applyFilters")
.addEventListener("click", applyReportsFilters);
document
.getElementById("exportExcel")
.addEventListener("click", exportToExcel);
}
function applyReportsFilters() {
const storeFilter = document.getElementById("filterStore").value;
const dateFrom = document.getElementById("filterDateFrom").value;
const dateTo = document.getElementById("filterDateTo").value;
let filteredReports = database.reports;
if (storeFilter) {
filteredReports = filteredReports.filter((r) => r.storeId == storeFilter);
}
if (dateFrom) {
filteredReports = filteredReports.filter((r) => r.date >= dateFrom);
}
if (dateTo) {
filteredReports = filteredReports.filter((r) => r.date <= dateTo);
}
// Обновление таблицы с отфильтрованными данными
const tbody = document.getElementById("reportsTableBody");
tbody.innerHTML = "";
filteredReports.forEach((report) => {
const store = database.stores.find((s) => s.id === report.storeId);
const user = database.users.find((u) => u.id === report.userId);
const profit = report.totalIncome - report.totalExpenses;
const row = document.createElement("tr");
row.className = "hover:bg-gray-50";
row.innerHTML = `
<td class="px-6 py-4 text-sm text-gray-900">${
report.date
}</td>
<td class="px-6 py-4 text-sm text-gray-900">${
store ? store.name : "Неизвестно"
}</td>
<td class="px-6 py-4 text-sm text-gray-900">€${report.totalIncome.toFixed(
2
)}</td>
<td class="px-6 py-4 text-sm text-gray-900">€${report.totalExpenses.toFixed(
2
)}</td>
<td class="px-6 py-4 text-sm ${
profit >= 0 ? "text-green-600" : "text-red-600"
}">€${profit.toFixed(2)}</td>
<td class="px-6 py-4 text-sm text-gray-900">${
user ? user.username : "Неизвестно"
}</td>
<td class="px-6 py-4">
<span class="px-2 py-1 text-xs rounded-full ${
report.verified
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}">
${report.verified ? "Проверен" : "Не проверен"}
</span>
</td>
<td class="px-6 py-4 text-sm">
<button class="text-blue-600 hover:text-blue-900 mr-2" onclick="viewReport(${
report.id
})">
<i class="fas fa-eye"></i> Просмотр
</button>
<button class="text-red-600 hover:text-red-900" onclick="deleteReport(${
report.id
})">
<i class="fas fa-trash"></i> Удалить
</button>
</td>
`;
tbody.appendChild(row);
});
showNotification(`Найдено ${filteredReports.length} отчетов`);
}
// Экспорт в Excel
function exportToExcel() {
const data = database.reports.map((report) => {
const store = database.stores.find((s) => s.id === report.storeId);
const user = database.users.find((u) => u.id === report.userId);
const profit = report.totalIncome - report.totalExpenses;
return {
Дата: report.date,
Магазин: store ? store.name : "Неизвестно",
Доход: `${report.totalIncome.toFixed(2)}`,
Расходы: `${report.totalExpenses.toFixed(2)}`,
Прибыль: `${profit.toFixed(2)}`,
Пользователь: user ? user.username : "Неизвестно",
Статус: report.verified ? "Проверен" : "Не проверен",
};
});
// Создание CSV
const headers = Object.keys(data[0]);
const csvContent = [
headers.join(","),
...data.map((row) => headers.map((header) => `"${row[header]}"`).join(",")),
].join("\n");
// Скачивание файла
const blob = new Blob([csvContent], {
type: "text/csv;charset=utf-8;",
});
const link = document.createElement("a");
const url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute(
"download",
`cash_reports_${new Date().toISOString().split("T")[0]}.csv`
);
link.style.visibility = "hidden";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showNotification("Отчет экспортирован!");
}
// === УПРАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯМИ ===
// Загрузка пользователей
// function loadUsers() {
// const tbody = document.getElementById("usersTableBody");
// tbody.innerHTML = "";
// database.users.forEach((user) => {
// const userStores = database.stores.filter((s) =>
// user.stores.includes(s.id)
// );
// const storeNames =
// userStores.map((s) => s.name).join(", ") || "Нет доступа";
// const row = document.createElement("tr");
// row.className = "hover:bg-gray-50";
// row.innerHTML = `
// <td class="px-6 py-4 text-sm text-gray-900">${user.id}</td>
// <td class="px-6 py-4 text-sm text-gray-900">${
// user.username
// }</td>
// <td class="px-6 py-4 text-sm">
// <span class="px-2 py-1 text-xs rounded-full ${
// user.role === "admin"
// ? "bg-purple-100 text-purple-800"
// : "bg-blue-100 text-blue-800"
// }">
// ${
// user.role === "admin"
// ? "Администратор"
// : "Сотрудник"
// }
// </span>
// </td>
// <td class="px-6 py-4 text-sm text-gray-900">${storeNames}</td>
// <td class="px-6 py-4 text-sm">
// <button class="text-blue-600 hover:text-blue-900 mr-2" onclick="editUser(${
// user.id
// })">
// <i class="fas fa-edit"></i> Редактировать
// </button>
// <button class="text-red-600 hover:text-red-900" onclick="deleteUser(${
// user.id
// })">
// <i class="fas fa-trash"></i> Удалить
// </button>
// </td>
// `;
// tbody.appendChild(row);
// });
// }
// Добавление пользователя
document.addEventListener("DOMContentLoaded", function () {
const addUserBtn = document.getElementById("addUserBtn");
if (addUserBtn) {
addUserBtn.addEventListener("click", () => {
editingUserId = null;
showUserEditModal();
});
}
});
// Редактирование пользователя
function editUser(userId) {
editingUserId = userId;
const user = usersList.find((u) => u.id === userId);
showUserEditModal(user);
}
// Показ модального окна редактирования пользователя
function showUserEditModal(user) {
const modal = document.getElementById("userEditModal");
const title = document.getElementById("userModalTitle");
const form = document.getElementById("userEditForm");
// If editingUserId, it's an edit; else, it's add
title.textContent = editingUserId
? "Редактирование пользователя"
: "Добавление пользователя";
document.getElementById("userLogin").value = (user && user.username) || "";
document.getElementById("userPassword").value = "";
document.getElementById("userRole").value = (user && user.role) || "employee";
// Загрузка чекбоксов магазинов
const storesContainer = document.getElementById("userStoresAccess");
storesContainer.innerHTML = "";
database.stores.forEach((store) => {
const isChecked = user && user.stores && user.stores.includes(store.id);
const checkbox = document.createElement("label");
checkbox.className = "flex items-center";
checkbox.innerHTML = `
<input type="checkbox" value="${store.id}" ${
isChecked ? "checked" : ""
} class="mr-2">
<span>${store.name}</span>
`;
storesContainer.appendChild(checkbox);
});
showModal("userEditModal");
}
// Сохранение пользователя
document.addEventListener("DOMContentLoaded", function () {
const saveUserBtn = document.getElementById("saveUserBtn");
const cancelUserBtn = document.getElementById("cancelUserBtn");
if (saveUserBtn) {
saveUserBtn.addEventListener("click", saveUser);
}
if (cancelUserBtn) {
cancelUserBtn.addEventListener("click", () => {
hideModal("userEditModal");
});
}
});
//save user for create and update
async function saveUser() {
const login = document.getElementById("userLogin").value.trim();
const password = document.getElementById("userPassword").value;
const role = document.getElementById("userRole").value;
const selectedStores = Array.from(
document.querySelectorAll(
'#userStoresAccess input[type="checkbox"]:checked'
)
).map((cb) => parseInt(cb.value));
if (!login) {
showNotification("Заполните логин!", "error");
return;
}
// Always build userData
const userData = {
username: login,
role: role,
storeIds: selectedStores,
};
if (password) userData.password = password;
// Determine CREATE or EDIT
let result;
if (!editingUserId) {
if (!password) {
showNotification("Укажите пароль для нового пользователя!", "error");
return;
}
// CREATE
result = await createUser(userData);
if (result.success) {
showNotification("Пользователь добавлен!");
}
} else {
// EDIT
result = await updateUser(editingUserId, userData);
if (result.success) {
showNotification("Пользователь обновлен!");
}
editingUserId = null;
}
// After save: UI update or error
if (result && result.success) {
hideModal("userEditModal");
loadUsers();
updateDashboard();
} else if (result) {
showNotification(result.error || "Ошибка операции", "error");
}
}
// Удаление пользователя
// function deleteUser(userId) {
// const user = database.users.find((u) => u.id === userId);
// const userReports = database.reports.filter((r) => r.userId === userId);
// let message = `Вы уверены, что хотите удалить пользователя "${user.username}"?`;
// if (userReports.length > 0) {
// message += `\n\nВнимание! У этого пользователя есть ${userReports.length} связанных отчетов. Они также будут удалены.`;
// }
// if (confirm(message)) {
// database.users = database.users.filter((u) => u.id !== userId);
// database.reports = database.reports.filter((r) => r.userId !== userId);
// loadUsers();
// loadReports();
// updateDashboard();
// showNotification("Пользователь и связанные отчеты удалены!");
// }
// }
// === УПРАВЛЕНИЕ МАГАЗИНАМИ ===
// Загрузка магазинов
function loadStores() {
const tbody = document.getElementById("storesTableBody");
tbody.innerHTML = "";
database.stores.forEach((store) => {
const storeReports = database.reports.filter((r) => r.storeId === store.id);
const row = document.createElement("tr");
row.className = "hover:bg-gray-50";
row.innerHTML = `
<td class="px-6 py-4 text-sm text-gray-900">${store.id}</td>
<td class="px-6 py-4 text-sm text-gray-900">${store.name}</td>
<td class="px-6 py-4 text-sm text-gray-900">${storeReports.length}</td>
<td class="px-6 py-4 text-sm">
<button class="text-blue-600 hover:text-blue-900 mr-2" onclick="editStore(${store.id})">
<i class="fas fa-edit"></i> Редактировать
</button>
<button class="text-red-600 hover:text-red-900" onclick="deleteStore(${store.id})">
<i class="fas fa-trash"></i> Удалить
</button>
</td>
`;
tbody.appendChild(row);
});
}
// Добавление магазина
document.addEventListener("DOMContentLoaded", function () {
const addStoreBtn = document.getElementById("addStoreBtn");
if (addStoreBtn) {
addStoreBtn.addEventListener("click", () => {
editingStoreId = null;
showStoreEditModal();
});
}
});
// Редактирование магазина
function editStore(storeId) {
editingStoreId = storeId;
showStoreEditModal();
}
// Показ модального окна редактирования магазина
function showStoreEditModal() {
const modal = document.getElementById("storeEditModal");
const title = document.getElementById("storeModalTitle");
const form = document.getElementById("storeEditForm");
if (editingStoreId) {
const store = database.stores.find((s) => s.id === editingStoreId);
title.textContent = "Редактирование магазина";
document.getElementById("storeName").value = store.name;
} else {
title.textContent = "Добавление магазина";
form.reset();
}
showModal("storeEditModal");
}
// Сохранение магазина
document.addEventListener("DOMContentLoaded", function () {
const saveStoreBtn = document.getElementById("saveStoreBtn");
const cancelStoreBtn = document.getElementById("cancelStoreBtn");
if (saveStoreBtn) {
saveStoreBtn.addEventListener("click", saveStore);
}
if (cancelStoreBtn) {
cancelStoreBtn.addEventListener("click", () => {
hideModal("storeEditModal");
});
}
});
function saveStore() {
const name = document.getElementById("storeName").value;
if (!name) {
showNotification("Заполните название магазина!", "error");
return;
}
if (editingStoreId) {
// Редактирование
const storeIndex = database.stores.findIndex(
(s) => s.id === editingStoreId
);
database.stores[storeIndex].name = name;
showNotification("Магазин обновлен!");
} else {
// Добавление
const newId = Math.max(...database.stores.map((s) => s.id)) + 1;
database.stores.push({
id: newId,
name: name,
});
showNotification("Магазин добавлен!");
}
hideModal("storeEditModal");
loadStores();
loadUsers(); // Обновить список пользователей для отображения новых магазинов
loadUserStores(); // Обновить список магазинов в форме пользователя
updateDashboard();
}
// Удаление магазина
function deleteStore(storeId) {
const store = database.stores.find((s) => s.id === storeId);
const storeReports = database.reports.filter((r) => r.storeId === storeId);
let message = `Вы уверены, что хотите удалить магазин "${store.name}"?`;
if (storeReports.length > 0) {
message += `\n\nВнимание! У этого магазина есть ${storeReports.length} связанных отчетов. Они также будут удалены.`;
}
if (confirm(message)) {
database.stores = database.stores.filter((s) => s.id !== storeId);
database.reports = database.reports.filter((r) => r.storeId !== storeId);
// Удаление магазина из доступов пользователей
database.users.forEach((user) => {
user.stores = user.stores.filter((sid) => sid !== storeId);
});
loadStores();
loadUsers();
loadReports();
updateDashboard();
showNotification("Магазин и связанные отчеты удалены!");
}
}
// === TODO СИСТЕМА ===
// Загрузка TODO
function loadTodos() {
const container = document.getElementById("todoList");
container.innerHTML = "";
database.todos.forEach((todo) => {
const todoItem = document.createElement("div");
todoItem.className = `p-4 border rounded-lg ${
todo.completed
? "bg-green-50 border-green-200"
: "bg-white border-gray-200"
}`;
const priorityColors = {
low: "bg-blue-100 text-blue-800",
medium: "bg-yellow-100 text-yellow-800",
high: "bg-red-100 text-red-800",
};
const priorityText = {
low: "Низкий",
medium: "Средний",
high: "Высокий",
};
todoItem.innerHTML = `
<div class="flex items-start justify-between">
<div class="flex items-start space-x-3 flex-1">
<input type="checkbox" ${
todo.completed ? "checked" : ""
}
onchange="toggleTodo(${todo.id})"
class="mt-1">
<div class="flex-1">
<h4 class="font-medium ${
todo.completed
? "line-through text-gray-500"
: "text-gray-900"
}">${todo.title}</h4>
<p class="text-sm text-gray-600 mt-1">${
todo.description
}</p>
<div class="flex items-center space-x-2 mt-2">
<span class="px-2 py-1 text-xs rounded-full ${
priorityColors[todo.priority]
}">
${priorityText[todo.priority]}
</span>
<span class="text-xs text-gray-500">${
todo.createdAt
}</span>
</div>
</div>
</div>
<div class="flex space-x-2">
<button onclick="editTodo(${
todo.id
})" class="text-blue-600 hover:text-blue-900">
<i class="fas fa-edit"></i>
</button>
<button onclick="deleteTodo(${
todo.id
})" class="text-red-600 hover:text-red-900">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`;
container.appendChild(todoItem);
});
}
// Переключение статуса TODO
function toggleTodo(todoId) {
const todoIndex = database.todos.findIndex((t) => t.id === todoId);
database.todos[todoIndex].completed = !database.todos[todoIndex].completed;
loadTodos();
showNotification("Статус задачи обновлен!");
}
// Добавление TODO
document.addEventListener("DOMContentLoaded", function () {
const addTodoBtn = document.getElementById("addTodoBtn");
if (addTodoBtn) {
addTodoBtn.addEventListener("click", () => {
editingTodoId = null;
showTodoEditModal();
});
}
});
// Редактирование TODO
function editTodo(todoId) {
editingTodoId = todoId;
showTodoEditModal();
}
// Показ модального окна редактирования TODO
function showTodoEditModal() {
const modal = document.getElementById("todoEditModal");
const title = document.getElementById("todoModalTitle");
const form = document.getElementById("todoEditForm");
if (editingTodoId) {
const todo = database.todos.find((t) => t.id === editingTodoId);
title.textContent = "Редактирование задачи";
document.getElementById("todoTitle").value = todo.title;
document.getElementById("todoDescription").value = todo.description;
document.getElementById("todoPriority").value = todo.priority;
} else {
title.textContent = "Добавление задачи";
form.reset();
}
showModal("todoEditModal");
}
// Сохранение TODO
document.addEventListener("DOMContentLoaded", function () {
const saveTodoBtn = document.getElementById("saveTodoBtn");
const cancelTodoBtn = document.getElementById("cancelTodoBtn");
if (saveTodoBtn) {
saveTodoBtn.addEventListener("click", saveTodo);
}
if (cancelTodoBtn) {
cancelTodoBtn.addEventListener("click", () => {
hideModal("todoEditModal");
});
}
});
function saveTodo() {
const title = document.getElementById("todoTitle").value;
const description = document.getElementById("todoDescription").value;
const priority = document.getElementById("todoPriority").value;
if (!title) {
showNotification("Заполните заголовок!", "error");
return;
}
if (editingTodoId) {
// Редактирование
const todoIndex = database.todos.findIndex((t) => t.id === editingTodoId);
database.todos[todoIndex].title = title;
database.todos[todoIndex].description = description;
database.todos[todoIndex].priority = priority;
showNotification("Задача обновлена!");
} else {
// Добавление
const newId = Math.max(...database.todos.map((t) => t.id)) + 1;
database.todos.push({
id: newId,
title: title,
description: description,
priority: priority,
completed: false,
createdAt: new Date().toISOString().split("T")[0],
});
showNotification("Задача добавлена!");
}
hideModal("todoEditModal");
loadTodos();
}
// Удаление TODO
function deleteTodo(todoId) {
if (confirm("Вы уверены, что хотите удалить эту задачу?")) {
database.todos = database.todos.filter((t) => t.id !== todoId);
loadTodos();
showNotification("Задача удалена!");
}
}
// Обработчики выхода
document.getElementById("logoutBtn").addEventListener("click", logout);
document.getElementById("adminLogoutBtn").addEventListener("click", logout);
function logout() {
currentUser = null;
editingReportId = null;
document.getElementById("loginScreen").classList.remove("hidden");
document.getElementById("userInterface").classList.add("hidden");
document.getElementById("adminInterface").classList.add("hidden");
document.getElementById("loginForm").reset();
document.getElementById("loginError").classList.add("hidden");
showNotification("Вы вышли из системы");
}
// Глобальные функции для onclick обработчиков
window.viewReport = viewReport;
window.deleteReport = deleteReport;
window.editUser = editUser;
window.deleteUser = deleteUser;
window.editStore = editStore;
window.deleteStore = deleteStore;
window.editTodo = editTodo;
window.deleteTodo = deleteTodo;
window.toggleTodo = toggleTodo;