Будь ласка, використовуйте цей ідентифікатор, щоб цитувати або посилатися на цей матеріал: https://er.chdtu.edu.ua/handle/ChSTU/9751
Назва: Мобільний застосунок для інтелектуального управління домашньою продуктовою коморою
Автори: Немченко , Вадим В'ячеславович
Мельник, Владислав Андрійович
Ключові слова: інформаційна система;мобільний застосунок;REACT NATIVE;штучний інтелект;LOCAL-FIRST;облік продуктів;синхронізація даних;SQLITE;кросплатформність;information system;mobile application;REACT NATIVE;artificial intelligence;LOCAL-FIRST;inventory management;data synchronization
Дата публікації: 17-чер-2026
Короткий огляд (реферат): АНОТАЦІЯ Кваліфікаційна робота бакалавра на тему «Мобільний застосунок для інтелектуального управління домашньою продуктовою коморою» містить 137 сторінок, 13 таблиць, 24 рисунків, список використаних джерел з 27 найменувань, 4 додатків. Метою виконання кваліфікаційної роботи бакалавра є розробка кросплатформного мобільного застосунку з використанням штучного інтелекту та архітектури Local-First для ефективного управління домашніми запасами та зменшення кількості харчових відходів. Головні завдання при проектуванні інформаційної системи включали створення архітектури для роботи без доступу до мережі, впровадження інтелектуальних модулів автоматизації введення даних та розробку механізмів персоналізованої генерації кулінарних рецептів. Об'єктом роботи є процеси створення ПЗ та інформаційних технологій для автоматизації обліку домашніх ресурсів. Предметом розробки є методи та програмні засоби створення мобільного застосунку з інтеграцією ШІ та підходом Local-First. Вирішено наступні поставлені задачі: проаналізовано предметну область; спроєктовано архітектуру системи та базу даних; розроблено інтерфейс та програмні модулі роботи з камерою і ШІ; впроваджено логіку контролю термінів придатності; проведено комплексне тестування системи.
ANNOTATION The bachelor's qualification work on the topic "Mobile Application for Smart Home Pantry Management" contains 137 pages, 13 tables, 24 figures, a list of references with 27 items, and 4 appendices. The purpose of the bachelor's qualification work is to develop a crossplatform mobile application using artificial intelligence and a Local-First architecture for efficient home inventory management and reducing food waste. The main tasks in designing the information system included creating an architecture to work without internet access, implementing smart modules for automated data entry, and developing mechanisms for personalized recipe generation. The object of the work is the processes of creating software and information technologies to automate the accounting of home resources. The subject of development is the methods and software tools for creating a mobile application with AI integration and a Local-First approach. The following tasks were solved: the subject area was analyzed; the system architecture and database were designed; the user interface and software modules for working with the camera and AI were developed; the expiration date control logic was implemented; comprehensive system testing was conducted.
URI (Уніфікований ідентифікатор ресурсу): https://er.chdtu.edu.ua/handle/ChSTU/9751
Розташовується у зібраннях:121 Інженерія програмного забезпечення (Інженерія програмного забезпечення)

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


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

Extracted text
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ 
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ 
Факультет інформаційних технологій і систем 
Кафедра програмного забезпечення автоматизованих систем 
 
 
 
 
ПОЯСНЮВАЛЬНА ЗАПИСКА 
до кваліфікаційної роботи 
«бакалавра» 
освітній рівень 
 
на тему:  Мобільний застосунок для інтелектуального управління 
домашньою продуктовою коморою 
 
Виконав: студент 4 курсу, групи ПЗ-2204 
Спеціальності  
121 «Інженерія програмного забезпечення»  
(шифр і назва напряму підготовки) 
 
 
Студент Мельник В. А. 
 (прізвище та ініціали) 
Керівник  Немченко В. В. 
 (прізвище та ініціали) 
Рецензент Ємелянов С. А. 
 (прізвище та ініціали) 
 
 
Черкаси 2026 
 
 
 Черкаський державний технологічний університет  
повне найменування вищого навчального закладу 
Факультет інформаційних технологій і систем  
Кафедра  програмного забезпечення автоматизованих систем  
Освітній рівень  бакалавр  
Спеціальність 121 «Інженерія програмного забезпечення»  
Освітня програма Інженерія програмного забезпечення  
 
 
ЗАТВЕРДЖУЮ 
Зав. кафедри ПЗАС, професор 
____________________ С. Голуб  
«___» _______________ 2026 року 
 
З А В Д А Н Н Я 
НА КВАЛІФІКАЦІЙНУ РОБОТУ СТУДЕНТУ 
  Мельник Владислав Андрійович  
(прізвище, ім’я, по батькові) 
1. Тему проекту (роботи) Мобільний застосунок для інтелектуального управління 
домашньою продуктовою коморою  
Керівник проекту (роботи) Немченко Вадим В’ячеславович, к.т.н., доцент  
(прізвище, ім’я , по батькові, науковий ступінь, вчене звання) 
Затверджені наказом Черкаського державного технологічного університету від « 12 » 
березня 2026 року №56/03-03 
2. Строк подання студентом проекту (роботи) _1_червня 2026 року 
3. Вхідні дані до проекту (роботи) стандарти програмного забезпечення; вимоги до 
проєкту; технічні вимоги до кросплатформних мобільних систем; технічна документація 
середовищ розробки; дані профілю користувача та харчові стандарти.                                  _ 
4. Зміст розрахунково-пояснювальної записки (перелік питань, які потрібно розробити) 
Вступ; Існуючі методи та засоби розв’язання поставлених завдань; Проектування та 
моделювання програмного забезпечення; Розробка та тестування програмного 
забезпечення; Висновки; Список використаних джерел; Додатки.                                          _  
5. Перелік графічного матеріалу (з точним зазначенням обов’язкових робіт проекту); 
 
 
 
Додаток А – Документація; Додаток Б – Специфікація програми; Додаток В – Текст 
програми; Додаток Г – Презентаційний матеріал.                                                                     _ 
6. Консультанти розділів роботи 
Розділ Прізвище, ініціали та посади Підпис, дата 
консультанта Завдання видав Завдання прийняв 
1    
2    
3    
 
7. Дата видачі завдання 2 грудня 2025 р.  
 
КАЛЕНДАРНИЙ ПЛАН 
Строк 
№ виконання 
п/п Назва етапів випускної роботи етапів Примітки 
кваліфікаційної
роботи 
1 Постановка задачі 05.12.2025 виконано 
2 Підготовка завдання 13.12.2025 виконано 
3 Погодження завдання 16.12.2025 виконано 
4 Затвердження завдання 19.02.2026 виконано 
 Основна стадія   
1 Підбір матеріалів 27.02.2026 виконано 
2 Аналіз шляхів вирішення поставленої задачі 04.02.2026 виконано 
3 Розрахунок основних параметрів роботи 10.03.2026 виконано 
4 Вибір кінцевого варіанту проектного рішення 17.03.2026 виконано 
5 Оформлення первісної редакції роботи 25.03.2026 виконано 
 Заключна стадія   
1 Узгодження прийнятих проектних рішень з 31.04.2026 виконано 
керівником 
2 Оформлення пояснювальної записки роботи в 13.05.2026 виконано 
кінцевій редакції 
3 Попередній захист роботи 19.05.2026 виконано 
4 Затвердження роботи 25.05.2026 виконано 
5 Рецензування роботи 28.05.2026 виконано 
6 Захист роботи 03.06.2026  
 
Студент                                                  _____________________ Мельник В.А. 
  (підпис)   (прізвище та ініціали) 
 
Керівник роботи                                   _____________________  Немченко В.В. 
  (підпис)   (прізвище та ініціали) 
 
 
 
 
АНОТАЦІЯ 
Кваліфікаційна робота бакалавра на тему «Мобільний застосунок для 
інтелектуального управління домашньою продуктовою коморою» містить 
137 сторінок, 13 таблиць, 24 рисунків, список використаних джерел  з 27 
найменувань, 4 додатків. 
Метою виконання кваліфікаційної роботи бакалавра є розробка 
кросплатформного мобільного застосунку з використанням штучного 
інтелекту та архітектури Local-First для ефективного управління домашніми 
запасами та зменшення кількості харчових відходів. 
Головні завдання при проектуванні інформаційної системи включали 
створення архітектури для роботи без доступу до мережі, впровадження 
інтелектуальних модулів автоматизації введення даних та розробку 
механізмів персоналізованої генерації кулінарних рецептів. 
Об'єктом роботи є процеси створення ПЗ та інформаційних технологій 
для автоматизації обліку домашніх ресурсів. 
Предметом розробки є методи та програмні засоби створення 
мобільного застосунку з інтеграцією ШІ та підходом Local-First. 
Вирішено наступні поставлені задачі: проаналізовано предметну 
область; спроєктовано архітектуру системи та базу даних; розроблено 
інтерфейс та програмні модулі роботи з камерою і ШІ; впроваджено логіку 
контролю термінів придатності; проведено комплексне тестування системи. 
Ключові слова: інформаційна система, мобільний застосунок, REACT 
NATIVE, штучний інтелект, LOCAL-FIRST, облік продуктів, синхронізація 
даних, SQLITE, кросплатформність. 
 
 
 
ANNOTATION 
The bachelor's qualification work on the topic "Mobile Application for 
Smart Home Pantry Management" contains 137 pages, 13 tables, 24 figures, a list 
of references with 27 items, and 4 appendices. 
The purpose of the bachelor's qualification work is to develop a cross-
platform mobile application using artificial intelligence and a Local-First 
architecture for efficient home inventory management and reducing food waste. 
The main tasks in designing the information system included creating an 
architecture to work without internet access, implementing smart modules for 
automated data entry, and developing mechanisms for personalized recipe 
generation. 
The object of the work is the processes of creating software and information 
technologies to automate the accounting of home resources. 
The subject of development is the methods and software tools for creating a 
mobile application with AI integration and a Local-First approach. 
The following tasks were solved: the subject area was analyzed; the system 
architecture and database were designed; the user interface and software modules 
for working with the camera and AI were developed; the expiration date control 
logic was implemented; comprehensive system testing was conducted. 
Keywords: information system, mobile application, REACT NATIVE, 
artificial intelligence, LOCAL-FIRST, inventory management, data 
synchronization, SQLITE, cross-platform.  
 
 
ЗМІСТ 
 
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ ................................................................. 6 
ВСТУП .................................................................................................................. 7 
РОЗДІЛ 1 ІСНУЮЧІ МЕТОДИ ТА ЗАСОБИ РОЗВ’ЯЗАННЯ 
ПОСТАВЛЕНИХ ЗАВДАНЬ ............................................................................ 10 
1.1. Аналіз технічної інформації .................................................................. 10 
1.2. Актуальність задачі ................................................................................ 10 
1.3. Аналіз існуючих програмних засобів ................................................... 11 
1.4. Аналіз способів вирішення задачі ........................................................ 12 
1.5. Постановка задачі ................................................................................... 12 
РОЗДІЛ 2 ВПРОВАДЖЕННЯ РЕЗУЛЬТАТІВ ДОСЛІДЖЕНЬ У 
ПРАКТИКУ ПРОЕКТУВАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ 
ІНФОРМАЦІЙНИХ СИСТЕМ .......................................................................... 15 
2.1. Моделювання предметної області ........................................................ 15 
2.1.1. Предметна область моделювання. Модель предметної області. 
Словник предметної області ........................................................................... 15 
2.1.2. Елементи моделювання предметної області ............................. 17 
2.1.3. Робоча область моделювання ..................................................... 18 
2.2. Формування та аналіз вимог ................................................................. 19 
2.2.1. Формування вимог до програмного забезпечення. Первинні і 
детальні вимоги. Вимоги замовника і розробника. Функціональні та 
нефункціональні вимоги ................................................................................. 19 
2.2.2. Формування вимог за допомогою діаграми прецедентів ........ 22 
2.3. Проектування логічної структури програмного комплексу .............. 24 
2.3.1. Діаграми класів ............................................................................ 24 
2.3.2. Діаграми пакетів .......................................................................... 27 
2.4. Архітектурне проектування .................................................................. 29 
2.4.1. Діаграма компонентів ................................................................. 29 
ЧДТУ.262251.013 ПЗ 
 Зм Арк № докум. Підп. Дата 
 Розроб. Мельник В.А. МОБІ ЛЬНИЙ ЗАСТОСУНОК Лит. Лист Листів 
 Пров. Немченко В.В. ДЛЯ ІНТЕЛЕКТУАЛЬНОГО                   4                   
 УПРАВЛІННЯ ДОМАШНЬОЮ ФІТІС,  
 Н.контр. Півень О.Б. ПРОДУКТОВОЮ КОМОРОЮ група ПЗ-2204 
 Затв. Голуб С.В. 
 
2.4.2. Розгортання програмної системи на апаратних засобах. 
Діаграма розгортання ...................................................................................... 31 
2.5. Моделювання поведінки системи ......................................................... 32 
2.5.1. Діаграма діяльності ..................................................................... 33 
2.5.2. Діаграма послідовності ............................................................... 34 
2.5.3. Діаграма комунікації ................................................................... 36 
2.5.4. Діаграма скінченного автомата .................................................. 37 
РОЗДІЛ 3 РОЗРОБКА ТА ТЕСТУВАННЯ ПРОГРАМНОГО 
ЗАБЕЗПЕЧЕННЯ ............................................................................................... 40 
3.1. Розробка програмного комплексу ........................................................ 40 
3.1.1. Обґрунтування вибору засобів реалізації ................................. 40 
3.1.2. Опис структурної (функціональної) схеми ............................... 41 
3.1.3. Опис логічної схеми системи ..................................................... 43 
3.1.4. Розробка бази даних .................................................................... 46 
3.1.5. Розробка інтерфейсу користувача ............................................. 52 
3.1.6. Опис розробки програмних компонентів ................................. 59 
3.2. Тестування системи ................................................................................ 66 
3.2.1. Модульне тестування .................................................................. 66 
3.2.2. Інтеграційне тестування ............................................................. 69 
3.2.3. Системне тестування ................................................................... 71 
3.2.4. Приймальне тестування .............................................................. 73 
3.3. Приклади впровадженого програмного комплексу ............................ 75 
ВИСНОВКИ ........................................................................................................ 81 
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ .......................................................... 82 
ДОДАТОК А ....................................................................................................... 85 
ДОДАТОК Б ....................................................................................................... 87 
ДОДАТОК В ..................................................................................................... 115 
ДОДАТОК Г ..................................................................................................... 122 
 Лист 
ЧДТУ.262251.013 ПЗ  
Зм. Лист № документа Підпис Дата 5 
     
ЧДТУ.262251.013 ПЗ 
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ 
БД – База даних. 
СУБД – Система управління базами даних. 
ШІ – Штучний інтелект. 
API – (Application Programming Interface, ua. «Програмний інтерфейс застосунку») 
Набір правил, за якими різні програми або компоненти взаємодіють між собою. 
CRUD – (Create, Read, Update, Delete) Чотири базові операції для роботи з 
даними: створення, читання, оновлення та видалення. 
JSON – (JavaScript Object Notation) Простий текстовий формат обміну даними, 
зручний для читання людиною та машиною. 
LLM – (Large Language Model, ua. «Велика мовна модель») Нейромережа, здатна 
розпізнавати та генерувати текст на основі великих масивів даних. 
RLS – (Row Level Security, ua. «Безпека на рівні рядків») Механізм захисту БД, що 
обмежує доступ до окремих записів залежно від прав користувача. 
SQL – (Structured Query Language, ua. «Мова структурованих запитів») 
Стандартизована мова запитів для взаємодії з реляційними базами даних. 
SQLite – Компактна та автономна СУБД, яка зберігає дані у локальному файлі на 
пристрої без використання окремого сервера. 
SSOT – (Single Source of Truth, ua. «Єдине джерело правди») Архітектурна 
концепція, за якої дані зберігаються лише в одному місці, що виключає їх 
дублювання. 
UI – (User Interface, ua. «Інтерфейс користувача») Візуальна частина застосунку 
(екрани, кнопки), з якою безпосередньо взаємодіє користувач. 
UX – (User Experience, ua. «Досвід користувача») Рівень зручності та загальне 
враження людини від використання програми. 
UML – (Unified Modeling Language, ua. «Уніфікована мова моделювання») 
Стандартизована мова візуального моделювання для проєктування архітектури 
програмного забезпечення. 
6 
ЧДТУ.262251.013 ПЗ 
ВСТУП 
Актуальність теми. Дана тема «Мобільний застосунок для 
інтелектуального управління домашньою продуктовою коморою» належить до 
спеціальності 121 «Інженерія програмного забезпечення». Актуальність розробки 
визначається необхідністю створення програмного забезпечення інформаційної 
технології інтелектуального управління домашньою продуктовою коморою і 
зумовлена глобальною проблемою нераціонального споживання харчових 
продуктів, що приводить до необхідності створення інструментів для 
автоматизації побутових процесів, оптимізації обліку запасів та мінімізації 
харчових відходів. 
Розробка цієї системи також актуальна з точки зору дослідження роботи 
автономних архітектур із локальними базами даних, а також взаємодії 
клієнтського програмного забезпечення із хмарними сервісами штучного 
інтелекту для розпізнавання даних. 
Клієнтську частину реалізовано за допомогою сучасних програмних 
підходів, що забезпечують зручну, автономну та ефективну взаємодію 
користувача з системою. В основі програми лежить робота з продуктовими 
запасами, їх термінами зберігання та дієтичними вподобаннями користувача. Ці 
дані будуть відображатись у графічному інтерфейсі системи, і за допомогою 
вибору відповідних елементів, камери та модулів штучного інтелекту буде 
відбуватись їх автоматизована обробка для поповнення комори та планування 
рецептів. 
Мета і завдання розробки 
Мета розробки: проектування та розробка програмного забезпечення 
інформаційної технології інтелектуального управління домашньою продуктовою 
коморою, що надасть можливість користувачам зручно контролювати продуктові 
запаси та автоматизувати їх облік, в тому числі ефективно планувати харчування і 
мінімізувати харчові відходи шляхом створення кросплатформної клієнтської 
частини з інтеграцією елементів штучного інтелекту та архітектури Local-First.  
7 
ЧДТУ.262251.013 ПЗ 
Завдання розробки: 
‒ проаналізувати предметну область та існуючі програмні рішення; 
‒ спроєктувати загальну архітектуру та логіку роботи системи; 
‒ реалізувати програмний комплекс та його інтелектуальні модулі; 
‒ провести комплексне тестування створеного програмного забезпечення. 
Об’єкт розробки. Процес автоматизації обліку побутових ресурсів та 
управління домашнім господарством за допомогою інформаційних технологій. 
Предмет розробки. Програмне забезпечення інформаційної технології 
інтелектуального управління домашньою продуктовою коморою. 
Методи проєктування та конструювання. Для досягнення поставленої 
мети та успішної реалізації програмного продукту було застосовано такі підходи 
та методи: 
‒ дослідження реальних побутових процесів обліку продуктів та 
порівняння існуючих застосунків-аналогів. Це дозволило виявити їхні 
недоліки та сформувати чіткі вимоги до власної програми; 
‒ візуальне моделювання за допомогою UML-діаграм для структурування 
архітектури та логіки системи ще до початку написання коду; 
‒ застосування компонентно-орієнтованого підходу на базі фреймворку 
React Native для побудови гнучкого клієнтського інтерфейсу; 
‒ використання суворої типізації та статичного аналізу коду засобами мови 
TypeScript для підвищення загальної надійності системи. 
Опис отриманих результатів. В результаті виконання кваліфікаційної 
роботи розроблено мобільний застосунок, який включає: 
‒ спроектовану та реалізовану реляційну базу даних; 
‒ зручний користувацький інтерфейс; 
‒ розроблений модуль інтеграції зі штучним інтелектом, який автоматизує 
процес додавання товарів; 
8 
ЧДТУ.262251.013 ПЗ 
‒ систему інтелектуальних рецептів, яка здатна генерувати страви з 
наявних продуктів з урахуванням дієтичний вподобань та алергій, а також 
імпортувати рецепти із кулінарних сайтів або звичайного тексту; 
‒ механізм відстеження термінів придатності з можливістю додавання 
прострочених продуктів до списків покупок. 
Практичне значення отриманих результатів. Розроблене програмне 
забезпечення надає користувачам ефективний інструмент для ведення 
домашнього господарства, що дозволяє: 
‒ економити кошти завдяки зменшенню кількості зіпсованих продуктів та 
непотрібних покупок; 
‒ економити час, оскільки автоматизація введення даних та імпорт рецептів 
з інтернету зменшує кількість ручної роботи. 
Завдяки використанню кросплатформних технологій та архітектури Local-
First, застосунок здатний повноцінно працювати на різних системах та в умовах 
відсутності інтернету, що підвищує його доступність. 
Особистий внесок автора. Особистий внесок автора полягає у 
самостійному виконанні всіх етапів дослідження та практичної розробки, що 
представлені у даній роботі. 
9 
ЧДТУ.262251.013 ПЗ 
РОЗДІЛ 1 ІСНУЮЧІ МЕТОДИ ТА ЗАСОБИ РОЗВ’ЯЗАННЯ 
ПОСТАВЛЕНИХ ЗАВДАНЬ 
1.1. Аналіз технічної інформації 
Сучасний розвиток мобільних технологій та штучного інтелекту дозволяє 
створювати складні інформаційні системи для автоматизації побутових завдань. 
Для ефективного розв'язання проблеми обліку продуктів необхідне поєднання 
кількох технічних підходів.  
По-перше, використання архітектури Local-First, яка забезпечує збереження 
даних безпосередньо на мобільному пристрої та їх подальшу фонову 
синхронізацію з хмарним сервером. Це гарантує швидку та безперебійну роботу 
програми, навіть якщо відсутній доступ до інтернету.   
По-друге, інтеграція великих мовних моделей для обробки 
неструктурованого тексту з фотографій чеків та перетворення його у 
структурований формат.  
По-третє, застосування кросплатформних інструментів, що дає змогу 
створити єдиний код, який працюватиме на різних операційних системах. Також 
критично важливим пунктом є впровадження надійних політик безпеки на рівні 
рядків бази даних для захисту особистої інформації користувачів. 
1.2. Актуальність задачі 
Проблема нераціонального споживання їжі сьогодні має глобальний 
характер. Домогосподарства потребують зручних інструментів, які допоможуть 
контролювати запаси та уникати зайвих витрат. Згідно зі звітом Організації 
Об'єднаних Націй з навколишнього середовища «UNEP Food Waste Index Report 
2024», у 2022 році у світі було викинуто 1,05 мільярда тонн їжі. При цьому саме 
домогосподарства несуть відповідальність за найбільшу частку харчових відходів 
- 60%, що становить 631 мільйон тонн [1]. 
Основна причина полягає в тому, що люди часто забувають про терміни 
придатності продуктів або купують зайве через відсутність чіткого списку 
10 
ЧДТУ.262251.013 ПЗ 
покупок. В умовах економічної нестабільності мінімізація харчових відходів стає 
як екологічним, так і економічним пріоритетом для кожної родини. На практиці 
ведення домашнього обліку стикається з кількома перешкодами: 
‒ їжа часто лежить до закінчення терміну придатності, оскільки відсутня 
зручна системи нагадувань; 
‒ списки покупок дублюються або губляться, що призводить до зайвих 
витрат; 
‒ користувачам важко швидко придумати страву виключно з тих 
інгредієнтів, які вже є під рукою; 
‒ необхідність вручну вписувати кожен куплений товар - головна причина, 
чому люди припиняють процес обліку; 
‒ більшість додатків не працюють належним чином, якщо в супермаркеті 
немає доступу до інтернету. 
1.3. Аналіз існуючих програмних засобів 
На ринку вже представлено чимало мобільних застосунків для контролю за 
продуктовими запасами, але під час реального використання вони містять певні 
обмеження. 
Мобільний застосунок NoWaste [2] дозволяє створювати віртуальні 
сховища, відстежувати терміни придатності та отримувати відповідні сповіщення. 
Розробники цієї програми вже впровадили можливість додавання продуктів за 
допомогою сканування штрихкодів та фотографій. Проте навіть за наявності 
таких функцій, інтерфейс програми залишається доволі перевантаженим, а також 
проблеми з українською локалізацією. 
Програма Fridgely: AI Recipe Maker [3] пропонує схожий функціонал для 
облігу холодильника та вміє пропонувати рецепти з наявних продуктів. Її 
головний недолік полягає в класичній хмарній архітектурі. Без стабільного 
доступу до інтернету програма не дозволяє користувачу додавати нові продукти 
та переглянути навіть вже додані. 
Платформа Samsung Food [4] має зручні інструменти для збереження 
11 
ЧДТУ.262251.013 ПЗ 
кулінарних рецептів з інтернету та планування меню на тиждень. Проте цей 
застосунок розроблений переважно для інтеграції з сервісами онлайн-покупок та 
розумною технікою, а не для ведення обліку домашньої комори. У ньому відсутні 
функції для автоматичного розпізнавання продуктів, штрихкодів. 
1.4. Аналіз способів вирішення задачі 
Для усунення недоліків існуючих програм потрібно застосувати сучасні 
підходи до розробки. Найбільшою перешкодою для регулярного використання 
застосунків такого типу є необхідність постійно вписувати кожен куплений товар 
вручну. Щоб розв'язати цю проблему, необхідно інтегрувати сервіси штучного 
інтелекту, які автоматично витягуватимуть назви товарів із фотографій чеків та 
структуруватимуть їх.   
Інша критична проблема це залежність програм від інтернету, через що 
вони взагалі не працюють або працюють, але з помилками. Рішенням є 
архітектурний підхід Local-First [5]. Він передбачає, що всі операції користувача 
спочатку виконуються у локальній базі даних на телефоні, забезпечуючи 
миттєвий відгук інтерфейсу. Синхронізація з сервером відбувається непомітно у 
фоновому режимі лише за наявності стабільного доступу до інтернету. Також для 
швидкої роботи інтерфейсної частини варто використовувати сучасні бібліотеки 
управління глобальним станом та високопродуктивне сховище даних.   
1.5. Постановка задачі 
Аналіз існуючих програм для обліку продуктів показав, що їхніми 
головними недоліками є залежність від стабільного інтернету та потреба постійно 
вводити інформацію вручну. Щоб вирішити цю проблему, необхідно створити 
автономний і зручний інструмент, який автоматизує рутину за допомогою 
сучасних технологій. Для досягнення цього потрібно виконати такі завдання: 
‒ спроектувати архітектуру кросплатформного застосунку, який зможе 
повноцінно працювати без інтернету; 
12 
ЧДТУ.262251.013 ПЗ 
‒ розробити структуру локальної бази даних та налаштувати її непомітну 
синхронізацію з хмарним сервером; 
‒ створити зручний та зрозумілий користувацький інтерфейс; 
‒ налаштувати взаємодію з камерою смартфона та підключити сервіси 
штучного інтелекту, щоб автоматично розпізнавати товари з чеків і 
генерувати рецепти; 
‒ написати основну логіку програми: контроль термінів придатності 
продуктів, управління списками покупок  та збереження рецептів; 
‒ протестувати готове програмне забезпечення, щоб переконатися в його 
надійності та правильній роботі всіх функцій. 
Виконання цих завдань дозволить отримати цілісну систему, яка не лише 
спрощує облік продуктів, а й активно допомагає користувачу в плануванні 
харчування та зменшенні обсягів відходів. 
13 
ЧДТУ.262251.013 ПЗ 
ВИСНОВКИ ДО ПЕРШОГО РОЗДІЛУ 
У першому розділі проаналізовано проблему нераціонального споживання 
їжі та обґрунтовано потребу в автоматизації обліку домашніх продуктів. 
Ввідсутність зручних інструментів контролю найчастіше призводить до псування 
запасів і збільшення кількості харчових відходів. 
Розглянуто популярні аналоги, зокрема NoWaste, Fridgely та Samsung Food. 
Аналіз показав, що хоча окремі програми вже підтримують додавання товарів по 
фото, на ринку досі бракує збалансованих рішень. Більшість існуючих систем або 
змушують користувача постійно вводити дані вручну, або ж не мають можливості 
працювати без інтернету. 
Отже, перший розділ закладає теоретичну основу й обґрунтовує потребу в 
розробці інформаційної системи, яка б поєднала переваги проаналізованих 
аналогів та врахувала їх недоліки. Для успішної реалізації було сформовано 
перелік завдань, виконання яких дозволить створити надійний і зручний продукт 
для щоденного використання. 
14 
ЧДТУ.262251.013 ПЗ 
РОЗДІЛ 2 ВПРОВАДЖЕННЯ РЕЗУЛЬТАТІВ ДОСЛІДЖЕНЬ У 
ПРАКТИКУ ПРОЕКТУВАННЯ ПРОГРАМНОГО 
ЗАБЕЗПЕЧЕННЯ ІНФОРМАЦІЙНИХ СИСТЕМ 
2.1. Моделювання предметної області 
Проектування будь-якої інформаційної системи починається з аналізу її 
предметної області. Цей процес передбачає виявлення ключових понять із 
реального життя, які згодом будуть відображені в коді. Побудова такої моделі за 
допомогою стандартних засобів візуалізації дозволяє структурувати логіку 
застосунку, визначити основні властивості об’єктів та налагодити зв’язки між 
ними ще до початку розробки [6]. 
2.1.1. Предметна область моделювання. Модель предметної області. 
Словник предметної області 
Предметна область розроблюваного кросплатформного мобільного 
застосунку PantryControl охоплює процеси автоматизації обліку продуктів, 
управління списками покупок та генерації кулінарних рецептів за допомогою 
штучного інтелекту. Розробка такої системи є актуальною, оскільки саме 
домогосподарства генерують значну частку харчових відходів в світі через 
неправильне планування [1].  
Модель предметної області базується на архітектурі Local-First. Це 
означає, що система повноцінно працює без інтернету, зберігаючи все на 
телефоні, а коли з'являється зв'язок, вона непомітно синхронізує дані з хмарним 
сервером. Головними об'єктами системи є місця зберігання (сховища), продукти, 
колекції із рецептами та списки покупок. Взаємодія між ними побудована навколо 
концепції єдиного джерела правди (Single Source of Truth) [7], де хмарна база 
зберігає оригінал даних, а локальна виступає швидким середовищем для роботи 
користувача. 
Представлення логічної структури та зв'язків між виявленими сутностями 
зображено на рисунок 2.1. 
15 
ЧДТУ.262251.013 ПЗ 
 
Рисунок 2.1 – Модель предметної області 
 
Для того щоб усі учасники процесу розробки мали однакове розуміння 
базових абстракцій та уникли дублювання термінів під час складання варіантів 
використання, сформовано словник предметної області. Основні поняття 
наведено в таблиці 2.1. 
 
Таблиця 2.1 
Словник предметної області 
Назва поняття Опис 
Користувач Людина, яка користується застосунком. Має профіль, де 
вказані її харчові звички, дієти та можливі алергії. 
Сховище  Фізичне або логічне місце для зберігання продуктів 
(наприклад, «Холодильник») яке створює користувач. 
Продукт  Головний об'єкт обліку. Містить назву, категорію, кількість, 
термін придатності та поточний статус. 
Список Перелік того, що треба купити. Може поповнюватися 
покупок продуктами, які закінчилися, та має функцію поширення. 
16 
ЧДТУ.262251.013 ПЗ 
Продовження таблиці 2.1 
Назва поняття Опис 
Рецепт Інструкція з приготування страви. Її можна створити вручну, 
згенерувати через ШІ або імпортувати з інтернету. 
Колекція Папка для зручного сортування та збереження рецептів. 
Штрихкод Унікальний цифровий ідентифікатор товару, що 
використовується як зовнішній або локальний довідник для 
автоматизованого розпізнавання, внесення та категоризації 
продуктів харчування. 
 
2.1.2. Елементи моделювання предметної області 
Для візуального проектування системи використовується уніфікована мова 
моделювання – UML [6, 8]. Стандартні графічні символи допомагають чітко 
зобразити структуру програми та зв'язки між її частинами. У таблиці 2.2 
перелічені основні елементи та зв'язки, які зустрічатимуться у цьому розділі. 
 
Таблиця 2.2 
Елементи та зв'язки моделювання предметної області 
Графічний символ Назва елемента 
Дійова особа (актор) 
 
Прецедент 
 
Пакет 
 
Клас / Об'єкт 
 
Компонент 
 
17 
ЧДТУ.262251.013 ПЗ 
 
Продовження таблиці 2.2 
Графічний символ Назва елемента 
Вузол 
 
База даних 
 
Дія / Активність 
 
Розгалуження 
 
Лінія життя 
 
Стан 
 
Початок / Кінець процесу 
 
або 
 
Спрямована асоціація 
 
Агрегація 
 
Композиція 
 
Залежність 
 
Множинність 
 
 
2.1.3. Робоча область моделювання 
 PantryControl – це кросплатформний мобільний застосунок, здатний 
повноцінно працювати без постійного доступу до інтернету. За результатами 
аналізу його інформаційної моделі виділено такі базові процеси обробки даних: 
18 
ЧДТУ.262251.013 ПЗ 
1 Авторизація та налаштування: безпечний вхід у систему з захистом 
особистих даних. Параметри профілю зберігаються в пам'яті пристрою, 
щоб інтерфейс завантажувався без затримок. 
2 Облік запасів: додавання та переміщення продуктів у коморі. Користувач 
може вводити товари вручну, сканувати штрихкоди або використовувати 
систему автоматичного розпізнавання. 
3 Обробка неструктурованих даних: використання нейромереж для аналізу 
фотографій та PDF-файлів, з яких отримуються список куплених товарів. 
4 Контроль життєвого циклу продуктів: відстеження термінів придатності 
та зміна статусів. Включає логіку можливого додавання прострочених 
позицій зі сховищ до списку покупок. 
5 Робота з рецептами: створення страв за допомогою ШІ на основі тих 
інгредієнтів, що вже є вдома, з урахуванням дієт та алергій. Також 
підтримується збереження рецептів із кулінарних сайтів. 
6 Синхронізація: збереження даних у локальній базі для миттєвого відгуку 
інтерфейсу та подальша відправка цих даних на хмарний сервер, коли 
з'являється інтернет. 
2.2. Формування та аналіз вимог 
Збір та аналіз вимог – це базовий крок перед написанням коду, який 
виконується з урахуванням державних стандартів до системної та програмної 
інженерії [9]. На цьому етапі чітко визначається, що саме має робити програма. 
Він гарантує, що система побудована правильно та відповідає поставленим цілям. 
[10]. 
2.2.1. Формування вимог до програмного забезпечення. Первинні і детальні 
вимоги. Вимоги замовника і розробника. Функціональні та нефункціональні 
вимоги 
Формування вимог до програмного забезпечення. Для побудови системи 
необхідно чітко зафіксувати, які саме функції вона повинна виконувати та які 
обмеження на неї накладаються. Ці умови класифікують за кількома категоріями. 
19 
ЧДТУ.262251.013 ПЗ 
Первинні вимоги описують потреби на рівні загальної ідеї. Для застосунку 
PantryControl вони зводяться до створення зручного інструменту для обліку 
домашніх продуктів, щоб зменшити обсяги зіпсованої їжі. Програма повинна 
позбавити людину від ручного введення кожної покупки, допомагати у 
плануванні меню та працювати за повної відсутності інтернету. 
Детальні вимоги визначають технічні шляхи реалізації цих ідей: побудова 
архітектури за принципом Local-First, налаштування фонової відправки даних на 
хмарний сервер, застосування ШІ для отримання даних із фотографій чеків або 
PDF-файлів у структурованому форматі, а також управління глобальним станом 
програми через бібліотеки Zustand [11] і MMKV [12]. 
Оскільки система створюється для масового споживача, вимоги замовника 
(первинні вимоги) фокусуються на зручності та практичній користі: 
‒ економія часу на створення списків товарів та перевірку запасів їжі; 
‒ інтуїтивний інтерфейс із підтримкою швидких жестів та плавних 
переходів; 
‒ повноцінна робота в супермаркетах без доступу до мережі; 
‒ автоматичне розпізнавання куплених товарів з фотографії чека; 
‒ врахування індивідуальних дієт та харчових алергій під час генерації ідей 
для страв; 
‒ нагадування про продукти, термін придатності яких добігає кінця; 
‒ можливість ділитися списком покупок з іншими членами родини; 
‒ підтримка кількох мов (української та англійської). 
Водночас вимоги розробника (детальні вимоги) описують архітектурні 
рішення для створення надійного коду: 
‒ написати клієнтську частину на фреймворку React Native [13] з 
використанням мови TypeScript [14], це забезпечить сувору типізацію та 
дозволить уникнути помилок на етапі розробки; 
‒ організувати архітектуру проекту за модульним підходом, розділивши 
код на логічні шари (екрани, компоненти, сервіси, стейт-менеджери, 
20 
ЧДТУ.262251.013 ПЗ 
хуки) з подальшим групуванням за бізнес-доменами (сховища, списки 
покупок, рецепти) для зручності та кращої підтримки; 
‒ розробити серверну частину на базі платформи Supabase [15], 
спроектувавши реляційну базу даних та налаштувавши політики безпеки 
RLS [16]; 
‒ взаємодіяти з платними сервісами виключно через захищені серверні 
функції, також  там відбуватиметься перевірка статусу підписки 
користувача перед виконанням запитів до ШІ; 
‒ реалізувати синхронізацію даних між локальною базою та хмарним 
сервером, що забезпечить автономність застосунку; 
‒ використати бібліотеку Zustand у зв'язці з MMKV для управління 
глобальним станом системи та швидкого збереження налаштувань без 
затримок в інтерфейсі; 
‒ створити локальну таблицю на телефоні для збереження відсканованих 
штрихкодів, що дозволить додатку працювати швидше і не робити зайвих 
запитів при повторному скануванні тих самих товарів. 
Функціональні вимоги описують конкретні дії, які програма має вміти 
виконувати [10]. У нашому випадку це 5 основних пунктів: 
‒ робота з продуктами: створення віртуальних сховищ, додавання туди 
товарів, зміна їх статусів та контроль термінів придатності; 
‒ додавання товарів: ручний спосіб через форму товару,  розпізнавання 
штрихкодів через камеру та обробка чеків за допомогою ШІ, щоб не 
вписувати всі покупки вручну; 
‒ управління списками покупок: створення списків для магазину з 
можливістю легко поділитися ними з рідними, а також зручно додавати 
туди ті продукти, які щойно закінчилися або прострочилися; 
‒ генерація розумних рецептів: підбір страв за допомогою ШІ із залишків 
їжі, враховуючи дієтичні вподобання та алергії; 
21 
ЧДТУ.262251.013 ПЗ 
‒ збереження рецептів: можливість зберігати згенеровані страви, улюблені 
рецепти з інтернету або створювати власні та додавати їх у колекції. 
Нефункціональні вимоги визначають критерії якості, швидкості та 
безпеки системи [10]. Основні з них: 
‒ автономність: додаток має працювати без інтернету. Усі зміни спочатку 
зберігаються на телефоні за принципом Local-First; 
‒ швидкодія: миттєвий відгук інтерфейсу завдяки використанню швидкої 
локальної бази даних; 
‒ кросплатформність: стабільна робота однієї кодової бази на пристроях з 
iOS та Android; 
‒ надійна синхронізація та безпека: непомітна фонова відправка даних на 
хмарний сервер при появі мережі та надійний захист особистої 
інформації. 
2.2.2. Формування вимог за допомогою діаграми прецедентів 
Діаграма прецедентів є одним із базових інструментів UML, який допомагає 
візуалізувати функціональні вимоги до системи [8].  Вона просто і наочно 
демонструє, що саме користувач може робити в додатку і з якими зовнішніми 
сервісами програма взаємодіє. Головна мета цієї діаграми – показати межі 
системи та її можливості, не занурюючись у складні технічні деталі реалізації. 
Основними елементами тут виступають актори (люди або сторонні сервіси), самі 
прецеденти (функції застосунку) та зв'язки між ними. 
На діаграмі (рисунок 2.1) зображено головного актора – «Користувача», 
який взаємодіє із системою ведення домашнього обліку продуктів. Також 
виділено двох зовнішніх акторів: «Модуль ШІ (LLM)» та сторонній «Відкритий 
API» для пошуку товарів. Ключові прецеденти системи: 
‒ керувати сховищами: створення та налаштування логічних місць 
зберігання; 
‒ керувати продуктами: основні дії з продуктами, такі як ручне додавання 
товарів, зміна статусу та контроль термінів придатності. Цей прецедент 
22 
ЧДТУ.262251.013 ПЗ 
розширюється додатковими зручними методами: швидким додаванням 
одразу багатьох товарів через фото чека та розпізнаванням штрихкодів. 
Логіка сканера штрихкодів побудована так, що спочатку інформація 
шукається в локальній таблиці, і лише за її відсутності система робить 
запит до Відкритого API; 
‒ керувати списками покупок: формування переліку товарів для покупок та 
можливість поділитися списоком з іншими членами родини; 
‒ керувати колекціями рецептів: створення папок для зручного сортування 
страв; 
‒ керувати рецептами: CRUD-операції з рецептами, також є розширення 
для автоматизації: генерація страви на основі наявних продуктів або 
розумний імпорт рецепта за посиланням. В обох випадках система 
звертається до Модуля ШІ. 
 
 
Рисунок 2.1 – Діаграма прецедентів 
23 
ЧДТУ.262251.013 ПЗ 
2.3. Проектування логічної структури програмного комплексу 
Проектування логічної структури – це етап, на якому визначається, з яких 
саме блоків складатиметься програма і як вони спілкуватимуться між собою. Це 
допомагає чітко уявити внутрішню будову застосунку ще до початку написання 
коду, що робить саму розробку простішою та організованішою. 
2.3.1. Діаграми класів 
Діаграма класів – це центральна частина об'єктно-орієнтованого 
проектування, що відображає статичну структуру системи [8]. Вона показує класи 
системи, їхні атрибути, методи та взаємозв'язки між ними. Оскільки клієнтська 
частина застосунку пишеться на React Native з використанням сучасного 
функціонального підходу, діаграма відображає актуальну структуру моделей бази 
даних, об'єднаний глобальний стан та основні сервіси, які керують бізнес-логікою.  
На діаграмі (рисунок 2.2 та 2.3) зображено основні компоненти та сутності 
системи: 
‒ моделі даних бази: класи Storage, Product, ShoppingList, Collection, Recipe, 
UserPreferences та Barcode. Усі ці таблиці містять необхідний набір 
доменних полів. Окрім локального довідника штрихкодів, кожна модель 
обов'язково включає системні поля (created_at, updated_at, deleted_at, 
arrived_at), які потрібні для фонової синхронізації даних з хмарним 
сервером Supabase. Слід зазначити, що у таблиці Barcode первинним 
ключем (id) виступає безпосередньо сам відсканований код товару у 
текстовому форматі, що забезпечує унікальність записів; 
‒ глобальний стан (AppStore): для спрощення візуалізації численні Zustand-
сховища об'єднані в єдиний клас AppStore. Він зберігає актуальні масиви 
даних та відповідає за їхнє швидке відображення в інтерфейсі; 
‒ DatabaseService: головний клас для роботи з усіма даними. Він містить 
повний перелік методів для створення, читання, оновлення та видалення 
об'єктів. Після будь-якої операції запису чи видалення цей сервіс 
24 
ЧДТУ.262251.013 ПЗ 
викликає метод triggerHydration. Це дає сигнал оновити глобальний стан 
AppStore, щоб інтерфейс користувача відреагував миттєво; 
‒ AIService: сервіс для роботи зі штучним інтелектом. Він виконує складну 
бізнес-логіку: розпізнає товари з чеків, генерує нові страви та імпортує 
рецепти із зовнішніх джерел. При генерації рецептів сервіс обов'язково 
звертається до сутності UserPreferences, щоб врахувати наявні дієтичні 
обмеження та алергії; 
‒ BarcodeService: сервіс, який відповідає за обробку відсканованих 
штрихкодів. Він спочатку перевіряє наявність інформації про товар у 
локальній таблиці Barcode через DatabaseService, а в разі її відсутності 
виконує мережевий запит до відкритого зовнішнього API. 
 
Рисунок 2.2 - Діаграма класів без атрибутів 
 
25 
ЧДТУ.262251.013 ПЗ 
 
Рисунок 2.3 – Діаграма класів 
26 
ЧДТУ.262251.013 ПЗ 
2.3.2. Діаграми пакетів 
Діаграма пакетів використовується для групування елементів системи у 
великі логічні блоки – пакети [8]. Вона показує архітектуру додатка на високому 
рівні абстракції, демонструючи залежності між різними підсистемами. Для 
складних мобільних застосунків ця діаграма відображає не просто файлову 
структуру, а саме архітектурні шари. Це допомагає зрозуміти, як різні частини 
програми взаємодіють між собою, що є критично важливим для зручної 
підтримки проекту. 
 
 
Рисунок 2.4 – Діаграма пакетів 
27 
ЧДТУ.262251.013 ПЗ 
Діаграма включає чотири основні пакети (рисунок 2.4), які з’єднані 
спрямованими асоціаціями, що вказують на їхні взаємозв'язки та взаємодію: 
‒ UI Layer (інтерфейсна частина) – пакет, що відповідає за інтерфейс та 
навігацію в застосунку. Він взаємодіє з шарами стану та бізнес-логіки. 
Включає наступні підпакети: 
- screens – містить екрани, розбиті за бізнес-доменами (сховища, списки 
покупок, рецепти, сканери тощо); 
- components – містить спільні та перевикористовувані UI-елементи 
(кнопки, висувні BottomSheet, модалки, логіку роботи з камерою); 
- navigation – відповідає за налаштування маршрутизації між основними 
екранами системи. 
‒ State Management (управління станом) – пакет, що відповідає за 
збереження даних в пам'яті телефону для їх швидкого відображення. 
Включає: 
- stores – глобальні та доменні стейт-менеджери, реалізовані за 
допомогою Zustand (наприклад, productStore, storageStore, draftStore), 
які забезпечують швидке та зручне управління станом. 
‒ Services (бізнес-логіка) – пакет, що містить основну логіку обробки 
інформації та звернень до зовнішніх систем: 
- services – забезпечує взаємодію з базою даних через DatabaseService та 
зовнішніми API, зокрема розпізнавання чеків і генерації рецептів 
через AIService. 
‒ Infrastructure (інфраструктура) – пакет, що відповідає за базову 
конфігурацію застосунку та налаштування зв'язку з базами даних: 
- lib – містить параметри підключення  до хмарної платформи Supabase 
та конфігурацію рушія синхронізації supastash; 
- utils – набір допоміжних функцій для зручної обробки дат, сортування 
масивів та взаємодії з локальним сховищем MMKV. 
Залежності та взаємодія між пакетами: 
28 
ЧДТУ.262251.013 ПЗ 
‒ пакет UI Layer має зв'язок з пакетами State Management та Services, 
оскільки інтерфейс читає дані зі стейт-менеджерів та відправляє команди 
до сервісів; 
‒ пакет State Management взаємодіє із Services, оскільки сервіси після 
успішного виконання операцій у базі даних (додавання, оновлення чи 
видалення) дають команду стейт-менеджерам оновити свої списки даних; 
‒ пакет Services тісно пов'язаний з пакетом Infrastructure, оскільки для 
виконання запитів до бази даних чи зовнішніх API сервіси 
використовують клієнти баз даних (Supabase, supastash) та утиліти, які 
налаштовані в інфраструктурному шарі. 
2.4. Архітектурне проектування 
Архітектурне проектування відіграє важливу роль у розробці програмного 
забезпечення, оскільки воно визначає фундаментальну структуру системи. На 
цьому етапі визначаються основні частини програми, їхні функції та взаємодії між 
собою. Правильно спроектована архітектура гарантує стабільну роботу додатку, 
полегшує його підтримку та дозволяє швидше додавати новий функціонал у 
майбутньому. 
2.4.1. Діаграма компонентів 
Діаграма компонентів моделює фізичну структуру програмного 
забезпечення. Вона показує, з яких компонентів складається система та як вони 
з'єднані між собою через інтерфейси [8]. Компоненти – це автономні частини 
системи, які можна замінити або оновити незалежно одна від одної. Ця діаграма 
критично важлива для розуміння того, як збирається додаток і які сторонні 
бібліотеки в ньому використовуються.  
На діаграмі (рисунок 2.5) фізичну архітектуру мобільного застосунку 
PantryControl розділено на три основні вузли: мобільний пристрій користувача, 
хмарну платформу та зовнішні сервіси. 
1 Мобільний пристрій – це сам додаток, встановлений на телефон. Він 
складається з таких ізольованих компонентів: 
29 
ЧДТУ.262251.013 ПЗ 
‒ інтерфейс: візуальна частина системи, яка відповідає за відображення 
екранів та реакцію на дії користувача; 
‒ управління станом: модуль оперативної пам'яті, який зберігає дані для 
швидкого відображення в інтерфейсі; 
‒ бізнес-логіка: центральний вузол додатку, який приймає команди від 
інтерфейсу, виконує обчислення і робить запити до баз даних або 
зовнішніх API; 
‒ сховище налаштувань: швидке локальне сховище типу "ключ-значення", 
яке використовується виключно для збереження локальних конфігурацій 
(тема додатку, токени сесії тощо); 
‒ локальна база даних: повноцінна реляційна база на пристрої, де 
зберігається весь перелік продуктів, сховища та рецепти, забезпечуючи 
роботу системи без підключення до інтернету; 
‒ модуль синхронізації: фоновий рушій, який підключений безпосередньо 
до локальної бази даних і фіксує всі зміни для подальшої відправки на 
сервер. 
2 Хмарна платформа – серверна частина проекту, що включає: 
‒ аутентифікація: готовий компонент для безпечної реєстрації та входу 
користувачів; 
‒ хмарна база даних: централізоване сховище, яке слугує єдиним джерелом 
правди для всіх користувачів системи. Вона безпосередньо взаємодіє з 
модулем синхронізації на телефоні; 
‒ серверна логіка: захищені хмарні функції, які отримують специфічні 
запити та перевіряють права доступу користувача у базі. 
3 Зовнішні сервіси – незалежні компоненти, які розширюють можливості 
додатку: 
‒ модуль штучного інтелекту: нейромережа, яка аналізує розпізнаний з 
чеків текст та генерує рецепти на основі вподобань користувача; 
30 
ЧДТУ.262251.013 ПЗ 
‒ API штрихкодів: відкритий довідник товарів. Мобільний додаток 
звертається до нього напряму, якщо інформація про відсканований 
штрихкод відсутня в локальній базі даних. 
 
 
Рисунок 2.5 – Діаграма компонентів 
 
2.4.2. Розгортання програмної системи на апаратних засобах. Діаграма 
розгортання 
Діаграма розгортання показує апаратну конфігурацію системи: на яких 
фізичних пристроях (вузлах) виконуються програмні елементи [8]. Вона 
візуалізує топологію системи, включаючи клієнтські пристрої, сервери, бази 
даних та мережеві з'єднання. Це дозволяє оцінити вимоги до обладнання, 
зрозуміти фізичний розподіл обчислювальних ресурсів і те, як компоненти 
зв'язуються між собою через інтернет. 
Для забезпечення підходу Local-first та безперебійної роботи мобільного 
додатку система розгортається на трьох основних фізичних і логічних вузлах 
(рисунок 2.6): 
‒ смартфон користувача: мобільний пристрій на базі iOS або Android. На 
ньому встановлено додаток та розгорнуто локальну базу даних. Це 
критично важливий вузол, оскільки основні дані та обчислення 
31 
ЧДТУ.262251.013 ПЗ 
знаходяться безпосередньо на пристрої, що дозволяє програмі працювати 
навіть за відсутності інтернету; 
‒ хмарна платформа: зовнішня платформа на базі Supabase. Мобільний 
пристрій з'єднується з нею через інтернет з метою фонової синхронізації 
бази даних та для виклику серверних функцій; 
‒ зовнішні сервіси: сторонні сервери, з якими система спількується для 
виконання первних завдань. Смартфон напряму звертається до сервера 
API штрихкодів по HTTPS для пошуку невідомих товарів. Водночас 
звернення до Модуля ШІ для розпізнавання чеків та генерації рецептів 
відбувається не напряму з телефону, а через хмарні Edge Functions, що 
гарантує безпеку ключів доступу та дозволяє контролювати ліміти 
запитів. 
 
 
Рисунок 2.6 – Діаграма розгортання 
 
2.5. Моделювання поведінки системи 
Цей етап проектування присвячений опису динаміки застосунку. Якщо 
попередні діаграми показували статичну структуру, то поведінкові моделі 
демонструють, як саме ці блоки взаємодіють у часі для виконання конкретних 
бізнес-завдань. 
32 
ЧДТУ.262251.013 ПЗ 
2.5.1. Діаграма діяльності 
Діаграма діяльності – динамічна діаграма, яка відображає потік управління 
від однієї діяльності до іншої [8]. Вона схожа на блок-схему алгоритму і 
використовується для моделювання бізнес-процесів, логіки виконання складних 
методів або сценаріїв використання. Діаграма дозволяє побачити послідовність 
дій, умови розгалуження та паралельне виконання процесів.  
На діаграмі відображено повний процес додавання продуктів у 
PantryControl через аналіз чеку (рисунок 2.7). 
 
 
Рисунок 2.7 – Діаграма діяльності 
33 
ЧДТУ.262251.013 ПЗ 
Послідовність дій така: 
‒ користувач робить фото чеку; 
‒ відбувається локальне вилучення тексту з фотографії, якщо тексту немає, 
то процес зупиняється з помилкою; 
‒ розпізнаний текст відправляється до AIService для обробки штучним 
інтелектом; 
‒ сервіс AIService аналізує дані та повертає розпізнаний список продуктів; 
‒ користувачу відображається прев'ю списку, де він може переглянути 
продукти та за потреби відредагувати, додати або видалити їх; 
‒ користувач підтверджує збереження фінального списку; 
‒ лише вибрані продукти зберігаються в локальній базі; 
‒ застосунок отримує оновлені дані з бази та оновлює глобальний стан 
Store; 
‒ інтерфейс автоматично відображає користувачу оновлений список 
продуктів у сховищі. 
2.5.2. Діаграма послідовності 
Діаграма послідовності – це різновид діаграми взаємодії, яка показує, як 
об'єкти обмінюються повідомленнями у хронологічному порядку [8]. На ній 
зображено життєві лінії об'єктів (вертикальні лінії) та повідомлення 
(горизонтальні стрілки), якими вони обмінюються для реалізації певного 
сценарію. Ця діаграма є ідеальною для деталізації логіки роботи конкретних 
методів та взаємодії між шарами архітектури. 
На діаграмі відображено детальний хронологічний процес обміну 
повідомленнями під час успішної обробки чеку (рисунок 2.8). Послідовність 
взаємодії така: 
‒ користувач взаємодіє з інтерфейсом і надає фотографію чеку; 
‒ інтерфейс виконує вилучення сирого тексту із зображення; 
‒ UI відправляє вилучений текст до AIService для глибокого аналізу; 
34 
ЧДТУ.262251.013 ПЗ 
‒ AIService обробляє запит через ШІ та повертає структурований список 
розпізнаних продуктів; 
‒ UI відображає цей список користувачу для попереднього перегляду; 
‒ користувач переглядає кожен продукт, редагує його властивості та 
підтверджує свій вибір; 
‒ UI передає фінальний відредагований список до DatabaseService; 
‒ DatabaseService виконує SQL запит для збереження вибраних продуктів у 
локальній базі; 
‒ після успішного запису DatabaseService викликає функцію гідратації у 
Store; 
‒ Store самостійно звертається до локальної бази, щоб отримати свіжий 
масив даних; 
‒ отримавши актуальні дані, Store оновлює свій глобальний стан; 
‒ зміни у Store автоматично викликають реактивне оновлення UI, і 
користувач бачить оновлений список продуктів на екрані. 
 
 
Рисунок 2.8 – Діаграма послідовності 
35 
ЧДТУ.262251.013 ПЗ 
2.5.3. Діаграма комунікації 
Діаграма комунікації також моделює взаємодію об'єктів, але, на відміну від 
діаграми послідовності, акцентує увагу не на часі, а на структурних зв'язках між 
об'єктами [8]. Вона показує, які компоненти пов'язані між собою та якими 
повідомленнями вони обмінюються в рамках виконання завдання. Це дозволяє 
краще зрозуміти розподіл обов'язків між вузлами системи. 
Діаграма відображає централізовану архітектуру, де UI виступає головним 
координатором початкового етапу процесу обробки чеку, а після підтвердження 
користувача управління переходить до сервісів роботи з даними (рисунок 2.9). 
 
 
Рисунок 2.9 – Діаграма комунікації 
 
Взаємодія має строгу циклічну структуру і відбувається за таким 
алгоритмом: 
‒ початкова взаємодія (1, 4, 5): користувач ініціює процес, надаючи 
фотографію. Отримавши проміжні результати від системи, UI показує 
36 
ЧДТУ.262251.013 ПЗ 
користувачу прев'ю продуктів, після чого користувач вносить правки та 
підтверджує свій вибір; 
‒ інтелектуальна обробка (2, 3): UI вилучає текст із фотографії та 
звертається до AIService, отримуючи у відповідь структурований список 
товарів; 
‒ збереження даних (6, 7): після підтвердження користувачем, UI передає 
дані у DatabaseService для виконання запису у локальну базу даних; 
‒ синхронізація стану (8, 9, 10, 11): завершивши запис, DatabaseService 
викликає функцію гідратації в Store, який самостійно звертається до бази 
даних, отримує свіжий масив усіх товарів та ініціює оновлення 
інтерфейсу, після чого користувач одразу бачить додані продукти на 
екрані свого смартфона. 
2.5.4. Діаграма скінченного автомата 
Діаграма скінченного автомата описує динамічну поведінку об'єкта, 
показуючи всі можливі стани, у яких він може перебувати протягом свого 
життєвого циклу, та події, що спричиняють перехід з одного стану в інший [8]. 
Вона є незамінною для моделювання об'єктів зі складною логікою зміни статусу, 
якими у системі PantryControl є продукти. Позначення станів продукту (рисунок 
2.10): 
‒ П (початок) – ініціалізація процесу: отримання фото чеку або натискання 
кнопки ручного додавання; 
‒ 1 – стан редагування (чернетка), де користувач перевіряє розпізнані дані 
перед збереженням; 
‒ 2 – наявність продукту на зберіганні, коли товар фізично додано до 
запасів користувача; 
‒ 3 – стан прострочення, коли поточна дата перевищила вказаний термін 
придатності продукту; 
‒ 4 – стан споживання, коли продукт успішно використано за 
призначенням; 
37 
ЧДТУ.262251.013 ПЗ 
‒ 5 – стан утилізації, коли продукт викинуто через псування або видалено з 
системи; 
‒ К (кінець) – завершення життєвого циклу продукту, його видалення або 
переміщення в архів бази даних. 
 
 
Рисунок 2.10 – Діаграма скінченного автомата життєвого циклу продукту 
 
У початковому стані ініціюється процес створення запису про продукт 
шляхом сканування чеку або ручного введення. З цього етапу система переходить 
у стан 1, де виконується аналіз та попереднє редагування атрибутів продукту у 
тимчасовому сховищі. Зі стану 1 йде розгалуження: у стан 2, коли користувач 
підтверджує додавання і продукт зберігається у локальну базу даних як наявний 
на зберіганні, або безпосередньо у кінцевий стан, якщо користувач скасовує 
процес створення. Перебуваючи у стані 2 (на зберіганні), продукт має три шляхи 
розвитку: він може перейти у стан 4, якщо користувач позначає його як успішно 
спожитий, або у стан 5, якщо користувач вирішує викинути чи видалити його 
вручну. Третій шлях є автоматичним: якщо термін придатності продукту спливає, 
відбувається логічний перехід у стан 3 (прострочено). Зі стану 3 перехід 
можливий виключно у стан 5, оскільки прострочений товар врешті-решт підлягає 
утилізації. Перехід у кінцевий стан здійснюється із станів 4 та 5, де життєвий цикл 
продукту остаточно завершується, а дані про нього видаляються з активного 
екрана та архівуються. Дана модель чітко описує управління запасами в системі, 
виключаючи будь-які нелогічні переходи. 
38 
ЧДТУ.262251.013 ПЗ 
ВИСНОВКИ ДО ДРУГОГО РОЗДІЛУ 
У другому розділі було здійснено комплексне проектування архітектури та 
логічної структури мобільного застосунку PantryControl, що стало логічним 
переходом від аналізу проблеми до технічної реалізації.  
Особливу увагу приділено моделюванню системи за допомогою стандарту 
UML. Було детально спроектовано статичну будову застосунку з чітким поділом 
коду на архітектурні шари та компоненти, також динамічну поведінку системи: 
алгоритми взаємодії між телефоном і сервісами штучного інтелекту під час 
розпізнавання чеків, процеси фонової синхронізації даних та життєвий цикл 
продукту від моменту покупки до споживання чи утилізації. 
Проведене проектування сформувало надійний фундамент для розробки 
базового функціоналу:  поповнення комори, контролю термінів придатності та 
розумної генерації рецептів з урахуванням дієтичних вподобань користувача. Усі 
архітектурні рішення відповідають поставленим цілям і дозволяють створити 
зручний, безпечний та автономний інструмент для ефективного домашнього 
обліку. 
 
39 
ЧДТУ.262251.013 ПЗ 
РОЗДІЛ 3 РОЗРОБКА ТА ТЕСТУВАННЯ ПРОГРАМНОГО 
ЗАБЕЗПЕЧЕННЯ 
3.1. Розробка програмного комплексу 
Вибір програмних та апаратних інструментів для створення застосунку 
базувався на необхідності створити швидку мобільну програму для управління 
продуктами, яка має стабільно працювати незалежно від наявності інтернет-
з'єднання. 
3.1.1. Обґрунтування вибору засобів реалізації 
Для реалізації мобільного застосунку було проведено аналіз сучасних 
інструментів, що дозволяють створити швидкий, продуктивний продукт із 
підтримкою автономної роботи на різних мобільних пристроях. 
Основним інструментом для розробки клієнтської частини обрано 
фреймворк React Native. Мовами програмування виступають JavaScript та 
TypeScript. Такий вибір дозволяє створити єдину кодову базу для обох платформ. 
Важливо зазначити, що написаний JavaScript/TypeScript код не компілюється у 
нативний машинний код. Замість цього він виконується в реальному часі за 
допомогою вбудованого JS-рушія, який керує справжніми нативними 
компонентами інтерфейсу для Android та iOS. Це суттєво оптимізує час та 
вартість розробки, оскільки усуває потребу у створенні двох окремих програм 
різними мовами. 
Оскільки застосунком можуть користуватися у супермаркетах чи інших 
місцях, де зв'язок часто нестабільний, архітектура була побудована за принципом 
Local-first. Це передбачає використання таких інструментів: 
‒ локальна БД. Для збереження даних безпосередньо на телефоні 
використовується SQLite [17]. Це легка і перевірена СУБД, яка не 
потребує окремого сервера і миттєво обробляє запити. Для дрібних даних 
(токени, налаштування) використано бібліотеку react-native-mmkv, що 
забезпечує високу швидкість читання/запису; 
40 
ЧДТУ.262251.013 ПЗ 
‒ хмарна БД. Для резервного копіювання та реєстрації користувачів обрано 
платформу Supabase, яка працює на базі PostgreSQL [18]. Це економить 
час, бо не потрібно писати власний бекенд з нуля. Supabase надає готову 
авторизацію та захист даних на рівні рядків, що гарантує безпеку кожного 
користувача; 
‒ синхронізація. Щоб автоматично обмінюватися даними між SQLite та 
Supabase, використовується рушій Supastash [19]. Він фоново відправляє 
локальні зміни на сервер, як тільки з'являється інтернет. 
Для управління глобальним станом програми обрано бібліотеку Zustand. 
Вона значно простіша у використанні, ніж більш складний Redux, вимагає 
мінімум шаблонного коду і легко налаштовується для підвантаження даних з 
локальної бази. 
Для написання коду та збірки проекту використовується пристрій з 
операційною системою macOS. Цей вибір є обов'язковим, оскільки компіляція та 
тестування iOS-версії застосунку можливі лише в середовищі від Apple. Апаратні 
характеристики комп'ютера дозволяють без затримок запускати локальні сервери 
та мобільні емулятори. Додатково для тестування використовуються фізичні 
смартфони на базі Android та iOS, що необхідно для перевірки роботи камери під 
час сканування чеків та швидкодії локальної бази даних на реальному пристрої. 
У підсумку, такий набір технологій дозволяє максимально оптимізувати 
процес створення програми, використання готових інфраструктурних рішень та 
кросплатформного фреймворку значно скорочує час розробки, а підхід Local-first 
забезпечує високу функціональність та комфорт для кінцевого користувача. 
3.1.2. Опис структурної (функціональної) схеми 
Щоб показати будову розробленої системи та зрозуміти принципи її роботи, 
було створено дві схеми. Вони наочно відображають усі складові частини 
програми та інформаційні зв'язки між ними. 
Структурна схема показує базові елементи системи управління домашніми 
запасами (рисунок 3.1). Програма поділяється на чотири основні блоки: 
41 
ЧДТУ.262251.013 ПЗ 
‒ підсистема інтерфейсу – відповідає за взаємодію з користувачем та 
виведення інформації на екран смартфона; 
‒ підсистема бізнес-логіки – обробляє ці команди, перевіряє дані та виконує 
основні обчислення; 
‒ підсистема збереження даних – забезпечує запис та зчитування 
інформації з локальної бази на пристрої; 
‒ підсистема синхронізації – працює незалежно та копіює локальні дані на 
зовнішній сервер. 
 
 
Рисунок 3.1 – Структурна схема програмної системи 
 
Функціональна схема є детальнішим продовженням структурної (рисунок 
3.2). Вона базується на тих самих чотирьох підсистемах, але додатково показує, як 
саме між ними рухається інформація під час роботи програми. Для її побудови 
використані стандартні графічні символи оформлення документації. Згідно зі 
схемою, потік даних виглядає так: 
‒ спочатку відбувається ручне введення, коли людина сканує чек або 
друкує назву продукту; 
‒ інформація передається до блоку процесу, який відповідає підсистемі 
бізнес-логіки. Там вона приводиться до потрібного формату; 
‒ далі готові дані потрапляють до підсистемі збереження даних з прямим 
доступом до локальної бази даних SQLite; 
42 
ЧДТУ.262251.013 ПЗ 
‒ після успішного збереження потік даних іде у двох напрямках. Перша 
частина інформації надходить на дисплей смартфона, оновлюючи 
інтерфейс. Інша частина передається до наступного процесу – підсистеми 
синхронізації, яка відправляє дані у хмарне сховище. 
Такий опис дозволяє чітко простежити весь шлях даних від моменту їх 
створення користувачем до остаточного збереження та відображення. 
 
 
Рисунок 3.2 – Функціональна схема інформаційних потоків 
 
3.1.3. Опис логічної схеми системи 
Логічна схема системи визначає послідовність дій, які виконує застосунок 
від моменту запуску до завершення роботи. Вона описує основні інформаційні 
процеси: перевірку умов, завантаження даних, обробку команд користувача та 
фонову синхронізацію. Оскільки програма побудована за принципом офлайн-
пріоритету, її головний алгоритм сфокусований на роботі з локальним сховищем. 
43 
ЧДТУ.262251.013 ПЗ 
Для кращого розуміння цього процесу, було побудовано загальну блок-
схему алгоритму роботи системи (рисунок 3.3). Вона описує послідовність 
виконання програми, перевірку критичних умов та порядок взаємодії з базами 
даних. Загальний алгоритм функціонування застосунку складається з таких 
логічних кроків: 
‒ запуск застосунку. Ініціалізація основних модулів системи на мобільному 
пристрої; 
‒ перевірка авторизації. Система перевіряє наявність збереженого токена 
доступу. Якщо користувач не авторизований, його перенаправляють на 
екран входу. Якщо авторизація успішна, алгоритм продовжує роботу; 
‒ завантаження локальних даних. Система звертається до локальної бази 
даних SQLite та зчитує збережену інформацію; 
‒ гідратація стану. Зчитані дані передаються до менеджера глобального 
стану, який оновлює інтерфейс користувача. З'являється головний екран 
із даними; 
‒ очікування дій користувача (циклічна структура). Система переходить у 
режим очікування. Користувач може виконувати різні операції: 
переглядати списки, додавати нові продукти, редагувати їх або видаляти; 
‒ обробка запиту. Коли користувач виконує дію, підсистема бізнес-логіки 
перевіряє введені дані; 
‒ локальне збереження. Оновлені дані записуються у локальну базу. Після 
цього стан інтерфейсу миттєво оновлюється, відображаючи зміни; 
‒ перевірка підключення. Модуль синхронізації перевіряє чи є доступ до 
інтернету і якщо інтернету немає, зміни стають у локальну чергу. Якщо 
з'єднання стабільне, виконується фонова передача даних на хмарний 
сервер для резервного копіювання; 
‒ повернення до очікування. Після завершення циклу обробки та 
синхронізації система знову повертається на етап очікування нових 
команд від користувача. 
44 
ЧДТУ.262251.013 ПЗ 
 
Рисунок 3.3 – Узагальнена блок-схема алгоритму функціонування системи 
45 
ЧДТУ.262251.013 ПЗ 
3.1.4. Розробка бази даних 
База даних є важливою складовою розробленої інформаційної системи. 
Вона виконує роль центрального сховища, де надійно розміщується вся 
інформація користувача. Замість збереження даних у різних файлах усі записи 
згруповані разом, що дозволяє уникнути зайвого дублювання та ефективно 
керувати інформацією. 
Для організації даних обрано реляційну модель. Вона вирізняється 
простотою, зрозумілістю та зручністю у розробці. Оскільки система має 
повноцінно працювати без підключення до інтернету, база даних фізично 
розділена на дві частини. На мобільному телефоні працює локальна база під 
управлінням SQLite, яка гарантує миттєвий доступ до збереженої інформації. 
Паралельно у хмарному сервісі Supabase розгорнута база даних PostgreSQL. Вона 
відповідає за автоматичну синхронізацію, коли всі локальні записи непомітно 
дублюються на сервер. Такий підхід гарантує надійне збереження інформації 
навіть у разі поломки або втрати мобільного телефону. 
Використання двох різних технологій впливає на фізичну реалізацію 
сховищ. Локальна база SQLite оперує переважно базовими типами даних, такими 
як текст або числа. Натомість хмарна база PostgreSQL має ширші можливості і 
дозволяє використовувати формат JSON для зручного збереження складних 
масивів інформації. 
Для забезпечення конфіденційністі інформації, у хмарній базі даних 
налаштовано політику безпеки на рівні рядків. Цей механізм суворо обмежує 
доступ, тому кожен користувач може бачити та редагувати виключно особисті 
дані. 
Процес моделювання бази складався з трьох послідовних кроків. На початку 
було створено концептуальну модель, щоб визначити основні сутності з точки 
зору майбутнього користувача. Другим кроком стала розробка логічної моделі, 
яка перетворила ці поняття на конкретні об'єкти та зв'язки. На фінальному етапі 
було спроектовано фізичну модель, яка описує вже реальні таблиці, їхні стовпці 
та ключі. 
46 
ЧДТУ.262251.013 ПЗ 
Щоб не дублювати інформацію під час опису структури, хочу зазначити 
системні поля, які автоматично додаються до кожної хмарної таблиці. До них 
належать: унікальний ідентифікатор id (первинний ключ), created_at (час 
створення запису), updated_at (час останнього оновлення), deleted_at (час 
видалення для можливості відновлення) та arrived_at (час потрапляння даних на 
пристрій при синхронізації). У наведених нижче таблицях ці системні поля 
опущені. 
Для збереження інформації про логічні або фізичні місця розташування 
продуктів (холодильник, комора) використовується окрема сутність. Структура 
таблиці «Сховища» наведена в таблиці 3.1. 
 
Таблиця 3.1  
Структура таблиці "Сховища" (storages) 
№ п/п Назва поля Тип поля Ключ Опис 
1 id uuid PK Унікальний ідентифікатор 
2 name text  Назва місця зберігання 
3 icon text  Візуальна іконка 
4 user_id uuid FK Ідентифікатор власника 
 
Планування майбутніх покупок та організація списків товарів реалізується 
за допомогою спеціальної таблиці. Структура таблиці «Списки покупок» наведена 
в таблиці 3.2. 
 
Таблиця 3.2  
Структура таблиці "Списки покупок "(shopping_lists) 
№ п/п Назва поля Тип поля Ключ Опис 
1 id uuid PK Унікальний ідентифікатор 
2 name text  Назва списку покупок 
3 user_id uuid FK Ідентифікатор власника 
 
47 
ЧДТУ.262251.013 ПЗ 
Головним елементом системи для обліку запасів, їх кількості та поточних 
статусів є таблиця продуктів. Структура таблиці «Продукти» наведена в таблиці 
3.3.  
Таблиця 3.3  
Структура таблиці "Продукти" (products) 
№ п/п Назва поля Тип поля Ключ Опис 
1 id uuid PK Унікальний ідентифікатор 
2 name text  Назва продукту 
3 icon text  Візуальна іконка 
4 category text  Категорія товару 
5 quantity numeric  Кількість продукту 
6 unit text  Одиниця виміру 
7 expiration_date timestamptz  Термін придатності 
8 status text  Поточний стан наявності 
9 status_changed_at timestamptz  Час зміни статусу 
10 barcode text  Штрихкод товару 
11 storage_id uuid FK Зв'язок зі сховищем 
12 shopping_list_id uuid FK Зв'язок зі списком покупок 
13 related_product_id uuid FK Зв'язок з іншим продуктом 
 
Для зручного групування кулінарних рецептів за папками створено 
додаткову сутність. Структура таблиці «Колекції рецептів» наведена в таблиці 
3.4. 
 
Таблиця 3.4  
Структура таблиці "Колекції рецептів" (collections) 
№ п/п Назва поля Тип поля Ключ Опис 
1 id uuid PK Унікальний ідентифікатор 
2 name text  Назва категорії рецептів 
48 
ЧДТУ.262251.013 ПЗ 
Продовження таблиці 3.4 
№ п/п Назва поля Тип поля Ключ Опис 
3 icon text  Візуальна іконка 
4 user_id uuid FK Ідентифікатор власника 
5 color text  Колір відображення тегу 
 
Усі дані про страви, інгредієнти та кроки їх приготування містяться у 
відповідній таблиці. Структура таблиці «Рецепти» наведена в таблиці 3.5.  
 
Таблиця 3.5  
Структура таблиці "Рецепти" (recipes) 
№ п/п Назва поля Тип поля Ключ Опис 
1 id uuid PK Унікальний ідентифікатор 
2 title text  Назва кулінарної страви 
3 collection_id uuid FK Зв'язок з колекцією 
4 is_favorite bool  Позначка улюбленої страви 
5 prep_time_minutes int4  Час підготовки продуктів 
6 cook_time_minutes int4  Час самого готування 
7 servings int4  Кількість порцій 
8 image_url text  Посилання на фотографію 
9 ingredients jsonb  Масив інгредієнтів 
10 instructions jsonb  Алгоритм приготування 
11 source_type text  Походження рецепту 
12 description text  Короткий опис рецепту 
13 source_url text  Посилання на джерело 
 
Харчові звички, дієти та можливі алергії користувача винесені в окремі 
налаштування профілю для їх подальшого використання при генерації рецептів. 
Структура таблиці «Вподобання користувача» наведена в таблиці 3.6. 
49 
ЧДТУ.262251.013 ПЗ 
Таблиця 3.6  
Структура таблиці "Вподобання користувача" (user_preferences) 
№ п/п Назва поля Тип поля Ключ Опис 
1 id uuid PK Ідентифікатор запису 
2 diets jsonb  Дієтичні обмеження 
3 allergies jsonb  Список алергій 
4 cuisines jsonb  Улюблені кухні світу 
 
Окремо від хмарної інфраструктури розроблена таблиця для збереження 
штрихкодів. Вона працює як довідник для розпізнавання товарів. Особливістю 
цієї таблиці є те, що первинним ключем (id) виступає безпосередньо 
відсканований штрихкод. Структура таблиці «Штрихкоди» наведена в таблиці 3.6. 
 
Таблиця 3.7  
Структура таблиці "Штрихкоди" (barcodes) 
№ п/п Назва поля Тип поля Ключ Опис 
1 id text PK Відсканований код товару 
2 name text  Збережена назва товару 
3 icon text  Візуальна іконка 
4 category text  Категорія товару 
5 quantity numeric  Стандартна кількість 
6 unit text  Одиниця виміру 
 
Для кращого розуміння побудовано відповідну ER-діаграму (рисунок 3.4). 
Вона об'єднує описані вище таблиці в єдину логічну систему та наочно показує 
всі ключі, типи даних для кожної сутності та зв'язки між ними. 
Варто звернути увагу на таблицю користувачів (users), яка присутня на 
схемі, але не розроблялася вручну. Оскільки хмарна база працює на платформі 
Supabase, ця система повністю бере на себе керування авторизацією та безпекою. 
Supabase автоматично створює та підтримує системну таблицю користувачів, а 
50 
ЧДТУ.262251.013 ПЗ 
також багато інших службових таблиць, які приховані від прямого втручання. 
Саме тому в розроблених таблицях поле user_id просто виступає зовнішнім 
ключем, який посилається на цю готову системну сутність. 
Також окремо на схемі зображено локальну таблицю штрихкодів (barcodes). 
Оскільки вона працює виключно на мобільному пристрої, вона не має прямих 
зв'язків із хмарними таблицями бази даних в Supabase. 
 
 
Рисунок 3.4 – Загальна ER-діаграма бази даних 
51 
ЧДТУ.262251.013 ПЗ 
3.1.5. Розробка інтерфейсу користувача 
Розробка інтерфейсу користувача є одним із найважливіших етапів 
створення програми. Головне завдання полягало в тому, щоб забезпечити зручну 
взаємодію людини з системою. Оскільки застосунок призначений для щоденного 
використання в побуті або під час покупок, інтерфейс має працювати швидко та 
не перевантажувати екран зайвою інформацією. Також треюба було передбачити 
здатність системи функціонувати в автономному режимі без доступу до мережі 
інтернет. 
Сам процес проектування інтерфейсу базувався на принципах Якоба 
Нільсена, які дозволяють створювати ефективні та зручні програмні продукти. 
Під час розробки було виконано такі вимоги: 
‒ видимість стану системи. Користувач завжди розуміє поточну ситуацію 
на екрані. Наприклад, у списку покупок відображається смуга прогресу, 
яка показує відсоток зібраних товарів. При відсутності результатів 
пошуку виводиться зрозуміле текстове повідомлення замість порожнього 
простору. Додатково на картках продуктів є яскраві кольорові позначки, 
які відразу сигналізують про його статус. Також у налаштуваннях 
відображається точний час останньої успішної синхронізації бази даних; 
‒ зв'язок між системою та реальним світом. Інтерфейс розмовляє з 
людиною простою мовою та використовує знайомі концепції. Наприклад, 
куплені товари у списку візуально закреслюються, що повністю імітує 
звичне викреслювання ручкою на паперовому аркуші. Користувач не 
обмежений готовими шаблонами і може самостійно створювати місця 
зберігання, називаючи їх так, як вони реально існують у нього вдома, та 
підбирати для них відповідні іконки. Також для вказування кількості 
продуктів застосовуються звичайні міри, такі як грами, літри, упаковки 
або штуки. Усе це робить процес обліку домашніх запасів максимально 
природним і схожим на реальне життя; 
52 
ЧДТУ.262251.013 ПЗ 
‒ свобода дій та контроль. Програма дозволяє легко виправляти власні дії 
та захищає від випадкових кроків. Якщо людина намагається видалити 
сховище чи список, обов'язково з'являється попередження про 
незворотність процесу. Передбачена можливість повернути активний стан 
товару, який був випадково відмічений як списаний. Окрім цього, при 
скануванні чека система не зберігає результати миттєво, а дає змогу 
перевірити та відредагувати розпізнаний список перед фінальним 
записом. Також завжди можна скасувати вихід із форми створення, якщо 
там залишилися незбережені дані; 
‒ запобігання помилкам. Інтерфейс спроектований таким чином, щоб 
звести до мінімуму можливість введення хибної інформації. Кнопки 
збереження залишаються неактивними до моменту заповнення всіх 
обов'язкових полів. Замість ручного введення категорій чи одиниць 
виміру використовуються зручні списки вибору, що унеможливлює 
друкарські помилки. Також при спробі згенерувати рецепт система не 
дозволить продовжити дію, якщо не обрано жодного базового 
інгредієнта; 
‒ гнучкість та ефективність використання. Інтерфейс адаптовано для 
швидкого виконання повсякденних завдань. Для прискорення роботи 
додано підтримку жестів: можна провести пальцем по картці товару або 
затиснути її, щоб побачити список можливих дій з ним. Замість довгого 
ручного введення реалізовано сканер штрихкодів, який запам'ятовує нові 
товари і при наступному скануванні заповнює форму автоматично. Крім 
того, для зручної навігації у сховищах передбачені швидкі фільтри для 
відображення тільки активних або вже спожитих продуктів. 
Для забезпечення цих принципів розроблено зрозумілу навігаційну 
структуру. Головний екран є стартовою точкою програми і містить три основні 
блоки. Перший показує перелік усіх створених сховищ із кнопкою для додавання 
нового. Другий відображає до двох останніх списків покупок для швидкого 
доступу, а при наявності більшої кількості пропонує перейти до повного переліку. 
53 
ЧДТУ.262251.013 ПЗ 
Третій блок містить додаткові інструменти, зокрема перехід до налаштувань та 
кнопку швидкої генерації рецепту. З елементами на головному екрані можна 
взаємодіяти за допомогою жестів для швидкого редагування або видалення 
(рисунок 3.5). 
Перейшовши до конкретного сховища, користувач бачить панель фільтрів 
та рядок пошуку за назвою. Нижче відображаються картки доданих товарів. За 
допомогою жестів або затискання картки продукт можна відредагувати, видалити, 
а також відмітити як спожитий або викинутий. У нижній частині екрана 
розміщені інструменти для поповнення запасів, які дозволяють додати продукт 
вручну або відкрити сканер штрихкодів (рисунок 3.6). 
 
 
Рисунок 3.5 – Головний екран застосунку 
54 
ЧДТУ.262251.013 ПЗ 
 
 
 
Рисунок 3.6 – Екран перегляду вмісту сховища 
 
Окремий розділ виділено під планування майбутніх покупок. На загальному 
екрані виводяться картки списків з назвою, загальною кількістю продуктів, часом 
останнього оновлення та індикатором повного завершення. Усередині 
конкретного списку присутня смуга прогресу та перелік товарів. Коли продукт 
позначається як куплений, його текст відразу закреслюється. У верхній частині 
інтерфейсу реалізовано випадаюче меню для додавання вже існуючих товарів та 
редагування списку, також є кнопка поширення списку іншим людям (рисунок 
3.7). 
55 
ЧДТУ.262251.013 ПЗ 
 
Рисунок 3.7 – Екран списку покупок із прогресом 
 
Для максимальної зручності поповнення запасів створено центральну 
вкладку швидких дій. Вона зібрала всі можливі способи додавання продуктів в 
одному місці. Користувач може обрати ручне введення, сканування штрихкоду, 
розпізнавання товарів з фотографії чека або з файлу, а також швидкий вибір 
товарів із власної історії. Це значно економить час при поверненні з магазину з 
великим чеком продуктів (рисунок 3.8). 
Розділ колекцій призначений для збереження кулінарного досвіду. На цьому 
екрані є рядок пошуку, створені користувачем тематичні папки та системна 
категорія улюблених страв. Усередині вибраної колекції розміщується список 
рецептів у вигляді карток. Кожна картка містить фотографію, назву, загальний час 
приготування та кількість порцій. У нижній панелі доступні три способи 
створення нової страви. Додати рецепт можна заповнивши форму вручну, 
імпортувавши його за посиланням з кулінарного сайту або згенерувавши на 
56 
ЧДТУ.262251.013 ПЗ 
основі наявних інгредієнтів (рисунок 3.9). 
Екран налаштувань дозволяє повністю адаптувати програму під власні 
потреби та звички. Тут користувач має змогу змінити тему інтерфейсу, обрати 
мову та налаштувати час отримання сповіщень. Важливою секцією є харчові 
вподобання, де можна вказати власні дієтичні обмеження, улюблені кухні світу 
або наявні алергії, які синхронізуються з хмарною базою даних. Ці дані потім 
автоматично враховуються системою при генерації рецептів (рисунок 3.10). 
Застосунок використовує єдиний зрозумілий підхід до форм створення та 
редагування всіх об'єктів. Для прикладу, коли користувач відкриває форму 
додавання нового продукту або рецепту, клавіатура з'являється автоматично, а 
курсор відразу стає у поле введення назви. Це дрібне покращення позбавляє від 
необхідності робити зайвий натиск по екрану. Усі форми мають систему 
перевірки введених даних, тому кнопка збереження стає активною лише після 
заповнення всіх обов'язкових полів. Якщо ж людина спробує закрити вікно з 
незбереженими даними, програма обов'язково попередить про можливу втрату 
змін (рисунок 3.11). 
 
 
Рисунок 3.8 – Вкладка швидких дій для поповнення запасів 
57 
ЧДТУ.262251.013 ПЗ 
 
Рисунок 3.9 – Екран перегляду колекції рецептів 
 
 
Рисунок 3.10 – Екран налаштувань та харчових вподобань 
58 
ЧДТУ.262251.013 ПЗ 
 
Рисунок 3.11 – Форма створення нового продукту з попередженням про втрату 
даних 
 
3.1.6. Опис розробки програмних компонентів 
Програмна частина мобільного застосунку побудована на базі сучасного 
фреймворку React Native. Це дозволило створити єдину кодову базу, яка стабільно 
працює на різних мобільних операційних системах. Для написання логіки 
використовується мова програмування TypeScript, вона забезпечує сувору 
типізацію даних, що значно зменшує кількість помилок на етапі розробки [14]. 
Система має яскраво виражену компонентну структуру та поділена на 
незалежні логічні модулі. Кожен з них відповідає за своє конкретне завдання та 
взаємодіє з іншими частинами програми через чітко визначені методи. Серед усіх 
структурних частин можна виділити три найголовніші компоненти. 
Модуль управління глобальним станом та локальною базою даних. Цей 
рівень архітектури гарантує миттєву реакцію інтерфейсу на дії користувача та 
можливість повноцінного використання програми без доступу до інтернету. Для 
забезпечення максимальної продуктивності модуль технічно поділено на дві 
59 
ЧДТУ.262251.013 ПЗ 
взаємопов'язані частини: систему оперативного керування станом (бібліотека 
Zustand) та постійну локальну базу даних (SQLite). 
Щоб уникнути заплутаності в коді, система стану розбита на кілька 
незалежних ізольованих контейнерів. Наприклад, існує окремий глобальний 
контейнер, який відповідає виключно за збереження загальних налаштувань 
програми (тема, мова). Водночас існують зовсім інші тематичні контейнери, як-от 
контейнер продуктів або рецептів, які призначені для керування великими 
масивами інформації. 
Алгоритм взаємодії цих технологій найкраще розглянути на прикладі 
роботи зі сховищами. Уся основна інформація фізично зберігається в таблицях 
локальної бази SQLite. Коли людина створює нове місце зберігання, сервіс бази 
даних виконує запит і безпечно записує інформацію в таблицю. Відразу після 
успішного збереження викликається спеціальна функція синхронізації та 
наповнення пам'яті. Вона автоматично звертається до бази, зчитує оновлений 
список сховищ і завантажує його безпосередньо у відповідний контейнер. Завдяки 
такому підходу інтерфейс екрана завжди бере готові дані з оперативної пам'яті і 
перемальовується миттєво, без жодних візуальних затримок. Цей процес взаємодії 
наочно продемонстровано у лістингу 3.1. 
 
Лістинг 3.1 – Фрагмент алгоритму збереження та оновлення стану 
class DatabaseService { 
  // ... інший код класу ...  
 
  // Метод створення нового запису у фізичній базі SQLite 
  async addStorage(storage: StorageInsert) { 
    try { 
      const { data, error } = await 
db('storages').insert<StorageRow>(storage).run(); 
      if (error) throw error; 
 
      // Після успішного запису викликаємо оновлення контейнерів у 
пам'яті 
      await this.triggerHydration('storages'); 
      return { rows: data || [], isError: false }; 
    } catch (error) { 
      return { rows: [], isError: true }; 
    } 
  } 
  // ... інші методи для роботи з базою даних 
} 
60 
ЧДТУ.262251.013 ПЗ 
 
// Незалежний тематичний контейнер для управління станом сховищ 
export const useStoragesStore = create<StoragesStore>((set, get) => ({ 
  storages: [], 
   
  // Метод для наповнення пам'яті свіжими даними з бази 
  hydrateStorages: async (newLimit) => { 
    // Читання актуальних записів напряму з таблиці бази даних 
    const { data, error } = await supastash 
      .from('storages') 
      .select('*') 
      .is('deleted_at', null) 
      .run(); 
     
    if (data) { 
      // Завантаження даних у контейнер для миттєвого відображення на 
екрані 
      set({ storages: data }); 
    } 
  } 
  // ... інші поля та методи управління станом 
})); 
 
Модуль інтелектуальної обробки даних. Цей компонент відповідає за 
автоматизацію рутини та взаємодію з ШІ. Його головне завдання полягає у 
перетворенні неструктурованого тексту на чіткі об'єкти для подальшого запису в 
базу. 
Процес обробки фотографії чека працює у кілька етапів і поєднує роботу 
локальних інструментів та хмарного сервера. Спочатку локальна бібліотека 
розпізнавання тексту (наприклад, Expo PDF Text Extract [20]) аналізує зображення 
і витягує з нього всі наявні символи. Якщо текст успішно зчитано, він передається 
у відповідний метод модуля. Цей сервіс виконує мережевий запит до хмарної 
функції Edge Functions [21], яка взаємодіє з API великої мовної моделі (Google 
Gemini API [22]) для структурування даних та повертає готові об'єкти продуктів. 
Важливою архітектурною особливістю є те, що після отримання відповіді 
від сервера програма не зберігає продукти відразу у фізичну базу даних. 
Натомість отриманий масив записується у спеціальний контейнер чернеток. Це 
дозволяє перевести людину на екран попереднього перегляду, де вона зможе 
перевірити правильність розпізнавання, відредагувати результати та підтвердити 
фінальне збереження. Увесь цей ланцюжок дій керується функцією, яка наведена 
у лістингу 3.2. 
61 
ЧДТУ.262251.013 ПЗ 
Лістинг 3.2 – Фрагмент методу обробки розпізнаного тексту 
// ... інший код компонента екрана ... 
const handleImage = async () => { 
  if (photoPath.length === 0) return; 
  setIsLoading(true); 
 
  try { 
    // 1. Витягування сирого тексту з фотографії чека 
    const result = await TextRecognition.recognize(photoPath); 
 
    if (result.text.length === 0) { 
      Alert.alert('Помилка', 'Текст на зображенні не знайдено'); 
      return; 
    } 
 
    // 2. Передача тексту в сервіс ШІ для структурування 
    const response = await 
AIService.recognizeProductsFromReceipt(result.text); 
 
    if (!response.success || response.productsToInsert.length === 0) { 
      Alert.alert('Помилка', 'Не вдалося розпізнати продукти'); 
      return; 
    } 
    // 3. Очищення попередніх даних та запис нових у тимчасовий контейнер 
чернеток 
    useDraftStore.getState().clear(); 
    useDraftStore.getState().setItems(response.productsToInsert, 
'storage'); 
 
    // 4. Перехід на екран перевірки та редагування розпізнаних товарів 
    navigation.replace('PreviewProducts'); 
  } catch (error) { 
    console.log(error); 
  } finally { 
    setIsLoading(false); 
  } 
} 
// ... інший код компонента екрана ... 
 
Сам сервіс AIService, який приймає цей текст, виконує мережевий запит до 
хмарної функції. Сервер аналізує рядки, структурує їх та повертає готові об'єкти. 
Після отримання відповіді метод динамічно обробляє дані, зокрема автоматично 
вираховує точну календарну дату закінчення терміну придатності на основі 
отриманої кількості днів (лістинг 3.3). 
 
Лістинг 3.3 – Фрагмент методу отримання структурованих даних 
const AIService = { 
  // ... інший код модуля ... 
  async recognizeProductsFromReceipt(receiptText: string) { 
    try { 
      // Відправка сирого тексту на хмарну функцію сервера 
      const { data, error } = await supabase.functions.invoke( 
62 
ЧДТУ.262251.013 ПЗ 
        'recognize-receipt', { body: { receiptText } } 
      ); 
 
      if (error || !data?.products) { 
         throw new Error('Продукти не знайдено'); 
      } 
 
      // Обробка отриманих даних та підготовка об'єктів для інтерфейсу 
      const productsToInsert = data.products.map(item => { 
        // Динамічний розрахунок дати на основі прогнозу алгоритму 
        const expirationDate = item.estimated_shelf_life_days 
          ? dayjs().add(item.estimated_shelf_life_days, 
'day').toISOString() 
          : null; 
        return createProductDraft({ expiration_date: expirationDate, 
...item }); 
      }); 
 
      return { success: true, productsToInsert }; 
    } catch (err) { 
      return { success: false, productsToInsert: [] }; 
    } 
  }, 
  // ... інші методи для генерації та імпорту рецептів ... 
} 
 
Модуль взаємодії з апаратною частиною смартфона. Для повноцінної 
роботи мобільного застосунку необхідна тісна інтеграція з фізичними 
компонентами пристрою, зокрема з камерою. Цей зв'язок реалізовано за 
допомогою бібліотеки React Native Vision Camera [23]. Щоб код залишався 
чистим та легко масштабувався, логіку роботи з камерою та її візуальне 
відображення розділено на дві окремі сутності. 
Керування станом винесено у спеціальну функцію-обгортку useCamera. 
Вона відповідає за запит дозволів у користувача на використання камери, вибір 
правильного об'єктива пристрою, керування ліхтариком та безпечну активацію 
або деактивацію відеопотоку. Це дозволяє легко використовувати камеру на 
різних екранах програми, не дублюючи один і той самий код (лістинг 3.4). 
 
Лістинг 3.4 – Фрагмент функції керування станом камери 
// ... інший код файлу ... 
 
export const useCamera = (position: CameraPosition) => { 
  const cameraRef = useRef<Camera | null>(null); 
  const device = useCameraDevice(position); 
  const [isActive, setIsActive] = useState(false); 
  const [torchOn, setTorchOn] = useState(false); 
63 
ЧДТУ.262251.013 ПЗ 
 
  // Метод для безпечного запуску камери із перевіркою дозволів 
  const presentCamera = async () => { 
    const status = await Camera.requestCameraPermission(); 
     
    if (status === 'denied') { 
      Alert.alert('Помилка', 'Необхідний доступ до камери для    
сканування.'); 
      return; 
    } 
    setIsActive(true); 
  }; 
 
  const toggleTorch = () => { 
    device?.hasTorch && setTorchOn(prev => !prev); 
  }; 
 
  return { cameraRef, device, isActive, torchOn, presentCamera, 
toggleTorch }; 
} 
 
За візуальне відображення відеопотоку відповідає компонент CameraFrame. 
Він приймає готові дані та методи від попередньої функції і будує інтерфейс 
сканера. Компонент виводить зображення з об'єктива на весь екран, а поверх 
нього розміщує зручні кнопки керування спалахом, створення знімка, вибору 
фотографії з галереї та закриття вікна сканера. Такий підхід гарантує гнучкість 
налаштування інтерфейсу (лістинг 3.5). 
 
Лістинг 3.5 – Фрагмент компонента візуального відображення камери 
// ... інший код компонента ... 
 
const CameraFrame = ({ device, isActive, torchOn, toggleTorch, 
onTakePhoto, ...props }) => { 
 
  return ( 
    <View style={styles.cameraFrame}> 
      {/* Відображення апаратного відеопотоку */} 
      {device && isActive && ( 
        <Camera 
          style={StyleSheet.absoluteFill} 
          device={device} 
          torch={torchOn ? 'on' : 'off'} 
          isActive={isActive} 
          {...props} 
        /> 
      )} 
 
      {/* Накладання кнопок керування поверх камери */} 
      <View style={styles.footer}> 
        {toggleTorch && ( 
           <SelectableContentIconButton onPress={toggleTorch} 
selected={torchOn} iconName="flashlight" /> 
64 
ЧДТУ.262251.013 ПЗ 
        )} 
        {onTakePhoto && ( 
           <SelectableContentIconButton onPress={onTakePhoto} 
iconName="camera" selected /> 
        )} 
      </View> 
    </View> 
  ); 
} 
 
Коли камера успішно фіксує штрихкод, викликається логіка його обробки. 
Алгоритм сканування побудований за дворівневою системою для економії 
інтернет-трафіку та прискорення роботи. Програма спочатку робить запит до 
локальної таблиці кешу. Якщо товар сканується не вперше, його дані миттєво 
завантажуються з пам'яті телефону. Лише коли код є абсолютно новим для 
системи, програма виконує мережевий запит до відкритої глобальної бази даних 
OpenFoodFacts API [24], розбирає отриманий результат і автоматично заповнює 
форму створення продукту (лістинг 3.6). 
 
Лістинг 3.6 – Фрагмент дворівневого алгоритму обробки штрихкоду 
// ... інший код файлу ... 
 
const processScannedBarcode = async (db: DatabaseService, code: string) 
=> { 
  try { 
    // 1. Пошук товару в локальній таблиці кешу на пристрої 
    const knownItem = await db.getProductInfoByCode(code); 
    if (knownItem) { 
      return { isKnown: true, product: knownItem }; 
    } 
 
    // 2. У разі відсутності виконується звернення до глобальної бази 
    const response = await 
fetch(`https://world.openfoodfacts.org/api/v2/product/${code}`); 
    const data = await response.json(); 
     
    // 3. Форматування отриманої відповіді для заповнення полів форми 
    if (data.status === 1 && data.product) { 
      return { 
        isKnown: false, 
        product: { 
          id: code, 
          name: data.product.product_name, 
          category: 'other', 
          quantity: data.product.quantity || 1, 
        } 
      }; 
    } 
    return null; 
65 
ЧДТУ.262251.013 ПЗ 
 
  } catch (error) {  
    return null;  
  } 
} 
 
// ... інший код файлу ... 
 
3.2. Тестування системи 
3.2.1. Модульне тестування 
Модульне тестування – це процес перевірки ізольованих програмних 
процедур, методів та структур даних, що входять до складу загальної архітектури 
системи. Цей етап проводився із застосуванням середовища автоматизованого 
тестування Jest [25]. Такий підхід дозволив переконатися, що внутрішня логіка 
управління станом працює коректно без прив'язки до локальної бази даних чи 
візуального інтерфейсу. 
Одним із об'єктів для написання модульних тестів виступив контейнер 
тимчасових чернеток. Він відіграє важливу роль у системі, він тимчасово зберігає 
список продуктів, дозволяючи людині відредагувати їх перед фінальним записом 
у фізичну пам'ять смартфона. 
Цей контейнер містить набір методів для повного циклу управління 
інформацією: ініціалізація списку, додавання одного товару, його зміна, 
видалення та повне очищення стану. Найбільш складною є функція оновлення, 
оскільки вона містить вбудовану бізнес-логіку контролю цілісності: продукт не 
може одночасно знаходитися і в домашньому сховищі, і в списку майбутніх 
покупок. Фрагмент розробленого тестового сценарію для перевірки цих умов 
наведено у лістингу 3.7. 
 
Лістинг 3.7 – Фрагмент модульних тестів методів контейнера тимчасових 
чернеток 
import { useDraftStore } from '@/stores/useDraftStore'; 
 
describe('Draft Store (useDraftStore)', () => { 
66 
ЧДТУ.262251.013 ПЗ 
  beforeEach(() => { 
    useDraftStore.getState().clear(); // Скидання стану перед кожним 
тестом 
  }); 
 
  it('Scenario 1: should initialize a new array of drafts', () => { 
    const initialItems = [ 
      { id: '1', name: 'Apple' },  
      { id: '2', name: 'Pear' } 
    ]; 
    useDraftStore.getState().setItems(initialItems, 'storage'); 
    const state = useDraftStore.getState(); 
     
    expect(state.items.length).toBe(2); 
    expect(state.mode).toBe('storage'); 
  }); 
 
  it('Scenario 2: should successfully add a new product to the list', () 
=> { 
    const newItem = { id: '3', name: 'Fresh Milk' }; 
    useDraftStore.getState().pushItem(newItem);  
    const items = useDraftStore.getState().items; 
 
    expect(items.length).toBe(1); 
    expect(items[0].name).toBe('Fresh Milk'); 
  }); 
 
 // ... інші тести ... 
 
  it('Scenario 5: should reset shopping list id when moving to storage', 
() => { 
   // Імітація товару, який знаходиться у списку покупок 
    useDraftStore.getState().pushItem({  
      id: '1',  
      name: 'Meat',  
      shopping_list_id: 'list-123'  
    }); 
 
    // Оновлення ідентифікатора сховища 
    useDraftStore.getState().updateItem(0, { storage_id: 'storage-456' 
}); 
 
    const updatedItem = useDraftStore.getState().items[0]; 
 
    expect(updatedItem.storage_id).toBe('storage-456'); 
    expect(updatedItem.shopping_list_id).toBeNull(); 
  }); 
 
  // ... інші тести ... 
}); 
 
Для забезпечення високого рівня покриття коду автоматизованими тестами 
було перевірено функціонування більшості модулів управління станом, зокрема 
контейнерів для обліку продуктів, управління сховищами та списками покупок, а 
також параметри генерації рецептів. Загальні результати перевірки працездатності 
цих компонентів наведено у таблиці 3.8. 
67 
ЧДТУ.262251.013 ПЗ 
Таблиця 3.8 
Сценарії та результати модульного тестування контейнера чернеток 
Назва тесту Модуль Очікуваний результат Результат 
Управління useDraftStore Зберігання тимчасових товарів, Успішно 
чернетками редагування та скидання конфліктуючих 
ідентифікаторів сховища або списку 
покупок. 
Управління useStorageStore Завантаження масиву сховищ із Успішно 
сховищами локальної бази даних, їх пошук за 
ідентифікатором та очищення стану. 
Управління useProductStore Завантаження продуктів, їх групування Успішно 
продуктами за сховищами чи списками покупок та 
фільтрація прострочених товарів. 
Управління useShoppingList Завантаження переліку списків покупок Успішно 
списками sStore із локальної бази, пошук за 
покупок ідентифікатором. 
Управління useRecipesStore Завантаження колекцій та рецептів, Успішно 
рецептами групування страв за відповідними 
колекціями та отримання масиву 
улюблених рецептів. 
Параметри useRecipeGeneraДодавання, видалення та збереження Успішно 
генератора torStore обраних інгредієнтів, дієт, алергій та 
страв часу для подальшої генерації рецепта. 
Управління usePreferencesSt Завантаження з бази та збереження в Успішно 
вподобання ore пам'яті списків дієт, алергій та 
ми улюблених кухонь користувача. 
Управління useGlobalStore Збереження та зміна глобальних Успішно 
налаштуван параметрів застосунку: теми, мови та 
нями часу надсилання сповіщень. 
 
68 
ЧДТУ.262251.013 ПЗ 
3.2.2. Інтеграційне тестування 
Інтеграційне тестування проводиться для перевірки спільної роботи 
окремих модулів програми та передує тестуванню всієї системи як єдиного 
цілого. Головною метою цього етапу є виявлення помилок у взаємодії між 
різними підсистемами, перевірка правильності передачі даних через інтерфейси 
зв'язку та підтвердження їхньої сумісності. 
Враховуючи багаторівневу архітектуру програми, яка об'єднує локальну 
базу даних, апаратні модулі смартфона та хмарні технології, інтеграційне 
тестування фокусувалося на перевірці повного циклу обміну даними між цими 
компонентами. Основними об'єктами для перевірки стали зв'язки між сервісом 
бази даних (DatabaseService) та контейнерами стану (Zustand), а також взаємодія 
модуля камери із сервісом інтелектуальної обробки та зовнішніми інтерфейсами. 
Перший ключовий інтеграційний вузол – це синхронізація фізичної пам'яті з 
візуальним інтерфейсом. Процес тестування полягав у перевірці того, чи дійсно 
після успішного виконання SQL-запиту на додавання або видалення запису в 
таблиці SQLite система коректно викликає тригер оновлення і чи потрапляють ці 
зміни до відповідного контейнера стану. 
Другий важливий вузол – це ланцюжок розпізнавання штрихкодів. Він 
охоплює роботу камери (апаратний рівень), звернення до локальної таблиці 
(рівень бази даних) та відправку мережевого запиту до відкритої бази 
OpenFoodFacts (рівень мережі). Тестування цього процесу вимагає підтвердження 
того, що дані безпомилково передаються від одного компонента до іншого без 
втрати контексту та зависання програми в очікуванні відповіді. 
Тестування цих інтеграційних зв'язків здійснювалося ручним методом на 
симуляторах мобільних пристроїв та фізичному смартфоні за допомогою 
спеціально підготовлених сценаріїв. Результати перевірки взаємодії модулів 
наведено у таблиці 3.9. 
 
Таблиця 3.9 
Сценарії та результати інтеграційного тестування 
69 
ЧДТУ.262251.013 ПЗ 
Модулі, що Опис сценарію Очікуваний результат Статус 
взаємодіють перевірки 
Візуальна Створення нового Дані успішно записуються в Успішно 
форма → База сховища через форму SQLite, після чого тригер 
даних → та перевірка його оновлює список в  пам'яті, і 
Контейнер миттєвої появи у сховище з'являється в 
стану списку на головному інтерфейсі без 
екрані. перезавантаження програми. 
База даних → Видалення існуючого Сховище отримує статус Успішно 
Контейнер сховища та перевірка видаленого в базі, стан 
стану → відсутності пов'язаних синхронізується, інтерфейс 
Візуальний з ним продуктів у приховує сховище та всі 
інтерфейс загальному списку. прив'язані до нього товари. 
Камера → Зчитування тексту з Фотографія коректно Успішно 
Модуль ШІ → фотографії чека, обробляється, сервер повертає 
Контейнер передача на сервер структуровані продукти, які 
чернеток Supabase та запис відразу з'являються на екрані 
отриманого JSON- попереднього перегляду для 
масиву у тимчасовий перевірки. 
стан. 
Камера → Сканування відомого Модуль камери передає код, Успішно 
Локальний кеш штрихкоду, який вже система знаходить його в 
→ Форма присутній у локальній локальному кеші та миттєво 
товару таблиці barcodes. заповнює поля форми без 
звернення до інтернету. 
Сканування абсолютно Система не знаходить код 
Камера → 
нового штрихкоду, локально, робить запит до API, 
Зовнішнє API 
відсутнього в отримує назву товару, заповнює Успішно 
→ Форма 
локальній базі форму та зберігає результат для 
товару 
смартфона. майбутнього використання. 
 
70 
ЧДТУ.262251.013 ПЗ 
Проведене інтеграційне тестування підтвердило, що всі розроблені 
підсистеми працюють злагоджено. Передача даних між локальною базою, 
пам'яттю та хмарними сервісами відбувається коректно, без втрат інформації та 
блокування графічного інтерфейсу користувача. 
3.2.3. Системне тестування 
Системне тестування призначене для перевірки програмного продукту в 
цілому. На цьому етапі програма розглядається як єдиний механізм, а тестування 
імітує реальні дії кінцевого користувача. Головна мета полягає у підтвердженні 
того, що застосунок повністю відповідає початковим вимогам, логічно 
побудований та здатний стабільно працювати у повсякденних умовах. 
Оскільки ключовою особливістю розробленої системи є здатність 
працювати без доступу до мережі інтернет, особлива увага приділялася 
тестуванню в повністю автономному режимі. Також перевірялася стабільність 
роботи програми при активному використанні роботи камери. 
Системне тестування проводилося ручним методом на реальному 
фізичному пристрої, щоб максимально наблизити умови до реального життєвого 
використання. Для перевірки було розроблено набір комплексних сценаріїв від 
моменту запуску програми до отримання кінцевого результату.  Результати 
системного тестування наведено у таблиці 3.10. 
 
Таблиця 3.10 
Сценарії та результати системного тестування застосунку 
Назва 
Опис дій (кроки) Очікуваний результат Статус 
сценарію 
Робота без 1. Вимкнути інтернет. Програма працює без Успішно 
інтернету 2. Відкрити програму. помилок. Нов сховище та 
3. Створити сховище. продукт успішно зберігаються 
4. Додати продукт вручну локально і залишаються 
та перезапустити додаток. локально після перезапуску. 
71 
ЧДТУ.262251.013 ПЗ 
Продовження таблиці 3.10 
Назва 
Опис дій (кроки) Очікуваний результат Статус 
сценарію 
Повний 1. Відкрити сканер чеків. Фотографія успішно Успішно 
цикл 2. Зробити фотографію. розпізнається, система 
додавання 3. Дочекатися розумної пропонує правильний список 
товарів обробки. товарів. Після підтвердження 
через чек 4. Перевірити товари на всі продукти з'являються у 
екрані чернеток. вибраному домашньому 
5. Підтвердити збереження. сховищі. 
Повний 1. Відкрити сканер Камера швидко зчитує код, Успішно 
цикл штрихкодів. система знаходить товар у 
сканування 2. Навести камеру на відкритій базі, автоматично 
нового реальний товар. заповнює форму (назва, 
штрихкоду 3. Дочекатися результату з категорія) і дозволяє безпечно 
глобальної бази. зберегти запис. 
4. Зберегти товар. 
Розумна 1. Відкрити розділ рецептів. Система успішно формує Успішно 
генерація 2. Натиснути кнопку запит, враховує обрані 
страви з генерації. інгредієнти та повертає готову 
наявних 3. Вибрати кілька продуктів картку рецепту з покроковою 
запасів зі свого сховища. інструкцією та часом 
4. Запустити процес приготування. 
створення страви. 
Керування 1. Створити список покупок Смуга прогресу збільшується. Успішно 
списком і додати 3 товари. Куплені товари візуально 
покупок у 2. У магазині позначати закреслюються. Коли всі 
магазині товари як куплені товари зібрані, список 
натисканням на них. отримує статус завершеного. 
 
72 
ЧДТУ.262251.013 ПЗ 
Проведене системне тестування довело, що розроблений застосунок 
повністю виконує поставлені перед ним завдання. Архітектура програми 
бездоганно справляється з автономною роботою, а всі складні інструменти 
(камера, сканери, генерація рецептів) функціонують стабільно та не викликають 
критичних збоїв чи раптового закриття програми під час використання. 
3.2.4. Приймальне тестування 
Приймальне тестування є фінальним етапом перевірки програмного 
забезпечення перед його введенням у реальну експлуатацію. Воно проводиться з 
позиції кінцевого користувача з метою підтвердження того, що розроблена 
система повністю вирішує поставлене завдання, відповідає початковим вимогам 
та є зручною у повсякденному використанні. 
Для проведення цього етапу клієнтську частину мобільного застосунку було 
скомпільовано у виконуваний файл та встановлено на реальний фізичний 
смартфон. Після цього програму було передано тестовому користувачеві, який не 
брав участі у процесі написання коду та проектування архітектури. Людина мала 
самостійно виконати кілька типових життєвих сценаріїв. Результати виконання 
сценаріїв приймального тестування наведено у таблиці 3.11. 
 
Таблиця 3.11 
Сценарії та результати приймального тестування застосунку 
Назва 
сценарію Опис дій (кроки) Очікуваний результат Статус 
Початковий 1. Створити нове Сховище успішно Успішно 
облік сховище. створено. Продукти 
домашніх 2. Додати наявні продукти відображаються у списку 
запасів вручну, вказавши назву, з термінами придатності. 
кількість та термін Сканер штрихкодів 
придатності. швидко знаходить товар 
3. Використати сканер у довіднику. 
штрихкодів. 
73 
ЧДТУ.262251.013 ПЗ 
Продовження таблиці 3.11 
Назва 
сценарію Опис дій (кроки) Очікуваний результат Статус 
Планування 1. Відключити доступ до Застосунок працює без Успішно 
покупок в мережі на пристрої. затримок в офлайн-
офлайн- 2. Створити новий список режимі. Товари 
режимі покупок. додаються та 
3. Додати товари до позначаються 
списку та відмітити купленими. Після появи 
частину як куплені. мережі дані автоматично 
4. Увімкнути Інтернет для синхронізуються з 
перевірки синхронізації. хмарною базою. 
Автоматизація 1. Перейти в меню ШІ-модуль коректно Успішно 
обліку швидкого додавання. розпізнає текст на фото, 
2. Зробити фотографію вилучає назви продуктів 
реального паперового та їхню кількість. 
чека з магазину. Структурований список 
3. Відправити фото на відповідає чеку та 
обробку ШІ-модулю. успішно зберігається до 
4. Перевірити обраного сховища. 
згенерований список та 
зберегти у сховище. 
Генерація 1. Відкрити розділ Система генерує Успішно 
рецепта з генерації рецептів. кулінарний рецепт на 
наявних 2. Обрати продукти, що основі обраних 
продуктів залишилися в сховищі. залишків. Рецепт містить 
3. Запустити генерацію кроки приготування та 
страви через ШІ. успішно зберігається у 
4. Зберегти рецепт до вибраній колекції 
власної колекції. користувача. 
 
74 
ЧДТУ.262251.013 ПЗ 
Під час приймального тестування, окрім функціональних можливостей 
(таблиця 3.11), оцінювалися також ключові нефункціональні параметри системи: 
‒ інтуїтивність інтерфейсу: наскільки легко людина орієнтується в 
застосунку, чи зрозуміло призначення кнопок та чи не викликає процес 
додавання товарів труднощів; 
‒ швидкодія: суб'єктивна оцінка плавності анімацій та швидкості переходу 
між екранами. Завдяки ізольованій архітектурі контейнерів стану 
користувач не відчував жодних затримок; 
‒ практична цінність інструментів: чи дійсно сканер штрихкодів та розумна 
обробка чеків прискорюють процес заповнення бази даних у порівнянні з 
ручним введенням; 
‒ автономність: можливість комфортно користуватися системою у 
магазині, де може бути відсутнє або нестабільне підключення до мережі 
інтернет. 
За результатами приймального тестування було підтверджено, що інтерфейс 
програми не перевантажений зайвими елементами, а логіка навігації є логічною та 
інтуїтивно зрозумілою. Система повністю задовольняє потреби в контролі 
домашніх запасів, виконує всі функції та відповідає на вимоги, які ставилися у 
завданні на кваліфікаційну роботу. 
3.3. Приклади впровадженого програмного комплексу 
Скріншоти інтерфейсу та інструкція користувача. Результатом 
виконання кваліфікаційної роботи є повністю готовий мобільний застосунок 
PantryControl. Програмний продукт скомпільовано та розгорнуто на фізичному 
мобільному пристрої. Послідовність кроків користувача реалізована через 
інтуїтивну навігацію. Головне вікно системи (рисунок 3.12) об'єднує перелік 
місць зберігання, активні списки покупок та інструменти генерації рецептів. Для 
швидкого поповнення запасів розроблено окреме меню действий (рисунок 3.13), 
де користувач може відсканувати штрихкод або завантажити чек для 
розпізнавання. 
75 
ЧДТУ.262251.013 ПЗ 
 
Рисунок 3.12 – Інтерфейс головного екрана 
 
 
Рисунок 3.13 – Інтерфейс вікна швидких дій для додавання продуктів 
76 
ЧДТУ.262251.013 ПЗ 
У розділі колекцій (рисунок 3.14) реалізовано збереження кулінарних 
інструкцій та генерацію нових страв за допомогою штучного інтелекту. 
Детальний опис кожного кроку взаємодії із системою та покрокова інструкція 
користувача наведені в Додатку В. 
 
 
Рисунок 3.14 – Інтерфейс екрана колекцій рецептів 
 
Гнучке програмування. Процес створення системи базувався на 
принципах гнучкої методології розробки Agile [26]. Для мінімізації архітектурних 
ризиків, пов'язаних із впровадженням підходу Local-First, розробка була поділена 
на серію коротких циклів (спринтів). Перші ітерації включали проектування 
локальної бази даних SQLite та розробку базового інтерфейсу, наступні – 
підключення хмарної синхронізації, а фінальні спринти були присвячені 
інтеграції модулів штучного інтелекту для обробки чеків. Такий підхід дозволив 
постійно тестувати стабільність системи та вносити зміни без порушення 
цілісності коду.  
Хмарні технології. У проекті активно застосовано методику використання 
77 
ЧДТУ.262251.013 ПЗ 
хмарних технологій [27]. Замість розробки класичного сервера, серверну 
інфраструктуру делеговано хмарній платформі Supabase. Відбувся чіткий 
розподіл ролей: локальна база на пристрої забезпечує миттєвий доступ до даних, а 
хмарна PostgreSQL-база виступає єдиним джерелом правди для резервного 
копіювання. Крім того, для ресурсомістких завдань (взаємодія з ШІ) 
використовуються хмарні функції (Edge Functions), що знімає навантаження з 
мобільного клієнта. 
Розробка програмного сервісу. Розробка комплексу відбувалася за 
класичною послідовністю створення програмних сервісів:  
‒ аналіз конкурентів: досліджено аналоги та виявлено їхні недоліки; 
‒ проектування: створено UML-моделі та специфікацію архітектури Local-
First;  
‒ R&D та БД: досліджено рушій Supastash для фонової синхронізації та 
спроєктовано структуру реляційних баз даних;  
‒ front-end та back-end розробка: клієнтську частину реалізовано на React 
Native з використанням TypeScript. Серверну логіку, налаштування 
політики безпеки (RLS) та API інтегровано через Supabase;  
‒ інтеграція API: реалізовано підключення до відкритої бази OpenFoodFacts 
та інтеграцію LLM-моделі через захищені хмарні виклики; 
‒ тестування та запуск: проведено модульне тестування, інтеграційне, 
системне та приймальне тестування, після чого додаток розгорнуто на 
фізичних пристроях. 
Серверне програмування та архітектура. Архітектура додатку 
побудована за модульним принципом, що чітко розділяє клієнтську та серверну 
частини. Клієнтська логіка базується на фреймворку React Native, де для 
надійного керування станом системи та швидкого відгуку інтерфейсу 
використовується бібліотека Zustand. Взаємодія з хмарною серверною частиною 
реалізована безпосередньо через офіційний клієнтський SDK платформи 
Supabase. Це забезпечує виконання безпечних асинхронних запитів, передачу 
78 
ЧДТУ.262251.013 ПЗ 
потоків даних та автентифікацію користувачів без необхідності написання та 
розгортання складної серверної архітектури. 
Основи інтеграції інформаційних потоків. Для побудови сучасного 
інформаційного сервісу та повноцінної інтеграції потоків даних у застосунку 
PantryControl реалізовано взаємодію з кількома ключовими сторонніми сервісами 
та API. Для автоматичного розпізнавання та категоризації продуктів за 
штрихкодами інтегровано відкриту базу даних OpenFoodFacts API, звідки система 
отримує структуровану інформацію про товари. Обробка текстової інформації та 
розпізнавання вмісту магазинних чеків побудована на базі інтеграції з API 
штучного інтелекту через захищені хмарні функції Supabase Edge Functions. Це 
дозволяє з неструктурованого тексту із чеку, вилучати продукти і отримувати дані 
у форматі JSON. 
Оцінка результатів та перспективи. Поставлені задачі вирішено в 
повному обсязі. Одержані результати підтверджують ефективність обраної 
архітектури. Порівняно з існуючими аналогами, PantryControl забезпечує 
автономну роботу без втрати функціональності та автоматизує введення даних 
завдяки ШІ. Характеристики швидкодії доводять надійність системи. Подальші 
дослідження та розробка мають бути спрямовані на розширення функціональних 
можливостей застосунку. Важливим кроком є впровадження модуля аналітики, 
який збиратиме статистику споживання їжі та наочно показуватиме динаміку 
витрат. Для підвищення зручності планується реалізувати функції голосового 
введення, що дозволить додавати продукти за допомогою голосових команд, 
розпізнавання та генерації продуктів безпосередньо за фотографією самих 
продуктів, а не лише за текстовою інформацією з чеків, планувальника 
харчування, який допомагатиме складати меню на тиждень та налаштування 
інтеграції зі сторонніми сервісами доставки або торгівельними мережами. Крім 
того, можна покращити логіку обліку за рахунок часткового списання, що 
дозволить користувачеві віднімати не всю одиницю продукту одразу, а фіксувати 
витрату його певної частини. Обсяг та якість розробленого функціоналу 
дозволяють успішно впроваджувати продукт у повсякденну експлуатацію. 
79 
ЧДТУ.262251.013 ПЗ 
ВИСНОВКИ ДО ТРЕТЬОГО РОЗДІЛУ 
У третьому розділі було детально розглянуто процес розробки та тестування 
мобільного застосунку. Обґрунтовано вибір технологічного стека: фреймворку 
React Native для кросплатформної розробки, локальної бази даних SQLite та 
контейнерів стану Zustand, що забезпечили швидку роботу програми в 
автономному режимі. 
Описано архітектуру програмних компонентів, зокрема реалізацію 
інтелектуального модуля для обробки фотографій чеків за допомогою хмарних 
функцій та механізм взаємодії з апаратною камерою смартфона. Наведено 
фрагменти коду, які демонструють логіку роботи ключових алгоритмів. 
Проведено комплексне тестування системи на чотирьох рівнях. Модульне 
тестування підтвердило правильність роботи окремих методів управління станом, 
а інтеграційне та системне довели надійність обміну даними між локальною 
пам'яттю та візуальним інтерфейсом. 
Приймальне тестування підтвердило, що створений програмний продукт 
повністю відповідає технічному завданню. Наведені приклади інтерфейсів 
демонструють, що додаток є зручним, функціональним і готовим до практичного 
впровадження у повсякденне життя. 
80 
ЧДТУ.262251.013 ПЗ 
ВИСНОВКИ 
Результатом роботи стало створення програмного забезпечення для 
автоматизації обліку домашніх продуктів та зниження кількості харчових 
відходів. Під час його розробки були повністю вирішені всі завдання, визначені 
на початку проєкту. 
Спочатку детально досліджено процес обліку продуктів у побуті та вивчено 
популярні програми-конкуренти. Це допомогло чітко побачити їхні головні 
недоліки, головні із них це обов'язковий доступ до інтернету та необхідність 
постійно вписувати всі покупки руками. На основі цього був складений точний 
перелік вимог до майбутньої системи. Щоб програма могла працювати без 
інтернету, обрано архітектурний підхід Local-First. Усю внутрішню структуру 
програми та логіку взаємодії її частин зображено за допомогою UML-діаграм. 
Для зберігання інформації створено реляційну структуру даних, яку 
розділено на два рівні: локальну базу SQLite прямо на пристрої та хмарну базу на 
платформі Supabase. Обмін даними між ними налаштовано за допомогою рушія 
синхронізації supastash. Візуальну частину програми написано на фреймворку 
React Native з використанням мови TypeScript. 
Щоб позбутися рутинного введення, реалізовано сканування чеків камерою 
смартфона. Текст із фотографії передається до Google Gemini через захищені 
функції Supabase Edge Functions, а штучний інтелект повертає вже готовий список 
товарів. Крім того реалізовано ключові функції програми: автоматичне 
відстеження термінів придатності, сімейні списки покупок і підбір рецептів із 
домашніх запасів з огляду на дієти та алергії. 
На фінальному етапі проведено комплексну перевірку системи. Модульні 
тести у середовищі Jest підтвердили правильність коду, а тестування на реальних 
смартфонах довело стабільність інтерфейсу та надійну роботу в офлайн-режимі. 
Отже, всі поставлені завдання успішно виконано. Результатом роботи є 
повністю функціональний та надійний програмний продукт, готовий до 
використання в реальних умовах. 
81 
ЧДТУ.262251.013 ПЗ 
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ 
1 UNEP Food Waste Index Report 2024 [Електронний ресурс] / United Nations 
Environment Programme. — 2024. — Режим доступу: 
https://www.unep.org/resources/publication/unep-food-waste-index-report-2024 
(дата звернення: 29.04.2026). 
2 NoWaste - Food Inventory [Електронний ресурс]. — Режим доступу: 
https://www.nowasteapp.com/ (дата звернення: 29.04.2026). 
3 Fridgely: AI Recipe Maker [Електронний ресурс] / App Store. — Режим 
доступу: https://apps.apple.com/app/fridgely-ai-recipe-maker/id6756694148 
(дата звернення: 29.04.2026). 
4 Samsung Food: Meal Planning & Recipes [Електронний ресурс]. — Режим 
доступу: https://samsungfood.com/ (дата звернення: 29.04.2026). 
5 Kleppmann M. Local-first software: you own your data, in spite of the cloud 
[Електронний ресурс] / [M. Kleppmann, A. Wiggins, P. van Hardenberg et al.] 
// Proceedings of the 2019 ACM SIGPLAN International Symposium. — 2019. 
— P. 154–178. — Режим доступу: https://doi.org/10.1145/3359591.3359737 
(дата звернення: 29.04.2026). 
6 OMG® Unified Modeling Language® (OMG UML®) Version 2.5.1 
[Електронний ресурс] / Object Management Group. — Режим доступу: 
https://www.omg.org/spec/UML/ (дата звернення: 29.04.2026). 
7  What is a Single Source of Truth (SSOT)? [Електронний ресурс] / MuleSoft. — 
Режим доступу: https://www.mulesoft.com/integration/what-is-single-source-of-
truth-ssot (дата звернення: 29.04.2026). 
8  What is Unified Modeling Language (UML)? [Електронний ресурс] / Visual 
Paradigm. — Режим доступу: https://www.visual-paradigm.com/guide/uml-
unified-modeling-language/what-is-uml/ (дата звернення: 29.04.2026). 
82 
ЧДТУ.262251.013 ПЗ 
9 ДСТУ ISO/IEC/IEEE 29148:2016 Інженерія систем і програмного 
забезпечення. Процеси життєвого циклу. Вимоги до інженерії 
(ISO/IEC/IEEE 29148:2011, IDT). – Київ: ДП «УкрНДНЦ», 2018. 
10  Functional vs Non Functional Requirements [Електронний ресурс] / 
GeeksforGeeks. — Режим доступу: https://www.geeksforgeeks.org/functional-
vs-non-functional-requirements/ (дата звернення: 29.04.2026). 
11 Zustand: Bear necessities for state management in React [Електронний ресурс]. 
— Режим доступу: https://zustand.docs.pmnd.rs/learn/getting-
started/introduction (дата звернення: 29.04.2026). 
12 React Native MMKV: The fastest key/value storage for React Native 
[Електронний ресурс]. — Режим доступу: https://github.com/mrousavy/react-
native-mmkv (дата звернення: 29.04.2026). 
13  React Native: A framework for building native applications using React 
[Електронний ресурс]. — Режим доступу: https://reactnative.dev/ (дата 
звернення: 29.04.2026). 
14 TypeScript: Typed JavaScript at Any Scale [Електронний ресурс]. — Режим 
доступу: https://www.typescriptlang.org/ (дата звернення: 29.04.2026). 
15 Supabase: The Open Source Firebase Alternative [Електронний ресурс]. — 
Режим доступу: https://supabase.com/ (дата звернення: 29.04.2026). 
16 Row Level Security [Електронний ресурс] / PostgreSQL Documentation. — 
Режим доступу: https://www.postgresql.org/docs/current/ddl-rowsecurity.html 
(дата звернення: 29.04.2026). 
17 SQLite: Small. Fast. Reliable. Choose any three [Електронний ресурс]. — 
Режим доступу: https://www.sqlite.org/ (дата звернення: 29.04.2026). 
18 PostgreSQL: The World's Most Advanced Open Source Relational Database 
[Електронний ресурс]. — Режим доступу: https://www.postgresql.org/ (дата 
звернення: 29.04.2026). 
83 
ЧДТУ.262251.013 ПЗ 
19 Supastash: Local-First sync engine [Електронний ресурс]. — Режим доступу: 
https://0xzekea.github.io/supastash/ (дата звернення: 29.04.2026). 
20 Expo PDF Text Extract [Електронний ресурс]. — Режим доступу: 
https://github.com/gr8pathik/expo-pdf-text-extract (дата звернення: 29.04.2026). 
21 Edge Functions [Електронний ресурс] / Supabase Docs. — Режим доступу: 
https://supabase.com/docs/guides/functions (дата звернення: 29.04.2026). 
22 Google Gemini API Documentation [Електронний ресурс]. — Режим доступу: 
https://ai.google.dev/docs (дата звернення: 29.04.2026). 
23 React Native Vision Camera [Електронний ресурс]. — Режим доступу: 
https://react-native-vision-camera.com/ (дата звернення: 29.04.2026). 
24 Open Food Facts: The free food products database [Електронний ресурс]. — 
Режим доступу: https://world.openfoodfacts.org/ (дата звернення: 29.04.2026). 
25 Jest: Delightful JavaScript Testing [Електронний ресурс]. — Режим доступу: 
https://jestjs.io/ (дата звернення: 29.04.2026). 
26 Manifesto for Agile Software Development [Електронний ресурс]. — Режим 
доступу: https://agilemanifesto.org/ (дата звернення: 29.04.2026). 
27 Mell P. The NIST Definition of Cloud Computing [Електронний ресурс] / P. 
Mell, T. Grance // National Institute of Standards and Technology. — 2011. — 
Режим доступу: https://nvlpubs.nist.gov/nistpubs/Legacy/SP 
/nistspecialpublication800-145.pdf (дата звернення: 29.04.2026). 
 
84 
ДОДАТОК А 
 
 ЗАТВЕРДЖЕНО: 
Зав. кафедрою ПЗАС, професор 
_________________ Голуб С.В. 
„____” ______________ 2026 р. 
 
 
 
 
«Мобільний застосунок для інтелектуального управління домашньою 
продуктовою коморою» 
 
 
 
Специфікація 
482.ЧДТУ 262251.013  
Листів 2 
 
 
 
 
 
Розробник ________________ Мельник В. А. 
Керівник ________________ Немченко В. В. 
 
 
2026 
 
 482.ЧДТУ 262251.013 2 
Позначення Найменування Примітка 
 Документація  
482.ЧДТУ 262251 12 01 Текст програми  
482.ЧДТУ 262251 34 01 Інструкція користувачеві  
482.ЧДТУ 262251 90 01 Графічні матеріали  
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
86 
 
ДОДАТОК Б 
 
 
  
 
 
 
 
 
 
«Мобільний застосунок для інтелектуального управління домашньою 
продуктовою коморою» 
 
 
 
Текст програми  
482.ЧДТУ 262251 12 01 
Листів 28 
 
 
 
 
 
Розробник ________________ Мельник В. А. 
 
 
 
2026 
 482.ЧДТУ 262251 12 01   2 
index.js. Головна точка входу мобільного застосунку. 
import './polyfill' 
 
import '@/localization/i18n' 
import { AppRegistry } from 'react-native' 
import App from './App' 
import { name as appName } from './app.json' 
import './datejsConfig' 
 
AppRegistry.registerComponent(appName, () => App)	
 
App.tsx. Основний компонент, який огортає програму провайдерами стану, 
авторизації та навігації. 
import { useGlobalStore } from '@/stores/store' 
import SplashScreenController from '@/components/common/SplashScreenController' 
import { closeAllSwipeables } from '@/components/common/swipeable/SwipeableItem' 
import { Colors, DarkTheme, LightTheme } from '@/constants/Colors' 
import { useColorScheme } from '@/hooks/useColorScheme' 
import RootNavigator from '@/navigation/RootNavigator' 
import AuthProvider from '@/providers/AuthProvider' 
import { setRootViewBackgroundColor } from '@pnthach95/react-native-root-view-
background' 
import { NavigationContainer } from '@react-navigation/native' 
import { useEffect } from 'react' 
import { Appearance } from 'react-native' 
import { GestureHandlerRootView } from 'react-native-gesture-handler' 
import { KeyboardProvider } from 'react-native-keyboard-controller' 
import { featureFlags } from 'react-native-screens' 
 
function App() { 
  const colorScheme = useColorScheme() 
  const theme = useGlobalStore(s => s.theme) 
 
  useEffect(() => { 
    setRootViewBackgroundColor(Colors[colorScheme].background) 
     
    Appearance.setColorScheme(theme === 'system' ? 'unspecified' : theme) 
  }, [colorScheme, theme]) 
 
  return ( 
    <GestureHandlerRootView> 
      <NavigationContainer 
        onStateChange={closeAllSwipeables} 
        theme={colorScheme === 'dark' ? DarkTheme : LightTheme} 
      > 
        <KeyboardProvider statusBarTranslucent={true}> 
          <AuthProvider> 
            <SplashScreenController /> 
            <RootNavigator /> 
          </AuthProvider> 
        </KeyboardProvider> 
      </NavigationContainer> 
    </GestureHandlerRootView> 
  ) 
} 
 
export default App	
 
88 
 482.ЧДТУ 262251 12 01   3 
AuthProvider.tsx. Провайдер управління сесією користувача через Supabase. 
import { supabase } from '@/lib/supabase'  
import { activeLocalDb } from '@/lib/supastash' 
import { useProductsStore } from '@/stores/productStore' 
import { useRecipesStore } from '@/stores/recipesStore' 
import { useShoppingListsStore } from '@/stores/shoppingListStore' 
import { useStoragesStore } from '@/stores/storageStore' 
import { useGlobalStore } from '@/stores/store' 
import { getDatabaseName } from '@/utils/databaseUtils' 
import { Session } from '@supabase/supabase-js' 
import React, { useEffect, useState } from 'react' 
import RNRestart from 'react-native-restart' 
type AuthContextType = { 
  session: Session | null 
  isLoading: boolean 
  signOut: () => Promise<void> 
  isLoggedIn: boolean 
  isPremium: boolean 
} 
 
const AuthContext = React.createContext<AuthContextType>({ 
  session: null, 
  isLoading: true, 
  signOut: async () => {}, 
  isLoggedIn: false, 
  isPremium: false, 
}) 
 
export const useAuth = () => React.useContext(AuthContext) 
 
const AuthProvider = ({ children }: { children: React.ReactNode }) => { 
  const [session, setSession] = useState<Session | null>(null) 
  const [isLoading, setIsLoading] = useState(true) 
 
  useEffect(() => { 
    supabase.auth.getSession().then(({ data: { session: currentSession } }) => { 
      setSession(currentSession) 
      setIsLoading(false) 
    }) 
   const { 
      data: { subscription }, 
    } = supabase.auth.onAuthStateChange((_event, newSession) => { 
      setSession(newSession) 
      setIsLoading(false) 
    }) 
    return () => { 
      subscription.unsubscribe() 
    } 
  }, []) 
 
  const signOut = async () => { 
    try { 
      setIsLoading(true) 
      const userId = session?.user?.id 
 
      if (userId) { 
        useProductsStore.getState().clearStore() 
        useStoragesStore.getState().clearStore() 
        useShoppingListsStore.getState().clearStore() 
        useRecipesStore.getState().clearStore() 
        useGlobalStore.getState().clearStore() 
        if (activeLocalDb) { 
          const dbName = getDatabaseName(userId) 
89 
482.ЧДТУ 262251 12 01 4 
try { 
activeLocalDb.close() 
activeLocalDb.delete() 
console.log(`[Logout] Local DB deleted: ${dbName} `) 
} catch (dbError) { 
console.warn('[Logout] Local DB delete error:', dbError) 
} 
} 
} 
await supabase.auth.signOut({ scope: 'local' }) 
RNRestart.restart() 
    } catch (error) { 
console.error('[Logout] Critical error:', error) 
    } finally { 
setIsLoading(false) 
    } 
  } 
  const isLoggedIn = !!session 
  return ( 
    <AuthContext.Provider 
value={{ 
session, 
isLoading, 
signOut, 
isLoggedIn, 
isPremium: session?.user?.app_metadata?.is_premium === true, 
}} 
    > 
{children} 
    </AuthContext.Provider> 
  ) 
} 
export default AuthProvider 
DatabaseProvider.tsx. Провайдер ініціалізації локальної бази SQLite та системи 
синхронізації Supastash. 
import { initSupastash } from '@/lib/supastash' 
import React from 'react' 
import { useAuth } from './AuthProvider' 
import DatabaseSyncController from './DatabaseController' 
const DatabaseProvider = ({ children }: { children: React.ReactNode }) => { 
  const [isConfigured, setIsConfigured] = React.useState(false) 
  const { session } = useAuth() 
  const userId = session?.user.id 
  React.useEffect(() => { 
    if (!userId) return 
    initSupastash(userId) 
    setIsConfigured(true) 
  }, [userId]) 
  if (!isConfigured) { 
    return null 
  } 
  return <DatabaseSyncController>{children}</DatabaseSyncController> 
} 
export default DatabaseProvider 
90 
 482.ЧДТУ 262251 12 01   5 
AppNavigator.tsx. Маршрутизація програми та поділ на екрани. 
import { useColorScheme } from '@/hooks/useColorScheme' 
import { useHydrateStores } from '@/hooks/useHydrateStores' 
import { useNotificationManager } from '@/hooks/useNotificationManager' 
import CollectionScreen from '@/screens/Collection/CollectionScreen' 
import CollectionFormScreen from '@/screens/Collection/CollectionsFormScreen' 
import ManagePreferencesScreen from '@/screens/ManagePreferencesScreen' 
import PreviewProductsScreen from '@/screens/Product/PreviewProductsScreen' 
import ProductListPickerScreen from '@/screens/Product/ProductListPickerScreen' 
import ShoppingListProductFormScreen from 
'@/screens/Product/ShoppingListProductFormScreen' 
import StorageProductFormScreen from '@/screens/Product/StorageProductFormScreen' 
import ReceiptScannerScreen from '@/screens/ReceiptScannerScreen' 
import GenerateRecipeScreen from '@/screens/Recipe/GenerateRecipeScreen' 
import HowToImportRecipeScreen from '@/screens/Recipe/HowToImportRecipeScreen' 
import ImportRecipeScreen from '@/screens/Recipe/ImportRecipesScreen' 
import ProcessingRecipeScreen from '@/screens/Recipe/ProcessingRecipeScreen' 
import RecipeDetailScreen from '@/screens/Recipe/RecipeDetailScreen' 
import RecipeDraftScreen from '@/screens/Recipe/RecipeDraftScreen' 
import RecipeFormScreen from '@/screens/Recipe/RecipeFormScreen' 
import SettingsScreen from '@/screens/SettingsScreen' 
import ShoppingListFormScreen from '@/screens/ShoppingList/ShoppingListFormScreen' 
import ShoppingListShareScreen from 
'@/screens/ShoppingList/ShoppingListShareScreen' 
import ShoppingListsScreen from '@/screens/ShoppingList/ShoppingListsScreen' 
import SingleShoppingListScreen from 
'@/screens/ShoppingList/SingleShoppingListScreen' 
import StorageFormScreen from '@/screens/Storage/StorageFormScreen' 
import StorageScreen from '@/screens/Storage/StorageScreen' 
import { AppStackParamList, RootTabParamList } from '@/types/navigation' 
import { screenOptionsWithLargeTitle } from '@/utils/navigationUtils' 
import { getFocusedRouteNameFromRoute } from '@react-navigation/native' 
import { createNativeStackNavigator } from '@react-navigation/native-stack' 
import React from 'react' 
import { useTranslation } from 'react-i18next' 
import TabNavigator from './TabNavigator' 
const AppStack = createNativeStackNavigator<AppStackParamList>() 
const AppNavigator = () => { 
  const theme = useColorScheme() 
  const { t } = useTranslation() 
  useHydrateStores() 
  useNotificationManager() 
  return ( 
    <AppStack.Navigator 
      screenOptions={{ 
        headerBackButtonDisplayMode: 'minimal', 
      }} 
    > 
      <AppStack.Screen 
        name="Tab" 
        component={TabNavigator} 
        options={({ route }) => { 
          const routeName = 
            (getFocusedRouteNameFromRoute(route) as keyof RootTabParamList) ?? 
            'Home' 
          return { 
            headerShown: false, 
            title: t(`navigation.tabs.${routeName}`), 
          } 
        }} 
      /> 
      <AppStack.Screen name="Settings" component={SettingsScreen} /> 
      <AppStack.Screen name="StorageForm" component={StorageFormScreen} /> 
91 
 482.ЧДТУ 262251 12 01   6 
      <AppStack.Screen name="Storage" component={StorageScreen} /> 
      <AppStack.Screen 
        name="ShoppingListForm" 
        component={ShoppingListFormScreen} 
      /> 
      <AppStack.Screen 
        name="ShoppingLists" 
        component={ShoppingListsScreen} 
        options={screenOptionsWithLargeTitle(theme)} 
      /> 
      <AppStack.Screen 
        name="SingleShoppingList" 
        component={SingleShoppingListScreen} 
        options={screenOptionsWithLargeTitle(theme)} 
      /> 
      <AppStack.Screen 
        name="StorageProductForm" 
        component={StorageProductFormScreen} 
      /> 
      <AppStack.Screen 
        name="ShoppingListProductForm" 
        component={ShoppingListProductFormScreen} 
        options={{ 
          presentation: 'pageSheet', 
        }} 
      /> 
      <AppStack.Screen 
        name="ShoppingListShare" 
        component={ShoppingListShareScreen} 
        options={{ 
          presentation: 'formSheet', 
          sheetGrabberVisible: true, 
        }} 
      /> 
      <AppStack.Screen 
        name="ProductListPicker" 
        component={ProductListPickerScreen} 
      /> 
      <AppStack.Screen 
        name="PreviewProducts" 
        component={PreviewProductsScreen} 
      /> 
      <AppStack.Screen name="ReceiptScanner" component={ReceiptScannerScreen} /> 
      <AppStack.Screen name="CollectionForm" component={CollectionFormScreen} /> 
      <AppStack.Screen 
        name="Collection" 
        component={CollectionScreen} 
        options={screenOptionsWithLargeTitle(theme)} 
      /> 
      <AppStack.Screen name="GenerateRecipe" component={GenerateRecipeScreen} /> 
      <AppStack.Screen name="ImportRecipe" component={ImportRecipeScreen} /> 
      <AppStack.Screen 
        name="HowToImportRecipe" 
        component={HowToImportRecipeScreen} 
        options={{ 
          presentation: 'pageSheet', 
        }} 
      /> 
      <AppStack.Screen 
        name="ProcessingRecipe" 
        component={ProcessingRecipeScreen} 
      /> 
      <AppStack.Screen 
        name="ManagePreferences" 
92 
 482.ЧДТУ 262251 12 01   7 
        component={ManagePreferencesScreen} 
      /> 
      <AppStack.Screen name="RecipeForm" component={RecipeFormScreen} /> 
      <AppStack.Screen name="RecipeDraft" component={RecipeDraftScreen} /> 
      <AppStack.Screen name="RecipeDetail" component={RecipeDetailScreen} /> 
    </AppStack.Navigator> 
  ) 
} 
export default AppNavigator 
 
HomeScreen.tsx. Головний екран із відображенням трьох основних блоків даних. 
import { closeAllSwipeables } from '@/components/common/swipeable/SwipeableItem' 
import ExpiredProductsModal from '@/components/home/ExpiredProductsModal' 
import HomeSwipeableListItem from '@/components/home/HomeSwipeableItemList' 
import ListItem, { ListItemProps } from '@/components/ui/ListItem' 
import ThemedList from '@/components/ui/ThemedList' 
import { Colors } from '@/constants/Colors' 
import { FontFamily } from '@/constants/FontFamily' 
import { CONTAINER_PADDING } from '@/constants/global' 
import { DeleteEditActionKey } from '@/constants/SwipeableActions' 
import { useColorScheme } from '@/hooks/useColorScheme' 
import { activeLocalDb } from '@/lib/supastash' 
import dbService from '@/services/DatabaseService' 
import { useShoppingListsStore } from '@/stores/shoppingListStore' 
import { useStoragesStore } from '@/stores/storageStore' 
import { useGlobalStore } from '@/stores/store' 
import { ProductRow } from '@/types/database-strict' 
import { AppStackParamList, RootTabParamList } from '@/types/navigation' 
import { ValueOf } from '@/types/typeUtils' 
import { confirmDelete } from '@/utils/alertUtils' 
import { NativeBottomTabScreenProps } from '@react-navigation/bottom-
tabs/unstable' 
import { CompositeScreenProps } from '@react-navigation/native' 
import { NativeStackScreenProps } from '@react-navigation/native-stack' 
import React, { useCallback, useEffect, useState } from 'react' 
import { useTranslation } from 'react-i18next' 
import { ScrollView, StyleSheet } from 'react-native' 
type Props = CompositeScreenProps< 
  NativeBottomTabScreenProps<RootTabParamList, 'Home'>, 
  NativeStackScreenProps<AppStackParamList> 
> 
 
export type HomeSection = ValueOf<typeof HOME_SECTIONS> 
export const HOME_SECTIONS = { 
  STORAGES: 'storages', 
  SHOPPING_LISTS: 'shopping_lists', 
  OTHERS: 'others', 
} as const 
 
const HomeScreen = ({ navigation }: Props) => { 
  const theme = useColorScheme() 
  const { t } = useTranslation() 
  const storages = useStoragesStore(state => state.storages) 
  const shoppingLists = useShoppingListsStore(state => state.shoppingLists) 
  const showExpiredModal = useGlobalStore(s => s.showExpiredModal) 
  const [expiredBatch, setExpiredBatch] = useState<ProductRow[]>([]) 
  const [hasCheckedExpired, setHasCheckedExpired] = useState(false) 
 
  useEffect(() => { 
    if (hasCheckedExpired) return 
    const checkExpiredProducts = async () => { 
93 
 482.ЧДТУ 262251 12 01   8 
      const data = await dbService.getExpiredProducts() 
      if (data && data.length > 0) { 
        setExpiredBatch(data) 
      } 
      setHasCheckedExpired(true) 
    } 
    checkExpiredProducts() 
  }, [hasCheckedExpired, showExpiredModal]) 
 
  const onPressAction = useCallback( 
    async ( 
      action: DeleteEditActionKey, 
      itemValue: string | undefined, 
      section: HomeSection, 
    ) => { 
      if (!itemValue) { 
        console.warn('Action triggered on item without value') 
        return 
      } 
      const deleteHandlers: Partial<Record<HomeSection, () => Promise<void>>> = 
        { 
          [HOME_SECTIONS.STORAGES]: async () => 
            await dbService.deleteStorage(itemValue), 
          [HOME_SECTIONS.SHOPPING_LISTS]: async () => 
            await dbService.deleteShoppingList(itemValue), 
        } 
      const editHandlers: Partial<Record<HomeSection, () => void>> = { 
        [HOME_SECTIONS.STORAGES]: () => 
          navigation.navigate('StorageForm', { storageId: itemValue }), 
        [HOME_SECTIONS.SHOPPING_LISTS]: () => 
          navigation.navigate('ShoppingListForm', { listId: itemValue }), 
      } 
      if (action === 'delete') { 
        const handler = deleteHandlers[section] 
        if (handler) { 
          confirmDelete({ 
            type: section === 'storages' ? 'storages' : 'shopping_lists', 
            onConfirm: handler, 
          }) 
        } else { 
          console.warn(`Delete action not supported for section: ${section}`) 
        } 
      } else if (action === 'edit') { 
        const handler = editHandlers[section] 
        if (handler) { 
          handler() 
        } else { 
          console.warn(`Edit action not supported for section: ${section}`) 
        } 
      } 
    }, 
    [navigation], 
  ) 
  const storageItems: ListItemProps[] = [ 
    ...storages.map(storage => ({ 
      id: storage.id, 
      icon: storage.icon, 
      title: storage.name, 
      showChevron: true, 
      onPressItem: () => { 
        navigation.navigate('Storage', { storageId: storage.id }) 
      }, 
      disablePress: true, 
    })), 
94 
 482.ЧДТУ 262251 12 01   9 
    { 
      icon: 'file-plus', 
      title: t('home.actions.create_storage'), 
      showChevron: true, 
      itemsColor: Colors[theme].primary, 
      onPressItem: () => { 
        navigation.navigate('StorageForm') 
      }, 
    }, 
  ] 
  const shoppingItems = [ 
    ...shoppingLists.slice(0, 2).map(list => ({ 
      id: list.id, 
      title: list.name, 
      showChevron: true, 
      onPressItem: () => { 
        navigation.navigate('SingleShoppingList', { listId: list.id }) 
      }, 
      disablePress: true, 
    })), 
    ...(shoppingLists.length > 0 
      ? [ 
          { 
            title: t('common.show_all'), 
            itemsColor: Colors[theme].primary, 
            onPressItem: () => { 
              navigation.navigate('ShoppingLists') 
            }, 
          }, 
        ] 
      : []), 
    { 
      id: 'create-new-list', 
      title: t('home.actions.create_list'), 
      itemsColor: Colors[theme].primary, 
      onPressItem: () => { 
        navigation.navigate('ShoppingListForm') 
      }, 
    }, 
  ] satisfies ListItemProps[] 
  const otherItems: ListItemProps[] = [ 
    { 
      icon: 'sparkles', 
      title: t('home.actions.generate_recipes'), 
      onPressItem: () => { 
        navigation.navigate('GenerateRecipe') 
      }, 
      showChevron: true, 
      itemsColor: Colors[theme].primary, 
    }, 
    { 
      icon: 'settings', 
      title: t('home.actions.settings'), 
      onPressItem: () => { 
        navigation.navigate('Settings') 
      }, 
      showChevron: true, 
      itemsColor: Colors[theme].primary, 
    }, 
  ] 
  const sections: { 
    key: HomeSection 
    label: string 
    items: ListItemProps[] 
95 
 482.ЧДТУ 262251 12 01   10 
  }[] = [ 
    { 
      key: HOME_SECTIONS.STORAGES, 
      label: t('home.sections.storages'), 
      items: storageItems, 
    }, 
    { 
      key: HOME_SECTIONS.SHOPPING_LISTS, 
      label: t('home.sections.shopping_lists'), 
      items: shoppingItems, 
    }, 
    { 
      key: HOME_SECTIONS.OTHERS, 
      label: t('home.sections.others'), 
      items: otherItems, 
    }, 
  ] 
  return ( 
    <ScrollView 
      contentContainerStyle={styles.container} 
      onScrollBeginDrag={closeAllSwipeables} 
    > 
      {sections && 
        sections.map(section => ( 
          <ThemedList 
            key={section.key} 
            title={section.label} 
            data={section.items} 
            renderItem={item => { 
              return item.disablePress ? ( 
                <HomeSwipeableListItem 
                  section={section.key} 
                  swipeableId={`${section.key}-${item.id}`} 
                  onAction={onPressAction} 
                  {...item} 
                /> 
              ) : ( 
                <ListItem 
                  id={item.id} 
                  icon={item.icon} 
                  title={item.title} 
                  showChevron={item.showChevron} 
                  itemsColor={item.itemsColor} 
                /> 
              ) 
            }} 
          /> 
        ))} 
      <ExpiredProductsModal 
        visible={expiredBatch.length > 0} 
        products={expiredBatch} 
        storages={storages} 
      /> 
    </ScrollView> 
  ) 
} 
export default HomeScreen 
const styles = StyleSheet.create({ 
  container: { 
    paddingHorizontal: CONTAINER_PADDING, 
    paddingTop: CONTAINER_PADDING, 
    gap: 16, 
  }, 
  createButtonText: { 
96 
 482.ЧДТУ 262251 12 01   11 
    fontSize: 16, 
    fontFamily: FontFamily.RobotoRegular, 
    flexShrink: 1, 
  }, 
  activityIndicator: { paddingVertical: 8 }, 
}) 
 
CollectionsScreen.tsx. Екран роботи з кулінарними рецептами та папками. 
import { CompositeScreenProps } from '@react-navigation/native' 
import { 
  NativeStackNavigationOptions, 
  NativeStackScreenProps, 
} from '@react-navigation/native-stack' 
import React, { useCallback, useLayoutEffect, useMemo, useState } from 'react' 
import { FlatList, Pressable, StyleSheet, View } from 'react-native' 
 
import CollectionCard from '@/components/collection/CollectionCard' 
import ContentIcon from '@/components/ui/ContentIcon' 
import ContextMenu from '@/components/ui/ContextMenu' 
import Icon from '@/components/ui/Icon' 
import { ThemedText } from '@/components/ui/ThemedText' 
import {Colors,FAVORITES_COLOR,NEUTRAL_COLOR,PREMIUM_COLOR} from 
'@/constants/Colors' 
import { CONTEXT_MENU_DELETE_EDIT_ACTIONS } from '@/constants/ContextMenuActions' 
import { HEADER_STYLES } from '@/constants/global' 
import { SYSTEM_ICONS } from '@/constants/icons/SystemIcons' 
import { DeleteEditActionKey } from '@/constants/SwipeableActions' 
import { useColorScheme } from '@/hooks/useColorScheme' 
import { useAuth } from '@/providers/AuthProvider' 
import dbService from '@/services/DatabaseService' 
import { useRecipesStore } from '@/stores/recipesStore' 
import { CollectionRow } from '@/types/database-strict' 
import { ThemeMode } from '@/types/global' 
import { AppStackParamList } from '@/types/navigation' 
import { confirmDelete } from '@/utils/alertUtils' 
import { screenOptionsWithSearchBar } from '@/utils/navigationUtils' 
import { useTranslation } from 'react-i18next' 
 
type CollectionsScreenNavigationProp = CompositeScreenProps< 
  NativeStackScreenProps<{ Collections: undefined }>, 
  NativeStackScreenProps<AppStackParamList> 
> 
const screenOptions = ( 
  theme: ThemeMode, 
  title: string, 
  onGeneratePress: () => void, 
  onCreatePress: () => void, 
  onSearch: (text: string) => void, 
  disabled: boolean, 
): NativeStackNavigationOptions => { 
  return { 
    title, 
    unstable_headerRightItems: () => [ 
      { 
        type: 'button', 
        label: 'generate', 
        disabled, 
        icon: { 
          type: 'sfSymbol', 
          name: SYSTEM_ICONS.wand.ios, 
        }, 
97 
 482.ЧДТУ 262251 12 01   12 
        onPress: onGeneratePress, 
      }, 
      { 
        type: 'button', 
        label: 'add', 
        disabled, 
        icon: { 
          type: 'sfSymbol', 
          name: SYSTEM_ICONS.add.ios, 
        }, 
        onPress: onCreatePress, 
      }, 
    ], 
    headerRight: () => ( 
      <View style={HEADER_STYLES.itemsContainer}> 
        <Pressable disabled onPress={onGeneratePress}> 
          <ContentIcon 
            name={SYSTEM_ICONS.wand.android} 
            color={disabled ? 'gray' : Colors[theme].text} 
          /> 
        </Pressable> 
        <Pressable disabled onPress={onCreatePress}> 
          <ContentIcon 
            name={SYSTEM_ICONS.add.android} 
            size={26} 
            color={disabled ? 'gray' : Colors[theme].text} 
          /> 
        </Pressable> 
      </View> 
    ), 
    ...screenOptionsWithSearchBar(theme, onSearch), 
  } 
} 
export default function CollectionsScreen({ 
  navigation, 
}: CollectionsScreenNavigationProp) { 
  const { t } = useTranslation() 
  const theme = useColorScheme() 
  const { isPremium } = useAuth() 
  const collections = useRecipesStore(s => s.collections) 
  const recipes = useRecipesStore(s => s.recipes) 
  const getFavoritesCount = useRecipesStore(s => s.getFavoritesCount) 
  const getRecipeCount = useRecipesStore(s => s.getRecipeCount) 
  const [searchText, setSearchText] = useState('') 
  const handleGeneratePress = useCallback(() => { 
    navigation.navigate('GenerateRecipe') 
  }, [navigation]) 
 
  const handleCreateCollectionPress = useCallback(() => { 
    navigation.navigate('CollectionForm', {}) 
  }, [navigation]) 
  const handleCollectionLongPress = ( 
    value: DeleteEditActionKey, 
    id: string, 
  ) => { 
    if (value === 'edit') { 
      navigation.navigate('CollectionForm', { 
        collectionId: id, 
      }) 
    } 
    if (value === 'delete') { 
      confirmDelete({ 
        type: 'collections', 
        onConfirm: () => { 
98 
 482.ЧДТУ 262251 12 01   13 
          dbService.deleteCollection(id) 
        }, 
      }) 
    } 
  } 
  useLayoutEffect(() => { 
    navigation.setOptions( 
      screenOptions( 
        theme, 
        t('collections.title'), 
        handleGeneratePress, 
        handleCreateCollectionPress, 
        setSearchText, 
        !isPremium, 
      ), 
    ) 
  }, [ 
    navigation, 
    theme, 
    isPremium, 
    handleGeneratePress, 
    handleCreateCollectionPress, 
    t, 
  ]) 
  const listData = useMemo(() => { 
    const data: (CollectionRow & { type: string })[] = [] 
    if (!searchText) { 
      data.push({ id: 'favorites', type: 'favorites' } as any) 
    } 
    const filtered = collections.filter(c => 
      c.name.toLowerCase().includes(searchText.toLowerCase()), 
    ) 
    filtered.forEach(c => data.push({ ...c, type: 'collection' })) 
    if (!searchText) { 
      data.push({ id: 'create', type: 'create' } as any) 
    } 
    return data 
  }, [collections, searchText]) 
  const renderItem = ({ item }: { item: CollectionRow & { type: string } }) => { 
    if (item.type === 'favorites') { 
      return ( 
        <View style={styles.gridItem}> 
          <CollectionCard 
            title={t('collections.favorites')} 
            count={getFavoritesCount()} 
            icon={'heart'} 
            bgColor={FAVORITES_COLOR} 
            onPress={() => 
              navigation.navigate('Collection', { collectionId: 'fav' }) 
            } 
          /> 
        </View> 
      ) 
    } 
    if (item.type === 'create') { 
      return ( 
        <Pressable 
          style={[styles.createCard, styles.gridItem]} 
          onPress={handleCreateCollectionPress} 
        > 
          <View style={styles.createIconWrapper}> 
            {isPremium ? ( 
              <Icon name="add" color={NEUTRAL_COLOR} size={32} /> 
            ) : ( 
99 
 482.ЧДТУ 262251 12 01   14 
              <Icon name="crown" color={PREMIUM_COLOR} size={28} /> 
            )} 
          </View> 
          <ThemedText style={styles.createCardText}> 
            {isPremium 
              ? t('collections.new_collection') 
              : t('collections.get_premium')} 
          </ThemedText> 
        </Pressable> 
      ) 
    } 
    return ( 
      <ContextMenu 
        style={styles.gridItem} 
        options={CONTEXT_MENU_DELETE_EDIT_ACTIONS} 
        onSelect={({ value }) => handleCollectionLongPress(value, item.id)} 
      > 
        <CollectionCard 
          title={item.name} 
          count={getRecipeCount(item.id)} 
          icon={item.icon} 
          bgColor={item.color || '#1E1E2D'} 
          onPress={() => 
            navigation.navigate('Collection', { collectionId: item.id }) 
          } 
        /> 
      </ContextMenu> 
    ) 
  } 
  return ( 
    <FlatList 
      data={listData} 
      extraData={recipes} 
      keyExtractor={item => item.id} 
      numColumns={2} 
      contentInsetAdjustmentBehavior="automatic" 
      showsVerticalScrollIndicator={false} 
      contentContainerStyle={styles.scrollContent} 
      columnWrapperStyle={styles.columnWrapper} 
      renderItem={renderItem} 
    /> 
  ) 
} 
const styles = StyleSheet.create({ 
  columnWrapper: { 
    gap: 16, 
    marginBottom: 16, 
  }, 
  gridItem: { width: '48%' }, 
  scrollContent: { paddingHorizontal: 20, paddingTop: 10, paddingBottom: 100 }, 
  createCard: { 
    aspectRatio: 1, 
    borderRadius: 24, 
    backgroundColor: '#161722', 
    justifyContent: 'center', 
    alignItems: 'center', 
    gap: 12, 
  }, 
  createIconWrapper: { 
    width: 56, 
    height: 56, 
    borderRadius: 28, 
    backgroundColor: '#1E1E2D', 
    justifyContent: 'center', 
100 
 482.ЧДТУ 262251 12 01   15 
    alignItems: 'center', 
  }, 
  createCardText: { 
    color: '#e5e6e8', 
    fontSize: 16, 
    fontWeight: '600', 
    textAlign: 'center', 
  }, 
}) 
 
QuickActionsScreen.tsx. Меню швидкого поповнення запасів. 
import ContentIcon from '@/components/ui/ContentIcon' 
import Icon from '@/components/ui/Icon' 
import ScalePressable from '@/components/ui/ScalePressable' 
import ThemedContainer from '@/components/ui/ThemedContainer' 
import { ThemedText } from '@/components/ui/ThemedText' 
import { PREMIUM_COLOR } from '@/constants/Colors' 
import { BORDER_RADIUS, CONTAINER_PADDING } from '@/constants/global' 
import { useAuth } from '@/providers/AuthProvider' 
import AIService from '@/services/AIService' 
import { useDraftStore } from '@/stores/draftStore' 
import { useStoragesStore } from '@/stores/storageStore' 
import { ContentIconName } from '@/types/global' 
import { AppStackParamList, RootTabParamList } from '@/types/navigation' 
import { confirm } from '@/utils/alertUtils' 
import { pick } from '@react-native-documents/picker' 
import { NativeBottomTabScreenProps } from '@react-navigation/bottom-
tabs/unstable' 
import { CompositeScreenProps } from '@react-navigation/native' 
import { NativeStackScreenProps } from '@react-navigation/native-stack' 
import { extractText, isAvailable } from 'expo-pdf-text-extract' 
import React from 'react' 
import { useTranslation } from 'react-i18next' 
import { Alert, ScrollView, StyleSheet, View } from 'react-native' 
type Props = CompositeScreenProps< 
  NativeBottomTabScreenProps<RootTabParamList, 'QuickActions'>, 
  NativeStackScreenProps<AppStackParamList> 
> 
 
interface AddMethod { 
  id: string 
  icon: ContentIconName 
  iconColor: string 
  isPremium: boolean 
} 
const ADD_METHODS = [ 
  { id: 'manual', icon: 'pencil', iconColor: '#f59e0b', isPremium: false }, 
  { id: 'barcode', icon: 'barcode', iconColor: '#3b82f6', isPremium: false }, 
  { 
    id: 'receipt_photo', 
    icon: 'receipt', 
    iconColor: '#10b981', 
    isPremium: true, 
  }, 
  { 
    id: 'receipt_pdf', 
    icon: 'file-text', 
    iconColor: '#b0b910', 
    isPremium: true, 
  }, 
  { 
101 
 482.ЧДТУ 262251 12 01   16 
    id: 'existing', 
    icon: 'list-check', 
    iconColor: '#8b5cf6', 
    isPremium: false, 
  }, 
] as const satisfies AddMethod[] 
const QuickActionsScreen = ({ navigation }: Props) => { 
  const { t } = useTranslation() 
  const { isPremium } = useAuth() 
  const storages = useStoragesStore(s => s.storages) 
 
  const handleMethodPress = (methodId: (typeof ADD_METHODS)[number]['id']) => { 
    const hasStorages = !!storages && storages.length > 0 
    if (!hasStorages) { 
      confirm({ 
        title: t('product.form.no_storages_title'), 
        message: t('quick_actions.alerts.no_storages_message'), 
 
        confirmText: t('quick_actions.alerts.create_storage_btn'), 
        onConfirm: () => navigation.navigate('StorageForm'), 
      }) 
      return 
    } 
    switch (methodId) { 
      case 'barcode': 
        navigation.navigate('StorageProductForm', { startWithScanner: true }) 
        break 
      case 'receipt_photo': 
        navigation.navigate('ReceiptScanner') 
        break 
      case 'receipt_pdf': 
        handlePdfPress() 
        break 
      case 'manual': 
        navigation.navigate('StorageProductForm') 
        break 
      case 'existing': 
        navigation.navigate('ProductListPicker') 
        break 
      default: 
        throw Error('Unknown methodId: ' + (methodId satisfies never)) 
    } 
  } 
  const handlePdfPress = async () => { 
    if (!isPremium) { 
      confirm({ 
        title: t('premium.alert_title'), 
        message: t('premium.scanner_limit_message'), 
        confirmText: t('premium.learn_more'), 
        onConfirm: () => { 
          // navigation.navigate('Paywall') 
        }, 
      }) 
      return 
    } 
    const test = await pick() 
      if (isAvailable()) { 
      const extractedText = await extractText(test[0].uri) 
      if (extractedText.length === 0) { 
        Alert.alert(t('common.error_title'), t('scanner.errors.no_text')) 
        return 
      } 
      const response = await AIService.recognizeProductsFromReceipt( 
        extractedText, 
102 
 482.ЧДТУ 262251 12 01   17 
      ) 
      if (!response.success) { 
        Alert.alert(t('common.error_title'), t('common.something_went_wrong')) 
        return 
      } 
      const draftItems = response.productsToInsert 
      if (draftItems.length === 0) { 
        Alert.alert( 
          t('scanner.errors.no_products_title'), 
          t('scanner.errors.no_products_desc'), 
        ) 
        return 
      } 
      useDraftStore.getState().clear() 
      useDraftStore.getState().setItems(draftItems, 'storage') 
      navigation.replace('PreviewProducts') 
    } 
    Alert.alert(t('common.error_title'), t('common.something_went_wrong')) 
  } 
  return ( 
    <ScrollView 
      style={styles.container} 
      contentContainerStyle={styles.contentContainer} 
      showsVerticalScrollIndicator={false} 
    > 
      <View style={styles.header}> 
        <ThemedText type="title">{t('quick_actions.title')}</ThemedText> 
        <ThemedText type="default" style={styles.subtitle}> 
          {t('quick_actions.subtitle')} 
        </ThemedText> 
      </View> 
      <View style={styles.methodsGrid}> 
        {ADD_METHODS.map(method => ( 
          <ScalePressable 
            key={method.id} 
            targetScale={0.97} 
            onPress={() => handleMethodPress(method.id)} 
            style={styles.pressableWrapper} 
          > 
            <ThemedContainer type="card" style={styles.cardContainer}> 
              <View 
                style={[ 
                  styles.iconBox, 
                  { backgroundColor: `${method.iconColor}20` }, 
                ]} 
              > 
                <ContentIcon 
                  name={method.icon} 
                  color={method.iconColor} 
                  size={28} 
                /> 
              </View> 
              <View style={styles.textContainer}> 
                <ThemedText type="defaultSemiBold" style={styles.cardTitle}> 
                  {t(`quick_actions.methods.${method.id}.title`)}{' '} 
                  {method.isPremium && ( 
                    <Icon name="crown" size={16} color={PREMIUM_COLOR} /> 
                  )} 
                </ThemedText> 
                <ThemedText style={styles.cardDescription}> 
                  {t(`quick_actions.methods.${method.id}.description`)} 
                </ThemedText> 
              </View> 
              <View style={styles.chevron}> 
103 
 482.ЧДТУ 262251 12 01   18 
                <Icon name="chevronRight" size={20} /> 
              </View> 
            </ThemedContainer> 
          </ScalePressable> 
        ))} 
      </View> 
    </ScrollView> 
  ) 
} 
const styles = StyleSheet.create({ 
  container: { 
    flex: 1, 
  }, 
  contentContainer: { 
    padding: CONTAINER_PADDING, 
    paddingBottom: CONTAINER_PADDING * 2, 
  }, 
  header: { 
    marginBottom: 24, 
    marginTop: 10, 
  }, 
  subtitle: { 
    marginTop: 8, 
    opacity: 0.7, 
  }, 
  methodsGrid: { 
    gap: 16, 
  }, 
  pressableWrapper: { 
    borderRadius: BORDER_RADIUS, 
  }, 
  pressedCard: { 
    opacity: 0.7, 
    transform: [{ scale: 0.98 }], 
  }, 
  cardContainer: { 
    flexDirection: 'row', 
    alignItems: 'center', 
    minHeight: 80, 
  }, 
  iconBox: { 
    width: 50, 
    height: 50, 
    borderRadius: 12, 
    justifyContent: 'center', 
    alignItems: 'center', 
    marginRight: 16, 
  }, 
  textContainer: { 
    flex: 1, 
    justifyContent: 'center', 
  }, 
  cardTitle: { 
    fontSize: 17, 
    marginBottom: 4, 
  }, 
  cardDescription: { 
    fontSize: 13, 
    opacity: 0.6, 
    lineHeight: 18, 
  }, 
  chevron: { 
    marginLeft: 12, 
    opacity: 0.5, 
104 
 482.ЧДТУ 262251 12 01   19 
  }, 
}) 
export default QuickActionsScreen 
 
storageStore.ts. Управління глобальним станом сховищ через Zustand. 
import { StorageRow } from '@/types/database-strict' 
import { supastash } from 'supastash' 
import { create } from 'zustand' 
 
const DEFAULT_LIMIT = 100 
 
interface StoragesStore { 
  storages: StorageRow[] 
  dataMap: Map<string, StorageRow> 
  limit: number 
  hydrateStorages: (newLimit?: number) => Promise<void> 
  loadMore: (amount?: number) => Promise<void> 
  getById: (id?: string) => StorageRow | undefined 
  getPersonalStorages: () => StorageRow[] 
  getFamilyStorages: (familyId?: string) => StorageRow[] 
  clearStore: () => void 
} 
 
export const useStoragesStore = create<StoragesStore>((set, get) => ({ 
  storages: [], 
  dataMap: new Map(), 
  limit: DEFAULT_LIMIT, 
  hydrateStorages: async newLimit => { 
    const currentLimit = newLimit ?? get().limit 
 
    const { data, error } = await supastash 
      .from('storages') 
      .select<StorageRow>('*') 
      .is('deleted_at', null) 
      .limit(currentLimit) 
      .run() 
    if (error) { 
      console.error('[StoragesStore] Error hydrating:', error) 
      return 
    } 
    if (data) { 
      const newMap = new Map<string, StorageRow>() 
      for (const item of data) { 
        newMap.set(item.id, item) 
      } 
      set({ 
        storages: data, 
        dataMap: newMap, 
        limit: currentLimit, 
      }) 
    } 
  }, 
  loadMore: async (amount = 50) => { 
    const nextLimit = get().limit + amount 
    await get().hydrateStorages(nextLimit) 
  }, 
  getById: id => (id ? get().dataMap.get(id) : undefined), 
  clearStore: () => 
    set({ 
      storages: [], 
      dataMap: new Map(), 
105 
 482.ЧДТУ 262251 12 01   20 
      limit: DEFAULT_LIMIT, 
    }), 
})) 
 
draftStore.ts. Контейнер тимчасового стану для збереження та перевірки 
відсканованих продуктів. 
import { ProductRow } from '@/types/database-strict' 
import { create } from 'zustand' 
 
interface DraftStore { 
  items: ProductRow[] 
  mode: 'storage' | 'shoppingList' | null 
  setItems: (items: ProductRow[], mode: 'storage' | 'shoppingList') => void 
  pushItem: (item: ProductRow) => void 
  deleteItem: (index: number) => void 
  updateItem: (index: number, updates: Partial<ProductRow>) => void 
  clear: () => void 
} 
export const useDraftStore = create<DraftStore>(set => ({ 
  items: [], 
  mode: null, 
  setItems: (items, mode) => { 
    const sanitizedItems = items.map(item => ({ 
      ...item, 
      storage_id: mode === 'shoppingList' ? null : item.storage_id, 
      shopping_list_id: mode === 'storage' ? null : item.shopping_list_id, 
    })) 
    set({ items: sanitizedItems, mode }) 
  }, 
  pushItem: item => { 
    set(state => { 
      const sanitizedItem = { ...item } 
      if (state.mode === 'storage') { 
        sanitizedItem.shopping_list_id = null 
      } else if (state.mode === 'shoppingList') { 
        sanitizedItem.storage_id = null 
      } 
      return { items: [...state.items, sanitizedItem] } 
    }) 
  }, 
  deleteItem: index => 
    set(state => ({ items: state.items.filter((_, i) => i !== index) })), 
  updateItem: (index, updates) => 
    set(state => { 
      const newItems = [...state.items] 
      const currentItem = newItems[index] 
      const merged = { ...currentItem, ...updates } 
      if ('storage_id' in updates && updates.storage_id !== undefined) { 
        merged.shopping_list_id = null 
      } else if ( 
        'shopping_list_id' in updates && 
        updates.shopping_list_id !== undefined 
      ) { 
        merged.storage_id = null 
      } 
      newItems[index] = merged 
      return { items: newItems } 
    }), 
  clear: () => set({ items: [], mode: null }), 
}))	
106 
 482.ЧДТУ 262251 12 01   21 
AIService.ts. Модуль взаємодії з хмарними сервісами ШІ для обробки чеків та 
генерації рецептів. 
import { supabase } from '@/lib/supabase' 
import i18n from '@/localization/i18n' 
import { useRecipeGeneratorStore } from '@/stores/recipeGeneratorStore' 
import { ProductCategory, RecipeSourceType, Unit } from '@/types/global' 
import { createProductDraft } from '@/utils/productUtils' 
import dayjs from 'dayjs' 
 
export interface AIRecognizedProduct { 
  name: string 
  category: ProductCategory 
  quantity: number 
  unit: Unit 
  estimated_shelf_life_days: number | null 
} 
export interface AiRecipeDraft { 
  title: string 
  description: string 
  prep_time_minutes: number 
  cook_time_minutes: number 
  servings: number 
  image_url: string | null 
  ingredients: { name: string; amount: string; unit: string }[] 
  instructions: string[] 
  source_type: RecipeSourceType 
  source_url: string | null 
} 
const AIService = { 
  async recognizeProductsFromReceipt(receiptText: string) { 
    try { 
      const { data, error } = await supabase.functions.invoke<{ 
        products: AIRecognizedProduct[] 
      }>('recognize-receipt', { 
        body: { receiptText }, 
      }) 
      if (error) { 
        console.error( 
          '[AIService] Issue with Supabase Edge Function:', 
          error.message, 
        ) 
        throw error 
      } 
      if (!data || !data.products) { 
        throw new Error('No products recognized') 
      } 
      const productsToInsert = data.products.map(item => { 
        const expirationDate = item.estimated_shelf_life_days 
          ? dayjs().add(item.estimated_shelf_life_days, 'day').toISOString() 
          : null 
        return createProductDraft({ expiration_date: expirationDate, ...item }) 
      }) 
      return { success: true, productsToInsert } 
    } catch (err) { 
      console.error('[AIService] Error recognizing products:', err) 
      return { success: false, productsToInsert: [] } 
    } 
  }, 
  async generateRecipeFromFilters(): Promise<AiRecipeDraft> { 
    const store = useRecipeGeneratorStore.getState() 
    const allIngredients = [ 
107 
 482.ЧДТУ 262251 12 01   22 
      ...store.ingredients.map(i => i.name), 
      ...store.customIngredients, 
    ] 
    const payload = { 
      mode: 'generate', 
      language: i18n.language, 
      ingredients: allIngredients, 
      diets: [...store.diets, ...store.customDiets], 
      allergies: [...store.allergies, ...store.customAllergies], 
      cuisines: store.cuisines, 
      maxTimeMinutes: store.maxTime, 
    } 
    const { data, error } = await supabase.functions.invoke< 
      Omit<AiRecipeDraft, 'source_type' | 'source_url'> 
    >('process-recipe', { 
      body: payload, 
    }) 
    if (error) throw error 
    if (!data) throw new Error('No data returned from recipe generator') 
    const draft: AiRecipeDraft = { 
      ...data, 
      image_url: data.image_url || null, 
      source_type: 'generated_ai', 
      source_url: null, 
    } 
    return draft 
  }, 
  async importRecipeFromSource(source: string): Promise<AiRecipeDraft> { 
    const processed = source.trim() 
    const payload = { 
      mode: 'import', 
      language: i18n.language, 
      importData: processed, 
    } 
     
    const { data, error } = await supabase.functions.invoke< 
      Omit<AiRecipeDraft, 'source_type' | 'source_url'> 
    >('process-recipe', { 
      body: payload, 
    }) 
     
    if (error) throw error 
     
    if (!data) throw new Error('No data returned from recipe generator') 
     
    const urlRegex = /(https?:\/\/[^\s]+)/g 
    const matches = processed.match(urlRegex) 
    const url = matches ? matches[0] : null 
    const draft: AiRecipeDraft = { 
      ...data, 
      image_url: data.image_url || null, 
      source_type: 'imported', 
      source_url: url ? url : null, 
    } 
    return draft 
  }, 
} 
export default AIService 
 
BarcodeService.ts. Алгоритм обробки штрихкодів. 
import { PRODUCT_CATEGORIES } from '@/constants/Product/ProductCategories' 
108 
 482.ЧДТУ 262251 12 01   23 
import { PRODUCT_ICONS } from '@/constants/Product/ProductIcons' 
import { QUANTITY_TYPES, UNITS } from '@/constants/Product/Units' 
import { DatabaseService } from '@/services/DatabaseService' 
import { BarcodeRow } from '@/types/database-strict' 
 
const BarcodeService = { 
  processScannedBarcode: async ( 
    db: DatabaseService, 
    code: string, 
  ): Promise<{ isKnown: boolean; product: BarcodeRow } | null> => { 
    try { 
      const knownItem = await db.getProductInfoByCode(code) 
      if (knownItem) { 
        return { isKnown: true, product: knownItem } 
      } 
      const response = await fetch( 
        `${process.env.API_OPENFOODFACTS_URL}${code}`, 
      ) 
 
      if (!response.ok) { 
        throw new Error(`HTTP error! status: ${response.status}`) 
      } 
 
      const data = await response.json() 
 
      if (data.status === 1 && data.product) { 
        const productName = 
          data.product.product_name_uk || data.product.product_name || '' 
        const quantity = 
          data.product.quantity || data.product.serving_quantity || 1 
        return { 
          isKnown: false, 
          product: { 
            id: code, 
            name: productName, 
            quantity: Number(quantity), 
            category: PRODUCT_CATEGORIES.OTHER, 
            icon: PRODUCT_ICONS[0], 
            unit: UNITS[QUANTITY_TYPES.COUNT][1], 
          }, 
        } 
      } 
      return null 
    } catch (error) { 
      console.error('processScannedBarcode:', error) 
      return null 
    } 
  }, 
} 
 
export default BarcodeService	
 
useCamera.ts. Хук для налаштування відеопотоку та прав доступу камери 
смартфона. 
import { useRef, useState } from 'react' 
import { Alert } from 'react-native' 
import { 
  Camera, 
  CameraPosition, 
  useCameraDevice, 
109 
 482.ЧДТУ 262251 12 01   24 
} from 'react-native-vision-camera' 
export const useCamera = (position: CameraPosition) => { 
  const cameraRef = useRef<Camera | null>(null) 
  const device = useCameraDevice(position) 
  const [isActive, setIsActive] = useState(false) 
  const [torchOn, setTorchOn] = useState(false) 
  const dismissCamera = () => { 
    setIsActive(false) 
    setTorchOn(false) 
  } 
  const presentCamera = async () => { 
    const status = await Camera.requestCameraPermission() 
    if (status === 'denied') { 
      Alert.alert('Permission required', 'Camera access is needed to scan.') 
      return 
    } 
    setIsActive(true) 
  } 
  const toggleTorch = () => { 
    device?.hasTorch && setTorchOn(prev => !prev) 
  } 
  
return{cameraRef,device,isActive,torchOn,dismissCamera,presentCamera,toggleTorch} 
}	
 
CameraFrame.tsx. Візуальний компонент для відображення відеопотоку та 
елементів керування. 
import { WINDOW_HEIGHT } from '@/constants/global' 
import React from 'react' 
import { Pressable, StyleSheet, View } from 'react-native' 
import { Camera, CameraDevice, CameraProps } from 'react-native-vision-camera' 
import ContentIcon from '../ui/ContentIcon' 
import SelectableContentIconButton from '../ui/SelectableContentIconButton' 
import ScannerHint from './ScannerHint' 
interface Props extends Omit<CameraProps, 'device'> { 
  ref?: React.RefObject<Camera | null> 
  device: CameraDevice | undefined 
  torchOn: boolean 
  handleDismiss?: () => void 
  toggleTorch?: () => void 
  onTakePhoto?: () => void 
  selectFromGallery?: () => void 
  hints?: string[] 
} 
const CameraFrame = ({ 
  device, 
  hints, 
  handleDismiss, 
  isActive, 
  toggleTorch, 
  torchOn, 
  onTakePhoto, 
  selectFromGallery, 
  ref, 
  ...cameraProps 
}: Props) => { 
  return ( 
    <View style={styles.cameraFrame}> 
      {device && isActive && ( 
        <Camera 
110 
 482.ЧДТУ 262251 12 01   25 
          ref={ref} 
          style={[StyleSheet.absoluteFill]} 
          device={device} 
          torch={torchOn ? 'on' : 'off'} 
          isActive={isActive} 
          {...cameraProps} 
        /> 
      )} 
      {handleDismiss && ( 
        <Pressable onPress={handleDismiss} style={styles.dismissBtn}> 
          <ContentIcon name="x" color="white" size={32} /> 
        </Pressable> 
      )} 
      <View style={styles.footer}> 
        {toggleTorch && ( 
          <View style={onTakePhoto && styles.leftButtonContainer}> 
            <SelectableContentIconButton 
              onPress={toggleTorch} 
              activeOpacity={0.5} 
              selected={torchOn} 
              iconName={torchOn ? 'flashlight' : 'flashlight-off'} 
              iconSize={22} 
            /> 
          </View> 
        )} 
        {onTakePhoto && ( 
          <SelectableContentIconButton 
            onPress={onTakePhoto} 
            iconName={'camera'} 
            selected 
            iconSize={30} 
            activeOpacity={0.5} 
          /> 
        )} 
        {selectFromGallery && ( 
          <View style={styles.rightButtonContainer}> 
            <SelectableContentIconButton 
              onPress={selectFromGallery} 
              activeOpacity={0.5} 
              iconName={'images'} 
              iconSize={22} 
            /> 
          </View> 
        )} 
      </View> 
      {hints && device && isActive && <ScannerHint hints={hints} />} 
    </View> 
  ) 
} 
export default CameraFrame 
const styles = StyleSheet.create({ 
  cameraFrame: { 
    height: WINDOW_HEIGHT * 0.8, 
    borderRadius: 16, 
    overflow: 'hidden', 
    backgroundColor: '#000', 
  }, 
  })	
 
lib/supastash.ts. Ініціалізація рушія синхронізації Local-First. 
import { getDatabaseName } from '@/utils/databaseUtils' 
111 
 482.ЧДТУ 262251 12 01   26 
import { NitroSQLiteConnection, open } from 'react-native-nitro-sqlite' 
import { configureSupastash, defineLocalSchema } from 'supastash' 
import { supabase } from './supabase' 
export let activeLocalDb: NitroSQLiteConnection | null = null 
const proxyOpen = (options: any) => { 
  activeLocalDb = open(options) 
  return activeLocalDb 
} 
export const initSupastash = (userId: string) => { 
  configureSupastash({ 
    supabaseClient: supabase, 
    dbName: getDatabaseName(userId), 
    sqliteClient: { open: proxyOpen }, 
    sqliteClientType: 'rn-nitro', 
    replicationMode: 'server-side', 
    deleteConflictedRows: true, 
    syncPolicy: { 
      onNonRetryable: 'delete-local', 
    }, 
    pollingInterval: { 
      pull: 5000, 
    }, 
    onSchemaInit: async () => { 
       
      defineLocalSchema('storages', { 
        id: 'TEXT PRIMARY KEY', 
        name: 'TEXT NOT NULL', 
        icon: 'TEXT NOT NULL', 
        user_id: 'TEXT', 
        family_id: 'TEXT', 
        created_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        updated_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        arrived_at: 'TEXT', 
        deleted_at: 'TEXT', 
        __indices: ['user_id', 'family_id'], 
      }) 
      defineLocalSchema('shopping_lists', { 
        id: 'TEXT PRIMARY KEY', 
        name: 'TEXT NOT NULL', 
        user_id: 'TEXT', 
        family_id: 'TEXT', 
        created_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        updated_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        arrived_at: 'TEXT', 
        deleted_at: 'TEXT', 
        __indices: ['user_id', 'family_id'], 
      }) 
      defineLocalSchema('products', { 
        id: 'TEXT PRIMARY KEY', 
        name: 'TEXT NOT NULL', 
        icon: 'TEXT NOT NULL', 
        category: 'TEXT NOT NULL', 
        quantity: 'REAL NOT NULL', 
        quantity_type: 'TEXT NOT NULL', 
        unit: 'TEXT NOT NULL', 
        status: 'TEXT NOT NULL', 
        status_changed_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        barcode: 'TEXT', 
        expiration_date: 'TEXT', 
112 
 482.ЧДТУ 262251 12 01   27 
        storage_id: 'TEXT', 
        shopping_list_id: 'TEXT', 
        related_product_id: 'TEXT', 
        created_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        updated_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        arrived_at: 'TEXT', 
        deleted_at: 'TEXT', 
        __indices: ['storage_id', 'shopping_list_id'], 
      }) 
      defineLocalSchema('collections', { 
        id: 'TEXT PRIMARY KEY', 
        name: 'TEXT NOT NULL', 
        icon: 'TEXT', 
        color: "TEXT NOT NULL DEFAULT '#1E1E2D'", 
        user_id: 'TEXT', 
        family_id: 'TEXT', 
        created_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        updated_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        arrived_at: 'TEXT', 
        deleted_at: 'TEXT', 
        __indices: ['user_id', 'family_id'], 
      }) 
      defineLocalSchema('recipes', { 
        id: 'TEXT PRIMARY KEY', 
        title: 'TEXT NOT NULL', 
        description: 'TEXT NOT NULL', 
        collection_id: 'TEXT NOT NULL', 
        is_favorite: 'INTEGER NOT NULL DEFAULT 0', 
        prep_time_minutes: 'REAL', 
        cook_time_minutes: 'REAL', 
        servings: 'REAL', 
        image_url: 'TEXT', 
        ingredients: 'TEXT NOT NULL', 
        instructions: 'TEXT NOT NULL', 
        source_type: 'TEXT NOT NULL', 
        source_url: 'TEXT', 
        created_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        updated_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        arrived_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        deleted_at: 'TEXT', 
        __indices: ['collection_id'], 
      }) 
      defineLocalSchema('user_preferences', { 
        id: 'TEXT PRIMARY KEY', 
        diets: "TEXT NOT NULL DEFAULT '[]'", 
        allergies: "TEXT NOT NULL DEFAULT '[]'", 
        cuisines: "TEXT NOT NULL DEFAULT '[]'", 
        created_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        updated_at: 
          "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", 
        arrived_at: 'TEXT', 
        deleted_at: 'TEXT', 
      }) 
      defineLocalSchema('barcodes', { 
        id: 'TEXT PRIMARY KEY', 
        name: 'TEXT NOT NULL', 
113 
 482.ЧДТУ 262251 12 01   28 
        icon: 'TEXT NOT NULL', 
        category: 'TEXT NOT NULL', 
        quantity: 'REAL NOT NULL', 
        quantity_type: 'TEXT NOT NULL', 
        unit: 'TEXT NOT NULL', 
      } 
    }, 
    excludeTables: { 
      push: ['barcodes'], 
      pull: ['barcodes'], 
    }, 
    debugMode: __DEV__, 
    syncEngine: { 
      push: true, 
      pull: true, 
    }, 
  }) 
}	
114 
 
ДОДАТОК В 
 
 
  
 
 
 
 
 
 
 
«Мобільний застосунок для інтелектуального управління домашньою 
продуктовою коморою» 
 
 
 
Інструкція користувачеві 
482.ЧДТУ 262251 34 01 
Листів 7 
 
 
 
 
 
Розробник ________________ Мельник В. А. 
 
 
 
2026 
 482.ЧДТУ 262251 34 01 2 
Для початку роботи із застосунком необхідно пройти авторизацію. На 
початковому екрані користувач може увійти у свій обліковий запис або створити 
новий (рисунок В.1). Це дозволяє безпечно зберігати дані в хмарі та мати до них 
доступ із різних пристроїв. 
Після успішного входу відкривається головне вікно програми (рисунок В.2). 
Воно розбите на три логічні блоки: список місць зберігання, списки покупок та 
розділ налаштувань. У нижній частині екрана розміщена панель для швидкої 
навігації між розділами. 
 
 
Рисунок В.1 – Екран входу та реєстрації у системі 
116 
 482.ЧДТУ 262251 34 01 3 
 
Рисунок В.2 – Головне вікно застосунку 
 
Щоб розпочати облік продуктів, необхідно створити сховище. Для цього на 
головному екрані слід натиснути кнопку додавання, ввести назву та обрати іконку 
(рисунок В.3). Кожне сховище можна відредагувати або видалити, провівши 
пальцем по його назві вліво. 
Усередині конкретного сховища відображається список усіх наявних 
продуктів (рисунок В.4). Користувач може шукати товари за назвою або 
фільтрувати їх за категоріями. Якщо продукт закінчився або зіпсувався, його 
статус можна змінити через меню дій, яке викликається тривалим натисканням на 
картку товару або проведенням по ній пальцем вліво чи вправо. 
117 
 482.ЧДТУ 262251 34 01 4 
 
Рисунок В.3 – Створення нового місця зберігання 
 
 
Рисунок В.4 – Перегляд вмісту сховища та фільтрація продуктів 
 
118 
 482.ЧДТУ 262251 34 01 5 
Безпосередньо на екрані конкретного сховища в нижній частині 
розташовані кнопки для швидкого додавання нового товару вручну або за 
допомогою зчитування штрихкоду. Проте для найзручнішого масового 
поповнення запасів створена окрема центральна вкладка швидких дій (рисунок 
В.5). Вона об'єднує всі доступні інструменти в одному місці: ручне створення 
запису, сканування штрихкоду через камеру смартфона, додавання вже з 
існуючих продуктів та інтелектуальне розпізнавання паперових чеків. Останній 
метод дозволяє системі автоматично зчитати назви всіх придбаних продуктів із 
чека та додати їх до комори за один раз, що повністю звільняє користувача від 
ручної роботи. 
 
 
Рисунок В.5 – Інтерфейс вкладки швидких дій для поповнення запасів 
 
Для планування покупок у застосунку є розділ «Списки покупок» (рисунок 
В.6). Під час покупок користувач просто натискає на куплений товар, і він стає 
закресленим, а смуга прогресу зверху показує, скільки ще залишилося купити. 
Готовим списком можна поділитися з іншими членами родини. 
119 
 482.ЧДТУ 262251 34 01 6 
 
Рисунок В.6 – Робота зі списком покупок та відстеження прогресу 
 
У розділі «Колекції» можна зручно сортувати та зберігати рецепти (рисунок 
В.7). Ці колекції працюють як звичайні папки: користувач може створювати 
власні або зберігати страви у базову папку «Улюблені». Додати новий рецепт 
дуже просто, і для цього є три способи: написати його вручну, імпортувати вже 
готовий з інтернету або ж згенерувати за допомогою штучного інтелекту з тих 
продуктів, що зараз є в наявності.  
В налаштуваннях можна змінити мову інтерфейсу, тему оформлення, а 
також вказати свої дієтичні вподобання чи алергії (рисунок В.8). Ці дані будуть 
автоматично враховуватися системою під час підбору рецептів для користувача. 
 
120 
 482.ЧДТУ 262251 34 01 7 
 
Рисунок В.7 – Інтерфейс екрана колекцій 
 
 
Рисунок В.8 – Налаштування профілю та харчових вподобань 
121 
 
ДОДАТОК Г 
 
 
  
 
 
 
 
 
 
 
«Мобільний застосунок для інтелектуального управління домашньою 
продуктовою коморою» 
 
 
 
Графічні матеріали 
482.ЧДТУ 262251 90 01 
Листів 16 
 
 
 
 
 
Розробник ________________ Мельник В. А. 
 
 
 
2026 
 482.ЧДТУ 262251 90 01   2 
 
Рисунок Г.1 – Титульний слайд 
 
 
Рисунок Г.2 – Мета та об'єкт розробки 
123 
 482.ЧДТУ 262251 90 01   3 
 
Рисунок Г.3 – Завдання роботи 
 
 
Рисунок Г.4 – Аналіз технічної інформаці 
124 
 482.ЧДТУ 262251 90 01   4 
 
Рисунок Г.5 – Актуальність задачі 
 
 
Рисунок Г.6 – Аналіз існуючих аналогів 
 
125 
 482.ЧДТУ 262251 90 01   5 
 
Рисунок Г.7 – Аналіз способів вирішення задачі 
 
 
Рисунок Г.8 – Постановка задачі 
 
126 
 482.ЧДТУ 262251 90 01   6 
 
Рисунок Г.9 – Формування вимог 
 
 
Рисунок Г.10 – Діаграма прецедентів 
 
127 
 482.ЧДТУ 262251 90 01   7 
 
Рисунок Г.11 – Діаграма класів 
 
 
Рисунок Г.12 – Діаграма пакетів 
 
128 
 482.ЧДТУ 262251 90 01   8 
 
Рисунок Г.13 – Діаграма компонентів 
 
 
Рисунок Г.14 – Діаграма розгортання 
 
129 
 482.ЧДТУ 262251 90 01   9 
 
Рисунок Г.15 – Діаграма діяльності 
 
 
Рисунок Г.16 – Діаграма послідовності 
 
130 
 482.ЧДТУ 262251 90 01   10 
 
Рисунок Г.17 – Діаграма комунікації 
 
 
Рисунок Г.18 – Діаграма скінченого автомату 
131 
 482.ЧДТУ 262251 90 01   11 
 
Рисунок Г.19 – Вибір засобів розробки 
 
 
Рисунок Г.20 – Структурна схема. Функціональна схема 
132 
 482.ЧДТУ 262251 90 01   12 
 
Рисунок Г.21 – Логічна схема 
 
 
Рисунок Г.22 – Структура бази даних 
133 
 482.ЧДТУ 262251 90 01   13 
 
Рисунок Г.23 – Розробка інтерфейсу користувача 
 
 
Рисунок Г.24– Модульне тестування 
 
134 
 482.ЧДТУ 262251 90 01   14 
 
Рисунок Г.25 – Інтеграційне теустування 
 
 
Рисунок Г.26 – Системне тестування 
 
135 
 482.ЧДТУ 262251 90 01   15 
 
Рисунок Г.27 – Приймальне тестування 
 
 
Рисунок Г.28 – Висновки та перспективи розвитку 
 
136 
 482.ЧДТУ 262251 90 01   16 
 
Рисунок Г.29 – Дякую за увагу! 
 
 
Рисунок Г.30 – Відео 
137