From 1c7e05f6a39a5a741324e4789218da40402e8cb6 Mon Sep 17 00:00:00 2001 From: Danil-Bodry Date: Fri, 24 Apr 2026 01:44:03 +0300 Subject: [PATCH] fix frontend --- frontend/src/components/Layout/AppLayout.vue | 27 ++++ frontend/src/composables/useNotification.ts | 43 +++++ frontend/src/locales/en.json | 33 +++- frontend/src/locales/ru.json | 35 ++-- frontend/src/views/AdminSettings.vue | 12 +- frontend/src/views/Dashboard.vue | 3 + frontend/src/views/Profile.vue | 39 +---- frontend/src/views/Restaurants.vue | 160 +++++++------------ frontend/src/views/Users.vue | 103 +++++++----- iiko-app.dev.xserver.su.nginx.conf | 14 +- 10 files changed, 260 insertions(+), 209 deletions(-) create mode 100644 frontend/src/composables/useNotification.ts diff --git a/frontend/src/components/Layout/AppLayout.vue b/frontend/src/components/Layout/AppLayout.vue index 1e7483f..624fa9d 100644 --- a/frontend/src/components/Layout/AppLayout.vue +++ b/frontend/src/components/Layout/AppLayout.vue @@ -191,6 +191,18 @@ + + +
+ + + + + + + {{ notification.message }} +
+
@@ -200,7 +212,9 @@ import { useRoute, useRouter } from 'vue-router' import { useSettingsStore } from '../../stores/settings' import { useUserStore } from '../../stores/user' import { useI18n } from 'vue-i18n' +import { useNotification } from '../../composables/useNotification' +const { notification } = useNotification() const settings = useSettingsStore() const userStore = useUserStore() const route = useRoute() @@ -237,6 +251,7 @@ async function toggleLanguage() { locale.value = newLang localStorage.setItem('locale', newLang) } else { + showNotification('profile.updateError', 'error'); // В случае ошибки всё равно меняем локаль, но не сохраняем в БД locale.value = newLang localStorage.setItem('locale', newLang) @@ -248,3 +263,15 @@ async function toggleLanguage() { } } + + diff --git a/frontend/src/composables/useNotification.ts b/frontend/src/composables/useNotification.ts new file mode 100644 index 0000000..e072e19 --- /dev/null +++ b/frontend/src/composables/useNotification.ts @@ -0,0 +1,43 @@ +import { ref, readonly } from 'vue' +import { useI18n } from 'vue-i18n' + +type NotificationType = 'success' | 'error' + +interface Notification { + show: boolean + type: NotificationType + message: string +} + +const notification = ref({ + show: false, + type: 'success', + message: '' +}) + +let timeoutId: number | null = null + +export function useNotification() { + const { t } = useI18n() + + const showNotification = (messageKey: string, type: NotificationType = 'success', params?: Record) => { + const message = params ? t(messageKey, params) : t(messageKey) + + // Очищаем предыдущий таймер, чтобы уведомление не закрылось раньше времени + if (timeoutId) { + clearTimeout(timeoutId) + timeoutId = null + } + + notification.value = { show: true, type, message } + timeoutId = window.setTimeout(() => { + notification.value.show = false + timeoutId = null + }, 3000) + } + + return { + notification: readonly(notification), + showNotification + } +} diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index bc6a6d5..19f3f09 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -45,7 +45,8 @@ "leavePasswordBlank": "Leave blank to keep current password", "deleteConfirmation": "Are you sure you want to delete this item? This action cannot be undone.", "operationSuccess": "Operation completed successfully", - "operationFailed": "Operation failed" + "operationFailed": "Operation failed", + "networkError": "Network error" }, "dashboard": { "totalUsers": "Total Users", @@ -68,7 +69,8 @@ "new": "New", "today": "Today", "yesterday": "Yesterday", - "daysAgo": "{count} days ago" + "daysAgo": "{count} days ago", + "loadError": "Failed to load dashboard data" }, "users": { "pageName": "Users Management", @@ -76,9 +78,21 @@ "edit": "Edit User", "delete": "Delete User", "you": "(You)", - "cannotChangeOwnRole": "You cannot change your own role", "confirmDelete": "Delete User", - "deleteConfirmation": "Are you sure you want to delete this user? This action cannot be undone." + "deleteConfirmation": "Are you sure you want to delete this user? This action cannot be undone.", + "statusUpdated": "User status updated", + "statusUpdateError": "Failed to update user status", + "passwordRequired": "Password is required for new user", + "createSuccess": "User created successfully", + "createError": "Failed to create user", + "updateSuccess": "User updated successfully", + "updateError": "Failed to update user", + "deleteSuccess": "User deleted", + "deleteError": "Failed to delete user", + "cannotChangeOwnRole": "You cannot change your own role", + "noUsers": "No users found", + "loadError": "Failed to load users", + "loadCurrentError": "Failed to load current user info" }, "restaurants": { "pageName": "Restaurants", @@ -92,7 +106,7 @@ "noRestaurants": "No restaurants found. Click \"Add Restaurant\" to create one.", "deleteConfirmation": "Are you sure you want to delete this restaurant? This action cannot be undone.", "check": "Check connection", - "checkError": "Error", + "checkError": "Check failed: {error}", "loadError": "Failed to load restaurants", "createSuccess": "Restaurant created successfully", "updateSuccess": "Restaurant updated successfully", @@ -109,7 +123,10 @@ "saved": "Settings saved successfully", "saveFailed": "Failed to save settings", "loadFailed": "Failed to load settings metadata", - "enabled": "Enabled" + "enabled": "Enabled", + "saveSuccess": "Settings saved successfully", + "saveError": "Failed to save settings", + "loadMetaError": "Failed to load settings metadata" }, "profile": { "title": "My Profile", @@ -122,9 +139,9 @@ "save": "Save Changes", "reset": "Reset", "role": "Role", - "passwordMismatch": "Passwords do not match", + "passwordsMismatch": "Passwords do not match", "updateSuccess": "Profile updated successfully", - "updateFailed": "Failed to update profile" + "updateError": "Failed to update profile" }, "login": { "title": "Welcome Back", diff --git a/frontend/src/locales/ru.json b/frontend/src/locales/ru.json index 0953af7..4aac035 100644 --- a/frontend/src/locales/ru.json +++ b/frontend/src/locales/ru.json @@ -45,7 +45,8 @@ "leavePasswordBlank": "Оставьте пустым, чтобы оставить текущий пароль", "deleteConfirmation": "Вы уверены, что хотите удалить этот элемент? Это действие необратимо.", "operationSuccess": "Операция выполнена успешно", - "operationFailed": "Операция не удалась" + "operationFailed": "Операция не удалась", + "networkError": "Ошибка сети" }, "dashboard": { "totalUsers": "Всего пользователей", @@ -68,7 +69,8 @@ "new": "Новый", "today": "Сегодня", "yesterday": "Вчера", - "daysAgo": "дн. назад" + "daysAgo": "дн. назад", + "loadError": "Ошибка загрузки данных дашборда" }, "users": { "pageName": "Управление пользователями", @@ -76,9 +78,21 @@ "edit": "Редактировать пользователя", "delete": "Удалить пользователя", "you": "(Вы)", - "cannotChangeOwnRole": "Вы не можете изменить свою собственную роль", "confirmDelete": "Удалить пользователя", - "deleteConfirmation": "Вы уверены, что хотите удалить этого пользователя? Это действие необратимо." + "deleteConfirmation": "Вы уверены, что хотите удалить этого пользователя? Это действие необратимо.", + "statusUpdated": "Статус пользователя обновлён", + "statusUpdateError": "Не удалось обновить статус", + "passwordRequired": "Пароль обязателен для нового пользователя", + "createSuccess": "Пользователь создан", + "createError": "Ошибка создания пользователя", + "updateSuccess": "Пользователь обновлён", + "updateError": "Ошибка обновления пользователя", + "deleteSuccess": "Пользователь удалён", + "deleteError": "Ошибка удаления пользователя", + "cannotChangeOwnRole": "Вы не можете изменить свою роль", + "noUsers": "Пользователи не найдены", + "loadError": "Ошибка загрузки списка пользователей", + "loadCurrentError": "Ошибка загрузки информации о текущем пользователе" }, "restaurants": { "pageName": "Рестораны", @@ -92,7 +106,7 @@ "noRestaurants": "Ресторанов не найдено. Нажмите \"Добавить ресторан\", чтобы создать его.", "deleteConfirmation": "Вы уверены, что хотите удалить этот ресторан? Это действие необратимо.", "check": "Проверить подключение", - "checkError": "Ошибка", + "checkError": "Ошибка проверки: {error}", "loadError": "Ошибка загрузки списка ресторанов", "createSuccess": "Ресторан успешно создан", "updateSuccess": "Ресторан успешно обновлён", @@ -109,7 +123,10 @@ "saved": "Настройки успешно сохранены", "saveFailed": "Не удалось сохранить настройки", "loadFailed": "Не удалось загрузить метаданные настроек", - "enabled": "Включено" + "enabled": "Включено", + "saveSuccess": "Настройки сохранены", + "saveError": "Ошибка сохранения настроек", + "loadMetaError": "Ошибка загрузки метаданных" }, "profile": { "title": "Мой профиль", @@ -122,9 +139,9 @@ "save": "Сохранить изменения", "reset": "Сбросить", "role": "Роль", - "passwordMismatch": "Пароли не совпадают", - "updateSuccess": "Профиль успешно обновлен", - "updateFailed": "Не удалось обновить профиль" + "passwordsMismatch": "Пароли не совпадают", + "updateSuccess": "Профиль обновлён", + "updateError": "Ошибка обновления профиля" }, "login": { "title": "С возвращением", diff --git a/frontend/src/views/AdminSettings.vue b/frontend/src/views/AdminSettings.vue index 331d1ef..647954a 100644 --- a/frontend/src/views/AdminSettings.vue +++ b/frontend/src/views/AdminSettings.vue @@ -70,7 +70,9 @@ import { ref, onMounted } from 'vue'; import AppLayout from '../components/Layout/AppLayout.vue'; import { useI18n } from 'vue-i18n' +import { useNotification } from '../composables/useNotification' +const { showNotification } = useNotification() const { t, locale } = useI18n() interface FieldMeta { key: string; @@ -92,7 +94,7 @@ async function loadMeta() { if (res.ok) { meta.value = await res.json(); } else { - showMessage('Failed to load settings metadata', 'bg-red-50 text-red-800'); + showNotification('settings.loadMetaError', 'error'); } } @@ -101,7 +103,7 @@ async function loadValues() { if (res.ok) { values.value = await res.json(); } else { - showMessage('Failed to load settings values', 'bg-red-50 text-red-800'); + showNotification('settings.loadMetaError', 'error'); } } @@ -117,12 +119,12 @@ async function saveSettings() { body: JSON.stringify(values.value), }); if (res.ok) { - showMessage('Settings saved successfully', 'bg-green-50 text-green-800'); + showNotification('settings.saveSuccess', 'success'); } else { - showMessage('Failed to save settings', 'bg-red-50 text-red-800'); + showNotification('settings.saveError', 'error'); } } catch (e) { - showMessage('Network error', 'bg-red-50 text-red-800'); + showNotification('common.networkError', 'error'); } } diff --git a/frontend/src/views/Dashboard.vue b/frontend/src/views/Dashboard.vue index 5125a11..4d47e41 100644 --- a/frontend/src/views/Dashboard.vue +++ b/frontend/src/views/Dashboard.vue @@ -178,7 +178,9 @@ import { ref, onMounted, onUnmounted } from 'vue'; import AppLayout from '../components/Layout/AppLayout.vue'; import { useI18n } from 'vue-i18n' const { t } = useI18n() +import { useNotification } from '../composables/useNotification' +const { showNotification } = useNotification() const stats = ref({ totalUsers: 0, activeSessions: 0, systemHealth: 100, uptime: '99.9%' }); const userGrowth = ref(12); const sessionGrowth = ref(5); @@ -222,6 +224,7 @@ async function loadDashboardData() { })); } } catch (e) { + showNotification('dashboard.loadError', 'error'); console.error('Failed to load dashboard data', e); } } diff --git a/frontend/src/views/Profile.vue b/frontend/src/views/Profile.vue index c8be17f..9aab7e6 100644 --- a/frontend/src/views/Profile.vue +++ b/frontend/src/views/Profile.vue @@ -77,19 +77,6 @@ - - - -
- - - - - - - {{ notification.message }} -
-
@@ -112,18 +99,10 @@ const form = reactive({ }); const loading = ref(false); -const notification = ref({ show: false, type: 'success', message: '' }); const userInitials = computed(() => (userStore.login[0] || 'U').toUpperCase()); const passwordMismatch = computed(() => !!form.password && form.password !== form.confirmPassword); -function showNotification(message: string, type: 'success' | 'error') { - notification.value = { show: true, type, message }; - setTimeout(() => { - notification.value.show = false; - }, 3000); -} - function resetForm() { form.email = userStore.email; form.password = ''; @@ -133,7 +112,7 @@ function resetForm() { async function saveProfile() { if (form.password && form.password !== form.confirmPassword) { - showNotification('Passwords do not match', 'error'); + showNotification('profile.passwordsMismatch', 'error'); return; } @@ -149,10 +128,10 @@ async function saveProfile() { if (ok) { locale.value = form.language; - showNotification('Profile updated successfully', 'success'); + showNotification('profile.updateSuccess', 'success'); resetForm(); // очищаем поля пароля } else { - showNotification('Failed to update profile', 'error'); + showNotification('profile.updateError', 'error'); } } @@ -161,15 +140,3 @@ onMounted(() => { form.language = userStore.language; }); - - diff --git a/frontend/src/views/Restaurants.vue b/frontend/src/views/Restaurants.vue index 9397913..58bc3f6 100644 --- a/frontend/src/views/Restaurants.vue +++ b/frontend/src/views/Restaurants.vue @@ -31,12 +31,7 @@ {{ rest.host }} @@ -45,13 +40,7 @@ {{ formatDate(rest.created) }}
- - - - - - + {{ rest.checkResult }}
@@ -162,28 +148,17 @@ - - - -
- - - - - - - {{ notification.message }} -
-