//SHARED
// Инициализация Flatpickr
document.addEventListener('DOMContentLoaded', () => {
// Установка сегодняшней даты по умолчанию
const today = new Date().toISOString().split('T')[0];
document.getElementById("reportDate").value = today;
flatpickr("#reportDate", {
locale: "es",
dateFormat: "Y-m-d",
maxDate: "today",
altInput: true,
altFormat: "j F Y",
ariaDateFormat: "j F Y"
});
const demoHint = document.getElementById('demoAccountsHint');
if (demoHint && window.GENERATE_DEMO_DATA === 'true') {
demoHint.removeAttribute('hidden');
}
});
//helper to destroyChart on change
function destroyChart(instance) {
if (instance) instance.destroy();
}
// Система уведомлений
// function showNotification(message, type = "success") {
// const notification = document.getElementById("notification");
// const notificationText = document.getElementById("notificationText");
// if (!notification || !notificationText) return;
// notificationText.textContent = message;
// notification.className = `fixed top-4 right-4 z-50 animate-fade-in`;
// if (type === "error") {
// notification.innerHTML = `
//
//
//
// ${message}
//
//
// `;
// } else {
// notification.innerHTML = `
//
//
//
// ${message}
//
//
// `;
// }
// notification.classList.remove("hidden");
// setTimeout(() => {
// notification.classList.add("hidden");
// }, 3000);
// }
let notificationTimeout = null; // At top of your script!
function showNotification(message, type = "success") {
const notification = document.getElementById("notification");
if (!notification) return;
notification.innerHTML = `
`;
notification.className = "fixed top-4 right-4 z-50 animate-fade-in";
notification.classList.remove("hidden");
notification.style.display = "block";
// previous timeout exists -> clear it!
if (notificationTimeout) {
clearTimeout(notificationTimeout);
}
notificationTimeout = setTimeout(() => {
notification.classList.add("hidden");
notification.style.display = "none";
notificationTimeout = null;
}, 3000);
}
// Gestión de ventanas modales con desplazamiento corregido
function showModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.add("show");
// Bloquear desplazamiento del cuerpo
document.body.classList.add("modal-open");
}
}
function hideModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.remove("show");
// Разблокируем прокрутку body
document.body.classList.remove("modal-open");
}
}
// Настройка вкладок администратора
function setupAdminTabs() {
if (appState.adminTabsInitialized) return;
appState.adminTabsInitialized = true;
const tabButtons = document.querySelectorAll(".admin-tab-btn");
const tabContents = document.querySelectorAll(".admin-tab-content");
tabButtons.forEach((button) => {
button.addEventListener("click", () => {
const tabId = button.dataset.tab;
// Переключение активной вкладки
tabButtons.forEach((btn) => {
btn.className =
"admin-tab-btn px-6 py-3 font-medium text-gray-600 hover:text-blue-600 transition-colors border-b-2 border-transparent hover:border-blue-500";
});
button.className =
"admin-tab-btn px-6 py-3 font-medium transition-colors border-b-2 border-blue-500 text-blue-600";
// Показ/скрытие содержимого
tabContents.forEach((content) => {
content.classList.add("hidden");
});
document.getElementById(tabId + "Tab").classList.remove("hidden");
if (tabId === "dashboard") updateDashboard();
// Optionally, reload todos for TODO tab
if (tabId === "todo") loadTodos();
// Загрузка данных при переключении
// switch (tabId) {
// case "dashboard":
// updateDashboard();
// break;
// case "reports":
// loadReports();
// break;
// case "users":
// loadUsers();
// break;
// case "stores":
// loadStores();
// break;
// case "todo":
// loadTodos();
// break;
// }
});
});
}
// Закрытие модального окна при клике вне его
document.addEventListener("click", (e) => {
if (e.target.classList.contains("modal")) {
hideModal(e.target.id);
}
});
// Глобальные функции для onclick обработчиков
window.viewReport = viewReport;
window.deleteReport = deleteReport;
window.editUser = editUser;
window.deleteUser = deleteUser;
window.editStore = editStore;
window.deleteStore = deleteStore;
window.editTodo = editTodo;
window.deleteTodo = deleteTodo;
window.toggleTodo = toggleTodo;
//AUTH
// Обработчики авторизации
document.getElementById("loginForm").addEventListener("submit", async (e) => {
e.preventDefault();
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
// Call backend login
const result = await loginUser(username, password);
if (result.success) {
// Save token for later API calls
localStorage.setItem("token", result.token);
// Save user info in global state
appState.currentUser = result.user;
// save globally reports of today for user if they are presented in what login giving back
appState.todaysReports = result.todaysReports || [];
// Hide login, show correct UI
document.getElementById("loginScreen").classList.add("hidden");
if (result.user.role === "admin") {
showAdminInterface();
} else {
showUserInterface();
}
showNotification("¡Autenticación exitosa!");
} else {
const errorDiv = document.getElementById("loginError");
errorDiv.textContent = result.error || "Usuario o contraseña incorrectos";
errorDiv.classList.remove("hidden");
}
});
// Показать интерфейс пользователя
async function showUserInterface() {
document.getElementById("userInterface").classList.remove("hidden");
document.getElementById(
"userWelcome"
).textContent = `Bienvenido, ${appState.currentUser.username}!`;
loadUserStores();
setupFormCalculations();
await loadReports();
}
// Показать интерфейс администратора
async function showAdminInterface() {
document.getElementById("adminInterface").classList.remove("hidden");
document.getElementById(
"adminWelcome"
).textContent = `Bienvenido, ${appState.currentUser.username}!`;
await loadStores();
await loadUsers();
await loadReports();
updateDashboard();
loadTodos();
setupAdminTabs();
// Activate first tab (dashboard)
document.querySelector('.admin-tab-btn[data-tab="dashboard"]').click();
}
// Обработчики выхода
document.getElementById("logoutBtn").addEventListener("click", logout);
document.getElementById("adminLogoutBtn").addEventListener("click", logout);
function logout() {
appState.currentUser = null;
appState.editingReportId = null;
appState.usersList = [];
appState.storesList = [];
appState.reportsList = [];
// database.users = [];
// database.reports = [];
// database.stores = [];
// Remove all admin tab event listeners by replacing each node
document.querySelectorAll(".admin-tab-btn").forEach((btn) => {
btn.replaceWith(btn.cloneNode(true));
});
appState.adminTabsInitialized = false;
document.getElementById("loginScreen").classList.remove("hidden");
document.getElementById("userInterface").classList.add("hidden");
document.getElementById("adminInterface").classList.add("hidden");
const reportForm = document.getElementById("reportForm");
if (reportForm) {
reportForm.reset();
document.getElementById("wagesContainer").innerHTML = "";
addWageRow(); // Add one empty row
document.getElementById("expensesContainer").innerHTML = "";
addExpenseRow();
}
document.getElementById("loginForm").reset();
document.getElementById("loginError").classList.add("hidden");
showNotification("¡Sesión cerrada!");
}
//DASHBOARD
// Обновление дашборда
function updateDashboard() {
const reports = appState.reportsList || [];
const users = appState.usersList || [];
// Расчет статистики
const totalRevenue = reports.reduce((sum, r) => sum + r.totalIncome, 0);
const totalExpenses = reports.reduce((sum, r) => sum + r.totalExpenses, 0);
const totalReports = reports.length;
const totalUsers = users.length;
// Обновление карточек
document.getElementById(
"totalRevenueCard"
).textContent = `€${totalRevenue.toFixed(2)}`;
document.getElementById(
"totalExpensesCard"
).textContent = `€${totalExpenses.toFixed(2)}`;
document.getElementById("totalReportsCard").textContent = totalReports;
document.getElementById("totalUsersCard").textContent = totalUsers;
// Создание графиков
createCharts();
}
// Создание графиков
function createCharts() {
// График доходов по дням
const reports = appState.reportsList || [];
const stores = appState.storesList || [];
const revenueCtx = document.getElementById("revenueChart");
if (revenueCtx) {
destroyChart(appState.revenueChartInstance);
const last7Days = [];
const revenueData = [];
for (let i = 6; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split("T")[0];
last7Days.push(dateStr);
const dayReports = reports.filter(
(r) => (r.reportDate || r.date) === dateStr
);
const dayRevenue = dayReports.reduce(
(sum, r) => sum + (Number(r.totalIncome) || 0),
0
);
revenueData.push(dayRevenue);
}
appState.revenueChartInstance = new Chart(revenueCtx, {
type: "line",
data: {
labels: last7Days,
datasets: [
{
label: "Доходы",
data: revenueData,
borderColor: "rgb(59, 130, 246)",
backgroundColor: "rgba(59, 130, 246, 0.1)",
tension: 0.4,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function (value) {
return "€" + value.toFixed(0);
},
},
},
},
},
});
}
// Круговая диаграмма расходов
const expensesCtx = document.getElementById("expensesChart");
if (expensesCtx) {
destroyChart(appState.expensesChartInstance);
const totalWages = reports.reduce(
(sum, r) => sum + (Number(r.totalWages) || 0),
0
);
const totalInternal = reports.reduce(
(sum, r) => sum + (Number(r.totalExpenses) || 0),
0
);
const totalEnvelope = reports.reduce(
(sum, r) => sum + (Number(r.envelope) || 0),
0
);
appState.expensesChartInstance = new Chart(expensesCtx, {
type: "doughnut",
data: {
labels: ["Salarios", "Otros gastos", "Envelope"],
datasets: [
{
data: [totalWages, totalInternal, totalEnvelope],
backgroundColor: ["#F59E0B", "#EF4444", "#3B82F6"],
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom",
},
},
},
});
}
// График по магазинам
const storesCtx = document.getElementById("storesChart");
if (storesCtx) {
destroyChart(appState.storesChartInstance);
const storeData = stores.map((store) => {
const storeReports = reports.filter((r) => r.storeId === store.id);
const revenue = storeReports.reduce((sum, r) => sum + r.totalIncome, 0);
return { name: store.name, revenue };
});
// console.log("storeData for bar chart:", storeData);
appState.storesChartInstance = new Chart(storesCtx, {
type: "bar",
data: {
labels: storeData.map((s) => s.name),
datasets: [
{
label: "Ingresos por tienda",
data: storeData.map((s) => s.revenue),
backgroundColor: "#10B981",
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function (value) {
return "€" + value.toFixed(0);
},
},
},
},
},
});
}
// Тренды продаж
const trendsCtx = document.getElementById("trendsChart");
if (trendsCtx) {
destroyChart(appState.trendsChartInstance);
const last30Days = [];
const profitData = [];
for (let i = 29; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split("T")[0];
last30Days.push(dateStr);
const dayReports = reports.filter(
(r) => (r.reportDate || r.date) === dateStr
);
const dayProfit = dayReports.reduce(
(sum, r) =>
sum + ((Number(r.totalIncome) || 0) - (Number(r.totalExpenses) || 0)),
0
);
profitData.push(dayProfit);
}
appState.trendsChartInstance = new Chart(trendsCtx, {
type: "line",
data: {
labels: last30Days,
datasets: [
{
label: "Beneficio",
data: profitData,
borderColor: "#8B5CF6",
backgroundColor: "rgba(139, 92, 246, 0.1)",
tension: 0.4,
fill: true,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function (value) {
return "€" + value.toFixed(0);
},
},
},
},
},
});
}
}
//REPORTS
async function loadReports() {
if (!appState.storesList || appState.storesList.length === 0) {
await loadStores();
return;
}
const tbody = document.getElementById("reportsTableBody");
const filterStore = document.getElementById("filterStore");
const result = await getReports();
// console.log("getReports() result:", result);
appState.reportsList = result.success ? result.reports : [];
if (!result.success) {
showNotification(result.error || "Error al cargar informes", "error");
return;
}
// extract reports array
const reports = result.reports;
const stores = appState.storesList || [];
// parse wages/expenses from JSON string to array ---
reports.forEach((report) => {
// parse wages if it's a string (backend gives string, we need array)
if (typeof report.wages === "string") {
try {
report.wages = JSON.parse(report.wages);
} catch {
report.wages = [];
}
}
// parse expenses if it's a string
if (typeof report.expenses === "string") {
try {
report.expenses = JSON.parse(report.expenses);
} catch {
report.expenses = [];
}
}
});
filterStore.innerHTML = `
${stores
.map((store) => ``)
.join("")}
`;
// render table rows
tbody.innerHTML = "";
reports.forEach((report) => {
const profit =
(Number(report.totalIncome) || 0) - (Number(report.totalExpenses) || 0);
const store = stores.find((s) => Number(s.id) === Number(report.storeId));
const storeName = store ? store.name : report.storeName || report.storeId;
const row = document.createElement("tr");
row.className = "hover:bg-gray-50";
row.innerHTML = `
${
report.reportDate || report.date || ""
} |
${storeName} |
€${Number(
report.totalIncome
).toFixed(2)} |
€${Number(
report.totalExpenses
).toFixed(2)} |
€${profit.toFixed(2)} |
${
report.username || report.userId
} |
${report.isVerified ? "Verificado" : "No verificado"}
|
|
`;
tbody.appendChild(row);
});
setupReportsFilters();
}
// Use global array for backend reports
function viewReport(reportId) {
if (!appState.reportsList) {
showNotification("Reports not loaded yet!", "error");
return;
}
const report = appState.reportsList.find((r) => r.id === reportId);
if (report) {
showReportModal(report, true);
} else {
showNotification("Report not found!", "error");
}
}
function editReport(report) {
// console.log("editReport() called with:", report);
appState.editingReportId = report.id;
const content = document.getElementById("reportViewContent");
const buttons = document.getElementById("reportModalButtons");
const shopName = (report.storeName || report.storeId || "").replace(
/"/g,
"""
);
content.innerHTML = `
`;
buttons.innerHTML = `
`;
document
.getElementById("saveReportBtn")
.addEventListener("click", async (e) => {
e.preventDefault();
await saveEditedReport(report.id);
});
document.getElementById("cancelEditBtn").addEventListener("click", (e) => {
e.preventDefault();
showReportModal(report, true);
});
}
async function saveEditedReport(reportId) {
// Get values from the form
const storeId = document.getElementById("editStoreSelect").value;
const reportDate = document.getElementById("editDate").value;
const incomeVal = document.getElementById("editIncome").value;
const initialCashVal = document.getElementById("editCajaInicial").value;
const envelopeVal = document.getElementById("editEnvelope").value;
const isVerified = document.getElementById("editVerified").checked ? 1 : 0;
// Build the payload dynamically
const data = {};
if (storeId) data.storeId = parseInt(storeId, 10);
if (reportDate) data.reportDate = reportDate;
if (incomeVal !== "") data.income = parseFloat(incomeVal);
if (initialCashVal !== "") data.initialCash = parseFloat(initialCashVal);
if (envelopeVal !== "") data.envelope = parseFloat(envelopeVal);
if (data.income !== undefined && data.initialCash !== undefined) {
data.totalIncome = data.income + data.initialCash;
}
data.isVerified = isVerified;
// REMOVE any NaN values
Object.keys(data).forEach((k) => {
if (typeof data[k] === "number" && isNaN(data[k])) delete data[k];
});
await updateReport(reportId, data);
}
// Показ модального окна отчета с исправленной прокруткой
function showReportModal(report, isAdmin = false) {
// --- Parse wages/expenses as arrays if needed ---
let wages = report.wages;
let expenses = report.expenses;
if (typeof wages === "string") {
try {
wages = JSON.parse(wages);
} catch {
wages = [];
}
}
if (typeof expenses === "string") {
try {
expenses = JSON.parse(expenses);
} catch {
expenses = [];
}
}
let totalExpensesInternal = 0;
if (Array.isArray(expenses)) {
totalExpensesInternal = expenses.reduce(
(sum, e) => sum + (Number(e.amount) || 0),
0
);
}
const modal = document.getElementById("reportViewModal");
const content = document.getElementById("reportViewContent");
const buttons = document.getElementById("reportModalButtons");
const title = document.getElementById("reportModalTitle");
title.textContent = `Informe del ${report.reportDate || report.date} - ${
report.storeName || report.storeId || "Tienda desconocida"
}`;
content.innerHTML = `
Información básica
Fecha: ${
report.date || report.reportDate || ""
}
Tienda: ${
report.storeName || report.storeId
}
Usuario: ${
report.username || report.fullName || report.userId
}
Estado:
${
report.isVerified || report.verified
? "Verificado"
: "No verificado"
}
Ingresos
Ingresos del día: €${safeToFixed(report.income)}
Caja inicial: €${safeToFixed(
report.cajaInicial || report.initialCash
)}
Ingresos totales: €${safeToFixed(
report.totalIncome
)}
Salarios
${
Array.isArray(wages) && wages.length > 0
? `
${wages
.map(
(w) => `
${w.name}
€${safeToFixed(w.amount)}
`
)
.join("")}
Total salarios:
€${safeToFixed(report.totalWages)}
`
: '
No hay datos de salarios
'
}
Gastos
${
Array.isArray(expenses) && expenses.length > 0
? `
${expenses
.map(
(e) => `
${e.name}
€${safeToFixed(e.amount)}
`
)
.join("")}
Total gastos internos:
€${safeToFixed(totalExpensesInternal)}
`
: '
No hay datos de gastos
'
}
Resumen final
Ingresos totales: €${safeToFixed(
report.totalIncome
)}
Gastos totales: €${safeToFixed(
report.totalExpenses
)}
Envelope: €${safeToFixed(
report.envelope
)}
Caja final: €${safeToFixed(
report.cajaFinal || report.finalCash
)}
`;
buttons.innerHTML = "";
if (isAdmin) {
if (report.isVerified || report.verified) {
// Verified: only unverify + close
buttons.innerHTML = `
Informe verificado.
Para hacer cambios, retire la verificación.
`;
document
.getElementById("unverifyReportBtn")
.addEventListener("click", () => {
unverifyReport(report.id);
});
} else {
buttons.innerHTML = `
`;
document.getElementById("editReportBtn").addEventListener("click", () => {
editReport(report);
});
document
.getElementById("verifyReportBtn")
.addEventListener("click", () => {
verifyReport(report.id);
});
}
} else {
if (!report.isVerified && !report.verified) {
buttons.innerHTML = `
`;
document
.getElementById("editReportUserBtn")
.addEventListener("click", () => {
appState.editingReportId = report.id;
fillFormWithReport(report);
hideModal("reportViewModal");
});
} else {
buttons.innerHTML = `
El informe está verificado y no se puede modificar. Contacte al administrador para cambios.
`;
}
}
document
.getElementById("closeReportModalBtn")
.addEventListener("click", () => {
hideModal("reportViewModal");
});
showModal("reportViewModal");
}
// helpder of the modal of showReportModal - unverify report for ADMIN
async function unverifyReport(reportId) {
await updateReport(reportId, { isVerified: 0 });
showNotification("Verificación retirada. Ahora se puede editar.");
await loadReports();
hideModal("reportViewModal");
}
// Настройка фильтров отчетов
function setupReportsFilters() {
document
.getElementById("applyFilters")
.addEventListener("click", applyReportsFilters);
document
.getElementById("exportExcel")
.addEventListener("click", exportToExcel);
}
function applyReportsFilters() {
const storeFilter = document.getElementById("filterStore").value;
const dateFrom = document.getElementById("filterDateFrom").value;
const dateTo = document.getElementById("filterDateTo").value;
let filteredReports = appState.reportsList || [];
if (storeFilter) {
filteredReports = filteredReports.filter(
(r) => String(r.storeId) === String(storeFilter)
);
}
if (dateFrom) {
filteredReports = filteredReports.filter((r) => r.reportDate >= dateFrom);
}
if (dateTo) {
filteredReports = filteredReports.filter((r) => r.reportDate <= dateTo);
}
// Обновление таблицы с отфильтрованными данными
const tbody = document.getElementById("reportsTableBody");
tbody.innerHTML = "";
filteredReports.forEach((report) => {
const storeName = report.storeName || report.storeId;
const username = report.username || report.userId;
const profit =
(Number(report.totalIncome) || 0) - (Number(report.totalExpenses) || 0);
const row = document.createElement("tr");
row.className = "hover:bg-gray-50";
row.innerHTML = `
${
report.reportDate || report.date || ""
} |
${storeName} |
€${report.totalIncome.toFixed(
2
)} |
€${report.totalExpenses.toFixed(
2
)} |
€${profit.toFixed(2)} |
${username} |
${report.isVerified ? "Verificado" : "No verificado"}
|
|
`;
tbody.appendChild(row);
});
showNotification(`Se encontraron ${filteredReports.length} informes`);
}
function deleteReport(reportId) {
showConfirmModal("¿Estás seguro de que deseas eliminar este informe?", () => {
apiDeleteReport(reportId);
loadReports();
updateDashboard();
showNotification("¡Informe eliminado!");
});
}
//USER BEHAVIOUR INSIDE REPORTS
// Заполнение формы данными отчета (для пользователя)
function fillFormWithReport(report) {
appState.editingReportId = report.id;
document.getElementById("storeSelect").value = report.storeId;
document.getElementById("income").value = report.income || 0;
document.getElementById("cajaInicial").value =
report.cajaInicial || report.initialCash || 0;
document.getElementById("envelope").value = report.envelope;
document.getElementById("displayTotalIncome").value = report.totalIncome;
// document.getElementById("reportDate").value =
// report.reportDate || report.date || "";
// --- Deal with date change tooltip ---
// Set the date value
const dateInput = document.getElementById("reportDate");
const originalDate = report.reportDate || report.date || "";
dateInput.value = originalDate;
// Store the original date for later check
dateInput.dataset.originalDate = originalDate;
// Hide warning by default
document.getElementById("dateEditWarning").classList.add("hidden");
// Set up change listener
dateInput.oninput = function () {
if (dateInput.value && dateInput.value !== dateInput.dataset.originalDate) {
document.getElementById("dateEditWarning").classList.remove("hidden");
} else {
document.getElementById("dateEditWarning").classList.add("hidden");
}
};
// --- Parse wages/expenses if they are string ---
let wages = report.wages;
let expenses = report.expenses;
if (typeof wages === "string") {
try {
wages = JSON.parse(wages);
} catch {
wages = [];
}
}
if (typeof expenses === "string") {
try {
expenses = JSON.parse(expenses);
} catch {
expenses = [];
}
}
// --- Fill Wages ---
const wagesContainer = document.getElementById("wagesContainer");
wagesContainer.innerHTML = "";
if (Array.isArray(wages) && wages.length > 0) {
wages.forEach((wage) => {
const row = document.createElement("div");
row.className = "wage-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3";
row.innerHTML = `
`;
wagesContainer.appendChild(row);
});
} else {
addWageRow();
}
// --- Fill Expenses ---
const expensesContainer = document.getElementById("expensesContainer");
expensesContainer.innerHTML = "";
if (Array.isArray(expenses) && expenses.length > 0) {
expenses.forEach((expense) => {
const row = document.createElement("div");
row.className = "expense-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3";
row.innerHTML = `
`;
expensesContainer.appendChild(row);
});
} else {
addExpenseRow();
}
updateTotals();
}
// Экспорт в Excel
function exportToExcel() {
// Use backend data
const reports = appState.reportsList || [];
const stores = appState.storesList || [];
const users = appState.usersList || [];
if (!reports.length) {
showNotification("No hay informes para exportar", "info");
return;
}
const data = reports.map((report) => {
// Find store and user by id
const store =
stores.find((s) => Number(s.id) === Number(report.storeId)) || {};
const user =
users.find((u) => Number(u.id) === Number(report.userId)) || {};
const profit =
(Number(report.totalIncome) || 0) - (Number(report.totalExpenses) || 0);
return {
Дата: report.reportDate || report.date || "",
Tienda: store.name || report.storeName || "Desconocido",
Доход: `€${Number(report.totalIncome || 0).toFixed(2)}`,
Расходы: `€${Number(report.totalExpenses || 0).toFixed(2)}`,
Прибыль: `€${profit.toFixed(2)}`,
Usuario: user.username || report.username || "Desconocido",
Estado: report.isVerified
? "Verificado"
: report.verified
? "Verificado"
: "No verificado",
};
});
if (!data.length) {
showNotification("No hay informes para exportar", "info");
return;
}
// Create CSV
const headers = Object.keys(data[0]);
const csvContent = [
headers.join(","),
...data.map((row) => headers.map((header) => `"${row[header]}"`).join(",")),
].join("\n");
// Download file
const blob = new Blob([csvContent], {
type: "text/csv;charset=utf-8;",
});
const link = document.createElement("a");
const url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute(
"download",
`cash_reports_${new Date().toISOString().split("T")[0]}.csv`
);
link.style.visibility = "hidden";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showNotification("¡Informe exportado!");
}
//REPORTS FORM LOGIC
// Загрузка магазинов для пользователя
function loadUserStores() {
const select = document.getElementById("storeSelect");
if (!select) return;
select.innerHTML =
'';
if (!appState.currentUser) return;
// For admin: show all
if (appState.currentUser.role === "admin") {
(appState.storesList || []).forEach((store) => {
const option = document.createElement("option");
option.value = store.id;
option.textContent = store.name;
select.appendChild(option);
});
} else {
// For employee: only their own stores
(appState.currentUser.stores || []).forEach((storeObj) => {
// storeObj could be an object ({id, name, ...}) or just an ID; check backend API
let store = storeObj;
if (typeof storeObj === "number") {
// If backend sends just IDs, look up in appState.storesList
store = (appState.storesList || []).find((s) => s.id === storeObj);
}
if (store) {
const option = document.createElement("option");
option.value = store.id;
option.textContent = store.name;
select.appendChild(option);
}
});
}
}
// Настройка автоматических расчетов в форме
function setupFormCalculations() {
const incomeInput = document.getElementById("income");
const cajaInicialInput = document.getElementById("cajaInicial");
const envelopeInput = document.getElementById("envelope");
function updateCalculations() {
const income = parseFloat(incomeInput.value) || 0;
const cajaInicial = parseFloat(cajaInicialInput.value) || 0;
const envelope = parseFloat(envelopeInput.value) || 0;
const totalIncome = income + cajaInicial;
document.getElementById("totalIncome").value = totalIncome.toFixed(2);
document.getElementById("displayTotalIncome").value =
totalIncome.toFixed(2);
const totalWages = calculateTotalWages();
const totalExpensesInternal = calculateTotalExpenses();
const totalExpenses = totalWages + totalExpensesInternal;
document.getElementById("totalExpenses").value = totalExpenses.toFixed(2);
const cajaFinal = totalIncome - totalExpenses - envelope;
document.getElementById("cajaFinal").textContent = cajaFinal.toFixed(2);
}
incomeInput.addEventListener("input", updateCalculations);
cajaInicialInput.addEventListener("input", updateCalculations);
envelopeInput.addEventListener("input", updateCalculations);
// setupDynamicRows();
}
document.addEventListener("DOMContentLoaded", setupDynamicRows);
// Настройка динамических строк
let dynamicRowsInitialized = false;
function setupDynamicRows() {
// Only run the global listeners ONCE!
if (!dynamicRowsInitialized) {
document.addEventListener("input", (e) => {
if (
e.target.classList.contains("wage-amount") ||
e.target.classList.contains("expense-amount")
) {
updateTotals();
}
});
document.addEventListener("click", (e) => {
if (e.target.classList.contains("remove-wage")) {
e.target.closest(".wage-row").remove();
updateTotals();
} else if (e.target.classList.contains("remove-expense")) {
e.target.closest(".expense-row").remove();
updateTotals();
}
});
dynamicRowsInitialized = true;
}
// Always attach these because they might be removed and re-injected
const addWageBtn = document.getElementById("addWage");
if (addWageBtn && !addWageBtn.hasListener) {
addWageBtn.addEventListener("click", () => addWageRow());
addWageBtn.hasListener = true;
}
const addExpenseBtn = document.getElementById("addExpense");
if (addExpenseBtn && !addExpenseBtn.hasListener) {
addExpenseBtn.addEventListener("click", () => addExpenseRow());
addExpenseBtn.hasListener = true;
}
}
document.addEventListener("DOMContentLoaded", setupDynamicRows);
function addWageRow() {
const container = document.getElementById("wagesContainer");
const row = document.createElement("div");
row.className = "wage-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3";
row.innerHTML = `
`;
container.appendChild(row);
}
function addExpenseRow() {
const container = document.getElementById("expensesContainer");
const row = document.createElement("div");
row.className = "expense-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3";
row.innerHTML = `
`;
container.appendChild(row);
}
function calculateTotalWages() {
const amounts = document.querySelectorAll(".wage-amount");
let total = 0;
amounts.forEach((amount) => {
total += parseFloat(amount.value) || 0;
});
return total;
}
function calculateTotalExpenses() {
const amounts = document.querySelectorAll(".expense-amount");
let total = 0;
amounts.forEach((amount) => {
total += parseFloat(amount.value) || 0;
});
return total;
}
function updateTotals() {
const totalWages = calculateTotalWages();
const totalExpensesInternal = calculateTotalExpenses();
document.getElementById("totalWages").textContent = totalWages.toFixed(2);
document.getElementById("totalExpensesInternal").textContent =
totalExpensesInternal.toFixed(2);
const totalExpenses = totalWages + totalExpensesInternal;
document.getElementById("totalExpenses").value = totalExpenses.toFixed(2);
// Обновить Caja Final
const income = parseFloat(document.getElementById("income").value) || 0;
const cajaInicial =
parseFloat(document.getElementById("cajaInicial").value) || 0;
const envelope = parseFloat(document.getElementById("envelope").value) || 0;
const totalIncome = income + cajaInicial;
const cajaFinal = totalIncome - totalExpenses - envelope;
document.getElementById("totalIncome").value = totalIncome.toFixed(2);
document.getElementById("cajaFinal").textContent = cajaFinal.toFixed(2);
}
function collectWages() {
const wages = [];
document.querySelectorAll(".wage-row").forEach((row) => {
const name = row.querySelector(".wage-name").value;
const amount = parseFloat(row.querySelector(".wage-amount").value) || 0;
if (name && amount > 0) wages.push({ name, amount });
});
return wages;
}
function collectExpenses() {
const expenses = [];
document.querySelectorAll(".expense-row").forEach((row) => {
const name = row.querySelector(".expense-name").value;
const amount = parseFloat(row.querySelector(".expense-amount").value) || 0;
if (name && amount > 0) expenses.push({ name, amount });
});
return expenses;
}
function safeToFixed(value, digits = 2) {
return (Number(value) || 0).toFixed(digits);
}
function upsertTodaysReport(report) {
const today = new Date().toISOString().split("T")[0];
const storeId = String(report.storeId);
appState.todaysReports = (appState.todaysReports || []).filter((r) => {
return (
!(r.id === report.id) &&
!(
String(r.storeId) === storeId &&
(r.reportDate || r.date) === today &&
(r.userId == report.userId || r.username === report.username)
)
);
});
// Add again only if the edited report is for today
if ((report.reportDate || report.date) === today) {
appState.todaysReports.push(report);
}
}
// save report as user-worker
document.getElementById("reportForm").addEventListener("submit", async (e) => {
e.preventDefault();
const incomeValue = parseFloat(document.getElementById("income").value);
const cajaInicialValue = parseFloat(
document.getElementById("cajaInicial").value
);
const envelopeValue = parseFloat(document.getElementById("envelope").value);
const totalIncomeValue = incomeValue + cajaInicialValue;
const totalWagesValue = calculateTotalWages();
const totalExpensesInternalValue = calculateTotalExpenses();
const totalExpensesValue = totalExpensesInternalValue + totalWagesValue;
const finalCashValue = totalIncomeValue - totalExpensesValue - envelopeValue;
const formData = {
storeId: parseInt(document.getElementById("storeSelect").value),
// reportDate: new Date().toISOString().split("T")[0],
reportDate:
document.getElementById("reportDate").value ||
new Date().toISOString().split("T")[0],
income: isNaN(incomeValue) ? 0 : incomeValue,
initialCash: isNaN(cajaInicialValue) ? 0 : cajaInicialValue,
totalIncome: isNaN(totalIncomeValue) ? 0 : totalIncomeValue,
wages: JSON.stringify(collectWages()),
expenses: JSON.stringify(collectExpenses()),
totalWages: isNaN(totalWagesValue) ? 0 : totalWagesValue,
totalExpensesInternal: isNaN(totalExpensesInternalValue)
? 0
: totalExpensesInternalValue,
totalExpenses: isNaN(totalExpensesValue) ? 0 : totalExpensesValue,
envelope: isNaN(envelopeValue) ? 0 : envelopeValue,
finalCash: isNaN(finalCashValue) ? 0 : finalCashValue,
};
let result;
if (appState.editingReportId) {
// EDIT mode: update existing report
// Update!
result = await updateReport(appState.editingReportId, formData);
} else {
// CREATE mode: new report
result = await createReport(formData);
}
// if result is missing --> error
if (!result) {
showNotification("No hay respuesta del servidor. Inténtalo de nuevo.", "error");
return;
}
// if (result.success && (!appState.editingReportId || result.id)) {
if (
(appState.editingReportId && result.success) || // edit: only need success
(!appState.editingReportId && result.success && result.id) // create: need id
) {
resetReportForm();
const wasEdit = !!appState.editingReportId;
const reportId = appState.editingReportId || result.id;
appState.editingReportId = null;
window.scrollTo({ top: 0, behavior: "smooth" });
const newReport = {
...formData,
id: reportId,
userId: appState.currentUser.id,
username: appState.currentUser.username,
};
upsertTodaysReport(newReport);
showNotification(
wasEdit ? "¡Informe editado con éxito!" : "¡Informe creado con éxito!"
);
await loadReports();
} else {
resetReportForm();
window.scrollTo({ top: 0, behavior: "smooth" });
showNotification(result.error || "Error al crear el informe", "error");
}
});
function resetReportForm() {
document.getElementById("reportForm").reset();
document.getElementById("wagesContainer").innerHTML = "";
addWageRow();
document.getElementById("expensesContainer").innerHTML = "";
addExpenseRow();
document.getElementById("totalWages").textContent = "0.00";
document.getElementById("totalExpensesInternal").textContent = "0.00";
document.getElementById("cajaFinal").textContent = "0.00";
// today
const today = new Date().toISOString().split("T")[0];
// left date blank
document.getElementById("reportDate").value = "";
document.getElementById("reportDate").setAttribute("max", today);
// OR
// Set date field to today and restrict to today or earlier
// document.getElementById("reportDate").value = today;
// document.getElementById("reportDate").setAttribute("max", today);
//clean tooltip about changing date on the report
document.getElementById("dateEditWarning").classList.add("hidden");
}
// Отчет за сегодня для пользователя
document.getElementById("todayReportBtn").addEventListener("click", () => {
const storeId = document.getElementById("storeSelect").value;
if (!storeId) {
showNotification("¡Por favor seleccione una tienda!", "error");
return;
}
const todaysReport = (appState.todaysReports || []).find(
(r) => String(r.storeId) === storeId
);
console.log(todaysReport);
console.log("Matching today's report:", todaysReport);
if (todaysReport) {
showReportModal(todaysReport, false);
} else {
showNotification("El informe de hoy aún no se ha creado", "error");
document.getElementById("reportForm").reset();
document.getElementById("storeSelect").value = storeId;
document.getElementById("wagesContainer").innerHTML = "";
addWageRow();
document.getElementById("expensesContainer").innerHTML = "";
addExpenseRow();
}
});
//USERS
async function loadUsers() {
// console.log("Loading users...");
const tbody = document.getElementById("usersTableBody");
tbody.innerHTML = "";
const result = await getAllUsers();
// console.log("getAllUsers result:", result);
if (!result.success) {
showNotification(result.error, "error");
return;
}
const users = result.users;
appState.usersList = users;
users.forEach((user) => {
const userStores =
user.role === "admin"
? "Все магазины"
: user.stores
.map((storeId) => {
const store = (appState.storesList || []).find(
(s) => s.id === storeId
);
return store ? store.name : "Sin acceso";
})
.join(", ") || "Sin acceso";
const row = document.createElement("tr");
row.className = "hover:bg-gray-50";
row.innerHTML = `
${user.id} |
${user.username} |
${user.role === "admin" ? "Administrador" : "Empleado"}
|
${userStores} |
|
`;
tbody.appendChild(row);
});
if (users.length === 0) {
showNotification("No hay usuarios para mostrar", "info");
}
}
// Редактирование пользователя
function editUser(userId) {
appState.editingUserId = userId;
const user = appState.usersList.find((u) => u.id === userId);
showUserEditModal(user);
}
// Показ модального окна редактирования пользователя
function showUserEditModal(user) {
const modal = document.getElementById("userEditModal");
const title = document.getElementById("userModalTitle");
const form = document.getElementById("userEditForm");
// If appState.editingUserId, it's an edit; else, it's add
title.textContent = appState.editingUserId
? "Editar usuario"
: "Agregar usuario";
document.getElementById("userLogin").value = (user && user.username) || "";
const loginInput = document.getElementById("userLogin");
loginInput.value = (user && user.username) || "";
// if (appState.editingUserId) {
// loginInput.disabled = true;
// } else {
// loginInput.disabled = false;
// }
document.getElementById("userPassword").value =
user && user.plaintextPassword ? user.plaintextPassword : "";
const pwInput = document.getElementById("userPassword");
pwInput.value = user && user.plaintextPassword ? user.plaintextPassword : "";
pwInput.type = "password"; // Always set hidden on open!
const eye = document.getElementById("eyeIcon");
if (eye) eye.textContent = "👁️";
document.getElementById("userRole").value = (user && user.role) || "employee";
// Загрузка чекбоксов магазинов
const storesContainer = document.getElementById("userStoresAccess");
storesContainer.innerHTML = "";
(appState.storesList || []).forEach((store) => {
const isChecked = user && user.stores && user.stores.includes(store.id);
const checkbox = document.createElement("label");
checkbox.className = "flex items-center";
checkbox.innerHTML = `
${store.name}
`;
storesContainer.appendChild(checkbox);
});
showModal("userEditModal");
}
//eye toggle for the password
document.getElementById("togglePasswordBtn").onclick = function () {
const pw = document.getElementById("userPassword");
const eye = document.getElementById("eyeIcon");
if (pw.type === "password") {
pw.type = "text";
eye.textContent = "🙈";
} else {
pw.type = "password";
eye.textContent = "👁️";
}
};
//save user for create and update
async function saveUser() {
const login = document.getElementById("userLogin").value.trim();
const password = document.getElementById("userPassword").value;
const role = document.getElementById("userRole").value;
const selectedStores = Array.from(
document.querySelectorAll(
'#userStoresAccess input[type="checkbox"]:checked'
)
).map((cb) => parseInt(cb.value));
if (!login) {
showNotification("¡Complete el nombre de usuario!", "error");
return;
}
// Always build userData
const userData = {
username: login,
role: role,
storeIds: selectedStores,
};
if (password) userData.password = password;
// Determine CREATE or EDIT
let result;
if (!appState.editingUserId) {
if (!password) {
showNotification("¡Especifique una contraseña para el nuevo usuario!", "error");
return;
}
// CREATE
result = await createUser(userData);
if (result.success) {
showNotification("¡Usuario agregado!");
}
} else {
// EDIT
result = await updateUser(appState.editingUserId, userData);
if (result.success) {
showNotification("¡Usuario actualizado!");
}
appState.editingUserId = null;
}
// After save: UI update or error
if (result && result.success) {
hideModal("userEditModal");
loadUsers();
updateDashboard();
loadReports();
} else if (result) {
showNotification(result.error || "Error de operación", "error");
}
}
//UI trigger: delete user with modal
function deleteUser(userId) {
showConfirmModal("¿Está seguro de que desea eliminar este usuario?", () =>
apiDeleteUser(userId)
);
}
// Добавление пользователя
document.addEventListener("DOMContentLoaded", function () {
const addUserBtn = document.getElementById("addUserBtn");
if (addUserBtn) {
addUserBtn.addEventListener("click", () => {
appState.editingUserId = null;
showUserEditModal();
});
}
});
// Сохранение пользователя
document.addEventListener("DOMContentLoaded", function () {
const saveUserBtn = document.getElementById("saveUserBtn");
const cancelUserBtn = document.getElementById("cancelUserBtn");
if (saveUserBtn) {
saveUserBtn.addEventListener("click", saveUser);
}
if (cancelUserBtn) {
cancelUserBtn.addEventListener("click", () => {
hideModal("userEditModal");
});
}
});
//SHOPS
// Загрузка магазинов
async function loadStores() {
console.log("loadStores CALLED");
const tbody = document.getElementById("storesTableBody");
tbody.innerHTML = "";
const result = await getStores();
if (!result.success) {
showNotification(result.error || "Error al cargar tiendas", "error");
appState.storesList = result.stores;
return;
}
const stores = result.stores;
appState.storesList = stores;
console.log("Fetched stores from API:", result.stores);
console.log("appState.storesList:", appState.storesList);
stores.forEach((store) => {
console.log("Rendering store:", store.name);
const row = document.createElement("tr");
row.className = "hover:bg-gray-50";
row.innerHTML = `
${store.id} |
${store.name} |
${
store.reportsCount || 0
} |
|
`;
tbody.appendChild(row);
});
if (stores.length === 0) {
showNotification("No hay tiendas para mostrar", "info");
}
}
// Редактирование магазина
function editStore(storeId) {
appState.editingStoreId = storeId;
showStoreEditModal();
}
// Показ модального окна редактирования магазина
function showStoreEditModal() {
const modal = document.getElementById("storeEditModal");
const title = document.getElementById("storeModalTitle");
const form = document.getElementById("storeEditForm");
const store = appState.storesList.find(
(s) => s.id === appState.editingStoreId
);
if (!store) {
showNotification("¡Tienda no encontrada!", "error");
return;
}
title.textContent = "Editar tienda";
document.getElementById("storeName").value = store.name || "";
showModal("storeEditModal");
}
async function saveStore() {
const name = document.getElementById("storeName").value.trim();
if (!name) {
showNotification("¡Complete el nombre de la tienda!", "error");
return;
}
let result;
if (appState.editingStoreId == null) {
// Add
result = await createStore({ name });
if (result.success) {
showNotification("¡Tienda agregada!");
hideModal("storeEditModal");
await loadStores();
await loadReports();
if (typeof loadUsers === "function") loadUsers();
if (typeof loadUserStores === "function") loadUserStores();
if (typeof updateDashboard === "function") updateDashboard();
}
} else {
// Edit
result = await updateStore(appState.editingStoreId, { name });
if (result.success) {
showNotification("¡Tienda actualizada!");
hideModal("storeEditModal");
await loadStores();
await loadReports();
if (typeof loadUsers === "function") loadUsers();
if (typeof loadUserStores === "function") loadUserStores();
if (typeof updateDashboard === "function") updateDashboard();
}
}
if (result && !result.success) {
if (
result.status === 409 ||
(result.error && result.error.includes("already exists"))
) {
showNotification("¡Ya existe una tienda con este nombre!", "error");
} else {
showNotification(result.error || "Error al guardar tienda", "error");
}
return;
}
hideModal("storeEditModal");
appState.editingStoreId = null;
await loadStores();
if (typeof updateDashboard === "function") updateDashboard();
}
// Удаление магазина
function deleteStore(storeId) {
const store = appState.storesList.find((s) => s.id === storeId);
let message = `¿Está seguro de que desea eliminar esta tienda "${store.name}"?`;
if (store.reportsCount && store.reportsCount > 0) {
message += `\n\n¡Atención! Esta tienda tiene ${store.reportsCount} informes asociados. También serán eliminados.`;
}
showConfirmModal(message, () => handleDeleteStore(storeId));
}
async function handleDeleteStore(storeId) {
const result = await deleteStoreApi(storeId);
if (result.success) {
showNotification("Shop and related reports deleted!");
await loadStores();
if (typeof loadReports === "function") loadReports();
if (typeof updateDashboard === "function") updateDashboard();
if (typeof loadUsers === "function") loadUsers();
} else {
showNotification(result.error || "Failed to delete shop", "error");
}
}
// Добавление магазина
document.getElementById("addStoreBtn").onclick = () => {
appState.editingStoreId = null;
document.getElementById("storeModalTitle").textContent =
"Agregar tienda";
document.getElementById("storeName").value = "";
showModal("storeEditModal");
};
// Сохранение магазина
document.addEventListener("DOMContentLoaded", function () {
const saveStoreBtn = document.getElementById("saveStoreBtn");
const cancelStoreBtn = document.getElementById("cancelStoreBtn");
if (saveStoreBtn) saveStoreBtn.onclick = saveStore;
if (cancelStoreBtn)
cancelStoreBtn.onclick = () => {
hideModal("storeEditModal");
appState.editingStoreId = null;
document.getElementById("storeEditForm").reset();
};
});
//TODO
// Загрузка TODO
function loadTodos() {
const container = document.getElementById("todoList");
container.innerHTML = "";
database.todos.forEach((todo) => {
const todoItem = document.createElement("div");
todoItem.className = `p-4 border rounded-lg ${
todo.completed
? "bg-green-50 border-green-200"
: "bg-white border-gray-200"
}`;
const priorityColors = {
low: "bg-blue-100 text-blue-800",
medium: "bg-yellow-100 text-yellow-800",
high: "bg-red-100 text-red-800",
};
const priorityText = {
low: "Bajo",
medium: "Medio",
high: "Alto",
};
todoItem.innerHTML = `
`;
container.appendChild(todoItem);
});
}
// Переключение статуса TODO
function toggleTodo(todoId) {
const todoIndex = database.todos.findIndex((t) => t.id === todoId);
database.todos[todoIndex].completed = !database.todos[todoIndex].completed;
loadTodos();
showNotification("¡Estado de tarea actualizado!");
}
// Редактирование TODO
function editTodo(todoId) {
appState.editingTodoId = todoId;
showTodoEditModal();
}
// Показ модального окна редактирования TODO
function showTodoEditModal() {
const modal = document.getElementById("todoEditModal");
const title = document.getElementById("todoModalTitle");
const form = document.getElementById("todoEditForm");
if (appState.editingTodoId) {
const todo = database.todos.find((t) => t.id === appState.editingTodoId);
title.textContent = "Editar tarea";
document.getElementById("todoTitle").value = todo.title;
document.getElementById("todoDescription").value = todo.description;
document.getElementById("todoPriority").value = todo.priority;
} else {
title.textContent = "Agregar tarea";
form.reset();
}
showModal("todoEditModal");
}
function saveTodo() {
const title = document.getElementById("todoTitle").value;
const description = document.getElementById("todoDescription").value;
const priority = document.getElementById("todoPriority").value;
if (!title) {
showNotification("¡Complete el título!", "error");
return;
}
if (appState.editingTodoId) {
// Редактирование
const todoIndex = database.todos.findIndex(
(t) => t.id === appState.editingTodoId
);
database.todos[todoIndex].title = title;
database.todos[todoIndex].description = description;
database.todos[todoIndex].priority = priority;
showNotification("¡Tarea actualizada!");
} else {
// Добавление
const newId = Math.max(...database.todos.map((t) => t.id)) + 1;
database.todos.push({
id: newId,
title: title,
description: description,
priority: priority,
completed: false,
createdAt: new Date().toISOString().split("T")[0],
});
showNotification("¡Tarea agregada!");
}
hideModal("todoEditModal");
loadTodos();
}
// Удаление TODO
function deleteTodo(todoId) {
if (confirm("¿Está seguro de que desea eliminar esta tarea?")) {
database.todos = database.todos.filter((t) => t.id !== todoId);
loadTodos();
showNotification("¡Tarea eliminada!");
}
}
// Добавление TODO
document.addEventListener("DOMContentLoaded", function () {
const addTodoBtn = document.getElementById("addTodoBtn");
if (addTodoBtn) {
addTodoBtn.addEventListener("click", () => {
appState.editingTodoId = null;
showTodoEditModal();
});
}
});
// Сохранение TODO
document.addEventListener("DOMContentLoaded", function () {
const saveTodoBtn = document.getElementById("saveTodoBtn");
const cancelTodoBtn = document.getElementById("cancelTodoBtn");
if (saveTodoBtn) {
saveTodoBtn.addEventListener("click", saveTodo);
}
if (cancelTodoBtn) {
cancelTodoBtn.addEventListener("click", () => {
hideModal("todoEditModal");
});
}
});
// ####################MOCK###########
// База данных (симуляция)
let database = {
users: [
{
id: 1,
username: "admin",
password: "admin123",
role: "admin",
stores: [],
},
{
id: 2,
username: "employee",
password: "password123",
role: "employee",
stores: [1, 2],
},
{
id: 3,
username: "cashier1",
password: "password123",
role: "employee",
stores: [1, 2],
},
{
id: 4,
username: "cashier2",
password: "password123",
role: "employee",
stores: [3],
},
],
stores: [
{ id: 1, name: "Магазин 1" },
{ id: 2, name: "Магазин 2" },
{ id: 3, name: "Магазин 3" },
{ id: 4, name: "Магазин 4" },
],
reports: [],
todos: [
{
id: 1,
title: "Исправить модальные окна",
description: "Исправить прокрутку и видимость кнопок в модальных окнах",
completed: true,
priority: "high",
createdAt: "2024-01-15",
},
{
id: 2,
title: "Добавить экспорт в PDF",
description: "Реализовать функцию экспорта отчетов в PDF формат",
completed: false,
priority: "medium",
createdAt: "2024-01-16",
},
{
id: 3,
title: "Улучшить графики",
description: "Добавить больше интерактивности в графики Dashboard",
completed: false,
priority: "low",
createdAt: "2024-01-17",
},
],
};
// Генерация тестовых данных для отчетов
function generateTestData() {
const reports = [];
const today = new Date();
for (let i = 0; i < 30; i++) {
const date = new Date(today);
date.setDate(date.getDate() - i);
const storeId = Math.floor(Math.random() * 4) + 1;
const userId = Math.floor(Math.random() * 3) + 2; // employee пользователи
const income = Math.floor(Math.random() * 2000) + 500;
const cajaInicial = Math.floor(Math.random() * 300) + 100;
const totalIncome = income + cajaInicial;
const wages = Math.floor(Math.random() * 400) + 100;
const expenses = Math.floor(Math.random() * 200) + 50;
const totalExpenses = wages + expenses;
const envelope = Math.floor(Math.random() * 200) + 100;
const cajaFinal = totalIncome - totalExpenses - envelope;
reports.push({
id: i + 1,
date: date.toISOString().split("T")[0],
storeId: storeId,
userId: userId,
income: income,
cajaInicial: cajaInicial,
totalIncome: totalIncome,
wages: [
{
name: "Сотрудник " + (Math.floor(Math.random() * 3) + 1),
amount: wages,
},
],
expenses: [
{
name: "Расходы " + (Math.floor(Math.random() * 3) + 1),
amount: expenses,
},
],
totalWages: wages,
totalExpensesInternal: expenses,
totalExpenses: totalExpenses,
envelope: envelope,
cajaFinal: cajaFinal,
verified: Math.random() > 0.3,
createdAt: date.toISOString(),
});
}
database.reports = reports;
}
// Инициализация тестовых данных
generateTestData();
// ####################################