607 lines
17 KiB
JavaScript
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" };
|
|
}
|
|
}
|