536 lines
15 KiB
JavaScript
536 lines
15 KiB
JavaScript
//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: [],
|
||
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 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");
|
||
|
||
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 = `
|
||
<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: "Нет соединения с сервером" };
|
||
}
|
||
}
|
||
|
||
//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
|
||
// api.js
|
||
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),
|
||
});
|
||
const result = await response.json();
|
||
if (response.ok && result.id) {
|
||
return { success: true, id: result.id };
|
||
} else {
|
||
return {
|
||
success: false,
|
||
error:
|
||
result.error ||
|
||
(result.errors && result.errors[0]?.msg) ||
|
||
"Ошибка создания отчета",
|
||
};
|
||
}
|
||
} catch (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: "Нет соединения с сервером" };
|
||
}
|
||
}
|