feat: updated user UI behaviour with report
This commit is contained in:
@@ -25,6 +25,7 @@ window.appState = createReactiveState(
|
||||
usersList: [],
|
||||
reportsList: [],
|
||||
storesList: [],
|
||||
todaysReports: [],
|
||||
editingReportId: null,
|
||||
editingUserId: null,
|
||||
editingStoreId: null,
|
||||
@@ -34,6 +35,7 @@ window.appState = createReactiveState(
|
||||
storesChartInstance: null,
|
||||
trendsChartInstance: null,
|
||||
adminTabsInitialized: false,
|
||||
initialEditReportFormData: null,
|
||||
},
|
||||
function (prop, value) {
|
||||
// React to changes in critical state
|
||||
@@ -88,6 +90,9 @@ function logout() {
|
||||
document.getElementById("adminInterface").classList.add("hidden");
|
||||
document.getElementById("loginForm").reset();
|
||||
document.getElementById("loginError").classList.add("hidden");
|
||||
document.getElementById("reportForm").reset();
|
||||
if (document.getElementById("storeSelect"))
|
||||
document.getElementById("storeSelect").selectedIndex = 0;
|
||||
|
||||
showNotification("Вы вышли из системы", "info");
|
||||
}
|
||||
@@ -317,6 +322,24 @@ async function getReports() {
|
||||
}
|
||||
}
|
||||
|
||||
//GET single report by ID
|
||||
async function getReportById(reportId) {
|
||||
const token = localStorage.getItem("token");
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/reports/${reportId}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok && data.report) {
|
||||
return { success: true, report: data.report };
|
||||
} else {
|
||||
return { success: false, error: data.error || "Ошибка получения отчета" };
|
||||
}
|
||||
} catch (err) {
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
}
|
||||
}
|
||||
|
||||
//edit report
|
||||
async function updateReport(reportId, data) {
|
||||
const token = localStorage.getItem("token");
|
||||
@@ -405,7 +428,6 @@ async function apiDeleteReport(reportId) {
|
||||
|
||||
//REPORTS WORKER
|
||||
//create report
|
||||
// api.js
|
||||
async function createReport(data) {
|
||||
const token = localStorage.getItem("token");
|
||||
try {
|
||||
@@ -417,19 +439,53 @@ async function createReport(data) {
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
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 {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
result.error ||
|
||||
(result.errors && result.errors[0]?.msg) ||
|
||||
"Ошибка создания отчета",
|
||||
};
|
||||
// Extract error
|
||||
let errorMsg =
|
||||
result.error ||
|
||||
(Array.isArray(result.errors) && result.errors[0]?.msg) ||
|
||||
response.statusText ||
|
||||
"Ошибка создания отчета";
|
||||
|
||||
if (response.status === 409) {
|
||||
errorMsg =
|
||||
"Отчет за этот магазин и дату уже был отправлен этим пользователем.";
|
||||
}
|
||||
|
||||
// Debug: Log error case
|
||||
console.warn(
|
||||
"createReport failed:",
|
||||
errorMsg,
|
||||
"Status:",
|
||||
response.status,
|
||||
result
|
||||
);
|
||||
|
||||
return { success: false, error: errorMsg };
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Network/server error in createReport:", err);
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,38 +6,70 @@ function destroyChart(instance) {
|
||||
}
|
||||
|
||||
// Система уведомлений
|
||||
// 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 = `
|
||||
// <div class="bg-red-500 text-white px-6 py-4 rounded-lg shadow-lg">
|
||||
// <div class="flex items-center">
|
||||
// <i class="fas fa-exclamation-triangle mr-2"></i>
|
||||
// <span>${message}</span>
|
||||
// </div>
|
||||
// </div>
|
||||
// `;
|
||||
// } else {
|
||||
// notification.innerHTML = `
|
||||
// <div class="bg-green-500 text-white px-6 py-4 rounded-lg shadow-lg">
|
||||
// <div class="flex items-center">
|
||||
// <i class="fas fa-check-circle mr-2"></i>
|
||||
// <span>${message}</span>
|
||||
// </div>
|
||||
// </div>
|
||||
// `;
|
||||
// }
|
||||
|
||||
// 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");
|
||||
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 = `
|
||||
<div class="bg-red-500 text-white px-6 py-4 rounded-lg shadow-lg">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-exclamation-triangle mr-2"></i>
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
notification.innerHTML = `
|
||||
<div class="bg-green-500 text-white px-6 py-4 rounded-lg shadow-lg">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-check-circle mr-2"></i>
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (!notification) return;
|
||||
|
||||
notification.innerHTML = `
|
||||
<div class="bg-${
|
||||
type === "error" ? "red" : "green"
|
||||
}-500 text-white px-6 py-4 rounded-lg shadow-lg">
|
||||
<div class="flex items-center">
|
||||
<i class="fas ${
|
||||
type === "error" ? "fa-exclamation-triangle" : "fa-check-circle"
|
||||
} mr-2"></i>
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
notification.className = "fixed top-4 right-4 z-50 animate-fade-in";
|
||||
notification.classList.remove("hidden");
|
||||
notification.style.display = "block";
|
||||
|
||||
setTimeout(() => {
|
||||
// previous timeout exists -> clear it!
|
||||
if (notificationTimeout) {
|
||||
clearTimeout(notificationTimeout);
|
||||
}
|
||||
notificationTimeout = setTimeout(() => {
|
||||
notification.classList.add("hidden");
|
||||
notification.style.display = "none";
|
||||
notificationTimeout = null;
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
@@ -142,10 +174,19 @@ document.getElementById("loginForm").addEventListener("submit", async (e) => {
|
||||
const result = await loginUser(username, password);
|
||||
|
||||
if (result.success) {
|
||||
// Save user/token in JS (or localStorage)
|
||||
appState.currentUser = result.user;
|
||||
// Save token for later API calls
|
||||
localStorage.setItem("token", result.token);
|
||||
|
||||
// Save user info in global state
|
||||
appState.currentUser = result.user;
|
||||
|
||||
// Save today's reports if present (for workers), or empty for admin ===
|
||||
if (result.todaysReports) {
|
||||
appState.todaysReports = result.todaysReports;
|
||||
} else {
|
||||
appState.todaysReports = [];
|
||||
}
|
||||
// Hide login, show correct UI
|
||||
document.getElementById("loginScreen").classList.add("hidden");
|
||||
|
||||
if (result.user.role === "admin") {
|
||||
@@ -157,8 +198,7 @@ document.getElementById("loginForm").addEventListener("submit", async (e) => {
|
||||
showNotification("Успешная авторизация!");
|
||||
} else {
|
||||
const errorDiv = document.getElementById("loginError");
|
||||
// errorDiv.textContent = result.error;
|
||||
errorDiv.textContent = "Неверный логин или пароль";
|
||||
errorDiv.textContent = result.error || "Неверный логин или пароль";
|
||||
errorDiv.classList.remove("hidden");
|
||||
}
|
||||
});
|
||||
@@ -220,6 +260,15 @@ function logout() {
|
||||
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");
|
||||
|
||||
@@ -716,6 +765,24 @@ async function saveEditedReport(reportId) {
|
||||
|
||||
// Показ модального окна отчета с исправленной прокруткой
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
const modal = document.getElementById("reportViewModal");
|
||||
const content = document.getElementById("reportViewContent");
|
||||
const buttons = document.getElementById("reportModalButtons");
|
||||
@@ -772,10 +839,10 @@ function showReportModal(report, isAdmin = false) {
|
||||
<div class="report-section wages">
|
||||
<h4 class="font-bold text-yellow-700 mb-3"><i class="fas fa-users mr-2"></i>Зарплаты (Wages)</h4>
|
||||
${
|
||||
Array.isArray(report.wages) && report.wages.length > 0
|
||||
Array.isArray(wages) && wages.length > 0
|
||||
? `
|
||||
<div class="space-y-2">
|
||||
${report.wages
|
||||
${wages
|
||||
.map(
|
||||
(w) => `
|
||||
<div class="flex justify-between text-sm">
|
||||
@@ -800,10 +867,10 @@ function showReportModal(report, isAdmin = false) {
|
||||
<div class="report-section expenses">
|
||||
<h4 class="font-bold text-red-700 mb-3"><i class="fas fa-arrow-down mr-2"></i>Расходы (Expenses)</h4>
|
||||
${
|
||||
Array.isArray(report.expenses) && report.expenses.length > 0
|
||||
Array.isArray(expenses) && expenses.length > 0
|
||||
? `
|
||||
<div class="space-y-2">
|
||||
${report.expenses
|
||||
${expenses
|
||||
.map(
|
||||
(e) => `
|
||||
<div class="flex justify-between text-sm">
|
||||
@@ -848,9 +915,25 @@ function showReportModal(report, isAdmin = false) {
|
||||
`;
|
||||
|
||||
buttons.innerHTML = "";
|
||||
|
||||
if (isAdmin) {
|
||||
buttons.innerHTML = `
|
||||
if (report.isVerified || report.verified) {
|
||||
// Verified: only unverify + close
|
||||
buttons.innerHTML = `
|
||||
<div class="mb-2 text-yellow-700 font-medium text-sm">Отчет подтвержден. Для внесения изменений снимите подтверждение.</div>
|
||||
<button id="unverifyReportBtn" class="bg-yellow-500 text-white px-4 py-2 rounded-lg hover:bg-yellow-600">
|
||||
<i class="fas fa-undo mr-2"></i>Снять подтверждение
|
||||
</button>
|
||||
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-times mr-2"></i>Закрыть
|
||||
</button>
|
||||
`;
|
||||
document
|
||||
.getElementById("unverifyReportBtn")
|
||||
.addEventListener("click", () => {
|
||||
unverifyReport(report.id);
|
||||
});
|
||||
} else {
|
||||
buttons.innerHTML = `
|
||||
<button id="editReportBtn" class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
|
||||
<i class="fas fa-edit mr-2"></i>Редактировать
|
||||
</button>
|
||||
@@ -861,37 +944,39 @@ function showReportModal(report, isAdmin = false) {
|
||||
<i class="fas fa-times mr-2"></i>Закрыть
|
||||
</button>
|
||||
`;
|
||||
|
||||
document.getElementById("editReportBtn").addEventListener("click", () => {
|
||||
editReport(report);
|
||||
});
|
||||
|
||||
document.getElementById("verifyReportBtn").addEventListener("click", () => {
|
||||
verifyReport(report.id);
|
||||
});
|
||||
document.getElementById("editReportBtn").addEventListener("click", () => {
|
||||
editReport(report);
|
||||
});
|
||||
document
|
||||
.getElementById("verifyReportBtn")
|
||||
.addEventListener("click", () => {
|
||||
verifyReport(report.id);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!report.isVerified && !report.verified) {
|
||||
buttons.innerHTML = `
|
||||
<button id="editReportUserBtn" class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
|
||||
<i class="fas fa-edit mr-2"></i>Редактировать
|
||||
</button>
|
||||
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-times mr-2"></i>Закрыть
|
||||
</button>
|
||||
`;
|
||||
|
||||
<button id="editReportUserBtn" class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
|
||||
<i class="fas fa-edit mr-2"></i>Редактировать
|
||||
</button>
|
||||
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-times mr-2"></i>Закрыть
|
||||
</button>
|
||||
`;
|
||||
document
|
||||
.getElementById("editReportUserBtn")
|
||||
.addEventListener("click", () => {
|
||||
appState.editingReportId = report.id;
|
||||
fillFormWithReport(report);
|
||||
hideModal("reportViewModal");
|
||||
});
|
||||
} else {
|
||||
buttons.innerHTML = `
|
||||
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-times mr-2"></i>Закрыть
|
||||
</button>
|
||||
`;
|
||||
<div class="mb-2 text-yellow-700 font-medium text-sm">Отчет подтвержден и не может быть изменен. Обратитесь к администратору для изменений.</div>
|
||||
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-times mr-2"></i>Закрыть
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -904,6 +989,14 @@ function showReportModal(report, isAdmin = false) {
|
||||
showModal("reportViewModal");
|
||||
}
|
||||
|
||||
// helpder of the modal of showReportModal - unverify report for ADMIN
|
||||
async function unverifyReport(reportId) {
|
||||
await updateReport(reportId, { isVerified: 0 });
|
||||
showNotification("Подтверждение снято. Теперь можно редактировать.");
|
||||
await loadReports();
|
||||
hideModal("reportViewModal");
|
||||
}
|
||||
|
||||
// Настройка фильтров отчетов
|
||||
function setupReportsFilters() {
|
||||
document
|
||||
@@ -999,49 +1092,69 @@ function deleteReport(reportId) {
|
||||
});
|
||||
}
|
||||
|
||||
//USER BEHAVIOUR INSIDE REPORTS
|
||||
// Заполнение формы данными отчета (для пользователя)
|
||||
function fillFormWithReport(report) {
|
||||
appState.editingReportId = report.id;
|
||||
|
||||
document.getElementById("storeSelect").value = report.storeId;
|
||||
document.getElementById("income").value = report.income;
|
||||
document.getElementById("cajaInicial").value = report.cajaInicial;
|
||||
document.getElementById("income").value = report.income || 0;
|
||||
document.getElementById("cajaInicial").value =
|
||||
report.cajaInicial || report.initialCash || 0;
|
||||
document.getElementById("envelope").value = report.envelope;
|
||||
|
||||
// Заполнение зарплат
|
||||
// --- 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 (report.wages && report.wages.length > 0) {
|
||||
report.wages.forEach((wage) => {
|
||||
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 = `
|
||||
<input type="text" placeholder="Имя сотрудника" value="${wage.name}" class="wage-name form-input px-3 py-2 rounded-lg">
|
||||
<input type="number" step="0.01" placeholder="Сумма €" value="${wage.amount}" class="wage-amount form-input px-3 py-2 rounded-lg">
|
||||
<button type="button" class="remove-wage bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
`;
|
||||
<input type="text" placeholder="Имя сотрудника" value="${wage.name}" class="wage-name form-input px-3 py-2 rounded-lg">
|
||||
<input type="number" step="0.01" placeholder="Сумма €" value="${wage.amount}" class="wage-amount form-input px-3 py-2 rounded-lg">
|
||||
<button type="button" class="remove-wage bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
`;
|
||||
wagesContainer.appendChild(row);
|
||||
});
|
||||
} else {
|
||||
addWageRow();
|
||||
}
|
||||
|
||||
// Заполнение расходов
|
||||
// --- Fill Expenses ---
|
||||
const expensesContainer = document.getElementById("expensesContainer");
|
||||
expensesContainer.innerHTML = "";
|
||||
if (report.expenses && report.expenses.length > 0) {
|
||||
report.expenses.forEach((expense) => {
|
||||
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 = `
|
||||
<input type="text" placeholder="Название расхода" value="${expense.name}" class="expense-name form-input px-3 py-2 rounded-lg">
|
||||
<input type="number" step="0.01" placeholder="Сумма €" value="${expense.amount}" class="expense-amount form-input px-3 py-2 rounded-lg">
|
||||
<button type="button" class="remove-expense bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
`;
|
||||
<input type="text" placeholder="Название расхода" value="${expense.name}" class="expense-name form-input px-3 py-2 rounded-lg">
|
||||
<input type="number" step="0.01" placeholder="Сумма €" value="${expense.amount}" class="expense-amount form-input px-3 py-2 rounded-lg">
|
||||
<button type="button" class="remove-expense bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
`;
|
||||
expensesContainer.appendChild(row);
|
||||
});
|
||||
} else {
|
||||
@@ -1186,41 +1299,86 @@ function setupFormCalculations() {
|
||||
cajaInicialInput.addEventListener("input", updateCalculations);
|
||||
envelopeInput.addEventListener("input", updateCalculations);
|
||||
|
||||
setupDynamicRows();
|
||||
// setupDynamicRows();
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", setupDynamicRows);
|
||||
|
||||
// Настройка динамических строк
|
||||
// function setupDynamicRows() {
|
||||
// document.getElementById("addWage").addEventListener("click", () => {
|
||||
// addWageRow();
|
||||
// });
|
||||
|
||||
// document.getElementById("addExpense").addEventListener("click", () => {
|
||||
// addExpenseRow();
|
||||
// });
|
||||
|
||||
// // Обновление при изменении значений
|
||||
// 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();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
let dynamicRowsInitialized = false;
|
||||
|
||||
function setupDynamicRows() {
|
||||
document.getElementById("addWage").addEventListener("click", () => {
|
||||
addWageRow();
|
||||
});
|
||||
// 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.getElementById("addExpense").addEventListener("click", () => {
|
||||
addExpenseRow();
|
||||
});
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
// Обновление при изменении значений
|
||||
document.addEventListener("input", (e) => {
|
||||
if (
|
||||
e.target.classList.contains("wage-amount") ||
|
||||
e.target.classList.contains("expense-amount")
|
||||
) {
|
||||
updateTotals();
|
||||
}
|
||||
});
|
||||
dynamicRowsInitialized = true;
|
||||
}
|
||||
|
||||
// Удаление строк
|
||||
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();
|
||||
}
|
||||
});
|
||||
// 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");
|
||||
@@ -1286,6 +1444,7 @@ function updateTotals() {
|
||||
const totalIncome = income + cajaInicial;
|
||||
const cajaFinal = totalIncome - totalExpenses - envelope;
|
||||
|
||||
document.getElementById("totalIncome").value = totalIncome.toFixed(2); // <-- Add this line!
|
||||
document.getElementById("cajaFinal").textContent = cajaFinal.toFixed(2);
|
||||
}
|
||||
|
||||
@@ -1312,47 +1471,104 @@ function safeToFixed(value, digits = 2) {
|
||||
return (Number(value) || 0).toFixed(digits);
|
||||
}
|
||||
|
||||
// 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 totalExpensesValue = calculateTotalExpenses() + totalWagesValue;
|
||||
const finalCashValue = totalIncomeValue - totalExpensesValue - envelopeValue;
|
||||
|
||||
const formData = {
|
||||
storeId: parseInt(document.getElementById("storeSelect").value),
|
||||
reportDate: new Date().toISOString().split("T")[0], // Or get from input if user chooses date
|
||||
income: parseFloat(document.getElementById("income").value),
|
||||
initialCash: parseFloat(document.getElementById("cajaInicial").value),
|
||||
totalIncome: parseFloat(document.getElementById("totalIncome").value),
|
||||
reportDate: 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: calculateTotalWages(),
|
||||
totalExpenses: calculateTotalExpenses() + calculateTotalWages(),
|
||||
envelope: parseFloat(document.getElementById("envelope").value),
|
||||
finalCash:
|
||||
parseFloat(document.getElementById("totalIncome").value) -
|
||||
(calculateTotalWages() + calculateTotalExpenses()) -
|
||||
parseFloat(document.getElementById("envelope").value),
|
||||
totalWages: isNaN(totalWagesValue) ? 0 : totalWagesValue,
|
||||
totalExpenses: isNaN(totalExpensesValue) ? 0 : totalExpensesValue,
|
||||
envelope: isNaN(envelopeValue) ? 0 : envelopeValue,
|
||||
finalCash: isNaN(finalCashValue) ? 0 : finalCashValue,
|
||||
};
|
||||
// console.log("Sending report:", formData);
|
||||
const result = await createReport(formData);
|
||||
if (result.success) {
|
||||
showNotification("Отчет успешно создан!");
|
||||
await loadReports();
|
||||
document.getElementById("reportForm").reset();
|
||||
|
||||
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("Нет ответа от сервера. Попробуйте еще раз.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
const wasEdit = !!appState.editingReportId;
|
||||
appState.editingReportId = null;
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
document.getElementById("reportForm").reset();
|
||||
document.getElementById("wagesContainer").innerHTML = "";
|
||||
addWageRow();
|
||||
|
||||
document.getElementById("expensesContainer").innerHTML = "";
|
||||
addExpenseRow();
|
||||
|
||||
showNotification(
|
||||
wasEdit ? "Отчет успешно отредактирован!" : "Отчет успешно создан!"
|
||||
);
|
||||
await loadReports();
|
||||
refreshTodaysReports();
|
||||
console.log("Updated todaysReports:", appState.todaysReports);
|
||||
} else {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
document.getElementById("reportForm").reset();
|
||||
showNotification(result.error || "Ошибка создания отчета", "error");
|
||||
}
|
||||
});
|
||||
|
||||
//helper for USER UI
|
||||
function refreshTodaysReports() {
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
appState.todaysReports = (appState.reportsList || []).filter(
|
||||
(r) => (r.reportDate || r.date) === today
|
||||
);
|
||||
}
|
||||
|
||||
// Отчет за сегодня для пользователя
|
||||
document.getElementById("todayReportBtn").addEventListener("click", () => {
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
const todayReport = database.reports.find(
|
||||
(r) => r.date === today && r.userId === appState.currentUser.id
|
||||
const storeId = document.getElementById("storeSelect").value;
|
||||
if (!storeId) {
|
||||
showNotification("Пожалуйста, выберите магазин!", "error");
|
||||
return;
|
||||
}
|
||||
const report = (appState.todaysReports || []).find(
|
||||
(r) => String(r.storeId) === String(storeId)
|
||||
);
|
||||
|
||||
if (todayReport) {
|
||||
showReportModal(todayReport, false); // false = не админ режим
|
||||
if (report) {
|
||||
showReportModal(report, false);
|
||||
} else {
|
||||
showNotification("Отчет за сегодня не найден", "error");
|
||||
showNotification("Сегодняшний отчет еще не создан", "error");
|
||||
document.getElementById("reportForm").reset();
|
||||
document.getElementById("storeSelect").value = storeId;
|
||||
|
||||
document.getElementById("wagesContainer").innerHTML = "";
|
||||
addWageRow();
|
||||
|
||||
document.getElementById("expensesContainer").innerHTML = "";
|
||||
addExpenseRow();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1545,6 +1761,7 @@ async function saveUser() {
|
||||
hideModal("userEditModal");
|
||||
loadUsers();
|
||||
updateDashboard();
|
||||
loadReports();
|
||||
} else if (result) {
|
||||
showNotification(result.error || "Ошибка операции", "error");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user