//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: "Нет соединения с сервером" };
}
}