) => {
+ 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) }} |
-
-
@@ -162,28 +148,17 @@
-
-
-
-
-
-
- {{ notification.message }}
-
-
|