cash-report-system/frontend/api.js
2025-08-09 14:36:22 +00:00

607 lines
17 KiB
JavaScript

//API
//const API_BASE_URL = "http://195.209.214.159/api";
//API Product
const API_BASE_URL = "https://csr.bbox.wtf/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) ||
"Error de autenticación",
};
}
} catch (err) {
return { success: false, error: "Sin conexión con el servidor" };
}
}
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("Sesión cerrada", "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("Sin conexión con el servidor", "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 || "Error al obtener usuarios",
};
}
} catch (err) {
return { success: false, error: "Sin conexión con el servidor" };
}
}
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) ||
"Error al crear usuario",
};
}
} catch {
return { success: false, error: "Sin conexión con el servidor" };
}
}
//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) ||
"Error al actualizar usuario",
};
}
} catch {
return { success: false, error: "Sin conexión con el servidor" };
}
}
// 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">Eliminar</button>
<button id="confirmModalCancel" class="px-5 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400">Cancelar</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("Usuario eliminado!");
loadUsers();
updateDashboard();
} else {
showNotification(data.error || "Error al eliminar usuario", "error");
}
} catch {
showNotification("Sin conexión con el servidor", "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 || "Error al obtener informes",
};
}
} catch (err) {
return { success: false, error: "Sin conexión con el servidor" };
}
}
//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 || "Error al obtener informe" };
}
} catch (err) {
return { success: false, error: "Sin conexión con el servidor" };
}
}
//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(
"Ya existe un informe para esta tienda y fecha.",
"error"
);
return { success: false, error: "Informe duplicado" };
}
const result = await res.json();
if (res.ok && result.updated) {
showNotification("Informe actualizado!", "success");
hideModal("reportViewModal");
await loadReports();
console.log(data);
if (typeof updateDashboard === "function") updateDashboard();
return { success: true };
} else {
showNotification(result.error || "Error al actualizar informe", "error");
return { success: false, error: result.error };
}
} catch (err) {
showNotification("Error de red. Intente nuevamente.", "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("Informe verificado exitosamente!", "success");
hideModal("reportViewModal");
await loadReports();
if (typeof updateDashboard === "function") {
updateDashboard(); // Refresh dashboard if it exists
}
return { success: true, ...data };
} else {
showNotification(data.error || "Error al verificar informe", "error");
return { success: false, error: data.error || "Error" };
}
} catch (err) {
showNotification("Sin conexión con el servidor", "error");
return { success: false, error: "Sin conexión con el servidor" };
}
}
//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("Informe eliminado!");
await loadReports();
if (typeof updateDashboard === "function") updateDashboard();
} else {
showNotification(data.error || "Error al eliminar informe", "error");
}
} catch {
showNotification("Sin conexión con el servidor", "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 ||
"Error al crear informe";
if (response.status === 409) {
errorMsg =
"Ya existe un informe para esta tienda y fecha.";
}
// 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: "Sin conexión con el servidor" };
}
}
//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 || "Error al obtener tiendas",
};
}
} catch {
return { success: false, error: "Sin conexión con el servidor" };
}
}
// 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 || "Error al crear tienda",
};
}
} catch {
return { success: false, error: "Sin conexión con el servidor" };
}
}
// 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: "Sin conexión con el servidor" };
}
}
// 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 || "Error al eliminar tienda",
};
}
} catch {
return { success: false, error: "Sin conexión con el servidor" };
}
}