1925 lines
70 KiB
JavaScript
1925 lines
70 KiB
JavaScript
// Глобальные переменные
|
||
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;
|