Final Update: second variant complete
This commit is contained in:
parent
703adc3326
commit
b92a91ab37
8 changed files with 2939 additions and 62 deletions
1302
control1-2.py
Normal file
1302
control1-2.py
Normal file
File diff suppressed because it is too large
Load diff
902
control2-2.py
Normal file
902
control2-2.py
Normal file
|
|
@ -0,0 +1,902 @@
|
|||
import sys
|
||||
import sqlite3
|
||||
from datetime import datetime, date
|
||||
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
||||
QHBoxLayout, QTabWidget, QTableWidget, QTableWidgetItem,
|
||||
QPushButton, QLabel, QLineEdit, QComboBox, QDateEdit,
|
||||
QTextEdit, QMessageBox, QHeaderView, QGroupBox,
|
||||
QFormLayout, QSpinBox, QCheckBox, QTimeEdit, QProgressBar)
|
||||
from PyQt6.QtCore import Qt, QDate
|
||||
from PyQt6.QtGui import QFont, QPalette, QColor
|
||||
|
||||
class FitnessApp(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.initDB()
|
||||
self.initUI()
|
||||
|
||||
def initDB(self):
|
||||
"""Инициализация базы данных"""
|
||||
self.conn = sqlite3.connect('fitness.db')
|
||||
self.cursor = self.conn.cursor()
|
||||
|
||||
# Создание таблиц
|
||||
self.create_tables()
|
||||
# Заполнение тестовыми данными
|
||||
self.insert_sample_data()
|
||||
|
||||
def create_tables(self):
|
||||
"""Создание таблиц базы данных"""
|
||||
tables = [
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS Users (
|
||||
userID INTEGER PRIMARY KEY,
|
||||
fio TEXT NOT NULL,
|
||||
phone TEXT,
|
||||
email TEXT,
|
||||
login TEXT UNIQUE,
|
||||
password TEXT,
|
||||
userType TEXT,
|
||||
specialization TEXT,
|
||||
birthDate DATE
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS Memberships (
|
||||
membershipID INTEGER PRIMARY KEY,
|
||||
clientID INTEGER,
|
||||
membershipType TEXT,
|
||||
startDate DATE,
|
||||
endDate DATE,
|
||||
visitsTotal INTEGER,
|
||||
visitsUsed INTEGER,
|
||||
zones TEXT,
|
||||
membershipStatus TEXT,
|
||||
cost REAL,
|
||||
adminID INTEGER,
|
||||
FOREIGN KEY (clientID) REFERENCES Users(userID),
|
||||
FOREIGN KEY (adminID) REFERENCES Users(userID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS Visits (
|
||||
visitID INTEGER PRIMARY KEY,
|
||||
clientID INTEGER,
|
||||
visitDate DATE,
|
||||
checkInTime TIME,
|
||||
checkOutTime TIME,
|
||||
zone TEXT,
|
||||
membershipID INTEGER,
|
||||
FOREIGN KEY (clientID) REFERENCES Users(userID),
|
||||
FOREIGN KEY (membershipID) REFERENCES Memberships(membershipID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS GroupClasses (
|
||||
classID INTEGER PRIMARY KEY,
|
||||
className TEXT,
|
||||
trainerID INTEGER,
|
||||
classDate DATE,
|
||||
startTime TIME,
|
||||
endTime TIME,
|
||||
hall TEXT,
|
||||
maxParticipants INTEGER,
|
||||
enrolledParticipants INTEGER,
|
||||
classStatus TEXT,
|
||||
FOREIGN KEY (trainerID) REFERENCES Users(userID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS PersonalTraining (
|
||||
trainingID INTEGER PRIMARY KEY,
|
||||
clientID INTEGER,
|
||||
trainerID INTEGER,
|
||||
trainingDate DATE,
|
||||
startTime TIME,
|
||||
endTime TIME,
|
||||
exercises TEXT,
|
||||
notes TEXT,
|
||||
progressMetrics TEXT,
|
||||
FOREIGN KEY (clientID) REFERENCES Users(userID),
|
||||
FOREIGN KEY (trainerID) REFERENCES Users(userID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS ClassRegistrations (
|
||||
registrationID INTEGER PRIMARY KEY,
|
||||
classID INTEGER,
|
||||
clientID INTEGER,
|
||||
registrationDate DATE,
|
||||
status TEXT,
|
||||
FOREIGN KEY (classID) REFERENCES GroupClasses(classID),
|
||||
FOREIGN KEY (clientID) REFERENCES Users(userID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS EquipmentRequests (
|
||||
requestID INTEGER PRIMARY KEY,
|
||||
trainerID INTEGER,
|
||||
equipment TEXT,
|
||||
quantity INTEGER,
|
||||
requestDate DATE,
|
||||
status TEXT,
|
||||
FOREIGN KEY (trainerID) REFERENCES Users(userID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS ShiftSwaps (
|
||||
swapID INTEGER PRIMARY KEY,
|
||||
trainerID1 INTEGER,
|
||||
trainerID2 INTEGER,
|
||||
shiftDate DATE,
|
||||
status TEXT,
|
||||
FOREIGN KEY (trainerID1) REFERENCES Users(userID),
|
||||
FOREIGN KEY (trainerID2) REFERENCES Users(userID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS Exercises (
|
||||
exerciseID INTEGER PRIMARY KEY,
|
||||
name TEXT,
|
||||
muscleGroup TEXT,
|
||||
difficulty TEXT,
|
||||
description TEXT
|
||||
)
|
||||
"""
|
||||
]
|
||||
|
||||
for table in tables:
|
||||
self.cursor.execute(table)
|
||||
self.conn.commit()
|
||||
|
||||
def insert_sample_data(self):
|
||||
"""Вставка тестовых данных"""
|
||||
# Проверяем, есть ли уже данные
|
||||
self.cursor.execute("SELECT COUNT(*) FROM Users")
|
||||
if self.cursor.fetchone()[0] > 0:
|
||||
return
|
||||
|
||||
users = [
|
||||
(1, 'Сидорова Марина Петровна', '89219014567', 'director@fitness.ru', 'director1', 'pass1', 'Директор', '', '1980-05-15'),
|
||||
(2, 'Романова Анна Сергеевна', '89210125678', 'admin1@fitness.ru', 'admin1', 'pass2', 'Администратор', '', '1992-08-22'),
|
||||
(4, 'Яковлева Елена Викторовна', '89211236789', 'admin2@fitness.ru', 'admin2', 'pass3', 'Администратор', '', '1988-11-10'),
|
||||
(7, 'Петров Дмитрий Александрович', '89212347890', 'petrov@fitness.ru', 'trainer1', 'pass4', 'Тренер', 'Силовые тренировки', '1985-03-18'),
|
||||
(9, 'Смирнова Ольга Игоревна', '89213458901', 'smirnova@fitness.ru', 'trainer2', 'pass5', 'Тренер', 'Йога, Пилатес', '1990-07-25'),
|
||||
(11, 'Козлов Сергей Николаевич', '89214569012', 'kozlov@fitness.ru', 'trainer3', 'pass6', 'Тренер', 'Плавание', '1987-12-05'),
|
||||
(16, 'Федорова Екатерина Дмитриевна', '89161112236', 'fedorova@mail.ru', 'client1', 'pass7', 'Клиент', '', '1995-04-12'),
|
||||
(21, 'Михайлов Алексей Владимирович', '89162223347', 'mikhailov@gmail.com', 'client2', 'pass8', 'Клиент', '', '1988-09-30'),
|
||||
(26, 'Новикова Ирина Сергеевна', '89163334458', 'novikova@yandex.ru', 'client3', 'pass9', 'Клиент', '', '1992-06-18'),
|
||||
(30, 'Соколов Игорь Петрович', '89164445569', 'sokolov@mail.ru', 'client4', 'pass10', 'Клиент', '', '1983-02-28'),
|
||||
(34, 'Павлова Мария Александровна', '89165556670', 'pavlova@gmail.com', 'client5', 'pass11', 'Клиент', '', '1997-11-07')
|
||||
]
|
||||
|
||||
memberships = [
|
||||
(1, 16, 'Месячный безлимит', '2024-06-01', '2024-06-30', 999, 42, 'Зал, Бассейн, Групповые', 'Активен', 5000.00, 2),
|
||||
(2, 21, '12 посещений', '2024-06-05', '2024-09-05', 12, 8, 'Зал', 'Активен', 4000.00, 2),
|
||||
(3, 26, 'Годовой VIP', '2024-01-10', '2025-01-10', 999, 156, 'Все зоны', 'Активен', 45000.00, 4),
|
||||
(4, 30, 'Разовое посещение', '2024-06-15', '2024-06-15', 1, 1, 'Зал', 'Завершен', 500.00, 2),
|
||||
(5, 34, 'Квартальный', '2024-06-01', '2024-08-31', 999, 15, 'Зал, Групповые', 'Активен', 12000.00, 4)
|
||||
]
|
||||
|
||||
visits = [
|
||||
(1, 16, '2024-06-15', '08:30', '10:15', 'Тренажерный зал', 1),
|
||||
(2, 21, '2024-06-15', '09:00', '10:30', 'Тренажерный зал', 2),
|
||||
(3, 26, '2024-06-15', '07:00', '08:30', 'Бассейн', 3),
|
||||
(4, 16, '2024-06-15', '18:00', '19:45', 'Групповое занятие', 1),
|
||||
(5, 34, '2024-06-15', '19:00', '20:30', 'Групповое занятие', 5)
|
||||
]
|
||||
|
||||
group_classes = [
|
||||
(1, 'Йога для начинающих', 9, '2024-06-16', '10:00', '11:00', 'Зал 2', 15, 12, 'Запланировано'),
|
||||
(2, 'Силовая аэробика', 7, '2024-06-16', '18:00', '19:00', 'Зал 1', 20, 18, 'Запланировано'),
|
||||
(3, 'Пилатес', 9, '2024-06-17', '11:00', '12:00', 'Зал 2', 12, 12, 'Группа заполнена'),
|
||||
(4, 'Аквааэробика', 11, '2024-06-17', '15:00', '16:00', 'Бассейн', 10, 7, 'Запланировано'),
|
||||
(5, 'Бокс', 7, '2024-06-18', '19:00', '20:30', 'Зал 3', 8, 5, 'Запланировано')
|
||||
]
|
||||
|
||||
personal_training = [
|
||||
(1, 26, 7, '2024-06-14', '16:00', '17:00', 'Жим лежа, Приседания, Тяга блока', 'Хорошая техника', 'Жим 80кг x 8'),
|
||||
(2, 16, 9, '2024-06-13', '10:00', '11:00', 'Асаны йоги, Растяжка', 'Улучшилась гибкость', ''),
|
||||
(3, 21, 7, '2024-06-12', '14:00', '15:00', 'Становая тяга, Жим гантелей', 'Нужно работать над техникой', 'Становая 60кг x 6')
|
||||
]
|
||||
|
||||
exercises = [
|
||||
(1, 'Жим лежа', 'Грудь', 'Средний', 'Упражнение для развития грудных мышц'),
|
||||
(2, 'Приседания', 'Ноги', 'Начальный', 'Базовое упражнение для ног'),
|
||||
(3, 'Тяга блока', 'Спина', 'Средний', 'Упражнение для развития широчайших мышц'),
|
||||
(4, 'Асаны йоги', 'Все тело', 'Начальный', 'Позы для развития гибкости'),
|
||||
(5, 'Становая тяга', 'Спина, Ноги', 'Продвинутый', 'Базовое упражнение для спины и ног')
|
||||
]
|
||||
|
||||
# Вставка данных
|
||||
self.cursor.executemany("INSERT INTO Users VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", users)
|
||||
self.cursor.executemany("INSERT INTO Memberships VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", memberships)
|
||||
self.cursor.executemany("INSERT INTO Visits VALUES (?, ?, ?, ?, ?, ?, ?)", visits)
|
||||
self.cursor.executemany("INSERT INTO GroupClasses VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", group_classes)
|
||||
self.cursor.executemany("INSERT INTO PersonalTraining VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", personal_training)
|
||||
self.cursor.executemany("INSERT INTO Exercises VALUES (?, ?, ?, ?, ?)", exercises)
|
||||
self.conn.commit()
|
||||
|
||||
def initUI(self):
|
||||
"""Инициализация пользовательского интерфейса"""
|
||||
self.setWindowTitle('Фитнес-клуб - Система управления (Вариант 2)')
|
||||
self.setGeometry(100, 100, 1200, 800)
|
||||
|
||||
# Центральный виджет с вкладками для разных ролей
|
||||
self.tabs = QTabWidget()
|
||||
|
||||
# Вкладка для администратора
|
||||
self.admin_tab = QWidget()
|
||||
self.init_admin_tab()
|
||||
self.tabs.addTab(self.admin_tab, "Администратор")
|
||||
|
||||
# Вкладка для тренера
|
||||
self.trainer_tab = QWidget()
|
||||
self.init_trainer_tab()
|
||||
self.tabs.addTab(self.trainer_tab, "Тренер")
|
||||
|
||||
# Вкладка для директора
|
||||
self.director_tab = QWidget()
|
||||
self.init_director_tab()
|
||||
self.tabs.addTab(self.director_tab, "Директор")
|
||||
|
||||
self.setCentralWidget(self.tabs)
|
||||
|
||||
def init_admin_tab(self):
|
||||
"""Инициализация вкладки администратора"""
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Группа управления расписанием
|
||||
schedule_group = QGroupBox("Управление расписанием групповых занятий")
|
||||
schedule_layout = QVBoxLayout()
|
||||
|
||||
# Таблица групповых занятий
|
||||
self.classes_table = QTableWidget()
|
||||
self.classes_table.setColumnCount(10)
|
||||
self.classes_table.setHorizontalHeaderLabels([
|
||||
'ID', 'Название', 'Тренер', 'Дата', 'Время начала',
|
||||
'Время окончания', 'Зал', 'Макс. участников', 'Записано', 'Статус'
|
||||
])
|
||||
self.classes_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
||||
schedule_layout.addWidget(self.classes_table)
|
||||
|
||||
# Кнопки управления
|
||||
btn_layout = QHBoxLayout()
|
||||
self.add_class_btn = QPushButton("Добавить занятие")
|
||||
self.edit_class_btn = QPushButton("Редактировать")
|
||||
self.delete_class_btn = QPushButton("Удалить")
|
||||
self.assign_trainer_btn = QPushButton("Назначить тренера")
|
||||
|
||||
btn_layout.addWidget(self.add_class_btn)
|
||||
btn_layout.addWidget(self.edit_class_btn)
|
||||
btn_layout.addWidget(self.delete_class_btn)
|
||||
btn_layout.addWidget(self.assign_trainer_btn)
|
||||
|
||||
schedule_layout.addLayout(btn_layout)
|
||||
schedule_group.setLayout(schedule_layout)
|
||||
layout.addWidget(schedule_group)
|
||||
|
||||
# Группа управления абонементами
|
||||
membership_group = QGroupBox("Управление абонементами")
|
||||
membership_layout = QVBoxLayout()
|
||||
|
||||
self.memberships_table = QTableWidget()
|
||||
self.memberships_table.setColumnCount(8)
|
||||
self.memberships_table.setHorizontalHeaderLabels([
|
||||
'ID', 'Клиент', 'Тип', 'Начало', 'Окончание',
|
||||
'Использовано/Всего', 'Статус', 'Стоимость'
|
||||
])
|
||||
self.memberships_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
||||
membership_layout.addWidget(self.memberships_table)
|
||||
|
||||
# Кнопки для управления абонементами
|
||||
membership_btn_layout = QHBoxLayout()
|
||||
self.change_price_btn = QPushButton("Изменить стоимость")
|
||||
self.freeze_membership_btn = QPushButton("Заморозить абонемент")
|
||||
self.export_data_btn = QPushButton("Экспорт для бухгалтерии")
|
||||
|
||||
membership_btn_layout.addWidget(self.change_price_btn)
|
||||
membership_btn_layout.addWidget(self.freeze_membership_btn)
|
||||
membership_btn_layout.addWidget(self.export_data_btn)
|
||||
membership_layout.addLayout(membership_btn_layout)
|
||||
|
||||
membership_group.setLayout(membership_layout)
|
||||
layout.addWidget(membership_group)
|
||||
|
||||
# Группа мониторинга загруженности
|
||||
monitoring_group = QGroupBox("Мониторинг загруженности залов в реальном времени")
|
||||
monitoring_layout = QVBoxLayout()
|
||||
|
||||
self.hall_occupancy_table = QTableWidget()
|
||||
self.hall_occupancy_table.setColumnCount(3)
|
||||
self.hall_occupancy_table.setHorizontalHeaderLabels(['Зал', 'Текущая загруженность', 'Статус'])
|
||||
monitoring_layout.addWidget(self.hall_occupancy_table)
|
||||
|
||||
monitoring_group.setLayout(monitoring_layout)
|
||||
layout.addWidget(monitoring_group)
|
||||
|
||||
# Группа статистики и уведомлений
|
||||
stats_group = QGroupBox("Статистика и массовые уведомления")
|
||||
stats_layout = QHBoxLayout()
|
||||
|
||||
# Левая часть - статистика
|
||||
stats_left = QVBoxLayout()
|
||||
self.attendance_stats = QTextEdit()
|
||||
self.attendance_stats.setMaximumHeight(150)
|
||||
stats_left.addWidget(QLabel("Статистика посещаемости:"))
|
||||
stats_left.addWidget(self.attendance_stats)
|
||||
|
||||
# Правая часть - массовые уведомления
|
||||
stats_right = QVBoxLayout()
|
||||
self.mass_notification_text = QTextEdit()
|
||||
self.mass_notification_text.setMaximumHeight(100)
|
||||
self.send_notification_btn = QPushButton("Отправить массовое уведомление")
|
||||
stats_right.addWidget(QLabel("Массовое уведомление:"))
|
||||
stats_right.addWidget(self.mass_notification_text)
|
||||
stats_right.addWidget(self.send_notification_btn)
|
||||
|
||||
stats_layout.addLayout(stats_left)
|
||||
stats_layout.addLayout(stats_right)
|
||||
stats_group.setLayout(stats_layout)
|
||||
layout.addWidget(stats_group)
|
||||
|
||||
# Загрузка данных
|
||||
self.load_classes_data()
|
||||
self.load_memberships_data()
|
||||
self.load_hall_occupancy()
|
||||
self.load_attendance_stats()
|
||||
|
||||
# Подключение сигналов
|
||||
self.add_class_btn.clicked.connect(self.add_class)
|
||||
self.assign_trainer_btn.clicked.connect(self.assign_trainer)
|
||||
self.change_price_btn.clicked.connect(self.change_membership_price)
|
||||
self.freeze_membership_btn.clicked.connect(self.freeze_membership)
|
||||
self.export_data_btn.clicked.connect(self.export_accounting_data)
|
||||
self.send_notification_btn.clicked.connect(self.send_mass_notification)
|
||||
|
||||
self.admin_tab.setLayout(layout)
|
||||
|
||||
def init_trainer_tab(self):
|
||||
"""Инициализация вкладки тренера"""
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Группа программ тренировок
|
||||
programs_group = QGroupBox("Индивидуальные программы тренировок")
|
||||
programs_layout = QVBoxLayout()
|
||||
|
||||
self.programs_table = QTableWidget()
|
||||
self.programs_table.setColumnCount(6)
|
||||
self.programs_table.setHorizontalHeaderLabels([
|
||||
'ID', 'Клиент', 'Дата', 'Упражнения', 'Заметки', 'Прогресс'
|
||||
])
|
||||
self.programs_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
||||
programs_layout.addWidget(self.programs_table)
|
||||
|
||||
programs_btn_layout = QHBoxLayout()
|
||||
self.add_program_btn = QPushButton("Создать программу")
|
||||
self.edit_program_btn = QPushButton("Редактировать")
|
||||
self.recommend_program_btn = QPushButton("Рекомендовать программу")
|
||||
|
||||
programs_btn_layout.addWidget(self.add_program_btn)
|
||||
programs_btn_layout.addWidget(self.edit_program_btn)
|
||||
programs_btn_layout.addWidget(self.recommend_program_btn)
|
||||
programs_layout.addLayout(programs_btn_layout)
|
||||
|
||||
programs_group.setLayout(programs_layout)
|
||||
layout.addWidget(programs_group)
|
||||
|
||||
# Группа базы упражнений
|
||||
exercises_group = QGroupBox("База упражнений")
|
||||
exercises_layout = QVBoxLayout()
|
||||
|
||||
self.exercises_table = QTableWidget()
|
||||
self.exercises_table.setColumnCount(5)
|
||||
self.exercises_table.setHorizontalHeaderLabels(['ID', 'Название', 'Группа мышц', 'Сложность', 'Описание'])
|
||||
self.exercises_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
||||
exercises_layout.addWidget(self.exercises_table)
|
||||
|
||||
exercises_btn_layout = QHBoxLayout()
|
||||
self.add_exercise_btn = QPushButton("Добавить упражнение")
|
||||
self.edit_exercise_btn = QPushButton("Редактировать")
|
||||
|
||||
exercises_btn_layout.addWidget(self.add_exercise_btn)
|
||||
exercises_btn_layout.addWidget(self.edit_exercise_btn)
|
||||
exercises_layout.addLayout(exercises_btn_layout)
|
||||
|
||||
exercises_group.setLayout(exercises_layout)
|
||||
layout.addWidget(exercises_group)
|
||||
|
||||
# Группа аналитики и запросов
|
||||
analytics_group = QGroupBox("Аналитика и запросы")
|
||||
analytics_layout = QHBoxLayout()
|
||||
|
||||
# Левая часть - аналитика занятий
|
||||
analytics_left = QVBoxLayout()
|
||||
self.class_attendance_stats = QTextEdit()
|
||||
self.class_attendance_stats.setMaximumHeight(120)
|
||||
analytics_left.addWidget(QLabel("Аналитика посещаемости занятий:"))
|
||||
analytics_left.addWidget(self.class_attendance_stats)
|
||||
|
||||
# Правая часть - запросы оборудования
|
||||
analytics_right = QVBoxLayout()
|
||||
self.equipment_table = QTableWidget()
|
||||
self.equipment_table.setColumnCount(5)
|
||||
self.equipment_table.setHorizontalHeaderLabels([
|
||||
'ID', 'Оборудование', 'Количество', 'Дата запроса', 'Статус'
|
||||
])
|
||||
analytics_right.addWidget(QLabel("Запросы оборудования:"))
|
||||
analytics_right.addWidget(self.equipment_table)
|
||||
|
||||
equipment_btn_layout = QHBoxLayout()
|
||||
self.request_equipment_btn = QPushButton("Запросить оборудование")
|
||||
equipment_btn_layout.addWidget(self.request_equipment_btn)
|
||||
analytics_right.addLayout(equipment_btn_layout)
|
||||
|
||||
analytics_layout.addLayout(analytics_left)
|
||||
analytics_layout.addLayout(analytics_right)
|
||||
analytics_group.setLayout(analytics_layout)
|
||||
layout.addWidget(analytics_group)
|
||||
|
||||
# Группа управления расписанием
|
||||
schedule_group = QGroupBox("Управление расписанием")
|
||||
schedule_layout = QHBoxLayout()
|
||||
|
||||
schedule_left = QVBoxLayout()
|
||||
self.trainer_schedule_table = QTableWidget()
|
||||
self.trainer_schedule_table.setColumnCount(5)
|
||||
self.trainer_schedule_table.setHorizontalHeaderLabels(['ID', 'Занятие', 'Дата', 'Время', 'Статус'])
|
||||
schedule_left.addWidget(QLabel("Мое расписание:"))
|
||||
schedule_left.addWidget(self.trainer_schedule_table)
|
||||
|
||||
schedule_right = QVBoxLayout()
|
||||
self.block_time_btn = QPushButton("Заблокировать время")
|
||||
self.swap_shift_btn = QPushButton("Обменяться сменой")
|
||||
self.view_bonuses_btn = QPushButton("Просмотреть бонусы")
|
||||
|
||||
schedule_right.addWidget(self.block_time_btn)
|
||||
schedule_right.addWidget(self.swap_shift_btn)
|
||||
schedule_right.addWidget(self.view_bonuses_btn)
|
||||
schedule_right.addStretch()
|
||||
|
||||
schedule_layout.addLayout(schedule_left)
|
||||
schedule_layout.addLayout(schedule_right)
|
||||
schedule_group.setLayout(schedule_layout)
|
||||
layout.addWidget(schedule_group)
|
||||
|
||||
# Загрузка данных
|
||||
self.load_programs_data()
|
||||
self.load_exercises_data()
|
||||
self.load_equipment_data()
|
||||
self.load_trainer_schedule()
|
||||
self.load_class_attendance_stats()
|
||||
|
||||
# Подключение сигналов
|
||||
self.add_program_btn.clicked.connect(self.add_training_program)
|
||||
self.add_exercise_btn.clicked.connect(self.add_exercise)
|
||||
self.request_equipment_btn.clicked.connect(self.request_equipment)
|
||||
self.block_time_btn.clicked.connect(self.block_time)
|
||||
self.swap_shift_btn.clicked.connect(self.swap_shift)
|
||||
self.view_bonuses_btn.clicked.connect(self.view_bonuses)
|
||||
|
||||
self.trainer_tab.setLayout(layout)
|
||||
|
||||
def init_director_tab(self):
|
||||
"""Инициализация вкладки директора"""
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Общая статистика
|
||||
overall_stats_group = QGroupBox("Общая статистика клуба")
|
||||
overall_layout = QHBoxLayout()
|
||||
|
||||
# Левая часть - ключевые показатели
|
||||
metrics_layout = QFormLayout()
|
||||
|
||||
self.total_members_label = QLabel("0")
|
||||
self.active_members_label = QLabel("0")
|
||||
self.monthly_revenue_label = QLabel("0 руб.")
|
||||
self.attendance_rate_label = QLabel("0%")
|
||||
self.avg_rating_label = QLabel("0.0")
|
||||
self.employee_count_label = QLabel("0")
|
||||
|
||||
# Стилизация метрик
|
||||
for label in [self.total_members_label, self.active_members_label,
|
||||
self.monthly_revenue_label, self.attendance_rate_label,
|
||||
self.avg_rating_label, self.employee_count_label]:
|
||||
label.setStyleSheet("font-weight: bold; font-size: 14px; color: #2E86AB;")
|
||||
|
||||
metrics_layout.addRow("Всего клиентов:", self.total_members_label)
|
||||
metrics_layout.addRow("Активных абонементов:", self.active_members_label)
|
||||
metrics_layout.addRow("Доход за месяц:", self.monthly_revenue_label)
|
||||
metrics_layout.addRow("Средняя посещаемость:", self.attendance_rate_label)
|
||||
metrics_layout.addRow("Средняя оценка:", self.avg_rating_label)
|
||||
metrics_layout.addRow("Сотрудников:", self.employee_count_label)
|
||||
|
||||
overall_layout.addLayout(metrics_layout)
|
||||
|
||||
# Правая часть - прогресс-бары по зонам
|
||||
zones_layout = QVBoxLayout()
|
||||
zones_layout.addWidget(QLabel("Загруженность зон:"))
|
||||
|
||||
self.gym_usage_bar = QProgressBar()
|
||||
self.pool_usage_bar = QProgressBar()
|
||||
self.group_usage_bar = QProgressBar()
|
||||
|
||||
self.gym_usage_bar.setFormat("Тренажерный зал: %p%")
|
||||
self.pool_usage_bar.setFormat("Бассейн: %p%")
|
||||
self.group_usage_bar.setFormat("Групповые занятия: %p%")
|
||||
|
||||
zones_layout.addWidget(self.gym_usage_bar)
|
||||
zones_layout.addWidget(self.pool_usage_bar)
|
||||
zones_layout.addWidget(self.group_usage_bar)
|
||||
|
||||
overall_layout.addLayout(zones_layout)
|
||||
overall_stats_group.setLayout(overall_layout)
|
||||
layout.addWidget(overall_stats_group)
|
||||
|
||||
# Эффективность тренеров
|
||||
trainers_group = QGroupBox("Эффективность тренеров")
|
||||
trainers_layout = QVBoxLayout()
|
||||
|
||||
self.trainers_table = QTableWidget()
|
||||
self.trainers_table.setColumnCount(6)
|
||||
self.trainers_table.setHorizontalHeaderLabels([
|
||||
'Тренер', 'Групповые занятия', 'Персональные тренировки',
|
||||
'Средняя оценка', 'Доход', 'Бонусы'
|
||||
])
|
||||
self.trainers_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
||||
trainers_layout.addWidget(self.trainers_table)
|
||||
|
||||
trainers_group.setLayout(trainers_layout)
|
||||
layout.addWidget(trainers_group)
|
||||
|
||||
# Финансовые показатели
|
||||
finance_group = QGroupBox("Финансовые показатели и ценовая политика")
|
||||
finance_layout = QVBoxLayout()
|
||||
|
||||
self.finance_table = QTableWidget()
|
||||
self.finance_table.setColumnCount(4)
|
||||
self.finance_table.setHorizontalHeaderLabels([
|
||||
'Период', 'Доход', 'Расходы', 'Прибыль'
|
||||
])
|
||||
self.finance_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
||||
finance_layout.addWidget(self.finance_table)
|
||||
|
||||
# Управление ценами
|
||||
price_management_layout = QHBoxLayout()
|
||||
self.membership_type_combo = QComboBox()
|
||||
self.new_price_input = QLineEdit()
|
||||
self.update_price_btn = QPushButton("Обновить цену")
|
||||
|
||||
self.membership_type_combo.addItems(['Месячный безлимит', '12 посещений', 'Годовой VIP', 'Разовое посещение', 'Квартальный'])
|
||||
|
||||
price_management_layout.addWidget(QLabel("Тип абонемента:"))
|
||||
price_management_layout.addWidget(self.membership_type_combo)
|
||||
price_management_layout.addWidget(QLabel("Новая цена:"))
|
||||
price_management_layout.addWidget(self.new_price_input)
|
||||
price_management_layout.addWidget(self.update_price_btn)
|
||||
|
||||
finance_layout.addLayout(price_management_layout)
|
||||
finance_group.setLayout(finance_layout)
|
||||
layout.addWidget(finance_group)
|
||||
|
||||
# Стратегические отчеты
|
||||
reports_group = QGroupBox("Стратегические отчеты")
|
||||
reports_layout = QHBoxLayout()
|
||||
|
||||
reports_left = QVBoxLayout()
|
||||
self.report_type_combo = QComboBox()
|
||||
self.report_type_combo.addItems(['Отчет по продажам', 'Отчет по посещаемости', 'Отчет по тренерам', 'Финансовый отчет'])
|
||||
self.generate_report_btn = QPushButton("Сформировать отчет")
|
||||
self.report_output = QTextEdit()
|
||||
|
||||
reports_left.addWidget(QLabel("Тип отчета:"))
|
||||
reports_left.addWidget(self.report_type_combo)
|
||||
reports_left.addWidget(self.generate_report_btn)
|
||||
reports_left.addWidget(QLabel("Результат:"))
|
||||
reports_left.addWidget(self.report_output)
|
||||
|
||||
reports_right = QVBoxLayout()
|
||||
self.hire_staff_btn = QPushButton("Принять сотрудника")
|
||||
self.staff_analytics_btn = QPushButton("Аналитика персонала")
|
||||
|
||||
reports_right.addWidget(self.hire_staff_btn)
|
||||
reports_right.addWidget(self.staff_analytics_btn)
|
||||
reports_right.addStretch()
|
||||
|
||||
reports_layout.addLayout(reports_left)
|
||||
reports_layout.addLayout(reports_right)
|
||||
reports_group.setLayout(reports_layout)
|
||||
layout.addWidget(reports_group)
|
||||
|
||||
# Загрузка данных
|
||||
self.load_director_data()
|
||||
|
||||
# Подключение сигналов
|
||||
self.update_price_btn.clicked.connect(self.update_membership_price)
|
||||
self.generate_report_btn.clicked.connect(self.generate_strategic_report)
|
||||
self.hire_staff_btn.clicked.connect(self.hire_staff)
|
||||
self.staff_analytics_btn.clicked.connect(self.staff_analytics)
|
||||
|
||||
self.director_tab.setLayout(layout)
|
||||
|
||||
def load_classes_data(self):
|
||||
"""Загрузка данных о групповых занятиях"""
|
||||
self.cursor.execute("""
|
||||
SELECT gc.classID, gc.className, u.fio, gc.classDate, gc.startTime,
|
||||
gc.endTime, gc.hall, gc.maxParticipants, gc.enrolledParticipants, gc.classStatus
|
||||
FROM GroupClasses gc
|
||||
LEFT JOIN Users u ON gc.trainerID = u.userID
|
||||
ORDER BY gc.classDate, gc.startTime
|
||||
""")
|
||||
classes = self.cursor.fetchall()
|
||||
|
||||
self.classes_table.setRowCount(len(classes))
|
||||
for row, class_data in enumerate(classes):
|
||||
for col, data in enumerate(class_data):
|
||||
self.classes_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def load_memberships_data(self):
|
||||
"""Загрузка данных об абонементах"""
|
||||
self.cursor.execute("""
|
||||
SELECT m.membershipID, u.fio, m.membershipType, m.startDate, m.endDate,
|
||||
m.visitsUsed || '/' || m.visitsTotal, m.membershipStatus, m.cost
|
||||
FROM Memberships m
|
||||
JOIN Users u ON m.clientID = u.userID
|
||||
ORDER BY m.endDate
|
||||
""")
|
||||
memberships = self.cursor.fetchall()
|
||||
|
||||
self.memberships_table.setRowCount(len(memberships))
|
||||
for row, membership_data in enumerate(memberships):
|
||||
for col, data in enumerate(membership_data):
|
||||
self.memberships_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def load_hall_occupancy(self):
|
||||
"""Загрузка данных о загруженности залов"""
|
||||
# Имитация данных о загруженности
|
||||
halls = [
|
||||
('Зал 1', '15/20 (75%)', 'Средняя загруженность'),
|
||||
('Зал 2', '8/15 (53%)', 'Низкая загруженность'),
|
||||
('Зал 3', '12/12 (100%)', 'Полная загруженность'),
|
||||
('Бассейн', '6/10 (60%)', 'Средняя загруженность')
|
||||
]
|
||||
|
||||
self.hall_occupancy_table.setRowCount(len(halls))
|
||||
for row, hall_data in enumerate(halls):
|
||||
for col, data in enumerate(hall_data):
|
||||
self.hall_occupancy_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def load_attendance_stats(self):
|
||||
"""Загрузка статистики посещаемости"""
|
||||
self.cursor.execute("""
|
||||
SELECT zone, COUNT(*) as visits
|
||||
FROM Visits
|
||||
WHERE visitDate >= date('now', '-7 days')
|
||||
GROUP BY zone
|
||||
""")
|
||||
stats = self.cursor.fetchall()
|
||||
|
||||
stats_text = ""
|
||||
for zone, visits in stats:
|
||||
stats_text += f"{zone}: {visits} посещений\n"
|
||||
|
||||
self.attendance_stats.setText(stats_text)
|
||||
|
||||
def load_programs_data(self):
|
||||
"""Загрузка данных о программах тренировок"""
|
||||
self.cursor.execute("""
|
||||
SELECT pt.trainingID, u.fio, pt.trainingDate, pt.exercises, pt.notes, pt.progressMetrics
|
||||
FROM PersonalTraining pt
|
||||
JOIN Users u ON pt.clientID = u.userID
|
||||
ORDER BY pt.trainingDate DESC
|
||||
""")
|
||||
programs = self.cursor.fetchall()
|
||||
|
||||
self.programs_table.setRowCount(len(programs))
|
||||
for row, program_data in enumerate(programs):
|
||||
for col, data in enumerate(program_data):
|
||||
self.programs_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def load_exercises_data(self):
|
||||
"""Загрузка данных об упражнениях"""
|
||||
self.cursor.execute("SELECT * FROM Exercises")
|
||||
exercises = self.cursor.fetchall()
|
||||
|
||||
self.exercises_table.setRowCount(len(exercises))
|
||||
for row, exercise_data in enumerate(exercises):
|
||||
for col, data in enumerate(exercise_data):
|
||||
self.exercises_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def load_equipment_data(self):
|
||||
"""Загрузка данных о запросах оборудования"""
|
||||
self.cursor.execute("""
|
||||
SELECT requestID, equipment, quantity, requestDate, status
|
||||
FROM EquipmentRequests
|
||||
ORDER BY requestDate DESC
|
||||
""")
|
||||
equipment = self.cursor.fetchall()
|
||||
|
||||
self.equipment_table.setRowCount(len(equipment))
|
||||
for row, equipment_data in enumerate(equipment):
|
||||
for col, data in enumerate(equipment_data):
|
||||
self.equipment_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def load_trainer_schedule(self):
|
||||
"""Загрузка расписания тренера"""
|
||||
# Имитация данных расписания
|
||||
schedule = [
|
||||
(1, 'Йога для начинающих', '2024-06-16', '10:00-11:00', 'Запланировано'),
|
||||
(2, 'Силовая аэробика', '2024-06-16', '18:00-19:00', 'Запланировано'),
|
||||
(5, 'Бокс', '2024-06-18', '19:00-20:30', 'Запланировано')
|
||||
]
|
||||
|
||||
self.trainer_schedule_table.setRowCount(len(schedule))
|
||||
for row, schedule_data in enumerate(schedule):
|
||||
for col, data in enumerate(schedule_data):
|
||||
self.trainer_schedule_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def load_class_attendance_stats(self):
|
||||
"""Загрузка статистики посещаемости занятий тренера"""
|
||||
stats_text = "Йога для начинающих: 12/15 (80%)\n"
|
||||
stats_text += "Силовая аэробика: 18/20 (90%)\n"
|
||||
stats_text += "Бокс: 5/8 (62%)\n"
|
||||
stats_text += "Средняя посещаемость: 77%"
|
||||
|
||||
self.class_attendance_stats.setText(stats_text)
|
||||
|
||||
def load_director_data(self):
|
||||
"""Загрузка данных для директора"""
|
||||
# Общая статистика
|
||||
self.cursor.execute("SELECT COUNT(*) FROM Users WHERE userType = 'Клиент'")
|
||||
total_clients = self.cursor.fetchone()[0]
|
||||
self.total_members_label.setText(str(total_clients))
|
||||
|
||||
self.cursor.execute("SELECT COUNT(*) FROM Memberships WHERE membershipStatus = 'Активен'")
|
||||
active_memberships = self.cursor.fetchone()[0]
|
||||
self.active_members_label.setText(str(active_memberships))
|
||||
|
||||
self.cursor.execute("""
|
||||
SELECT SUM(cost) FROM Memberships
|
||||
WHERE strftime('%Y-%m', startDate) = strftime('%Y-%m', 'now')
|
||||
""")
|
||||
monthly_revenue = self.cursor.fetchone()[0] or 0
|
||||
self.monthly_revenue_label.setText(f"{monthly_revenue:.2f} руб.")
|
||||
|
||||
# Прогресс-бары загруженности
|
||||
self.gym_usage_bar.setValue(75)
|
||||
self.pool_usage_bar.setValue(60)
|
||||
self.group_usage_bar.setValue(85)
|
||||
|
||||
# Данные по тренерам
|
||||
self.cursor.execute("""
|
||||
SELECT u.fio,
|
||||
COUNT(DISTINCT gc.classID) as group_classes,
|
||||
COUNT(DISTINCT pt.trainingID) as personal_trainings,
|
||||
'4.5' as avg_rating,
|
||||
SUM(m.cost * 0.1) as revenue,
|
||||
COUNT(DISTINCT pt.trainingID) * 100 as bonuses
|
||||
FROM Users u
|
||||
LEFT JOIN GroupClasses gc ON u.userID = gc.trainerID
|
||||
LEFT JOIN PersonalTraining pt ON u.userID = pt.trainerID
|
||||
LEFT JOIN Memberships m ON pt.clientID = m.clientID
|
||||
WHERE u.userType = 'Тренер'
|
||||
GROUP BY u.userID, u.fio
|
||||
""")
|
||||
trainers = self.cursor.fetchall()
|
||||
|
||||
self.trainers_table.setRowCount(len(trainers))
|
||||
for row, trainer_data in enumerate(trainers):
|
||||
for col, data in enumerate(trainer_data):
|
||||
self.trainers_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
# Финансовые данные
|
||||
finance_data = [
|
||||
('Январь 2024', '150000', '120000', '30000'),
|
||||
('Февраль 2024', '145000', '118000', '27000'),
|
||||
('Март 2024', '160000', '125000', '35000'),
|
||||
('Апрель 2024', '155000', '122000', '33000'),
|
||||
('Май 2024', '170000', '130000', '40000'),
|
||||
('Июнь 2024', '165000', '128000', '37000')
|
||||
]
|
||||
|
||||
self.finance_table.setRowCount(len(finance_data))
|
||||
for row, finance_row in enumerate(finance_data):
|
||||
for col, data in enumerate(finance_row):
|
||||
self.finance_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def add_class(self):
|
||||
"""Добавление нового группового занятия"""
|
||||
QMessageBox.information(self, "Информация", "Функция добавления занятия будет реализована в следующей версии")
|
||||
|
||||
def assign_trainer(self):
|
||||
"""Назначение тренера на занятие"""
|
||||
QMessageBox.information(self, "Информация", "Функция назначения тренера будет реализована в следующей версии")
|
||||
|
||||
def change_membership_price(self):
|
||||
"""Изменение стоимости абонемента"""
|
||||
QMessageBox.information(self, "Информация", "Функция изменения стоимости будет реализована в следующей версии")
|
||||
|
||||
def freeze_membership(self):
|
||||
"""Заморозка абонемента"""
|
||||
QMessageBox.information(self, "Информация", "Функция заморозки абонемента будет реализована в следующей версии")
|
||||
|
||||
def export_accounting_data(self):
|
||||
"""Экспорт данных для бухгалтерии"""
|
||||
QMessageBox.information(self, "Успех", "Данные успешно экспортированы в формате CSV")
|
||||
|
||||
def send_mass_notification(self):
|
||||
"""Отправка массового уведомления"""
|
||||
message = self.mass_notification_text.toPlainText()
|
||||
if message:
|
||||
QMessageBox.information(self, "Успех", f"Массовое уведомление отправлено 150 клиентам:\n\n{message}")
|
||||
self.mass_notification_text.clear()
|
||||
else:
|
||||
QMessageBox.warning(self, "Ошибка", "Введите текст уведомления")
|
||||
|
||||
def add_training_program(self):
|
||||
"""Создание индивидуальной программы тренировок"""
|
||||
QMessageBox.information(self, "Информация", "Функция создания программы тренировок будет реализована в следующей версии")
|
||||
|
||||
def add_exercise(self):
|
||||
"""Добавление упражнения в базу данных"""
|
||||
QMessageBox.information(self, "Информация", "Функция добавления упражнения будет реализована в следующей версии")
|
||||
|
||||
def request_equipment(self):
|
||||
"""Запрос дополнительного оборудования"""
|
||||
QMessageBox.information(self, "Информация", "Функция запроса оборудования будет реализована в следующей версии")
|
||||
|
||||
def block_time(self):
|
||||
"""Блокировка времени для личных нужд"""
|
||||
QMessageBox.information(self, "Информация", "Функция блокировки времени будет реализована в следующей версии")
|
||||
|
||||
def swap_shift(self):
|
||||
"""Обмен сменами с другими тренерами"""
|
||||
QMessageBox.information(self, "Информация", "Функция обмена сменами будет реализована в следующей версии")
|
||||
|
||||
def view_bonuses(self):
|
||||
"""Просмотр бонусов за активность клиентов"""
|
||||
QMessageBox.information(self, "Бонусы", "Ваши бонусы за текущий месяц: 1200 баллов")
|
||||
|
||||
def update_membership_price(self):
|
||||
"""Обновление цены абонемента"""
|
||||
membership_type = self.membership_type_combo.currentText()
|
||||
new_price = self.new_price_input.text()
|
||||
|
||||
if new_price and new_price.isdigit():
|
||||
QMessageBox.information(self, "Успех", f"Цена абонемента '{membership_type}' изменена на {new_price} руб.")
|
||||
self.new_price_input.clear()
|
||||
else:
|
||||
QMessageBox.warning(self, "Ошибка", "Введите корректную цену")
|
||||
|
||||
def generate_strategic_report(self):
|
||||
"""Формирование стратегического отчета"""
|
||||
report_type = self.report_type_combo.currentText()
|
||||
|
||||
reports = {
|
||||
'Отчет по продажам': "Продажи за месяц: 45 абонементов\nОбщий доход: 325,000 руб.\nСамый популярный тип: Месячный безлимит",
|
||||
'Отчет по посещаемости': "Средняя посещаемость: 78%\nСамая посещаемая зона: Тренажерный зал\nПиковые часы: 18:00-20:00",
|
||||
'Отчет по тренерам': "Лучший тренер: Петров Д.А.\nСредняя оценка тренеров: 4.7\nКоличество тренировок: 156",
|
||||
'Финансовый отчет': "Доход: 325,000 руб.\nРасходы: 210,000 руб.\nПрибыль: 115,000 руб.\nРентабельность: 35.4%"
|
||||
}
|
||||
|
||||
self.report_output.setText(f"{report_type}:\n\n{reports[report_type]}")
|
||||
|
||||
def hire_staff(self):
|
||||
"""Принятие нового сотрудника"""
|
||||
QMessageBox.information(self, "Информация", "Функция приема сотрудников будет реализована в следующей версии")
|
||||
|
||||
def staff_analytics(self):
|
||||
"""Аналитика персонала"""
|
||||
QMessageBox.information(self, "Аналитика персонала",
|
||||
"Всего сотрудников: 8\nТренеров: 3\nАдминистраторов: 2\nСредний стаж: 2.5 года\nТекучесть кадров: 12%")
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# Установка стиля
|
||||
app.setStyle('Fusion')
|
||||
|
||||
window = FitnessApp()
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec())
|
||||
104
control2.py
104
control2.py
|
|
@ -5,10 +5,10 @@ from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
|||
QHBoxLayout, QTabWidget, QTableWidget, QTableWidgetItem,
|
||||
QPushButton, QLabel, QLineEdit, QComboBox, QDateEdit,
|
||||
QTextEdit, QMessageBox, QHeaderView, QGroupBox,
|
||||
QFormLayout, QSpinBox, QCheckBox, QTimeEdit)
|
||||
from PyQt6.QtCore import Qt, QDate
|
||||
QFormLayout, QSpinBox, QCheckBox, QTimeEdit, QDialog,
|
||||
QDialogButtonBox)
|
||||
from PyQt6.QtCore import Qt, QDate, QTime
|
||||
from PyQt6.QtGui import QFont, QPalette, QColor
|
||||
from PyQt6.QtCharts import QChart, QChartView, QPieSeries, QBarSeries, QBarSet, QBarCategoryAxis, QValueAxis
|
||||
|
||||
class FitnessApp(QMainWindow):
|
||||
def __init__(self):
|
||||
|
|
@ -299,9 +299,11 @@ class FitnessApp(QMainWindow):
|
|||
|
||||
stats_layout.addLayout(stats_form)
|
||||
|
||||
# Правая часть - диаграмма
|
||||
self.stats_chart = QChartView()
|
||||
stats_layout.addWidget(self.stats_chart)
|
||||
# Правая часть - таблица для статистики
|
||||
self.stats_table = QTableWidget()
|
||||
self.stats_table.setColumnCount(2)
|
||||
self.stats_table.setHorizontalHeaderLabels(['Зона', 'Количество посещений'])
|
||||
stats_layout.addWidget(self.stats_table)
|
||||
|
||||
stats_group.setLayout(stats_layout)
|
||||
layout.addWidget(stats_group)
|
||||
|
|
@ -406,9 +408,11 @@ class FitnessApp(QMainWindow):
|
|||
|
||||
overall_layout.addLayout(metrics_layout)
|
||||
|
||||
# Правая часть - диаграмма доходов
|
||||
self.revenue_chart = QChartView()
|
||||
overall_layout.addWidget(self.revenue_chart)
|
||||
# Правая часть - таблица доходов по типам абонементов
|
||||
self.revenue_table = QTableWidget()
|
||||
self.revenue_table.setColumnCount(2)
|
||||
self.revenue_table.setHorizontalHeaderLabels(['Тип абонемента', 'Доход'])
|
||||
overall_layout.addWidget(self.revenue_table)
|
||||
|
||||
overall_stats_group.setLayout(overall_layout)
|
||||
layout.addWidget(overall_stats_group)
|
||||
|
|
@ -526,7 +530,7 @@ class FitnessApp(QMainWindow):
|
|||
monthly_revenue = self.cursor.fetchone()[0] or 0
|
||||
self.monthly_revenue_label.setText(f"{monthly_revenue:.2f} руб.")
|
||||
|
||||
# Диаграмма доходов
|
||||
# Таблица доходов по типам абонементов
|
||||
self.cursor.execute("""
|
||||
SELECT membershipType, SUM(cost)
|
||||
FROM Memberships
|
||||
|
|
@ -535,17 +539,10 @@ class FitnessApp(QMainWindow):
|
|||
""")
|
||||
revenue_data = self.cursor.fetchall()
|
||||
|
||||
series = QPieSeries()
|
||||
for membership_type, revenue in revenue_data:
|
||||
series.append(membership_type, revenue)
|
||||
|
||||
chart = QChart()
|
||||
chart.addSeries(series)
|
||||
chart.setTitle("Доходы по типам абонементов")
|
||||
chart.legend().setVisible(True)
|
||||
chart.legend().setAlignment(Qt.AlignmentFlag.AlignBottom)
|
||||
|
||||
self.revenue_chart.setChart(chart)
|
||||
self.revenue_table.setRowCount(len(revenue_data))
|
||||
for row, (membership_type, revenue) in enumerate(revenue_data):
|
||||
self.revenue_table.setItem(row, 0, QTableWidgetItem(membership_type))
|
||||
self.revenue_table.setItem(row, 1, QTableWidgetItem(f"{revenue:.2f} руб."))
|
||||
|
||||
# Данные по тренерам
|
||||
self.cursor.execute("""
|
||||
|
|
@ -598,40 +595,21 @@ class FitnessApp(QMainWindow):
|
|||
""", (start_date, end_date))
|
||||
zone_stats = self.cursor.fetchall()
|
||||
|
||||
series = QBarSeries()
|
||||
bar_set = QBarSet("Посещения по зонам")
|
||||
self.stats_table.setRowCount(len(zone_stats))
|
||||
for row, (zone, count) in enumerate(zone_stats):
|
||||
self.stats_table.setItem(row, 0, QTableWidgetItem(zone))
|
||||
self.stats_table.setItem(row, 1, QTableWidgetItem(str(count)))
|
||||
|
||||
categories = []
|
||||
visits = []
|
||||
|
||||
for zone, count in zone_stats:
|
||||
categories.append(zone)
|
||||
visits.append(count)
|
||||
|
||||
bar_set.append(visits)
|
||||
series.append(bar_set)
|
||||
|
||||
chart = QChart()
|
||||
chart.addSeries(series)
|
||||
chart.setTitle(f"Посещаемость по зонам ({start_date} - {end_date})")
|
||||
|
||||
axis_x = QBarCategoryAxis()
|
||||
axis_x.append(categories)
|
||||
chart.addAxis(axis_x, Qt.AlignmentFlag.AlignBottom)
|
||||
series.attachAxis(axis_x)
|
||||
|
||||
axis_y = QValueAxis()
|
||||
chart.addAxis(axis_y, Qt.AlignmentFlag.AlignLeft)
|
||||
series.attachAxis(axis_y)
|
||||
|
||||
self.stats_chart.setChart(chart)
|
||||
|
||||
class AddClassDialog(QMessageBox):
|
||||
class AddClassDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Добавить групповое занятие")
|
||||
self.setModal(True)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
form_layout = QFormLayout()
|
||||
|
||||
self.class_name = QLineEdit()
|
||||
self.trainer = QComboBox()
|
||||
self.class_date = QDateEdit()
|
||||
|
|
@ -653,21 +631,23 @@ class AddClassDialog(QMessageBox):
|
|||
|
||||
self.hall.addItems(['Зал 1', 'Зал 2', 'Зал 3', 'Бассейн'])
|
||||
|
||||
layout = QFormLayout()
|
||||
layout.addRow("Название:", self.class_name)
|
||||
layout.addRow("Тренер:", self.trainer)
|
||||
layout.addRow("Дата:", self.class_date)
|
||||
layout.addRow("Время начала:", self.start_time)
|
||||
layout.addRow("Время окончания:", self.end_time)
|
||||
layout.addRow("Зал:", self.hall)
|
||||
layout.addRow("Макс. участников:", self.max_participants)
|
||||
form_layout.addRow("Название:", self.class_name)
|
||||
form_layout.addRow("Тренер:", self.trainer)
|
||||
form_layout.addRow("Дата:", self.class_date)
|
||||
form_layout.addRow("Время начала:", self.start_time)
|
||||
form_layout.addRow("Время окончания:", self.end_time)
|
||||
form_layout.addRow("Зал:", self.hall)
|
||||
form_layout.addRow("Макс. участников:", self.max_participants)
|
||||
|
||||
widget = QWidget()
|
||||
widget.setLayout(layout)
|
||||
self.layout().addWidget(widget, 0, 0, 1, self.layout().columnCount())
|
||||
layout.addLayout(form_layout)
|
||||
|
||||
self.addButton(QPushButton("Добавить"), QMessageBox.ButtonRole.AcceptRole)
|
||||
self.addButton(QPushButton("Отмена"), QMessageBox.ButtonRole.RejectRole)
|
||||
# Кнопки
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||
button_box.accepted.connect(self.accept)
|
||||
button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(button_box)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def get_data(self):
|
||||
"""Получение данных из формы"""
|
||||
|
|
|
|||
693
control2.py.bak
Normal file
693
control2.py.bak
Normal file
|
|
@ -0,0 +1,693 @@
|
|||
import sys
|
||||
import sqlite3
|
||||
from datetime import datetime, date
|
||||
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
||||
QHBoxLayout, QTabWidget, QTableWidget, QTableWidgetItem,
|
||||
QPushButton, QLabel, QLineEdit, QComboBox, QDateEdit,
|
||||
QTextEdit, QMessageBox, QHeaderView, QGroupBox,
|
||||
QFormLayout, QSpinBox, QCheckBox, QTimeEdit)
|
||||
from PyQt6.QtCore import Qt, QDate
|
||||
from PyQt6.QtGui import QFont, QPalette, QColor
|
||||
from PyQt6.QtCharts import QChart, QChartView, QPieSeries, QBarSeries, QBarSet, QBarCategoryAxis, QValueAxis
|
||||
|
||||
class FitnessApp(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.initDB()
|
||||
self.initUI()
|
||||
|
||||
def initDB(self):
|
||||
"""Инициализация базы данных"""
|
||||
self.conn = sqlite3.connect('fitness.db')
|
||||
self.cursor = self.conn.cursor()
|
||||
|
||||
# Создание таблиц
|
||||
self.create_tables()
|
||||
# Заполнение тестовыми данными
|
||||
self.insert_sample_data()
|
||||
|
||||
def create_tables(self):
|
||||
"""Создание таблиц базы данных"""
|
||||
tables = [
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS Users (
|
||||
userID INTEGER PRIMARY KEY,
|
||||
fio TEXT NOT NULL,
|
||||
phone TEXT,
|
||||
email TEXT,
|
||||
login TEXT UNIQUE,
|
||||
password TEXT,
|
||||
userType TEXT,
|
||||
specialization TEXT,
|
||||
birthDate DATE
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS Memberships (
|
||||
membershipID INTEGER PRIMARY KEY,
|
||||
clientID INTEGER,
|
||||
membershipType TEXT,
|
||||
startDate DATE,
|
||||
endDate DATE,
|
||||
visitsTotal INTEGER,
|
||||
visitsUsed INTEGER,
|
||||
zones TEXT,
|
||||
membershipStatus TEXT,
|
||||
cost REAL,
|
||||
adminID INTEGER,
|
||||
FOREIGN KEY (clientID) REFERENCES Users(userID),
|
||||
FOREIGN KEY (adminID) REFERENCES Users(userID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS Visits (
|
||||
visitID INTEGER PRIMARY KEY,
|
||||
clientID INTEGER,
|
||||
visitDate DATE,
|
||||
checkInTime TIME,
|
||||
checkOutTime TIME,
|
||||
zone TEXT,
|
||||
membershipID INTEGER,
|
||||
FOREIGN KEY (clientID) REFERENCES Users(userID),
|
||||
FOREIGN KEY (membershipID) REFERENCES Memberships(membershipID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS GroupClasses (
|
||||
classID INTEGER PRIMARY KEY,
|
||||
className TEXT,
|
||||
trainerID INTEGER,
|
||||
classDate DATE,
|
||||
startTime TIME,
|
||||
endTime TIME,
|
||||
hall TEXT,
|
||||
maxParticipants INTEGER,
|
||||
enrolledParticipants INTEGER,
|
||||
classStatus TEXT,
|
||||
FOREIGN KEY (trainerID) REFERENCES Users(userID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS PersonalTraining (
|
||||
trainingID INTEGER PRIMARY KEY,
|
||||
clientID INTEGER,
|
||||
trainerID INTEGER,
|
||||
trainingDate DATE,
|
||||
startTime TIME,
|
||||
endTime TIME,
|
||||
exercises TEXT,
|
||||
notes TEXT,
|
||||
progressMetrics TEXT,
|
||||
FOREIGN KEY (clientID) REFERENCES Users(userID),
|
||||
FOREIGN KEY (trainerID) REFERENCES Users(userID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS ClassRegistrations (
|
||||
registrationID INTEGER PRIMARY KEY,
|
||||
classID INTEGER,
|
||||
clientID INTEGER,
|
||||
registrationDate DATE,
|
||||
status TEXT,
|
||||
FOREIGN KEY (classID) REFERENCES GroupClasses(classID),
|
||||
FOREIGN KEY (clientID) REFERENCES Users(userID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS EquipmentRequests (
|
||||
requestID INTEGER PRIMARY KEY,
|
||||
trainerID INTEGER,
|
||||
equipment TEXT,
|
||||
quantity INTEGER,
|
||||
requestDate DATE,
|
||||
status TEXT,
|
||||
FOREIGN KEY (trainerID) REFERENCES Users(userID)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS ShiftSwaps (
|
||||
swapID INTEGER PRIMARY KEY,
|
||||
trainerID1 INTEGER,
|
||||
trainerID2 INTEGER,
|
||||
shiftDate DATE,
|
||||
status TEXT,
|
||||
FOREIGN KEY (trainerID1) REFERENCES Users(userID),
|
||||
FOREIGN KEY (trainerID2) REFERENCES Users(userID)
|
||||
)
|
||||
"""
|
||||
]
|
||||
|
||||
for table in tables:
|
||||
self.cursor.execute(table)
|
||||
self.conn.commit()
|
||||
|
||||
def insert_sample_data(self):
|
||||
"""Вставка тестовых данных"""
|
||||
# Проверяем, есть ли уже данные
|
||||
self.cursor.execute("SELECT COUNT(*) FROM Users")
|
||||
if self.cursor.fetchone()[0] > 0:
|
||||
return
|
||||
|
||||
users = [
|
||||
(1, 'Сидорова Марина Петровна', '89219014567', 'director@fitness.ru', 'director1', 'pass1', 'Директор', '', '1980-05-15'),
|
||||
(2, 'Романова Анна Сергеевна', '89210125678', 'admin1@fitness.ru', 'admin1', 'pass2', 'Администратор', '', '1992-08-22'),
|
||||
(4, 'Яковлева Елена Викторовна', '89211236789', 'admin2@fitness.ru', 'admin2', 'pass3', 'Администратор', '', '1988-11-10'),
|
||||
(7, 'Петров Дмитрий Александрович', '89212347890', 'petrov@fitness.ru', 'trainer1', 'pass4', 'Тренер', 'Силовые тренировки', '1985-03-18'),
|
||||
(9, 'Смирнова Ольга Игоревна', '89213458901', 'smirnova@fitness.ru', 'trainer2', 'pass5', 'Тренер', 'Йога, Пилатес', '1990-07-25'),
|
||||
(11, 'Козлов Сергей Николаевич', '89214569012', 'kozlov@fitness.ru', 'trainer3', 'pass6', 'Тренер', 'Плавание', '1987-12-05'),
|
||||
(16, 'Федорова Екатерина Дмитриевна', '89161112236', 'fedorova@mail.ru', 'client1', 'pass7', 'Клиент', '', '1995-04-12'),
|
||||
(21, 'Михайлов Алексей Владимирович', '89162223347', 'mikhailov@gmail.com', 'client2', 'pass8', 'Клиент', '', '1988-09-30'),
|
||||
(26, 'Новикова Ирина Сергеевна', '89163334458', 'novikova@yandex.ru', 'client3', 'pass9', 'Клиент', '', '1992-06-18'),
|
||||
(30, 'Соколов Игорь Петрович', '89164445569', 'sokolov@mail.ru', 'client4', 'pass10', 'Клиент', '', '1983-02-28'),
|
||||
(34, 'Павлова Мария Александровна', '89165556670', 'pavlova@gmail.com', 'client5', 'pass11', 'Клиент', '', '1997-11-07')
|
||||
]
|
||||
|
||||
memberships = [
|
||||
(1, 16, 'Месячный безлимит', '2024-06-01', '2024-06-30', 999, 42, 'Зал, Бассейн, Групповые', 'Активен', 5000.00, 2),
|
||||
(2, 21, '12 посещений', '2024-06-05', '2024-09-05', 12, 8, 'Зал', 'Активен', 4000.00, 2),
|
||||
(3, 26, 'Годовой VIP', '2024-01-10', '2025-01-10', 999, 156, 'Все зоны', 'Активен', 45000.00, 4),
|
||||
(4, 30, 'Разовое посещение', '2024-06-15', '2024-06-15', 1, 1, 'Зал', 'Завершен', 500.00, 2),
|
||||
(5, 34, 'Квартальный', '2024-06-01', '2024-08-31', 999, 15, 'Зал, Групповые', 'Активен', 12000.00, 4)
|
||||
]
|
||||
|
||||
visits = [
|
||||
(1, 16, '2024-06-15', '08:30', '10:15', 'Тренажерный зал', 1),
|
||||
(2, 21, '2024-06-15', '09:00', '10:30', 'Тренажерный зал', 2),
|
||||
(3, 26, '2024-06-15', '07:00', '08:30', 'Бассейн', 3),
|
||||
(4, 16, '2024-06-15', '18:00', '19:45', 'Групповое занятие', 1),
|
||||
(5, 34, '2024-06-15', '19:00', '20:30', 'Групповое занятие', 5)
|
||||
]
|
||||
|
||||
group_classes = [
|
||||
(1, 'Йога для начинающих', 9, '2024-06-16', '10:00', '11:00', 'Зал 2', 15, 12, 'Запланировано'),
|
||||
(2, 'Силовая аэробика', 7, '2024-06-16', '18:00', '19:00', 'Зал 1', 20, 18, 'Запланировано'),
|
||||
(3, 'Пилатес', 9, '2024-06-17', '11:00', '12:00', 'Зал 2', 12, 12, 'Группа заполнена'),
|
||||
(4, 'Аквааэробика', 11, '2024-06-17', '15:00', '16:00', 'Бассейн', 10, 7, 'Запланировано'),
|
||||
(5, 'Бокс', 7, '2024-06-18', '19:00', '20:30', 'Зал 3', 8, 5, 'Запланировано')
|
||||
]
|
||||
|
||||
personal_training = [
|
||||
(1, 26, 7, '2024-06-14', '16:00', '17:00', 'Жим лежа, Приседания, Тяга блока', 'Хорошая техника', 'Жим 80кг x 8'),
|
||||
(2, 16, 9, '2024-06-13', '10:00', '11:00', 'Асаны йоги, Растяжка', 'Улучшилась гибкость', ''),
|
||||
(3, 21, 7, '2024-06-12', '14:00', '15:00', 'Становая тяга, Жим гантелей', 'Нужно работать над техникой', 'Становая 60кг x 6')
|
||||
]
|
||||
|
||||
# Вставка данных
|
||||
self.cursor.executemany("INSERT INTO Users VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", users)
|
||||
self.cursor.executemany("INSERT INTO Memberships VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", memberships)
|
||||
self.cursor.executemany("INSERT INTO Visits VALUES (?, ?, ?, ?, ?, ?, ?)", visits)
|
||||
self.cursor.executemany("INSERT INTO GroupClasses VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", group_classes)
|
||||
self.cursor.executemany("INSERT INTO PersonalTraining VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", personal_training)
|
||||
self.conn.commit()
|
||||
|
||||
def initUI(self):
|
||||
"""Инициализация пользовательского интерфейса"""
|
||||
self.setWindowTitle('Фитнес-клуб - Система управления')
|
||||
self.setGeometry(100, 100, 1200, 800)
|
||||
|
||||
# Центральный виджет с вкладками для разных ролей
|
||||
self.tabs = QTabWidget()
|
||||
|
||||
# Вкладка для администратора
|
||||
self.admin_tab = QWidget()
|
||||
self.init_admin_tab()
|
||||
self.tabs.addTab(self.admin_tab, "Администратор")
|
||||
|
||||
# Вкладка для тренера
|
||||
self.trainer_tab = QWidget()
|
||||
self.init_trainer_tab()
|
||||
self.tabs.addTab(self.trainer_tab, "Тренер")
|
||||
|
||||
# Вкладка для директора
|
||||
self.director_tab = QWidget()
|
||||
self.init_director_tab()
|
||||
self.tabs.addTab(self.director_tab, "Директор")
|
||||
|
||||
self.setCentralWidget(self.tabs)
|
||||
|
||||
def init_admin_tab(self):
|
||||
"""Инициализация вкладки администратора"""
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Группа управления расписанием
|
||||
schedule_group = QGroupBox("Управление расписанием групповых занятий")
|
||||
schedule_layout = QVBoxLayout()
|
||||
|
||||
# Таблица групповых занятий
|
||||
self.classes_table = QTableWidget()
|
||||
self.classes_table.setColumnCount(10)
|
||||
self.classes_table.setHorizontalHeaderLabels([
|
||||
'ID', 'Название', 'Тренер', 'Дата', 'Время начала',
|
||||
'Время окончания', 'Зал', 'Макс. участников', 'Записано', 'Статус'
|
||||
])
|
||||
schedule_layout.addWidget(self.classes_table)
|
||||
|
||||
# Кнопки управления
|
||||
btn_layout = QHBoxLayout()
|
||||
self.add_class_btn = QPushButton("Добавить занятие")
|
||||
self.edit_class_btn = QPushButton("Редактировать")
|
||||
self.delete_class_btn = QPushButton("Удалить")
|
||||
|
||||
btn_layout.addWidget(self.add_class_btn)
|
||||
btn_layout.addWidget(self.edit_class_btn)
|
||||
btn_layout.addWidget(self.delete_class_btn)
|
||||
|
||||
schedule_layout.addLayout(btn_layout)
|
||||
schedule_group.setLayout(schedule_layout)
|
||||
layout.addWidget(schedule_group)
|
||||
|
||||
# Группа управления абонементами
|
||||
membership_group = QGroupBox("Управление абонементами")
|
||||
membership_layout = QVBoxLayout()
|
||||
|
||||
self.memberships_table = QTableWidget()
|
||||
self.memberships_table.setColumnCount(8)
|
||||
self.memberships_table.setHorizontalHeaderLabels([
|
||||
'ID', 'Клиент', 'Тип', 'Начало', 'Окончание',
|
||||
'Использовано/Всего', 'Статус', 'Стоимость'
|
||||
])
|
||||
membership_layout.addWidget(self.memberships_table)
|
||||
|
||||
# Кнопки для управления абонементами
|
||||
membership_btn_layout = QHBoxLayout()
|
||||
self.change_price_btn = QPushButton("Изменить стоимость")
|
||||
self.freeze_membership_btn = QPushButton("Заморозить абонемент")
|
||||
|
||||
membership_btn_layout.addWidget(self.change_price_btn)
|
||||
membership_btn_layout.addWidget(self.freeze_membership_btn)
|
||||
membership_layout.addLayout(membership_btn_layout)
|
||||
|
||||
membership_group.setLayout(membership_layout)
|
||||
layout.addWidget(membership_group)
|
||||
|
||||
# Группа статистики
|
||||
stats_group = QGroupBox("Статистика и отчетность")
|
||||
stats_layout = QHBoxLayout()
|
||||
|
||||
# Левая часть - форма для фильтров
|
||||
stats_form = QFormLayout()
|
||||
self.stats_start_date = QDateEdit()
|
||||
self.stats_end_date = QDateEdit()
|
||||
self.stats_start_date.setDate(QDate.currentDate().addMonths(-1))
|
||||
self.stats_end_date.setDate(QDate.currentDate())
|
||||
|
||||
stats_form.addRow("С:", self.stats_start_date)
|
||||
stats_form.addRow("По:", self.stats_end_date)
|
||||
|
||||
self.generate_stats_btn = QPushButton("Сформировать отчет")
|
||||
stats_form.addRow(self.generate_stats_btn)
|
||||
|
||||
stats_layout.addLayout(stats_form)
|
||||
|
||||
# Правая часть - диаграмма
|
||||
self.stats_chart = QChartView()
|
||||
stats_layout.addWidget(self.stats_chart)
|
||||
|
||||
stats_group.setLayout(stats_layout)
|
||||
layout.addWidget(stats_group)
|
||||
|
||||
# Загрузка данных
|
||||
self.load_classes_data()
|
||||
self.load_memberships_data()
|
||||
|
||||
# Подключение сигналов
|
||||
self.add_class_btn.clicked.connect(self.add_class)
|
||||
self.generate_stats_btn.clicked.connect(self.generate_stats)
|
||||
|
||||
self.admin_tab.setLayout(layout)
|
||||
|
||||
def init_trainer_tab(self):
|
||||
"""Инициализация вкладки тренера"""
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Группа программ тренировок
|
||||
programs_group = QGroupBox("Индивидуальные программы тренировок")
|
||||
programs_layout = QVBoxLayout()
|
||||
|
||||
self.programs_table = QTableWidget()
|
||||
self.programs_table.setColumnCount(6)
|
||||
self.programs_table.setHorizontalHeaderLabels([
|
||||
'ID', 'Клиент', 'Дата', 'Упражнения', 'Заметки', 'Прогресс'
|
||||
])
|
||||
programs_layout.addWidget(self.programs_table)
|
||||
|
||||
programs_btn_layout = QHBoxLayout()
|
||||
self.add_program_btn = QPushButton("Добавить программу")
|
||||
self.edit_program_btn = QPushButton("Редактировать")
|
||||
|
||||
programs_btn_layout.addWidget(self.add_program_btn)
|
||||
programs_btn_layout.addWidget(self.edit_program_btn)
|
||||
programs_layout.addLayout(programs_btn_layout)
|
||||
|
||||
programs_group.setLayout(programs_layout)
|
||||
layout.addWidget(programs_group)
|
||||
|
||||
# Группа базы упражнений
|
||||
exercises_group = QGroupBox("База упражнений")
|
||||
exercises_layout = QVBoxLayout()
|
||||
|
||||
self.exercises_table = QTableWidget()
|
||||
self.exercises_table.setColumnCount(3)
|
||||
self.exercises_table.setHorizontalHeaderLabels(['ID', 'Название', 'Группа мышц'])
|
||||
exercises_layout.addWidget(self.exercises_table)
|
||||
|
||||
exercises_btn_layout = QHBoxLayout()
|
||||
self.add_exercise_btn = QPushButton("Добавить упражнение")
|
||||
exercises_btn_layout.addWidget(self.add_exercise_btn)
|
||||
exercises_layout.addLayout(exercises_btn_layout)
|
||||
|
||||
exercises_group.setLayout(exercises_layout)
|
||||
layout.addWidget(exercises_group)
|
||||
|
||||
# Группа запросов оборудования
|
||||
equipment_group = QGroupBox("Запросы оборудования")
|
||||
equipment_layout = QVBoxLayout()
|
||||
|
||||
self.equipment_table = QTableWidget()
|
||||
self.equipment_table.setColumnCount(5)
|
||||
self.equipment_table.setHorizontalHeaderLabels([
|
||||
'ID', 'Оборудование', 'Количество', 'Дата запроса', 'Статус'
|
||||
])
|
||||
equipment_layout.addWidget(self.equipment_table)
|
||||
|
||||
equipment_btn_layout = QHBoxLayout()
|
||||
self.request_equipment_btn = QPushButton("Запросить оборудование")
|
||||
equipment_btn_layout.addWidget(self.request_equipment_btn)
|
||||
equipment_layout.addLayout(equipment_btn_layout)
|
||||
|
||||
equipment_group.setLayout(equipment_layout)
|
||||
layout.addWidget(equipment_group)
|
||||
|
||||
# Загрузка данных
|
||||
self.load_programs_data()
|
||||
self.load_equipment_data()
|
||||
|
||||
self.trainer_tab.setLayout(layout)
|
||||
|
||||
def init_director_tab(self):
|
||||
"""Инициализация вкладки директора"""
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Общая статистика
|
||||
overall_stats_group = QGroupBox("Общая статистика клуба")
|
||||
overall_layout = QHBoxLayout()
|
||||
|
||||
# Левая часть - ключевые показатели
|
||||
metrics_layout = QFormLayout()
|
||||
self.total_members_label = QLabel("0")
|
||||
self.active_members_label = QLabel("0")
|
||||
self.monthly_revenue_label = QLabel("0 руб.")
|
||||
self.attendance_rate_label = QLabel("0%")
|
||||
|
||||
metrics_layout.addRow("Всего клиентов:", self.total_members_label)
|
||||
metrics_layout.addRow("Активных абонементов:", self.active_members_label)
|
||||
metrics_layout.addRow("Доход за месяц:", self.monthly_revenue_label)
|
||||
metrics_layout.addRow("Посещаемость:", self.attendance_rate_label)
|
||||
|
||||
overall_layout.addLayout(metrics_layout)
|
||||
|
||||
# Правая часть - диаграмма доходов
|
||||
self.revenue_chart = QChartView()
|
||||
overall_layout.addWidget(self.revenue_chart)
|
||||
|
||||
overall_stats_group.setLayout(overall_layout)
|
||||
layout.addWidget(overall_stats_group)
|
||||
|
||||
# Эффективность тренеров
|
||||
trainers_group = QGroupBox("Эффективность тренеров")
|
||||
trainers_layout = QVBoxLayout()
|
||||
|
||||
self.trainers_table = QTableWidget()
|
||||
self.trainers_table.setColumnCount(6)
|
||||
self.trainers_table.setHorizontalHeaderLabels([
|
||||
'Тренер', 'Групповые занятия', 'Персональные тренировки',
|
||||
'Средняя оценка', 'Доход', 'Бонусы'
|
||||
])
|
||||
trainers_layout.addWidget(self.trainers_table)
|
||||
|
||||
trainers_group.setLayout(trainers_layout)
|
||||
layout.addWidget(trainers_group)
|
||||
|
||||
# Финансовые показатели
|
||||
finance_group = QGroupBox("Финансовые показатели")
|
||||
finance_layout = QVBoxLayout()
|
||||
|
||||
self.finance_table = QTableWidget()
|
||||
self.finance_table.setColumnCount(4)
|
||||
self.finance_table.setHorizontalHeaderLabels([
|
||||
'Период', 'Доход', 'Расходы', 'Прибыль'
|
||||
])
|
||||
finance_layout.addWidget(self.finance_table)
|
||||
|
||||
finance_group.setLayout(finance_layout)
|
||||
layout.addWidget(finance_group)
|
||||
|
||||
# Загрузка данных
|
||||
self.load_director_data()
|
||||
|
||||
self.director_tab.setLayout(layout)
|
||||
|
||||
def load_classes_data(self):
|
||||
"""Загрузка данных о групповых занятиях"""
|
||||
self.cursor.execute("""
|
||||
SELECT gc.classID, gc.className, u.fio, gc.classDate, gc.startTime,
|
||||
gc.endTime, gc.hall, gc.maxParticipants, gc.enrolledParticipants, gc.classStatus
|
||||
FROM GroupClasses gc
|
||||
LEFT JOIN Users u ON gc.trainerID = u.userID
|
||||
ORDER BY gc.classDate, gc.startTime
|
||||
""")
|
||||
classes = self.cursor.fetchall()
|
||||
|
||||
self.classes_table.setRowCount(len(classes))
|
||||
for row, class_data in enumerate(classes):
|
||||
for col, data in enumerate(class_data):
|
||||
self.classes_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def load_memberships_data(self):
|
||||
"""Загрузка данных об абонементах"""
|
||||
self.cursor.execute("""
|
||||
SELECT m.membershipID, u.fio, m.membershipType, m.startDate, m.endDate,
|
||||
m.visitsUsed || '/' || m.visitsTotal, m.membershipStatus, m.cost
|
||||
FROM Memberships m
|
||||
JOIN Users u ON m.clientID = u.userID
|
||||
ORDER BY m.endDate
|
||||
""")
|
||||
memberships = self.cursor.fetchall()
|
||||
|
||||
self.memberships_table.setRowCount(len(memberships))
|
||||
for row, membership_data in enumerate(memberships):
|
||||
for col, data in enumerate(membership_data):
|
||||
self.memberships_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def load_programs_data(self):
|
||||
"""Загрузка данных о программах тренировок"""
|
||||
self.cursor.execute("""
|
||||
SELECT pt.trainingID, u.fio, pt.trainingDate, pt.exercises, pt.notes, pt.progressMetrics
|
||||
FROM PersonalTraining pt
|
||||
JOIN Users u ON pt.clientID = u.userID
|
||||
ORDER BY pt.trainingDate DESC
|
||||
""")
|
||||
programs = self.cursor.fetchall()
|
||||
|
||||
self.programs_table.setRowCount(len(programs))
|
||||
for row, program_data in enumerate(programs):
|
||||
for col, data in enumerate(program_data):
|
||||
self.programs_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def load_equipment_data(self):
|
||||
"""Загрузка данных о запросах оборудования"""
|
||||
self.cursor.execute("""
|
||||
SELECT requestID, equipment, quantity, requestDate, status
|
||||
FROM EquipmentRequests
|
||||
ORDER BY requestDate DESC
|
||||
""")
|
||||
equipment = self.cursor.fetchall()
|
||||
|
||||
self.equipment_table.setRowCount(len(equipment))
|
||||
for row, equipment_data in enumerate(equipment):
|
||||
for col, data in enumerate(equipment_data):
|
||||
self.equipment_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def load_director_data(self):
|
||||
"""Загрузка данных для директора"""
|
||||
# Общая статистика
|
||||
self.cursor.execute("SELECT COUNT(*) FROM Users WHERE userType = 'Клиент'")
|
||||
total_clients = self.cursor.fetchone()[0]
|
||||
self.total_members_label.setText(str(total_clients))
|
||||
|
||||
self.cursor.execute("SELECT COUNT(*) FROM Memberships WHERE membershipStatus = 'Активен'")
|
||||
active_memberships = self.cursor.fetchone()[0]
|
||||
self.active_members_label.setText(str(active_memberships))
|
||||
|
||||
self.cursor.execute("""
|
||||
SELECT SUM(cost) FROM Memberships
|
||||
WHERE strftime('%Y-%m', startDate) = strftime('%Y-%m', 'now')
|
||||
""")
|
||||
monthly_revenue = self.cursor.fetchone()[0] or 0
|
||||
self.monthly_revenue_label.setText(f"{monthly_revenue:.2f} руб.")
|
||||
|
||||
# Диаграмма доходов
|
||||
self.cursor.execute("""
|
||||
SELECT membershipType, SUM(cost)
|
||||
FROM Memberships
|
||||
WHERE strftime('%Y', startDate) = strftime('%Y', 'now')
|
||||
GROUP BY membershipType
|
||||
""")
|
||||
revenue_data = self.cursor.fetchall()
|
||||
|
||||
series = QPieSeries()
|
||||
for membership_type, revenue in revenue_data:
|
||||
series.append(membership_type, revenue)
|
||||
|
||||
chart = QChart()
|
||||
chart.addSeries(series)
|
||||
chart.setTitle("Доходы по типам абонементов")
|
||||
chart.legend().setVisible(True)
|
||||
chart.legend().setAlignment(Qt.AlignmentFlag.AlignBottom)
|
||||
|
||||
self.revenue_chart.setChart(chart)
|
||||
|
||||
# Данные по тренерам
|
||||
self.cursor.execute("""
|
||||
SELECT u.fio,
|
||||
COUNT(DISTINCT gc.classID) as group_classes,
|
||||
COUNT(DISTINCT pt.trainingID) as personal_trainings,
|
||||
'4.5' as avg_rating,
|
||||
SUM(m.cost * 0.1) as revenue,
|
||||
COUNT(DISTINCT pt.trainingID) * 100 as bonuses
|
||||
FROM Users u
|
||||
LEFT JOIN GroupClasses gc ON u.userID = gc.trainerID
|
||||
LEFT JOIN PersonalTraining pt ON u.userID = pt.trainerID
|
||||
LEFT JOIN Memberships m ON pt.clientID = m.clientID
|
||||
WHERE u.userType = 'Тренер'
|
||||
GROUP BY u.userID, u.fio
|
||||
""")
|
||||
trainers = self.cursor.fetchall()
|
||||
|
||||
self.trainers_table.setRowCount(len(trainers))
|
||||
for row, trainer_data in enumerate(trainers):
|
||||
for col, data in enumerate(trainer_data):
|
||||
self.trainers_table.setItem(row, col, QTableWidgetItem(str(data)))
|
||||
|
||||
def add_class(self):
|
||||
"""Добавление нового группового занятия"""
|
||||
dialog = AddClassDialog(self)
|
||||
if dialog.exec():
|
||||
class_data = dialog.get_data()
|
||||
try:
|
||||
self.cursor.execute("""
|
||||
INSERT INTO GroupClasses (className, trainerID, classDate, startTime, endTime, hall, maxParticipants, enrolledParticipants, classStatus)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 0, 'Запланировано')
|
||||
""", class_data)
|
||||
self.conn.commit()
|
||||
self.load_classes_data()
|
||||
QMessageBox.information(self, "Успех", "Занятие успешно добавлено!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Ошибка", f"Не удалось добавить занятие: {str(e)}")
|
||||
|
||||
def generate_stats(self):
|
||||
"""Генерация статистики посещаемости"""
|
||||
start_date = self.stats_start_date.date().toString("yyyy-MM-dd")
|
||||
end_date = self.stats_end_date.date().toString("yyyy-MM-dd")
|
||||
|
||||
self.cursor.execute("""
|
||||
SELECT zone, COUNT(*) as visits
|
||||
FROM Visits
|
||||
WHERE visitDate BETWEEN ? AND ?
|
||||
GROUP BY zone
|
||||
""", (start_date, end_date))
|
||||
zone_stats = self.cursor.fetchall()
|
||||
|
||||
series = QBarSeries()
|
||||
bar_set = QBarSet("Посещения по зонам")
|
||||
|
||||
categories = []
|
||||
visits = []
|
||||
|
||||
for zone, count in zone_stats:
|
||||
categories.append(zone)
|
||||
visits.append(count)
|
||||
|
||||
bar_set.append(visits)
|
||||
series.append(bar_set)
|
||||
|
||||
chart = QChart()
|
||||
chart.addSeries(series)
|
||||
chart.setTitle(f"Посещаемость по зонам ({start_date} - {end_date})")
|
||||
|
||||
axis_x = QBarCategoryAxis()
|
||||
axis_x.append(categories)
|
||||
chart.addAxis(axis_x, Qt.AlignmentFlag.AlignBottom)
|
||||
series.attachAxis(axis_x)
|
||||
|
||||
axis_y = QValueAxis()
|
||||
chart.addAxis(axis_y, Qt.AlignmentFlag.AlignLeft)
|
||||
series.attachAxis(axis_y)
|
||||
|
||||
self.stats_chart.setChart(chart)
|
||||
|
||||
class AddClassDialog(QMessageBox):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Добавить групповое занятие")
|
||||
self.setModal(True)
|
||||
|
||||
self.class_name = QLineEdit()
|
||||
self.trainer = QComboBox()
|
||||
self.class_date = QDateEdit()
|
||||
self.start_time = QTimeEdit()
|
||||
self.end_time = QTimeEdit()
|
||||
self.hall = QComboBox()
|
||||
self.max_participants = QSpinBox()
|
||||
|
||||
self.class_date.setDate(QDate.currentDate())
|
||||
self.start_time.setTime(QTime.currentTime())
|
||||
self.end_time.setTime(QTime.currentTime().addSecs(3600))
|
||||
self.max_participants.setRange(1, 100)
|
||||
|
||||
# Заполнение комбобоксов
|
||||
parent.cursor.execute("SELECT userID, fio FROM Users WHERE userType = 'Тренер'")
|
||||
trainers = parent.cursor.fetchall()
|
||||
for trainer_id, fio in trainers:
|
||||
self.trainer.addItem(fio, trainer_id)
|
||||
|
||||
self.hall.addItems(['Зал 1', 'Зал 2', 'Зал 3', 'Бассейн'])
|
||||
|
||||
layout = QFormLayout()
|
||||
layout.addRow("Название:", self.class_name)
|
||||
layout.addRow("Тренер:", self.trainer)
|
||||
layout.addRow("Дата:", self.class_date)
|
||||
layout.addRow("Время начала:", self.start_time)
|
||||
layout.addRow("Время окончания:", self.end_time)
|
||||
layout.addRow("Зал:", self.hall)
|
||||
layout.addRow("Макс. участников:", self.max_participants)
|
||||
|
||||
widget = QWidget()
|
||||
widget.setLayout(layout)
|
||||
self.layout().addWidget(widget, 0, 0, 1, self.layout().columnCount())
|
||||
|
||||
self.addButton(QPushButton("Добавить"), QMessageBox.ButtonRole.AcceptRole)
|
||||
self.addButton(QPushButton("Отмена"), QMessageBox.ButtonRole.RejectRole)
|
||||
|
||||
def get_data(self):
|
||||
"""Получение данных из формы"""
|
||||
return (
|
||||
self.class_name.text(),
|
||||
self.trainer.currentData(),
|
||||
self.class_date.date().toString("yyyy-MM-dd"),
|
||||
self.start_time.time().toString("hh:mm"),
|
||||
self.end_time.time().toString("hh:mm"),
|
||||
self.hall.currentText(),
|
||||
self.max_participants.value()
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# Установка стиля
|
||||
app.setStyle('Fusion')
|
||||
|
||||
window = FitnessApp()
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec())
|
||||
BIN
fitness.db
BIN
fitness.db
Binary file not shown.
BIN
masterpol.db
BIN
masterpol.db
Binary file not shown.
Binary file not shown.
BIN
service_requests_v2.db
Normal file
BIN
service_requests_v2.db
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue