feat: added store name for the report and filtering by shop update

This commit is contained in:
Angie
2025-07-26 01:17:32 +02:00
parent 0c46f20d19
commit efa4c0693f
2 changed files with 358 additions and 225 deletions

View File

@@ -337,3 +337,144 @@ function deleteUser(userId) {
apiDeleteUser(userId)
);
}
//Reports
//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();
if (response.ok) {
return { success: true, reports: data.reports };
} else {
return {
success: false,
error: data.error || data.message || "Ошибка получения отчетов",
};
}
} catch (err) {
return { success: false, error: "Нет соединения с сервером" };
}
}
async function loadReports() {
const tbody = document.getElementById("reportsTableBody");
const filterStore = document.getElementById("filterStore");
const result = await getReports();
console.log("getReports() result:", result);
window.reportsList = result.success ? result.reports : [];
if (!result.success) {
showNotification(result.error || "Ошибка загрузки отчетов", "error");
return;
}
const reports = result.reports;
// Build a map storeId => storeName from all reports
const storeMap = {};
reports.forEach((r) => {
if (r.storeId && r.storeName) {
storeMap[r.storeId] = r.storeName;
}
});
// Get unique [storeId, storeName] pairs sorted alphabetically
const storesWithReports = Object.entries(storeMap)
.map(([id, name]) => ({ id, name }))
.sort((a, b) => a.name.localeCompare(b.name));
filterStore.innerHTML = `
<option value="">Все магазины</option>
${storesWithReports
.map((store) => `<option value="${store.id}">${store.name}</option>`)
.join("")}
`;
tbody.innerHTML = "";
reports.forEach((report) => {
const store = database.stores.find((s) => s.id === report.storeId);
// Fallback for username if user might be missing from local array:
const user = database.users.find((u) => u.id === 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 = `
<td class="px-6 py-4 text-sm text-gray-900">${
report.reportDate || report.date || ""
}</td>
<td class="px-6 py-4 text-sm text-gray-900">${
report.storeName || report.storeId
}</td>
<td class="px-6 py-4 text-sm text-gray-900">€${Number(
report.totalIncome
).toFixed(2)}</td>
<td class="px-6 py-4 text-sm text-gray-900">€${Number(
report.totalExpenses
).toFixed(2)}</td>
<td class="px-6 py-4 text-sm ${
profit >= 0 ? "text-green-600" : "text-red-600"
}">€${profit.toFixed(2)}</td>
<td class="px-6 py-4 text-sm text-gray-900">${
user ? user.username : report.userId
}</td>
<td class="px-6 py-4">
<span class="px-2 py-1 text-xs rounded-full ${
report.isVerified
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}">
${report.isVerified ? "Проверен" : "Не проверен"}
</span>
</td>
<td class="px-6 py-4 text-sm">
<button class="text-blue-600 hover:text-blue-900 mr-2" onclick="viewReport(${
report.id
})">
<i class="fas fa-eye"></i> Просмотр
</button>
<button class="text-red-600 hover:text-red-900" onclick="deleteReport(${
report.id
})">
<i class="fas fa-trash"></i> Удалить
</button>
</td>
`;
tbody.appendChild(row);
});
setupReportsFilters();
}
// Use global array for backend reports
function viewReport(reportId) {
if (!window.reportsList) {
showNotification("Reports not loaded yet!", "error");
return;
}
const report = window.reportsList.find((r) => r.id === reportId);
if (report) {
showReportModal(report, true);
} else {
showNotification("Report not found!", "error");
}
}
//create report
// createReport(data)
//edit report
// updateReport(id, data)
//accept report (admin only)
// verifyReport(id)
//delete report
// deleteReport(id)

View File

@@ -499,6 +499,10 @@ document.getElementById("todayReportBtn").addEventListener("click", () => {
}
});
function safeToFixed(value, digits = 2) {
return (Number(value) || 0).toFixed(digits);
}
// Показ модального окна отчета с исправленной прокруткой
function showReportModal(report, isAdmin = false) {
const modal = document.getElementById("reportViewModal");
@@ -509,157 +513,146 @@ function showReportModal(report, isAdmin = false) {
const store = database.stores.find((s) => s.id === report.storeId);
const user = database.users.find((u) => u.id === report.userId);
title.textContent = `Отчет от ${report.date} - ${
title.textContent = `Отчет от ${report.date || report.reportDate} - ${
store ? store.name : "Неизвестный магазин"
}`;
// Формирование содержимого с красивыми стилями
content.innerHTML = `
<div class="space-y-6">
<!-- Основная информация -->
<div class="report-section">
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-info-circle mr-2"></i>Основная информация</h4>
<div class="grid grid-cols-2 gap-4 text-sm">
<div><strong>Дата:</strong> ${report.date}</div>
<div><strong>Магазин:</strong> ${
store ? store.name : "Неизвестно"
}</div>
<div><strong>Пользователь:</strong> ${
user ? user.username : "Неизвестно"
}</div>
<div><strong>Статус:</strong>
<span class="px-2 py-1 rounded text-xs ${
report.verified
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}">
${
report.verified
? "Проверен"
: "Не проверен"
}
</span>
</div>
</div>
</div>
<!-- Доходы -->
<div class="report-section income">
<h4 class="font-bold text-green-700 mb-3"><i class="fas fa-arrow-up mr-2"></i>Доходы (Ingresos)</h4>
<div class="grid grid-cols-3 gap-4 text-sm">
<div><strong>Income:</strong> €${report.income.toFixed(
2
)}</div>
<div><strong>Caja inicial:</strong> €${report.cajaInicial.toFixed(
2
)}</div>
<div><strong>Total income:</strong> €${report.totalIncome.toFixed(
2
)}</div>
</div>
</div>
<!-- Зарплаты -->
<div class="report-section wages">
<h4 class="font-bold text-yellow-700 mb-3"><i class="fas fa-users mr-2"></i>Зарплаты (Wages)</h4>
${
report.wages && report.wages.length > 0
? `
<div class="space-y-2">
${report.wages
.map(
(w) => `
<div class="flex justify-between text-sm">
<span>${w.name}</span>
<span>€${w.amount.toFixed(2)}</span>
</div>
`
)
.join("")}
<div class="border-t pt-2 font-bold">
<div class="flex justify-between">
<span>Total wages:</span>
<span>€${report.totalWages.toFixed(
2
)}</span>
</div>
</div>
</div>
`
: '<p class="text-gray-500 text-sm">Нет данных о зарплатах</p>'
}
</div>
<!-- Расходы -->
<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>
${
report.expenses && report.expenses.length > 0
? `
<div class="space-y-2">
${report.expenses
.map(
(e) => `
<div class="flex justify-between text-sm">
<span>${e.name}</span>
<span>€${e.amount.toFixed(2)}</span>
</div>
`
)
.join("")}
<div class="border-t pt-2 font-bold">
<div class="flex justify-between">
<span>Total expenses internal:</span>
<span>€${report.totalExpensesInternal.toFixed(
2
)}</span>
</div>
</div>
</div>
`
: '<p class="text-gray-500 text-sm">Нет данных о расходах</p>'
}
</div>
<!-- Итоговые расчеты -->
<div class="report-section">
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-calculator mr-2"></i>Итоговые расчеты</h4>
<div class="space-y-2">
<div class="flex justify-between"><strong>Total income:</strong> <span>€${report.totalIncome.toFixed(
2
)}</span></div>
<div class="flex justify-between"><strong>Total expenses:</strong> <span>€${report.totalExpenses.toFixed(
2
)}</span></div>
<div class="flex justify-between"><strong>Envelope:</strong> <span>€${report.envelope.toFixed(
2
)}</span></div>
<div class="flex justify-between border-t pt-2 text-lg font-bold text-blue-700">
<strong>Caja final:</strong> <span>€${report.cajaFinal.toFixed(
2
)}</span>
</div>
</div>
</div>
<div class="space-y-6">
<!-- Основная информация -->
<div class="report-section">
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-info-circle mr-2"></i>Основная информация</h4>
<div class="grid grid-cols-2 gap-4 text-sm">
<div><strong>Дата:</strong> ${
report.date || report.reportDate || ""
}</div>
<div><strong>Магазин:</strong> ${
report.storeName || report.storeId
}</div>
<div><strong>Пользователь:</strong> ${
user ? user.username : report.userId
}</div>
<div><strong>Статус:</strong>
<span class="px-2 py-1 rounded text-xs ${
report.isVerified || report.verified
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}">
${
report.isVerified || report.verified
? "Проверен"
: "Не проверен"
}
</span>
</div>
</div>
</div>
<!-- Доходы -->
<div class="report-section income">
<h4 class="font-bold text-green-700 mb-3"><i class="fas fa-arrow-up mr-2"></i>Доходы (Ingresos)</h4>
<div class="grid grid-cols-3 gap-4 text-sm">
<div><strong>Income:</strong> €${safeToFixed(report.income)}</div>
<div><strong>Caja inicial:</strong> €${safeToFixed(
report.cajaInicial || report.initialCash
)}</div>
<div><strong>Total income:</strong> €${safeToFixed(
report.totalIncome
)}</div>
</div>
</div>
<!-- Зарплаты -->
<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
? `
<div class="space-y-2">
${report.wages
.map(
(w) => `
<div class="flex justify-between text-sm">
<span>${w.name}</span>
<span>€${safeToFixed(w.amount)}</span>
</div>
`
)
.join("")}
<div class="border-t pt-2 font-bold">
<div class="flex justify-between">
<span>Total wages:</span>
<span>€${safeToFixed(report.totalWages)}</span>
</div>
`;
</div>
</div>
`
: '<p class="text-gray-500 text-sm">Нет данных о зарплатах</p>'
}
</div>
<!-- Расходы -->
<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
? `
<div class="space-y-2">
${report.expenses
.map(
(e) => `
<div class="flex justify-between text-sm">
<span>${e.name}</span>
<span>€${safeToFixed(e.amount)}</span>
</div>
`
)
.join("")}
<div class="border-t pt-2 font-bold">
<div class="flex justify-between">
<span>Total expenses internal:</span>
<span>€${safeToFixed(report.totalExpensesInternal)}</span>
</div>
</div>
</div>
`
: '<p class="text-gray-500 text-sm">Нет данных о расходах</p>'
}
</div>
<!-- Итоговые расчеты -->
<div class="report-section">
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-calculator mr-2"></i>Итоговые расчеты</h4>
<div class="space-y-2">
<div class="flex justify-between"><strong>Total income:</strong> <span>€${safeToFixed(
report.totalIncome
)}</span></div>
<div class="flex justify-between"><strong>Total expenses:</strong> <span>€${safeToFixed(
report.totalExpenses
)}</span></div>
<div class="flex justify-between"><strong>Envelope:</strong> <span>€${safeToFixed(
report.envelope
)}</span></div>
<div class="flex justify-between border-t pt-2 text-lg font-bold text-blue-700">
<strong>Caja final:</strong> <span>€${safeToFixed(
report.cajaFinal || report.finalCash
)}</span>
</div>
</div>
</div>
</div>
`;
// Настройка кнопок в зависимости от роли и статуса
buttons.innerHTML = "";
if (isAdmin) {
// Кнопки для администратора
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>
<button id="verifyReportBtn" class="btn-success text-white px-4 py-2 rounded-lg">
<i class="fas fa-check 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="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>
<button id="verifyReportBtn" class="btn-success text-white px-4 py-2 rounded-lg">
<i class="fas fa-check 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("editReportBtn").addEventListener("click", () => {
editReport(report);
@@ -669,16 +662,15 @@ function showReportModal(report, isAdmin = false) {
verifyReport(report.id);
});
} else {
// Кнопки для пользователя
if (!report.verified) {
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")
@@ -688,10 +680,10 @@ function showReportModal(report, isAdmin = false) {
});
} 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>
`;
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
<i class="fas fa-times mr-2"></i>Закрыть
</button>
`;
}
}
@@ -1161,83 +1153,83 @@ function createCharts() {
}
// Загрузка отчетов в админке
function loadReports() {
const tbody = document.getElementById("reportsTableBody");
const filterStore = document.getElementById("filterStore");
// function loadReports() {
// const tbody = document.getElementById("reportsTableBody");
// const filterStore = document.getElementById("filterStore");
// Заполнение фильтра магазинов
filterStore.innerHTML = '<option value="">Все магазины</option>';
database.stores.forEach((store) => {
const option = document.createElement("option");
option.value = store.id;
option.textContent = store.name;
filterStore.appendChild(option);
});
// // Заполнение фильтра магазинов
// filterStore.innerHTML = '<option value="">Все магазины</option>';
// database.stores.forEach((store) => {
// const option = document.createElement("option");
// option.value = store.id;
// option.textContent = store.name;
// filterStore.appendChild(option);
// });
// Отображение отчетов
tbody.innerHTML = "";
database.reports.forEach((report) => {
const store = database.stores.find((s) => s.id === report.storeId);
const user = database.users.find((u) => u.id === report.userId);
const profit = report.totalIncome - report.totalExpenses;
// // Отображение отчетов
// tbody.innerHTML = "";
// database.reports.forEach((report) => {
// const store = database.stores.find((s) => s.id === report.storeId);
// const user = database.users.find((u) => u.id === report.userId);
// const profit = report.totalIncome - report.totalExpenses;
const row = document.createElement("tr");
row.className = "hover:bg-gray-50";
row.innerHTML = `
<td class="px-6 py-4 text-sm text-gray-900">${
report.date
}</td>
<td class="px-6 py-4 text-sm text-gray-900">${
store ? store.name : "Неизвестно"
}</td>
<td class="px-6 py-4 text-sm text-gray-900">€${report.totalIncome.toFixed(
2
)}</td>
<td class="px-6 py-4 text-sm text-gray-900">€${report.totalExpenses.toFixed(
2
)}</td>
<td class="px-6 py-4 text-sm ${
profit >= 0 ? "text-green-600" : "text-red-600"
}">€${profit.toFixed(2)}</td>
<td class="px-6 py-4 text-sm text-gray-900">${
user ? user.username : "Неизвестно"
}</td>
<td class="px-6 py-4">
<span class="px-2 py-1 text-xs rounded-full ${
report.verified
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}">
${report.verified ? "Проверен" : "Не проверен"}
</span>
</td>
<td class="px-6 py-4 text-sm">
<button class="text-blue-600 hover:text-blue-900 mr-2" onclick="viewReport(${
report.id
})">
<i class="fas fa-eye"></i> Просмотр
</button>
<button class="text-red-600 hover:text-red-900" onclick="deleteReport(${
report.id
})">
<i class="fas fa-trash"></i> Удалить
</button>
</td>
`;
tbody.appendChild(row);
});
// const row = document.createElement("tr");
// row.className = "hover:bg-gray-50";
// row.innerHTML = `
// <td class="px-6 py-4 text-sm text-gray-900">${
// report.date
// }</td>
// <td class="px-6 py-4 text-sm text-gray-900">${
// store ? store.name : "Неизвестно"
// }</td>
// <td class="px-6 py-4 text-sm text-gray-900">€${report.totalIncome.toFixed(
// 2
// )}</td>
// <td class="px-6 py-4 text-sm text-gray-900">€${report.totalExpenses.toFixed(
// 2
// )}</td>
// <td class="px-6 py-4 text-sm ${
// profit >= 0 ? "text-green-600" : "text-red-600"
// }">€${profit.toFixed(2)}</td>
// <td class="px-6 py-4 text-sm text-gray-900">${
// user ? user.username : "Неизвестно"
// }</td>
// <td class="px-6 py-4">
// <span class="px-2 py-1 text-xs rounded-full ${
// report.verified
// ? "bg-green-100 text-green-800"
// : "bg-yellow-100 text-yellow-800"
// }">
// ${report.verified ? "Проверен" : "Не проверен"}
// </span>
// </td>
// <td class="px-6 py-4 text-sm">
// <button class="text-blue-600 hover:text-blue-900 mr-2" onclick="viewReport(${
// report.id
// })">
// <i class="fas fa-eye"></i> Просмотр
// </button>
// <button class="text-red-600 hover:text-red-900" onclick="deleteReport(${
// report.id
// })">
// <i class="fas fa-trash"></i> Удалить
// </button>
// </td>
// `;
// tbody.appendChild(row);
// });
// Настройка фильтров и экспорта
setupReportsFilters();
}
// // Настройка фильтров и экспорта
// setupReportsFilters();
// }
// Просмотр отчета
function viewReport(reportId) {
const report = database.reports.find((r) => r.id === reportId);
if (report) {
showReportModal(report, true); // true = админ режим
}
}
// function viewReport(reportId) {
// const report = database.reports.find((r) => r.id === reportId);
// if (report) {
// showReportModal(report, true); // true = админ режим
// }
// }
// Удаление отчета
function deleteReport(reportId) {