Будь ласка, використовуйте цей ідентифікатор, щоб цитувати або посилатися на цей матеріал: https://er.chdtu.edu.ua/handle/ChSTU/9764
Назва: Крос-платформенний додаток для управління тренажерним залом
Автори: Півень, Олександр Борисович
Гончар, Андрій Олександрович
Ключові слова: крос-платформенний додаток;спортивний зал;автоматизація;абонемент;тренування;бронювання;відвідуваність;NODE.JS;REACT;REACT NATIVE;cross-platform application;gym;automation;membership;workout;booking;attendance;NODE.JS;REACT;REACT NATIVE
Дата публікації: 17-чер-2026
Короткий огляд (реферат): АНОТАЦІЯ У кваліфікаційній роботі бакалавра спроєктовано та розроблено крос-платформенний додаток для управління тренажерним залом. Метою роботи є створення програмного комплексу, який забезпечує керування користувачами, абонементами, тренуваннями, бронюваннями, відвідуваністю, платежами та статистичною інформацією. У роботі проаналізовано предметну область автоматизації спортивного залу, визначено основні проблеми ручного обліку клієнтів, абонементів, тренувань і платежів. Розглянуто існуючі програмні аналоги та обґрунтовано доцільність розробки власного кросплатформенного рішення. Сформовано функціональні й нефункціональні вимоги до системи, виконано моделювання предметної області та побудовано UML-діаграми, що описують структуру, архітектуру та поведінку програмного комплексу. Програмна реалізація складається із серверної частини, адміністративної вебпанелі, мобільного застосунку та бази даних. Серверну частину реалізовано з використанням Node.js, Express.js, PostgreSQL та Prisma ORM. Адміністративну вебпанель розроблено за допомогою React і Vite, а мобільний застосунок — з використанням React Native та Expo. У системі реалізовано рольове розмежування доступу для адміністратора, тренера та клієнта. Практичне значення роботи полягає в можливості використання розробленого додатку для автоматизації основних процесів роботи спортивного залу, зменшення обсягу ручної роботи адміністратора, підвищення точності обліку та покращення взаємодії між користувачами системи.
ABSTRACT The bachelor's qualification work is devoted to the development of a cross-platform application for automating gym operations. The purpose of the work is to create a software system that provides management of users, memberships, workouts, bookings, attendance, payments, and statistical information. The work analyzes the subject area of gym automation and identifies the main problems of manual accounting of clients, memberships, workouts, and payments. Existing software analogues are considered, and the relevance of developing a custom cross-platform solution is substantiated. Functional and non-functional requirements for the system are defined. The subject area is modeled, and UML diagrams are developed to describe the structure, architecture, and behavior of the software system. The software implementation consists of a server side, an administrative web panel, a mobile application, and a database. The server side is implemented using Node.js, Express.js, PostgreSQL, and Prisma ORM. The administrative web panel is developed using React and Vite, while the mobile application is implemented using React Native and Expo. The system implements role-based access control for the administrator, trainer, and client. The practical value of the work lies in the possibility of using the developed application to automate the main processes of gym operation, reduce the amount of manual administrative work, improve accounting accuracy, and enhance interaction between system users.
URI (Уніфікований ідентифікатор ресурсу): https://er.chdtu.edu.ua/handle/ChSTU/9764
Розташовується у зібраннях:121 Інженерія програмного забезпечення (Інженерія програмного забезпечення)

Файли цього матеріалу:
Файл Опис РозмірФормат 
Кваліфікаційна робота бакалавра Гончар Андрій Олександрович.pdf
  Restricted Access
11.25 MBAdobe PDFПереглянути/Відкрити    Запит копії


Усі матеріали в архіві електронних ресурсів захищено авторським правом, усі права збережено.

Extracted text
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ 
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ 
Факультет інформаційних технологій і систем 
Кафедра програмного забезпечення автоматизованих систем 
ПОЯСНЮВАЛЬНА ЗАПИСКА 
до кваліфікаційної роботи 
«бакалавра» 
освітній рівень 
на тему:  Крос-платформенний додаток для управління тренажерним залом 
Виконав: студент 4 курсу, групи ПЗ-2204 
Спеціальності  
121 «Інженерія програмного забезпечення» 
(шифр і назва напряму підготовки) 
Студент Гончар А. О. 
(прізвище та ініціали)
Керівник  Півень О. Б. 
(прізвище та ініціали)
Рецензент  Захарова М.В. 
(прізвище та ініціали)
Черкаси 2026 
Черкаський державний технологічний університет
повне найменування вищого навчального закладу 
Факультет інформаційних технологій і систем 
Кафедра програмного забезпечення автоматизованих систем 
Освітній рівень бакалавр 
Спеціальність 121 «Інженерія програмного забезпечення» 
Освітня програма Інженерія програмного забезпечення 
ЗАТВЕРДЖУЮ 
Зав. кафедри ПЗАС, професор 
С. Голуб 
«___» _______________ 2026 року 
З А В Д А Н Н Я 
НА КВАЛІФІКАЦІЙНУ РОБОТУ СТУДЕНТУ 
Гончар Андрій Олександрович 
(прізвище, ім’я, по батькові) 
1. Тему проекту (роботи) Крос-платформенний додаток для управління тренажерним залом
Керівник проекту (роботи) Півень Олександр Борисович к.ф-м.н., доцент 
 (прізвище, ім’я , по батькові, науковий ступінь, вчене звання) 
Затверджені наказом Черкаського державного технологічного університету від «12 березня» 
2026 року № 56/03-03 
2.Строк подання студентом проекту (роботи) 03.06.2026
3. Вхідні дані до проекту (роботи) 1) Платформа x86-64,
2) Мова програмування JavaScript
4. Зміст розрахунково-пояснювальної записки (перелік питань, які потрібно розробити)
Розділ 1. Існуючі методи та засоби розв’язання поставлених завдань 
Розділ 2. Проектування програмного забезпечення системи 
Розділ 3. Розробка та тестування програмного забезпечення 
Висновки; 
Список використаних джерел; 
Додатки А, Додатки Б, Додатки В. 
5. Перелік графічного матеріалу (з точним зазначенням обов’язкових робіт проекту);
Додаток Г - Графічний матеріал. 
6. Консультанти розділів роботи
Прізвище, ініціали та посади Підпис, дата 
Розділ 
консультанта Завдання видав Завдання прийняв 
1 
2 
3 
7. Дата видачі завдання     грудень  2025 р.
КАЛЕНДАРНИЙ ПЛАН 
Строк виконання 
№ етапів 
Назва етапів випускної роботи Примітки 
п/п кваліфікаційної 
роботи 
1 Постановка задачі 10.12.2025 виконано 
2 Підготовка завдання 18.12.2025 виконано 
3 Погодження завдання 19.04.2026 виконано 
4 Затвердження завдання 22.04.2026 виконано 
Основна стадія 
1 Підбір матеріалів 20.04.2026 виконано 
2 Аналіз шляхів вирішення поставленої задачі 25.04.2026 виконано 
3 Розрахунок основних параметрів роботи 01.05.2026 виконано 
4 Вибір кінцевого варіанту проектного рішення 08.05.2026 виконано 
5 Оформлення первісної редакції роботи 15.05.2026 виконано 
Заключна стадія 22.05.2026 
1 Узгодження прийнятих проектних рішень з 26.05.2026 виконано 
керівником 
2 Оформлення пояснювальної записки роботи в 30.05.2026 виконано 
кінцевій редакції 
3 Попередній захист роботи 03.06.2026 виконано 
4 Затвердження роботи 05.06.2026 виконано 
5 Рецензування роботи 09.06.2026 виконано 
6 Захист роботи 12.06.2026 
Студент _____________________     Гончар А.О. 
(підпис) (прізвище та ініціали) 
Керівник роботи _____________________         Півень О.Б. 
(підпис) (прізвище та ініціали)ы 
АНОТАЦІЯ 
У кваліфікаційній роботі бакалавра спроєктовано та розроблено крос-
платформенний додаток для управління тренажерним залом. Метою роботи є 
створення програмного комплексу, який забезпечує керування користувачами, 
абонементами, тренуваннями, бронюваннями, відвідуваністю, платежами та 
статистичною інформацією. 
У роботі проаналізовано предметну область автоматизації спортивного 
залу, визначено основні проблеми ручного обліку клієнтів, абонементів, 
тренувань і платежів. Розглянуто існуючі програмні аналоги та обґрунтовано 
доцільність розробки власного кросплатформенного рішення. Сформовано 
функціональні й нефункціональні вимоги до системи, виконано моделювання 
предметної області та побудовано UML-діаграми, що описують структуру, 
архітектуру та поведінку програмного комплексу. 
Програмна реалізація складається із серверної частини, адміністративної 
вебпанелі, мобільного застосунку та бази даних. Серверну частину реалізовано 
з використанням Node.js, Express.js, PostgreSQL та Prisma ORM. 
Адміністративну вебпанель розроблено за допомогою React і Vite, а мобільний 
застосунок — з використанням React Native та Expo. У системі реалізовано 
рольове розмежування доступу для адміністратора, тренера та клієнта. 
Практичне значення роботи полягає в можливості використання 
розробленого додатку для автоматизації основних процесів роботи спортивного 
залу, зменшення обсягу ручної роботи адміністратора, підвищення точності 
обліку та покращення взаємодії між користувачами системи. 
Ключові слова: крос-платформенний додаток, спортивний зал, 
автоматизація, абонемент, тренування, бронювання, відвідуваність, NODE.JS, 
REACT, REACT NATIVE.
ABSTRACT 
The bachelor's qualification work is devoted to the development of a cross-
platform application for automating gym operations. The purpose of the work is to 
create a software system that provides management of users, memberships, workouts, 
bookings, attendance, payments, and statistical information. 
The work analyzes the subject area of gym automation and identifies the main 
problems of manual accounting of clients, memberships, workouts, and payments. 
Existing software analogues are considered, and the relevance of developing a 
custom cross-platform solution is substantiated. Functional and non-functional 
requirements for the system are defined. The subject area is modeled, and UML 
diagrams are developed to describe the structure, architecture, and behavior of the 
software system. 
The software implementation consists of a server side, an administrative web 
panel, a mobile application, and a database. The server side is implemented using 
Node.js, Express.js, PostgreSQL, and Prisma ORM. The administrative web panel is 
developed using React and Vite, while the mobile application is implemented using 
React Native and Expo. The system implements role-based access control for the 
administrator, trainer, and client. 
The practical value of the work lies in the possibility of using the developed 
application to automate the main processes of gym operation, reduce the amount of 
manual administrative work, improve accounting accuracy, and enhance interaction 
between system users. 
Keywords: cross-platform application, gym, automation, membership, 
workout, booking, attendance, NODE.JS, REACT, REACT NATIVE. 
ЗМІСТ 
ВСТУП……………………………………………………………………………….5 
РОЗДІЛ 1 ІСНУЮЧУ МЕТОДИ ТА ЗАСОБИ РОЗВ’ЯЗАННЯ ПОСТАВЛЕНИХ 
ЗАДАЧ……………………………………………………………………………….8 
 1.1 Опис існуючих методів на засобів використаних при розробці……..........8 
 1.2 Методи та засоби, які вже використовуються для усунення проблем та 
виконання завдань………………………………….................................................11 
    1.3 Конкретизація завдань роботи………………….…………………………..16 
Висновки до розділу 1……………………………………………………………...19 
РОЗДІЛ 2 ПРОЕКТУВАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ СИСТЕМИ 
 ..................................................................................................................................... 20 
2.1 Моделювання предметної області ................................................................. 20 
2.1.1 Предметна область моделювання. Модель предметної області. 
Словник предметної області ............................................................................ 20 
2.1.2 Елементи моделювання предметної області ......................................... 24 
2.1.3 Робоча область моделювання ................................................................. 26 
2.2 Формування та аналіз вимог .......................................................................... 29 
2.2.1 Формування вимог до програмного забезпечення. Первинні і детальні 
вимоги. Вимоги замовника і розробника. Функціональні та 
нефункціональні вимоги ................................................................................... 29 
2.2.2 Формування вимог за допомогою діаграми прецедентів ..................... 34 
2.3 Проектування логічної структури програмного комплексу ....................... 36 
2.3.1 Діаграми класів ......................................................................................... 36 
2.3.2 Діаграми пакетів ....................................................................................... 38 
2.4 Архітектурне проектування ........................................................................... 40 
2.4.1 Діаграма компонентів .............................................................................. 40 
2.4.2 Розгортання програмної системи на апаратних засобах.  Діаграма 
розгортання ........................................................................................................ 42 
ЧДТУ 262240.001 ПЗ
Зм Лис № Докум. Підпи Дат
 
.Р озтроб Гончар А.О. с а «Крос-платформенний додаток Літ. Лист Листів 
К. ерівни П івень О.Б. 
к для управління спортивним ФІТІС, Кафедра ПЗАС, 
Н .конт  Півень 
рЗатв. залом» 
Голуб С.В. ПЗ-2204 
. О.Б.
(Пояснювальна записка)
   2.5 Моделювання поведінки системи ........................................................... 44 
2.5.1 Діаграма діяльності .................................................................................. 44 
2.5.2 Діаграма послідовності ............................................................................ 47 
2.5.3 Діаграма комунікації ................................................................................ 48 
2.5.4 Діаграма скінченного автомату .............................................................. 49 
Висновки до розділу 2……………………………………………………………...51 
РОЗДІЛ 3 РОЗРОБКА ТА ТЕСТУВАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ
 ..................................................................................................................................... 52 
3.1 Розробка програмного комплексу ................................................................. 52 
3.1.1 Обґрунтування вибору засобів реалізації .............................................. 52 
3.1.2 Опис структурної (функціональної) схеми ........................................... 54 
3.1.3 Опис логічної схеми системи .................................................................. 56 
3.1.4 Розробка бази даних ................................................................................. 58 
3.1.5 Розробка інтерфейсу користувача .......................................................... 61 
3.1.6 Опис розробки програмних компонентів .............................................. 66 
3.2 Тестування системи ........................................................................................ 69 
3.2.1 Модульне тестування ............................................................................... 69 
3.2.2 Інтеграційне тестування .......................................................................... 72 
3.2.3 Системне тестування ............................................................................... 74 
3.2.4 Приймальне тестування ........................................................................... 76 
3.3 Приклади впровадженого програмного комплексу ..................................... 77 
Висновки до розділу 3 .............................................................................................. 80 
ВИСНОВКИ . .............................................................................................................. 82 
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ . ................................................................ 84 
ДОДАТКИ . ................................................................................................................. 87 
Лист 
ЧДТУ 262240.001 ПЗ
З м. Лист № док. Підпис Дата 
ЧДТУ 262240.001 ПЗ 
ВСТУП 
Актуальність теми. У сучасних умовах цифровізації сфери послуг 
спортивні зали потребують ефективних програмних засобів для автоматизації 
щоденних процесів. Робота такого закладу пов’язана з обліком клієнтів, 
тренерів, абонементів, оплат, тренувань, записів на заняття та відвідуваності. 
Виконання цих операцій вручну або за допомогою розрізнених інструментів 
збільшує ймовірність помилок, ускладнює контроль діяльності закладу та 
потребує додаткових витрат часу персоналу. 
Актуальним є створення кросп-латформенного додатку, який забезпечує 
доступ до системи з різних пристроїв і враховує потреби різних категорій 
користувачів: адміністратора, тренера та клієнта. Адміністратор отримує 
можливість керувати користувачами, абонементами, тренуваннями та 
статистикою; тренер — переглядати призначені заняття й відмічати 
відвідуваність; клієнт — контролювати свій абонемент, записуватися на 
тренування та переглядати історію відвідувань. 
Під час розробки системи застосовувалися принципи інженерії 
програмного забезпечення, зокрема модульна архітектура, розподіл системи на 
клієнтську та серверну частини, використання шаблону REST API для взаємодії 
між компонентами та рольова модель доступу користувачів. Основною мовою 
програмування є JavaScript, а для мобільного застосунку також 
використовувався TypeScript. Застосування сучасних технологій, таких як 
Node.js, Express.js, React, React Native, Expo, PostgreSQL та Prisma ORM, 
забезпечило масштабованість, надійність і зручність подальшого супроводу 
програмного продукту. 
Отже, розробка крос-платформенного додатку для автоматизації роботи 
спортивного залу є доцільною, оскільки дозволяє об’єднати основні бізнес-
процеси закладу в єдиній інформаційній системі та покращити взаємодію між 
адміністрацією, тренерами й клієнтами. 
Метою кваліфікаційної роботи є розробка крос-платформенного 
програмного додатку для автоматизації роботи спортивного залу, який 
5 
ЧДТУ 262240.001 ПЗ 
забезпечує керування користувачами, абонементами, тренуваннями, записами 
на заняття, відвідуваністю, платежами та аналітичною інформацією. 
Для досягнення поставленої мети необхідно вирішити такі завдання: 
1 Проаналізувати предметну область автоматизації роботи спортивного 
залу.  
2 Дослідити існуючі програмні засоби та визначити їхні переваги й 
недоліки.  
3 Сформувати функціональні та нефункціональні вимоги до додатку.  
4 Спроєктувати архітектуру системи, базу даних і основні програмні 
модулі.  
5 Реалізувати серверну частину, адміністративну вебпанель і мобільний 
застосунок.  
6 Провести тестування основних функцій системи.  
Об’єктом розробки є процес автоматизації діяльності спортивного залу, 
що охоплює облік клієнтів, тренерів, абонементів, тренувань, бронювань, 
відвідуваності та платежів. 
Предметом розробки є крос-платформенний програмний додаток для 
інформаційної підтримки та автоматизації основних процесів керування 
спортивним залом. 
Методи проектування та конструювання. Для виконання роботи 
використано методи аналізу предметної області, порівняння існуючих рішень, 
об’єктно-орієнтованого проектування, моделювання структури даних і 
проектування клієнт-серверної архітектури. 
Для реалізації серверної частини використано Node.js, Express, 
PostgreSQL та Prisma. Для створення адміністративної вебпанелі застосовано 
React і Vite, а для мобільного застосунку — React Native та Expo. Безпека 
системи забезпечується автентифікацією на основі JWT-токенів і 
розмежуванням доступу відповідно до ролей користувачів. 
У результаті було розроблено прототип системи для автоматизації роботи 
спортивного залу. Вона складається із серверної частини, бази даних,  
6 
ЧДТУ 262240.001 ПЗ 
адміністративної вебпанелі та мобільного застосунку. 
Система підтримує три основні ролі користувачів: адміністратора, 
тренера та клієнта. Адміністративна панель дозволяє керувати користувачами, 
абонементами, тренуваннями, платежами та статистикою. Мобільний 
застосунок забезпечує клієнтам і тренерам доступ до профілю, абонементу, 
тренувань, записів і відвідуваності. 
Практичне значення розробленого програмного забезпечення полягає у 
можливості його використання для автоматизації діяльності спортивного залу 
або фітнес-центру. Система дозволяє зменшити обсяг ручної роботи 
адміністратора, підвищити точність обліку, спростити запис на тренування та 
забезпечити зручний доступ користувачів до актуальної інформації. 
Розроблений додаток може бути використаний як базове рішення для 
невеликих і середніх спортивних закладів, а також розширений додатковими 
функціями, зокрема онлайн-оплатою, push-сповіщеннями та розширеною 
аналітикою. 
Особистий внесок автора полягає в аналізі предметної області, 
формуванні вимог, проектуванні архітектури системи, розробці бази даних, 
серверної частини, адміністративної WEB-панелі та мобільного застосунку. 
Автором реалізовано основні функції керування користувачами, абонементами, 
тренуваннями, бронюваннями, відвідуваністю і платежами, а також проведено 
перевірку працездатності програмного забезпечення. 
 
7 
ЧДТУ 262240.001 ПЗ 
РОЗДІЛ 1 ІСНУЮЧІ МЕТОДИ ТА ЗАСОБИ РОЗВ’ЯЗАННЯ 
ПОСТАВЛЕНИХ ЗАВДАНЬ 
1.1. Актуальні проблеми, що виникають в процесі виконання 
завдань 
У процесі автоматизації роботи спортивного залу виникає низка 
актуальних проблем, пов’язаних із організацією обліку клієнтів, керуванням 
абонементами, плануванням тренувань, контролем відвідуваності, обробкою 
платежів та забезпеченням зручної взаємодії між усіма учасниками процесу. 
Спортивний зал як об’єкт автоматизації має декілька груп користувачів, 
зокрема адміністратора, тренера та клієнта. Кожна з цих груп виконує власні 
функції та потребує доступу до різного набору даних. Тому головним 
завданням під час розробки програмного забезпечення є створення такої 
системи, яка дозволить об’єднати всі основні процеси в єдиному 
інформаційному середовищі та водночас забезпечити простоту, надійність і 
безпеку її використання. 
Однією з основних проблем у роботі спортивного залу є неефективний 
облік клієнтів. У багатьох закладах інформація про відвідувачів може 
зберігатися у паперових журналах, електронних таблицях або різних не 
пов’язаних між собою сервісах. Такий підхід ускладнює пошук потрібних 
даних, створює ризик дублювання записів, втрати інформації або внесення 
помилкових відомостей. Наприклад, адміністратору необхідно швидко 
перевірити, чи має клієнт активний абонемент, скільки тренувань залишилося, 
коли було здійснено останню оплату або які заняття вже відвідано. За 
відсутності єдиної системи такі дії потребують зайвого часу та можуть 
призводити до неточностей в обслуговуванні клієнтів. 
Наступною важливою проблемою є керування абонементами. У 
спортивному залі можуть використовуватися різні типи абонементів: за 
кількістю занять, за терміном дії, за видом тренувань або за індивідуальними 
умовами. Ручне ведення такого обліку ускладнює контроль за актуальністю 
8 
ЧДТУ 262240.001 ПЗ 
абонементів, залишком занять, датою завершення дії та історією оплат. Якщо 
система не автоматизована, адміністратор змушений самостійно перевіряти ці 
дані, що збільшує ризик помилок. Наприклад, клієнт може бути помилково 
допущений до тренування після завершення абонемента або, навпаки, не 
отримати доступ до заняття через неправильно внесену інформацію. 
Автоматизація цього процесу дозволяє зменшити залежність від людського 
фактора та забезпечити більш точний контроль. 
Окрему проблему становить планування тренувань і запис клієнтів на 
заняття. У спортивних залах часто проводяться групові та індивідуальні 
тренування, які мають визначений час, тренера, кількість доступних місць і 
певні обмеження для учасників. Якщо запис на тренування здійснюється 
вручну, через телефонні дзвінки, повідомлення або адміністратора, це може 
створювати плутанину. Можливі ситуації подвійного запису, перевищення 
кількості учасників, скасування заняття без своєчасного інформування клієнтів 
або відсутність актуального розкладу. Для тренера також важливо бачити 
список записаних клієнтів і мати можливість контролювати відвідуваність. 
Тому система повинна забезпечувати зручний механізм створення тренувань, 
бронювання місць і перегляду актуального розкладу. 
Ще однією актуальною проблемою є контроль відвідуваності. 
Відвідуваність є важливим показником як для адміністрації спортивного залу, 
так і для тренерів та клієнтів. Адміністрація може використовувати ці дані для 
аналізу завантаженості залу, популярності тренувань, ефективності роботи 
тренерів та активності клієнтів. Тренер повинен мати можливість відмічати 
присутність учасників заняття, а клієнт — переглядати власну історію 
відвідувань. За ручного ведення обліку ці дані можуть бути неповними або 
неточними. Крім того, складно швидко сформувати статистику за певний 
період або визначити, які тренування користуються найбільшим попитом. 
Автоматизований контроль відвідуваності дозволяє зробити ці процеси 
прозорими й зручними. 
Важливим аспектом роботи спортивного залу є облік платежів і 
9 
ЧДТУ 262240.001 ПЗ 
фінансових операцій. Навіть якщо система не передбачає повноцінної інтеграції 
з банківськими сервісами, вона повинна забезпечувати збереження інформації 
про оплати, баланс клієнта, придбані абонементи та історію фінансових дій. 
Відсутність централізованого обліку платежів може призвести до непорозумінь 
між клієнтами та адміністрацією, втрати інформації про оплату або 
неправильного нарахування послуг. Для адміністратора важливо мати швидкий 
доступ до фінансових даних клієнта, а також можливість переглядати загальну 
інформацію про надходження коштів. Тому під час розробки системи необхідно 
передбачити надійний механізм фіксації платежів і зв’язку цих платежів з 
конкретними користувачами та абонементами. 
Значною проблемою є також розмежування прав доступу. У системі для 
спортивного залу різні користувачі повинні мати різні можливості. 
Адміністратор повинен мати повний доступ до керування користувачами, 
абонементами, тренуваннями та платежами. Тренер має працювати переважно з 
інформацією про власні тренування, клієнтів і відвідуваність. Клієнт повинен 
мати доступ лише до власного профілю, абонементу, записів і історії 
відвідувань. Якщо розмежування доступу реалізоване недостатньо якісно, 
виникає ризик несанкціонованого перегляду або зміни даних. Це особливо 
важливо, оскільки система може містити персональну інформацію 
користувачів. Тому під час розробки необхідно передбачити автентифікацію, 
авторизацію та рольову модель доступу. 
Ще однією проблемою є забезпечення кросплатформенності. Користувачі 
системи можуть працювати з різних пристроїв: комп’ютерів, планшетів і 
смартфонів. Адміністратору зручніше використовувати повноцінну вебпанель 
на комп’ютері, оскільки вона дозволяє працювати з великими таблицями, 
формами та статистикою. Клієнти й тренери частіше користуються 
смартфонами, тому для них доцільним є мобільний застосунок. Розробка 
окремих рішень для кожної платформи може потребувати значних витрат часу 
й ресурсів. Саме тому актуальним є використання кросплатформенних 
технологій, які дозволяють створити систему з єдиною серверною частиною та 
10 
ЧДТУ 262240.001 ПЗ 
різними клієнтськими інтерфейсами для окремих груп користувачів. 
Також у процесі виконання завдань виникає проблема зручності 
користувацького інтерфейсу. Навіть функціонально повна система може бути 
неефективною, якщо користувачам складно виконувати основні дії. 
Адміністратор повинен швидко знаходити потрібного клієнта, створювати 
тренування, видавати абонементи та переглядати статистику. Тренер повинен 
без зайвих дій бачити власний розклад і відмічати відвідуваність. Клієнт має 
легко записуватися на заняття та переглядати інформацію про свій абонемент. 
Отже, під час розробки необхідно враховувати не лише технічну реалізацію 
функцій, а й логіку взаємодії користувача із системою. 
Не менш важливою є проблема цілісності та актуальності даних. У 
системі спортивного залу багато сутностей пов’язані між собою: користувачі, 
ролі, тренування, абонементи, бронювання, відвідування та платежі. Помилки в 
одному модулі можуть впливати на роботу інших частин системи. Наприклад, 
видалення користувача не повинно призводити до некоректного стану записів 
на тренування, а зміна абонемента має коректно відображатися у профілі 
клієнта. Тому важливо правильно спроєктувати структуру бази даних, зв’язки 
між таблицями та логіку обробки даних на серверній частині. 
Отже, основними актуальними проблемами, що виникають у процесі 
виконання завдань автоматизації роботи спортивного залу, є неефективний 
облік клієнтів, складність керування абонементами, організація запису на 
тренування, контроль відвідуваності, облік платежів, розмежування прав 
доступу, забезпечення кросплатформенності, зручність інтерфейсу та 
підтримання цілісності даних. Розв’язання цих проблем є необхідною умовою 
створення якісного програмного продукту, який може бути використаний для 
підвищення ефективності роботи спортивного залу та покращення якості 
обслуговування клієнтів. 
1.2. Методи та засоби, які вже використовуються для усунення 
проблем та виконання завдань 
Для автоматизації роботи спортивних залів у сучасній практиці 
11 
ЧДТУ 262240.001 ПЗ 
використовуються різні програмні засоби: електронні таблиці, CRM-системи, 
спеціалізовані WEB-сервіси та мобільні застосунки. Їх основне призначення 
полягає у спрощенні обліку клієнтів, керуванні абонементами, формуванні 
розкладу тренувань, записі клієнтів на заняття, контролі відвідуваності, обліку 
платежів та аналізі діяльності закладу. 
Найпростішим засобом автоматизації є використання електронних 
таблиць, наприклад Microsoft Excel або Google Sheets. Вони дозволяють вести 
списки клієнтів, фіксувати оплати, контролювати залишок занять і формувати 
розклад. Перевагами такого підходу є простота, доступність і відсутність 
потреби у складному налаштуванні. Проте електронні таблиці не забезпечують 
повноцінного розмежування прав доступу, автоматичного контролю 
абонементів, зручного запису на тренування та інтеграції з мобільними 
пристроями. Зі збільшенням кількості клієнтів та тренувань такий спосіб стає 
незручним і підвищує ризик помилок. 
Іншим поширеним підходом є використання CRM-систем. Вони 
дозволяють зберігати інформацію про клієнтів, історію взаємодії, платежі та 
статуси обслуговування. Однак універсальні CRM-системи не завжди 
враховують специфіку спортивного залу, зокрема роботу з абонементами, 
груповими тренуваннями, тренерами, записами на заняття та контролем 
відвідуваності. Тому для фітнес-сфери доцільніше використовувати 
спеціалізовані системи керування спортивними закладами. 
Одним із відомих аналогів є Mindbody — платформа для керування 
бізнесом у сфері фітнесу та wellness-послуг. Вона підтримує роботу з 
клієнтами, розкладом занять, записами, продажами та платежами. Mindbody 
має вебінтерфейс і мобільні можливості для персоналу, що дозволяє керувати 
основними процесами без прив’язки до одного робочого місця [1]. 
Перевагою Mindbody (рис. 1.1) є широкий функціонал і можливість 
використання для різних типів фітнес-бізнесу. Недоліком є комерційна модель, 
складність налаштування та можлива надлишковість функцій. 
 
12 
ЧДТУ 262240.001 ПЗ 
 
Рисунок 1.1 – Приклад інтерфейсу системи Mindbody для керування розкладом 
і клієнтами [1] 
 
Ще одним аналогом є ABC Glofox (рис.1.2) — система керування 
спортивними залами та фітнес-студіями. Вона забезпечує керування клієнтами, 
абонементами, платежами, розкладом занять, записами та відвідуваністю. 
Також система має мобільний застосунок для клієнтів, через який вони можуть 
записуватися на заняття, переглядати інформацію про членство та взаємодіяти 
із закладом [2]. 
 
 
Рисунок 1.2 – Приклад адміністративної панелі Glofox [2] 
 
Перевагою Glofox є орієнтація саме на фітнес-бізнес, підтримка 
13 
ЧДТУ 262240.001 ПЗ 
мобільного застосунку та наявність аналітичних інструментів. До недоліків 
можна віднести залежність від хмарної платформи, вартість використання та 
обмежену гнучкість при адаптації під індивідуальні вимоги конкретного залу. 
До популярних рішень також належить Wodify (рис. 1.3). Це програмне 
забезпечення для спортивних залів, яке містить засоби планування занять, 
обліку клієнтів, автоматизації платежів, мобільного доступу та відстеження 
спортивних результатів. Особливістю Wodify є акцент на тренувальному 
процесі та прогресі клієнтів, що робить його корисним для CrossFit-залів і 
студій функціонального тренінгу. 
Перевагою Wodify є зручність для тренерів і клієнтів, а також можливість 
відстеження результатів занять. Водночас для невеликого спортивного залу 
частина функцій може бути зайвою, якщо основною метою є базове керування 
абонементами, тренуваннями та відвідуваністю [3]. 
 
 
Рисунок 1.3 – Приклад мобільного інтерфейсу Wodify для клієнтів спортивного 
залу [3] 
14 
ЧДТУ 262240.001 ПЗ 
Ще одним аналогом є GymMaster (рис. 1.4) — система для керування 
спортивними клубами, яка дозволяє вести облік клієнтів, абонементів, 
бронювань, платежів і доступу до залу. Вона підтримує онлайн-бронювання, 
мобільний застосунок, point-of-sale функції та інтеграцію з контролем доступу 
[4]. 
 
 
Рисунок 1.4 – Приклад інтерфейсу GymMaster  [4] 
 
Перевагою GymMaster є комплексність і можливість автоматизації не 
лише програмних, а й організаційних процесів, зокрема доступу до 
приміщення. Недоліком є складність системи та потреба в додатковому 
налаштуванні або обладнанні [4]. 
Аналіз аналогів показує, що більшість сучасних систем використовує 
клієнт-серверну або хмарну архітектуру. Серверна частина відповідає за 
збереження даних, бізнес-логіку, автентифікацію та обробку запитів. 
Клієнтська частина зазвичай реалізується у вигляді вебпанелі для 
адміністратора та мобільного застосунку для клієнтів або персоналу. Такий 
підхід є доцільним, оскільки різні категорії користувачів мають різні сценарії 
роботи із системою. 
15 
ЧДТУ 262240.001 ПЗ 
Для вирішення проблеми ручного обліку клієнтів у таких системах 
використовується централізована база даних. Для керування абонементами 
застосовуються модулі membership management, які контролюють строк дії, 
залишок занять і статус клієнта. Для запису на тренування використовуються 
модулі booking and scheduling, що дозволяють створювати розклад, призначати 
тренерів і обмежувати кількість учасників. Для контролю відвідуваності 
застосовуються check-in модулі, а для фінансових операцій — billing та payment 
processing. 
Отже, на ринку вже існують потужні рішення для автоматизації 
спортивних залів. Проте більшість із них є комерційними хмарними 
платформами, які можуть бути складними, дорогими або надлишковими для 
невеликих закладів. Тому розробка власного кросплатформенного додатку є 
доцільною. У ньому варто використати найкращі підходи аналогів: 
централізовану базу даних, клієнт-серверну архітектуру, вебпанель 
адміністратора, мобільний застосунок для клієнтів і тренерів, рольове 
розмежування доступу та модульну структуру системи. 
1.3. Конкретизація завдань роботи 
У результаті аналізу актуальних проблем автоматизації роботи 
спортивного залу та існуючих програмних засобів було визначено, що 
розроблювана система повинна забезпечувати комплексну підтримку основних 
процесів діяльності закладу. До таких процесів належать облік користувачів, 
керування абонементами, планування тренувань, запис клієнтів на заняття, 
контроль відвідуваності, облік платежів і надання користувачам доступу до 
актуальної інформації відповідно до їхньої ролі в системі. 
Для досягнення мети кваліфікаційної роботи необхідно виконати такі 
завдання: 
1 Проаналізувати предметну область автоматизації роботи спортивного 
залу, визначити основні бізнес-процеси закладу, учасників системи та 
проблеми, які виникають під час ручного або частково 
автоматизованого керування. 
16 
ЧДТУ 262240.001 ПЗ 
2 Сформувати функціональні вимоги до програмного забезпечення, 
зокрема вимоги до роботи адміністратора, тренера та клієнта, а також 
визначити основні можливості системи для кожної категорії 
користувачів. 
3 Сформувати нефункціональні вимоги до системи, зокрема вимоги до 
зручності використання, безпеки, надійності, кросплатформенності, 
швидкодії та підтримки рольового доступу. 
4 Спроєктувати загальну архітектуру програмної системи, 
передбачивши серверну частину, базу даних, адміністративну WEB-
панель і мобільний застосунок для клієнтів та тренерів. 
5 Розробити структуру бази даних, визначити основні сутності системи, 
серед яких користувачі, ролі, абонементні плани, активні абонементи, 
тренування, записи на тренування, відвідування та платежі. 
6 Реалізувати серверну частину додатку, яка забезпечує обробку 
запитів, взаємодію з базою даних, автентифікацію, авторизацію та 
виконання основної бізнес-логіки системи. 
7 Реалізувати адміністративну WEB-панель, яка дозволяє 
адміністратору керувати користувачами, абонементами, 
тренуваннями, платежами та переглядати статистичну інформацію. 
8 Розробити мобільний застосунок для клієнтів і тренерів, що 
забезпечує перегляд профілю, абонемента, доступних тренувань, 
запис на заняття, скасування бронювання, перегляд історії відвідувань 
і роботу тренера зі своїми заняттями. 
9 Реалізувати механізм рольового доступу, який обмежує можливості 
користувачів відповідно до їхньої ролі та забезпечує захист 
персональних і службових даних. 
10 Провести тестування розробленої системи, перевірити коректність 
роботи основних функцій, взаємодію між серверною частиною, 
вебпанеллю та мобільним застосунком, а також правильність 
обмеження доступу за ролями. 
17 
ЧДТУ 262240.001 ПЗ 
11 Оцінити результати виконаної роботи, визначити практичне значення 
розробленого додатку та можливості його подальшого вдосконалення. 
Виконання зазначених завдань дозволить створити кросплатформенний 
програмний додаток, придатний для автоматизації основних процесів роботи 
спортивного залу та покращення взаємодії між адміністрацією, тренерами й 
клієнтами. 
 
18 
ЧДТУ 262240.001 ПЗ 
Висновки до розділу 1 
Було проаналізовано предметну область автоматизації роботи 
спортивного залу та визначено основні проблеми, які виникають під час 
організації діяльності закладу. До них належать складність обліку клієнтів, 
керування абонементами, планування тренувань, запису на заняття, контролю 
відвідуваності, обліку платежів і розмежування доступу між різними 
категоріями користувачів. 
Розглянуто методи та засоби, які вже використовуються для розв’язання 
зазначених проблем, зокрема електронні таблиці, CRM-системи та 
спеціалізовані програмні платформи для керування спортивними залами. 
Аналіз аналогів показав, що наявні рішення мають широкий функціонал, однак 
можуть бути складними, дорогими або надлишковими для невеликих 
спортивних закладів. 
На основі проведеного аналізу було конкретизовано завдання 
кваліфікаційної роботи. Визначено необхідність розробки кросплатформенного 
додатку, який поєднує серверну частину, базу даних, адміністративну 
вебпанель і мобільний застосунок для клієнтів та тренерів. Такий підхід 
дозволить автоматизувати основні процеси роботи спортивного залу, 
підвищити зручність користування системою та покращити якість 
обслуговування клієнтів. 
19 
ЧДТУ 262240.001 ПЗ 
2 ВПРОВАДЖЕННЯ РЕЗУЛЬТАТІВ ДОСЛІДЖЕНЬ У ПРАКТИКУ 
ПРОЕКТУВАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ 
ІНФОРМАЦІЙНИХ СИСТЕМ  
2.1. Моделювання предметної області 
Моделювання предметної області є важливим етапом розробки 
програмного забезпечення, оскільки дозволяє визначити основні об’єкти 
системи, їхні властивості та зв’язки між ними. Для кросплатформенного 
додатку автоматизації роботи спортивного залу предметна область охоплює 
процеси обліку користувачів, керування абонементами, планування тренувань, 
запису клієнтів на заняття, контролю відвідуваності та обліку платежів. 
Розроблювана система призначена для трьох основних категорій 
користувачів: адміністратора, тренера та клієнта. Адміністратор виконує 
керування основними даними системи: користувачами, абонементними 
планами, активними абонементами, тренуваннями та платежами. Тренер 
працює зі своїми заняттями, переглядає список записаних клієнтів і відмічає 
відвідуваність. Клієнт має можливість переглядати власний профіль, активний 
абонемент, доступні тренування, здійснювати запис на заняття та переглядати 
історію відвідувань. 
Моделювання предметної області дає змогу описати логічну структуру 
майбутньої системи до етапу безпосередньої реалізації. Це дозволяє уникнути 
дублювання даних, визначити основні сутності та правильно організувати 
взаємозв’язки між ними. У межах даної роботи модель предметної області 
представлена у вигляді діаграми класів, яка відображає основні сутності 
програмного продукту та їхні відношення. 
Основними об’єктами предметної області є користувачі, ролі, 
абонементні плани, абонементи, тренування, записи на тренування, 
відвідування та платежі. Кожен із цих об’єктів відповідає окремій частині 
бізнес-логіки спортивного залу. Наприклад, об’єкт «Користувач» може 
представляти адміністратора, тренера або клієнта, а об’єкт «Тренування» 
20 
ЧДТУ 262240.001 ПЗ 
містить інформацію про заняття, його час, тренера та кількість доступних 
місць. 
Під час моделювання було враховано, що система має працювати як 
єдине інформаційне середовище. Усі клієнтські частини додатку, зокрема 
адміністративна вебпанель і мобільний застосунок, взаємодіють із серверною 
частиною, яка виконує обробку запитів і працює з базою даних. Такий підхід 
забезпечує централізоване збереження інформації та дозволяє підтримувати 
актуальність даних для всіх користувачів системи. 
2.1.1. Предметна область моделювання. Модель предметної області. 
Словник предметної області. 
Предметною областю моделювання є діяльність спортивного залу, яка 
пов’язана з організацією тренувального процесу, обслуговуванням клієнтів, 
керуванням абонементами, фіксацією відвідуваності та обліком платежів. У 
межах цієї предметної області розглядаються процеси взаємодії адміністратора, 
тренера та клієнта з програмною системою. 
Модель предметної області описує основні сутності, які 
використовуються в системі, а також зв’язки між ними. У розроблюваному 
додатку центральною сутністю є користувач. Залежно від ролі користувач може 
виконувати різні функції: адміністратор керує системою, тренер проводить 
заняття, а клієнт записується на тренування та користується абонементом. 
Користувачі з роллю клієнта можуть мати активні абонементи. Кожен 
абонемент створюється на основі певного абонементного плану, який визначає 
назву, вартість, строк дії та можливу кількість відвідувань. Абонемент 
пов’язаний із платежами, що дозволяє фіксувати факт оплати послуг 
спортивного залу. 
Тренування створюється адміністратором і пов’язується з конкретним 
тренером. Клієнт може записатися на тренування, після чого в системі 
створюється запис на заняття. Для кожного запису може бути зафіксована 
відвідуваність, яку відмічає тренер. Таким чином, система дозволяє 
контролювати не лише факт запису клієнта на заняття, а й реальне відвідування 
21 
ЧДТУ 262240.001 ПЗ 
тренування. 
Для узгодженого використання термінів у роботі сформовано словник 
предметної області. 
Розшгянемо тепер останній пункт цього підрозділу , в спмк словник 
предметної обдасті. Словник предметної області розглянемо у вигляді таблиці 
2.1 
Таблиця 2.1 
Словник предметної області 
Термін Опис 
Користувач Особа, яка має обліковий запис у системі та взаємодіє з 
додатком відповідно до своєї ролі. 
Адміністратор Користувач, який має повний доступ до керування 
системою, користувачами, абонементами, тренуваннями та 
платежами. 
Тренер Користувач, який проводить тренування, переглядає 
власний розклад і відмічає відвідуваність клієнтів. 
Клієнт Користувач, який користується послугами спортивного 
залу, має абонемент і записується на тренування. 
Роль Тип користувача в системі, який визначає його права 
доступу та доступні функції. 
Абонементний Шаблон абонемента, який містить назву, ціну, строк дії та 
план обмеження за кількістю відвідувань. 
Абонемент Активна послуга клієнта, створена на основі абонементного 
плану. 
Тренування Заплановане заняття у спортивному залі з визначеним 
тренером, часом проведення та кількістю місць. 
Запис на Зв’язок між клієнтом і тренуванням, який підтверджує 
тренування участь клієнта в занятті. 
22 
ЧДТУ 262240.001 ПЗ 
Продовження таблиці 2.1 
Відвідуваність Факт присутності клієнта на тренуванні, який фіксується 
тренером. 
Платіж Запис про оплату клієнтом абонемента або іншої послуги 
спортивного залу. 
На основі визначених сутностей було побудовано діаграму класів 
предметної області (рис. 2.1). Вона відображає структуру основних класів 
системи, їхні атрибути та зв’язки. Центральним класом є User, який пов’язаний 
з абонементами, тренуваннями, записами та відвідуваністю. Клас 
MembershipPlan описує типи абонементів, Membership — конкретний 
абонемент клієнта, Workout — тренування, Booking — запис клієнта на заняття, 
Attendance — факт відвідування, а Payment — оплату. 
Рисунок 2.1 – Діаграма класів крос-платформенного додатку для автоматизації 
роботи спортивного залу 
23 
ЧДТУ 262240.001 ПЗ 
2.1.2. Елементи моделювання предметної області 
Для моделювання предметної області кросплатформенного додатку 
автоматизації роботи спортивного залу доцільно використовувати уніфіковану 
мову моделювання UML. UML є стандартною графічною мовою, яка 
застосовується для опису структури, поведінки та взаємодії елементів 
програмної системи. Використання UML дає змогу подати систему у 
зрозумілому вигляді ще до етапу програмної реалізації, визначити основні 
сутності, зв’язки між ними, сценарії роботи користувачів і логіку виконання 
окремих процесів. 
У межах даної кваліфікаційної роботи UML використовується для 
побудови моделей, які описують програмну систему з різних сторін. Для 
відображення функціональних можливостей системи застосовується діаграма 
прецедентів, яка показує взаємодію користувачів із додатком. Для опису 
логічної структури системи використовується діаграма класів, що містить 
основні сутності предметної області, їхні атрибути та зв’язки. Для опису 
послідовності виконання окремих дій можуть використовуватися діаграми 
діяльності або послідовності [7]. 
Основними елементами UML, які використовуються під час 
моделювання, є класи, актори, прецеденти, пакети, примітки та різні типи 
зв’язків. Клас відображає сутність предметної області або програмний об’єкт, 
який має атрибути та операції. У межах розроблюваної системи класами 
можуть бути User, Membership, Workout, Booking, Attendance та Payment. 
Атрибути класу описують його властивості, наприклад ім’я користувача, дату 
початку абонемента або час проведення тренування, а операції можуть 
описувати дії, які виконує об’єкт. 
Актор у UML позначає зовнішню сутність, яка взаємодіє із системою [7]. 
У розроблюваному додатку акторами є адміністратор, тренер і клієнт. Вони 
мають різні права доступу та виконують різні сценарії роботи. Адміністратор 
керує користувачами, абонементами, тренуваннями та платежами. Тренер 
переглядає свої заняття та відмічає відвідуваність. Клієнт переглядає 
24 
ЧДТУ 262240.001 ПЗ 
абонемент, записується на тренування та переглядає історію відвідувань. 
Прецедент використовується для опису функції або дії, яку користувач 
може виконати в системі [7]. Наприклад, для клієнта прецедентами є 
«Переглянути розклад», «Записатися на тренування», «Скасувати бронювання» 
та «Переглянути абонемент». Для адміністратора прецедентами є «Створити 
тренування», «Видати абонемент», «Керувати користувачами» та «Переглянути 
статистику». 
На рисунку 2.2 наведено основні елементи UML, які будуть 
використовуватися надалі для побудови діаграм програмної системи. 
 
 
Рисунок 2.2 – Основні елементи UML для моделювання предметної 
області [7] 
 
Важливу роль у UML відіграють зв’язки між елементами. Асоціація 
показує загальний зв’язок між класами, наприклад між користувачем і 
абонементом. Напрямлена асоціація вказує напрям взаємодії або залежності. 
Узагальнення використовується для відображення спадкування або відношення 
25 
ЧДТУ 262240.001 ПЗ 
типу «є різновидом». Агрегація та композиція описують відношення «ціле — 
частина». Залежність показує, що один елемент використовує інший або 
залежить від нього. Кратність зв’язку дозволяє вказати кількість об’єктів, які 
можуть брати участь у відношенні, наприклад один користувач може мати 
багато платежів або багато записів на тренування. 
Для групування логічно пов’язаних елементів UML використовується 
пакет. Наприклад, класи системи можна умовно поділити на пакети, що 
відповідають за користувачів, абонементи, тренування, платежі та авторизацію. 
Примітка застосовується для додавання пояснень до елементів діаграми або 
уточнення певних умов роботи системи. 
2.1.3. Робоча область моделювання 
Робоча область моделювання визначає межі програмної системи, її 
основних учасників, функції та інформаційні об’єкти, з якими вона працює. Для 
кросплатформенного додатку автоматизації роботи спортивного залу робочою 
областю є процеси керування діяльністю спортивного закладу, які виконуються 
адміністратором, тренером і клієнтом за допомогою вебпанелі та мобільного 
застосунку. 
Розроблювана система охоплює основні операції, необхідні для щоденної 
роботи спортивного залу: реєстрацію та облік користувачів, призначення ролей, 
створення абонементних планів, видачу абонементів клієнтам, планування 
тренувань, запис клієнтів на заняття, фіксацію відвідуваності та облік платежів. 
Усі ці процеси пов’язані між собою та повинні працювати в єдиному 
інформаційному середовищі. 
Основними користувачами системи є адміністратор, тренер і клієнт. 
Адміністратор має найбільший набір прав і відповідає за керування даними 
спортивного залу. Він створює та редагує облікові записи користувачів, додає 
тренерів і клієнтів, формує абонементні плани, видає клієнтам абонементи, 
створює тренування, контролює платежі та переглядає статистичну 
інформацію. Для адміністратора основним інструментом взаємодії із системою 
26 
ЧДТУ 262240.001 ПЗ 
є вебпанель, оскільки вона зручна для роботи з таблицями, формами та великою 
кількістю даних. 
Тренер використовує систему для перегляду власного розкладу занять і 
роботи зі списком клієнтів, які записалися на тренування. Після проведення 
заняття тренер має можливість відмітити присутність клієнтів. Це дозволяє 
системі зберігати історію відвідувань і забезпечувати актуальність інформації 
про використання абонемента. 
Клієнт взаємодіє із системою через мобільний застосунок. Він може 
переглядати власний профіль, інформацію про активний абонемент, доступні 
тренування, записуватися на заняття, скасовувати бронювання та переглядати 
історію відвідувань. Такий підхід робить взаємодію клієнта зі спортивним 
залом більш зручною, оскільки основні дії можна виконати самостійно без 
звернення до адміністратора. 
Робоча область моделювання також включає серверну частину та базу 
даних. Серверна частина виконує обробку запитів від вебпанелі та мобільного 
застосунку, перевіряє права доступу користувачів, реалізує бізнес-логіку й 
забезпечує взаємодію з базою даних. База даних зберігає інформацію про 
користувачів, ролі, абонементні плани, активні абонементи, тренування, записи 
на заняття, відвідуваність і платежі. 
Під час побудови моделі робочої області важливо визначити межі 
системи. До внутрішніх елементів належать вебпанель адміністратора, 
мобільний застосунок, серверна частина, база даних і програмні модулі, що 
реалізують основну логіку роботи. До зовнішніх учасників належать 
користувачі системи: адміністратор, тренер і клієнт. У межах цієї роботи не 
передбачається інтеграція з банківськими сервісами, турнікетами або 
зовнішніми CRM-системами, однак архітектура додатку може бути розширена 
для підтримки таких можливостей у майбутньому. 
Отже, робоча область моделювання кросплатформенного додатку 
охоплює основні процеси автоматизації спортивного залу та взаємодію 
користувачів із системою (рис. 2.3). Побудова такої моделі дозволяє наочно 
27 
ЧДТУ 262240.001 ПЗ 
показати, які компоненти входять до складу програмного продукту, які 
користувачі з ними взаємодіють і які дані обробляються в межах системи. 
Рисунок 2.3 – Модель робочої області автоматизованої системи онлайн-
прокторингу 
2.2. Формування та аналіз вимог 
2.2.1. Формування вимог до програмного забезпечення. Первинні і 
детальні вимоги. Вимоги замовника і розробника. Функціональні та 
нефункціональні вимоги  
Формування вимог до програмного забезпечення є одним із ключових 
етапів розробки інформаційної системи. На цьому етапі визначається, які саме 
задачі повинна виконувати система, які категорії користувачів будуть із нею 
взаємодіяти, які дані необхідно зберігати та які обмеження мають бути 
враховані під час реалізації. Для кросплатформенного додатку автоматизації 
28 
ЧДТУ 262240.001 ПЗ 
роботи спортивного залу вимоги формуються на основі аналізу предметної 
області, актуальних проблем і функцій, які необхідні для щоденної роботи 
закладу. 
Вимоги до програмного забезпечення можна поділити на первинні та 
детальні. 
Первинні вимоги відображають загальні потреби замовника або 
користувачів системи. Вони формулюються природною мовою і описують, що 
саме має забезпечувати система без уточнення технічних способів реалізації. 
Наприклад, система повинна дозволяти адміністратору створювати тренування, 
клієнту записуватися на заняття, а тренеру відмічати відвідуваність. 
Детальні вимоги уточнюють первинні вимоги та описують їх у більш 
конкретній формі. Вони визначають правила роботи окремих функцій, 
необхідні дані, умови виконання дій, обмеження доступу та очікуваний 
результат. Наприклад, якщо первинна вимога полягає в можливості запису 
клієнта на тренування, то детальна вимога повинна уточнювати, що запис 
можливий лише за наявності активного абонемента, вільних місць на занятті та 
відсутності дублювання бронювання. 
Також вимоги поділяються на вимоги замовника та вимоги розробника.      
Вимоги замовника описують очікувану поведінку системи з точки зору 
користувача. Вони є зрозумілими для адміністратора спортивного залу, тренера 
або клієнта.  
Для розроблюваного додатку можна визначити такі основні первинні 
вимоги замовника: 
1 Система повинна забезпечувати авторизацію користувачів.  
2 Система повинна підтримувати різні ролі користувачів: 
адміністратора, тренера та клієнта.  
3 Адміністратор повинен мати можливість керувати користувачами.  
4 Адміністратор повинен мати можливість створювати та редагувати 
абонементні плани.  
29 
ЧДТУ 262240.001 ПЗ 
5 Адміністратор повинен мати можливість видавати клієнтам 
абонементи.  
6 Адміністратор повинен мати можливість створювати та редагувати 
тренування.  
7 Клієнт повинен мати можливість переглядати доступні тренування.  
8 Клієнт повинен мати можливість записуватися на тренування та 
скасовувати бронювання.  
9 Тренер повинен мати можливість переглядати власні тренування.  
10 Тренер повинен мати можливість відмічати відвідуваність клієнтів.  
11 Система повинна зберігати інформацію про платежі.  
12 Система повинна надавати адміністраторам статистичну інформацію 
про роботу спортивного залу.  
Вимоги розробника деталізують ці очікування у вигляді технічних і 
логічних правил, які необхідно реалізувати в програмному коді, базі даних та 
інтерфейсах користувача. 
На основі первинних вимог формуються детальні вимоги розробника: 
1 Під час входу користувача в систему повинна виконуватися перевірка 
електронної пошти та пароля.  
2 Після успішної авторизації система повинна надавати користувачу 
доступ лише до функцій, дозволених його роллю.  
3 Паролі користувачів повинні зберігатися в захищеному вигляді.  
4 Для адміністратора повинні бути доступні функції створення, 
перегляду, редагування та видалення користувачів.  
5 Для кожного користувача повинні зберігатися ім’я, електронна пошта, 
пароль, роль, номер телефону, баланс та службові дати створення й 
оновлення запису.  
6 Для абонементного плану повинні зберігатися назва, опис, ціна, строк 
дії та кількість доступних відвідувань.  
7 Під час видачі абонемента клієнту система повинна пов’язувати його з 
конкретним користувачем і абонементним планом.  
30 
ЧДТУ 262240.001 ПЗ 
8 Для активного абонемента повинні зберігатися дата початку, дата 
завершення, залишок відвідувань і статус.  
9 Тренування повинно містити назву, опис, тренера, дату й час початку, 
дату й час завершення та максимальну кількість учасників.  
10 Клієнт може записатися на тренування лише за наявності вільних 
місць і активного абонемента.  
11 Система не повинна дозволяти клієнту повторно записуватися на одне 
й те саме тренування.  
12 Після скасування бронювання запис повинен змінювати свій статус, а 
місце на тренуванні має ставати доступним для інших клієнтів.  
13 Тренер повинен бачити лише ті тренування, які призначені саме йому.  
14 Відмітка відвідуваності повинна бути пов’язана з конкретним 
клієнтом, тренуванням і записом на тренування.  
15 Платіж повинен бути пов’язаний із користувачем та, за потреби, з 
конкретним абонементом.  
16 Адміністратор повинен мати можливість переглядати статистику 
щодо кількості користувачів, тренувань, записів, відвідувань і 
платежів.  
Функціональні вимоги описують конкретні дії, які повинна виконувати 
система. Для розроблюваного кросплатформенного додатку основними 
функціональними вимогами є таблиця 2.2: 
 
Таблиця 2.2 
Функціональні вимоги 
№ Функціональна вимога Користувач 
1 Авторизація в системі Адміністратор, тренер, клієнт 
2 Перегляд і редагування профілю Адміністратор, тренер, клієнт 
3 Керування користувачами Адміністратор 
4 Керування абонементними планами Адміністратор 
 
31 
ЧДТУ 262240.001 ПЗ 
Продовження таблиці 2.2 
5 Видача абонементів клієнтам Адміністратор 
6 Створення та редагування тренувань Адміністратор 
7 Перегляд розкладу тренувань Тренер, клієнт 
8 Запис на тренування Клієнт 
9 Скасування бронювання Клієнт 
10 Відмітка відвідуваності Тренер 
11 Облік платежів Адміністратор 
12 Перегляд статистики Адміністратор 
13 Перегляд активного абонемента Клієнт 
14 Перегляд історії відвідувань Клієнт 
 
Нефункціональні вимоги визначають якісні характеристики системи, які 
впливають на її надійність, безпеку, зручність і можливість подальшого 
розвитку. Для розроблюваного програмного забезпечення визначено такі 
нефункціональні вимоги таблиця 2.3: 
 
Таблиця 2.3 
Нефункціональні вимоги 
№ Нефункціональна 
Опис 
вимога 
1 Кросплатформенність Система повинна працювати через вебпанель і 
мобільний застосунок. 
2 Безпека Доступ до функцій системи повинен надаватися 
лише авторизованим користувачам. 
3 Рольове розмежування Користувачі повинні мати доступ лише до тих 
доступу функцій, які відповідають їхній ролі. 
4 Надійність Система повинна коректно обробляти помилки 
та не допускати втрати даних. 
32 
ЧДТУ 262240.001 ПЗ 
Продовження таблиці 2.3 
5 Цілісність даних Дані про користувачів, абонементи, тренування, 
бронювання, відвідування та платежі повинні 
бути узгодженими. 
6 Зручність інтерфейсу Інтерфейс повинен бути зрозумілим для 
адміністратора, тренера та клієнта. 
7 Масштабованість Архітектура системи повинна дозволяти 
додавати нові функції без повної переробки 
програмного продукту. 
8 Швидкодія Основні операції повинні виконуватися без 
значних затримок. 
9 Підтримуваність Код системи повинен мати зрозумілу структуру 
та бути придатним для подальшого 
вдосконалення. 
Таким чином, сформовані вимоги визначають основні функції та якісні 
характеристики майбутньої системи. Їх урахування під час проектування та 
реалізації дозволить створити програмний продукт, який відповідає потребам 
спортивного залу, забезпечує зручну взаємодію користувачів із системою та 
підтримує автоматизацію основних бізнес-процесів закладу. 
2.2.2. Формування вимог за допомогою діаграми прецедентів 
Діаграма прецедентів використовується для відображення функціональних 
вимог до програмної системи з погляду її користувачів (рис. 2.4). Вона показує, 
які категорії користувачів взаємодіють із системою та які основні дії вони 
можуть виконувати [10]. Для кросплатформенного додатку автоматизації 
роботи спортивного залу така діаграма дозволяє визначити межі системи, 
основні ролі користувачів і набір функцій, які повинні бути реалізовані. 
У розроблюваній системі передбачено три основні актори: адміністратор, 
тренер і клієнт. Кожен із них має власні функціональні можливості відповідно  
33 
ЧДТУ 262240.001 ПЗ 
 
Рисунок 2.4 – Діаграма прецедентів кросплатформенного додатку для 
автоматизації роботи спортивного залу 
34 
ЧДТУ 262240.001 ПЗ 
до ролі. Адміністратор здійснює керування системою: працює з користувачами, 
абонементами, тренуваннями, платежами та статистикою. Тренер переглядає 
власний розклад і відмічає відвідуваність клієнтів. Клієнт переглядає 
інформацію про свій профіль і абонемент, записується на тренування, скасовує 
бронювання та переглядає історію відвідувань. 
Спільним прецедентом для всіх користувачів є авторизація в системі, оскільки 
доступ до функцій додатку надається лише після входу в обліковий запис. 
Після авторизації система визначає роль користувача та надає доступ тільки до 
тих функцій, які передбачені для відповідної ролі. Такий підхід забезпечує 
розмежування прав доступу та захист персональних і службових даних. 
Діаграма прецедентів є основою для подальшого проектування системи, 
оскільки на її основі можна деталізувати сценарії роботи користувачів, 
визначити необхідні модулі програмного забезпечення та сформувати 
структуру інтерфейсів вебпанелі й мобільного застосунку (рис. 2.4). 
У наведеній вище діаграмі показано основні функціональні можливості 
системи та їхній розподіл між акторами. Адміністратор працює переважно з 
адміністративною вебпанеллю, а тренер і клієнт — з мобільним застосунком. 
Усі прецеденти включають авторизацію, оскільки доступ до функцій системи 
можливий лише для зареєстрованих користувачів із відповідними правами. 
2.3. Проектування логічної структури програмного комплексу 
2.3.1. Діаграми класів 
Діаграма класів є одним з основних засобів UML для опису логічної 
структури програмної системи. Вона відображає класи, їхні атрибути, методи та 
зв’язки між ними [9]. Для кросплатформенного додатку автоматизації роботи 
спортивного залу діаграми класів доцільно поділити на три частини: діаграму 
класів серверної частини, адміністративної WEB-панелі та мобільного 
застосунку. 
Такий поділ зумовлений архітектурою розроблюваної системи. Серверна 
частина відповідає за роботу з базою даних, бізнес-логіку, автентифікацію, 
35 
ЧДТУ 262240.001 ПЗ 
авторизацію та обробку запитів. Адміністративна вебпанель забезпечує 
інтерфейс для керування користувачами, абонементами, тренуваннями, 
платежами та статистикою. Мобільний застосунок використовується клієнтами 
й тренерами для перегляду розкладу, запису на заняття, перегляду абонемента 
та відмітки відвідуваності. 
 
 
Рисунок 2.5 – Діаграма класів серверної частини 
 
Серверна частина є центральним компонентом системи. Вона приймає 
запити від вебпанелі та мобільного застосунку, перевіряє права доступу 
користувачів, виконує основну бізнес-логіку та взаємодіє з базою даних. 
Основними класами серверної частини є контролери, сервіси та моделі даних. 
Контролери відповідають за приймання HTTP-запитів, сервіси реалізують 
36 
ЧДТУ 262240.001 ПЗ 
логіку роботи системи, а моделі описують основні сутності предметної області 
(рис. 2.5). 
Адміністративна вебпанель призначена для роботи адміністратора 
спортивного залу. Вона містить сторінки та компоненти, які забезпечують 
керування основними об’єктами системи. Логіка вебпанелі побудована навколо 
взаємодії сторінок з API-клієнтом, який надсилає запити до серверної частини 
(рис. 2.6). 
 
 
Рисунок 2.6 – Діаграма класів адіміністративної вебпанелі 
 
Мобільний застосунок призначений для клієнтів і тренерів. Клієнт 
використовує його для перегляду абонемента, розкладу тренувань, запису на 
заняття та перегляду історії відвідувань. Тренер за допомогою мобільного 
застосунку переглядає власні заняття та відмічає присутність клієнтів. Як і 
вебпанель, мобільний застосунок взаємодіє із серверною частиною через API 
(рис. 2.7). 
37 
ЧДТУ 262240.001 ПЗ 
Рисунок 2.7 – Діаграма класів мобільного застосунку 
Отже, побудовані діаграми класів відображають логічну структуру трьох 
основних частин системи: серверної частини, адміністративної вебпанелі та 
мобільного застосунку. Такий поділ дозволяє краще описати відповідальність 
кожного компонента, зв’язки між класами та принцип взаємодії клієнтських 
частин із сервером. 
2.3.2. Діаграми пакетів 
Діаграма пакетів використовується для відображення загальної логічної 
структури програмного комплексу та групування його елементів за 
функціональним призначенням. На відміну від діаграми класів, яка деталізує 
окремі класи, діаграма пакетів показує більші частини системи та залежності 
між ними (рис. 2.8). 
Для кросплатформенного додатку автоматизації роботи спортивного залу 
доцільно виділити три основні пакети: серверну частину, адміністративну 
вебпанель і мобільний застосунок. Серверна частина є центральним пакетом, 
оскільки саме вона обробляє запити, виконує бізнес-логіку, перевіряє права 
доступу та працює з базою даних. Адміністративна вебпанель і мобільний 
38 
ЧДТУ 262240.001 ПЗ 
застосунок залежать від серверної частини, оскільки отримують і передають 
дані через API. 
У серверній частині виділяються окремі пакети для автентифікації, 
користувачів, абонементів, тренувань, бронювань, відвідуваності, платежів і 
роботи з базою даних. Така модульна структура спрощує підтримку 
програмного коду, дозволяє незалежно змінювати окремі частини системи та 
полегшує подальше розширення функціональності. 
Адміністративна вебпанель містить пакети сторінок, компонентів, 
сервісів API та керування станом авторизації. Мобільний застосунок має 
подібну структуру, але орієнтований на інші сценарії використання: перегляд 
профілю, розкладу, абонемента, запис на тренування та роботу тренера з 
відвідуваністю. 
 
 
Рисунок 2.8 – Діаграма пакетів кросплатформенного додатку 
39 
ЧДТУ 262240.001 ПЗ 
З наведеної вище діаграми видно, що система має модульну структуру. 
Клієнтські частини не працюють безпосередньо з базою даних, а взаємодіють із 
нею тільки через серверну частину. Це дозволяє централізовано контролювати 
доступ до даних, зменшити дублювання логіки та забезпечити більшу 
надійність програмного комплексу. 
2.4. Архітектурне проектування 
2.4.1. Діаграма компонентів 
Діаграма компонентів використовується для відображення фізичної та 
логічної структури програмної системи на рівні окремих компонентів. Вона 
показує, з яких частин складається програмний комплекс, як ці частини 
взаємодіють між собою та які інтерфейси використовуються для обміну даними 
(рис 2.9). 
Кросплатформенний додаток для автоматизації роботи спортивного залу 
складається з трьох основних програмних частин: адміністративної вебпанелі, 
мобільного застосунку та серверної частини. Адміністративна вебпанель 
використовується адміністратором для керування користувачами, 
абонементами, тренуваннями, платежами та статистикою. Мобільний 
застосунок призначений для клієнтів і тренерів, які за його допомогою 
переглядають розклад, записуються на тренування, переглядають абонементи 
та працюють із відвідуваністю. 
Центральним компонентом системи є серверна частина, яка реалізує 
REST API. Вона приймає HTTP-запити від вебпанелі та мобільного застосунку, 
виконує автентифікацію, авторизацію, обробляє бізнес-логіку та взаємодіє з 
базою даних. Для доступу до даних використовується ORM-рівень, який 
забезпечує роботу з таблицями бази даних і спрощує виконання операцій 
читання, створення, оновлення та видалення записів. 
База даних PostgreSQL є окремим компонентом системи, у якому 
зберігається інформація про користувачів, ролі, абонементи, тренування, записи 
на тренування, відвідуваність і платежі. Клієнтські частини не звертаються до 
40 
ЧДТУ 262240.001 ПЗ 
бази даних безпосередньо, а працюють із нею тільки через серверну частину. 
Це дозволяє централізовано контролювати доступ, забезпечити безпеку даних і 
підтримувати єдину бізнес-логіку для всіх користувачів. 
Рис 2.11 - Діаграма компонентів кросплатформенного додатку 
Наведена діаграма показує, що система побудована за клієнт-серверною 
архітектурою. Вебпанель і мобільний застосунок виконують роль клієнтських 
інтерфейсів, а серверна частина забезпечує обробку запитів і взаємодію з базою 
даних. Такий підхід дозволяє розділити відповідальність між компонентами, 
спростити супровід системи та забезпечити можливість її подальшого 
розширення. 
2.4.2. Розгортання програмної системи на апаратних засобах. Діаграма 
розгортання 
41 
ЧДТУ 262240.001 ПЗ 
Діаграма розгортання використовується для відображення фізичного 
розміщення компонентів програмної системи на апаратних засобах. Вона 
показує, на яких пристроях або серверах працюють окремі частини 
програмного комплексу та яким способом вони взаємодіють між собою (рис. 
2.10). 
 
 
Рисунок 2.10 – Діаграма розгортання крос-платформенного додатку 
 
Кросплатформенний додаток для автоматизації роботи спортивного залу 
передбачає використання кількох типів апаратних засобів. Адміністратор 
працює із системою через персональний комп’ютер або ноутбук, на якому у 
42 
ЧДТУ 262240.001 ПЗ 
браузері відкривається адміністративна вебпанель. Клієнти та тренери 
використовують мобільні пристрої з установленим мобільним застосунком. Усі 
клієнтські пристрої взаємодіють із сервером застосунку через мережу Інтернет 
за допомогою HTTP/HTTPS-запитів. 
На сервері застосунку розміщується серверна частина, реалізована на 
Node.js та Express. Вона приймає запити від вебпанелі та мобільного 
застосунку, виконує автентифікацію, авторизацію, обробляє бізнес-логіку та 
звертається до бази даних. Окремим вузлом є сервер бази даних PostgreSQL, у 
якому зберігаються дані про користувачів, абонементи, тренування, 
бронювання, відвідуваність і платежі. 
Адміністративна вебпанель, реалізована за допомогою React і Vite, може 
бути розгорнута на вебсервері або сервісі статичного хостингу. Мобільний 
застосунок React Native/Expo встановлюється на смартфони клієнтів і тренерів. 
Такий варіант розгортання забезпечує доступ до системи з різних пристроїв і 
дозволяє централізовано зберігати дані на сервері. 
Отже, діаграма розгортання показує, що клієнтські частини системи 
розміщуються на пристроях користувачів, а основна логіка роботи та 
збереження даних зосереджені на сервері застосунку й сервері бази даних. 
Завдяки такому розгортанню система може одночасно обслуговувати різні 
категорії користувачів і забезпечувати доступ до актуальної інформації з різних 
платформ. 
2.5. Моделювання поведінки системи 
2.5.1. Діаграма діяльності 
Діаграма діяльності використовується для опису послідовності дій, які 
виконуються під час роботи програмної системи. Вона дозволяє показати 
логіку виконання окремого процесу, умови переходу між діями, альтернативні 
сценарії та завершення процесу. Для кросплатформенного додатку 
автоматизації роботи спортивного залу доцільно розглянути три основні 
43 
ЧДТУ 262240.001 ПЗ 
процеси: авторизацію користувача, запис клієнта на тренування та відмітку 
відвідуваності тренером. 
Перша діаграма діяльності описує процес авторизації користувача (рис. 
2.11). Користувач вводить електронну пошту та пароль, після чого система 
перевіряє правильність введених даних. Якщо дані некоректні, користувач 
отримує повідомлення про помилку. Якщо дані правильні, система визначає 
роль користувача та надає доступ до відповідного інтерфейсу: адміністративної 
вебпанелі або мобільного застосунку. 
 
 
Рисунок 2.11 – Діаграма діяльності процесу авторизації користувача 
 
Друга діаграма діяльності описує процес запису клієнта на тренування 
(рис. 2.12). Клієнт відкриває список доступних тренувань, обирає потрібне 
заняття та надсилає запит на бронювання. Система перевіряє наявність 
активного абонемента, вільних місць на тренуванні та відсутність повторного 
запису. Якщо всі умови виконано, створюється бронювання, а клієнт отримує 
44 
ЧДТУ 262240.001 ПЗ 
підтвердження запису. Якщо хоча б одна умова не виконана, система 
повідомляє про причину відмови. 
 
 
Рисунок 2.12 – Діаграма діяльності процесу запису клієнта на тренування 
 
Третя діаграма діяльності описує процес відмітки відвідуваності 
тренером (рис. 2.13). Тренер відкриває власний розклад, обирає конкретне 
тренування та переглядає список клієнтів, які записані на заняття. Після 
проведення тренування тренер відмічає присутніх і відсутніх клієнтів. Система 
45 
ЧДТУ 262240.001 ПЗ 
зберігає дані про відвідуваність і, за потреби, оновлює залишок відвідувань в 
абонементі клієнта. 
 
 
Рисунок 2.13 – Діаграма діяльності процесу відмітки відвідуваності для 
тренера  
 
Таким чином, наведені діаграми діяльності деталізують основні сценарії 
роботи системи та показують логіку взаємодії користувачів із програмним 
забезпеченням. Вони допомагають визначити послідовність операцій, 
перевірки, які повинні виконуватися системою, та можливі альтернативні 
варіанти завершення процесів. 
46 
ЧДТУ 262240.001 ПЗ 
2.5.2. Діаграма послідовності 
Діаграма послідовності використовується для відображення взаємодії 
об’єктів системи у часі. Вона показує, у якій послідовності користувач або 
компонент системи надсилає запити, які модулі їх обробляють і які відповіді 
повертаються після виконання операцій [11]. Для кросплатформенного додатку 
автоматизації роботи спортивного залу доцільно розглянути процес запису 
клієнта на тренування, оскільки він охоплює взаємодію мобільного застосунку, 
серверної частини, сервісів бізнес-логіки та бази даних. 
У цьому сценарії клієнт відкриває мобільний застосунок, переглядає 
список доступних тренувань і обирає заняття для запису. Мобільний застосунок 
надсилає запит до серверної частини. Сервер перевіряє авторизацію 
користувача, наявність активного абонемента, доступність вільних місць і 
відсутність повторного запису на це саме тренування. Якщо всі умови виконані, 
система створює запис на тренування та повертає клієнту повідомлення про 
успішне бронювання (рис. 2.14). 
 
 
Рисунок 2.14 –Діаграма послідовності процесу запису клієнта на тренування 
47 
ЧДТУ 262240.001 ПЗ 
Отже наведена діаграма послідовності показує порядок взаємодії між 
клієнтом, мобільним застосунком, серверною частиною та базою даних. Вона 
демонструє, що перед створенням запису система виконує кілька перевірок: 
авторизацію користувача, наявність активного абонемента, доступність місць 
на тренуванні та відсутність дублювання запису. Це дозволяє забезпечити 
коректність процесу бронювання та уникнути помилок під час роботи системи. 
2.5.3. Діаграма комунікації 
Діаграма комунікації використовується для відображення взаємодії між 
об’єктами системи під час виконання певного сценарію. На відміну від 
діаграми послідовності, основна увага в діаграмі комунікації приділяється не 
часовій шкалі, а зв’язкам між учасниками процесу та повідомленням, які вони 
передають один одному. 
 
 
Рисунок 2.15 – Діаграма комунікації процесу запита клієнта на тренування 
48 
ЧДТУ 262240.001 ПЗ 
Для розроблюваного кросплатформенного додатку доцільно розглянути 
комунікацію під час процесу запису клієнта на тренування. У цьому сценарії 
беруть участь клієнт, мобільний застосунок, REST API, модуль авторизації, 
модуль бронювань, модуль абонементів, модуль тренувань і база даних. Такий 
процес є важливим, оскільки перед створенням запису система повинна 
виконати кілька перевірок: авторизацію користувача, наявність активного 
абонемента, доступність вільних місць і відсутність повторного запису (рис. 
2.15). 
Після того як клієнт обирає тренування в мобільному застосунку, 
застосунок надсилає запит до серверної частини. Сервер перевіряє токен 
користувача та передає запит до модуля бронювань. Модуль бронювань 
звертається до модуля абонементів для перевірки активного абонемента та до 
модуля тренувань для перевірки кількості вільних місць. Після успішного 
проходження перевірок система створює запис у базі даних і повертає клієнту 
повідомлення про успішне бронювання. 
Таким чином, діаграма комунікації демонструє структуру взаємодії між 
основними об’єктами системи під час запису клієнта на тренування. Нумерація 
повідомлень показує порядок виконання операцій, а зв’язки між об’єктами 
відображають, які компоненти беруть участь у процесі обробки запиту. Така 
модель допомагає уточнити відповідальність окремих модулів і показує, що 
створення бронювання виконується лише після проходження всіх необхідних 
перевірок. 
2.5.4. Діаграма скінченного автомату 
Діаграма скінченного автомату використовується для опису станів 
об’єкта та переходів між ними залежно від подій, що відбуваються в системі 
[12]. У межах кросплатформенного додатку для автоматизації роботи 
спортивного залу доцільно змоделювати життєвий цикл запису клієнта на 
тренування, оскільки цей об’єкт проходить кілька важливих станів: створення 
бронювання, підтвердження участі, скасування, фіксація відвідування або 
неявки. 
49 
ЧДТУ 262240.001 ПЗ 
Об’єкт «Запис на тренування» створюється після того, як клієнт обирає 
заняття та надсилає запит на бронювання. Перед створенням запису система 
перевіряє наявність активного абонемента, вільних місць і відсутність 
дублювання запису. Якщо перевірки успішні, запис переходить у стан 
«Заброньовано». До початку тренування клієнт може скасувати бронювання, 
після чого запис переходить у стан «Скасовано». 
Після проведення тренування тренер відмічає відвідуваність клієнта. 
Якщо клієнт був присутній, запис переходить у стан «Відвідано», а система 
може зменшити кількість доступних відвідувань в абонементі. Якщо клієнт не 
прийшов на заняття, запис переходить у стан «Неявка». Завершальні стани 
показують, що подальші зміни для цього запису вже не виконуються або 
виконуються лише адміністратором у разі необхідності коригування даних 
(рис. 2.16). 
 
 
Рисунок 2.16 – Діаграма скінченного автомату запису клієнта на тренування 
50 
ЧДТУ 262240.001 ПЗ 
Наведена вище діаграма показує можливі стани запису клієнта на 
тренування та умови переходу між ними. Така модель дозволяє краще описати 
логіку роботи модуля бронювань і відвідуваності, а також визначити правила, 
за якими система повинна змінювати статус запису під час виконання основних 
операцій. 
 
51 
ЧДТУ 262240.001 ПЗ 
Висновки до розділу 2 
Було виконано проектування кросплатформенного додатку для 
автоматизації роботи спортивного залу. Було визначено предметну область 
системи, основні сутності, їхні зв’язки та сформовано словник предметної 
області. На основі цього побудовано модель робочої області та діаграму класів, 
що відображає логічну структуру програмного продукту. 
Сформовано вимоги до програмного забезпечення, зокрема первинні й 
детальні вимоги, вимоги замовника і розробника, функціональні та 
нефункціональні вимоги. За допомогою діаграми прецедентів визначено 
основні ролі користувачів системи: адміністратора, тренера та клієнта, а також 
функції, доступні кожній із цих ролей. 
У процесі проектування логічної та архітектурної структури системи було 
побудовано діаграми класів, пакетів, компонентів і розгортання. 
Для моделювання поведінки системи було розроблено діаграми 
діяльності, послідовності, комунікації та скінченного автомату. Вони 
деталізують основні сценарії роботи додатку, зокрема авторизацію 
користувача, запис клієнта на тренування, відмітку відвідуваності та зміну 
станів бронювання. 
Отже, у розділі було сформовано повну проектну основу програмної 
системи, яка визначає її структуру, функціональні можливості, архітектуру та 
логіку взаємодії користувачів із додатком. Це створює основу для подальшої 
реалізації та тестування програмного забезпечення. 
 
52 
ЧДТУ 262240.001 ПЗ 
3 РОЗРОБКА ТА ТЕСТУВАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ 
3.1 .  Розробка програмного комплексу 
3.1.1. Обґрунтування вибору засобів реалізації 
Для реалізації кросплатформенного додатку автоматизації роботи 
спортивного залу було обрано набір сучасних програмних засобів, які 
дозволяють створити серверну частину, адміністративну вебпанель, мобільний 
застосунок і базу даних. Основними критеріями вибору технологій були 
кросплатформенність, зручність розробки, швидкість створення інтерфейсів, 
підтримка клієнт-серверної архітектури, можливість масштабування та 
наявність активної спільноти розробників [13]. 
Для серверної частини було обрано Node.js. Ця платформа дозволяє 
виконувати JavaScript-код на сервері та добре підходить для створення 
вебсервісів і REST API. Перевагою Node.js є висока швидкодія під час обробки 
великої кількості запитів, зручна робота з асинхронними операціями та 
можливість використовувати одну мову програмування як на клієнтській, так і 
на серверній частині системи [14]. 
Для побудови серверного API використано фреймворк Express.js. Він має 
просту структуру, дозволяє швидко створювати маршрути, обробляти HTTP-
запити, підключати middleware та організовувати серверну логіку. За 
допомогою Express.js реалізовано маршрути для авторизації, керування 
користувачами, абонементами, тренуваннями, бронюваннями, відвідуваністю 
та платежами. 
Для збереження даних обрано систему керування базами даних 
PostgreSQL. Вона є надійною реляційною СКБД, яка підтримує складні зв’язки 
між таблицями, транзакції, обмеження цілісності та ефективну роботу з 
великими обсягами даних. PostgreSQL добре підходить для розроблюваної 
системи, оскільки її предметна область містить багато взаємопов’язаних 
сутностей: користувачів, ролі, абонементи, тренування, записи на заняття, 
відвідуваність і платежі. 
53 
ЧДТУ 262240.001 ПЗ 
Для спрощення взаємодії серверної частини з базою даних використано 
Prisma ORM. Prisma дозволяє описувати структуру бази даних у вигляді схеми, 
автоматично генерувати клієнт для роботи з даними та виконувати запити без 
написання великої кількості SQL-коду вручну. Це зменшує ймовірність 
помилок, підвищує зручність розробки та робить код більш зрозумілим і 
підтримуваним. 
Для реалізації механізму автентифікації та авторизації використано JWT-
токени. Такий підхід дозволяє після входу користувача в систему видавати 
йому токен, який надалі використовується для доступу до захищених 
маршрутів API. JWT зручно застосовувати в кросплатформенних системах, 
оскільки один механізм авторизації може використовуватися як у вебпанелі, так 
і в мобільному застосунку [15]. Розмежування доступу виконується відповідно 
до ролі користувача: адміністратора, тренера або клієнта. 
Адміністративну вебпанель реалізовано з використанням React. Ця 
бібліотека дозволяє створювати динамічні користувацькі інтерфейси на основі 
компонентного підходу. Компонентна структура спрощує повторне 
використання елементів інтерфейсу, таких як таблиці, форми, картки 
статистики та елементи навігації. React добре підходить для адміністративної 
панелі, оскільки вона містить багато інтерактивних сторінок для керування 
даними. 
Для швидкого збирання та запуску вебчастини використано Vite. Він 
забезпечує швидкий старт проєкту, зручний режим розробки, швидке 
оновлення сторінки після змін у коді та оптимізовану збірку готового 
застосунку. Це робить процес розробки адміністративної панелі більш 
ефективним. 
Для створення мобільного застосунку обрано React Native у поєднанні з 
Expo. React Native дозволяє розробляти мобільні застосунки для Android та iOS 
з використанням JavaScript і React-підходу. Expo спрощує процес запуску, 
тестування та налаштування мобільного застосунку, оскільки надає готові 
інструменти для роботи з мобільними пристроями. Завдяки цьому клієнти й 
54 
ЧДТУ 262240.001 ПЗ 
тренери можуть використовувати систему на смартфонах без потреби у 
веббраузері. 
Для взаємодії клієнтських частин із сервером використовується REST API 
та формат обміну даними JSON. Такий підхід є простим, зрозумілим і добре 
підтримується як вебзастосунками, так і мобільними застосунками. 
Адміністративна панель і мобільний застосунок надсилають HTTP-запити до 
серверної частини, отримують дані у форматі JSON і відображають їх 
користувачеві. 
Обрані засоби реалізації відповідають вимогам розроблюваної системи, 
оскільки дозволяють побудувати кросплатформенний програмний комплекс із 
чітким поділом на серверну частину, вебінтерфейс адміністратора, мобільний 
застосунок і базу даних. Використання Node.js, Express.js, PostgreSQL, Prisma, 
React, Vite, React Native та Expo забезпечує достатню гнучкість, швидкість 
розробки, зручність супроводу та можливість подальшого розширення 
функціональності системи. 
3.1.2.  Опис структурної (функціональної) схеми 
Структурна, або функціональна, схема програмного комплексу 
відображає склад основних частин системи та функції, які виконує кожна з них. 
Кросплатформенний додаток для автоматизації роботи спортивного залу 
побудований за клієнт-серверною архітектурою. Це означає, що користувацькі 
інтерфейси не працюють із базою даних напряму, а взаємодіють із серверною 
частиною через API. 
Програмний комплекс складається з таких основних функціональних 
блоків: адміністративна вебпанель, мобільний застосунок, серверна частина, 
база даних і модуль авторизації. Адміністративна вебпанель призначена для 
адміністратора спортивного залу. За її допомогою адміністратор може керувати 
користувачами, абонементними планами, активними абонементами, 
тренуваннями, платежами та переглядати статистичну інформацію. 
Мобільний застосунок використовується клієнтами та тренерами. Для 
клієнта він забезпечує перегляд профілю, активного абонемента, доступних 
55 
ЧДТУ 262240.001 ПЗ 
тренувань, запис на заняття, скасування бронювання та перегляд історії 
відвідувань. Для тренера мобільний застосунок надає можливість переглядати 
власний розклад, список записаних клієнтів і відмічати відвідуваність після 
проведення тренування. 
Серверна частина є центральним елементом програмного комплексу. 
Вона приймає запити від адміністративної вебпанелі та мобільного застосунку, 
перевіряє авторизацію користувачів, визначає їхню роль, обробляє бізнес-
логіку та виконує операції з базою даних. Серверна частина містить окремі 
функціональні модулі: модуль користувачів, модуль абонементів, модуль 
тренувань, модуль бронювань, модуль відвідуваності, модуль платежів і модуль 
статистики. 
Модуль авторизації відповідає за вхід користувача в систему, перевірку 
облікових даних, створення JWT-токена та обмеження доступу до функцій 
відповідно до ролі користувача. Це дозволяє забезпечити захист персональних 
даних і службової інформації. 
 
 
Рисунок 3.1 – Структурна (функціональна) схема кросплатформенного додатку 
для автоматизації роботи спортивного залу 
56 
ЧДТУ 262240.001 ПЗ 
База даних PostgreSQL використовується для централізованого 
збереження інформації. У ній зберігаються дані про користувачів, ролі, 
абонементні плани, активні абонементи, тренування, записи на тренування, 
відвідуваність і платежі. Взаємодія серверної частини з базою даних 
здійснюється через Prisma ORM, що спрощує виконання операцій читання, 
створення, оновлення та видалення записів. 
Функціональна схема показує, що всі основні дії користувачів проходять 
через серверну частину (рис. 3.1). Такий підхід дозволяє централізовано 
керувати доступом до даних, уникнути дублювання бізнес-логіки в клієнтських 
застосунках і забезпечити узгодженість роботи всієї системи. 
3.1.3.  Опис логічної схеми системи 
Логічна схема системи описує внутрішню організацію програмного 
комплексу, взаємозв’язки між основними модулями та порядок обробки даних 
у системі. На відміну від структурної схеми, яка показує загальний склад 
програмного продукту, логічна схема деталізує, як саме компоненти 
взаємодіють між собою під час виконання основних функцій. 
Кросплатформенний додаток для автоматизації роботи спортивного залу 
побудований за принципом розділення клієнтської та серверної частин. 
Клієнтські застосунки, а саме адміністративна вебпанель і мобільний 
застосунок, відповідають за взаємодію з користувачем, відображення даних і 
передавання запитів на сервер. Серверна частина виконує перевірку запитів, 
авторизацію користувачів, обробку бізнес-логіки та роботу з базою даних. 
Основним елементом логічної схеми є REST API, через який вебпанель і 
мобільний застосунок обмінюються даними із сервером. Усі запити 
користувачів проходять через модуль авторизації, який перевіряє JWT-токен і 
визначає роль користувача. Після цього запит передається до відповідного 
функціонального модуля: користувачів, абонементів, тренувань, бронювань, 
відвідуваності, платежів або статистики. 
Модуль користувачів відповідає за створення, редагування, видалення та 
перегляд облікових записів. Модуль абонементів забезпечує роботу з 
57 
ЧДТУ 262240.001 ПЗ 
абонементними планами та активними абонементами клієнтів. Модуль 
тренувань використовується для створення розкладу занять і призначення 
тренерів. Модуль бронювань обробляє запис клієнтів на тренування та 
скасування записів. Модуль відвідуваності дозволяє фіксувати присутність 
клієнтів на заняттях. Модуль платежів зберігає інформацію про фінансові 
операції, а модуль статистики формує узагальнені показники роботи 
спортивного залу. 
Для доступу до бази даних використовується Prisma ORM. Він є 
проміжним рівнем між бізнес-логікою та PostgreSQL і забезпечує виконання 
операцій читання, створення, оновлення та видалення записів. Такий підхід 
дозволяє зменшити кількість прямого SQL-коду, спростити підтримку системи 
та забезпечити узгоджену роботу з даними. 
Логічна схема системи також передбачає рольове розмежування доступу. 
Адміністратор має доступ до всіх основних модулів керування, тренер — до 
розкладу та відвідуваності, а клієнт — до власного профілю, абонемента, 
тренувань і бронювань. Це дозволяє обмежити доступ до службової інформації 
та забезпечити безпечну роботу системи. 
 
 
Рисунок 3.2 – Логічна схема системи крос-платформенного додатку 
58 
ЧДТУ 262240.001 ПЗ 
3.1.4.  Розробка бази даних 
База даних є одним із ключових елементів кросплатформенного додатку 
для автоматизації роботи спортивного залу, оскільки саме вона забезпечує 
централізоване збереження всієї інформації, необхідної для роботи системи. У 
базі даних зберігаються дані про користувачів, їхні ролі, абонементні плани, 
активні абонементи, тренування, записи на заняття, відвідуваність і платежі 
[15]. 
Для реалізації бази даних було обрано реляційну систему керування 
базами даних PostgreSQL. Такий вибір є доцільним, оскільки предметна область 
спортивного залу містить багато взаємопов’язаних сутностей. Наприклад, 
клієнт може мати абонемент, абонемент створюється на основі певного плану, 
клієнт може записуватися на тренування, а тренування проводить конкретний 
тренер. Реляційна модель дозволяє чітко описати такі зв’язки за допомогою 
таблиць, первинних і зовнішніх ключів. 
Основною таблицею бази даних є users. Вона зберігає інформацію про 
всіх користувачів системи: адміністраторів, тренерів і клієнтів. Для кожного 
користувача зберігаються ідентифікатор, ім’я, електронна пошта, хеш пароля, 
номер телефону, роль, баланс, дата створення та дата оновлення запису. Поле 
ролі визначає, які функції будуть доступні користувачу в системі. 
Таблиця membership_plans призначена для збереження абонементних 
планів. Вона містить назву абонемента, опис, ціну, строк дії та кількість 
доступних відвідувань. На основі цих планів адміністратор може видавати 
клієнтам конкретні абонементи. 
Таблиця memberships зберігає інформацію про активні або завершені 
абонементи клієнтів. Вона пов’язана з таблицею users, оскільки кожен 
абонемент належить конкретному клієнту, а також із таблицею 
membership_plans, оскільки кожен абонемент створюється на основі певного 
плану. У цій таблиці зберігаються дата початку, дата завершення, залишок 
відвідувань і статус абонемента. 
Таблиця workouts використовується для збереження тренувань. Кожне 
59 
ЧДТУ 262240.001 ПЗ 
тренування має назву, опис, час початку, час завершення, максимальну 
кількість учасників і посилання на тренера. Зв’язок із таблицею users дозволяє 
визначити, який саме тренер проводить заняття. 
Таблиця bookings відповідає за записи клієнтів на тренування. Вона 
пов’язує користувача з конкретним тренуванням. Завдяки цій таблиці система 
може визначати, які клієнти записані на певне заняття, чи є вільні місця, а 
також чи не записаний клієнт повторно на одне й те саме тренування. 
Таблиця attendance використовується для фіксації відвідуваності. Вона 
пов’язана із записом на тренування та дозволяє зберігати інформацію про те, чи 
був клієнт присутній на занятті. Також у таблиці зберігається дата та час 
відмітки. Ці дані можуть використовуватися для формування історії відвідувань 
клієнта та аналізу активності користувачів. 
Таблиця payments призначена для збереження інформації про платежі. 
Кожен платіж пов’язується з користувачем і, за потреби, з конкретним 
абонементом. У таблиці зберігаються сума платежу, статус оплати, дата 
платежу та службові дані. Це дозволяє адміністратору контролювати фінансові 
операції та переглядати історію оплат клієнтів. 
Між таблицями бази даних встановлено такі основні зв’язки: 
1 Один користувач може мати багато абонементів, але кожен абонемент 
належить лише одному користувачу.  
2 Один абонементний план може використовуватися для створення 
багатьох абонементів.  
3 Один тренер може проводити багато тренувань, але кожне тренування 
має одного тренера.  
4 Один клієнт може мати багато записів на тренування.  
5 Одне тренування може мати багато записів клієнтів.  
6 Один запис на тренування може мати одну відмітку відвідуваності.  
7 Один користувач може мати багато платежів.  
8 Один абонемент може бути пов’язаний з одним або кількома 
платежами.  
60 
ЧДТУ 262240.001 ПЗ 
Для забезпечення цілісності даних у базі використовуються первинні та 
зовнішні ключі. Первинні ключі однозначно ідентифікують записи в таблицях, 
а зовнішні ключі забезпечують правильні зв’язки між сутностями. Наприклад, 
поле userId у таблиці memberships посилається на таблицю users, а поле planId 
— на таблицю membership_plans. Такий підхід запобігає появі некоректних 
даних, наприклад абонемента без клієнта або запису на неіснуюче тренування. 
 
 
Рисунок 3.3 – ERD бази даних кросплатформенного додатку для автоматизації 
роботи спортивного залу 
61 
ЧДТУ 262240.001 ПЗ 
Наведена ERD-діаграма відображає структуру бази даних і зв’язки між її 
таблицями. Така модель забезпечує збереження всіх основних даних системи, 
підтримує цілісність інформації та дозволяє ефективно реалізувати функції 
керування спортивним залом. 
3.1.5.  Розробка інтерфейсу користувача 
Інтерфейс користувача є важливою частиною програмного комплексу, 
оскільки саме через нього користувачі взаємодіють із функціями системи. Під 
час розробки кросплатформенного додатку для автоматизації роботи 
спортивного залу було враховано потреби трьох основних категорій 
користувачів: адміністратора, тренера та клієнта. Для кожної ролі передбачено 
окремий набір доступних функцій і відповідні елементи інтерфейсу. 
Адміністративний інтерфейс реалізовано у вигляді вебпанелі. Такий 
підхід є зручним для адміністратора, оскільки більшість його дій пов’язана з 
роботою з таблицями, формами, списками користувачів, абонементами, 
тренуваннями та платежами [16]. Вебпанель дозволяє швидко переглядати 
велику кількість інформації, додавати нові записи, редагувати наявні дані та 
контролювати основні процеси роботи спортивного залу. 
Головна сторінка адміністративної панелі містить узагальнену 
інформацію про систему та статистичні показники. Вона дозволяє 
адміністратору швидко оцінити поточний стан роботи спортивного залу: 
кількість користувачів, тренувань, записів, активних абонементів або платежів. 
Для навігації між розділами використовується меню, яке забезпечує доступ до 
основних модулів системи. 
Розділ керування користувачами призначений для перегляду, створення, 
редагування та видалення облікових записів. У цьому розділі адміністратор 
може працювати з даними клієнтів і тренерів, вказувати роль користувача, 
контактну інформацію та інші необхідні відомості. Для зручності дані 
подаються у вигляді таблиці, а додавання або редагування інформації 
виконується через форми. 
62 
ЧДТУ 262240.001 ПЗ 
Розділ керування абонементами дозволяє адміністратору створювати 
абонементні плани, задавати їхню назву, ціну, строк дії та кількість відвідувань. 
Також адміністратор може видавати абонементи конкретним клієнтам і 
контролювати їхній статус. Це спрощує облік послуг спортивного залу та 
дозволяє уникнути ручного ведення інформації про абонементи. 
Розділ тренувань використовується для створення та редагування занять. 
Адміністратор вказує назву тренування, опис, тренера, дату й час проведення, а 
також максимальну кількість учасників. Завдяки цьому формується актуальний 
розклад, який надалі доступний клієнтам і тренерам у мобільному застосунку. 
Мобільний інтерфейс розроблено для клієнтів і тренерів. Основна вимога 
до нього — простота та швидкий доступ до найважливіших функцій. Клієнт у 
мобільному застосунку може переглядати власний профіль, активний 
абонемент, доступні тренування, записуватися на заняття, скасовувати 
бронювання та переглядати історію відвідувань. Інтерфейс побудований таким 
чином, щоб користувач міг виконати основні дії за мінімальну кількість кроків. 
Для тренера в мобільному застосунку передбачено перегляд власного 
розкладу та списку клієнтів, які записалися на заняття. Після проведення 
тренування тренер може відмітити присутність або відсутність клієнтів. Така 
реалізація дозволяє швидко фіксувати відвідуваність без використання 
паперових журналів або окремих таблиць. 
Під час розробки інтерфейсу було використано компонентний підхід. 
Окремі елементи, такі як кнопки, форми, таблиці, картки тренувань, картки 
абонементів і навігаційні елементи, реалізовано як повторно використовувані 
компоненти. Це спрощує підтримку коду, забезпечує однаковий стиль 
інтерфейсу та полегшує подальше розширення функціональності системи. 
Важливою особливістю інтерфейсу є його залежність від ролі 
користувача. Після авторизації система визначає роль користувача та 
відображає тільки ті розділи й функції, які йому доступні. Адміністратор 
отримує доступ до повного набору адміністративних можливостей, тренер — 
до розкладу та відвідуваності, а клієнт — до власного профілю, абонемента, 
63 
ЧДТУ 262240.001 ПЗ 
тренувань і бронювань. Такий підхід підвищує безпеку системи та робить 
інтерфейс більш зрозумілим для користувача. 
Отже, розроблений інтерфейс користувача забезпечує зручну взаємодію з 
програмним комплексом, враховує потреби різних категорій користувачів і 
відповідає функціональному призначенню системи. Адміністративна вебпанель 
орієнтована на керування даними та контроль роботи спортивного залу, а 
мобільний застосунок — на швидкий доступ клієнтів і тренерів до основних 
функцій. 
Нижче на рисунках 3.4 – 3.10 представлено ілюстрації розробленого 
інтерфейсу користувача, які демонструють основні екрани адміністративної 
вебпанелі та мобільного застосунку. 
 
 
Рисунок 3.4 – Сторінка входу у вебпанель адміністратора 
64 
ЧДТУ 262240.001 ПЗ 
 
Рисунок 3.5 – Сторінка огляду системи керування спотривним залом 
 
 
Рисунок 3.6 – Сторінка огляду користувачів системи 
 
 
Рисунок 3.7 – Сторінка поповнення балансу користувачів 
65 
ЧДТУ 262240.001 ПЗ 
 
 
Рисунок 3.8 – Сторінка запису на тренування у клієнта 
 
 
Рисунок 3.9 – Сторінка створення тренування у тренера 
66 
ЧДТУ 262240.001 ПЗ 
 
Рисунок 3.10 – Профіль тренера у мобільному застосунку 
 
3.1.6. Опис розробки програмних компонентів 
Розроблений програмний комплекс для автоматизації роботи спортивного 
залу складається з кількох основних програмних компонентів: серверної 
частини, адміністративної вебпанелі, мобільного застосунку та бази даних. 
Кожен із цих компонентів виконує окрему роль у системі, але всі вони 
взаємодіють між собою через єдиний механізм обміну даними. 
Серверна частина є центральним компонентом системи. Вона реалізована 
на основі Node.js та Express.js і відповідає за обробку HTTP-запитів, виконання 
бізнес-логіки, автентифікацію користувачів, перевірку ролей і взаємодію з 
базою даних [20]. Сервер надає REST API, яке використовується 
адміністративною вебпанеллю та мобільним застосунком для отримання,  
67 
ЧДТУ 262240.001 ПЗ 
створення, редагування й видалення даних. 
Одним із ключових компонентів серверної частини є модуль 
автентифікації. Він забезпечує вхід користувача в систему за допомогою 
електронної пошти та пароля. Після успішної перевірки облікових даних сервер 
формує JWT-токен, який надалі використовується для доступу до захищених 
маршрутів. Завдяки цьому користувач може виконувати тільки ті дії, які 
дозволені його роллю. 
Модуль користувачів відповідає за роботу з обліковими записами. Він 
забезпечує створення нових користувачів, перегляд списку користувачів, 
редагування їхніх даних і видалення записів. У системі передбачено три 
основні ролі: адміністратор, тренер і клієнт. Саме роль користувача визначає 
набір функцій, доступних після авторизації. 
Модуль абонементів реалізує функції створення абонементних планів і 
видачі абонементів клієнтам. Абонементний план містить назву, опис, вартість, 
строк дії та кількість доступних відвідувань. Активний абонемент клієнта 
пов’язується з конкретним планом і користувачем, а також містить інформацію 
про дату початку, дату завершення, залишок відвідувань і статус. 
Модуль тренувань відповідає за створення та керування розкладом 
занять. Адміністратор може додавати нові тренування, вказувати тренера, дату 
й час проведення, опис і максимальну кількість учасників. Клієнти та тренери 
отримують дані про тренування через мобільний застосунок. Для клієнта 
тренування відображаються як доступні заняття для запису, а для тренера — як 
його власний розклад. 
Модуль бронювань забезпечує запис клієнтів на тренування. Під час 
створення бронювання система перевіряє, чи має клієнт активний абонемент, 
чи є вільні місця на тренуванні та чи не записаний клієнт повторно на це саме 
заняття. Якщо всі умови виконані, створюється запис на тренування. Також 
модуль підтримує скасування бронювання клієнтом. 
Модуль відвідуваності призначений для фіксації факту присутності 
клієнтів на тренуваннях. Після проведення заняття тренер відкриває список 
68 
ЧДТУ 262240.001 ПЗ 
записаних клієнтів і відмічає, хто був присутній. У разі підтвердження 
відвідування система зберігає відповідний запис і може оновлювати залишок 
відвідувань в абонементі клієнта. Це дозволяє вести історію відвідувань і 
контролювати використання абонементів. 
Модуль платежів використовується для обліку фінансових операцій. Він 
дозволяє зберігати інформацію про оплату клієнтом абонемента або інших 
послуг спортивного залу. Кожен платіж пов’язується з користувачем і, за 
потреби, з конкретним абонементом. Це дає змогу адміністратору переглядати 
історію оплат і контролювати фінансові записи. 
Модуль статистики забезпечує формування узагальненої інформації для 
адміністратора. До таких даних можуть належати кількість користувачів, 
тренувань, бронювань, активних абонементів, платежів і відвідувань. 
Статистична інформація відображається в адміністративній вебпанелі та 
допомагає оцінити поточний стан роботи спортивного залу. 
Адміністративна вебпанель реалізована з використанням React і Vite. 
Вона складається зі сторінок і повторно використовуваних компонентів. 
Основними сторінками є сторінка входу, головна сторінка зі статистикою, 
сторінка користувачів, сторінка абонементних планів, сторінка активних 
абонементів, сторінка тренувань і сторінка платежів. Для відображення даних 
використовуються таблиці, форми, кнопки та картки статистики. Вебпанель 
взаємодіє із сервером через API-клієнт, який надсилає HTTP-запити та 
обробляє відповіді. 
Мобільний застосунок реалізовано за допомогою React Native та Expo. 
Він містить екрани для авторизації, перегляду профілю, активного абонемента, 
розкладу тренувань, деталей тренування, історії бронювань і відвідувань. Для 
тренера передбачено окремі екрани перегляду власних занять і відмітки 
відвідуваності. Мобільний застосунок також взаємодіє із серверною частиною 
через REST API. 
Окрему роль у програмному комплексі відіграє база даних PostgreSQL. 
Вона зберігає всі основні дані системи та забезпечує їхню цілісність [23]. Для 
69 
ЧДТУ 262240.001 ПЗ 
взаємодії з базою даних використовується Prisma ORM, що дозволяє працювати 
з таблицями через зручний програмний інтерфейс. Це спрощує розробку 
серверної частини та зменшує ймовірність помилок під час виконання запитів. 
Таким чином, розроблені програмні компоненти утворюють єдину 
систему, у якій кожна частина має чітко визначене призначення. Серверна 
частина забезпечує бізнес-логіку та доступ до даних, адміністративна вебпанель 
надає інструменти керування спортивним залом, мобільний застосунок 
забезпечує зручну роботу клієнтів і тренерів, а база даних відповідає за 
збереження інформації. Така структура робить програмний комплекс 
зрозумілим, підтримуваним і придатним для подальшого розширення. 
3.2. Тестування системи 
3.2.1.  Модульне тестування 
Модульне тестування є одним із етапів перевірки програмного 
забезпечення, під час якого окремі функції, модулі або компоненти системи 
перевіряються ізольовано від інших частин програмного комплексу [32]. 
Метою модульного тестування є виявлення помилок у роботі окремих частин 
системи на ранніх етапах розробки та підтвердження того, що кожен модуль 
виконує свої функції відповідно до поставлених вимог. 
У межах розробки кросплатформенного додатку для автоматизації роботи 
спортивного залу модульне тестування проводилося для основних компонентів 
серверної частини, адміністративної вебпанелі та мобільного застосунку. 
Особливу увагу було приділено модулям, які реалізують основну бізнес-логіку 
системи: авторизації, роботі з користувачами, абонементами, тренуваннями, 
бронюваннями, відвідуваністю та платежами. 
Під час тестування серверної частини перевірялася коректність роботи 
окремих API-маршрутів і сервісних функцій. Зокрема, тестувалися функції 
входу користувача в систему, перевірки JWT-токена, отримання списку 
користувачів, створення абонементного плану, видачі абонемента клієнту, 
створення тренування, запису клієнта на тренування, скасування бронювання, 
70 
ЧДТУ 262240.001 ПЗ 
фіксації відвідуваності та створення платежу. 
Для клієнтських частин системи перевірялися окремі елементи 
інтерфейсу та їхня взаємодія з даними. В адміністративній вебпанелі 
тестувалися форми введення, таблиці, кнопки створення, редагування й 
видалення записів, а також відображення статистичних показників. У 
мобільному застосунку перевірялися екрани авторизації, профілю, абонемента, 
розкладу тренувань, бронювань та відмітки відвідуваності. 
Основними критеріями успішного проходження модульного тестування 
були коректність виконання функції, правильність обробки вхідних даних, 
відображення очікуваного результату, обробка помилкових ситуацій і 
відсутність збоїв під час виконання окремої операції. Наприклад, модуль 
бронювання повинен створювати запис тільки тоді, коли клієнт має активний 
абонемент, на тренуванні є вільні місця, а повторний запис відсутній. 
Результати модульного тестування наведено в таблиці 3.1. 
 
Таблиця 3.1  
Результати модульного тестування 
Модуль / Перевірюва Очікуваний Фактичний 
№ Статус 
компонент на функція результат результат 
Модуль Вхід Користувач Авторизація Пройдено 
авторизації користувача успішно виконана, 
1 в систему авторизується, токен 
система повертає сформовано 
JWT-токен 
 
 
 
 
71 
ЧДТУ 262240.001 ПЗ 
Продовження таблиці 3.1 
Модуль Створення У базі даних Користувача Пройдено 
користувачів нового створюється створено та 
2 
користувача новий запис відображено у 
користувача списку 
Модуль Видача Клієнту Абонемент Пройдено 
абонементів абонемента призначається успішно 
3 
клієнту активний призначено 
абонемент клієнту 
Модуль Запис Бронювання Запис на Пройдено 
бронювань клієнта на створюється за тренування 
тренування наявності створено 
4 
активного 
абонемента та 
вільних місць 
Модуль Відмітка Система зберігає Відвідування Пройдено 
5 відвідуваності присутності факт відвідування зафіксовано в 
клієнта тренування системі 
 
За результатами модульного тестування встановлено, що основні модулі 
системи працюють коректно та виконують передбачені функції. Виявлені під 
час розробки незначні помилки були усунені до завершення тестування. 
3.2.2. Інтеграційне тестування 
Інтеграційне тестування проводиться для перевірки коректності взаємодії 
між окремими модулями та компонентами програмної системи [32]. Якщо під 
час модульного тестування перевіряється робота окремих функцій, то 
інтеграційне тестування дозволяє переконатися, що ці функції правильно 
72 
ЧДТУ 262240.001 ПЗ 
працюють разом у межах єдиного програмного комплексу. 
Для кросплатформенного додатку автоматизації роботи спортивного залу 
інтеграційне тестування охоплювало взаємодію між адміністративною 
вебпанеллю, мобільним застосунком, серверною частиною, API, модулями 
бізнес-логіки та базою даних. Основну увагу було приділено сценаріям, у яких 
дані передаються між кількома частинами системи: авторизація користувача, 
створення тренування адміністратором, запис клієнта на тренування, відмітка 
відвідуваності тренером і відображення оновленої інформації в інтерфейсах 
користувачів. 
Під час тестування перевірялося, чи коректно клієнтські частини 
надсилають HTTP-запити до серверної частини, чи правильно сервер обробляє 
ці запити, чи зберігаються дані в базі даних і чи відображаються зміни в 
адміністративній вебпанелі та мобільному застосунку. Також перевірялася 
робота JWT-авторизації та рольового доступу, оскільки різні користувачі 
повинні мати доступ лише до дозволених функцій. 
Окремо було перевірено сценарій запису клієнта на тренування. У цьому 
випадку мобільний застосунок передає запит на сервер, сервер перевіряє 
активний абонемент клієнта, наявність вільних місць і відсутність повторного 
запису, після чого створює бронювання в базі даних. Після успішного 
виконання операції інформація про запис повинна відображатися в мобільному 
застосунку клієнта та бути доступною тренеру у списку учасників заняття. 
Результати інтеграційного тестування наведено в таблиці 3.2. 
 
Таблиця 3.2 
Результати інтеграційного тестування 
№ Сценарій Компоненти Результат Статус 
1 Авторизація Мобільний Користувач входить у Пройдено 
користувача застосунок / систему, роль 
73 
ЧДТУ 262240.001 ПЗ 
вебпанель, API, БД визначається коректно 
2 Створення Вебпанель, API, Тренування Пройдено 
тренування модуль тренувань, створюється та 
БД відображається в 
розкладі 
3 Запис клієнта на Мобільний Бронювання Пройдено 
заняття застосунок, API, створюється після 
модулі абонементів і перевірки умов 
бронювань, БД 
4 Відмітка Мобільний Відвідування Пройдено 
відвідуваності застосунок тренера, зберігається та 
API, модуль відображається в 
відвідуваності, БД історії 
5 Перегляд Вебпанель, API, Дані статистики Пройдено 
статистики модуль статистики, завантажуються та 
БД відображаються 
адміністратору 
 
За результатами інтеграційного тестування встановлено, що основні 
компоненти системи коректно взаємодіють між собою, дані передаються та 
зберігаються правильно, а зміни відображаються у відповідних інтерфейсах 
користувачів. 
3.2.3. Системне тестування 
Системне тестування проводиться для перевірки роботи програмного 
комплексу в цілому [32]. На цьому етапі система розглядається як завершений 
програмний продукт, що складається із серверної частини, бази даних, 
адміністративної вебпанелі та мобільного застосунку. Метою системного 
74 
ЧДТУ 262240.001 ПЗ 
тестування є перевірка відповідності розробленого додатку функціональним і 
нефункціональним вимогам, визначеним на етапі проектування. 
Під час системного тестування кросплатформенного додатку для 
автоматизації роботи спортивного залу перевірялися основні сценарії 
використання системи різними категоріями користувачів. Для адміністратора 
тестувалися можливості входу в систему, керування користувачами, створення 
абонементних планів, видачі абонементів клієнтам, створення тренувань, 
обліку платежів і перегляду статистики. Для клієнта перевірялися перегляд 
профілю, активного абонемента, доступних тренувань, запис на заняття, 
скасування бронювання та перегляд історії відвідувань. Для тренера 
тестувалися перегляд власного розкладу, перегляд списку клієнтів на занятті та 
відмітка відвідуваності. 
Окрему увагу було приділено перевірці рольового доступу. Система 
повинна надавати користувачам доступ лише до тих функцій, які відповідають 
їхній ролі. Наприклад, клієнт не повинен мати доступу до адміністративного 
керування користувачами або платежами, а тренер не повинен мати можливості 
змінювати абонементні плани. Така перевірка є важливою для забезпечення 
безпеки персональних і службових даних. 
Також перевірялася коректність роботи системи з помилковими або 
неповними даними. Наприклад, тестувалися спроби входу з неправильним 
паролем, запису на тренування без активного абонемента, повторного запису на 
одне й те саме заняття, створення тренування без обов’язкових полів і 
виконання запитів без авторизації. У таких випадках система повинна не 
завершувати роботу з помилкою, а відображати користувачу зрозуміле 
повідомлення. 
Під час системного тестування також оцінювалася зручність 
користувацького інтерфейсу. Перевірялося, чи зрозуміло розміщені основні 
елементи керування, чи коректно відображаються дані, чи не виникає помилок 
під час переходу між сторінками та екранами, а також чи зручно виконувати 
основні дії в адміністративній вебпанелі й мобільному застосунку. 
75 
ЧДТУ 262240.001 ПЗ 
Результати системного тестування наведено в таблиці 3.3. 
 
Таблиця 3.3 
Результати системного тестування 
№ Перевірка Очікуваний результат Статус 
1 Робота системи для Адміністратор може керувати Пройдено 
адміністратора користувачами, абонементами, 
тренуваннями, платежами та 
статистикою 
2 Робота системи для Клієнт може переглядати абонемент, Пройдено 
клієнта тренування, бронювання та історію 
відвідувань 
3 Робота системи для Тренер може переглядати свої заняття Пройдено 
тренера та відмічати відвідуваність 
4 Перевірка рольового Користувачі мають доступ лише до Пройдено 
доступу функцій своєї ролі 
5 Обробка помилкових Система показує повідомлення про Пройдено 
дій помилку та не порушує цілісність 
даних 
 
За результатами проведення системного тестування встановлено, що 
розроблений програмний комплекс працює коректно як єдина система. Основні 
функціональні можливості реалізовано відповідно до вимог, рольове 
розмежування доступу працює правильно, а помилкові ситуації обробляються 
без порушення роботи додатку. 
3.2.4. Приймальне тестування 
Приймальне тестування проводиться на завершальному етапі перевірки 
програмного забезпечення [32]. Його метою є визначення того, чи відповідає 
розроблена система поставленим вимогам і чи може вона бути використана для 
76 
ЧДТУ 262240.001 ПЗ 
автоматизації основних процесів роботи спортивного залу. На цьому етапі 
система перевіряється з позиції кінцевих користувачів: адміністратора, тренера 
та клієнта. 
У процесі приймального тестування було перевірено основні сценарії 
роботи з додатком. Адміністратор виконував вхід у систему, створював 
користувачів, додавав абонементні плани, видавав абонементи клієнтам, 
створював тренування та переглядав статистику. Клієнт перевіряв можливість 
перегляду активного абонемента, доступних тренувань, запису на заняття та 
перегляду історії відвідувань. Тренер перевіряв перегляд власного розкладу та 
відмітку присутності клієнтів на тренуванні. 
Також було перевірено зручність використання інтерфейсу. 
Адміністративна вебпанель забезпечує швидкий доступ до основних функцій 
керування спортивним залом, а мобільний застосунок дозволяє клієнтам і 
тренерам виконувати потрібні дії без зайвих кроків. Повідомлення про успішні 
дії та помилки відображаються зрозуміло, що спрощує роботу користувачів із 
системою. 
Результати приймального тестування наведено в таблиці 3.4. 
 
Таблиця 3.4 
Результати приймального тестування 
№ Сценарій Очікуваний результат Статус 
1 Вхід користувача в Користувач авторизується та отримує Пройдено 
систему доступ відповідно до ролі 
2 Робота Адміністратор може керувати Пройдено 
адміністратора користувачами, абонементами, 
тренуваннями та платежами 
3 Робота клієнта Клієнт може переглядати абонемент, Пройдено 
тренування та записуватися на заняття 
 
77 
ЧДТУ 262240.001 ПЗ 
Продовження таблиці 3.4 
4 Робота тренера Тренер може переглядати власні заняття та Пройдено 
відмічати відвідуваність 
5 Зручність Основні функції доступні та зрозумілі для Пройдено 
інтерфейсу користувачів 
 
В підсумку, було встановлено, що розроблений кросплатформенний 
додаток відповідає визначеним вимогам і може бути використаний для 
автоматизації основних процесів роботи спортивного залу. Система забезпечує 
коректну роботу основних функцій, підтримує рольове розмежування доступу 
та надає зручний інтерфейс для різних категорій користувачів. 
3.3. Приклади впровадженого програмного комплексу 
Розроблений крос-платформенний додаток для автоматизації роботи 
спортивного залу реалізовано як програмний комплекс, що складається із 
серверної частини, адміністративної WEB-панелі, мобільного застосунку та 
бази даних. Для перевірки працездатності системи було виконано запуск 
основних компонентів і перевірено роботу ключових сценаріїв використання. 
Після запуску серверної частини система надає REST API для взаємодії з 
клієнтськими застосунками. Через API виконуються операції авторизації, 
отримання даних користувача, створення тренувань, керування абонементами, 
бронювання занять, фіксація відвідуваності та облік платежів. Серверна 
частина також забезпечує перевірку JWT-токенів і розмежування доступу 
відповідно до ролі користувача. 
Адміністративна вебпанель використовується для керування основними 
даними спортивного залу. Після авторизації адміністратор отримує доступ до 
сторінок керування користувачами, абонементними планами, активними 
абонементами, тренуваннями та платежами. Наприклад, адміністратор може 
створити нового користувача з роллю клієнта або тренера, додати новий 
абонементний план, призначити абонемент конкретному клієнту та сформувати 
78 
ЧДТУ 262240.001 ПЗ 
розклад тренувань. 
Одним із прикладів використання програмного комплексу є створення 
тренування. Адміністратор у WEB-панелі заповнює форму, де вказує назву 
заняття, опис, тренера, дату й час проведення та максимальну кількість 
учасників. Після збереження ці дані передаються на сервер, записуються до 
бази даних і стають доступними для перегляду в мобільному застосунку клієнта 
та тренера. 
Мобільний застосунок забезпечує зручну роботу клієнтів і тренерів зі 
смартфона. Клієнт після авторизації може переглянути власний профіль, 
активний абонемент, список доступних тренувань і записатися на обране 
заняття. Під час запису система перевіряє наявність активного абонемента, 
вільних місць і відсутність повторного бронювання. Якщо всі умови виконано, 
створюється запис на тренування, а клієнт отримує підтвердження. 
Для тренера мобільний застосунок надає можливість переглядати власний 
розклад занять. Після вибору конкретного тренування тренер бачить список 
клієнтів, які записалися на заняття, та може відмітити їхню присутність або 
відсутність. Після збереження відмітки інформація про відвідування фіксується 
в базі даних і відображається в історії клієнта. 
Ще одним прикладом впровадження є робота з абонементами. 
Адміністратор створює абонементний план із визначеною ціною, строком дії та 
кількістю відвідувань. Після цього він може видати абонемент конкретному 
клієнту. У мобільному застосунку клієнт бачить інформацію про свій активний 
абонемент, зокрема назву плану, дату завершення та залишок доступних 
відвідувань. 
Реалізований програмний комплекс також дозволяє вести облік платежів. 
Адміністратор може додавати записи про оплату послуг, переглядати історію 
платежів і контролювати фінансові операції клієнтів. Це спрощує ведення 
обліку та зменшує потребу в окремих таблицях або паперових журналах. 
Таким чином, розроблений додаток демонструє практичну придатність 
для автоматизації основних процесів спортивного залу. Він дозволяє об’єднати 
79 
ЧДТУ 262240.001 ПЗ 
роботу адміністратора, тренера та клієнта в єдиній системі, забезпечує 
централізоване збереження даних і підтримує основні сценарії роботи закладу: 
керування користувачами, абонементами, тренуваннями, бронюваннями, 
відвідуваністю і платежами. 
Нижче представлено приклади роботи впровадженого програмного 
комплексу, що демонструють основні екрани адміністративної вебпанелі, 
мобільного застосунку та результати виконання ключових функцій системи. 
 
80 
ЧДТУ 262240.001 ПЗ 
Висновки до розділу 3 
Було описано процес реалізації та тестування кросплатформенного 
додатку для автоматизації роботи спортивного залу. Обґрунтовано вибір 
засобів реалізації, зокрема Node.js, Express.js, PostgreSQL, Prisma ORM, React, 
Vite, React Native та Expo, які забезпечили створення серверної частини, 
адміністративної вебпанелі, мобільного застосунку та бази даних. 
Розглянуто структурну й логічну схеми системи, описано розробку бази 
даних, користувацького інтерфейсу та основних програмних компонентів. 
Реалізована система підтримує роботу з користувачами, абонементами, 
тренуваннями, бронюваннями, відвідуваністю, платежами та статистикою. 
Також проведено модульне, інтеграційне, системне та приймальне 
тестування. Результати тестування підтвердили коректну роботу основних 
функцій програмного комплексу, правильну взаємодію між його компонентами 
та відповідність системи визначеним вимогам. 
Отже, розроблений додаток є працездатним програмним рішенням, яке 
може бути використане для автоматизації основних процесів роботи 
спортивного залу та подальшого розширення функціональності. 
 
 
  
81 
ЧДТУ 262240.001 ПЗ 
 
ВИСНОВКИ 
 
У результаті виконання кваліфікаційної роботи бакалавра, було 
розроблено кросплатформенний додаток для автоматизації роботи спортивного 
залу. Розроблена система призначена для спрощення основних процесів 
діяльності спортивного закладу, зокрема обліку користувачів, керування 
абонементами, створення тренувань, запису клієнтів на заняття, контролю 
відвідуваності, обліку платежів і перегляду статистичної інформації. 
Проаналізовано предметну область автоматизації роботи спортивного 
залу. Визначено основні проблеми, що виникають під час ручного або частково 
автоматизованого керування закладом: складність обліку клієнтів, контроль 
актуальності абонементів, організація розкладу тренувань, запис на заняття, 
фіксація відвідуваності та облік платежів. Також було розглянуто існуючі 
програмні аналоги, визначено їхні переваги й недоліки та обґрунтовано 
доцільність створення власного програмного рішення. 
Виконано проектування програмної системи. Сформовано вимоги до 
програмного забезпечення, визначено основні ролі користувачів: 
адміністратора, тренера та клієнта. Побудовано UML-діаграми, які 
відображають структуру, архітектуру та поведінку системи, зокрема діаграми 
класів, пакетів, компонентів, розгортання, діяльності, послідовності, 
комунікації та скінченного автомату. Це дозволило сформувати повну проектну 
основу для подальшої реалізації програмного комплексу. 
Описано процес розробки та тестування додатку. Для реалізації серверної 
частини використано Node.js і Express.js, для роботи з базою даних — 
PostgreSQL та Prisma ORM, для адміністративної вебпанелі — React і Vite, а для 
мобільного застосунку — React Native та Expo. Реалізовано основні 
функціональні модулі системи: авторизацію, керування користувачами, 
абонементами, тренуваннями, бронюваннями, відвідуваністю, платежами та 
статистикою. 
82 
ЧДТУ 262240.001 ПЗ 
Розроблений програмний комплекс складається із серверної частини, бази 
даних, адміністративної вебпанелі та мобільного застосунку. Адміністратор 
отримує можливість керувати основними даними спортивного залу через 
вебінтерфейс. Клієнт може переглядати абонемент, доступні тренування, 
записуватися на заняття та переглядати історію відвідувань. Тренер може 
переглядати власний розклад і відмічати присутність клієнтів на тренуваннях. 
Проведене тестування підтвердило працездатність основних функцій 
системи. Було виконано модульне, інтеграційне, системне та приймальне 
тестування. За результатами перевірки встановлено, що програмний комплекс 
коректно обробляє запити користувачів, забезпечує рольове розмежування 
доступу, зберігає дані в базі даних і надає зручний інтерфейс для різних 
категорій користувачів. 
Отже, мету кваліфікаційної роботи досягнуто: розроблено 
кросплатформенний додаток для автоматизації роботи спортивного залу. 
Створене програмне забезпечення може бути використане як базове рішення 
для невеликих і середніх спортивних закладів, а також може бути розширене в 
майбутньому шляхом додавання онлайн-оплати, push-сповіщень, розширеної 
аналітики, персональних програм тренувань та інтеграції з додатковими 
сервісами. 
83 
ЧДТУ 262240.001 ПЗ 
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ 
1 Mindbody. Gym Management Software. URL: https://www. 
mindbodyonline.com/business/fitness/gym-software  
2 ABC Glofox. Gym Management Software. URL: https://www.glofox. 
com/business-types/gym-management-software/  
3 Wodify. Gym Management Software to Grow Your Business. URL: 
https://www.wodify.com/  
4 GymMaster. Gym Management Software. URL: https://www. 
gymmaster.com/  
5 Gymdesk. Gym Management Software. URL: https://gymdesk.com/  
6 Object Management Group. Unified Modeling Language 2.5.1 Specification. 
OMG, 2017. URL: https://www.omg.org/spec/UML/2.5.1/  
7 Object Management Group. UML — Unified Modeling Language. URL: 
https://www.omg.org/spec/UML/  
8 PlantUML. PlantUML Language Reference Guide. URL: 
https://plantuml.com/guide  
9 PlantUML. Class Diagram. URL: https://plantuml.com/class-diagram  
10 PlantUML. Use Case Diagram. URL: https://plantuml.com/use-case-
diagram  
11 PlantUML. Sequence Diagram. URL: https://plantuml.com/sequence-
diagram  
12 PlantUML. State Diagram. URL: https://plantuml.com/state-diagram  
13 Larman C. Applying UML and Patterns: An Introduction to Object-Oriented 
Analysis and Design and Iterative Development. 3rd ed. Prentice Hall, 2004.  
14 Sommerville I. Software Engineering. 10th ed. Pearson, 2016.  
15 Pressman R. S., Maxim B. R. Software Engineering: A Practitioner’s 
Approach. 9th ed. McGraw-Hill Education, 2020.  
16 ISO/IEC/IEEE 29148:2018. Systems and software engineering — Life cycle 
processes — Requirements engineering. International Organization for 
84 
ЧДТУ 262240.001 ПЗ 
Standardization, 2018. URL: https://www.iso.org/standard/72089.html  
17 Fielding R. T. Architectural Styles and the Design of Network-based 
Software Architectures : doctoral dissertation. University of California, 
Irvine, 2000. URL: https://www.ics.uci.edu/~fielding/pubs/dissertation/ 
top.htm  
18 Mozilla Developer Network. HTTP: Hypertext Transfer Protocol. MDN 
Web Docs. URL: https://developer.mozilla.org/en-US/docs/Web/HTTP  
19 Mozilla Developer Network. HTTP request methods. MDN Web Docs. 
URL:  https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/ 
Methods  
20 Node.js. Node.js Documentation. URL: https://nodejs.org/docs/latest/api/  
21 Node.js. Introduction to Node.js. URL: https://nodejs.org/learn/getting-
started/introduction-to-nodejs  
22 Express.js. Express — Node.js web application framework. URL: 
https://expressjs.com/  
23 PostgreSQL Global Development Group. PostgreSQL Documentation. 
URL: https://www.postgresql.org/docs/  
24 Prisma. Prisma Documentation. URL: https://www.prisma.io/docs  
25 Prisma. Prisma ORM Documentation. URL: https://www.prisma.io/docs 
/orm  
26 React. React Documentation. URL: https://react.dev/  
27 Vite. Vite Documentation. URL: https://vite.dev/  
28 React Native. React Native Documentation. URL:  https://reactnative. 
dev/docs/getting-started  
29 Expo. Expo Documentation. URL: https://docs.expo.dev/  
30 Jones M., Bradley J., Sakimura N. RFC 7519: JSON Web Token (JWT). 
Internet Engineering Task Force, 2015. URL:  https://datatracker. 
ietf.org/doc/html/rfc7519  
31 OWASP Foundation. OWASP Top 10:2021. URL: https://owasp. 
org/Top10/2021/  
85 
ЧДТУ 262240.001 ПЗ 
32 ISTQB. Certified Tester Foundation Level Syllabus v4.0.1. International 
Software Testing Qualifications Board, 2024. URL: 
https://istqb.org/sdm_downloads/istqb-certified-tester-foundation-level-
syllabus-v4-0/ 
86 
ДОДАТОК А  
 
 
ЗАТВЕРДЖЕНО:  
Зав. кафедри ПЗАС  
проф. Голуб С. В.  
___________________________  
 
 
КРОС-ПЛАТФОРМЕННИЙ ДОДАТОК ДЛЯ АВТОМАТИЗАЦІЇ РОБОТИ 
СПОРТИВНОГО ЗАЛУ 
 
Специфікація  
482.ЧДТУ 262240.001  
Листів 2  
 
 
 
Розробник      ________________   Гончар А.О. 
 
Керівник    ________________ Півень О.Б. 
 
Н. Контроль   ________________  Півень О.Б. 
 
 
 
 
 
 
Черкаси 2026 
482.ЧДТУ 262240.001 2 
Позначення Найменування Примітка 
482.ЧДТУ 262240 12 01 Лістинг програми  
482.ЧДТУ 262240 34 01 Інструкція користувача  
482ЧДТУ 262240 90 01 Графічні матеріали  
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
 
88 
ДОДАТОК Б 
КРОСПЛАТФОРМЕННИЙ ДОДАТОК ДЛЯ АВТОМАТИЗАЦІЇ 
РОБОТИ СПОРТИВНОГО ЗАЛУ 
Лістинг програми 
482.ЧДТУ 262240.12 01
Листів 61 
Розробник ________________ Гончар А. О. 
Черкаси 2026 
482.ЧДТУ 262240 12 01 2 
server.js 
import dotenv from "dotenv"; 
import express from "express"; 
import cors from "cors"; 
import morgan from "morgan"; 
import bcrypt from "bcryptjs"; 
import jwt from "jsonwebtoken"; 
import { z } from "zod"; 
import { Prisma } from "@prisma/client"; 
import { prisma } from "./db.js"; 
import { requireAuth, requireRole } from "./middleware/auth.js"; 
dotenv.config({ override: true }); 
const app = express(); 
const port = process.env.PORT || 4000; 
const host = process.env.HOST || "0.0.0.0"; 
app.use(cors()); 
app.use(express.json()); 
app.use(morgan("dev")); 
const userSelect = { 
  id: true, 
  name: true, 
  email: true, 
  phone: true, 
  balance: true, 
  role: true, 
  assignedTrainerId: true, 
  assignedTrainer: { 
    select: {
90 
482.ЧДТУ 262240 12 01 3 
      id: true, 
      name: true, 
      email: true, 
      phone: true, 
      role: true 
    } 
  }, 
  createdAt: true 
}; 
const userProfileSelect = { 
  ...userSelect, 
  assignedClients: { 
    select: { 
      id: true, 
      name: true, 
      email: true, 
      phone: true, 
      role: true 
    }, 
    orderBy: { name: "asc" } 
  } 
}; 
function tokenFor(user) { 
  return jwt.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET, { 
    expiresIn: "7d" 
  }); 
} 
function asyncHandler(handler) { 
  return (req, res, next) => Promise.resolve(handler(req, res, next)).catch(next); 
91 
482.ЧДТУ 262240 12 01 4 
} 
function startOfToday() { 
  const date = new Date(); 
  date.setHours(0, 0, 0, 0); 
  return date; 
} 
function endOfDay(date) { 
  const end = new Date(date); 
  end.setHours(23, 59, 59, 999); 
  return end; 
} 
async function cleanupFinishedWorkouts() { 
  const result = await prisma.workout.deleteMany({ 
    where: { 
      endsAt: { lte: new Date() } 
    } 
  }); 
  if (result.count > 0) { 
    console.log(`Deleted finished workouts: ${result.count}`); 
  } 
} 
app.get("/health", (req, res) => { 
  res.json({ ok: true, service: "gym-backend" }); 
}); 
app.post( 
  "/auth/login", 
92 
482.ЧДТУ 262240 12 01 5 
  asyncHandler(async (req, res) => { 
    const body = z.object({ email: z.string().email(), password: z.string().min(6) 
}).parse(req.body); 
    const user = await prisma.user.findUnique({ where: { email: body.email } }); 
    if (!user || !(await bcrypt.compare(body.password, user.passwordHash))) { 
      return res.status(401).json({ message: "Невірний email або пароль." }); 
    } 
    res.json({ 
      token: tokenFor(user), 
      user: { 
        id: user.id, 
        name: user.name, 
        email: user.email, 
        phone: user.phone, 
        balance: user.balance, 
        role: user.role 
      } 
    }); 
  }) 
); 
app.post( 
  "/auth/register", 
  asyncHandler(async (req, res) => { 
    const body = z 
      .object({ 
        name: z.string().min(2), 
        email: z.string().email(), 
        password: z.string().min(6), 
        phone: z.string().trim().min(7, "Телефон є обов'язковим.") 
93 
482.ЧДТУ 262240 12 01 6 
      }) 
      .parse(req.body); 
    const { password, ...userData } = body; 
    const passwordHash = await bcrypt.hash(password, 10); 
    const user = await prisma.user.create({ 
      data: { ...userData, passwordHash, role: "CLIENT" }, 
      select: userSelect 
    }); 
    res.status(201).json({ token: tokenFor(user), user }); 
  }) 
); 
app.patch( 
  "/me/password", 
  requireAuth, 
  asyncHandler(async (req, res) => { 
    const body = z 
  .object({ 
        currentPassword: z.string().min(6), 
        newPassword: z.string().min(6) 
      }) 
      .parse(req.body); 
    const user = await prisma.user.findUnique({ 
      where: { id: req.user.id }, 
      select: { id: true, passwordHash: true } 
    }); 
    if (!user || !(await bcrypt.compare(body.currentPassword, user.passwordHash))) { 
      return res.status(400).json({ message: "Поточний пароль введено невірно." }); 
94 
482.ЧДТУ 262240 12 01 7 
    } 
    const passwordHash = await bcrypt.hash(body.newPassword, 10); 
 await prisma.user.update({ 
      where: { id: req.user.id }, 
      data: { passwordHash } 
    }); 
    res.json({ message: "Пароль успішно змінено." }); 
  }) 
); 
app.get( 
  "/me", 
  requireAuth, 
  asyncHandler(async (req, res) => { 
    const user = await prisma.user.findUniqueOrThrow({ 
      where: { id: req.user.id }, 
      select: userProfileSelect 
    }); 
    res.json(user); 
  }) 
); 
app.get( 
  "/users", 
  requireAuth, 
  requireRole("ADMIN"), 
  asyncHandler(async (req, res) => { 
    const role = req.query.role; 
    const users = await prisma.user.findMany({ 
      where: role ? { role } : undefined, 
95 
482.ЧДТУ 262240 12 01 8 
      select: userSelect, 
      orderBy: { createdAt: "desc" } 
    }); 
    res.json(users); 
  }) 
); 
app.post( 
  "/users", 
  requireAuth, 
  requireRole("ADMIN"), 
  asyncHandler(async (req, res) => { 
    const body = z 
      .object({ 
        name: z.string().min(2), 
        email: z.string().email(), 
        password: z.string().min(6), 
        phone: z.string().trim().min(7, "Телефон є обов'язковим."), 
 role: z.enum(["ADMIN", "TRAINER", "CLIENT"]) 
      }) 
      .parse(req.body); 
    const { password, ...userData } = body; 
    const passwordHash = await bcrypt.hash(password, 10); 
    const user = await prisma.user.create({ 
      data: { ...userData, passwordHash }, 
      select: userSelect 
    }); 
    res.status(201).json(user); 
  }) 
); 
app.post( 
96 
482.ЧДТУ 262240 12 01 9 
  "/users/:id/balance", 
  requireAuth, 
  requireRole("ADMIN"), 
  asyncHandler(async (req, res) => { 
    const body = z 
      .object({ 
        amount: z.coerce.number().positive() 
      }) 
      .parse(req.body); 
    const user = await prisma.user.update({ 
      where: { id: req.params.id }, 
      data: { 
        balance: { 
          increment: new Prisma.Decimal(body.amount) 
        } 
      }, 
      select: userSelect 
    }); 
    res.json(user); 
  }) 
); 
app.patch( 
  "/users/:id/assigned-trainer", 
  requireAuth, 
  requireRole("ADMIN"), 
  asyncHandler(async (req, res) => { 
    const body = z 
      .object({ 
        trainerId: z.string().nullable().optional() 
97 
482.ЧДТУ 262240 12 01 10 
   }) 
      .parse(req.body); 
    const client = await prisma.user.findUnique({ where: { id: req.params.id } }); 
    if (!client || client.role !== "CLIENT") { 
      return res.status(404).json({ message: "Клієнта не знайдено." }); 
    } 
    if (body.trainerId) { 
      const trainer = await prisma.user.findUnique({ where: { id: body.trainerId } }); 
      if (!trainer || trainer.role !== "TRAINER") { 
        return res.status(400).json({ message: "Оберіть коректного тренера." }); 
      } 
    } 
    const user = await prisma.user.update({ 
      where: { id: req.params.id }, 
      data: { assignedTrainerId: body.trainerId ?? null }, 
      select: userSelect 
    }); 
    res.json(user); 
  }) 
); 
app.get( 
  "/membership-plans", 
  requireAuth, 
  asyncHandler(async (req, res) => { 
    const plans = await prisma.membershipPlan.findMany({ orderBy: { price: "asc" } }); 
    res.json(plans); 
  }) 
98 
482.ЧДТУ 262240 12 01 11
); 
app.post( 
  "/membership-plans", 
  requireAuth, 
  requireRole("ADMIN"), 
  asyncHandler(async (req, res) => { 
    const body = z 
      .object({ 
        name: z.string().min(2), 
        price: z.coerce.number().positive(), 
        durationDays: z.coerce.number().int().positive(), 
        visitLimit: z.coerce.number().int().positive().nullable().optional(), 
        description: z.string().optional() 
      }) 
      .parse(req.body); 
    const plan = await prisma.membershipPlan.create({ data: body }); 
    res.status(201).json(plan); 
  }) 
); 
app.delete( 
  "/membership-plans/:id", 
  requireAuth, 
  requireRole("ADMIN"), 
  asyncHandler(async (req, res) => { 
    await prisma.$transaction(async (tx) => { 
      await tx.membership.deleteMany({ where: { planId: req.params.id } }); 
      await tx.membershipPlan.delete({ where: { id: req.params.id } }); 
    }); 
    res.status(204).send(); 
99 
482.ЧДТУ 262240 12 01 12
  }) 
); 
app.get( 
  "/memberships", 
  requireAuth, 
  asyncHandler(async (req, res) => { 
    const where = req.user.role === "CLIENT" ? { clientId: req.user.id } : undefined; 
    const memberships = await prisma.membership.findMany({ 
      where, 
      include: { plan: true, client: { select: userSelect }, payments: true }, 
      orderBy: { startDate: "desc" } 
    }); 
    res.json(memberships); 
  }) 
); 
app.post( 
  "/memberships", 
  requireAuth, 
  requireRole("ADMIN"), 
  asyncHandler(async (req, res) => { 
    const body = z 
      .object({ 
        clientId: z.string(), 
        planId: z.string(), 
        startDate: z.coerce.date() 
      }) 
      .parse(req.body); 
    const plan = await prisma.membershipPlan.findUniqueOrThrow({ where: { id: 
body.planId } }); 
    const endDate = new Date(body.startDate); 
100 
482.ЧДТУ 262240 12 01 13
    endDate.setDate(endDate.getDate() + plan.durationDays); 
    const validUntil = endOfDay(endDate); 
    const membership = await prisma.membership.create({ 
      data: { 
        clientId: body.clientId, 
     planId: body.planId, 
        startDate: body.startDate, 
        endDate: validUntil, 
        visitsRemaining: plan.visitLimit 
      }, 
      include: { plan: true, client: { select: userSelect } } 
    }); 
    res.status(201).json(membership); 
  }) 
); 
app.delete( 
  "/memberships/:id", 
  requireAuth, 
  requireRole("ADMIN"), 
  asyncHandler(async (req, res) => { 
    await prisma.membership.delete({ where: { id: req.params.id } }); 
    res.status(204).send(); 
  }) 
); 
app.post( 
  "/membership-plans/:id/purchase", 
  requireAuth, 
  requireRole("CLIENT"), 
  asyncHandler(async (req, res) => {
101 
482.ЧДТУ 262240 12 01 14 
    const membership = await prisma.$transaction(async (tx) => { 
      const [plan, user] = await Promise.all([ 
        tx.membershipPlan.findUniqueOrThrow({ where: { id: req.params.id } }), 
        tx.user.findUniqueOrThrow({ where: { id: req.user.id }, select: { id: true, balance: true 
} }) 
      ]); 
      if (user.balance.lt(plan.price)) { 
        return null; 
      } 
      const startDate = new Date(); 
      const endDate = new Date(startDate); 
      endDate.setDate(endDate.getDate() + plan.durationDays); 
      await tx.user.update({ 
        where: { id: user.id }, 
        data: { 
          balance: { 
decrement: plan.price 
          } 
        } 
      }); 
      return tx.membership.create({ 
        data: { 
          clientId: req.user.id, 
          planId: plan.id, 
          startDate, 
          endDate: endOfDay(endDate), 
          visitsRemaining: plan.visitLimit, 
          payments: { create: { amount: plan.price } } 
102 
482.ЧДТУ 262240 12 01 15 
        }, 
        include: { plan: true, client: { select: userSelect }, payments: true } 
      }); 
    }); 
    if (!membership) { 
      return res.status(409).json({ message: "Недостатньо коштів на балансі для покупки 
абонемента." }); 
    } 
    res.status(201).json(membership); 
  }) 
); 
app.get( 
  "/workouts", 
  requireAuth, 
  asyncHandler(async (req, res) => { 
    let where; 
    if (req.user.role === "TRAINER") { 
      where = { trainerId: req.user.id }; 
    } else if (req.user.role === "CLIENT") { 
      const client = await prisma.user.findUnique({ 
        where: { id: req.user.id }, 
        select: { assignedTrainerId: true } 
  }); 
      where = { 
        startsAt: { gte: new Date() }, 
        OR: [ 
          { capacity: { gt: 1 } }, 
          ...(client?.assignedTrainerId ? [{ capacity: 1, trainerId: client.assignedTrainerId }] : []) 
        ] 
103 
482.ЧДТУ 262240 12 01 16 
      }; 
    } 
    const workouts = await prisma.workout.findMany({ 
      where, 
      include: { 
        trainer: { select: userSelect }, 
        bookings: { include: { client: { select: userSelect }, attendance: true } } 
      }, 
      orderBy: { startsAt: "asc" } 
    }); 
    res.json(workouts); 
  }) 
); 
app.post( 
  "/workouts", 
  requireAuth, 
  requireRole("ADMIN", "TRAINER"), 
  asyncHandler(async (req, res) => { 
    const body = z 
      .object({ 
        title: z.string().min(2), 
        description: z.string().optional(), 
        trainerId: z.string().optional(), 
        startsAt: z.coerce.date(), 
        endsAt: z.coerce.date(), 
        capacity: z.coerce.number().int().positive() 
      }) 
      .parse(req.body); 
    const trainerId = req.user.role === "TRAINER" ? req.user.id : body.trainerId; 
    if (!trainerId) { 
104 
482.ЧДТУ 262240 12 01 17 
      return res.status(400).json({ message: "Оберіть тренера для тренування." }); 
    } 
    if (body.endsAt <= body.startsAt) { 
      return res.status(400).json({ message: "Час завершення має бути пізніше часу 
початку." }); 
    } 
    const workout = await prisma.workout.create({ data: { ...body, trainerId } }); 
    res.status(201).json(workout); 
  }) 
); 
app.delete( 
  "/workouts/:id", 
  requireAuth, 
  requireRole("ADMIN"), 
  asyncHandler(async (req, res) => { 
    const workout = await prisma.workout.findUnique({ where: { id: req.params.id } }); 
    if (!workout) { 
      return res.status(404).json({ message: "Тренування не знайдено." }); 
    } 
    await prisma.workout.delete({ where: { id: req.params.id } }); 
    res.status(204).send(); 
  }) 
); 
app.post( 
  "/workouts/:id/book", 
  requireAuth, 
105 
482.ЧДТУ 262240 12 01 18
  requireRole("CLIENT"), 
  asyncHandler(async (req, res) => { 
    const now = new Date(); 
    const todayStart = startOfToday(); 
    const membership = await prisma.membership.findFirst({ 
      where: { 
        clientId: req.user.id, 
        status: "ACTIVE", 
        startDate: { lte: now }, 
        endDate: { gte: todayStart }, 
        OR: [{ visitsRemaining: null }, { visitsRemaining: { gt: 0 } }] 
      } 
    }); 
    if (!membership) { 
      return res.status(409).json({ message: "Для запису потрібен активний абонемент." }); 
    } 
    const workout = await prisma.workout.findUnique({ 
      where: { id: req.params.id }, 
      include: { bookings: true } 
    }); 
    if (!workout) { 
 return res.status(404).json({ message: "Тренування не знайдено." }); 
    } 
    if (workout.capacity === 1) { 
      const client = await prisma.user.findUnique({ 
        where: { id: req.user.id }, 
        select: { assignedTrainerId: true } 
      }); 
106 
482.ЧДТУ 262240 12 01 19 
   if (client?.assignedTrainerId !== workout.trainerId) { 
        return res.status(403).json({ message: "Індивідуальні заняття доступні лише у 
закріпленого тренера." }); 
      } 
    } 
    const activeBookings = workout.bookings.filter((booking) => booking.status === 
"BOOKED"); 
    if (activeBookings.length >= workout.capacity) { 
      return res.status(409).json({ message: "На тренування вже немає вільних місць." }); 
    } 
    const existingBooking = workout.bookings.find((booking) => booking.clientId === 
req.user.id); 
    if (existingBooking?.status === "BOOKED") { 
      return res.status(409).json({ message: "Ви вже записані на це тренування." }); 
    } 
    if (existingBooking && existingBooking.status !== "CANCELLED") { 
      return res.status(409).json({ message: "Цей запис уже не можна змінити." }); 
    } 
    const booking = existingBooking 
      ? await prisma.booking.update({ 
          where: { id: existingBooking.id }, 
          data: { status: "BOOKED" }, 
          include: { workout: true } 
      }) 
      : await prisma.booking.create({ 
          data: { workoutId: workout.id, clientId: req.user.id }, 
          include: { workout: true } 
        }); 
107 
482.ЧДТУ 262240 12 01 20 
    res.status(201).json(booking); 
  }) 
); 
app.post( 
  "/bookings/:id/cancel", 
  requireAuth, 
  requireRole("CLIENT"), 
  asyncHandler(async (req, res) => { 
    const booking = await prisma.booking.findUnique({ 
      where: { id: req.params.id }, 
      include: { workout: true } 
    }); 
    if (!booking || booking.clientId !== req.user.id) { 
  return res.status(404).json({ message: "Запис не знайдено." }); 
    } 
    if (booking.status !== "BOOKED") { 
      return res.status(409).json({ message: "Скасувати можна лише активний запис." }); 
    } 
    if (booking.workout.startsAt <= new Date()) { 
      return res.status(409).json({ message: "Не можна скасувати тренування, яке вже 
почалося." }); 
    } 
    const cancelledBooking = await prisma.booking.update({ 
      where: { id: booking.id }, 
      data: { status: "CANCELLED" }, 
      include: { workout: true } 
108 
482.ЧДТУ 262240 12 01 21 
    }); 
    res.json(cancelledBooking); 
  }) 
); 
app.post( 
  "/bookings/:id/attendance", 
  requireAuth, 
  requireRole("ADMIN", "TRAINER"), 
  asyncHandler(async (req, res) => { 
    const booking = await prisma.booking.findUnique({ 
      where: { id: req.params.id }, 
      include: { workout: true } 
    }); 
    if (!booking) { 
      return res.status(404).json({ message: "Запис не знайдено." }); 
    } 
    if (req.user.role === "TRAINER" && booking.workout.trainerId !== req.user.id) { 
      return res.status(403).json({ message: "Тренер може відмічати лише власні 
тренування." }); 
    } 
    const attendance = await prisma.$transaction(async (tx) => { 
      await tx.booking.update({ where: { id: booking.id }, data: { status: "ATTENDED" } }); 
      const membership = await tx.membership.findFirst({ 
        where: { 
          clientId: booking.clientId, 
          status: "ACTIVE", 
109 
482.ЧДТУ 262240 12 01 22 
          startDate: { lte: new Date() }, 
          endDate: { gte: startOfToday() } 
        }, 
        orderBy: { endDate: "asc" } 
      }); 
      if (membership?.visitsRemaining && membership.visitsRemaining > 0) { 
        await tx.membership.update({ 
          where: { id: membership.id }, 
          data: { visitsRemaining: membership.visitsRemaining - 1 } 
        }); 
      } 
      return tx.attendance.upsert({ 
        where: { bookingId: booking.id }, 
        create: { bookingId: booking.id, markedById: req.user.id }, 
        update: { markedById: req.user.id, markedAt: new Date() } 
      }); 
    }); 
    res.status(201).json(attendance); 
  }) 
); 
app.get( 
  "/dashboard/stats", 
  requireAuth, 
  requireRole("ADMIN"), 
  asyncHandler(async (req, res) => { 
    const now = new Date(); 
    const inSevenDays = new Date(now); 
    inSevenDays.setDate(now.getDate() + 7); 
110 
482.ЧДТУ 262240 12 01 23 
    const [clients, trainers, activeMemberships, expiringMemberships, workouts, attendance] 
= await Promise.all([ 
      prisma.user.count({ where: { role: "CLIENT" } }), 
      prisma.user.count({ where: { role: "TRAINER" } }), 
      prisma.membership.count({ where: { status: "ACTIVE", endDate: { gte: now } } }), 
      prisma.membership.count({ where: { status: "ACTIVE", endDate: { gte: now, lte: 
inSevenDays } } }), 
      prisma.workout.count(), 
      prisma.attendance.count() 
    ]); 
    res.json({ clients, trainers, activeMemberships, expiringMemberships, workouts, 
attendance }); 
  }) 
); 
app.use((error, req, res, next) => { 
  if (error instanceof z.ZodError) { 
    return res.status(400).json({ message: "Помилка валідації.", issues: error.issues }); 
  } 
  if (error.code === "P2002") { 
    return res.status(409).json({ message: "Користувач з таким email вже існує." }); 
  } 
  console.error(error); 
  res.status(500).json({ message: "Внутрішня помилка сервера." }); 
}); 
app.listen(port, host, () => { 
  console.log(`Gym backend is running on http://${host}:${port}`); 
111 
482.ЧДТУ 262240 12 01 24 
  cleanupFinishedWorkouts().catch((error) => console.error("Failed to clean finished 
workouts", error)); 
  setInterval(() => { 
    cleanupFinishedWorkouts().catch((error) => console.error("Failed to clean finished 
workouts", error)); 
  }, 60 * 1000); 
}); 
auth.js 
import jwt from "jsonwebtoken"; 
import { prisma } from "../db.js"; 
export async function requireAuth(req, res, next) { 
  const header = req.headers.authorization; 
  const token = header?.startsWith("Bearer ") ? header.slice(7) : null; 
  if (!token) { 
    return res.status(401).json({ message: "Потрібен токен авторизації." }); 
  } 
  try { 
    const payload = jwt.verify(token, process.env.JWT_SECRET); 
    const user = await prisma.user.findUnique({ 
      where: { id: payload.sub }, 
      select: { id: true, name: true, email: true, phone: true, balance: true, role: true } 
    }); 
    if (!user) { 
      return res.status(401).json({ message: "Користувача більше не існує." }); 
    } 
    req.user = user; 
    next(); 
112 
482.ЧДТУ 262240 12 01 25
  } catch { 
    res.status(401).json({ message: "Недійсний або прострочений токен." }); 
  } 
} 
export function requireRole(...roles) { 
  return (req, res, next) => { 
    if (!roles.includes(req.user.role)) { 
      return res.status(403).json({ message: "Недостатньо прав доступу." }); 
    } 
    next(); 
  }; 
} 
main.jsx 
import React, { useEffect, useMemo, useState } from "react"; 
import { createRoot } from "react-dom/client"; 
import { 
  Activity, 
  CalendarPlus, 
  ChevronLeft, 
  ChevronRight, 
  ClipboardList, 
  CreditCard, 
  Dumbbell, 
  LogOut, 
  Plus, 
  RefreshCw, 
  Search, 
  Shield, 
  Trash2, 
  Users, 
113 
482.ЧДТУ 262240 12 01 26 
  Wallet 
} from "lucide-react"; 
import "./styles.css"; 
const API_URL = import.meta.env.VITE_API_URL ?? "http://localhost:4000"; 
const pageSize = 6; 
const roleLabels = { 
  ADMIN: "Адміністратор", 
  TRAINER: "Тренер", 
  CLIENT: "Клієнт" 
}; 
const tabs = [ 
  { id: "overview", label: "Огляд", icon: Activity }, 
  { id: "users", label: "Користувачі", icon: Users }, 
  { id: "balance", label: "Баланс", icon: Wallet }, 
  { id: "plans", label: "Абонементи", icon: CreditCard }, 
  { id: "memberships", label: "Видача", icon: ClipboardList }, 
  { id: "workouts", label: "Тренування", icon: CalendarPlus } 
]; 
function apiRequest(path, options = {}, token) { 
  return fetch(`${API_URL}${path}`, { 
    ...options, 
    headers: { 
      "Content-Type": "application/json", 
      ...(token ? { Authorization: `Bearer ${token}` } : {}), 
      ...options.headers 
    } 
  }).then(async (response) => { 
    const data = await response.json().catch(() => null); 
114 
482.ЧДТУ 262240 12 01 27 
    if (!response.ok) { 
      throw new Error(data?.message ?? "Запит не виконано"); 
    } 
    return data; 
  }); 
} 
function formatRole(role) { 
  return roleLabels[role] ?? role; 
} 
function formatDateTime(value) { 
  return new Date(value).toLocaleString("uk-UA"); 
} 
function formatDate(value) { 
  return new Date(value).toLocaleDateString("uk-UA"); 
} 
function formatMoney(value) { 
  return `${Number(value ?? 0).toLocaleString("uk-UA", { 
    minimumFractionDigits: 2, 
    maximumFractionDigits: 2 
  })} грн`; 
} 
function todayInputValue() { 
  return new Date().toISOString().slice(0, 10); 
} 
function localDateTimeInputValue(date) { 
  const offsetDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000); 
115 
482.ЧДТУ 262240 12 01 28 
  return offsetDate.toISOString().slice(0, 16); 
} 
function addMinutes(date, minutes) { 
  const nextDate = new Date(date); 
  nextDate.setMinutes(nextDate.getMinutes() + minutes); 
  return nextDate; 
} 
function roundToHour(date) { 
  const nextDate = new Date(date); 
  nextDate.setMinutes(0, 0, 0); 
  return nextDate; 
} 
function isPersonalWorkout(workout) { 
  return Number(workout.capacity) === 1 || 
workout.title?.toLowerCase().includes("індивідуаль"); 
} 
function normalize(value) { 
  return String(value ?? "").toLowerCase().trim(); 
} 
function matchesQuery(values, query) { 
  const needle = normalize(query); 
  if (!needle) return true; 
  return values.some((value) => normalize(value).includes(needle)); 
} 
function usePagination(items, size = pageSize, resetKey = "") { 
  const [page, setPage] = useState(1); 
116 
482.ЧДТУ 262240 12 01 29 
  const totalPages = Math.max(1, Math.ceil(items.length / size)); 
  useEffect(() => { 
    setPage(1); 
  }, [resetKey]); 
  useEffect(() => { 
    setPage((current) => Math.min(current, totalPages)); 
  }, [totalPages]); 
  const start = (page - 1) * size; 
  return { 
    page, 
    totalPages, 
    pageItems: items.slice(start, start + size), 
    setPage 
  }; 
} 
function Pagination({ page, totalPages, setPage, total }) { 
  if (total <= pageSize) return null; 
  return ( 
    <div className="pagination"> 
      <span> 
        Сторінка {page} з {totalPages} 
      </span> 
      <div className="pagination-actions"> 
        <button className="icon-action" disabled={page === 1} onClick={() => setPage(page - 
1)} title="Попередня сторінка"> 
          <ChevronLeft size={18} /> 
        </button> 
117 
482.ЧДТУ 262240 12 01 30 
        <button className="icon-action" disabled={page === totalPages} onClick={() => 
setPage(page + 1)} title="Наступна сторінка"> 
          <ChevronRight size={18} /> 
        </button> 
      </div> 
    </div> 
  ); 
} 
function Field({ label, hint = " ", children }) { 
  return ( 
    <label className="field"> 
      <span>{label}</span> 
      {children} 
      <small>{hint}</small> 
    </label> 
  ); 
} 
function SectionHeader({ title, description }) { 
  return ( 
    <div className="section-header"> 
      <div> 
        <h3>{title}</h3> 
        <p>{description}</p> 
      </div> 
    </div> 
  ); 
} 
function FormMessage({ message }) { 
  if (!message) return null; 
118 
482.ЧДТУ 262240 12 01 31 
  return <p className={message.type === "error" ? "error" : "success"}>{message.text}</p>; 
} 
function SearchBox({ value, onChange, placeholder }) { 
  return ( 
    <label className="search-box"> 
      <Search size={18} /> 
      <input value={value} onChange={(event) => onChange(event.target.value)} 
placeholder={placeholder} /> 
    </label> 
  ); 
} 
function EmptyState({ text }) { 
  return <div className="empty-state">{text}</div>; 
} 
function Login({ onLogin }) { 
  const [email, setEmail] = useState("[email protected]"); 
  const [password, setPassword] = useState("password123"); 
  const [error, setError] = useState(""); 
  async function submit(event) { 
    event.preventDefault(); 
    setError(""); 
    try { 
      const session = await apiRequest("/auth/login", { 
        method: "POST", 
        body: JSON.stringify({ email, password }) 
      }); 
      if (session.user.role !== "ADMIN") { 
        setError("Веб-панель доступна лише адміністраторам."); 
119 
482.ЧДТУ 262240 12 01 32 
        return; 
      } 
      onLogin(session); 
    } catch (error) { 
      setError(error.message); 
    } 
  } 
  return ( 
    <main className="login-screen"> 
      <form className="login-box" onSubmit={submit}> 
        <div className="brand-row"> 
          <Shield size={28} /> 
          <div> 
<h1>Адмін-панель залу</h1> 
<p>Робоче місце адміністратора</p>
          </div> 
        </div> 
        <Field label="Email"> 
          <input value={email} onChange={(event) => setEmail(event.target.value)} /> 
        </Field> 
        <Field label="Пароль"> 
          <input type="password" value={password} onChange={(event) => 
setPassword(event.target.value)} /> 
        </Field> 
        {error && <p className="error">{error}</p>} 
        <button type="submit">Увійти</button> 
      </form> 
    </main> 
  ); 
} 
120 
482.ЧДТУ 262240 12 01 33
function StatCard({ icon: Icon, label, value }) { 
  return ( 
    <article className="stat-card"> 
      <Icon size={22} /> 
      <span>{label}</span> 
      <strong>{value ?? 0}</strong> 
    </article> 
  ); 
} 
function UsersForm({ token, reload }) { 
  const initial = { name: "", email: "", password: "password123", phone: "", role: "CLIENT" }; 
  const [form, setForm] = useState(initial); 
  const [message, setMessage] = useState(null); 
  const [saving, setSaving] = useState(false); 
  async function submit(event) { 
    event.preventDefault(); 
    setSaving(true); 
    setMessage(null); 
    try { 
      await apiRequest("/users", { method: "POST", body: JSON.stringify(form) }, token); 
      setForm(initial); 
      setMessage({ type: "success", text: "Користувача додано." }); 
      reload(); 
    } catch (error) { 
      setMessage({ type: "error", text: error.message }); 
    } finally { 
      setSaving(false); 
    } 
  } 
121 
482.ЧДТУ 262240 12 01 34
  return ( 
    <form className="form-card" onSubmit={submit}> 
      <div className="form-grid user-form-grid"> 
        <Field label="Ім'я" hint="Повне ім'я користувача"> 
          <input required value={form.name} onChange={(event) => setForm({ ...form, name: 
event.target.value })} /> 
        </Field> 
        <Field label="Email" hint="Використовується для входу"> 
          <input required type="email" value={form.email} onChange={(event) => setForm({ 
...form, email: event.target.value })} /> 
      </Field> 
        <Field label="Телефон" hint="Обов'язково для зв'язку з користувачем"> 
          <input required minLength={7} value={form.phone} onChange={(event) => setForm({ 
...form, phone: event.target.value })} /> 
        </Field> 
        <Field label="Роль" hint="Доступ у системі"> 
          <select value={form.role} onChange={(event) => setForm({ ...form, role: 
event.target.value })}> 
<option value="CLIENT">Клієнт</option> 
<option value="TRAINER">Тренер</option> 
    <option value="ADMIN">Адміністратор</option> 
          </select> 
        </Field> 
        <Field label="Початковий пароль" hint="Мінімум 6 символів"> 
          <input required minLength={6} value={form.password} onChange={(event) => 
setForm({ ...form, password: event.target.value })} /> 
        </Field> 
      </div> 
      <div className="form-footer"> 
        <FormMessage message={message} /> 
        <button disabled={saving}> 
          <Plus size={16} /> {saving ? "Збереження..." : "Додати користувача"} 
        </button> 
122 
482.ЧДТУ 262240 12 01 35 
      </div> 
    </form> 
  ); 
} 
function BalanceTopUpRow({ client, token, reload }) { 
  const [amount, setAmount] = useState(""); 
  const [message, setMessage] = useState(null); 
  const [saving, setSaving] = useState(false); 
  async function submit(event) { 
    event.preventDefault(); 
    setSaving(true); 
    setMessage(null); 
    try { 
      await apiRequest(`/users/${client.id}/balance`, { 
        method: "POST", 
        body: JSON.stringify({ amount: Number(amount) }) 
      }, token); 
      setAmount(""); 
      setMessage({ type: "success", text: "Баланс клієнта поповнено." }); 
      reload(); 
    } catch (error) { 
      setMessage({ type: "error", text: error.message }); 
    } finally { 
      setSaving(false); 
    } 
  } 
  return ( 
    <form className="balance-row" onSubmit={submit}> 
      <div className="balance-client"> 
123 
482.ЧДТУ 262240 12 01 36 
        <strong>{client.name}</strong> 
        <span>{client.email}</span> 
        <span>{client.phone || "Без телефону"}</span> 
      </div> 
      <strong className="balance-amount">{formatMoney(client.balance)}</strong> 
      <div className="balance-actions"> 
        <input required min="1" placeholder="Сума, грн" step="0.01" type="number" 
value={amount} onChange={(event) => setAmount(event.target.value)} /> 
        <button disabled={saving}> 
          <Plus size={16} /> {saving ? "Поповнення..." : "Поповнити баланс"} 
        </button> 
      </div> 
      <FormMessage message={message} /> 
    </form> 
  ); 
} 
function BalanceTab({ clients, pager, query, setQuery, token, reload }) { 
  return ( 
    <section className="panel"> 
      <SectionHeader title="Баланс клієнтів" description="Пошук клієнта і поповнення 
балансу без довгих випадаючих списків" count={clients.length} /> 
      <div className="toolbar"> 
        <SearchBox value={query} onChange={setQuery} placeholder="Пошук за ім'ям, email, 
телефоном або балансом" /> 
      </div> 
      {clients.length ? ( 
        <> 
          <div className="balance-list"> 
{pager.pageItems.map((client) => ( 
<BalanceTopUpRow client={client} key={client.id} reload={reload} token={token} /> 
))} 
          </div> 
124 
482.ЧДТУ 262240 12 01 37 
          <Pagination {...pager} total={clients.length} /> 
        </> 
      ) : <EmptyState text="Клієнтів за цим запитом не знайдено." />} 
    </section> 
  ); 
} 
function ClientTrainerAssignment({ client, trainers, token, reload }) { 
  const [editing, setEditing] = useState(false); 
  const [query, setQuery] = useState(""); 
  const [saving, setSaving] = useState(false); 
  const filteredTrainers = trainers 
    .filter((trainer) => matchesQuery([trainer.name, trainer.email, trainer.phone], query)) 
    .slice(0, 5); 
  async function assign(trainerId) { 
    setSaving(true); 
    try { 
      await apiRequest(`/users/${client.id}/assigned-trainer`, { 
        method: "PATCH", 
        body: JSON.stringify({ trainerId }) 
      }, token); 
      setEditing(false); 
      setQuery(""); 
      reload(); 
    } catch (error) { 
      window.alert(error.message); 
    } finally { 
      setSaving(false); 
    } 
  } 
125 
482.ЧДТУ 262240 12 01 38 
  return ( 
    <div className="assignment-cell"> 
      <div className="assignment-summary"> 
        <strong>{client.assignedTrainer?.name ?? "Без тренера"}</strong> 
        <span>{client.assignedTrainer ? client.assignedTrainer.email : "Індивідуальні заняття 
приховані"}</span> 
      </div> 
      <button className="ghost compact-action" disabled={saving} onClick={() => 
setEditing((current) => !current)} type="button"> 
        {client.assignedTrainer ? "Змінити" : "Призначити"} 
      </button> 
      {editing && ( 
        <div className="assignment-popover"> 
          <SearchBox value={query} onChange={setQuery} placeholder="Пошук тренера" /> 
          <div className="client-results"> 
{filteredTrainers.map((trainer) => ( 
<button className="client-result" disabled={saving} key={trainer.id} onClick={() => 
assign(trainer.id)} type="button"> 
<span className="client-result-main"> 
<strong>{trainer.name}</strong> 
<small>{trainer.email}</small> 
</span> 
<small className="client-result-meta">{trainer.phone || "Без телефону"}</small> 
</button> 
))} 
{client.assignedTrainer && ( 
<button className="client-result danger-soft" disabled={saving} onClick={() => 
assign(null)} type="button"> 
<span className="client-result-main"> 
<strong>Прибрати тренера</strong> 
<small>Клієнт не бачитиме індивідуальні заняття</small> 
</span> 
</button> 
126 
482.ЧДТУ 262240 12 01 39 
)} 
{trainers.length > 0 && filteredTrainers.length === 0 && <EmptyState text="Тренерів 
за цим запитом не знайдено." />} 
          </div> 
        </div> 
      )} 
    </div> 
  ); 
} 
function PlanForm({ token, reload }) { 
  const initial = { name: "", price: "", durationDays: 30, visitLimit: 12, description: "" }; 
  const [form, setForm] = useState(initial); 
  const [message, setMessage] = useState(null); 
  const [saving, setSaving] = useState(false); 
  async function submit(event) { 
    event.preventDefault(); 
    setSaving(true); 
 setMessage(null); 
    try { 
      await apiRequest("/membership-plans", { 
        method: "POST", 
        body: JSON.stringify({ 
          ...form, 
          price: Number(form.price), 
          durationDays: Number(form.durationDays), 
          visitLimit: form.visitLimit === "" ? null : Number(form.visitLimit) 
        }) 
      }, token); 
      setForm(initial); 
      setMessage({ type: "success", text: "Тип абонемента створено." }); 
127 
482.ЧДТУ 262240 12 01 40
      reload(); 
    } catch (error) { 
      setMessage({ type: "error", text: error.message }); 
    } finally { 
      setSaving(false); 
    } 
  } 
  return ( 
    <form className="form-card" onSubmit={submit}> 
      <div className="form-grid"> 
        <Field label="Назва"> 
          <input required value={form.name} onChange={(event) => setForm({ ...form, name: 
event.target.value })} /> 
        </Field> 
        <Field label="Ціна, грн"> 
          <input required min="1" type="number" value={form.price} onChange={(event) => 
setForm({ ...form, price: event.target.value })} /> 
    </Field> 
        <Field label="Тривалість, днів"> 
          <input required min="1" type="number" value={form.durationDays} onChange={(event) 
=> setForm({ ...form, durationDays: event.target.value })} /> 
        </Field> 
        <Field label="Ліміт відвідувань" hint="Залиш порожнім для безліміту"> 
          <input min="1" type="number" value={form.visitLimit ?? ""} onChange={(event) => 
setForm({ ...form, visitLimit: event.target.value })} /> 
        </Field> 
        <Field label="Опис"> 
          <textarea value={form.description} onChange={(event) => setForm({ ...form, description: 
event.target.value })} /> 
        </Field> 
      </div> 
      <div className="form-footer"> 
128 
482.ЧДТУ 262240 12 01 41 
        <FormMessage message={message} /> 
        <button disabled={saving}> 
     <Plus size={16} /> {saving ? "Збереження..." : "Створити абонемент"} 
        </button> 
      </div> 
    </form> 
  ); 
} 
function MembershipForm({ token, users, plans, reload }) { 
  const clients = users.filter((user) => user.role === "CLIENT"); 
  const initial = { planId: "", startDate: todayInputValue() }; 
  const [form, setForm] = useState(initial); 
  const [clientQuery, setClientQuery] = useState(""); 
  const [selectedClient, setSelectedClient] = useState(null); 
  const [message, setMessage] = useState(null); 
  const [saving, setSaving] = useState(false); 
  const selectedPlan = plans.find((plan) => plan.id === form.planId); 
  const filteredClients = clients 
    .filter((client) => matchesQuery([client.name, client.email, client.phone], clientQuery)) 
    .slice(0, 6); 
  async function submit(event) { 
    event.preventDefault(); 
    if (!selectedClient) { 
      setMessage({ type: "error", text: "Спочатку оберіть клієнта." }); 
      return; 
    } 
    setSaving(true); 
    setMessage(null); 
    try { 
129 
482.ЧДТУ 262240 12 01 42
      await apiRequest("/memberships", { method: "POST", body: JSON.stringify({ ...form, 
clientId: selectedClient.id }) }, token); 
      setForm(initial); 
      setClientQuery(""); 
      setSelectedClient(null); 
      setMessage({ type: "success", text: "Абонемент видано клієнту." }); 
      reload(); 
    } catch (error) { 
      setMessage({ type: "error", text: error.message }); 
    } finally { 
      setSaving(false); 
    } 
  } 
  return ( 
    <form className="form-card" onSubmit={submit}> 
      <div className="client-picker"> 
        <div className="client-picker-head"> 
          <div> 
<strong>Клієнт</strong> 
<span>{selectedClient ? `${selectedClient.name} · ${selectedClient.email}` : "Знайдіть 
клієнта за ім'ям, email або телефоном"}</span> 
          </div> 
          {selectedClient && (
<button className="ghost compact-action" onClick={() => setSelectedClient(null)} 
type="button"> 
Змінити 
</button> 
          )} 
        </div> 
        {!selectedClient && ( 
          <> 
130 
482.ЧДТУ 262240 12 01 43 
<SearchBox value={clientQuery} onChange={setClientQuery} placeholder="Пошук 
клієнта для видачі абонемента" /> 
<div className="client-results"> 
{filteredClients.map((client) => (
<button className="client-result" key={client.id} onClick={() => 
setSelectedClient(client)} type="button"> 
<span> 
<strong>{client.name}</strong> 
<small>{client.email}</small> 
</span> 
<small>{client.phone || "Без телефону"}</small> 
</button> 
))} 
{clients.length > 0 && filteredClients.length === 0 && <EmptyState text="Клієнтів за 
цим запитом не знайдено." />} 
</div> 
          </> 
        )} 
      </div> 
      <div className="form-grid"> 
        <Field label="Абонемент"> 
          <select required value={form.planId} onChange={(event) => setForm({ ...form, planId: 
event.target.value })}> 
<option value="">Оберіть тип</option> 
{plans.map((plan) => <option key={plan.id} value={plan.id}>{plan.name}</option>)} 
          </select> 
        </Field> 
        <Field label="Дата початку"> 
          <input required type="date" value={form.startDate} onChange={(event) => setForm({ 
...form, startDate: event.target.value })} /> 
        </Field> 
        <div className="summary-box"> 
          <span>Параметри</span> 
131 
482.ЧДТУ 262240 12 01 44
          <strong>{selectedPlan ? `${selectedPlan.durationDays} днів · ${selectedPlan.visitLimit 
?? "безліміт"} відвідувань` : "Оберіть абонемент"}</strong> 
        </div> 
      </div> 
      <div className="form-footer"> 
        <FormMessage message={message} /> 
        <button disabled={saving}> 
          <Plus size={16} /> {saving ? "Видача..." : "Видати абонемент"} 
        </button> 
      </div> 
    </form> 
  ); 
} 
function TrainerPicker({ trainers, selectedTrainer, setSelectedTrainer }) { 
  const [trainerQuery, setTrainerQuery] = useState(""); 
  const filteredTrainers = trainers 
    .filter((trainer) => matchesQuery([trainer.name, trainer.email, trainer.phone], trainerQuery)) 
    .slice(0, 6); 
  return ( 
    <div className="client-picker trainer-picker"> 
      <div className="client-picker-head"> 
        <div> 
          <strong>Тренер</strong> 
    <span> 
{selectedTrainer 
? `${selectedTrainer.name} · ${selectedTrainer.email}` 
: "Знайдіть тренера за ім'ям, email або телефоном"} 
          </span> 
        </div> 
        {selectedTrainer && ( 
132 
482.ЧДТУ 262240 12 01 45 
          <button className="ghost compact-action" onClick={() => setSelectedTrainer(null)} 
type="button"> 
Змінити 
          </button> 
        )} 
      </div> 
      {!selectedTrainer && ( 
        <> 
          <SearchBox value={trainerQuery} onChange={setTrainerQuery} placeholder="Пошук 
тренера для заняття" /> 
          <div className="client-results"> 
{filteredTrainers.map((trainer) => ( 
<button className="client-result" key={trainer.id} onClick={() => 
setSelectedTrainer(trainer)} type="button"> 
<span> 
<strong>{trainer.name}</strong> 
<small>{trainer.email}</small> 
</span> 
<small>{trainer.phone || "Без телефону"}</small> 
</button> 
))} 
{trainers.length > 0 && filteredTrainers.length === 0 && <EmptyState text="Тренерів 
за цим запитом не знайдено." />} 
          </div> 
        </> 
      )} 
    </div> 
  ); 
} 
function WorkoutTrainerFilter({ trainers, selectedTrainer, setSelectedTrainer }) { 
  const [query, setQuery] = useState(""); 
  const [open, setOpen] = useState(false); 
133 
482.ЧДТУ 262240 12 01 46 
  const filteredTrainers = trainers 
    .filter((trainer) => matchesQuery([trainer.name, trainer.email, trainer.phone], query)) 
    .slice(0, 6); 
  return ( 
    <div className="trainer-filter"> 
      <button className="ghost" onClick={() => setOpen((current) => !current)} type="button"> 
        {selectedTrainer ? selectedTrainer.name : "Усі тренери"} 
      </button> 
      {selectedTrainer && ( 
        <button className="icon-action" onClick={() => setSelectedTrainer(null)} title="Скинути 
фільтр тренера" type="button"> 
          × 
        </button> 
      )} 
      {open && (
        <div className="trainer-filter-popover"> 
          <SearchBox value={query} onChange={setQuery} placeholder="Пошук тренера" /> 
          <div className="client-results"> 
{filteredTrainers.map((trainer) => ( 
<button 
className="client-result" 
key={trainer.id} 
onClick={() => { 
setSelectedTrainer(trainer); 
setOpen(false); 
setQuery(""); 
}} 
type="button" 
> 
<span className="client-result-main"> 
          <strong>{trainer.name}</strong> 
134 
482.ЧДТУ 262240 12 01 47 
<small>{trainer.email}</small> 
</span> 
<small className="client-result-meta">{trainer.phone || "Без телефону"}</small> 
</button> 
))} 
       {trainers.length > 0 && filteredTrainers.length === 0 && <EmptyState text="Тренерів 
за цим запитом не знайдено." />} 
          </div> 
        </div> 
      )} 
    </div> 
  ); 
} 
function WorkoutForm({ token, users, reload }) { 
  const trainers = users.filter((user) => user.role === "TRAINER"); 
  const initialStartDate = roundToHour(addMinutes(new Date(), 60)); 
  const initialStart = localDateTimeInputValue(initialStartDate); 
  const initialEnd = localDateTimeInputValue(addMinutes(initialStartDate, 60)); 
  const initial = { title: "", description: "", startsAt: initialStart, endsAt: initialEnd, capacity: 10 }; 
  const [form, setForm] = useState(initial); 
  const [selectedTrainer, setSelectedTrainer] = useState(null); 
  const [message, setMessage] = useState(null); 
  const [saving, setSaving] = useState(false); 
  async function submit(event) { 
    event.preventDefault(); 
    if (!selectedTrainer) { 
      setMessage({ type: "error", text: "Спочатку оберіть тренера." }); 
      return; 
    } 
135 
482.ЧДТУ 262240 12 01 48 
    if (!form.title.trim() || Number(form.capacity) < 2) { 
      setMessage({ type: "error", text: "Заповніть назву тренування і місткість від 2 місць." }); 
      return; 
    } 
    setSaving(true); 
    setMessage(null); 
    try { 
      await apiRequest("/workouts", { 
       method: "POST", 
        body: JSON.stringify({ ...form, trainerId: selectedTrainer.id, capacity: 
Number(form.capacity) }) 
      }, token); 
      setForm(initial); 
      setSelectedTrainer(null); 
      setMessage({ type: "success", text: "Тренування додано в розклад." }); 
      reload(); 
    } catch (error) { 
      setMessage({ type: "error", text: error.message }); 
    } finally { 
      setSaving(false); 
    } 
  } 
  return ( 
    <form className="form-card" onSubmit={submit}> 
      <TrainerPicker trainers={trainers} selectedTrainer={selectedTrainer} 
setSelectedTrainer={setSelectedTrainer} /> 
      <div className="form-grid"> 
        <Field label="Назва тренування"> 
          <input required value={form.title} onChange={(event) => setForm({ ...form, title: 
event.target.value })} /> 
        </Field> 
136 
482.ЧДТУ 262240 12 01 49
        <Field label="Початок"> 
          <input required type="datetime-local" value={form.startsAt} onChange={(event) => 
setForm({ ...form, startsAt: event.target.value })} /> 
        </Field> 
        <Field label="Завершення"> 
          <input required type="datetime-local" value={form.endsAt} onChange={(event) => 
setForm({ ...form, endsAt: event.target.value })} /> 
        </Field> 
        <Field label="Місткість"> 
          <input required min="2" type="number" value={form.capacity} onChange={(event) => 
setForm({ ...form, capacity: event.target.value })} /> 
        </Field> 
        <Field label="Опис"> 
          <textarea value={form.description} onChange={(event) => setForm({ ...form, description: 
event.target.value })} /> 
        </Field> 
      </div> 
      <div className="form-footer"> 
        <FormMessage message={message} /> 
        <button disabled={saving}> 
          <CalendarPlus size={16} /> {saving ? "Збереження..." : "Додати тренування"} 
        </button> 
      </div> 
    </form> 
  ); 
} 
function AdminApp() { 
  const [session, setSession] = useState(() => { 
    const raw = localStorage.getItem("gym-admin-session"); 
    return raw ? JSON.parse(raw) : null; 
  }); 
  const [data, setData] = useState({ stats: {}, users: [], plans: [], memberships: [], workouts: [] }); 
137 
482.ЧДТУ 262240 12 01 50 
  const [activeTab, setActiveTab] = useState("overview"); 
  const [search, setSearch] = useState({ users: "", balance: "", plans: "", memberships: "", 
workouts: "" }); 
  const [workoutTrainerFilter, setWorkoutTrainerFilter] = useState(null); 
  const [workoutTypeFilter, setWorkoutTypeFilter] = useState("all"); 
  const [error, setError] = useState(""); 
  const [loading, setLoading] = useState(false); 
  const token = session?.token; 
  const filteredUsers = useMemo(() => data.users.filter((user) => user.role !== "ADMIN" && 
matchesQuery([ 
    user.name, 
    user.email, 
    user.phone, 
    user.balance, 
    user.assignedTrainer?.name, 
    user.assignedTrainer?.email 
  ], search.users)), [data.users, search.users]); 
  const filteredClients = useMemo(() => filteredUsers.filter((user) => user.role === "CLIENT"), 
[filteredUsers]); 
  const filteredTrainers = useMemo(() => filteredUsers.filter((user) => user.role === 
"TRAINER"), [filteredUsers]); 
  const filteredBalanceClients = useMemo(() => data.users.filter((user) => user.role === 
"CLIENT" && matchesQuery([ 
    user.name, 
    user.email, 
    user.phone, 
    user.balance 
  ], search.balance)), [data.users, search.balance]); 
138 
482.ЧДТУ 262240 12 01 51 
  const filteredPlans = useMemo(() => data.plans.filter((plan) => matchesQuery([ 
    plan.name, 
    plan.price, 
    plan.durationDays, 
    plan.visitLimit, 
    plan.description 
  ], search.plans)), [data.plans, search.plans]); 
  const filteredMemberships = useMemo(() => data.memberships.filter((membership) => 
matchesQuery([ 
    membership.client?.name, 
    membership.client?.email, 
    membership.plan?.name, 
    membership.endDate, 
    membership.visitsRemaining 
  ], search.memberships)), [data.memberships, search.memberships]); 
  const filteredWorkouts = useMemo(() => data.workouts.filter((workout) => { 
    const matchesTrainer = !workoutTrainerFilter || workout.trainerId === 
workoutTrainerFilter.id; 
    const matchesType = 
      workoutTypeFilter === "all" || 
      (workoutTypeFilter === "personal" ? isPersonalWorkout(workout) : 
!isPersonalWorkout(workout));
    return matchesTrainer && matchesType && matchesQuery([ 
      workout.title, 
      workout.trainer?.name, 
      workout.description, 
      isPersonalWorkout(workout) ? "індивідуальне" : "групове", 
      formatDateTime(workout.startsAt) 
    ], search.workouts); 
  }), [data.workouts, search.workouts, workoutTrainerFilter, workoutTypeFilter]); 
139 
482.ЧДТУ 262240 12 01 52 
  const clientsPager = usePagination(filteredClients, pageSize, search.users); 
  const trainersPager = usePagination(filteredTrainers, pageSize, search.users); 
  const balancePager = usePagination(filteredBalanceClients, pageSize, search.balance); 
  const plansPager = usePagination(filteredPlans, pageSize, search.plans); 
  const membershipsPager = usePagination(filteredMemberships, pageSize, 
search.memberships); 
  const workoutsPager = usePagination(filteredWorkouts, pageSize, `${search.workouts}-
${workoutTrainerFilter?.id ?? "all"}-${workoutTypeFilter}`); 
  async function load() { 
 if (!token) return; 
    setLoading(true); 
    setError(""); 
    try { 
      const [stats, users, plans, memberships, workouts] = await Promise.all([ 
        apiRequest("/dashboard/stats", {}, token), 
        apiRequest("/users", {}, token), 
        apiRequest("/membership-plans", {}, token), 
        apiRequest("/memberships", {}, token), 
        apiRequest("/workouts", {}, token) 
      ]); 
      setData({ stats, users, plans, memberships, workouts }); 
    } catch (error) { 
      setError(error.message); 
  } finally { 
      setLoading(false); 
    } 
  } 
  useEffect(() => { 
    load(); 
140 
482.ЧДТУ 262240 12 01 53 
  }, [token]); 
  function login(nextSession) { 
    localStorage.setItem("gym-admin-session", JSON.stringify(nextSession)); 
    setSession(nextSession); 
  } 
  function logout() { 
    localStorage.removeItem("gym-admin-session"); 
    setSession(null); 
  } 
  function updateSearch(key, value) { 
    setSearch((current) => ({ ...current, [key]: value })); 
  } 
  async function deletePlan(plan) { 
    const confirmed = window.confirm(`Видалити абонемент "${plan.name}"? Також будуть 
видалені видані клієнтам абонементи цього типу.`); 
    if (!confirmed) return; 
    try { 
      await apiRequest(`/membership-plans/${plan.id}`, { method: "DELETE" }, token); 
      load(); 
    } catch (error) { 
      setError(error.message); 
    } 
  } 
  async function deleteMembership(membership) { 
    const confirmed = window.confirm(`Видалити абонемент "${membership.plan.name}" у 
клієнта ${membership.client.name}?`); 
141 
482.ЧДТУ 262240 12 01 54 
    if (!confirmed) return; 
    try { 
      await apiRequest(`/memberships/${membership.id}`, { method: "DELETE" }, token); 
      load(); 
    } catch (error) { 
      setError(error.message); 
    } 
  } 
  async function deleteWorkout(workout) { 
    const confirmed = window.confirm(`Видалити тренування "${workout.title}"? Усі записи 
клієнтів на це тренування також буде видалено.`); 
    if (!confirmed) return; 
    try { 
      await apiRequest(`/workouts/${workout.id}`, { method: "DELETE" }, token); 
      load(); 
    } catch (error) { 
      setError(error.message); 
    } 
  } 
  if (!session) { 
    return <Login onLogin={login} />; 
  } 
  return ( 
    <main className="app-shell"> 
      <aside className="sidebar"> 
        <div className="brand-row"> 
          <Dumbbell size={28} /> 
142 
482.ЧДТУ 262240 12 01 55 
          <div> 
<h1>Адмін-панель залу</h1> 
<p>{session.user.name}</p> 
          </div> 
        </div> 
        <nav className="tabs" aria-label="Розділи адмін-панелі"> 
          {tabs.map(({ id, label, icon: Icon }) => ( 
<button key={id} className={`tab-button ${activeTab === id ? "active" : ""}`} 
onClick={() => setActiveTab(id)}> 
<Icon size={17} /> 
{label} 
</button> 
          ))} 
        </nav> 
        <button className="ghost" onClick={logout}><LogOut size={16} /> Вийти</button> 
      </aside> 
      <section className="content"> 
        <header className="topbar"> 
          <div> 
<h2>Панель керування</h2> 
<p>Користувачі, абонементи, видача та розклад спортивного залу</p>
         </div> 
          <button className="ghost" onClick={load} disabled={loading}> 
<RefreshCw size={16} /> {loading ? "Оновлення..." : "Оновити"} 
          </button> 
        </header> 
        {error && <p className="error">{error}</p>} 
     {activeTab === "overview" && ( 
          <section className="panel"> 
143 
482.ЧДТУ 262240 12 01 56 
<SectionHeader title="Огляд системи" description="Ключові показники для швидкої 
перевірки стану залу" count="4" /> 
<div className="stats-grid"> 
<StatCard icon={Users} label="Клієнти" value={data.stats.clients} /> 
<StatCard icon={Shield} label="Тренери" value={data.stats.trainers} /> 
<StatCard icon={Activity} label="Активні абонементи" 
value={data.stats.activeMemberships} /> 
<StatCard icon={CalendarPlus} label="Скоро завершуються" 
value={data.stats.expiringMemberships} /> 
</div> 
          </section> 
        )} 
        {activeTab === "users" && ( 
          <section className="panel"> 
<SectionHeader title="Користувачі" description="Створення клієнтів і тренерів. 
Адміністратори у списку не показуються" /> 
<UsersForm token={token} reload={load} /> 
<div className="toolbar"> 
<SearchBox value={search.users} onChange={(value) => updateSearch("users", 
value)} placeholder="Пошук за ім'ям, email, телефоном або балансом" /> 
</div> 
<div className="split-lists"> 
<section className="subpanel"> 
<div className="subpanel-header"> 
<h4>Клієнти</h4> 
<span>{filteredClients.length}</span> 
</div> 
{filteredClients.length ? ( 
<> 
<div className="table"> 
       {clientsPager.pageItems.map((user) => ( 
<div className="table-row users-row" key={user.id}> 
144 
482.ЧДТУ 262240 12 01 57 
<span>{user.name}</span> 
<span>{user.email}</span> 
<span>{user.phone || "Без телефону"}</span> 
<span>{formatMoney(user.balance)}</span> 
<ClientTrainerAssignment client={user} trainers={data.users.filter((nextUser) 
=> nextUser.role === "TRAINER")} token={token} reload={load} /> 
</div> 
))} 
</div> 
<Pagination {...clientsPager} total={filteredClients.length} /> 
</> 
) : <EmptyState text="Клієнтів за цим запитом не знайдено." />} 
</section> 
<section className="subpanel"> 
<div className="subpanel-header"> 
<h4>Тренери</h4> 
<span>{filteredTrainers.length}</span> 
      </div> 
{filteredTrainers.length ? ( 
<> 
<div className="table"> 
{trainersPager.pageItems.map((user) => ( 
<div className="table-row trainers-row" key={user.id}> 
<span>{user.name}</span> 
<span>{user.email}</span> 
<span>{user.phone || "Без телефону"}</span> 
</div> 
))} 
   </div> 
<Pagination {...trainersPager} total={filteredTrainers.length} /> 
</> 
145 
482.ЧДТУ 262240 12 01 58 
) : <EmptyState text="Тренерів за цим запитом не знайдено." />} 
</section> 
</div> 
          </section> 
        )} 
        {activeTab === "balance" && ( 
          <BalanceTab 
clients={filteredBalanceClients} 
pager={balancePager} 
query={search.balance} 
reload={load} 
setQuery={(value) => updateSearch("balance", value)} 
token={token} 
          /> 
        )} 
        {activeTab === "plans" && ( 
          <section className="panel"> 
<SectionHeader title="Типи абонементів" description="Тарифи, ліміти відвідувань і 
тривалість дії" count={filteredPlans.length} /> 
<PlanForm token={token} reload={load} /> 
<div className="toolbar"> 
<SearchBox value={search.plans} onChange={(value) => updateSearch("plans", 
value)} placeholder="Пошук за назвою, ціною або описом" /> 
</div> 
{filteredPlans.length ? ( 
<> 
<div className="cards-row"> 
{plansPager.pageItems.map((plan) => ( 
<article className="mini-card" key={plan.id}> 
<strong>{plan.name}</strong> 
146 
482.ЧДТУ 262240 12 01 59 
<span>{plan.price} грн</span> 
<span>{plan.durationDays} днів / {plan.visitLimit ?? "безліміт"} 
відвідувань</span> 
{plan.description && <p>{plan.description}</p>} 
<button className="danger-action" onClick={() => deletePlan(plan)} 
type="button"> 
<Trash2 size={16} /> Видалити 
</button> 
</article> 
))} 
</div> 
<Pagination {...plansPager} total={filteredPlans.length} /> 
</> 
) : <EmptyState text="Абонементів за цим запитом не знайдено." />} 
          </section> 
        )} 
        {activeTab === "memberships" && ( 
          <section className="panel"> 
<SectionHeader title="Видані абонементи" description="Призначення абонементів 
клієнтам і контроль залишку" count={filteredMemberships.length} /> 
<MembershipForm token={token} users={data.users} plans={data.plans} reload={load} 
/> 
<div className="toolbar"> 
<SearchBox value={search.memberships} onChange={(value) => 
updateSearch("memberships", value)} placeholder="Пошук за клієнтом або типом 
абонемента" /> 
</div> 
{filteredMemberships.length ? ( 
<> 
<div className="table"> 
{membershipsPager.pageItems.map((membership) => ( 
<div className="table-row membership-row" key={membership.id}> 
<span>{membership.client.name}</span> 
147 
482.ЧДТУ 262240 12 01 60 
<span>{membership.plan.name}</span> 
<span>{formatDate(membership.endDate)}</span> 
<strong>{membership.visitsRemaining ?? "безліміт"} відвідувань</strong> 
<button className="icon-action danger-icon" onClick={() => 
deleteMembership(membership)} title="Видалити абонемент клієнта" type="button"> 
<Trash2 size={16} /> 
</button> 
</div> 
))} 
</div> 
<Pagination {...membershipsPager} total={filteredMemberships.length} /> 
</> 
) : <EmptyState text="Виданих абонементів за цим запитом не знайдено." />} 
          </section> 
        )} 
        {activeTab === "workouts" && ( 
          <section className="panel"> 
<SectionHeader title="Тренування" description="Розклад занять, тренери, місткість і 
кількість записів" count={filteredWorkouts.length} /> 
<WorkoutForm token={token} users={data.users} reload={load} /> 
<div className="toolbar"> 
<SearchBox value={search.workouts} onChange={(value) => 
updateSearch("workouts", value)} placeholder="Пошук за назвою, тренером або датою" /> 
<div className="type-filter" aria-label="Фільтр типу тренування"> 
<button className={workoutTypeFilter === "all" ? "active" : ""} onClick={() => 
setWorkoutTypeFilter("all")} type="button"> 
Усі 
</button> 
<button className={workoutTypeFilter === "group" ? "active" : ""} onClick={() => 
setWorkoutTypeFilter("group")} type="button"> 
Групові 
</button> 
148 
482.ЧДТУ 262240 12 01 61 
<button className={workoutTypeFilter === "personal" ? "active" : ""} onClick={() 
=> setWorkoutTypeFilter("personal")} type="button"> 
Індивідуальні 
</button> 
</div> 
<WorkoutTrainerFilter 
selectedTrainer={workoutTrainerFilter} 
setSelectedTrainer={setWorkoutTrainerFilter} 
trainers={data.users.filter((user) => user.role === "TRAINER")} 
/> 
</div> 
      {filteredWorkouts.length ? ( 
<> 
<div className="table"> 
{workoutsPager.pageItems.map((workout) => ( 
<div className="table-row workout-row" key={workout.id}> 
<span>{workout.title}</span> 
<span className={isPersonalWorkout(workout) ? "type-pill personal" : "type-
pill"}> 
{isPersonalWorkout(workout) ? "Індивідуальне" : "Групове"} 
</span> 
          <span>{workout.trainer?.name}</span> 
<span>{formatDateTime(workout.startsAt)}</span> 
<strong>{workout.bookings.length}/{workout.capacity}</strong> 
<button className="icon-action danger-icon" onClick={() => 
deleteWorkout(workout)} title="Видалити тренування" type="button"> 
<Trash2 size={16} /> 
</button> 
</div> 
))} 
</div> 
  <Pagination {...workoutsPager} total={filteredWorkouts.length} /> 
</> 
149 
482.ЧДТУ 262240 12 01 62 
) : <EmptyState text="Тренувань за цим запитом не знайдено." />} 
          </section> 
        )} 
      </section> 
    </main> 
  ); 
} 
createRoot(document.getElementById("root")).render(<AdminApp />);App.js 
150 
ДОДАТОК В 
КРОС-ПЛАТФОРМЕННИЙ ДОДАТОК ДЛЯ АВТОМАТИЗАЦІЇ РОБОТИ 
СПОРТИВНОГО ЗАЛУ 
Інструкція користувача 
482.ЧДТУ 262240 34 01
Листів 8 
Розробник  ________________ Гончар А. О. 
Черкаси 2026 
482.ЧДТУ 262240 34 01  2
Розроблений програмний комплекс призначений для автоматизації 
роботи спортивного залу та складається з адміністративної вебпанелі й 
мобільного застосунку. Адміністративна вебпанель використовується 
адміністратором спортивного залу для керування користувачами, 
абонементами, тренуваннями та видачею абонементів. Мобільний застосунок 
призначений для тренерів і клієнтів, які можуть працювати з розкладом, 
профілем, абонементами та заняттями. 
Робота з адміністративною вебпанеллю 
Після входу в систему адміністратор потрапляє до панелі керування 
спортивним залом. У лівій частині інтерфейсу розміщено навігаційне меню з 
основними розділами: «Огляд», «Користувачі», «Баланс», «Абонементи», 
«Видача» та «Тренування». Також у меню доступна кнопка «Вийти» для 
завершення роботи з обліковим записом. 
Рисунок В.1 – Панель керування 
У розділі «Огляд» адміністратор може переглянути загальну 
інформацію про стан системи. На сторінці відображаються ключові 
показники: кількість клієнтів, кількість тренерів, активні абонементи та 
абонементи, які скоро завершуються. Для оновлення інформації 
використовується кнопка «Оновити». 
152 
482.ЧДТУ 262240 34 01                                           3 
У розділі «Користувачі» адміністратор може створювати нових клієнтів 
і тренерів. Для додавання користувача необхідно заповнити поля «Ім’я», 
«Email», «Телефон», обрати роль користувача та вказати початковий пароль. 
Після заповнення форми потрібно натиснути кнопку «Додати користувача». 
Нижче на сторінці відображається список клієнтів із контактною 
інформацією, балансом і можливістю призначення тренера. 
Рисунок В.2 – Розділ «Користувачі» 
У розділі «Абонементи» адміністратор створює типи абонементів. Для 
цього потрібно вказати назву, ціну, тривалість дії в днях, ліміт відвідувань та 
опис. Якщо абонемент є безлімітним, поле ліміту відвідувань можна 
залишити порожнім. Після натискання кнопки «Створити абонемент» новий 
тип абонемента з’являється у списку нижче. У цьому ж розділі можна 
переглядати вже створені абонементи та видаляти непотрібні записи. 
У розділі «Видача» адміністратор може видати абонемент конкретному 
клієнту. Для цього потрібно обрати клієнта, вибрати тип абонемента, 
зазначити дату початку та натиснути кнопку «Видати абонемент». У правій 
частині форми відображаються параметри вибраного абонемента. Нижче 
розміщено список уже виданих абонементів, де вказано ім’я клієнта, тип 
абонемента, дату завершення та кількість доступних відвідувань. Для 
153 
482.ЧДТУ 262240 34 01                                          4 
швидкого пошуку передбачено поле пошуку за клієнтом або типом 
абонемента. 
Рисунок В.3 – Розділ «Видача» 
У розділі «Тренування» адміністратор створює заняття та формує 
розклад. Спочатку необхідно обрати тренера зі списку або знайти його через 
поле пошуку. Далі потрібно ввести назву тренування, дату й час початку, 
дату й час завершення, місткість заняття та опис. Після заповнення форми 
адміністратор натискає кнопку «Додати тренування», після чого тренування 
стає доступним у системі. 
Робота з мобільним застосунком тренера 
Після входу в мобільний застосунок тренер бачить привітання з 
власним ім’ям і список тренувань. У нижній частині екрана розміщено 
навігаційне меню з вкладками «Тренування», «Створити» та «Профіль». 
У вкладці «Тренування» тренер переглядає заняття, які йому 
призначені. Для кожного тренування показується назва, дата, час, тип 
заняття, тривалість або кількість місць, а також список клієнтів, які 
записалися на заняття. Біля кожного клієнта є кнопка «Відмітити». Після 
натискання цієї кнопки клієнт позначається як присутній, а стан кнопки 
154 
482.ЧДТУ 262240 34 01                                           5 
змінюється на «Відмічено». Також поряд із клієнтом доступна кнопка 
«Зв’язатися». 
Розділ В.4 – Вкладка «Тренування» 
У вкладці «Створити» тренер може створити нове тренування. Для 
групового тренування потрібно обрати тип «Групове», ввести назву, опис, 
дату й час початку, дату й час завершення, а також місткість. Після цього 
потрібно натиснути кнопку «Створити тренування». 
Для створення індивідуального заняття тренер обирає тип 
«Індивідуальне». У формі необхідно вказати опис заняття, доступні дні 
тижня, час початку слота, тривалість заняття та кількість тижнів, на які 
створюється розклад. Після заповнення параметрів система формує 
індивідуальні заняття відповідно до заданого графіка. 
У вкладці «Профіль» тренер може переглянути особисту інформацію: 
ім’я, email, номер телефону та роль у системі. Також у профілі 
відображається список клієнтів тренера. Для кожного клієнта доступна 
155 
482.ЧДТУ 262240 34 01                                           6
кнопка «Зв’язатися». Нижче розміщено форму зміни пароля, де потрібно 
ввести поточний пароль, новий пароль і повторити новий пароль. Для 
збереження змін потрібно натиснути кнопку «Змінити пароль». Для 
завершення роботи із застосунком використовується кнопка «Вийти». 
Розділ В.5 – Вкладка «Профіль» 
Робота з мобільним застосунком клієнта 
Після входу в мобільний застосунок клієнт бачить головний екран із 
привітанням і доступними тренуваннями. У нижньому меню доступні 
вкладки «Тренування», «Мої заняття», «Абонемент» і «Профіль». 
У вкладці «Тренування» клієнт може переглядати доступні групові та 
індивідуальні заняття. Для перемикання між типами занять 
використовуються вкладки «Групові» та «Індивідуальні». Кожна картка 
тренування містить назву заняття, дату й час проведення, ім’я тренера та 
156 
482.ЧДТУ 262240 34 01                                           7 
кнопку «Записатися». Для запису на заняття потрібно натиснути кнопку 
«Записатися» на відповідній картці. Після цього система перевіряє 
можливість запису та створює бронювання. 
Рисунок В.6 – Вкладка «Тренування» 
У вкладці «Абонемент» клієнт може переглянути власний баланс, 
активні абонементи та доступні для придбання абонементи. Для активного 
абонемента відображається назва, строк дії, статус і кількість відвідувань, що 
залишилися. У блоці «Доступні абонементи» клієнт може переглянути 
тарифи, їхню ціну, опис і кількість відвідувань. Для придбання абонемента 
потрібно натиснути кнопку «Купити абонемент». Якщо коштів на балансі 
недостатньо, система відображає відповідне повідомлення. 
Вкладка «Мої заняття» призначена для перегляду тренувань, на які 
клієнт уже записався. У ній користувач може контролювати власний розклад 
і переглядати інформацію про майбутні заняття. 
157 
482.ЧДТУ 262240 34 01                                          8 
У вкладці «Профіль» клієнт може переглянути особисті дані та 
виконати зміну пароля. Для зміни пароля необхідно ввести поточний пароль, 
новий пароль і підтвердження нового пароля, після чого натиснути кнопку 
«Змінити пароль». Для завершення роботи з мобільним застосунком 
використовується кнопка «Вийти». 
Рисунок В.7 – Вкладка «Абонемент» 
Таким чином, адміністративна вебпанель забезпечує керування 
роботою спортивного залу, а мобільний застосунок надає тренерам і клієнтам 
зручний доступ до основних функцій системи. Інтерфейс побудований 
відповідно до ролей користувачів, тому кожен користувач бачить тільки ті 
можливості, які необхідні для виконання його завдань. 
158 
ДОДАТОК Г 
КРОС-ПЛАТФОРМЕННИЙ ДОДАТОК ДЛЯ АВТОМАТИЗАЦІЇ РОБОТИ 
СПОРТИВНОГО ЗАЛУ 
Графічні матеріали 
482.ЧДТУ 262240 90 01
Листів 10 
Розробник  ________________ Гончар А. О. 
Черкаси 2026 
482.ЧДТУ 262240 90 01 2 
Рисунок Г.1 - Слайд 1 
Рисунок Г.2 - Слайд 2 
160 
482.ЧДТУ 262240 90 01 3 
Рисунок Г.3 - Слайд 3 
Рисунок Г.4 - Слайд 4 
161 
482.ЧДТУ 262240 90 01 4 
Рисунок Г.5 - Слайд 5 
Рисунок Г.6 - Слайд 6 
162 
482.ЧДТУ 262240 90 01 5 
Рисунок Г.7 - Слайд 7 
Рисунок Г.8 - Слайд 8 
163 
482.ЧДТУ 262240 90 01 6 
Рисунок Г.9 - Слайд 9 
Рисунок Г.10 - Слайд 10 
164 
482.ЧДТУ 262240 90 01 7 
Рисунок Г.11 - Слайд 11 
Рисунок Г.12 - Слайд 12 
165 
482.ЧДТУ 262240 90 01 8 
Рисунок Г.13 - Слайд 13 
Рисунок Г.14 - Слайд 14 
166 
482.ЧДТУ 262240 90 01 9 
Рисунок Г.15 - Слайд 15 
Рисунок Г.16 - Слайд 16 
167 
482.ЧДТУ 262240 90 01 10 
Рисунок Г.17 - Слайд 17 
Рисунок Г.18 - Слайд 18 
168 
482.ЧДТУ 262240 90 01 11 
Рисунок Г.19 - Слайд 19 
169