Files
cash-report-system/frontend/api.js
2025-08-06 19:30:21 +02:00

604 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//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;
appState.todaysReports = data.todaysReports || [];
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 = `
<div class="bg-white rounded-lg shadow-lg p-8 max-w-sm w-full relative">
<div id="confirmModalText" class="text-lg text-gray-700 mb-6"></div>
<div class="flex justify-end space-x-4">
<button id="confirmModalOk" class="px-5 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700">Удалить</button>
<button id="confirmModalCancel" class="px-5 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400">Отмена</button>
</div>
</div>
`;
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),
});
// handler for duplicates error before parsing
if (res.status === 409) {
showNotification(
"Отчет за этот магазин и дату уже был отправлен этим пользователем.",
"error"
);
return { success: false, error: "Дубликат отчета" };
}
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: "Нет соединения с сервером" };
}
}