Translate Spain
This commit is contained in:
@@ -73,7 +73,7 @@ router.post(
|
||||
) {
|
||||
return res.status(409).json({
|
||||
error:
|
||||
"Отчет за этот магазин и дату уже был отправлен этим пользователем.",
|
||||
"Ya existe un informe para esta tienda y fecha enviado por este usuario.",
|
||||
});
|
||||
}
|
||||
console.error("DB error:", err);
|
||||
@@ -195,7 +195,7 @@ router.put(
|
||||
) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: "Запрещено редактировать подтвержденный отчет" });
|
||||
.json({ error: "No se permite editar un informe verificado" });
|
||||
}
|
||||
|
||||
//check for duplicate report, excluding the current, use new values if provided, else keep old ones from original report
|
||||
@@ -215,7 +215,7 @@ router.put(
|
||||
if (duplicate) {
|
||||
return res.status(409).json({
|
||||
error:
|
||||
"Отчет за этот магазин и дату уже был отправлен этим пользователем.",
|
||||
"Ya existe un informe para esta tienda y fecha enviado por este usuario.",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
//API
|
||||
const API_BASE_URL = "http://195.209.214.159/api";
|
||||
//const API_BASE_URL = "http://195.209.214.159/api";
|
||||
|
||||
//API Product
|
||||
const API_BASE_URL = "https://csr.bbox.wtf/api";
|
||||
|
||||
//API local
|
||||
// const API_BASE_URL = "http://localhost:3001/api";
|
||||
@@ -72,11 +75,11 @@ async function loginUser(username, password) {
|
||||
error:
|
||||
data.error ||
|
||||
(data.errors && data.errors[0]?.msg) ||
|
||||
"Ошибка авторизации",
|
||||
"Error de autenticación",
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
return { success: false, error: "Sin conexión con el servidor" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +97,7 @@ function logout() {
|
||||
if (document.getElementById("storeSelect"))
|
||||
document.getElementById("storeSelect").selectedIndex = 0;
|
||||
|
||||
showNotification("Вы вышли из системы", "info");
|
||||
showNotification("Sesión cerrada", "info");
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
@@ -123,7 +126,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
document.getElementById("loginScreen").classList.remove("hidden");
|
||||
}
|
||||
} catch (err) {
|
||||
showNotification("Нет соединения с сервером", "error");
|
||||
showNotification("Sin conexión con el servidor", "error");
|
||||
}
|
||||
} else {
|
||||
// No token, show login screen
|
||||
@@ -153,11 +156,11 @@ async function getAllUsers() {
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: data.error || data.message || "Ошибка получения пользователей",
|
||||
error: data.error || data.message || "Error al obtener usuarios",
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
return { success: false, error: "Sin conexión con el servidor" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,11 +187,11 @@ async function createUser(userData) {
|
||||
error:
|
||||
data.error ||
|
||||
(data.errors && data.errors[0]?.msg) ||
|
||||
"Ошибка создания пользователя",
|
||||
"Error al crear usuario",
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
return { success: false, error: "Sin conexión con el servidor" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,11 +216,11 @@ async function updateUser(userId, userData) {
|
||||
error:
|
||||
data.error ||
|
||||
(data.errors && data.errors[0]?.msg) ||
|
||||
"Ошибка обновления пользователя",
|
||||
"Error al actualizar usuario",
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
return { success: false, error: "Sin conexión con el servidor" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,8 +236,8 @@ function ensureConfirmModal() {
|
||||
<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>
|
||||
<button id="confirmModalOk" class="px-5 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700">Eliminar</button>
|
||||
<button id="confirmModalCancel" class="px-5 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -285,14 +288,14 @@ async function apiDeleteUser(userId) {
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showNotification("Пользователь удален!");
|
||||
showNotification("Usuario eliminado!");
|
||||
loadUsers();
|
||||
updateDashboard();
|
||||
} else {
|
||||
showNotification(data.error || "Ошибка удаления пользователя", "error");
|
||||
showNotification(data.error || "Error al eliminar usuario", "error");
|
||||
}
|
||||
} catch {
|
||||
showNotification("Нет соединения с сервером", "error");
|
||||
showNotification("Sin conexión con el servidor", "error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,11 +318,11 @@ async function getReports() {
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: data.error || data.message || "Ошибка получения отчетов",
|
||||
error: data.error || data.message || "Error al obtener informes",
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
return { success: false, error: "Sin conexión con el servidor" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,10 +337,10 @@ async function getReportById(reportId) {
|
||||
if (response.ok && data.report) {
|
||||
return { success: true, report: data.report };
|
||||
} else {
|
||||
return { success: false, error: data.error || "Ошибка получения отчета" };
|
||||
return { success: false, error: data.error || "Error al obtener informe" };
|
||||
}
|
||||
} catch (err) {
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
return { success: false, error: "Sin conexión con el servidor" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,16 +360,16 @@ async function updateReport(reportId, data) {
|
||||
// handler for duplicates error before parsing
|
||||
if (res.status === 409) {
|
||||
showNotification(
|
||||
"Отчет за этот магазин и дату уже был отправлен этим пользователем.",
|
||||
"Ya existe un informe para esta tienda y fecha.",
|
||||
"error"
|
||||
);
|
||||
return { success: false, error: "Дубликат отчета" };
|
||||
return { success: false, error: "Informe duplicado" };
|
||||
}
|
||||
|
||||
const result = await res.json();
|
||||
|
||||
if (res.ok && result.updated) {
|
||||
showNotification("Отчет обновлен!", "success");
|
||||
showNotification("Informe actualizado!", "success");
|
||||
hideModal("reportViewModal");
|
||||
await loadReports();
|
||||
|
||||
@@ -375,11 +378,11 @@ async function updateReport(reportId, data) {
|
||||
if (typeof updateDashboard === "function") updateDashboard();
|
||||
return { success: true };
|
||||
} else {
|
||||
showNotification(result.error || "Ошибка обновления отчета", "error");
|
||||
showNotification(result.error || "Error al actualizar informe", "error");
|
||||
return { success: false, error: result.error };
|
||||
}
|
||||
} catch (err) {
|
||||
showNotification("Ошибка сети. Попробуйте еще раз.", "error");
|
||||
showNotification("Error de red. Intente nuevamente.", "error");
|
||||
return { success: false, error: err.message };
|
||||
}
|
||||
}
|
||||
@@ -397,7 +400,7 @@ async function verifyReport(reportId) {
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showNotification("Отчет успешно подтвержден!", "success");
|
||||
showNotification("Informe verificado exitosamente!", "success");
|
||||
hideModal("reportViewModal");
|
||||
await loadReports();
|
||||
if (typeof updateDashboard === "function") {
|
||||
@@ -405,12 +408,12 @@ async function verifyReport(reportId) {
|
||||
}
|
||||
return { success: true, ...data };
|
||||
} else {
|
||||
showNotification(data.error || "Ошибка подтверждения отчета", "error");
|
||||
return { success: false, error: data.error || "Ошибка" };
|
||||
showNotification(data.error || "Error al verificar informe", "error");
|
||||
return { success: false, error: data.error || "Error" };
|
||||
}
|
||||
} catch (err) {
|
||||
showNotification("Нет соединения с сервером", "error");
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
showNotification("Sin conexión con el servidor", "error");
|
||||
return { success: false, error: "Sin conexión con el servidor" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,14 +430,14 @@ async function apiDeleteReport(reportId) {
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok && data.deleted) {
|
||||
showNotification("Отчет удален!");
|
||||
showNotification("Informe eliminado!");
|
||||
await loadReports();
|
||||
if (typeof updateDashboard === "function") updateDashboard();
|
||||
} else {
|
||||
showNotification(data.error || "Ошибка удаления отчета", "error");
|
||||
showNotification(data.error || "Error al eliminar informe", "error");
|
||||
}
|
||||
} catch {
|
||||
showNotification("Нет соединения с сервером", "error");
|
||||
showNotification("Sin conexión con el servidor", "error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,11 +481,11 @@ async function createReport(data) {
|
||||
result.error ||
|
||||
(Array.isArray(result.errors) && result.errors[0]?.msg) ||
|
||||
response.statusText ||
|
||||
"Ошибка создания отчета";
|
||||
"Error al crear informe";
|
||||
|
||||
if (response.status === 409) {
|
||||
errorMsg =
|
||||
"Отчет за этот магазин и дату уже был отправлен этим пользователем.";
|
||||
"Ya existe un informe para esta tienda y fecha.";
|
||||
}
|
||||
|
||||
// Debug: Log error case
|
||||
@@ -498,7 +501,7 @@ async function createReport(data) {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Network/server error in createReport:", err);
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
return { success: false, error: "Sin conexión con el servidor" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,11 +523,11 @@ async function getStores() {
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: data.error || "Ошибка получения магазинов",
|
||||
error: data.error || "Error al obtener tiendas",
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
return { success: false, error: "Sin conexión con el servidor" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,11 +549,11 @@ async function createStore(storeData) {
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: data.error || "Ошибка создания магазина",
|
||||
error: data.error || "Error al crear tienda",
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
return { success: false, error: "Sin conexión con el servidor" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,7 +576,7 @@ async function updateStore(storeId, storeData) {
|
||||
status: response.status,
|
||||
};
|
||||
} catch {
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
return { success: false, error: "Sin conexión con el servidor" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,10 +597,10 @@ async function deleteStoreApi(storeId) {
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: data.error || "Ошибка удаления магазина",
|
||||
error: data.error || "Error al eliminar tienda",
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
return { success: false, error: "Нет соединения с сервером" };
|
||||
return { success: false, error: "Sin conexión con el servidor" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Система учета ежедневной кассовой статистики</title>
|
||||
<title>Sistema de registro de estadísticas diarias de caja</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
|
||||
rel="stylesheet"
|
||||
@@ -13,10 +13,16 @@
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css"
|
||||
/>
|
||||
<!-- Flatpickr CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
||||
<!-- Flatpickr JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
||||
<!-- Localización española -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/es.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<!-- Контейнер приложения -->
|
||||
<!-- Contenedor de la aplicación -->
|
||||
<div id="app" class="min-h-screen">
|
||||
<!-- Экран авторизации -->
|
||||
<div
|
||||
@@ -27,15 +33,15 @@
|
||||
<div class="text-center mb-6">
|
||||
<i class="fas fa-cash-register text-4xl text-blue-600 mb-4"></i>
|
||||
<h1 class="text-2xl font-bold text-gray-800">
|
||||
Система учета кассовой статистики
|
||||
Sistema de registro de estadísticas de caja
|
||||
</h1>
|
||||
<p class="text-gray-600 mt-2">Войдите в систему</p>
|
||||
<p class="text-gray-600 mt-2">Iniciar sesión</p>
|
||||
</div>
|
||||
|
||||
<form id="loginForm">
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">
|
||||
<i class="fas fa-user mr-2"></i>Логин
|
||||
<i class="fas fa-user mr-2"></i>Usuario
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -47,7 +53,7 @@
|
||||
|
||||
<div class="mb-6">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">
|
||||
<i class="fas fa-lock mr-2"></i>Пароль
|
||||
<i class="fas fa-lock mr-2"></i>Contraseña
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
@@ -61,14 +67,14 @@
|
||||
type="submit"
|
||||
class="btn-primary w-full text-white font-bold py-2 px-4 rounded-lg"
|
||||
>
|
||||
<i class="fas fa-sign-in-alt mr-2"></i>Войти
|
||||
<i class="fas fa-sign-in-alt mr-2"></i>Acceder
|
||||
</button>
|
||||
|
||||
<div id="demoAccountsHint" hidden>
|
||||
<div class="mt-4 text-sm text-gray-600 text-center">
|
||||
<div class="mb-2"><strong>Тестовые аккаунты:</strong></div>
|
||||
<div>Админ: admin / admin123</div>
|
||||
<div>Кассир: cashier1 / password123</div>
|
||||
<div class="mb-2"><strong>Cuentas de prueba:</strong></div>
|
||||
<div>Administrador: admin / admin123</div>
|
||||
<div>Cajero: cashier1 / password123</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -87,7 +93,7 @@
|
||||
<div class="container mx-auto flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">
|
||||
<i class="fas fa-cash-register mr-2"></i>Кассовый отчет
|
||||
<i class="fas fa-cash-register mr-2"></i>Reporte de caja
|
||||
</h1>
|
||||
<p id="userWelcome" class="text-blue-100"></p>
|
||||
</div>
|
||||
@@ -96,13 +102,13 @@
|
||||
id="todayReportBtn"
|
||||
class="bg-white text-blue-600 px-4 py-2 rounded-lg hover:bg-blue-50 transition-colors"
|
||||
>
|
||||
<i class="fas fa-file-alt mr-2"></i>Отчет за сегодня
|
||||
<i class="fas fa-file-alt mr-2"></i>Informe de hoy
|
||||
</button>
|
||||
<button
|
||||
id="logoutBtn"
|
||||
class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition-colors"
|
||||
>
|
||||
<i class="fas fa-sign-out-alt mr-2"></i>Выйти
|
||||
<i class="fas fa-sign-out-alt mr-2"></i>Salir
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,7 +120,7 @@
|
||||
<!-- Дата отчета -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">
|
||||
<i class="fas fa-calendar-alt mr-2"></i>Дата отчета *
|
||||
<i class="fas fa-calendar-alt mr-2"></i>Fecha del informe *
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
@@ -128,34 +134,33 @@
|
||||
class="hidden mt-2 text-yellow-800 bg-yellow-100 rounded px-4 py-2 text-sm"
|
||||
>
|
||||
<i class="fas fa-exclamation-triangle mr-2"></i>
|
||||
Внимание: изменение даты отчета может повлиять на доступность
|
||||
редактирования.
|
||||
Advertencia: cambiar la fecha del informe puede afectar la disponibilidad de edición.
|
||||
</div>
|
||||
|
||||
<!-- Выбор магазина -->
|
||||
<!-- Selección de tienda -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">
|
||||
<i class="fas fa-store mr-2"></i>Магазин *
|
||||
<i class="fas fa-store mr-2"></i>Tienda *
|
||||
</label>
|
||||
<select
|
||||
id="storeSelect"
|
||||
class="form-input w-full px-3 py-2 rounded-lg"
|
||||
required
|
||||
>
|
||||
<option value="">Выберите магазин</option>
|
||||
<option value="">Seleccionar tienda</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Доходы -->
|
||||
<div class="report-section income">
|
||||
<h3 class="text-lg font-bold text-green-700 mb-4">
|
||||
<i class="fas fa-arrow-up mr-2"></i>Доходы (Ingresos)
|
||||
<i class="fas fa-arrow-up mr-2"></i>Ingresos
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Income (Выручка за день) * €</label
|
||||
>Ingresos del día * €</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
@@ -168,7 +173,7 @@
|
||||
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Caja inicial (Остаток со вчера) * €</label
|
||||
>Caja inicial * €</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
@@ -181,7 +186,7 @@
|
||||
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Total income (Общий доход) €</label
|
||||
>Ingresos totales €</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
@@ -196,7 +201,7 @@
|
||||
<!-- Зарплаты -->
|
||||
<div class="report-section wages">
|
||||
<h3 class="text-lg font-bold text-yellow-700 mb-4">
|
||||
<i class="fas fa-users mr-2"></i>Зарплаты (Wages)
|
||||
<i class="fas fa-users mr-2"></i>Salarios
|
||||
</h3>
|
||||
|
||||
<div id="wagesContainer">
|
||||
@@ -205,13 +210,13 @@
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Имя сотрудника"
|
||||
placeholder="Nombre del empleado"
|
||||
class="wage-name form-input px-3 py-2 rounded-lg"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="Сумма €"
|
||||
placeholder="Monto €"
|
||||
class="wage-amount form-input px-3 py-2 rounded-lg"
|
||||
/>
|
||||
<button
|
||||
@@ -228,7 +233,7 @@
|
||||
id="addWage"
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition-colors mr-4"
|
||||
>
|
||||
<i class="fas fa-plus mr-2"></i>Добавить сотрудника
|
||||
<i class="fas fa-plus mr-2"></i>Añadir empleado
|
||||
</button>
|
||||
|
||||
<div class="mt-4 text-right">
|
||||
@@ -238,10 +243,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Расходы -->
|
||||
<!-- Gastos -->
|
||||
<div class="report-section expenses">
|
||||
<h3 class="text-lg font-bold text-red-700 mb-4">
|
||||
<i class="fas fa-arrow-down mr-2"></i>Расходы (Expenses)
|
||||
<i class="fas fa-arrow-down mr-2"></i>Gastos
|
||||
</h3>
|
||||
|
||||
<div id="expensesContainer">
|
||||
@@ -250,13 +255,13 @@
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Название расхода"
|
||||
placeholder="Nombre del gasto"
|
||||
class="expense-name form-input px-3 py-2 rounded-lg"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="Сумма €"
|
||||
placeholder="Monto €"
|
||||
class="expense-amount form-input px-3 py-2 rounded-lg"
|
||||
/>
|
||||
<button
|
||||
@@ -273,7 +278,7 @@
|
||||
id="addExpense"
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition-colors mr-4"
|
||||
>
|
||||
<i class="fas fa-plus mr-2"></i>Добавить расход
|
||||
<i class="fas fa-plus mr-2"></i>Añadir gasto
|
||||
</button>
|
||||
|
||||
<div class="mt-4 text-right">
|
||||
@@ -285,16 +290,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Итоги -->
|
||||
<!-- Resumen -->
|
||||
<div class="report-section">
|
||||
<h3 class="text-lg font-bold text-gray-700 mb-4">
|
||||
<i class="fas fa-calculator mr-2"></i>Итого
|
||||
<i class="fas fa-calculator mr-2"></i>Resumen
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Total income €</label
|
||||
>Ingresos totales €</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
@@ -306,7 +311,7 @@
|
||||
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Total expenses €</label
|
||||
>Gastos totales €</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
@@ -318,7 +323,7 @@
|
||||
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Envelope (в конверт) * €</label
|
||||
>Sobre * €</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
@@ -332,7 +337,7 @@
|
||||
|
||||
<div class="text-center bg-blue-50 p-4 rounded-lg">
|
||||
<span class="text-xl font-bold text-blue-700"
|
||||
>Caja final (остаток на завтра): €<span id="cajaFinal"
|
||||
>Caja final: €<span id="cajaFinal"
|
||||
>0.00</span
|
||||
></span
|
||||
>
|
||||
@@ -344,7 +349,7 @@
|
||||
type="submit"
|
||||
class="btn-primary text-white font-bold py-3 px-8 rounded-lg text-lg"
|
||||
>
|
||||
<i class="fas fa-save mr-2"></i>Сохранить отчет
|
||||
<i class="fas fa-save mr-2"></i>Guardar informe
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -358,7 +363,7 @@
|
||||
<div class="container mx-auto flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">
|
||||
<i class="fas fa-chart-line mr-2"></i>Админ панель
|
||||
<i class="fas fa-chart-line mr-2"></i>Panel de administración
|
||||
</h1>
|
||||
<p id="adminWelcome" class="text-blue-100"></p>
|
||||
</div>
|
||||
@@ -366,12 +371,12 @@
|
||||
id="adminLogoutBtn"
|
||||
class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition-colors"
|
||||
>
|
||||
<i class="fas fa-sign-out-alt mr-2"></i>Выйти
|
||||
<i class="fas fa-sign-out-alt mr-2"></i>Salir
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Навигация -->
|
||||
<!-- Navegación -->
|
||||
<div class="bg-white shadow">
|
||||
<div class="container mx-auto">
|
||||
<div class="flex space-x-0">
|
||||
@@ -385,19 +390,19 @@
|
||||
class="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"
|
||||
data-tab="reports"
|
||||
>
|
||||
<i class="fas fa-file-alt mr-2"></i>Отчеты
|
||||
<i class="fas fa-file-alt mr-2"></i>Informes
|
||||
</button>
|
||||
<button
|
||||
class="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"
|
||||
data-tab="users"
|
||||
>
|
||||
<i class="fas fa-users mr-2"></i>Пользователи
|
||||
<i class="fas fa-users mr-2"></i>Usuarios
|
||||
</button>
|
||||
<button
|
||||
class="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"
|
||||
data-tab="stores"
|
||||
>
|
||||
<i class="fas fa-store mr-2"></i>Магазины
|
||||
<i class="fas fa-store mr-2"></i>Tiendas
|
||||
</button>
|
||||
<button
|
||||
class="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"
|
||||
@@ -409,7 +414,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Контент вкладок -->
|
||||
<!-- Contenido de pestañas -->
|
||||
<div class="container mx-auto p-6">
|
||||
<!-- Dashboard -->
|
||||
<div id="dashboardTab" class="admin-tab-content">
|
||||
@@ -423,7 +428,7 @@
|
||||
<p
|
||||
class="text-white-contrast text-sm font-medium uppercase tracking-wide"
|
||||
>
|
||||
Общий доход
|
||||
Ingresos totales
|
||||
</p>
|
||||
<p
|
||||
class="text-white-contrast text-3xl font-bold"
|
||||
@@ -448,7 +453,7 @@
|
||||
<p
|
||||
class="text-white-contrast text-sm font-medium uppercase tracking-wide"
|
||||
>
|
||||
Общие расходы
|
||||
Gastos totales
|
||||
</p>
|
||||
<p
|
||||
class="text-white-contrast text-3xl font-bold"
|
||||
@@ -473,7 +478,7 @@
|
||||
<p
|
||||
class="text-white-contrast text-sm font-medium uppercase tracking-wide"
|
||||
>
|
||||
Количество отчетов
|
||||
Número de informes
|
||||
</p>
|
||||
<p
|
||||
class="text-white-contrast text-3xl font-bold"
|
||||
@@ -514,12 +519,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Графики -->
|
||||
<!-- Gráficos -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div class="bg-white p-6 rounded-lg shadow-lg">
|
||||
<h3 class="text-lg font-bold text-gray-800 mb-4">
|
||||
<i class="fas fa-chart-line mr-2 text-blue-600"></i>Доходы по
|
||||
дням
|
||||
<i class="fas fa-chart-line mr-2 text-blue-600"></i>Ingresos por día
|
||||
|
||||
</h3>
|
||||
<div style="height: 300px">
|
||||
<canvas id="revenueChart"></canvas>
|
||||
@@ -528,8 +533,7 @@
|
||||
|
||||
<div class="bg-white p-6 rounded-lg shadow-lg">
|
||||
<h3 class="text-lg font-bold text-gray-800 mb-4">
|
||||
<i class="fas fa-chart-pie mr-2 text-red-600"></i>Расходы по
|
||||
категориям
|
||||
<i class="fas fa-chart-pie mr-2 text-red-600"></i>Gastos por categoría
|
||||
</h3>
|
||||
<div style="height: 300px">
|
||||
<canvas id="expensesChart"></canvas>
|
||||
@@ -538,8 +542,7 @@
|
||||
|
||||
<div class="bg-white p-6 rounded-lg shadow-lg">
|
||||
<h3 class="text-lg font-bold text-gray-800 mb-4">
|
||||
<i class="fas fa-chart-bar mr-2 text-green-600"></i>Сравнение
|
||||
по магазинам
|
||||
<i class="fas fa-chart-bar mr-2 text-green-600"></i>Comparación por tiendas
|
||||
</h3>
|
||||
<div style="height: 300px">
|
||||
<canvas id="storesChart"></canvas>
|
||||
@@ -548,8 +551,7 @@
|
||||
|
||||
<div class="bg-white p-6 rounded-lg shadow-lg">
|
||||
<h3 class="text-lg font-bold text-gray-800 mb-4">
|
||||
<i class="fas fa-chart-area mr-2 text-purple-600"></i>Тренды
|
||||
продаж
|
||||
<i class="fas fa-chart-area mr-2 text-purple-600"></i>Tendencias de ventas
|
||||
</h3>
|
||||
<div style="height: 300px">
|
||||
<canvas id="trendsChart"></canvas>
|
||||
@@ -563,27 +565,27 @@
|
||||
<div class="bg-white rounded-lg shadow-lg">
|
||||
<div class="p-6 border-b">
|
||||
<h2 class="text-xl font-bold text-gray-800">
|
||||
<i class="fas fa-file-alt mr-2"></i>Управление отчетами
|
||||
<i class="fas fa-file-alt mr-2"></i>Gestión de informes
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- Фильтры -->
|
||||
<!-- Filtros -->
|
||||
<div class="p-6 bg-gray-50 border-b">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>Магазин</label
|
||||
>Tienda</label
|
||||
>
|
||||
<select
|
||||
id="filterStore"
|
||||
class="form-input w-full px-3 py-2 rounded-lg"
|
||||
>
|
||||
<option value="">Все магазины</option>
|
||||
<option value="">Todas las tiendas</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>Дата от</label
|
||||
>Fecha desde</label
|
||||
>
|
||||
<input
|
||||
type="date"
|
||||
@@ -593,7 +595,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>Дата до</label
|
||||
>Fecha hasta</label
|
||||
>
|
||||
<input
|
||||
type="date"
|
||||
@@ -606,7 +608,7 @@
|
||||
id="applyFilters"
|
||||
class="btn-primary text-white px-4 py-2 rounded-lg mr-2"
|
||||
>
|
||||
<i class="fas fa-search mr-2"></i>Поиск
|
||||
<i class="fas fa-search mr-2"></i>Buscar
|
||||
</button>
|
||||
<button
|
||||
id="exportExcel"
|
||||
@@ -618,7 +620,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Таблица отчетов -->
|
||||
<!-- Tabla de informes -->
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-100">
|
||||
@@ -626,42 +628,42 @@
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Дата
|
||||
Fecha
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Магазин
|
||||
Tienda
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Доход
|
||||
Ingresos
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Расходы
|
||||
Gastos
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Прибыль
|
||||
Beneficio
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Пользователь
|
||||
Usuario
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Статус
|
||||
Estado
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Действия
|
||||
Acciones
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -679,13 +681,13 @@
|
||||
<div class="bg-white rounded-lg shadow-lg">
|
||||
<div class="p-6 border-b flex justify-between items-center">
|
||||
<h2 class="text-xl font-bold text-gray-800">
|
||||
<i class="fas fa-users mr-2"></i>Управление пользователями
|
||||
<i class="fas fa-users mr-2"></i>Gestión de usuarios
|
||||
</h2>
|
||||
<button
|
||||
id="addUserBtn"
|
||||
class="btn-primary text-white px-4 py-2 rounded-lg"
|
||||
>
|
||||
<i class="fas fa-plus mr-2"></i>Добавить пользователя
|
||||
<i class="fas fa-plus mr-2"></i>Añadir usuario
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -701,22 +703,22 @@
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Логин
|
||||
Usuario
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Роль
|
||||
Rol
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Доступные магазины
|
||||
Tiendas disponibles
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Действия
|
||||
Acciones
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -734,13 +736,13 @@
|
||||
<div class="bg-white rounded-lg shadow-lg">
|
||||
<div class="p-6 border-b flex justify-between items-center">
|
||||
<h2 class="text-xl font-bold text-gray-800">
|
||||
<i class="fas fa-store mr-2"></i>Управление магазинами
|
||||
<i class="fas fa-store mr-2"></i>Gestión de tiendas
|
||||
</h2>
|
||||
<button
|
||||
id="addStoreBtn"
|
||||
class="btn-primary text-white px-4 py-2 rounded-lg"
|
||||
>
|
||||
<i class="fas fa-plus mr-2"></i>Добавить магазин
|
||||
<i class="fas fa-plus mr-2"></i>Añadir tienda
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -756,17 +758,17 @@
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Название
|
||||
Nombre
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Количество отчетов
|
||||
Número de informes
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Действия
|
||||
Acciones
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -784,14 +786,14 @@
|
||||
<div class="bg-white rounded-lg shadow-lg">
|
||||
<div class="p-6 border-b flex justify-between items-center">
|
||||
<h2 class="text-xl font-bold text-gray-800">
|
||||
<i class="fas fa-tasks mr-2"></i>TODO лист - Отслеживание
|
||||
<i class="fas fa-tasks mr-2"></i>Lista TODO - Seguimiento
|
||||
изменений
|
||||
</h2>
|
||||
<button
|
||||
id="addTodoBtn"
|
||||
class="btn-primary text-white px-4 py-2 rounded-lg"
|
||||
>
|
||||
<i class="fas fa-plus mr-2"></i>Добавить задачу
|
||||
<i class="fas fa-plus mr-2"></i>Añadir tarea
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -808,7 +810,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="text-xl font-bold text-gray-800" id="reportModalTitle">
|
||||
Просмотр отчета
|
||||
Ver informe
|
||||
</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -829,14 +831,14 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="text-xl font-bold text-gray-800" id="userModalTitle">
|
||||
Пользователь
|
||||
Usuario
|
||||
</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="userEditForm">
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Логин</label
|
||||
>Usuario</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
@@ -848,7 +850,7 @@
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Пароль</label
|
||||
>Contraseña</label
|
||||
>
|
||||
<div class="relative">
|
||||
<input
|
||||
@@ -861,34 +863,34 @@
|
||||
id="togglePasswordBtn"
|
||||
class="absolute right-3 top-1/4 -translate-y-1/2 text-xl text-gray-500 hover:text-gray-700"
|
||||
tabindex="-1"
|
||||
aria-label="Показать пароль"
|
||||
aria-label="Mostrar contraseña"
|
||||
>
|
||||
<span id="eyeIcon">👁️</span>
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-gray-500"
|
||||
>Если не хотите менять пароль, оставьте поле без
|
||||
изменений</small
|
||||
>Si no desea cambiar la contraseña, deje este campo sin
|
||||
cambios</small
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Роль</label
|
||||
>Rol</label
|
||||
>
|
||||
<select
|
||||
id="userRole"
|
||||
class="form-input w-full px-3 py-2 rounded-lg"
|
||||
required
|
||||
>
|
||||
<option value="employee">Сотрудник</option>
|
||||
<option value="admin">Администратор</option>
|
||||
<option value="employee">Empleado</option>
|
||||
<option value="admin">Administrador</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Доступные магазины</label
|
||||
>Tiendas disponibles</label
|
||||
>
|
||||
<div id="userStoresAccess" class="space-y-2">
|
||||
<!-- Чекбоксы магазинов -->
|
||||
@@ -901,13 +903,13 @@
|
||||
id="saveUserBtn"
|
||||
class="btn-success text-white px-4 py-2 rounded-lg mr-2"
|
||||
>
|
||||
<i class="fas fa-save mr-2"></i>Сохранить
|
||||
<i class="fas fa-save mr-2"></i>Guardar
|
||||
</button>
|
||||
<button
|
||||
id="cancelUserBtn"
|
||||
class="bg-gray-500 text-white px-4 py-2 rounded-lg"
|
||||
>
|
||||
<i class="fas fa-times mr-2"></i>Отмена
|
||||
<i class="fas fa-times mr-2"></i>Cancelar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -918,14 +920,14 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="text-xl font-bold text-gray-800" id="storeModalTitle">
|
||||
Магазин
|
||||
Tienda
|
||||
</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="storeEditForm">
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Название магазина</label
|
||||
>Nombre de la tienda</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
@@ -942,13 +944,13 @@
|
||||
id="saveStoreBtn"
|
||||
class="btn-success text-white px-4 py-2 rounded-lg mr-2"
|
||||
>
|
||||
<i class="fas fa-save mr-2"></i>Сохранить
|
||||
<i class="fas fa-save mr-2"></i>Guardar
|
||||
</button>
|
||||
<button
|
||||
id="cancelStoreBtn"
|
||||
class="bg-gray-500 text-white px-4 py-2 rounded-lg"
|
||||
>
|
||||
<i class="fas fa-times mr-2"></i>Отмена
|
||||
<i class="fas fa-times mr-2"></i>Cancelar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -959,14 +961,14 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="text-xl font-bold text-gray-800" id="todoModalTitle">
|
||||
TODO задача
|
||||
Tarea TODO
|
||||
</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="todoEditForm">
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Заголовок</label
|
||||
>Título</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
@@ -978,7 +980,7 @@
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Описание</label
|
||||
>Descripción</label
|
||||
>
|
||||
<textarea
|
||||
id="todoDescription"
|
||||
@@ -989,15 +991,15 @@
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2"
|
||||
>Приоритет</label
|
||||
>Prioridad</label
|
||||
>
|
||||
<select
|
||||
id="todoPriority"
|
||||
class="form-input w-full px-3 py-2 rounded-lg"
|
||||
>
|
||||
<option value="low">Низкий</option>
|
||||
<option value="medium">Средний</option>
|
||||
<option value="high">Высокий</option>
|
||||
<option value="low">Baja</option>
|
||||
<option value="medium">Media</option>
|
||||
<option value="high">Alta</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1007,13 +1009,13 @@
|
||||
id="saveTodoBtn"
|
||||
class="btn-success text-white px-4 py-2 rounded-lg mr-2"
|
||||
>
|
||||
<i class="fas fa-save mr-2"></i>Сохранить
|
||||
<i class="fas fa-save mr-2"></i>Guardar
|
||||
</button>
|
||||
<button
|
||||
id="cancelTodoBtn"
|
||||
class="bg-gray-500 text-white px-4 py-2 rounded-lg"
|
||||
>
|
||||
<i class="fas fa-times mr-2"></i>Отмена
|
||||
<i class="fas fa-times mr-2"></i>Cancelar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1024,7 +1026,7 @@
|
||||
<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 id="notificationText">Успешно сохранено!</span>
|
||||
<span id="notificationText">Guardado exitosamente!</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
86
frontend/locales/es.json
Normal file
86
frontend/locales/es.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"errors": {
|
||||
"server_connection": "Sin conexión con el servidor",
|
||||
"auth_error": "Error de autenticación",
|
||||
"user_creation": "Error al crear usuario",
|
||||
"user_update": "Error al actualizar usuario",
|
||||
"user_delete": "Error al eliminar usuario",
|
||||
"report_creation": "Error al crear informe",
|
||||
"report_update": "Error al actualizar informe",
|
||||
"report_delete": "Error al eliminar informe",
|
||||
"report_duplicate": "Ya existe un informe para esta tienda y fecha",
|
||||
"store_creation": "Error al crear tienda",
|
||||
"store_update": "Error al actualizar tienda",
|
||||
"store_delete": "Error al eliminar tienda",
|
||||
"data_loading": "Error al cargar datos"
|
||||
},
|
||||
"notifications": {
|
||||
"login_success": "Autenticación exitosa",
|
||||
"logout": "Sesión cerrada",
|
||||
"report_created": "Informe creado exitosamente",
|
||||
"report_updated": "Informe actualizado",
|
||||
"report_deleted": "Informe eliminado",
|
||||
"report_verified": "Informe verificado",
|
||||
"user_created": "Usuario creado",
|
||||
"user_updated": "Usuario actualizado",
|
||||
"user_deleted": "Usuario eliminado",
|
||||
"store_created": "Tienda creada",
|
||||
"store_updated": "Tienda actualizada",
|
||||
"store_deleted": "Tienda eliminada",
|
||||
"todo_created": "Tarea creada",
|
||||
"todo_updated": "Tarea actualizada",
|
||||
"todo_deleted": "Tarea eliminada"
|
||||
},
|
||||
"ui": {
|
||||
"login": {
|
||||
"title": "Sistema de estadísticas de caja diaria",
|
||||
"subtitle": "Iniciar sesión",
|
||||
"username": "Usuario",
|
||||
"password": "Contraseña",
|
||||
"submit": "Entrar",
|
||||
"demo_accounts": "Cuentas de prueba:",
|
||||
"admin": "Admin: admin / admin123",
|
||||
"cashier": "Cajero: cashier1 / password123"
|
||||
},
|
||||
"buttons": {
|
||||
"add_field": "Añadir campo adicional",
|
||||
"add_employee": "Añadir empleado",
|
||||
"add_expense": "Añadir gasto"
|
||||
},
|
||||
"user": {
|
||||
"welcome": "Bienvenido, {username}!",
|
||||
"logout": "Salir",
|
||||
"today_report": "Informe de hoy"
|
||||
},
|
||||
"admin": {
|
||||
"welcome": "Bienvenido, {username}!",
|
||||
"logout": "Salir",
|
||||
"dashboard": "Panel",
|
||||
"reports": "Informes",
|
||||
"users": "Usuarios",
|
||||
"stores": "Tiendas",
|
||||
"todos": "Tareas",
|
||||
"users_management": "Gestión de usuarios",
|
||||
"stores_management": "Gestión de tiendas",
|
||||
"reports_management": "Gestión de informes"
|
||||
},
|
||||
"report": {
|
||||
"title": "Informe de caja",
|
||||
"date": "Fecha",
|
||||
"store": "Tienda",
|
||||
"income": "Ingresos",
|
||||
"expenses": "Gastos",
|
||||
"profit": "Beneficio",
|
||||
"status": "Estado",
|
||||
"verified": "Verificado",
|
||||
"unverified": "No verificado",
|
||||
"view": "Ver",
|
||||
"edit": "Editar",
|
||||
"delete": "Eliminar",
|
||||
"verify": "Verificar",
|
||||
"unverify": "Desverificar",
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,19 @@
|
||||
//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');
|
||||
@@ -81,12 +94,12 @@ function showNotification(message, type = "success") {
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Управление модальными окнами с исправленной прокруткой
|
||||
// Gestión de ventanas modales con desplazamiento corregido
|
||||
function showModal(modalId) {
|
||||
const modal = document.getElementById(modalId);
|
||||
if (modal) {
|
||||
modal.classList.add("show");
|
||||
// Блокируем прокрутку body
|
||||
// Bloquear desplazamiento del cuerpo
|
||||
document.body.classList.add("modal-open");
|
||||
}
|
||||
}
|
||||
@@ -200,10 +213,10 @@ document.getElementById("loginForm").addEventListener("submit", async (e) => {
|
||||
showUserInterface();
|
||||
}
|
||||
|
||||
showNotification("Успешная авторизация!");
|
||||
showNotification("¡Autenticación exitosa!");
|
||||
} else {
|
||||
const errorDiv = document.getElementById("loginError");
|
||||
errorDiv.textContent = result.error || "Неверный логин или пароль";
|
||||
errorDiv.textContent = result.error || "Usuario o contraseña incorrectos";
|
||||
errorDiv.classList.remove("hidden");
|
||||
}
|
||||
});
|
||||
@@ -213,7 +226,7 @@ async function showUserInterface() {
|
||||
document.getElementById("userInterface").classList.remove("hidden");
|
||||
document.getElementById(
|
||||
"userWelcome"
|
||||
).textContent = `Добро пожаловать, ${appState.currentUser.username}!`;
|
||||
).textContent = `Bienvenido, ${appState.currentUser.username}!`;
|
||||
|
||||
loadUserStores();
|
||||
setupFormCalculations();
|
||||
@@ -226,7 +239,7 @@ async function showAdminInterface() {
|
||||
document.getElementById("adminInterface").classList.remove("hidden");
|
||||
document.getElementById(
|
||||
"adminWelcome"
|
||||
).textContent = `Добро пожаловать, ${appState.currentUser.username}!`;
|
||||
).textContent = `Bienvenido, ${appState.currentUser.username}!`;
|
||||
|
||||
await loadStores();
|
||||
await loadUsers();
|
||||
@@ -279,7 +292,7 @@ function logout() {
|
||||
document.getElementById("loginForm").reset();
|
||||
document.getElementById("loginError").classList.add("hidden");
|
||||
|
||||
showNotification("Вы вышли из системы");
|
||||
showNotification("¡Sesión cerrada!");
|
||||
}
|
||||
|
||||
//DASHBOARD
|
||||
@@ -392,7 +405,7 @@ function createCharts() {
|
||||
appState.expensesChartInstance = new Chart(expensesCtx, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: ["Зарплаты", "Прочие расходы"],
|
||||
labels: ["Salarios", "Otros gastos"],
|
||||
datasets: [
|
||||
{
|
||||
data: [totalWages, totalInternal],
|
||||
@@ -429,7 +442,7 @@ function createCharts() {
|
||||
labels: storeData.map((s) => s.name),
|
||||
datasets: [
|
||||
{
|
||||
label: "Доходы по магазинам",
|
||||
label: "Ingresos por tienda",
|
||||
data: storeData.map((s) => s.revenue),
|
||||
backgroundColor: "#10B981",
|
||||
},
|
||||
@@ -487,7 +500,7 @@ function createCharts() {
|
||||
labels: last30Days,
|
||||
datasets: [
|
||||
{
|
||||
label: "Прибыль",
|
||||
label: "Beneficio",
|
||||
data: profitData,
|
||||
borderColor: "#8B5CF6",
|
||||
backgroundColor: "rgba(139, 92, 246, 0.1)",
|
||||
@@ -536,7 +549,7 @@ async function loadReports() {
|
||||
appState.reportsList = result.success ? result.reports : [];
|
||||
|
||||
if (!result.success) {
|
||||
showNotification(result.error || "Ошибка загрузки отчетов", "error");
|
||||
showNotification(result.error || "Error al cargar informes", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -604,19 +617,19 @@ async function loadReports() {
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-yellow-100 text-yellow-800"
|
||||
}">
|
||||
${report.isVerified ? "Проверен" : "Не проверен"}
|
||||
${report.isVerified ? "Verificado" : "No verificado"}
|
||||
</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> Просмотр
|
||||
<i class="fas fa-eye"></i> Ver
|
||||
</button>
|
||||
<button class="text-red-600 hover:text-red-900" onclick="deleteReport(${
|
||||
report.id
|
||||
})">
|
||||
<i class="fas fa-trash"></i> Удалить
|
||||
<i class="fas fa-trash"></i> Eliminar
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
@@ -720,10 +733,10 @@ function editReport(report) {
|
||||
|
||||
buttons.innerHTML = `
|
||||
<button id="saveReportBtn" class="btn-success text-white px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-save mr-2"></i>Сохранить
|
||||
<i class="fas fa-save mr-2"></i>Guardar
|
||||
</button>
|
||||
<button id="cancelEditBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-times mr-2"></i>Отмена
|
||||
<i class="fas fa-times mr-2"></i>Cancelar
|
||||
</button>
|
||||
`;
|
||||
|
||||
@@ -804,26 +817,26 @@ function showReportModal(report, isAdmin = false) {
|
||||
const buttons = document.getElementById("reportModalButtons");
|
||||
const title = document.getElementById("reportModalTitle");
|
||||
|
||||
title.textContent = `Отчет от ${report.reportDate || report.date} - ${
|
||||
report.storeName || report.storeId || "Неизвестный магазин"
|
||||
title.textContent = `Informe del ${report.reportDate || report.date} - ${
|
||||
report.storeName || report.storeId || "Tienda desconocida"
|
||||
}`;
|
||||
|
||||
content.innerHTML = `
|
||||
<div class="space-y-6">
|
||||
<!-- Основная информация -->
|
||||
<!-- Información básica -->
|
||||
<div class="report-section">
|
||||
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-info-circle mr-2"></i>Основная информация</h4>
|
||||
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-info-circle mr-2"></i>Información básica</h4>
|
||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||
<div><strong>Дата:</strong> ${
|
||||
<div><strong>Fecha:</strong> ${
|
||||
report.date || report.reportDate || ""
|
||||
}</div>
|
||||
<div><strong>Магазин:</strong> ${
|
||||
<div><strong>Tienda:</strong> ${
|
||||
report.storeName || report.storeId
|
||||
}</div>
|
||||
<div><strong>Пользователь:</strong> ${
|
||||
<div><strong>Usuario:</strong> ${
|
||||
report.username || report.fullName || report.userId
|
||||
}</div>
|
||||
<div><strong>Статус:</strong>
|
||||
<div><strong>Estado:</strong>
|
||||
<span class="px-2 py-1 rounded text-xs ${
|
||||
report.isVerified || report.verified
|
||||
? "bg-green-100 text-green-800"
|
||||
@@ -831,29 +844,29 @@ function showReportModal(report, isAdmin = false) {
|
||||
}">
|
||||
${
|
||||
report.isVerified || report.verified
|
||||
? "Проверен"
|
||||
: "Не проверен"
|
||||
? "Verificado"
|
||||
: "No verificado"
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Доходы -->
|
||||
<!-- Ingresos -->
|
||||
<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>
|
||||
<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>Ingresos del día:</strong> €${safeToFixed(report.income)}</div>
|
||||
<div><strong>Caja inicial:</strong> €${safeToFixed(
|
||||
report.cajaInicial || report.initialCash
|
||||
)}</div>
|
||||
<div><strong>Total income:</strong> €${safeToFixed(
|
||||
<div><strong>Ingresos totales:</strong> €${safeToFixed(
|
||||
report.totalIncome
|
||||
)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Зарплаты -->
|
||||
<!-- Salarios -->
|
||||
<div class="report-section wages">
|
||||
<h4 class="font-bold text-yellow-700 mb-3"><i class="fas fa-users mr-2"></i>Зарплаты (Wages)</h4>
|
||||
<h4 class="font-bold text-yellow-700 mb-3"><i class="fas fa-users mr-2"></i>Salarios</h4>
|
||||
${
|
||||
Array.isArray(wages) && wages.length > 0
|
||||
? `
|
||||
@@ -870,18 +883,18 @@ function showReportModal(report, isAdmin = false) {
|
||||
.join("")}
|
||||
<div class="border-t pt-2 font-bold">
|
||||
<div class="flex justify-between">
|
||||
<span>Total wages:</span>
|
||||
<span>Total salarios:</span>
|
||||
<span>€${safeToFixed(report.totalWages)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: '<p class="text-gray-500 text-sm">Нет данных о зарплатах</p>'
|
||||
: '<p class="text-gray-500 text-sm">No hay datos de salarios</p>'
|
||||
}
|
||||
</div>
|
||||
<!-- Расходы -->
|
||||
<!-- Gastos -->
|
||||
<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>
|
||||
<h4 class="font-bold text-red-700 mb-3"><i class="fas fa-arrow-down mr-2"></i>Gastos</h4>
|
||||
${
|
||||
Array.isArray(expenses) && expenses.length > 0
|
||||
? `
|
||||
@@ -898,26 +911,26 @@ function showReportModal(report, isAdmin = false) {
|
||||
.join("")}
|
||||
<div class="border-t pt-2 font-bold">
|
||||
<div class="flex justify-between">
|
||||
<span>Total expenses internal:</span>
|
||||
<span>Total gastos internos:</span>
|
||||
<span>€${safeToFixed(totalExpensesInternal)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: '<p class="text-gray-500 text-sm">Нет данных о расходах</p>'
|
||||
: '<p class="text-gray-500 text-sm">No hay datos de gastos</p>'
|
||||
}
|
||||
</div>
|
||||
<!-- Итоговые расчеты -->
|
||||
<!-- Resumen final -->
|
||||
<div class="report-section">
|
||||
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-calculator mr-2"></i>Итоговые расчеты</h4>
|
||||
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-calculator mr-2"></i>Resumen final</h4>
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between"><strong>Total income:</strong> <span>€${safeToFixed(
|
||||
<div class="flex justify-between"><strong>Ingresos totales:</strong> <span>€${safeToFixed(
|
||||
report.totalIncome
|
||||
)}</span></div>
|
||||
<div class="flex justify-between"><strong>Total expenses:</strong> <span>€${safeToFixed(
|
||||
<div class="flex justify-between"><strong>Gastos totales:</strong> <span>€${safeToFixed(
|
||||
report.totalExpenses
|
||||
)}</span></div>
|
||||
<div class="flex justify-between"><strong>Envelope:</strong> <span>€${safeToFixed(
|
||||
<div class="flex justify-between"><strong>Sobre:</strong> <span>€${safeToFixed(
|
||||
report.envelope
|
||||
)}</span></div>
|
||||
<div class="flex justify-between border-t pt-2 text-lg font-bold text-blue-700">
|
||||
@@ -937,14 +950,14 @@ function showReportModal(report, isAdmin = false) {
|
||||
buttons.innerHTML = `
|
||||
<div class="grid grid-cols-2 items-center justify-between gap-4">
|
||||
<div class="text-yellow-700 font-medium text-sm">
|
||||
Отчет подтвержден.<br>Для внесения изменений снимите подтверждение.
|
||||
Informe verificado.<br>Para hacer cambios, retire la verificación.
|
||||
</div>
|
||||
<div class="flex justify-end gap-3">
|
||||
<button id="unverifyReportBtn" class="bg-yellow-500 text-white px-4 py-2 rounded-lg hover:bg-yellow-600 flex items-center">
|
||||
<i class="fas fa-undo mr-2"></i>Снять подтверждение
|
||||
<i class="fas fa-undo mr-2"></i>Retirar verificación
|
||||
</button>
|
||||
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg flex items-center">
|
||||
<i class="fas fa-times mr-2"></i>Закрыть
|
||||
<i class="fas fa-times mr-2"></i>Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -957,13 +970,13 @@ function showReportModal(report, isAdmin = false) {
|
||||
} 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>Редактировать
|
||||
<i class="fas fa-edit mr-2"></i>Editar
|
||||
</button>
|
||||
<button id="verifyReportBtn" class="btn-success text-white px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-check mr-2"></i>Подтвердить отчет
|
||||
<i class="fas fa-check mr-2"></i>Verificar informe
|
||||
</button>
|
||||
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-times mr-2"></i>Закрыть
|
||||
<i class="fas fa-times mr-2"></i>Cerrar
|
||||
</button>
|
||||
`;
|
||||
document.getElementById("editReportBtn").addEventListener("click", () => {
|
||||
@@ -979,10 +992,10 @@ function showReportModal(report, isAdmin = false) {
|
||||
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>Редактировать
|
||||
<i class="fas fa-edit mr-2"></i>Editar
|
||||
</button>
|
||||
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-times mr-2"></i>Закрыть
|
||||
<i class="fas fa-times mr-2"></i>Cerrar
|
||||
</button>
|
||||
`;
|
||||
document
|
||||
@@ -994,7 +1007,7 @@ function showReportModal(report, isAdmin = false) {
|
||||
});
|
||||
} else {
|
||||
buttons.innerHTML = `
|
||||
<div class="mb-2 text-yellow-700 font-medium text-sm">Отчет подтвержден и не может быть изменен. Обратитесь к администратору для изменений.</div>
|
||||
<div class="mb-2 text-yellow-700 font-medium text-sm">El informe está verificado y no se puede modificar. Contacte al administrador para cambios.</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>
|
||||
@@ -1014,7 +1027,7 @@ function showReportModal(report, isAdmin = false) {
|
||||
// helpder of the modal of showReportModal - unverify report for ADMIN
|
||||
async function unverifyReport(reportId) {
|
||||
await updateReport(reportId, { isVerified: 0 });
|
||||
showNotification("Подтверждение снято. Теперь можно редактировать.");
|
||||
showNotification("Verificación retirada. Ahora se puede editar.");
|
||||
await loadReports();
|
||||
hideModal("reportViewModal");
|
||||
}
|
||||
@@ -1083,34 +1096,34 @@ function applyReportsFilters() {
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-yellow-100 text-yellow-800"
|
||||
}">
|
||||
${report.isVerified ? "Проверен" : "Не проверен"}
|
||||
${report.isVerified ? "Verificado" : "No verificado"}
|
||||
</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> Просмотр
|
||||
<i class="fas fa-eye"></i> Ver
|
||||
</button>
|
||||
<button class="text-red-600 hover:text-red-900" onclick="deleteReport(${
|
||||
report.id
|
||||
})">
|
||||
<i class="fas fa-trash"></i> Удалить
|
||||
<i class="fas fa-trash"></i> Eliminar
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
showNotification(`Найдено ${filteredReports.length} отчетов`);
|
||||
showNotification(`Se encontraron ${filteredReports.length} informes`);
|
||||
}
|
||||
|
||||
function deleteReport(reportId) {
|
||||
showConfirmModal("Вы уверены, что хотите удалить этот отчет?", () => {
|
||||
showConfirmModal("¿Estás seguro de que deseas eliminar este informe?", () => {
|
||||
apiDeleteReport(reportId);
|
||||
loadReports();
|
||||
updateDashboard();
|
||||
showNotification("Отчет удален!");
|
||||
showNotification("¡Informe eliminado!");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1174,8 +1187,8 @@ function fillFormWithReport(report) {
|
||||
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">
|
||||
<input type="text" placeholder="Nombre del empleado" value="${wage.name}" class="wage-name form-input px-3 py-2 rounded-lg">
|
||||
<input type="number" step="0.01" placeholder="Cantidad €" 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>
|
||||
@@ -1194,8 +1207,8 @@ function fillFormWithReport(report) {
|
||||
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">
|
||||
<input type="text" placeholder="Nombre del gasto" value="${expense.name}" class="expense-name form-input px-3 py-2 rounded-lg">
|
||||
<input type="number" step="0.01" placeholder="Cantidad €" 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>
|
||||
@@ -1217,7 +1230,7 @@ function exportToExcel() {
|
||||
const users = appState.usersList || [];
|
||||
|
||||
if (!reports.length) {
|
||||
showNotification("Нет отчетов для экспорта", "info");
|
||||
showNotification("No hay informes para exportar", "info");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1233,21 +1246,21 @@ function exportToExcel() {
|
||||
|
||||
return {
|
||||
Дата: report.reportDate || report.date || "",
|
||||
Магазин: store.name || report.storeName || "Неизвестно",
|
||||
Tienda: store.name || report.storeName || "Desconocido",
|
||||
Доход: `€${Number(report.totalIncome || 0).toFixed(2)}`,
|
||||
Расходы: `€${Number(report.totalExpenses || 0).toFixed(2)}`,
|
||||
Прибыль: `€${profit.toFixed(2)}`,
|
||||
Пользователь: user.username || report.username || "Неизвестно",
|
||||
Статус: report.isVerified
|
||||
? "Проверен"
|
||||
Usuario: user.username || report.username || "Desconocido",
|
||||
Estado: report.isVerified
|
||||
? "Verificado"
|
||||
: report.verified
|
||||
? "Проверен"
|
||||
: "Не проверен",
|
||||
? "Verificado"
|
||||
: "No verificado",
|
||||
};
|
||||
});
|
||||
|
||||
if (!data.length) {
|
||||
showNotification("Нет отчетов для экспорта", "info");
|
||||
showNotification("No hay informes para exportar", "info");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1274,7 +1287,7 @@ function exportToExcel() {
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
showNotification("Отчет экспортирован!");
|
||||
showNotification("¡Informe exportado!");
|
||||
}
|
||||
|
||||
//REPORTS FORM LOGIC
|
||||
@@ -1284,7 +1297,7 @@ function loadUserStores() {
|
||||
const select = document.getElementById("storeSelect");
|
||||
if (!select) return;
|
||||
select.innerHTML =
|
||||
'<option value="" disabled selected hidden>Выберите магазин</option>';
|
||||
'<option value="" disabled selected hidden>Seleccione tienda</option>';
|
||||
|
||||
if (!appState.currentUser) return;
|
||||
// For admin: show all
|
||||
@@ -1398,8 +1411,8 @@ function addWageRow() {
|
||||
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="Имя сотрудника" class="wage-name form-input px-3 py-2 rounded-lg">
|
||||
<input type="number" step="0.01" placeholder="Сумма €" class="wage-amount form-input px-3 py-2 rounded-lg">
|
||||
<input type="text" placeholder="Nombre del empleado" class="wage-name form-input px-3 py-2 rounded-lg">
|
||||
<input type="number" step="0.01" placeholder="Cantidad €" 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>
|
||||
@@ -1412,8 +1425,8 @@ function addExpenseRow() {
|
||||
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="Название расхода" class="expense-name form-input px-3 py-2 rounded-lg">
|
||||
<input type="number" step="0.01" placeholder="Сумма €" class="expense-amount form-input px-3 py-2 rounded-lg">
|
||||
<input type="text" placeholder="Nombre del gasto" class="expense-name form-input px-3 py-2 rounded-lg">
|
||||
<input type="number" step="0.01" placeholder="Cantidad €" 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>
|
||||
@@ -1553,7 +1566,7 @@ document.getElementById("reportForm").addEventListener("submit", async (e) => {
|
||||
}
|
||||
// if result is missing --> error
|
||||
if (!result) {
|
||||
showNotification("Нет ответа от сервера. Попробуйте еще раз.", "error");
|
||||
showNotification("No hay respuesta del servidor. Inténtalo de nuevo.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1578,14 +1591,14 @@ document.getElementById("reportForm").addEventListener("submit", async (e) => {
|
||||
upsertTodaysReport(newReport);
|
||||
|
||||
showNotification(
|
||||
wasEdit ? "Отчет успешно отредактирован!" : "Отчет успешно создан!"
|
||||
wasEdit ? "¡Informe editado con éxito!" : "¡Informe creado con éxito!"
|
||||
);
|
||||
|
||||
await loadReports();
|
||||
} else {
|
||||
resetReportForm();
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
showNotification(result.error || "Ошибка создания отчета", "error");
|
||||
showNotification(result.error || "Error al crear el informe", "error");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1619,7 +1632,7 @@ function resetReportForm() {
|
||||
document.getElementById("todayReportBtn").addEventListener("click", () => {
|
||||
const storeId = document.getElementById("storeSelect").value;
|
||||
if (!storeId) {
|
||||
showNotification("Пожалуйста, выберите магазин!", "error");
|
||||
showNotification("¡Por favor seleccione una tienda!", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1633,7 +1646,7 @@ document.getElementById("todayReportBtn").addEventListener("click", () => {
|
||||
if (todaysReport) {
|
||||
showReportModal(todaysReport, false);
|
||||
} else {
|
||||
showNotification("Сегодняшний отчет еще не создан", "error");
|
||||
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 = "";
|
||||
@@ -1671,9 +1684,9 @@ async function loadUsers() {
|
||||
const store = (appState.storesList || []).find(
|
||||
(s) => s.id === storeId
|
||||
);
|
||||
return store ? store.name : "Нет доступа";
|
||||
return store ? store.name : "Sin acceso";
|
||||
})
|
||||
.join(", ") || "Нет доступа";
|
||||
.join(", ") || "Sin acceso";
|
||||
|
||||
const row = document.createElement("tr");
|
||||
row.className = "hover:bg-gray-50";
|
||||
@@ -1686,7 +1699,7 @@ async function loadUsers() {
|
||||
? "bg-purple-100 text-purple-800"
|
||||
: "bg-blue-100 text-blue-800"
|
||||
}">
|
||||
${user.role === "admin" ? "Администратор" : "Сотрудник"}
|
||||
${user.role === "admin" ? "Administrador" : "Empleado"}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-900">${userStores}</td>
|
||||
@@ -1694,12 +1707,12 @@ async function loadUsers() {
|
||||
<button class="text-blue-600 hover:text-blue-900 mr-2" onclick="editUser(${
|
||||
user.id
|
||||
})">
|
||||
<i class="fas fa-edit"></i> Редактировать
|
||||
<i class="fas fa-edit"></i> Editar
|
||||
</button>
|
||||
<button class="text-red-600 hover:text-red-900" onclick="deleteUser(${
|
||||
user.id
|
||||
})">
|
||||
<i class="fas fa-trash"></i> Удалить
|
||||
<i class="fas fa-trash"></i> Eliminar
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
@@ -1707,7 +1720,7 @@ async function loadUsers() {
|
||||
});
|
||||
|
||||
if (users.length === 0) {
|
||||
showNotification("Нет пользователей для отображения", "info");
|
||||
showNotification("No hay usuarios para mostrar", "info");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1726,8 +1739,8 @@ function showUserEditModal(user) {
|
||||
|
||||
// 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");
|
||||
@@ -1794,7 +1807,7 @@ async function saveUser() {
|
||||
).map((cb) => parseInt(cb.value));
|
||||
|
||||
if (!login) {
|
||||
showNotification("Заполните логин!", "error");
|
||||
showNotification("¡Complete el nombre de usuario!", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1810,19 +1823,19 @@ async function saveUser() {
|
||||
let result;
|
||||
if (!appState.editingUserId) {
|
||||
if (!password) {
|
||||
showNotification("Укажите пароль для нового пользователя!", "error");
|
||||
showNotification("¡Especifique una contraseña para el nuevo usuario!", "error");
|
||||
return;
|
||||
}
|
||||
// CREATE
|
||||
result = await createUser(userData);
|
||||
if (result.success) {
|
||||
showNotification("Пользователь добавлен!");
|
||||
showNotification("¡Usuario agregado!");
|
||||
}
|
||||
} else {
|
||||
// EDIT
|
||||
result = await updateUser(appState.editingUserId, userData);
|
||||
if (result.success) {
|
||||
showNotification("Пользователь обновлен!");
|
||||
showNotification("¡Usuario actualizado!");
|
||||
}
|
||||
appState.editingUserId = null;
|
||||
}
|
||||
@@ -1834,13 +1847,13 @@ async function saveUser() {
|
||||
updateDashboard();
|
||||
loadReports();
|
||||
} else if (result) {
|
||||
showNotification(result.error || "Ошибка операции", "error");
|
||||
showNotification(result.error || "Error de operación", "error");
|
||||
}
|
||||
}
|
||||
|
||||
//UI trigger: delete user with modal
|
||||
function deleteUser(userId) {
|
||||
showConfirmModal("Вы уверены, что хотите удалить этого пользователя?", () =>
|
||||
showConfirmModal("¿Está seguro de que desea eliminar este usuario?", () =>
|
||||
apiDeleteUser(userId)
|
||||
);
|
||||
}
|
||||
@@ -1882,7 +1895,7 @@ async function loadStores() {
|
||||
const result = await getStores();
|
||||
|
||||
if (!result.success) {
|
||||
showNotification(result.error || "Ошибка загрузки магазинов", "error");
|
||||
showNotification(result.error || "Error al cargar tiendas", "error");
|
||||
appState.storesList = result.stores;
|
||||
return;
|
||||
}
|
||||
@@ -1907,12 +1920,12 @@ async function loadStores() {
|
||||
<button class="text-blue-600 hover:text-blue-900 mr-2" onclick="editStore(${
|
||||
store.id
|
||||
})">
|
||||
<i class="fas fa-edit"></i> Редактировать
|
||||
<i class="fas fa-edit"></i> Editar
|
||||
</button>
|
||||
<button class="text-red-600 hover:text-red-900" onclick="deleteStore(${
|
||||
store.id
|
||||
})">
|
||||
<i class="fas fa-trash"></i> Удалить
|
||||
<i class="fas fa-trash"></i> Eliminar
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
@@ -1920,7 +1933,7 @@ async function loadStores() {
|
||||
});
|
||||
|
||||
if (stores.length === 0) {
|
||||
showNotification("Нет магазинов для отображения", "info");
|
||||
showNotification("No hay tiendas para mostrar", "info");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,11 +1954,11 @@ function showStoreEditModal() {
|
||||
);
|
||||
|
||||
if (!store) {
|
||||
showNotification("Магазин не найден!", "error");
|
||||
showNotification("¡Tienda no encontrada!", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
title.textContent = "Редактирование магазина";
|
||||
title.textContent = "Editar tienda";
|
||||
document.getElementById("storeName").value = store.name || "";
|
||||
showModal("storeEditModal");
|
||||
}
|
||||
@@ -1953,7 +1966,7 @@ function showStoreEditModal() {
|
||||
async function saveStore() {
|
||||
const name = document.getElementById("storeName").value.trim();
|
||||
if (!name) {
|
||||
showNotification("Заполните название магазина!", "error");
|
||||
showNotification("¡Complete el nombre de la tienda!", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1962,7 +1975,7 @@ async function saveStore() {
|
||||
// Add
|
||||
result = await createStore({ name });
|
||||
if (result.success) {
|
||||
showNotification("Магазин добавлен!");
|
||||
showNotification("¡Tienda agregada!");
|
||||
hideModal("storeEditModal");
|
||||
await loadStores();
|
||||
await loadReports();
|
||||
@@ -1974,7 +1987,7 @@ async function saveStore() {
|
||||
// Edit
|
||||
result = await updateStore(appState.editingStoreId, { name });
|
||||
if (result.success) {
|
||||
showNotification("Магазин обновлен!");
|
||||
showNotification("¡Tienda actualizada!");
|
||||
hideModal("storeEditModal");
|
||||
await loadStores();
|
||||
await loadReports();
|
||||
@@ -1989,9 +2002,9 @@ async function saveStore() {
|
||||
result.status === 409 ||
|
||||
(result.error && result.error.includes("already exists"))
|
||||
) {
|
||||
showNotification("Магазин с таким названием уже существует!", "error");
|
||||
showNotification("¡Ya existe una tienda con este nombre!", "error");
|
||||
} else {
|
||||
showNotification(result.error || "Ошибка сохранения магазина", "error");
|
||||
showNotification(result.error || "Error al guardar tienda", "error");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -2005,10 +2018,10 @@ async function saveStore() {
|
||||
// Удаление магазина
|
||||
function deleteStore(storeId) {
|
||||
const store = appState.storesList.find((s) => s.id === storeId);
|
||||
let message = `Вы уверены, что хотите удалить этот магазин "${store.name}"?`;
|
||||
let message = `¿Está seguro de que desea eliminar esta tienda "${store.name}"?`;
|
||||
|
||||
if (store.reportsCount && store.reportsCount > 0) {
|
||||
message += `\n\nОбратите внимание! У этого магазина ${store.reportsCount} есть связынные с ним отчеты. Они также будут удалены.`;
|
||||
message += `\n\n¡Atención! Esta tienda tiene ${store.reportsCount} informes asociados. También serán eliminados.`;
|
||||
}
|
||||
|
||||
showConfirmModal(message, () => handleDeleteStore(storeId));
|
||||
@@ -2031,7 +2044,7 @@ async function handleDeleteStore(storeId) {
|
||||
document.getElementById("addStoreBtn").onclick = () => {
|
||||
appState.editingStoreId = null;
|
||||
document.getElementById("storeModalTitle").textContent =
|
||||
"Добавление магазина";
|
||||
"Agregar tienda";
|
||||
document.getElementById("storeName").value = "";
|
||||
showModal("storeEditModal");
|
||||
};
|
||||
@@ -2071,9 +2084,9 @@ function loadTodos() {
|
||||
};
|
||||
|
||||
const priorityText = {
|
||||
low: "Низкий",
|
||||
medium: "Средний",
|
||||
high: "Высокий",
|
||||
low: "Bajo",
|
||||
medium: "Medio",
|
||||
high: "Alto",
|
||||
};
|
||||
|
||||
todoItem.innerHTML = `
|
||||
@@ -2128,7 +2141,7 @@ function toggleTodo(todoId) {
|
||||
const todoIndex = database.todos.findIndex((t) => t.id === todoId);
|
||||
database.todos[todoIndex].completed = !database.todos[todoIndex].completed;
|
||||
loadTodos();
|
||||
showNotification("Статус задачи обновлен!");
|
||||
showNotification("¡Estado de tarea actualizado!");
|
||||
}
|
||||
|
||||
// Редактирование TODO
|
||||
@@ -2145,12 +2158,12 @@ function showTodoEditModal() {
|
||||
|
||||
if (appState.editingTodoId) {
|
||||
const todo = database.todos.find((t) => t.id === appState.editingTodoId);
|
||||
title.textContent = "Редактирование задачи";
|
||||
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 = "Добавление задачи";
|
||||
title.textContent = "Agregar tarea";
|
||||
form.reset();
|
||||
}
|
||||
|
||||
@@ -2163,7 +2176,7 @@ function saveTodo() {
|
||||
const priority = document.getElementById("todoPriority").value;
|
||||
|
||||
if (!title) {
|
||||
showNotification("Заполните заголовок!", "error");
|
||||
showNotification("¡Complete el título!", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2175,7 +2188,7 @@ function saveTodo() {
|
||||
database.todos[todoIndex].title = title;
|
||||
database.todos[todoIndex].description = description;
|
||||
database.todos[todoIndex].priority = priority;
|
||||
showNotification("Задача обновлена!");
|
||||
showNotification("¡Tarea actualizada!");
|
||||
} else {
|
||||
// Добавление
|
||||
const newId = Math.max(...database.todos.map((t) => t.id)) + 1;
|
||||
@@ -2187,7 +2200,7 @@ function saveTodo() {
|
||||
completed: false,
|
||||
createdAt: new Date().toISOString().split("T")[0],
|
||||
});
|
||||
showNotification("Задача добавлена!");
|
||||
showNotification("¡Tarea agregada!");
|
||||
}
|
||||
|
||||
hideModal("todoEditModal");
|
||||
@@ -2196,10 +2209,10 @@ function saveTodo() {
|
||||
|
||||
// Удаление TODO
|
||||
function deleteTodo(todoId) {
|
||||
if (confirm("Вы уверены, что хотите удалить эту задачу?")) {
|
||||
if (confirm("¿Está seguro de que desea eliminar esta tarea?")) {
|
||||
database.todos = database.todos.filter((t) => t.id !== todoId);
|
||||
loadTodos();
|
||||
showNotification("Задача удалена!");
|
||||
showNotification("¡Tarea eliminada!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user