From fa28994974dc8369bf935e380d022d1a64a86834 Mon Sep 17 00:00:00 2001 From: Angie Date: Wed, 30 Jul 2025 19:11:10 +0200 Subject: [PATCH 1/3] feat: updated structure + issue of users solved --- frontend/api.js | 167 +--- frontend/script.js | 2264 +++++++++++++++++++------------------------- 2 files changed, 998 insertions(+), 1433 deletions(-) diff --git a/frontend/api.js b/frontend/api.js index 20d9b45..552868c 100644 --- a/frontend/api.js +++ b/frontend/api.js @@ -1,7 +1,29 @@ //API -const API_BASE_URL = "http://195.209.214.159/api"; +// const API_BASE_URL = "http://195.209.214.159/api"; -//Login +//API local +const API_BASE_URL = "http://localhost:3001/api"; + +//SHARED +//universal global state +window.appState = window.appState || { + currentUser: null, + usersList: [], + reportsList: [], + storesList: [], + editingReportId: null, + editingUserId: null, + editingStoreId: null, + editingTodoId: null, + revenueChartInstance: null, + expensesChartInstance: null, + storesChartInstance: null, + trendsChartInstance: null, + adminTabsInitialized: false, +}; + +//AUTH +//Login & Logout async function loginUser(username, password) { try { const response = await fetch(`${API_BASE_URL}/auth/login`, { @@ -28,35 +50,19 @@ async function loginUser(username, password) { } } -document.getElementById("loginForm").addEventListener("submit", async (e) => { - e.preventDefault(); +function logout() { + appState.currentUser = null; + localStorage.removeItem("token"); - const username = document.getElementById("username").value; - const password = document.getElementById("password").value; + // Show login screen, hide other interfaces + 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"); - // Call backend login - const result = await loginUser(username, password); - - if (result.success) { - // Save user/token in JS (or localStorage) - currentUser = result.user; - localStorage.setItem("token", result.token); - - document.getElementById("loginScreen").classList.add("hidden"); - - if (result.user.role === "admin") { - showAdminInterface(); - } else { - showUserInterface(); - } - - showNotification("Успешная авторизация!"); - } else { - const errorDiv = document.getElementById("loginError"); - errorDiv.textContent = result.error; - errorDiv.classList.remove("hidden"); - } -}); + showNotification("Вы вышли из системы", "info"); +} document.addEventListener("DOMContentLoaded", async () => { const token = localStorage.getItem("token"); @@ -70,9 +76,9 @@ document.addEventListener("DOMContentLoaded", async () => { }); if (response.ok) { const data = await response.json(); - currentUser = data.user; + appState.currentUser = data.user; document.getElementById("loginScreen").classList.add("hidden"); - if (currentUser.role === "admin") { + if (appState.currentUser.role === "admin") { showAdminInterface(); } else { showUserInterface(); @@ -91,24 +97,10 @@ document.addEventListener("DOMContentLoaded", async () => { } }); -function logout() { - currentUser = null; - localStorage.removeItem("token"); - - // Show login screen, hide other interfaces - 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("Вы вышли из системы", "info"); -} - document.getElementById("logoutBtn").addEventListener("click", logout); document.getElementById("adminLogoutBtn").addEventListener("click", logout); -//Users +//USERS //GET all users (admin only) async function getAllUsers() { @@ -135,68 +127,7 @@ async function getAllUsers() { } } -let usersList = []; - -async function loadUsers() { - const tbody = document.getElementById("usersTableBody"); - tbody.innerHTML = ""; - - const result = await getAllUsers(); - - console.log("getAllUsers result:", result); - - if (!result.success) { - showNotification(result.error, "error"); - return; - } - - const users = result.users; - window.usersList = users; - - users.forEach((user) => { - const userStores = - user.stores - .map((storeId) => { - const store = (window.storesList || []).find((s) => s.id === storeId); - return store ? store.name : "Нет доступа"; - }) - .join(", ") || "Нет доступа"; - - const row = document.createElement("tr"); - row.className = "hover:bg-gray-50"; - row.innerHTML = ` - ${user.id} - ${user.username} - - - ${user.role === "admin" ? "Администратор" : "Сотрудник"} - - - ${userStores} - - - - - `; - tbody.appendChild(row); - }); - - if (users.length === 0) { - showNotification("Нет пользователей для отображения", "info"); - } -} +appState.usersList = []; //add user async function createUser(userData) { @@ -331,14 +262,7 @@ async function apiDeleteUser(userId) { } } -// 4. UI trigger: delete user with modal -function deleteUser(userId) { - showConfirmModal("Вы уверены, что хотите удалить этого пользователя?", () => - apiDeleteUser(userId) - ); -} - -//Reports +//REPORTS ADMIN //GET all reports async function getReports() { const token = localStorage.getItem("token"); @@ -372,7 +296,7 @@ async function loadReports() { const result = await getReports(); console.log("getReports() result:", result); - window.reportsList = result.success ? result.reports : []; + appState.reportsList = result.success ? result.reports : []; if (!result.success) { showNotification(result.error || "Ошибка загрузки отчетов", "error"); @@ -456,11 +380,11 @@ async function loadReports() { // Use global array for backend reports function viewReport(reportId) { - if (!window.reportsList) { + if (!appState.reportsList) { showNotification("Reports not loaded yet!", "error"); return; } - const report = window.reportsList.find((r) => r.id === reportId); + const report = appState.reportsList.find((r) => r.id === reportId); if (report) { showReportModal(report, true); } else { @@ -684,7 +608,7 @@ async function apiDeleteReport(reportId) { } } -//worker +//REPORTS WORKER //create report // api.js async function createReport(data) { @@ -715,8 +639,7 @@ async function createReport(data) { } } -//stores (for admin) - +//STORES (for admin) // 1. Get all stores async function getStores() { const token = localStorage.getItem("token"); diff --git a/frontend/script.js b/frontend/script.js index 1b74e7d..0c70199 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -1,147 +1,10 @@ -// Глобальные переменные -let currentUser = null; -let editingReportId = null; -let editingUserId = null; -let editingStoreId = null; -let editingTodoId = null; - -//default state -let revenueChartInstance = null; -let expensesChartInstance = null; -let storesChartInstance = null; -let trendsChartInstance = null; +//SHARED //helper to destroyChart on change function destroyChart(instance) { if (instance) instance.destroy(); } -// База данных (симуляция) -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"); @@ -196,6 +59,57 @@ function hideModal(modalId) { document.body.classList.remove("modal-open"); } } +// Настройка вкладок администратора +function setupAdminTabs() { + if (appState.adminTabsInitialized) return; + appState.adminTabsInitialized = true; + + 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"); + + if (tabId === "dashboard") updateDashboard(); + // Optionally, reload todos for TODO tab + if (tabId === "todo") loadTodos(); + + // Загрузка данных при переключении + // switch (tabId) { + // case "dashboard": + // updateDashboard(); + // break; + // case "reports": + // loadReports(); + // break; + // case "users": + // loadUsers(); + // break; + // case "stores": + // loadStores(); + // break; + // case "todo": + // loadTodos(); + // break; + // } + }); + }); +} // Закрытие модального окна при клике вне его document.addEventListener("click", (e) => { @@ -204,41 +118,57 @@ document.addEventListener("click", (e) => { } }); +// Глобальные функции для 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; + +//AUTH + // Обработчики авторизации -// document.getElementById("loginForm").addEventListener("submit", (e) => { -// e.preventDefault(); +document.getElementById("loginForm").addEventListener("submit", async (e) => { + e.preventDefault(); -// const username = document.getElementById("username").value; -// const password = document.getElementById("password").value; + const username = document.getElementById("username").value; + const password = document.getElementById("password").value; -// const user = database.users.find( -// (u) => u.username === username && u.password === password -// ); + // Call backend login + const result = await loginUser(username, password); -// if (user) { -// currentUser = user; -// document.getElementById("loginScreen").classList.add("hidden"); + if (result.success) { + // Save user/token in JS (or localStorage) + appState.currentUser = result.user; + localStorage.setItem("token", result.token); -// if (user.role === "admin") { -// showAdminInterface(); -// } else { -// showUserInterface(); -// } + document.getElementById("loginScreen").classList.add("hidden"); -// showNotification("Успешная авторизация!"); -// } else { -// const errorDiv = document.getElementById("loginError"); -// errorDiv.textContent = "Неверный логин или пароль"; -// errorDiv.classList.remove("hidden"); -// } -// }); + if (result.user.role === "admin") { + showAdminInterface(); + } else { + showUserInterface(); + } + + showNotification("Успешная авторизация!"); + } else { + const errorDiv = document.getElementById("loginError"); + // errorDiv.textContent = result.error; + errorDiv.textContent = "Неверный логин или пароль"; + errorDiv.classList.remove("hidden"); + } +}); // Показать интерфейс пользователя function showUserInterface() { document.getElementById("userInterface").classList.remove("hidden"); document.getElementById( "userWelcome" - ).textContent = `Добро пожаловать, ${currentUser.username}!`; + ).textContent = `Добро пожаловать, ${appState.currentUser.username}!`; loadUserStores(); setupFormCalculations(); @@ -249,329 +179,289 @@ async function showAdminInterface() { document.getElementById("adminInterface").classList.remove("hidden"); document.getElementById( "adminWelcome" - ).textContent = `Добро пожаловать, ${currentUser.username}!`; + ).textContent = `Добро пожаловать, ${appState.currentUser.username}!`; await Promise.all([loadUsers(), loadReports(), loadStores()]); updateDashboard(); - loadTodos(); setupAdminTabs(); + + // Activate first tab (dashboard) + document.querySelector('.admin-tab-btn[data-tab="dashboard"]').click(); } -// Загрузка магазинов для пользователя -function loadUserStores() { - const select = document.getElementById("storeSelect"); - if (!select) return; - select.innerHTML = - ''; +// Обработчики выхода +document.getElementById("logoutBtn").addEventListener("click", logout); +document.getElementById("adminLogoutBtn").addEventListener("click", logout); - // For admin: show all - if (currentUser.role === "admin") { - (window.storesList || []).forEach((store) => { - const option = document.createElement("option"); - option.value = store.id; - option.textContent = store.name; - select.appendChild(option); +function logout() { + appState.currentUser = null; + appState.editingReportId = null; + + appState.usersList = []; + appState.storesList = []; + appState.reportsList = []; + + database.users = []; + database.reports = []; + database.stores = []; + + // Remove all admin tab event listeners by replacing each node + document.querySelectorAll(".admin-tab-btn").forEach((btn) => { + btn.replaceWith(btn.cloneNode(true)); + }); + + appState.adminTabsInitialized = false; + + 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("Вы вышли из системы"); +} + +//DASHBOARD + +// Обновление дашборда +function updateDashboard() { + const reports = appState.reportsList || []; + const users = appState.usersList || []; + + // Расчет статистики + 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 = users.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 reports = appState.reportsList || []; + const stores = appState.storesList || []; + + const revenueCtx = document.getElementById("revenueChart"); + if (revenueCtx) { + destroyChart(appState.revenueChartInstance); + 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 = reports.filter( + (r) => (r.reportDate || r.date) === dateStr + ); + + const dayRevenue = dayReports.reduce( + (sum, r) => sum + (Number(r.totalIncome) || 0), + 0 + ); + + revenueData.push(dayRevenue); + } + + appState.revenueChartInstance = 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); + }, + }, + }, + }, + }, }); - } else { - // For employee: only their own stores - (currentUser.stores || []).forEach((storeObj) => { - // storeObj could be an object ({id, name, ...}) or just an ID; check backend API - let store = storeObj; - if (typeof storeObj === "number") { - // If backend sends just IDs, look up in window.storesList - store = (window.storesList || []).find((s) => s.id === storeObj); - } - if (store) { - const option = document.createElement("option"); - option.value = store.id; - option.textContent = store.name; - select.appendChild(option); - } + } + + // Круговая диаграмма расходов + const expensesCtx = document.getElementById("expensesChart"); + if (expensesCtx) { + destroyChart(appState.expensesChartInstance); + const totalWages = reports.reduce( + (sum, r) => sum + (Number(r.totalWages) || 0), + 0 + ); + const totalInternal = reports.reduce( + (sum, r) => sum + (Number(r.totalExpenses) || 0), + 0 + ); + + appState.expensesChartInstance = 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) { + destroyChart(appState.storesChartInstance); + const storeData = stores.map((store) => { + const storeReports = reports.filter((r) => r.storeId === store.id); + const revenue = storeReports.reduce((sum, r) => sum + r.totalIncome, 0); + return { name: store.name, revenue }; + }); + console.log("storeData for bar chart:", storeData); + + appState.storesChartInstance = 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) { + destroyChart(appState.trendsChartInstance); + 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 = reports.filter( + (r) => (r.reportDate || r.date) === dateStr + ); + const dayProfit = dayReports.reduce( + (sum, r) => + sum + ((Number(r.totalIncome) || 0) - (Number(r.totalExpenses) || 0)), + 0 + ); + profitData.push(dayProfit); + } + + appState.trendsChartInstance = 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 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 = ` - - - - `; - 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 = ` - - - - `; - 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 = ` -//
-// -// -// -//
-// `; -// document.getElementById("expensesContainer").innerHTML = ` -//
-// -// -// -//
-// `; - -// updateTotals(); -// }); - -document.getElementById("reportForm").addEventListener("submit", async (e) => { - e.preventDefault(); - - const formData = { - storeId: parseInt(document.getElementById("storeSelect").value), - reportDate: new Date().toISOString().split("T")[0], // Or get from input if user chooses date - income: parseFloat(document.getElementById("income").value), - initialCash: parseFloat(document.getElementById("cajaInicial").value), - totalIncome: parseFloat(document.getElementById("totalIncome").value), - wages: JSON.stringify(collectWages()), - expenses: JSON.stringify(collectExpenses()), - totalWages: calculateTotalWages(), - totalExpenses: calculateTotalExpenses() + calculateTotalWages(), - envelope: parseFloat(document.getElementById("envelope").value), - finalCash: - parseFloat(document.getElementById("totalIncome").value) - - (calculateTotalWages() + calculateTotalExpenses()) - - parseFloat(document.getElementById("envelope").value), - }; - console.log("Sending report:", formData); - const result = await createReport(formData); - if (result.success) { - showNotification("Отчет успешно создан!"); - document.getElementById("reportForm").reset(); - } else { - showNotification(result.error || "Ошибка создания отчета", "error"); - } -}); - -function collectWages() { - const wages = []; - document.querySelectorAll(".wage-row").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 }); - }); - return wages; -} -function collectExpenses() { - const expenses = []; - document.querySelectorAll(".expense-row").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 }); - }); - return expenses; -} - -// Отчет за сегодня для пользователя -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 safeToFixed(value, digits = 2) { - return (Number(value) || 0).toFixed(digits); -} +//REPORTS // Показ модального окна отчета с исправленной прокруткой function showReportModal(report, isAdmin = false) { @@ -763,578 +653,34 @@ function showReportModal(report, isAdmin = false) { showModal("reportViewModal"); } -// Редактирование отчета администратором -// function editReport(report) { -// const content = document.getElementById("reportViewContent"); -// const buttons = document.getElementById("reportModalButtons"); +document.getElementById("reportForm").addEventListener("submit", async (e) => { + e.preventDefault(); -// // Преобразуем просмотр в форму редактирования -// content.innerHTML = ` -//
-// -//
-//

Основная информация

-//
-//
-// -// -//
-//
-// -// -//
-//
-//
- -// -//
-//

Доходы

-//
-//
-// -// -//
-//
-// -// -//
-//
-// -// -//
-//
-//
- -// -//
-//

Статус

-// -//
-//
-// `; - -// // Изменяем кнопки на режим редактирования -// buttons.innerHTML = ` -// -// -// `; - -// 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 = ` - - - - `; - wagesContainer.appendChild(row); - }); + const formData = { + storeId: parseInt(document.getElementById("storeSelect").value), + reportDate: new Date().toISOString().split("T")[0], // Or get from input if user chooses date + income: parseFloat(document.getElementById("income").value), + initialCash: parseFloat(document.getElementById("cajaInicial").value), + totalIncome: parseFloat(document.getElementById("totalIncome").value), + wages: JSON.stringify(collectWages()), + expenses: JSON.stringify(collectExpenses()), + totalWages: calculateTotalWages(), + totalExpenses: calculateTotalExpenses() + calculateTotalWages(), + envelope: parseFloat(document.getElementById("envelope").value), + finalCash: + parseFloat(document.getElementById("totalIncome").value) - + (calculateTotalWages() + calculateTotalExpenses()) - + parseFloat(document.getElementById("envelope").value), + }; + console.log("Sending report:", formData); + const result = await createReport(formData); + if (result.success) { + showNotification("Отчет успешно создан!"); + document.getElementById("reportForm").reset(); } else { - addWageRow(); + showNotification(result.error || "Ошибка создания отчета", "error"); } - - // Заполнение расходов - 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 = ` - - - - `; - 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 reports = window.reportsList || []; - const users = window.usersList || []; - - // Расчет статистики - 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 = users.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 reports = window.reportsList || []; - const stores = window.storesList || []; - - const revenueCtx = document.getElementById("revenueChart"); - if (revenueCtx) { - destroyChart(revenueChartInstance); - 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 = reports.filter( - (r) => (r.reportDate || r.date) === dateStr - ); - - const dayRevenue = dayReports.reduce( - (sum, r) => sum + (Number(r.totalIncome) || 0), - 0 - ); - - revenueData.push(dayRevenue); - } - - revenueChartInstance = 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) { - destroyChart(expensesChartInstance); - const totalWages = reports.reduce( - (sum, r) => sum + (Number(r.totalWages) || 0), - 0 - ); - const totalInternal = reports.reduce( - (sum, r) => sum + (Number(r.totalExpenses) || 0), - 0 - ); - - expensesChartInstance = 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) { - destroyChart(storesChartInstance); - const storeData = stores.map((store) => { - const storeReports = reports.filter((r) => r.storeId === store.id); - const revenue = storeReports.reduce((sum, r) => sum + r.totalIncome, 0); - return { name: store.name, revenue }; - }); - console.log("storeData for bar chart:", storeData); - - storesChartInstance = 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) { - destroyChart(trendsChartInstance); - 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 = reports.filter( - (r) => (r.reportDate || r.date) === dateStr - ); - const dayProfit = dayReports.reduce( - (sum, r) => - sum + ((Number(r.totalIncome) || 0) - (Number(r.totalExpenses) || 0)), - 0 - ); - profitData.push(dayProfit); - } - - trendsChartInstance = 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 = ''; -// 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 = ` -// ${ -// report.date -// } -// ${ -// store ? store.name : "Неизвестно" -// } -// €${report.totalIncome.toFixed( -// 2 -// )} -// €${report.totalExpenses.toFixed( -// 2 -// )} -// €${profit.toFixed(2)} -// ${ -// user ? user.username : "Неизвестно" -// } -// -// -// ${report.verified ? "Проверен" : "Не проверен"} -// -// -// -// -// -// -// `; -// 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 deleteReport(reportId) { - showConfirmModal("Вы уверены, что хотите удалить этот отчет?", () => { - apiDeleteReport(reportId); - loadReports(); - updateDashboard(); - showNotification("Отчет удален!"); - }); -} +}); // Настройка фильтров отчетов function setupReportsFilters() { @@ -1351,7 +697,7 @@ function applyReportsFilters() { const dateFrom = document.getElementById("filterDateFrom").value; const dateTo = document.getElementById("filterDateTo").value; - let filteredReports = window.reportsList || []; + let filteredReports = appState.reportsList || []; if (storeFilter) { filteredReports = filteredReports.filter( @@ -1422,55 +768,87 @@ function applyReportsFilters() { showNotification(`Найдено ${filteredReports.length} отчетов`); } +function deleteReport(reportId) { + showConfirmModal("Вы уверены, что хотите удалить этот отчет?", () => { + apiDeleteReport(reportId); + loadReports(); + updateDashboard(); + showNotification("Отчет удален!"); + }); +} + +// Заполнение формы данными отчета (для пользователя) +function fillFormWithReport(report) { + appState.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 = ` + + + + `; + 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 = ` + + + + `; + expensesContainer.appendChild(row); + }); + } else { + addExpenseRow(); + } + + 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 === appState.currentUser.id + ); + + if (todayReport) { + showReportModal(todayReport, false); // false = не админ режим + } else { + showNotification("Отчет за сегодня не найден", "error"); + } +}); + // Экспорт в 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 exportToExcel() { // Use backend data - const reports = window.reportsList || []; - const stores = window.storesList || []; - const users = window.usersList || []; + const reports = appState.reportsList || []; + const stores = appState.storesList || []; + const users = appState.usersList || []; if (!reports.length) { showNotification("Нет отчетов для экспорта", "info"); @@ -1533,73 +911,268 @@ function exportToExcel() { showNotification("Отчет экспортирован!"); } -// === УПРАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯМИ === +//REPORTS FORM LOGIC -// Загрузка пользователей -// function loadUsers() { -// const tbody = document.getElementById("usersTableBody"); -// tbody.innerHTML = ""; +// Загрузка магазинов для пользователя +function loadUserStores() { + const select = document.getElementById("storeSelect"); + if (!select) return; + select.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 = ` -// ${user.id} -// ${ -// user.username -// } -// -// -// ${ -// user.role === "admin" -// ? "Администратор" -// : "Сотрудник" -// } -// -// -// ${storeNames} -// -// -// -// -// `; -// tbody.appendChild(row); -// }); -// } - -// Добавление пользователя -document.addEventListener("DOMContentLoaded", function () { - const addUserBtn = document.getElementById("addUserBtn"); - if (addUserBtn) { - addUserBtn.addEventListener("click", () => { - editingUserId = null; - showUserEditModal(); + // For admin: show all + if (appState.currentUser.role === "admin") { + (appState.storesList || []).forEach((store) => { + const option = document.createElement("option"); + option.value = store.id; + option.textContent = store.name; + select.appendChild(option); + }); + } else { + // For employee: only their own stores + (appState.currentUser.stores || []).forEach((storeObj) => { + // storeObj could be an object ({id, name, ...}) or just an ID; check backend API + let store = storeObj; + if (typeof storeObj === "number") { + // If backend sends just IDs, look up in appState.storesList + store = (appState.storesList || []).find((s) => s.id === storeObj); + } + if (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 = ` + + + + `; + 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 = ` + + + + `; + 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); +} + +function collectWages() { + const wages = []; + document.querySelectorAll(".wage-row").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 }); + }); + return wages; +} +function collectExpenses() { + const expenses = []; + document.querySelectorAll(".expense-row").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 }); + }); + return expenses; +} + +function safeToFixed(value, digits = 2) { + return (Number(value) || 0).toFixed(digits); +} + +//USERS + +async function loadUsers() { + console.log("Loading users..."); + const tbody = document.getElementById("usersTableBody"); + tbody.innerHTML = ""; + + const result = await getAllUsers(); + + // console.log("getAllUsers result:", result); + + if (!result.success) { + showNotification(result.error, "error"); + return; + } + + const users = result.users; + appState.usersList = users; + + users.forEach((user) => { + const userStores = + user.stores + .map((storeId) => { + const store = (appState.storesList || []).find( + (s) => s.id === storeId + ); + return store ? store.name : "Нет доступа"; + }) + .join(", ") || "Нет доступа"; + + const row = document.createElement("tr"); + row.className = "hover:bg-gray-50"; + row.innerHTML = ` + ${user.id} + ${user.username} + + + ${user.role === "admin" ? "Администратор" : "Сотрудник"} + + + ${userStores} + + + + + `; + tbody.appendChild(row); + }); + + if (users.length === 0) { + showNotification("Нет пользователей для отображения", "info"); + } +} // Редактирование пользователя function editUser(userId) { - editingUserId = userId; - const user = usersList.find((u) => u.id === userId); + appState.editingUserId = userId; + const user = appState.usersList.find((u) => u.id === userId); showUserEditModal(user); } @@ -1609,12 +1182,20 @@ function showUserEditModal(user) { const title = document.getElementById("userModalTitle"); const form = document.getElementById("userEditForm"); - // If editingUserId, it's an edit; else, it's add - title.textContent = editingUserId + // If appState.editingUserId, it's an edit; else, it's add + title.textContent = appState.editingUserId ? "Редактирование пользователя" : "Добавление пользователя"; document.getElementById("userLogin").value = (user && user.username) || ""; + const loginInput = document.getElementById("userLogin"); + loginInput.value = (user && user.username) || ""; + if (appState.editingUserId) { + loginInput.disabled = true; + } else { + loginInput.disabled = false; + } + document.getElementById("userPassword").value = ""; document.getElementById("userRole").value = (user && user.role) || "employee"; @@ -1622,7 +1203,7 @@ function showUserEditModal(user) { const storesContainer = document.getElementById("userStoresAccess"); storesContainer.innerHTML = ""; - (window.storesList || []).forEach((store) => { + (appState.storesList || []).forEach((store) => { const isChecked = user && user.stores && user.stores.includes(store.id); const checkbox = document.createElement("label"); checkbox.className = "flex items-center"; @@ -1638,22 +1219,6 @@ function showUserEditModal(user) { 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(); @@ -1680,7 +1245,7 @@ async function saveUser() { // Determine CREATE or EDIT let result; - if (!editingUserId) { + if (!appState.editingUserId) { if (!password) { showNotification("Укажите пароль для нового пользователя!", "error"); return; @@ -1692,11 +1257,11 @@ async function saveUser() { } } else { // EDIT - result = await updateUser(editingUserId, userData); + result = await updateUser(appState.editingUserId, userData); if (result.success) { showNotification("Пользователь обновлен!"); } - editingUserId = null; + appState.editingUserId = null; } // After save: UI update or error @@ -1709,27 +1274,41 @@ async function saveUser() { } } -// Удаление пользователя -// function deleteUser(userId) { -// const user = database.users.find((u) => u.id === userId); -// const userReports = database.reports.filter((r) => r.userId === userId); +//UI trigger: delete user with modal +function deleteUser(userId) { + showConfirmModal("Вы уверены, что хотите удалить этого пользователя?", () => + apiDeleteUser(userId) + ); +} -// let message = `Вы уверены, что хотите удалить пользователя "${user.username}"?`; -// if (userReports.length > 0) { -// message += `\n\nВнимание! У этого пользователя есть ${userReports.length} связанных отчетов. Они также будут удалены.`; -// } +// Добавление пользователя +document.addEventListener("DOMContentLoaded", function () { + const addUserBtn = document.getElementById("addUserBtn"); + if (addUserBtn) { + addUserBtn.addEventListener("click", () => { + appState.editingUserId = null; + showUserEditModal(); + }); + } +}); -// 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("Пользователь и связанные отчеты удалены!"); -// } -// } +// Сохранение пользователя +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"); + }); + } +}); + +//SHOPS // Загрузка магазинов async function loadStores() { @@ -1750,7 +1329,7 @@ async function loadStores() { return; } const stores = data.stores; - window.storesList = stores; + appState.storesList = stores; stores.forEach((store) => { const row = document.createElement("tr"); @@ -1785,18 +1364,9 @@ async function loadStores() { } } -// Добавление магазина -document.getElementById("addStoreBtn").onclick = () => { - window.editingStoreId = null; - document.getElementById("storeModalTitle").textContent = - "Добавление магазина"; - document.getElementById("storeName").value = ""; - showModal("storeEditModal"); -}; - // Редактирование магазина function editStore(storeId) { - window.editingStoreId = storeId; + appState.editingStoreId = storeId; showStoreEditModal(); } @@ -1806,7 +1376,9 @@ function showStoreEditModal() { const title = document.getElementById("storeModalTitle"); const form = document.getElementById("storeEditForm"); - const store = window.storesList.find((s) => s.id === window.editingStoreId); + const store = appState.storesList.find( + (s) => s.id === appState.editingStoreId + ); if (!store) { showNotification("Магазин не найден!", "error"); @@ -1826,7 +1398,7 @@ async function saveStore() { } let result; - if (window.editingStoreId == null) { + if (appState.editingStoreId == null) { // Add result = await createStore({ name }); if (result.success) { @@ -1839,7 +1411,7 @@ async function saveStore() { } } else { // Edit - result = await updateStore(window.editingStoreId, { name }); + result = await updateStore(appState.editingStoreId, { name }); if (result.success) { showNotification("Магазин обновлен!"); hideModal("storeEditModal"); @@ -1863,68 +1435,14 @@ async function saveStore() { } hideModal("storeEditModal"); - window.editingStoreId = null; + appState.editingStoreId = null; await loadStores(); if (typeof updateDashboard === "function") updateDashboard(); } -// Сохранение магазина -// 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"); -// }); -// } -// }); -//2nd version -document.addEventListener("DOMContentLoaded", function () { - const saveStoreBtn = document.getElementById("saveStoreBtn"); - const cancelStoreBtn = document.getElementById("cancelStoreBtn"); - if (saveStoreBtn) saveStoreBtn.onclick = saveStore; - if (cancelStoreBtn) - cancelStoreBtn.onclick = () => { - hideModal("storeEditModal"); - window.editingStoreId = null; - document.getElementById("storeEditForm").reset(); - }; -}); - // Удаление магазина -// 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("Магазин и связанные отчеты удалены!"); -// } -// } - function deleteStore(storeId) { - const store = window.storesList.find((s) => s.id === storeId); + const store = appState.storesList.find((s) => s.id === storeId); let message = `Вы уверены, что хотите удалить этот магазин "${store.name}"?`; if (store.reportsCount && store.reportsCount > 0) { @@ -1947,7 +1465,29 @@ async function handleDeleteStore(storeId) { } } -// === TODO СИСТЕМА === +// Добавление магазина +document.getElementById("addStoreBtn").onclick = () => { + appState.editingStoreId = null; + document.getElementById("storeModalTitle").textContent = + "Добавление магазина"; + document.getElementById("storeName").value = ""; + showModal("storeEditModal"); +}; + +// Сохранение магазина +document.addEventListener("DOMContentLoaded", function () { + const saveStoreBtn = document.getElementById("saveStoreBtn"); + const cancelStoreBtn = document.getElementById("cancelStoreBtn"); + if (saveStoreBtn) saveStoreBtn.onclick = saveStore; + if (cancelStoreBtn) + cancelStoreBtn.onclick = () => { + hideModal("storeEditModal"); + appState.editingStoreId = null; + document.getElementById("storeEditForm").reset(); + }; +}); + +//TODO // Загрузка TODO function loadTodos() { @@ -2029,20 +1569,9 @@ function toggleTodo(todoId) { 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; + appState.editingTodoId = todoId; showTodoEditModal(); } @@ -2052,8 +1581,8 @@ function showTodoEditModal() { const title = document.getElementById("todoModalTitle"); const form = document.getElementById("todoEditForm"); - if (editingTodoId) { - const todo = database.todos.find((t) => t.id === editingTodoId); + if (appState.editingTodoId) { + const todo = database.todos.find((t) => t.id === appState.editingTodoId); title.textContent = "Редактирование задачи"; document.getElementById("todoTitle").value = todo.title; document.getElementById("todoDescription").value = todo.description; @@ -2066,22 +1595,6 @@ function showTodoEditModal() { 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; @@ -2092,9 +1605,11 @@ function saveTodo() { return; } - if (editingTodoId) { + if (appState.editingTodoId) { // Редактирование - const todoIndex = database.todos.findIndex((t) => t.id === editingTodoId); + const todoIndex = database.todos.findIndex( + (t) => t.id === appState.editingTodoId + ); database.todos[todoIndex].title = title; database.todos[todoIndex].description = description; database.todos[todoIndex].priority = priority; @@ -2126,31 +1641,158 @@ function deleteTodo(todoId) { } } -// Обработчики выхода -document.getElementById("logoutBtn").addEventListener("click", logout); -document.getElementById("adminLogoutBtn").addEventListener("click", logout); +// Добавление TODO +document.addEventListener("DOMContentLoaded", function () { + const addTodoBtn = document.getElementById("addTodoBtn"); + if (addTodoBtn) { + addTodoBtn.addEventListener("click", () => { + appState.editingTodoId = null; + showTodoEditModal(); + }); + } +}); -function logout() { - currentUser = null; - editingReportId = null; +// Сохранение TODO +document.addEventListener("DOMContentLoaded", function () { + const saveTodoBtn = document.getElementById("saveTodoBtn"); + const cancelTodoBtn = document.getElementById("cancelTodoBtn"); - document.getElementById("loginScreen").classList.remove("hidden"); - document.getElementById("userInterface").classList.add("hidden"); - document.getElementById("adminInterface").classList.add("hidden"); + if (saveTodoBtn) { + saveTodoBtn.addEventListener("click", saveTodo); + } - document.getElementById("loginForm").reset(); - document.getElementById("loginError").classList.add("hidden"); + if (cancelTodoBtn) { + cancelTodoBtn.addEventListener("click", () => { + hideModal("todoEditModal"); + }); + } +}); - showNotification("Вы вышли из системы"); +// ####################MOCK########### +// База данных (симуляция) +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; } -// Глобальные функции для 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; +// Инициализация тестовых данных +generateTestData(); + +// #################################### From 599c3fb4c9a1b271485e42c346e0392aab7b8036 Mon Sep 17 00:00:00 2001 From: Angie Date: Wed, 30 Jul 2025 20:48:48 +0200 Subject: [PATCH 2/3] feat: on change of the shopname, update reports and users --- frontend/api.js | 294 +++++++---------------------------------- frontend/script.js | 317 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 319 insertions(+), 292 deletions(-) diff --git a/frontend/api.js b/frontend/api.js index 552868c..36d8f4a 100644 --- a/frontend/api.js +++ b/frontend/api.js @@ -5,22 +5,53 @@ const API_BASE_URL = "http://localhost:3001/api"; //SHARED + +//REACTIVITY (experimental) + +function createReactiveState(stateObj, onChange) { + return new Proxy(stateObj, { + set(target, prop, value) { + target[prop] = value; + onChange(prop, value); + return true; + }, + }); +} + //universal global state -window.appState = window.appState || { - currentUser: null, - usersList: [], - reportsList: [], - storesList: [], - editingReportId: null, - editingUserId: null, - editingStoreId: null, - editingTodoId: null, - revenueChartInstance: null, - expensesChartInstance: null, - storesChartInstance: null, - trendsChartInstance: null, - adminTabsInitialized: false, -}; +window.appState = createReactiveState( + window.appState || { + currentUser: null, + usersList: [], + reportsList: [], + storesList: [], + editingReportId: null, + editingUserId: null, + editingStoreId: null, + editingTodoId: null, + revenueChartInstance: null, + expensesChartInstance: null, + storesChartInstance: null, + trendsChartInstance: null, + adminTabsInitialized: false, + }, + function (prop, value) { + // React to changes in critical state + if (prop === "storesList") { + // if (typeof loadStores === "function") loadStores(); + if (typeof loadUserStores === "function") loadUserStores(); + if (typeof updateDashboard === "function") updateDashboard(); + } + if (prop === "usersList") { + // if (typeof loadUsers === "function") loadUsers(); + if (typeof updateDashboard === "function") updateDashboard(); + } + if (prop === "reportsList") { + // if (typeof loadReports === "function") loadReports(); + if (typeof updateDashboard === "function") updateDashboard(); + } + } +); //AUTH //Login & Logout @@ -289,109 +320,6 @@ async function getReports() { } } -async function loadReports() { - const tbody = document.getElementById("reportsTableBody"); - const filterStore = document.getElementById("filterStore"); - - const result = await getReports(); - console.log("getReports() result:", result); - - appState.reportsList = result.success ? result.reports : []; - - if (!result.success) { - showNotification(result.error || "Ошибка загрузки отчетов", "error"); - return; - } - const reports = result.reports; - - // Build a map storeId => storeName from all reports - const storeMap = {}; - reports.forEach((r) => { - if (r.storeId && r.storeName) { - storeMap[r.storeId] = r.storeName; - } - }); - - // Get unique [storeId, storeName] pairs sorted alphabetically - const storesWithReports = Object.entries(storeMap) - .map(([id, name]) => ({ id, name })) - .sort((a, b) => a.name.localeCompare(b.name)); - - filterStore.innerHTML = ` - - ${storesWithReports - .map((store) => ``) - .join("")} -`; - - tbody.innerHTML = ""; - reports.forEach((report) => { - const profit = - (Number(report.totalIncome) || 0) - (Number(report.totalExpenses) || 0); - - const row = document.createElement("tr"); - row.className = "hover:bg-gray-50"; - row.innerHTML = ` - ${ - report.reportDate || report.date || "" - } - ${ - report.storeName || report.storeId - } - €${Number( - report.totalIncome - ).toFixed(2)} - €${Number( - report.totalExpenses - ).toFixed(2)} - €${profit.toFixed(2)} - ${ - report.username || report.userId - } - - - ${report.isVerified ? "Проверен" : "Не проверен"} - - - - - - - `; - tbody.appendChild(row); - }); - - setupReportsFilters(); -} - -// Use global array for backend reports -function viewReport(reportId) { - if (!appState.reportsList) { - showNotification("Reports not loaded yet!", "error"); - return; - } - const report = appState.reportsList.find((r) => r.id === reportId); - if (report) { - showReportModal(report, true); - } else { - showNotification("Report not found!", "error"); - } -} - //edit report async function updateReport(reportId, data) { const token = localStorage.getItem("token"); @@ -424,136 +352,6 @@ async function updateReport(reportId, data) { } } -function editReport(report) { - console.log("editReport() called with:", report); - const content = document.getElementById("reportViewContent"); - const buttons = document.getElementById("reportModalButtons"); - - const shopName = (report.storeName || report.storeId || "").replace( - /"/g, - """ - ); - - content.innerHTML = ` -
- -
-

Основная информация

-
-
- - - -
-
- - -
-
-
- -
-

Доходы

-
-
- - -
-
- - -
-
- - -
-
-
- -
-

Статус

- -
-
- `; - - buttons.innerHTML = ` - - - `; - - document - .getElementById("saveReportBtn") - .addEventListener("click", async (e) => { - e.preventDefault(); - await saveEditedReport(report.id); - }); - - document.getElementById("cancelEditBtn").addEventListener("click", (e) => { - e.preventDefault(); - showReportModal(report, true); - }); -} - -async function saveEditedReport(reportId) { - // Get values from the form - const storeId = document.getElementById("editStoreSelect").value; - const reportDate = document.getElementById("editDate").value; - const incomeVal = document.getElementById("editIncome").value; - const initialCashVal = document.getElementById("editCajaInicial").value; - const envelopeVal = document.getElementById("editEnvelope").value; - const isVerified = document.getElementById("editVerified").checked ? 1 : 0; - - // Build the payload dynamically - const data = {}; - - if (storeId) data.storeId = parseInt(storeId, 10); - if (reportDate) data.reportDate = reportDate; - if (incomeVal !== "") data.income = parseFloat(incomeVal); - if (initialCashVal !== "") data.initialCash = parseFloat(initialCashVal); - if (envelopeVal !== "") data.envelope = parseFloat(envelopeVal); - - if (data.income !== undefined && data.initialCash !== undefined) { - data.totalIncome = data.income + data.initialCash; - } - data.isVerified = isVerified; - - // REMOVE any NaN values - Object.keys(data).forEach((k) => { - if (typeof data[k] === "number" && isNaN(data[k])) delete data[k]; - }); - - await updateReport(reportId, data); -} - //accept report (admin only) async function verifyReport(reportId) { const token = localStorage.getItem("token"); diff --git a/frontend/script.js b/frontend/script.js index 0c70199..95efe03 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -181,7 +181,7 @@ async function showAdminInterface() { "adminWelcome" ).textContent = `Добро пожаловать, ${appState.currentUser.username}!`; - await Promise.all([loadUsers(), loadReports(), loadStores()]); + await Promise.all([loadUsers(), loadStores(), loadReports()]); updateDashboard(); loadTodos(); @@ -463,6 +463,232 @@ function createCharts() { //REPORTS +async function loadReports() { + if (!appState.storesList || appState.storesList.length === 0) { + await loadStores(); + } + + const tbody = document.getElementById("reportsTableBody"); + const filterStore = document.getElementById("filterStore"); + + const result = await getReports(); + console.log("getReports() result:", result); + + appState.reportsList = result.success ? result.reports : []; + + if (!result.success) { + showNotification(result.error || "Ошибка загрузки отчетов", "error"); + return; + } + const reports = result.reports; + const stores = appState.storesList || []; + + filterStore.innerHTML = ` + + ${stores + .map((store) => ``) + .join("")} + `; + + tbody.innerHTML = ""; + reports.forEach((report) => { + const profit = + (Number(report.totalIncome) || 0) - (Number(report.totalExpenses) || 0); + + const store = stores.find((s) => Number(s.id) === Number(report.storeId)); + const storeName = store ? store.name : report.storeName || report.storeId; + + const row = document.createElement("tr"); + row.className = "hover:bg-gray-50"; + row.innerHTML = ` + ${ + report.reportDate || report.date || "" + } + ${storeName} + €${Number( + report.totalIncome + ).toFixed(2)} + €${Number( + report.totalExpenses + ).toFixed(2)} + €${profit.toFixed(2)} + ${ + report.username || report.userId + } + + + ${report.isVerified ? "Проверен" : "Не проверен"} + + + + + + + `; + tbody.appendChild(row); + }); + + setupReportsFilters(); +} + +// Use global array for backend reports +function viewReport(reportId) { + if (!appState.reportsList) { + showNotification("Reports not loaded yet!", "error"); + return; + } + const report = appState.reportsList.find((r) => r.id === reportId); + if (report) { + showReportModal(report, true); + } else { + showNotification("Report not found!", "error"); + } +} + +function editReport(report) { + console.log("editReport() called with:", report); + const content = document.getElementById("reportViewContent"); + const buttons = document.getElementById("reportModalButtons"); + + const shopName = (report.storeName || report.storeId || "").replace( + /"/g, + """ + ); + + content.innerHTML = ` +
+ +
+

Основная информация

+
+
+ + + +
+
+ + +
+
+
+ +
+

Доходы

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+

Статус

+ +
+
+ `; + + buttons.innerHTML = ` + + + `; + + document + .getElementById("saveReportBtn") + .addEventListener("click", async (e) => { + e.preventDefault(); + await saveEditedReport(report.id); + }); + + document.getElementById("cancelEditBtn").addEventListener("click", (e) => { + e.preventDefault(); + showReportModal(report, true); + }); +} + +async function saveEditedReport(reportId) { + // Get values from the form + const storeId = document.getElementById("editStoreSelect").value; + const reportDate = document.getElementById("editDate").value; + const incomeVal = document.getElementById("editIncome").value; + const initialCashVal = document.getElementById("editCajaInicial").value; + const envelopeVal = document.getElementById("editEnvelope").value; + const isVerified = document.getElementById("editVerified").checked ? 1 : 0; + + // Build the payload dynamically + const data = {}; + + if (storeId) data.storeId = parseInt(storeId, 10); + if (reportDate) data.reportDate = reportDate; + if (incomeVal !== "") data.income = parseFloat(incomeVal); + if (initialCashVal !== "") data.initialCash = parseFloat(initialCashVal); + if (envelopeVal !== "") data.envelope = parseFloat(envelopeVal); + + if (data.income !== undefined && data.initialCash !== undefined) { + data.totalIncome = data.income + data.initialCash; + } + data.isVerified = isVerified; + + // REMOVE any NaN values + Object.keys(data).forEach((k) => { + if (typeof data[k] === "number" && isNaN(data[k])) delete data[k]; + }); + + await updateReport(reportId, data); +} + // Показ модального окна отчета с исправленной прокруткой function showReportModal(report, isAdmin = false) { const modal = document.getElementById("reportViewModal"); @@ -653,35 +879,6 @@ function showReportModal(report, isAdmin = false) { showModal("reportViewModal"); } -document.getElementById("reportForm").addEventListener("submit", async (e) => { - e.preventDefault(); - - const formData = { - storeId: parseInt(document.getElementById("storeSelect").value), - reportDate: new Date().toISOString().split("T")[0], // Or get from input if user chooses date - income: parseFloat(document.getElementById("income").value), - initialCash: parseFloat(document.getElementById("cajaInicial").value), - totalIncome: parseFloat(document.getElementById("totalIncome").value), - wages: JSON.stringify(collectWages()), - expenses: JSON.stringify(collectExpenses()), - totalWages: calculateTotalWages(), - totalExpenses: calculateTotalExpenses() + calculateTotalWages(), - envelope: parseFloat(document.getElementById("envelope").value), - finalCash: - parseFloat(document.getElementById("totalIncome").value) - - (calculateTotalWages() + calculateTotalExpenses()) - - parseFloat(document.getElementById("envelope").value), - }; - console.log("Sending report:", formData); - const result = await createReport(formData); - if (result.success) { - showNotification("Отчет успешно создан!"); - document.getElementById("reportForm").reset(); - } else { - showNotification(result.error || "Ошибка создания отчета", "error"); - } -}); - // Настройка фильтров отчетов function setupReportsFilters() { document @@ -829,20 +1026,6 @@ function fillFormWithReport(report) { 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 === appState.currentUser.id - ); - - if (todayReport) { - showReportModal(todayReport, false); // false = не админ режим - } else { - showNotification("Отчет за сегодня не найден", "error"); - } -}); - // Экспорт в Excel function exportToExcel() { // Use backend data @@ -1103,6 +1286,50 @@ function safeToFixed(value, digits = 2) { return (Number(value) || 0).toFixed(digits); } +document.getElementById("reportForm").addEventListener("submit", async (e) => { + e.preventDefault(); + + const formData = { + storeId: parseInt(document.getElementById("storeSelect").value), + reportDate: new Date().toISOString().split("T")[0], // Or get from input if user chooses date + income: parseFloat(document.getElementById("income").value), + initialCash: parseFloat(document.getElementById("cajaInicial").value), + totalIncome: parseFloat(document.getElementById("totalIncome").value), + wages: JSON.stringify(collectWages()), + expenses: JSON.stringify(collectExpenses()), + totalWages: calculateTotalWages(), + totalExpenses: calculateTotalExpenses() + calculateTotalWages(), + envelope: parseFloat(document.getElementById("envelope").value), + finalCash: + parseFloat(document.getElementById("totalIncome").value) - + (calculateTotalWages() + calculateTotalExpenses()) - + parseFloat(document.getElementById("envelope").value), + }; + console.log("Sending report:", formData); + const result = await createReport(formData); + if (result.success) { + showNotification("Отчет успешно создан!"); + await loadReports(); + document.getElementById("reportForm").reset(); + } else { + showNotification(result.error || "Ошибка создания отчета", "error"); + } +}); + +// Отчет за сегодня для пользователя +document.getElementById("todayReportBtn").addEventListener("click", () => { + const today = new Date().toISOString().split("T")[0]; + const todayReport = database.reports.find( + (r) => r.date === today && r.userId === appState.currentUser.id + ); + + if (todayReport) { + showReportModal(todayReport, false); // false = не админ режим + } else { + showNotification("Отчет за сегодня не найден", "error"); + } +}); + //USERS async function loadUsers() { @@ -1329,6 +1556,7 @@ async function loadStores() { return; } const stores = data.stores; + // appState.usersList = users; appState.storesList = stores; stores.forEach((store) => { @@ -1416,6 +1644,7 @@ async function saveStore() { showNotification("Магазин обновлен!"); hideModal("storeEditModal"); await loadStores(); + await loadReports(); if (typeof loadUsers === "function") loadUsers(); if (typeof loadUserStores === "function") loadUserStores(); if (typeof updateDashboard === "function") updateDashboard(); From 000c23aea1c7bfa4d9cccd487e06d9f420ba391f Mon Sep 17 00:00:00 2001 From: Angie Date: Wed, 30 Jul 2025 20:59:11 +0200 Subject: [PATCH 3/3] update: changed the API URL --- frontend/api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/api.js b/frontend/api.js index 36d8f4a..a1da71e 100644 --- a/frontend/api.js +++ b/frontend/api.js @@ -1,8 +1,8 @@ //API -// const API_BASE_URL = "http://195.209.214.159/api"; +const API_BASE_URL = "http://195.209.214.159/api"; //API local -const API_BASE_URL = "http://localhost:3001/api"; +// const API_BASE_URL = "http://localhost:3001/api"; //SHARED