diff --git a/frontend/src/features/sidebar/ui/admin/ReportsSection.vue b/frontend/src/features/sidebar/ui/admin/ReportsSection.vue
index 0e60e7c..25e8732 100644
--- a/frontend/src/features/sidebar/ui/admin/ReportsSection.vue
+++ b/frontend/src/features/sidebar/ui/admin/ReportsSection.vue
@@ -82,9 +82,13 @@
layout="total, sizes, prev, pager, next"
>
+
+ Экспортировать в JSON
+
+
@@ -143,6 +147,11 @@
:page-sizes="[5,10,20,50]"
layout="total, sizes, prev, pager, next"
>
+
+
+
+ Экспортировать в JSON
+
@@ -235,8 +244,8 @@ const fetchDataAbonements = () => {
return {
id: item.client.id,
clientName: item.client.name + ' ' + item.client.surname,
- startDate: item.startDate ? item.startDate : 'Не указано',
- endDate: item.endDate ? item.endDate : 'Не указано',
+ startDate: item.startDate ? dayjs(item.startDate).format('DD.MM.YYYY') : 'Не указано',
+ endDate: item.endDate ? dayjs(item.endDate).format('DD.MM.YYYY') : 'Не указано',
status: item.status
}
});
@@ -318,7 +327,7 @@ const fetchDataTrainings = () => {
axiosInstance.get('/admins/statistics/trainings', { params: requestParamsTrainings.value })
.then(res => {
const data = res.data;
- totalTrainings.value = data.count;
+ totalTrainings.value = data.length;
trainingsData.value = data.map((d:any) => {
return {
...d,
@@ -361,6 +370,26 @@ const loadClients = () => {
});
};
+// Функция экспорта данных абонементов в JSON
+const exportAbonementsToJson = () => {
+ const dataStr = JSON.stringify(subscriptions.value, null, 2);
+ const blob = new Blob([dataStr], { type: 'application/json' });
+ const link = document.createElement('a');
+ link.href = URL.createObjectURL(blob);
+ link.download = 'abonements_data.json';
+ link.click();
+};
+
+// Функция экспорта данных по тренингам в JSON
+const exportTrainingsToJson = () => {
+ const dataStr = JSON.stringify(trainingsData.value, null, 2);
+ const blob = new Blob([dataStr], { type: 'application/json' });
+ const link = document.createElement('a');
+ link.href = URL.createObjectURL(blob);
+ link.download = 'trainings_data.json';
+ link.click();
+};
+
onMounted(() => {
loadClients();
const savedTab = localStorage.getItem('currentTab');
diff --git a/frontend/src/features/sidebar/ui/admin/ServiceInfoSection.vue b/frontend/src/features/sidebar/ui/admin/ServiceInfoSection.vue
index 643ee27..6c6c726 100644
--- a/frontend/src/features/sidebar/ui/admin/ServiceInfoSection.vue
+++ b/frontend/src/features/sidebar/ui/admin/ServiceInfoSection.vue
@@ -5,7 +5,6 @@
{{ isFilterOpened ? 'Закрыть фильтрацию и поиск' : 'Открыть фильтрацию и поиск' }}
-
@@ -87,24 +86,18 @@
Сбросить
-
-
-
-
-
-
-
-
-
-
-
+
+ Название: {{ room.name }}
+ Емкость: {{ room.capacity }}
+ Адрес: {{ room.address }}
+ Рабочие дни: {{ room.workingDays }}
+ Часы открытия: {{ room.openingTime }}
+ Часы закрытия: {{ room.closingTime }}
+ Тренеры: {{ room.trainers }}
+ Секции: {{ room.sections }}
+
-
+
-
-
-
- {{ isFilterOpened ? 'Закрыть фильтрацию и поиск' : 'Открыть фильтрацию и поиск' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Сбросить фильтры
-
-
-
-
@@ -113,7 +23,7 @@
Подробнее
- Удалить
+ Удалить
@@ -127,10 +37,11 @@ import UserInfoSection from '@/features/sidebar/ui/admin/sections/UserInfoSectio
import Spacer from '@/shared/components/Spacer.vue';
import axiosInstance from '@/widgets/axios/index.ts';
import { onMounted, ref, computed } from 'vue';
+import { ElNotification } from 'element-plus';
-const isLoading = ref(true)
+const isLoading = ref(true);
const userInfoSectionClicked = ref(false);
-const clickedUser = ref();
+const clickedUser = ref(null);
const users = ref([]);
const isFilterOpened = ref(false);
@@ -190,13 +101,13 @@ onMounted(() => {
users.value = res.data;
}).catch(error => {
console.error('Ошибка при загрузке пользователей:', error);
+ }).finally(() => {
+ isLoading.value = false;
});
-
- isLoading.value = false;
});
// Фильтрация и сортировка
-const filteredSortedUsers = computed
(() => {
+const filteredSortedUsers = computed(() => {
let filtered = users.value;
// Фильтрация по каждому полю
@@ -298,8 +209,60 @@ const filteredSortedUsers = computed(() => {
return filtered;
});
-
-
+// Функция для удаления пользователя
+const handleUserDelete = (user) => {
+ if (user.roles.includes('ROLE_USER')) {
+ // Для пользователей с ролью ROLE_USER проверяем статус абонемента
+ axiosInstance.get(`/clients/${user.id}/subscription`).then((res) => {
+ const subscription = res.data;
+ if (subscription.status === 'active') {
+ ElNotification({
+ title: 'Ошибка',
+ message: 'У этого пользователя есть активный абонемент. Удаление невозможно.',
+ type: 'error'
+ });
+ } else {
+ deleteUser(user);
+ }
+ }).catch((error) => {
+ ElNotification({
+ title: 'Ошибка',
+ message: error.response.data?.errors || 'Неизвестная ошибка',
+ type: 'error'
+ });
+ });
+ } else {
+ deleteUser(user);
+ }
+};
+
+// Функция для отправки запроса на удаление пользователя
+const deleteUser = (user) => {
+ let deleteUrl = '';
+
+ if (user.roles.includes('ROLE_USER')) {
+ deleteUrl = `/clients/${user.id}`;
+ } else if (user.roles.includes('ROLE_TRAINER')) {
+ deleteUrl = `/trainers/${user.id}`;
+ } else if (user.roles.includes('ROLE_ADMIN')) {
+ deleteUrl = `/admins/${user.id}`;
+ }
+
+ axiosInstance.delete(deleteUrl).then(() => {
+ ElNotification({
+ title: 'Успех',
+ message: `Пользователь ${user.name} ${user.surname} успешно удалён.`,
+ type: 'success'
+ });
+ // Обновить список пользователей
+ users.value = users.value.filter(u => u.id !== user.id);
+ }).catch((error) => {
+ ElNotification({
+ title: 'Ошибка',
+ message: error.response.data?.errors || 'Неизвестная ошибка',
+ type: 'error'
+ });
+ });
+};
+
diff --git a/frontend/src/features/sidebar/ui/client/AbonementSection.vue b/frontend/src/features/sidebar/ui/client/AbonementSection.vue
index 4bc2eee..2bf972a 100644
--- a/frontend/src/features/sidebar/ui/client/AbonementSection.vue
+++ b/frontend/src/features/sidebar/ui/client/AbonementSection.vue
@@ -2,11 +2,9 @@
Информация об абонементе
-
+
- Статус:
+ Статус:
Дата начала: {{ formatDate(userAbonementInfo.startDate) }}
+ Дата начала: {{ formatDate(userAbonementInfo.startDate) }}
- Дата окончания: {{ formatDate(userAbonementInfo.endDate) }}
+ Дата окончания: {{ formatDate(userAbonementInfo.endDate) }}
- Оставшиеся дни: {{ userAbonementInfo.restDays }}
+ Оставшиеся дни: {{ userAbonementInfo.restDays }}
- Длительность: {{ userAbonementInfo.duration }} дней
+ Длительность: {{ userAbonementInfo.duration }} дней
@@ -38,12 +36,9 @@
+
-
+
Продление абонемента
@@ -68,9 +63,7 @@
-
+
{{ userAbonementInfo.status === 'FREEZE' ? 'Вы уверены что хотите разморозить абонемент?' : 'Вы уверены что хотите заморозить абонемент?' }}
@@ -172,6 +165,17 @@ const submitExtend = () => {
};
const submitFreeze = () => {
+ // Проверяем, если оставшиеся дни меньше или равны 7
+ if (userAbonementInfo.value?.restDays <= 7) {
+ ElNotification({
+ title: 'Ошибка',
+ message: 'Заморозка недоступна, так как абонемент истекает менее чем через 7 дней.',
+ type: 'warning',
+ duration: 3000
+ });
+ return; // Не отправляем запрос
+ }
+
axiosInstance
.put(`/subscriptions/${userInfo.value?.id}/freeze`)
.then((res) => {
diff --git a/frontend/src/features/sidebar/ui/client/TrainersAndSections.vue b/frontend/src/features/sidebar/ui/client/TrainersAndSections.vue
index 64b7981..73d3087 100644
--- a/frontend/src/features/sidebar/ui/client/TrainersAndSections.vue
+++ b/frontend/src/features/sidebar/ui/client/TrainersAndSections.vue
@@ -62,7 +62,25 @@
display: 'grid',
gridTemplateColumns: '1fr auto'
}">
-
+
+ {{ `${trainer.name} ${trainer.surname}` }}
+ Подробнее
+
+
+
+ Специализация {{ trainer.sections.join(', ') }}
+
+
+
+ Квалификация: {{ trainer.qualification }}
+
+
+
+ Связь {{ trainer.phoneNumber ?? trainer.email }}
+
+
+
+
+
diff --git a/frontend/src/features/sidebar/ui/trainer/TrainReportsSection.vue b/frontend/src/features/sidebar/ui/trainer/TrainReportsSection.vue
index e32fef8..fe8dfd2 100644
--- a/frontend/src/features/sidebar/ui/trainer/TrainReportsSection.vue
+++ b/frontend/src/features/sidebar/ui/trainer/TrainReportsSection.vue
@@ -1,39 +1,192 @@
Статистика тренировок
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Применить фильтры
+
+
+
+
+
+
+
+ Дата начала не может быть больше даты окончания.
+
+
+
+
+
- Статистика отсутствует
+
+
+
+
\ No newline at end of file
+
diff --git a/frontend/src/features/sidebar/ui/trainer/components/TrainerInfo.vue b/frontend/src/features/sidebar/ui/trainer/components/TrainerInfo.vue
index 8dc1508..70fa539 100644
--- a/frontend/src/features/sidebar/ui/trainer/components/TrainerInfo.vue
+++ b/frontend/src/features/sidebar/ui/trainer/components/TrainerInfo.vue
@@ -1,41 +1,71 @@
-
-
{{ `${trainer.name} ${trainer.surname}` }}
+
+
Имя: {{ trainerData.name }}
-
- Специализация {{ trainer.sections.join(', ') }}
-
+
Фамилия: {{ trainerData.surname }}
-
- Квалификация: {{ trainer.qualification }}
-
+
Email: {{ trainerData.email }}
-
- Связь {{ trainer.phoneNumber ?? trainer.email }}
-
+
Номер телефона: {{ trainerData.phoneNumber }}
+
Квалификация: {{ trainerData.qualification }}
+
+
Часовая ставка: {{ trainerData.hourlyRate }} руб/час
+
+
Опыт: {{ trainerData.experience || "Не указан" }}
+
+
Специализации:
+
+
+
Пол: {{ trainerData.gender === "MALE" ? "Мужской" : "Женский" }}
+
+
Дата рождения: {{ formatDate(trainerData.birthday) }}
\ No newline at end of file
+ ul {
+ margin: 10px 0;
+ padding-left: 20px;
+
+ li {
+ margin: 5px 0;
+ }
+ }
+}
+
diff --git a/frontend/src/pages/Login/LoginPage.vue b/frontend/src/pages/Login/LoginPage.vue
index 48422b7..adaaff6 100644
--- a/frontend/src/pages/Login/LoginPage.vue
+++ b/frontend/src/pages/Login/LoginPage.vue
@@ -46,6 +46,7 @@
diff --git a/frontend/src/shared/components/modals/AssignToTrainModal.vue b/frontend/src/shared/components/modals/AssignToTrainModal.vue
index 0dc4f55..7b90517 100644
--- a/frontend/src/shared/components/modals/AssignToTrainModal.vue
+++ b/frontend/src/shared/components/modals/AssignToTrainModal.vue
@@ -106,12 +106,41 @@ const emits = defineEmits([ 'close' ]);
const selectedTrainingId = ref
(null); // Хранит id выбранной тренировки
// Функция для обработки подтверждения записи на тренировку
+import { ElNotification } from 'element-plus';
+
const handleConfirmAssignToTraining = (training: ITraining) => {
- axiosInstance.post(`/trainings/${training.id}/registration/${userInfo.value?.id}`).then(() => {
- alert('Запись успешна');
- emits('close')
- });
-}
+ axiosInstance.post(`/trainings/${training.id}/registration/${userInfo.value?.id}`)
+ .then(() => {
+ ElNotification.success({
+ title: 'Успех',
+ message: 'Запись успешна',
+ duration: 3000, // Продолжительность уведомления (в миллисекундах)
+ });
+ emits('close');
+ })
+ .catch((error) => {
+ if (error.response) {
+ ElNotification.error({
+ title: 'Ошибка',
+ message: error.response.data?.errors || 'Неизвестная ошибка',
+ duration: 3000,
+ });
+ } else if (error.request) {
+ ElNotification.error({
+ title: 'Ошибка сети',
+ message: 'Нет ответа от сервера',
+ duration: 3000,
+ });
+ } else {
+ ElNotification.error({
+ title: 'Ошибка',
+ message: error.message,
+ duration: 3000,
+ });
+ }
+ });
+};
+
// Переключение состояния выбора тренировки
const toggleSelect = (trainingId: string) => {
diff --git a/frontend/src/shared/components/modals/TrainerInfoModal.vue b/frontend/src/shared/components/modals/TrainerInfoModal.vue
new file mode 100644
index 0000000..d6a0efd
--- /dev/null
+++ b/frontend/src/shared/components/modals/TrainerInfoModal.vue
@@ -0,0 +1,35 @@
+
+
+
+
+ Закрыть
+
+
+
+
+
+
+
\ No newline at end of file