717 lines
22 KiB
JavaScript
717 lines
22 KiB
JavaScript
//API
|
||
const API_BASE_URL = "http://localhost:3001/api";
|
||
|
||
//Login
|
||
async function loginUser(username, password) {
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/auth/login`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({ username, password }),
|
||
});
|
||
const data = await response.json();
|
||
if (response.ok) {
|
||
return { success: true, ...data };
|
||
} else {
|
||
return {
|
||
success: false,
|
||
error:
|
||
data.error ||
|
||
(data.errors && data.errors[0]?.msg) ||
|
||
"Ошибка авторизации",
|
||
};
|
||
}
|
||
} catch (err) {
|
||
return { success: false, error: "Нет соединения с сервером" };
|
||
}
|
||
}
|
||
|
||
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 user/token in JS (or localStorage)
|
||
currentUser = result.user;
|
||
localStorage.setItem("token", result.token);
|
||
|
||
document.getElementById("loginScreen").classList.add("hidden");
|
||
|
||
if (result.user.role === "admin") {
|
||
showAdminInterface();
|
||
} else {
|
||
showUserInterface();
|
||
}
|
||
|
||
showNotification("Успешная авторизация!");
|
||
} else {
|
||
const errorDiv = document.getElementById("loginError");
|
||
errorDiv.textContent = result.error;
|
||
errorDiv.classList.remove("hidden");
|
||
}
|
||
});
|
||
|
||
document.addEventListener("DOMContentLoaded", async () => {
|
||
const token = localStorage.getItem("token");
|
||
if (token) {
|
||
// Try to get user info from backend
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/auth/me`, {
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
},
|
||
});
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
currentUser = data.user;
|
||
document.getElementById("loginScreen").classList.add("hidden");
|
||
if (currentUser.role === "admin") {
|
||
showAdminInterface();
|
||
} else {
|
||
showUserInterface();
|
||
}
|
||
} else {
|
||
// Token invalid/expired
|
||
localStorage.removeItem("token");
|
||
document.getElementById("loginScreen").classList.remove("hidden");
|
||
}
|
||
} catch (err) {
|
||
showNotification("Нет соединения с сервером", "error");
|
||
}
|
||
} else {
|
||
// No token, show login screen
|
||
document.getElementById("loginScreen").classList.remove("hidden");
|
||
}
|
||
});
|
||
|
||
function logout() {
|
||
currentUser = null;
|
||
localStorage.removeItem("token");
|
||
|
||
// Show login screen, hide other interfaces
|
||
document.getElementById("loginScreen").classList.remove("hidden");
|
||
document.getElementById("userInterface").classList.add("hidden");
|
||
document.getElementById("adminInterface").classList.add("hidden");
|
||
document.getElementById("loginForm").reset();
|
||
document.getElementById("loginError").classList.add("hidden");
|
||
|
||
showNotification("Вы вышли из системы", "info");
|
||
}
|
||
|
||
document.getElementById("logoutBtn").addEventListener("click", logout);
|
||
document.getElementById("adminLogoutBtn").addEventListener("click", logout);
|
||
|
||
//Users
|
||
//GET all users (admin only)
|
||
|
||
async function getAllUsers() {
|
||
const token = localStorage.getItem("token");
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/users`, {
|
||
method: "GET",
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
"Content-Type": "application/json",
|
||
},
|
||
});
|
||
const data = await response.json();
|
||
if (response.ok) {
|
||
return { success: true, users: data.users };
|
||
} else {
|
||
return {
|
||
success: false,
|
||
error: data.error || data.message || "Ошибка получения пользователей",
|
||
};
|
||
}
|
||
} catch (err) {
|
||
return { success: false, error: "Нет соединения с сервером" };
|
||
}
|
||
}
|
||
|
||
let usersList = [];
|
||
|
||
async function loadUsers() {
|
||
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;
|
||
usersList = users;
|
||
|
||
users.forEach((user) => {
|
||
const userStores =
|
||
user.stores
|
||
.map((storeId) => {
|
||
const store = database.stores.find((s) => s.id === storeId);
|
||
return store ? store.name : "Нет доступа";
|
||
})
|
||
.join(", ") || "Нет доступа";
|
||
|
||
const row = document.createElement("tr");
|
||
row.className = "hover:bg-gray-50";
|
||
row.innerHTML = `
|
||
<td class="px-6 py-4 text-sm text-gray-900">${user.id}</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">${user.username}</td>
|
||
<td class="px-6 py-4 text-sm">
|
||
<span class="px-2 py-1 text-xs rounded-full ${
|
||
user.role === "admin"
|
||
? "bg-purple-100 text-purple-800"
|
||
: "bg-blue-100 text-blue-800"
|
||
}">
|
||
${user.role === "admin" ? "Администратор" : "Сотрудник"}
|
||
</span>
|
||
</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">${userStores}</td>
|
||
<td class="px-6 py-4 text-sm">
|
||
<button class="text-blue-600 hover:text-blue-900 mr-2" onclick="editUser(${
|
||
user.id
|
||
})">
|
||
<i class="fas fa-edit"></i> Редактировать
|
||
</button>
|
||
<button class="text-red-600 hover:text-red-900" onclick="deleteUser(${
|
||
user.id
|
||
})">
|
||
<i class="fas fa-trash"></i> Удалить
|
||
</button>
|
||
</td>
|
||
`;
|
||
tbody.appendChild(row);
|
||
});
|
||
|
||
if (users.length === 0) {
|
||
showNotification("Нет пользователей для отображения", "info");
|
||
}
|
||
}
|
||
|
||
//add user
|
||
async function createUser(userData) {
|
||
const token = localStorage.getItem("token");
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/users`, {
|
||
method: "POST",
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify(userData),
|
||
});
|
||
const data = await response.json();
|
||
if (response.ok) {
|
||
return { success: true, ...data };
|
||
} else {
|
||
return {
|
||
success: false,
|
||
error:
|
||
data.error ||
|
||
(data.errors && data.errors[0]?.msg) ||
|
||
"Ошибка создания пользователя",
|
||
};
|
||
}
|
||
} catch {
|
||
return { success: false, error: "Нет соединения с сервером" };
|
||
}
|
||
}
|
||
|
||
//edit user
|
||
async function updateUser(userId, userData) {
|
||
const token = localStorage.getItem("token");
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/users/${userId}`, {
|
||
method: "PUT",
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify(userData),
|
||
});
|
||
const data = await response.json();
|
||
if (response.ok) {
|
||
return { success: true };
|
||
} else {
|
||
return {
|
||
success: false,
|
||
error:
|
||
data.error ||
|
||
(data.errors && data.errors[0]?.msg) ||
|
||
"Ошибка обновления пользователя",
|
||
};
|
||
}
|
||
} catch {
|
||
return { success: false, error: "Нет соединения с сервером" };
|
||
}
|
||
}
|
||
|
||
// 1. Create modal on-demand if missing
|
||
function ensureConfirmModal() {
|
||
if (document.getElementById("confirmModal")) return;
|
||
|
||
const modal = document.createElement("div");
|
||
modal.id = "confirmModal";
|
||
modal.className =
|
||
"hidden fixed inset-0 z-50 bg-black bg-opacity-30 flex items-center justify-center";
|
||
modal.innerHTML = `
|
||
<div class="bg-white rounded-lg shadow-lg p-8 max-w-sm w-full relative">
|
||
<div id="confirmModalText" class="text-lg text-gray-700 mb-6"></div>
|
||
<div class="flex justify-end space-x-4">
|
||
<button id="confirmModalOk" class="px-5 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700">Удалить</button>
|
||
<button id="confirmModalCancel" class="px-5 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400">Отмена</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
document.body.appendChild(modal);
|
||
}
|
||
|
||
// 2. Show confirm modal
|
||
function showConfirmModal(message, onConfirm) {
|
||
ensureConfirmModal();
|
||
|
||
const modal = document.getElementById("confirmModal");
|
||
const text = document.getElementById("confirmModalText");
|
||
const okBtn = document.getElementById("confirmModalOk");
|
||
const cancelBtn = document.getElementById("confirmModalCancel");
|
||
|
||
text.textContent = message;
|
||
modal.classList.remove("hidden");
|
||
|
||
function cleanup() {
|
||
modal.classList.add("hidden");
|
||
okBtn.removeEventListener("click", okHandler);
|
||
cancelBtn.removeEventListener("click", cancelHandler);
|
||
}
|
||
|
||
function okHandler() {
|
||
cleanup();
|
||
onConfirm();
|
||
}
|
||
|
||
function cancelHandler() {
|
||
cleanup();
|
||
}
|
||
|
||
okBtn.addEventListener("click", okHandler);
|
||
cancelBtn.addEventListener("click", cancelHandler);
|
||
}
|
||
|
||
// 3. API call to delete user
|
||
async function apiDeleteUser(userId) {
|
||
const token = localStorage.getItem("token");
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/users/${userId}`, {
|
||
method: "DELETE",
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
"Content-Type": "application/json",
|
||
},
|
||
});
|
||
const data = await response.json();
|
||
if (response.ok) {
|
||
showNotification("Пользователь удален!");
|
||
loadUsers();
|
||
updateDashboard();
|
||
} else {
|
||
showNotification(data.error || "Ошибка удаления пользователя", "error");
|
||
}
|
||
} catch {
|
||
showNotification("Нет соединения с сервером", "error");
|
||
}
|
||
}
|
||
|
||
// 4. UI trigger: delete user with modal
|
||
function deleteUser(userId) {
|
||
showConfirmModal("Вы уверены, что хотите удалить этого пользователя?", () =>
|
||
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();
|
||
console.log(data);
|
||
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 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">${
|
||
report.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");
|
||
}
|
||
}
|
||
|
||
//edit report
|
||
async function updateReport(reportId, data) {
|
||
const token = localStorage.getItem("token");
|
||
try {
|
||
const res = await fetch(`${API_BASE_URL}/reports/${reportId}`, {
|
||
method: "PUT",
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify(data),
|
||
});
|
||
const result = await res.json();
|
||
if (res.ok && result.updated) {
|
||
showNotification("Отчет обновлен!", "success");
|
||
hideModal("reportViewModal");
|
||
await loadReports();
|
||
|
||
console.log(data);
|
||
|
||
if (typeof updateDashboard === "function") updateDashboard();
|
||
return { success: true };
|
||
} else {
|
||
showNotification(result.error || "Ошибка обновления отчета", "error");
|
||
return { success: false, error: result.error };
|
||
}
|
||
} catch (err) {
|
||
showNotification("Ошибка сети. Попробуйте еще раз.", "error");
|
||
return { success: false, error: err.message };
|
||
}
|
||
}
|
||
|
||
function editReport(report) {
|
||
console.log("editReport() called with:", report);
|
||
const content = document.getElementById("reportViewContent");
|
||
const buttons = document.getElementById("reportModalButtons");
|
||
|
||
const shopName = (report.storeName || report.storeId || "").replace(
|
||
/"/g,
|
||
"""
|
||
);
|
||
|
||
content.innerHTML = `
|
||
<form id="editReportForm" 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">
|
||
<div>
|
||
<label class="block text-sm font-bold text-gray-700 mb-1">Магазин</label>
|
||
<input
|
||
type="text"
|
||
value="${shopName}"
|
||
class="form-input w-full px-3 py-2 rounded-lg bg-gray-100 text-gray-900 cursor-not-allowed"
|
||
readonly
|
||
disabled
|
||
>
|
||
<input type="hidden" id="editStoreSelect" value="${report.storeId}">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-bold text-gray-700 mb-1">Дата</label>
|
||
<input
|
||
type="date"
|
||
id="editDate"
|
||
value="${report.reportDate || report.date || ""}"
|
||
class="form-input w-full px-3 py-2 rounded-lg bg-gray-100 text-gray-900 cursor-not-allowed"
|
||
readonly
|
||
disabled
|
||
>
|
||
</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>Доходы</h4>
|
||
<div class="grid grid-cols-3 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-bold text-gray-700 mb-1">Income</label>
|
||
<input type="number" id="editIncome" value="${
|
||
report.income || ""
|
||
}" step="0.01" class="form-input w-full px-3 py-2 rounded-lg">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-bold text-gray-700 mb-1">Caja inicial</label>
|
||
<input type="number" id="editCajaInicial" value="${
|
||
report.initialCash || report.cajaInicial || ""
|
||
}" step="0.01" class="form-input w-full px-3 py-2 rounded-lg">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-bold text-gray-700 mb-1">Envelope</label>
|
||
<input type="number" id="editEnvelope" value="${
|
||
report.envelope || ""
|
||
}" step="0.01" class="form-input w-full px-3 py-2 rounded-lg">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Статус проверки -->
|
||
<div class="report-section">
|
||
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-check mr-2"></i>Статус</h4>
|
||
<label class="flex items-center">
|
||
<input type="checkbox" id="editVerified" ${
|
||
report.isVerified || report.verified ? "checked" : ""
|
||
} class="mr-2">
|
||
<span>Отчет проверен</span>
|
||
</label>
|
||
</div>
|
||
</form>
|
||
`;
|
||
|
||
buttons.innerHTML = `
|
||
<button id="saveReportBtn" class="btn-success text-white px-4 py-2 rounded-lg">
|
||
<i class="fas fa-save mr-2"></i>Сохранить
|
||
</button>
|
||
<button id="cancelEditBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||
<i class="fas fa-times mr-2"></i>Отмена
|
||
</button>
|
||
`;
|
||
|
||
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);
|
||
}
|
||
|
||
//accept report (admin only)
|
||
async function verifyReport(reportId) {
|
||
const token = localStorage.getItem("token");
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/reports/${reportId}/verify`, {
|
||
method: "POST",
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
"Content-Type": "application/json",
|
||
},
|
||
});
|
||
const data = await response.json();
|
||
if (response.ok) {
|
||
showNotification("Отчет успешно подтвержден!", "success");
|
||
hideModal("reportViewModal");
|
||
await loadReports();
|
||
if (typeof updateDashboard === "function") {
|
||
updateDashboard(); // Refresh dashboard if it exists
|
||
}
|
||
return { success: true, ...data };
|
||
} else {
|
||
showNotification(data.error || "Ошибка подтверждения отчета", "error");
|
||
return { success: false, error: data.error || "Ошибка" };
|
||
}
|
||
} catch (err) {
|
||
showNotification("Нет соединения с сервером", "error");
|
||
return { success: false, error: "Нет соединения с сервером" };
|
||
}
|
||
}
|
||
|
||
//delete report (admin only)
|
||
async function apiDeleteReport(reportId) {
|
||
const token = localStorage.getItem("token");
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/reports/${reportId}`, {
|
||
method: "DELETE",
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
"Content-Type": "application/json",
|
||
},
|
||
});
|
||
const data = await response.json();
|
||
if (response.ok && data.deleted) {
|
||
showNotification("Отчет удален!");
|
||
await loadReports();
|
||
if (typeof updateDashboard === "function") updateDashboard();
|
||
} else {
|
||
showNotification(data.error || "Ошибка удаления отчета", "error");
|
||
}
|
||
} catch {
|
||
showNotification("Нет соединения с сервером", "error");
|
||
}
|
||
}
|
||
|
||
//worker
|
||
//create report
|
||
// api.js
|
||
async function createReport(data) {
|
||
const token = localStorage.getItem("token");
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/reports`, {
|
||
method: "POST",
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify(data),
|
||
});
|
||
const result = await response.json();
|
||
if (response.ok && result.id) {
|
||
return { success: true, id: result.id };
|
||
} else {
|
||
return {
|
||
success: false,
|
||
error:
|
||
result.error ||
|
||
(result.errors && result.errors[0]?.msg) ||
|
||
"Ошибка создания отчета",
|
||
};
|
||
}
|
||
} catch (err) {
|
||
return { success: false, error: "Нет соединения с сервером" };
|
||
}
|
||
}
|