//API // const API_BASE_URL = "http://195.209.214.159/api"; //API local 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 = createReactiveState( window.appState || { currentUser: null, usersList: [], reportsList: [], storesList: [], todaysReports: [], editingReportId: null, editingUserId: null, editingStoreId: null, editingTodoId: null, revenueChartInstance: null, expensesChartInstance: null, storesChartInstance: null, trendsChartInstance: null, adminTabsInitialized: false, initialEditReportFormData: null, }, function (prop, value) { // React to changes in critical state if (prop === "storesList") { if (typeof loadUserStores === "function") loadUserStores(); if (typeof updateDashboard === "function") updateDashboard(); } if (prop === "usersList") { if (typeof updateDashboard === "function") updateDashboard(); } if (prop === "reportsList") { if (typeof updateDashboard === "function") updateDashboard(); } } ); //AUTH //Login & Logout async function loginUser(username, password) { try { const response = await fetch(`${API_BASE_URL}/auth/login`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username, password }), }); const data = await response.json(); if (response.ok) { return { success: true, ...data }; } else { return { success: false, error: data.error || (data.errors && data.errors[0]?.msg) || "Ошибка авторизации", }; } } catch (err) { return { success: false, error: "Нет соединения с сервером" }; } } function logout() { appState.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"); document.getElementById("reportForm").reset(); if (document.getElementById("storeSelect")) document.getElementById("storeSelect").selectedIndex = 0; showNotification("Вы вышли из системы", "info"); } document.addEventListener("DOMContentLoaded", async () => { const token = localStorage.getItem("token"); if (token) { // Try to get user info from backend try { const response = await fetch(`${API_BASE_URL}/auth/me`, { headers: { Authorization: `Bearer ${token}`, }, }); if (response.ok) { const data = await response.json(); appState.currentUser = data.user; document.getElementById("loginScreen").classList.add("hidden"); if (appState.currentUser.role === "admin") { showAdminInterface(); } else { showUserInterface(); } } else { // Token invalid/expired localStorage.removeItem("token"); document.getElementById("loginScreen").classList.remove("hidden"); } } catch (err) { showNotification("Нет соединения с сервером", "error"); } } else { // No token, show login screen document.getElementById("loginScreen").classList.remove("hidden"); } }); document.getElementById("logoutBtn").addEventListener("click", logout); document.getElementById("adminLogoutBtn").addEventListener("click", logout); //USERS //GET all users (admin only) async function getAllUsers() { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/users`, { method: "GET", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }); const data = await response.json(); if (response.ok) { return { success: true, users: data.users }; } else { return { success: false, error: data.error || data.message || "Ошибка получения пользователей", }; } } catch (err) { return { success: false, error: "Нет соединения с сервером" }; } } appState.usersList = []; //add user async function createUser(userData) { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/users`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify(userData), }); const data = await response.json(); if (response.ok) { return { success: true, ...data }; } else { return { success: false, error: data.error || (data.errors && data.errors[0]?.msg) || "Ошибка создания пользователя", }; } } catch { return { success: false, error: "Нет соединения с сервером" }; } } //edit user async function updateUser(userId, userData) { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/users/${userId}`, { method: "PUT", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify(userData), }); const data = await response.json(); if (response.ok) { return { success: true }; } else { return { success: false, error: data.error || (data.errors && data.errors[0]?.msg) || "Ошибка обновления пользователя", }; } } catch { return { success: false, error: "Нет соединения с сервером" }; } } // 1. Create modal on-demand if missing function ensureConfirmModal() { if (document.getElementById("confirmModal")) return; const modal = document.createElement("div"); modal.id = "confirmModal"; modal.className = "hidden fixed inset-0 z-50 bg-black bg-opacity-30 flex items-center justify-center"; modal.innerHTML = `
`; document.body.appendChild(modal); } // 2. Show confirm modal function showConfirmModal(message, onConfirm) { ensureConfirmModal(); const modal = document.getElementById("confirmModal"); const text = document.getElementById("confirmModalText"); const okBtn = document.getElementById("confirmModalOk"); const cancelBtn = document.getElementById("confirmModalCancel"); text.textContent = message; modal.classList.remove("hidden"); function cleanup() { modal.classList.add("hidden"); okBtn.removeEventListener("click", okHandler); cancelBtn.removeEventListener("click", cancelHandler); } function okHandler() { cleanup(); onConfirm(); } function cancelHandler() { cleanup(); } okBtn.addEventListener("click", okHandler); cancelBtn.addEventListener("click", cancelHandler); } // 3. API call to delete user async function apiDeleteUser(userId) { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/users/${userId}`, { method: "DELETE", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }); const data = await response.json(); if (response.ok) { showNotification("Пользователь удален!"); loadUsers(); updateDashboard(); } else { showNotification(data.error || "Ошибка удаления пользователя", "error"); } } catch { showNotification("Нет соединения с сервером", "error"); } } //REPORTS ADMIN //GET all reports async function getReports() { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/reports`, { method: "GET", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); if (response.ok) { return { success: true, reports: data.reports }; } else { return { success: false, error: data.error || data.message || "Ошибка получения отчетов", }; } } catch (err) { return { success: false, error: "Нет соединения с сервером" }; } } //GET single report by ID async function getReportById(reportId) { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/reports/${reportId}`, { headers: { Authorization: `Bearer ${token}` }, }); const data = await response.json(); if (response.ok && data.report) { return { success: true, report: data.report }; } else { return { success: false, error: data.error || "Ошибка получения отчета" }; } } catch (err) { return { success: false, error: "Нет соединения с сервером" }; } } //edit report async function updateReport(reportId, data) { const token = localStorage.getItem("token"); try { const res = await fetch(`${API_BASE_URL}/reports/${reportId}`, { method: "PUT", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify(data), }); const result = await res.json(); if (res.ok && result.updated) { showNotification("Отчет обновлен!", "success"); hideModal("reportViewModal"); await loadReports(); console.log(data); if (typeof updateDashboard === "function") updateDashboard(); return { success: true }; } else { showNotification(result.error || "Ошибка обновления отчета", "error"); return { success: false, error: result.error }; } } catch (err) { showNotification("Ошибка сети. Попробуйте еще раз.", "error"); return { success: false, error: err.message }; } } //accept report (admin only) async function verifyReport(reportId) { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/reports/${reportId}/verify`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }); const data = await response.json(); if (response.ok) { showNotification("Отчет успешно подтвержден!", "success"); hideModal("reportViewModal"); await loadReports(); if (typeof updateDashboard === "function") { updateDashboard(); // Refresh dashboard if it exists } return { success: true, ...data }; } else { showNotification(data.error || "Ошибка подтверждения отчета", "error"); return { success: false, error: data.error || "Ошибка" }; } } catch (err) { showNotification("Нет соединения с сервером", "error"); return { success: false, error: "Нет соединения с сервером" }; } } //delete report (admin only) async function apiDeleteReport(reportId) { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/reports/${reportId}`, { method: "DELETE", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }); const data = await response.json(); if (response.ok && data.deleted) { showNotification("Отчет удален!"); await loadReports(); if (typeof updateDashboard === "function") updateDashboard(); } else { showNotification(data.error || "Ошибка удаления отчета", "error"); } } catch { showNotification("Нет соединения с сервером", "error"); } } //REPORTS WORKER //create report async function createReport(data) { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/reports`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify(data), }); let result = {}; try { result = await response.json(); } catch (e) { // In case response is not JSON console.error("Could not parse JSON from server:", e); result = {}; } // Debug: Log response status and result console.log( "createReport response.status:", response.status, "result:", result ); if (response.ok && result.id) { // Successfully created return { success: true, id: result.id }; } else { // Extract error let errorMsg = result.error || (Array.isArray(result.errors) && result.errors[0]?.msg) || response.statusText || "Ошибка создания отчета"; if (response.status === 409) { errorMsg = "Отчет за этот магазин и дату уже был отправлен этим пользователем."; } // Debug: Log error case console.warn( "createReport failed:", errorMsg, "Status:", response.status, result ); return { success: false, error: errorMsg }; } } catch (err) { console.error("Network/server error in createReport:", err); return { success: false, error: "Нет соединения с сервером" }; } } //STORES (for admin) // 1. Get all stores async function getStores() { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/stores`, { method: "GET", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }); const data = await response.json(); if (response.ok) { return { success: true, stores: data.stores }; } else { return { success: false, error: data.error || "Ошибка получения магазинов", }; } } catch { return { success: false, error: "Нет соединения с сервером" }; } } // 2. Create new store (admin only) async function createStore(storeData) { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/stores`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify(storeData), }); const data = await response.json(); if (response.ok && data.id) { return { success: true, id: data.id }; } else { return { success: false, error: data.error || "Ошибка создания магазина", }; } } catch { return { success: false, error: "Нет соединения с сервером" }; } } // 3. Edit store (admin only) async function updateStore(storeId, storeData) { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/stores/${storeId}`, { method: "PUT", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify(storeData), }); const data = await response.json(); return { success: response.ok && data.updated, error: data.error || "", status: response.status, }; } catch { return { success: false, error: "Нет соединения с сервером" }; } } // 4. Delete store (admin only) async function deleteStoreApi(storeId) { const token = localStorage.getItem("token"); try { const response = await fetch(`${API_BASE_URL}/stores/${storeId}`, { method: "DELETE", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }); const data = await response.json(); if (response.ok && data.deleted) { return { success: true }; } else { return { success: false, error: data.error || "Ошибка удаления магазина", }; } } catch { return { success: false, error: "Нет соединения с сервером" }; } }