From 3f67fe27d0f46053f7fe694ba59329441af007fd Mon Sep 17 00:00:00 2001 From: David Katrinka Date: Sat, 5 Jul 2025 20:19:45 +0200 Subject: [PATCH 1/4] TIST-39: added ajax to daily meals select --- apps/Recipes/Templates/single.php | 1 + assets/js/single.js | 43 ++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/apps/Recipes/Templates/single.php b/apps/Recipes/Templates/single.php index 0cd22ae..a2df27d 100644 --- a/apps/Recipes/Templates/single.php +++ b/apps/Recipes/Templates/single.php @@ -12,6 +12,7 @@ the_header( ?> +
diff --git a/assets/js/single.js b/assets/js/single.js index fd96f5b..5f15fe0 100644 --- a/assets/js/single.js +++ b/assets/js/single.js @@ -17,11 +17,30 @@ document.addEventListener('click', (e) => { const options = document.querySelectorAll('.dropdown-item'); options.forEach(option => { - option.addEventListener('click', () => { + option.addEventListener('click', async (e) => { const selectedValue = option.getAttribute('data-value'); options.forEach(opt => opt.classList.remove('dropdown-selected')); + const formData = new FormData(); + formData.append('action', 'usermenu'); + formData.append('recipe_id', recipeId); + formData.append('dayofweek', selectedValue); + + const response = await fetch('/ajax', { + method: 'POST', + body: formData + }); + + const json = await response.json(); + + if (!response.ok) { + const message = json.error; + showToastify(message, 'error'); + return; + } + + if (selectedValue === 'remove') { toggleBtn.textContent = 'Add to list'; } else { @@ -31,7 +50,7 @@ options.forEach(option => { dropdown.classList.add('hidden'); - alert(`You selected: ${selectedValue}`); + showToastify(json.success, 'success'); }); }); @@ -42,20 +61,20 @@ const qrPopup = document.querySelector(".qr-popup"); const overlay = document.getElementById('overlay'); qrBtn.addEventListener("click", () => { - + qrPopup.classList.toggle("hidden") overlay.classList.toggle("hidden") - + qrContainer.innerHTML = ""; - + new QRCode(qrContainer, { text: window.location.href, width: 200, height: 200 }); - + setTimeout(() => { const qrImg = qrContainer.querySelector("img"); if (qrImg) { @@ -64,8 +83,8 @@ qrBtn.addEventListener("click", () => { }, 500); }); -document.addEventListener('click', (e) =>{ - if(!qrPopup.contains(e.target) && !qrBtn.contains(e.target)){ +document.addEventListener('click', (e) => { + if (!qrPopup.contains(e.target) && !qrBtn.contains(e.target)) { qrPopup.classList.add("hidden"); overlay.classList.add("hidden"); } @@ -85,7 +104,7 @@ favoriteBtn.addEventListener('click', async (e) => { formData.append('type', type); favoriteBtn.disabled = true; - const response = await fetch('/ajax', { + const response = await fetch('/ajax', { method: 'POST', body: formData }); @@ -93,20 +112,20 @@ favoriteBtn.addEventListener('click', async (e) => { const json = await response.json(); - if(!response.ok) { + if (!response.ok) { const message = json.error; showToastify(message, 'error'); return; } favoriteBtn.classList.toggle('active'); - if(type == 'add') { + if (type == 'add') { favoriteIcon.classList.remove('fa-regular'); favoriteIcon.classList.add('fa-solid'); } else { favoriteIcon.classList.add('fa-regular'); favoriteIcon.classList.remove('fa-solid'); } - + showToastify(json.success, 'success'); }); \ No newline at end of file From b854c923779670495e24c0fb5e4fe709c42325c7 Mon Sep 17 00:00:00 2001 From: Stepan Date: Sat, 5 Jul 2025 20:20:33 +0200 Subject: [PATCH 2/4] Filled index, meals and favorites page. Added pagination for recipes --- apps/Index/Controllers/HomepageController.php | 31 +++ apps/Index/Templates/index.php | 255 ++---------------- .../Recipes/Controllers/CatalogController.php | 6 +- .../Controllers/DailyMealsController.php | 18 +- .../Controllers/FavoritesController.php | 36 ++- apps/Recipes/Models/RecipeModel.php | 7 + apps/Recipes/Models/RecipeUserMenu.php | 42 ++- apps/Recipes/Templates/catalog.php | 15 +- .../Templates/components/recipe-ings-item.php | 18 ++ apps/Recipes/Templates/daily-meals.php | 135 +--------- apps/Recipes/Templates/favorites.php | 11 +- apps/Recipes/components.php | 4 + functions.php | 6 +- includes/Const/recipes.php | 2 +- 14 files changed, 200 insertions(+), 386 deletions(-) create mode 100644 apps/Recipes/Templates/components/recipe-ings-item.php diff --git a/apps/Index/Controllers/HomepageController.php b/apps/Index/Controllers/HomepageController.php index 2114f69..eb9111d 100644 --- a/apps/Index/Controllers/HomepageController.php +++ b/apps/Index/Controllers/HomepageController.php @@ -2,9 +2,40 @@ namespace Lycoreco\Apps\Index\Controllers; +use Lycoreco\Apps\Recipes\Models\CategoryModel; +use Lycoreco\Apps\Recipes\Models\RecipeModel; +use Lycoreco\Apps\Recipes\Models\RecipeUserMenu; use Lycoreco\Includes\BaseController; +require_once(INCLUDES_PATH . '/Const/recipes.php'); + class HomepageController extends BaseController { protected $template_name = APPS_PATH . '/Index/Templates/index.php'; + + public function get_context_data() { + $context = parent::get_context_data(); + + $context['latest_recipes'] = RecipeModel::filter(array( + [ + 'name' => 'obj.status', + 'type' => '=', + 'value' => 'publish' + ]), + ['-obj.created_at'], + 3 + ); + $context['categories'] = CategoryModel::filter(); + $dayNumber = date("w"); + $dayofweek = DAYS_OF_WEEK[$dayNumber]; + + if(CURRENT_USER) { + $context['usermenu_recipe_prefetch'] = RecipeUserMenu::get_prefetch_recipes(CURRENT_USER, $dayofweek); + } + else { + $context['usermenu_recipe_prefetch'] = [ ]; + } + + return $context; + } } diff --git a/apps/Index/Templates/index.php b/apps/Index/Templates/index.php index 1a6c246..9c03da4 100644 --- a/apps/Index/Templates/index.php +++ b/apps/Index/Templates/index.php @@ -1,4 +1,6 @@ - @@ -86,87 +67,14 @@
+ +
diff --git a/apps/Recipes/Controllers/CatalogController.php b/apps/Recipes/Controllers/CatalogController.php index 5a63e84..c12c6fd 100644 --- a/apps/Recipes/Controllers/CatalogController.php +++ b/apps/Recipes/Controllers/CatalogController.php @@ -9,7 +9,7 @@ use Lycoreco\Apps\Recipes\Models\{ }; use Lycoreco\Includes\BaseController; -define('CATALOG_MAX_RECIPES', 20); +define('CATALOG_MAX_RECIPES', 10); class CatalogController extends BaseController { @@ -45,10 +45,12 @@ class CatalogController extends BaseController ); } + $context['recipes_count'] = RecipeModel::count($fields); $context['recipes'] = RecipeModel::filter( $fields, ['-obj.created_at'], - CATALOG_MAX_RECIPES + CATALOG_MAX_RECIPES, + offset: calc_page_offset(CATALOG_MAX_RECIPES, $context['page']) ); return $context; diff --git a/apps/Recipes/Controllers/DailyMealsController.php b/apps/Recipes/Controllers/DailyMealsController.php index c50320f..dad7262 100644 --- a/apps/Recipes/Controllers/DailyMealsController.php +++ b/apps/Recipes/Controllers/DailyMealsController.php @@ -2,12 +2,26 @@ namespace Lycoreco\Apps\Recipes\Controllers; - +use Lycoreco\Apps\Recipes\Models\RecipeUserMenu; use Lycoreco\Includes\BaseController; - +require_once(INCLUDES_PATH . '/Const/recipes.php'); class DailyMealsController extends BaseController { protected $template_name = APPS_PATH . '/Recipes/Templates/daily-meals.php'; + + protected $allow_role = 'user'; + + function get_context_data() { + $context = parent::get_context_data(); + + $context['weeks'] = array(); + + foreach (DAYS_OF_WEEK as $week) { + $context['weeks'][$week] = RecipeUserMenu::get_prefetch_recipes(CURRENT_USER, $week); + } + + return $context; + } } \ No newline at end of file diff --git a/apps/Recipes/Controllers/FavoritesController.php b/apps/Recipes/Controllers/FavoritesController.php index ab406cc..c5047d6 100644 --- a/apps/Recipes/Controllers/FavoritesController.php +++ b/apps/Recipes/Controllers/FavoritesController.php @@ -2,24 +2,48 @@ namespace Lycoreco\Apps\Recipes\Controllers; +use Lycoreco\Apps\Recipes\Models\FavoriteModel; use Lycoreco\Apps\Recipes\Models\RecipeModel; use Lycoreco\Includes\BaseController; -define('FAVORITES_MAX_RECIPES', 20); +define('FAVORITES_MAX_RECIPES', 500); class FavoritesController extends BaseController { protected $template_name = APPS_PATH . '/Recipes/Templates/favorites.php'; + protected $allow_role = 'user'; public function get_context_data() { $context = parent::get_context_data(); + + $favorite_recipes = FavoriteModel::filter(array( + [ + 'name' => 'obj.user_id', + 'type' => '=', + 'value' => CURRENT_USER->get_id() + ] + )); + $fav_ids = array_map(function($recipe) { + return $recipe->field_recipe_id; + }, $favorite_recipes); - $context['recipes'] = RecipeModel::filter( - [], - ['-obj.created_at'], - FAVORITES_MAX_RECIPES - ); + if(!empty($fav_ids)) { + $context['recipes'] = RecipeModel::filter( + array( + [ + 'name' => 'obj.id', + 'type' => 'IN', + 'value' => $fav_ids + ] + ), + ['-obj.created_at'], + FAVORITES_MAX_RECIPES + ); + } + else { + $context['recipes'] = []; + } return $context; } diff --git a/apps/Recipes/Models/RecipeModel.php b/apps/Recipes/Models/RecipeModel.php index 1358a09..1d0fee5 100644 --- a/apps/Recipes/Models/RecipeModel.php +++ b/apps/Recipes/Models/RecipeModel.php @@ -16,6 +16,7 @@ class RecipeModel extends BaseModel public $field_created_at; public $category_name; + public $author_username; public $is_in_favorite = 0; const STATUS = [['publish', 'Publish'], ['pending', 'Pending']]; @@ -30,6 +31,12 @@ class RecipeModel extends BaseModel ], 'join_table' => 'categories tb1 ON tb1.id = obj.category_id' ], + [ + 'field' => [ + 'us.username AS author_username' + ], + 'join_table' => 'users us ON us.id = obj.author_id' + ], [ 'field' => [ diff --git a/apps/Recipes/Models/RecipeUserMenu.php b/apps/Recipes/Models/RecipeUserMenu.php index 4de708d..4141b05 100644 --- a/apps/Recipes/Models/RecipeUserMenu.php +++ b/apps/Recipes/Models/RecipeUserMenu.php @@ -2,6 +2,7 @@ namespace Lycoreco\Apps\Recipes\Models; +use Lycoreco\Apps\Users\Models\UserModel; use Lycoreco\Includes\Model\BaseModel; class RecipeUserMenu extends BaseModel @@ -39,7 +40,7 @@ class RecipeUserMenu extends BaseModel { require_once INCLUDES_PATH . '/Const/recipes.php'; - if(!in_array($this->field_dayofweek, DAYS_OF_WEEK)) + if (!in_array($this->field_dayofweek, DAYS_OF_WEEK)) return ['Day of Week is not valid']; $recipe = RecipeModel::get(array( @@ -49,9 +50,44 @@ class RecipeUserMenu extends BaseModel 'value' => $this->field_recipe_id ] )); - if(!$recipe) + if (!$recipe) return ['Recipe does not exist']; return true; } -} \ No newline at end of file + + public static function get_prefetch_recipes(UserModel $user, string $week) { + $usermenus = RecipeUserMenu::filter(array( + [ + 'name' => 'obj.user_id', + 'type' => '=', + 'value' => $user->get_id(), + ], + [ + 'name' => 'obj.dayofweek', + 'type' => '=', + 'value' => $week + ] + ), count: 100); + + if(empty($usermenus)) + return []; + + $usermenu_ids = array_map(function($usermenu) { + return $usermenu->field_recipe_id; + }, $usermenus); + + + $recipes = RecipeModel::filter(array( + [ + 'name' => 'obj.id', + 'type' => 'IN', + 'value' => $usermenu_ids + ] + )); + + $prefetch_recipes_ings = prefetch_related($recipes, 'Recipes:IngredientInRecipeModel', 'recipe_id'); + + return $prefetch_recipes_ings; + } +} diff --git a/apps/Recipes/Templates/catalog.php b/apps/Recipes/Templates/catalog.php index 2c2d6ec..b08f088 100644 --- a/apps/Recipes/Templates/catalog.php +++ b/apps/Recipes/Templates/catalog.php @@ -13,12 +13,15 @@ the_header( ?>
-
- +
+
+ +
+
diff --git a/apps/Recipes/Templates/components/recipe-ings-item.php b/apps/Recipes/Templates/components/recipe-ings-item.php new file mode 100644 index 0000000..b9eb960 --- /dev/null +++ b/apps/Recipes/Templates/components/recipe-ings-item.php @@ -0,0 +1,18 @@ + +
+ meal-img +
+
+
+

field_title ?>

+ field_estimated_time ?>mins to make +
+
+
    + +
  • ingredient_name ?>
  • + +
+
+
+
\ No newline at end of file diff --git a/apps/Recipes/Templates/daily-meals.php b/apps/Recipes/Templates/daily-meals.php index 33ac0e9..56d4e46 100644 --- a/apps/Recipes/Templates/daily-meals.php +++ b/apps/Recipes/Templates/daily-meals.php @@ -1,4 +1,5 @@ diff --git a/apps/Recipes/components.php b/apps/Recipes/components.php index c0c76ef..a56faab 100644 --- a/apps/Recipes/components.php +++ b/apps/Recipes/components.php @@ -5,4 +5,8 @@ use Lycoreco\Apps\Recipes\Models\RecipeModel; function the_product_item(RecipeModel $recipe) { include APPS_PATH . '/Recipes/Templates/components/catalog-item.php'; +} +function the_product_recipes_item(RecipeModel $recipe, array $ingredients) +{ + include APPS_PATH . '/Recipes/Templates/components/recipe-ings-item.php'; } \ No newline at end of file diff --git a/functions.php b/functions.php index 1c36911..0159aa7 100644 --- a/functions.php +++ b/functions.php @@ -133,13 +133,13 @@ function the_pagination(int $count, int $elem_per_page, int $current_page) continue; $GET['page'] = $i; ?> -
  • +
  • 0 && $current_page <= $total_pages): ?>
  • -
    +
  • @@ -147,7 +147,7 @@ function the_pagination(int $count, int $elem_per_page, int $current_page) -
  • +
  • diff --git a/includes/Const/recipes.php b/includes/Const/recipes.php index 8b971ca..19b83d5 100644 --- a/includes/Const/recipes.php +++ b/includes/Const/recipes.php @@ -1,5 +1,5 @@ 'monday', 2 => 'tuesday', 3 => 'wednesday', 4 => 'thursday', 5 => 'friday', 6 => 'saturday', 0 => 'sunday' )); \ No newline at end of file From 81a88b93d506f8f29e88e11e206a74f9927d5e2f Mon Sep 17 00:00:00 2001 From: Stepan Date: Sat, 5 Jul 2025 20:35:34 +0200 Subject: [PATCH 3/4] Added in_usermenu field for RecipeModel --- apps/Recipes/Models/RecipeModel.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/Recipes/Models/RecipeModel.php b/apps/Recipes/Models/RecipeModel.php index 1d0fee5..561a84e 100644 --- a/apps/Recipes/Models/RecipeModel.php +++ b/apps/Recipes/Models/RecipeModel.php @@ -18,6 +18,7 @@ class RecipeModel extends BaseModel public $category_name; public $author_username; public $is_in_favorite = 0; + public $in_usermenu = false; const STATUS = [['publish', 'Publish'], ['pending', 'Pending']]; @@ -70,6 +71,16 @@ class RecipeModel extends BaseModel ] )); } + if(CURRENT_USER) { + $add_fields = array_merge($add_fields, array( + [ + "field" => [ + "MAX(m.dayofweek) AS in_usermenu" + ], + "join_table" => "recipe_usermenu m ON m.recipe_id = obj.id AND m.user_id = " . CURRENT_USER->get_id() + ] + )); + } return $add_fields; } From 4ccb8ac05e6a38be54f04673e6bc0efc2ccad282 Mon Sep 17 00:00:00 2001 From: David Katrinka Date: Sat, 5 Jul 2025 20:51:43 +0200 Subject: [PATCH 4/4] fixed display issue with select dropdown --- apps/Recipes/Templates/single.php | 2 +- assets/js/single.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/Recipes/Templates/single.php b/apps/Recipes/Templates/single.php index a2df27d..925223d 100644 --- a/apps/Recipes/Templates/single.php +++ b/apps/Recipes/Templates/single.php @@ -12,7 +12,7 @@ the_header( ?> - +
    diff --git a/assets/js/single.js b/assets/js/single.js index 5f15fe0..c8754d7 100644 --- a/assets/js/single.js +++ b/assets/js/single.js @@ -15,7 +15,16 @@ document.addEventListener('click', (e) => { const options = document.querySelectorAll('.dropdown-item'); +const inUsermenu = document.getElementById('in-usermenu').textContent; +if (inUsermenu) { + options.forEach(option => { + if (option.getAttribute('data-value') === inUsermenu && option.getAttribute('data-value') !== 'remove') { + option.classList.add('dropdown-selected'); + toggleBtn.textContent = option.textContent; + } + }); +} options.forEach(option => { option.addEventListener('click', async (e) => { const selectedValue = option.getAttribute('data-value');