Merge pull request 'TIST-16: Add new recipes by user' (#42) from TIST-16 into develop
Reviewed-on: #42 Reviewed-by: greendavid004 <davidkatrinka1995@gmail.com>
This commit is contained in:
commit
7012c9aa85
@ -44,6 +44,11 @@ class CatalogController extends BaseController
|
|||||||
'is_having' => true
|
'is_having' => true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
$fields[] = array(
|
||||||
|
'name' => 'obj.status',
|
||||||
|
'type' => '=',
|
||||||
|
'value' => 'publish'
|
||||||
|
);
|
||||||
|
|
||||||
$context['recipes_count'] = RecipeModel::count($fields);
|
$context['recipes_count'] = RecipeModel::count($fields);
|
||||||
$context['recipes'] = RecipeModel::filter(
|
$context['recipes'] = RecipeModel::filter(
|
||||||
|
|||||||
@ -2,13 +2,71 @@
|
|||||||
|
|
||||||
namespace Lycoreco\Apps\Recipes\Controllers;
|
namespace Lycoreco\Apps\Recipes\Controllers;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Lycoreco\Apps\Recipes\Models\RecipeModel;
|
||||||
use Lycoreco\Includes\BaseController;
|
use Lycoreco\Includes\BaseController;
|
||||||
use Lycoreco\Apps\Recipes\Models\CategoryModel;
|
use Lycoreco\Apps\Recipes\Models\CategoryModel;
|
||||||
|
use Lycoreco\Apps\Recipes\Models\IngredientInRecipeModel;
|
||||||
|
use Lycoreco\Includes\Model\ValidationError;
|
||||||
|
|
||||||
class SingleSubmitController extends BaseController
|
class SingleSubmitController extends BaseController
|
||||||
{
|
{
|
||||||
protected $template_name = APPS_PATH . '/Recipes/Templates/single-submit.php';
|
protected $template_name = APPS_PATH . '/Recipes/Templates/single-submit.php';
|
||||||
|
|
||||||
|
protected function post()
|
||||||
|
{
|
||||||
|
$title = $_POST['title'] ?? '';
|
||||||
|
$description = $_POST['description'] ?? '';
|
||||||
|
$image = $_FILES['image'] ?? null;
|
||||||
|
$estimated_time = $_POST['est-time'] ?? 0;
|
||||||
|
$estimated_price = $_POST['est-price'] ?? 0;
|
||||||
|
$category_id = $_POST['category'] ?? null;
|
||||||
|
|
||||||
|
$ingredient_ids = $_POST['ing-id'] ?? [];
|
||||||
|
$ingredient_counts = $_POST['ing-count'] ?? [];
|
||||||
|
|
||||||
|
$recipe = new RecipeModel();
|
||||||
|
$recipe->field_title = $title;
|
||||||
|
$recipe->field_instruction = $description;
|
||||||
|
|
||||||
|
if($image) {
|
||||||
|
$file_url = upload_file($image, RecipeModel::get_table_name() . '/', 'image');
|
||||||
|
$recipe->field_image_url = $file_url;
|
||||||
|
}
|
||||||
|
$recipe->field_estimated_time = $estimated_time;
|
||||||
|
$recipe->field_estimated_price = $estimated_price;
|
||||||
|
$recipe->field_category_id = $category_id;
|
||||||
|
$recipe->field_author_id = CURRENT_USER->get_id();
|
||||||
|
$recipe->field_status = 'pending';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$recipe->save();
|
||||||
|
}
|
||||||
|
catch (ValidationError $ex) {
|
||||||
|
$this->context['error_message'] = $ex->getMessage();
|
||||||
|
}
|
||||||
|
catch (Exception $ex) {
|
||||||
|
$this->context['error_message'] = "Unexpected error";
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($ingredient_ids); $i++) {
|
||||||
|
$ing_id = $ingredient_ids[$i];
|
||||||
|
$ing_count = (int) $ingredient_counts[$i];
|
||||||
|
|
||||||
|
if($ing_count <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$relation = new IngredientInRecipeModel();
|
||||||
|
$relation->field_ingredient_id = $ing_id;
|
||||||
|
$relation->field_amount = $ing_count;
|
||||||
|
$relation->field_recipe_id = $recipe->get_id();
|
||||||
|
|
||||||
|
$relation->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect_to($recipe->get_absolute_url());
|
||||||
|
}
|
||||||
|
|
||||||
public function get_context_data()
|
public function get_context_data()
|
||||||
{
|
{
|
||||||
$context = parent::get_context_data();
|
$context = parent::get_context_data();
|
||||||
|
|||||||
@ -25,7 +25,7 @@ class CategoryModel extends BaseModel
|
|||||||
|
|
||||||
public static function get_cat_values()
|
public static function get_cat_values()
|
||||||
{
|
{
|
||||||
$cat_list = self::filter(array());
|
$cat_list = self::filter(array(), count: 200);
|
||||||
$result = array();
|
$result = array();
|
||||||
foreach($cat_list as $cat) {
|
foreach($cat_list as $cat) {
|
||||||
$result[] = [ $cat->get_id(), $cat->field_name ];
|
$result[] = [ $cat->get_id(), $cat->field_name ];
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Lycoreco\Apps\Recipes\Models;
|
namespace Lycoreco\Apps\Recipes\Models;
|
||||||
|
|
||||||
use Lycoreco\Includes\Model\BaseModel;
|
use Lycoreco\Includes\Model\BaseModel;
|
||||||
@ -39,9 +40,7 @@ class RecipeModel extends BaseModel
|
|||||||
'join_table' => 'users us ON us.id = obj.author_id'
|
'join_table' => 'users us ON us.id = obj.author_id'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'field' => [
|
'field' => [],
|
||||||
|
|
||||||
],
|
|
||||||
'join_table' => 'recipe_ingredients tb2 ON tb2.recipe_id = obj.id'
|
'join_table' => 'recipe_ingredients tb2 ON tb2.recipe_id = obj.id'
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@ -57,21 +56,22 @@ class RecipeModel extends BaseModel
|
|||||||
'status' => 'string',
|
'status' => 'string',
|
||||||
'created_at' => 'DateTime'
|
'created_at' => 'DateTime'
|
||||||
];
|
];
|
||||||
protected static function get_additional_fields(){
|
protected static function get_additional_fields()
|
||||||
|
{
|
||||||
$add_fields = parent::get_additional_fields();
|
$add_fields = parent::get_additional_fields();
|
||||||
|
|
||||||
// If user is authorized, we also check product in thw wishlist
|
// If user is authorized, we also check product in thw wishlist
|
||||||
if(CURRENT_USER) {
|
if (CURRENT_USER) {
|
||||||
$add_fields = array_merge($add_fields, array(
|
$add_fields = array_merge($add_fields, array(
|
||||||
[
|
[
|
||||||
"field" => [
|
"field" => [
|
||||||
"MAX(CASE WHEN fav.user_id = ". CURRENT_USER->get_id() ." THEN 1 ELSE 0 END) AS is_in_favorite"
|
"MAX(CASE WHEN fav.user_id = " . CURRENT_USER->get_id() . " THEN 1 ELSE 0 END) AS is_in_favorite"
|
||||||
],
|
],
|
||||||
"join_table" => "recipe_favorites fav ON fav.recipe_id = obj.id"
|
"join_table" => "recipe_favorites fav ON fav.recipe_id = obj.id"
|
||||||
]
|
]
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if(CURRENT_USER) {
|
if (CURRENT_USER) {
|
||||||
$add_fields = array_merge($add_fields, array(
|
$add_fields = array_merge($add_fields, array(
|
||||||
[
|
[
|
||||||
"field" => [
|
"field" => [
|
||||||
@ -106,7 +106,7 @@ class RecipeModel extends BaseModel
|
|||||||
}
|
}
|
||||||
public function get_absolute_url()
|
public function get_absolute_url()
|
||||||
{
|
{
|
||||||
return get_permalink('recipes:single', [ $this->get_id() ]);
|
return get_permalink('recipes:single', [$this->get_id()]);
|
||||||
}
|
}
|
||||||
public function get_price()
|
public function get_price()
|
||||||
{
|
{
|
||||||
@ -129,7 +129,30 @@ class RecipeModel extends BaseModel
|
|||||||
return nl2br(trim($this->field_instruction));
|
return nl2br(trim($this->field_instruction));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_time(){
|
public function get_time()
|
||||||
|
{
|
||||||
return $this->field_estimated_time . " minutes";
|
return $this->field_estimated_time . " minutes";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function valid()
|
||||||
|
{
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
|
if(mb_strlen($this->field_title) <= 3)
|
||||||
|
$errors[] = 'Title field must be more than 3 symbols';
|
||||||
|
|
||||||
|
if($this->field_estimated_time <= 0)
|
||||||
|
$errors[] = "Estimated time must be more than 0 minutes";
|
||||||
|
|
||||||
|
if($this->field_estimated_price <= 0)
|
||||||
|
$errors[] = "Estimated price must be more than 0$";
|
||||||
|
|
||||||
|
if($this->field_category_id === null)
|
||||||
|
$errors[] = "Recipe must have category";
|
||||||
|
|
||||||
|
if(!empty($errors))
|
||||||
|
return $errors;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -9,12 +9,21 @@ the_header(
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
?>
|
?>
|
||||||
|
<link rel="stylesheet" href="<?php echo ASSETS_PATH . '/css/single-submit.css' ?>">
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="submit-recipe">
|
<div class="submit-recipe">
|
||||||
<h1 class="title">Submit a Recipe</h1>
|
<h1 class="title">Submit a Recipe</h1>
|
||||||
</div>
|
</div>
|
||||||
<form action="" class="single-submit-form" method="post" enctype="multipart/form-data">
|
<?php
|
||||||
|
if(isset($context['success_message']))
|
||||||
|
the_alert($context['success_message'], 'success');
|
||||||
|
|
||||||
|
if(isset($context['error_message']))
|
||||||
|
the_alert($context['error_message'], 'warning');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<form class="single-submit-form" method="post" enctype="multipart/form-data">
|
||||||
<label for="title-input">Title</label><span>*</span>
|
<label for="title-input">Title</label><span>*</span>
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<input type="text" id="title-input" name="title" placeholder="Enter the recipe title" required>
|
<input type="text" id="title-input" name="title" placeholder="Enter the recipe title" required>
|
||||||
@ -26,28 +35,34 @@ the_header(
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label for="ingredients-input">Ingredients</label><span>*</span>
|
<label for="ingredients-input">Ingredients</label><span>*</span>
|
||||||
<button type="button" id="add-ingredient-btn" class="btn btn-primary hover-anim add-ingredient-btn">Add new
|
|
||||||
ingredient</button>
|
|
||||||
<div id="overlay" class="hidden"></div>
|
|
||||||
<div class="ing-modal hidden" id="ingredient-modal">
|
|
||||||
<div id="new-ingredient">
|
|
||||||
<label for="ing-name-input">Ingredient Name</label><span>*</span>
|
|
||||||
<div class="input">
|
|
||||||
<input type="text" id="ing-name-input" name="title" placeholder="Enter the ingredient name"
|
|
||||||
required>
|
|
||||||
</div>
|
|
||||||
<label for="unit-input">Unit of measure</label><span>*</span>
|
|
||||||
<div class="input">
|
|
||||||
<input type="text" id="ing-unit-input" name="title" placeholder="Enter the unit of measure"
|
|
||||||
required>
|
|
||||||
</div>
|
|
||||||
<button type="button" id="new-ingredient-submit" class="btn btn-primary hover-anim">Add new ingredient</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="input">
|
<table class="ingredients-table">
|
||||||
select 2 goes here
|
<thead>
|
||||||
</div>
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="ing-table-rows">
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div id="search-ingredient">
|
||||||
|
<input type="text" placeholder="Search ingredients...">
|
||||||
|
|
||||||
|
<div class="custom-select-dropdown" hidden>
|
||||||
|
<div class="dropdown-item hover-anim">Vegetable Oil</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" id="add-ingredient-btn" class="btn btn-primary hover-anim add-ingredient-btn">Create new</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
|
||||||
<label for="image-input">Image</label><span>*</span>
|
<label for="image-input">Image</label><span>*</span>
|
||||||
<div class="input-file">
|
<div class="input-file">
|
||||||
@ -56,12 +71,12 @@ the_header(
|
|||||||
|
|
||||||
<label for="time-input">Estimated Time (minutes)</label><span>*</span>
|
<label for="time-input">Estimated Time (minutes)</label><span>*</span>
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<input type="text" id="time-input" name="est-time" placeholder="Enter the recipe estimated time" required>
|
<input type="number" id="time-input" name="est-time" placeholder="Enter the recipe estimated time" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label for="price-input">Estimated Price ($)</label><span>*</span>
|
<label for="price-input">Estimated Price ($)</label><span>*</span>
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<input type="text" id="price-input" name="est-price" placeholder="Enter the recipe estimated price"
|
<input type="number" id="price-input" name="est-price" placeholder="Enter the recipe estimated price"
|
||||||
required>
|
required>
|
||||||
</div>
|
</div>
|
||||||
<label for="category-select">Category</label><span>*</span>
|
<label for="category-select">Category</label><span>*</span>
|
||||||
@ -82,6 +97,23 @@ the_header(
|
|||||||
<button type="submit" class="btn btn-primary hover-anim">Submit Recipe</button>
|
<button type="submit" class="btn btn-primary hover-anim">Submit Recipe</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div id="overlay" class="hidden"></div>
|
||||||
|
<div class="ing-modal hidden" id="ingredient-modal">
|
||||||
|
<div id="new-ingredient">
|
||||||
|
<label for="ing-name-input">Ingredient Name</label><span>*</span>
|
||||||
|
<div class="input">
|
||||||
|
<input type="text" id="ing-name-input" name="title" placeholder="Enter the ingredient name"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<label for="unit-input">Unit of measure</label><span>*</span>
|
||||||
|
<div class="input">
|
||||||
|
<input type="text" id="ing-unit-input" name="title" placeholder="Enter the unit of measure"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="new-ingredient-submit" class="btn btn-primary hover-anim">Add new ingredient</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
23
assets/css/single-submit.css
Normal file
23
assets/css/single-submit.css
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
.ingredients-table {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.ingredients-table .input {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.ingredients-table .input input[type="number"] {
|
||||||
|
height: auto;
|
||||||
|
padding: 5px;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
#search-ingredient {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#search-ingredient input {
|
||||||
|
border: 0;
|
||||||
|
padding: 5px 0;
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
.custom-select-dropdown .dropdown-item:last-child {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
@ -1076,6 +1076,16 @@ input[type="checkbox"]{
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid #b3b3b3;
|
border: 1px solid #b3b3b3;
|
||||||
border-collapse: separate;
|
border-collapse: separate;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.ingredients-table i {
|
||||||
|
color: #f00;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.ingredients-table td:nth-child(2) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ingredients-table th {
|
.ingredients-table th {
|
||||||
@ -1248,7 +1258,7 @@ label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.add-ingredient-btn{
|
.add-ingredient-btn{
|
||||||
margin-bottom: 25px;
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.single-submit-form span{
|
.single-submit-form span{
|
||||||
|
|||||||
@ -53,3 +53,94 @@ ingredientSubmitBtn.addEventListener('click', async (e) => {
|
|||||||
ingModal.classList.add('hidden');
|
ingModal.classList.add('hidden');
|
||||||
overlay.classList.add('hidden');
|
overlay.classList.add('hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const searchIngredientWrapper = document.getElementById('search-ingredient');
|
||||||
|
const searchIngInput = searchIngredientWrapper.querySelector('input');
|
||||||
|
const searchIngDropdown = searchIngredientWrapper.querySelector('.custom-select-dropdown');
|
||||||
|
|
||||||
|
const tableIngRows = document.querySelector('.ing-table-rows');
|
||||||
|
const ingredientsAdded = new Map();
|
||||||
|
|
||||||
|
searchIngInput.addEventListener('input', function () {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
|
||||||
|
searchTimeout = setTimeout(async () => {
|
||||||
|
let searchValue = this.value.trim();
|
||||||
|
|
||||||
|
|
||||||
|
if (searchValue.length < 3) {
|
||||||
|
searchIngInput.innerHTML = '';
|
||||||
|
searchIngDropdown.hidden = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('action', 'search_ingredient');
|
||||||
|
formData.append('query', searchValue);
|
||||||
|
|
||||||
|
const response = await fetch('/ajax', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const message = json.error || 'Something went wrong.';
|
||||||
|
showToastify(message, 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = json.result;
|
||||||
|
|
||||||
|
searchIngDropdown.innerHTML = '';
|
||||||
|
|
||||||
|
if (results.length > 0) {
|
||||||
|
results.forEach(ingredient => {
|
||||||
|
const ingredientName = `${ingredient.field_name} (${ingredient.field_unit_name})`;
|
||||||
|
|
||||||
|
const option = document.createElement('div');
|
||||||
|
option.className = "dropdown-item hover-anim";
|
||||||
|
option.textContent = ingredientName;
|
||||||
|
|
||||||
|
option.addEventListener('click', (e) => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
|
||||||
|
if(ingredientsAdded.get(ingredient.id) != undefined) {
|
||||||
|
showToastify("This field already exists.", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
row.innerHTML += `
|
||||||
|
<td>${ingredientName}</td>
|
||||||
|
<td>
|
||||||
|
<input class="ing-id" type="number" name="ing-id[]" hidden />
|
||||||
|
<div class="input ing-name"><input type="number" name="ing-count[]" value="1" /></div>
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
row.querySelector(".ing-id").value = ingredient.id;
|
||||||
|
|
||||||
|
|
||||||
|
const rowDeleteBtn = row.querySelector('i');
|
||||||
|
rowDeleteBtn.addEventListener('click', (e) => {
|
||||||
|
ingredientsAdded.delete(ingredient.id);
|
||||||
|
row.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
ingredientsAdded.set(ingredient.id, ingredientName);
|
||||||
|
tableIngRows.append(row);
|
||||||
|
searchIngInput.value = '';
|
||||||
|
searchIngDropdown.hidden = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
searchIngDropdown.append(option);
|
||||||
|
});
|
||||||
|
searchIngDropdown.hidden = false;
|
||||||
|
} else {
|
||||||
|
searchIngDropdown.innerHTML = `<div class="search-result-item">No recipes found</div>`;
|
||||||
|
searchIngDropdown.hidden = false;
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
BIN
media/recipes/caesar_686a6c042a944.jpg
Normal file
BIN
media/recipes/caesar_686a6c042a944.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
BIN
media/recipes/garlic_686a6a714284a.jpg
Normal file
BIN
media/recipes/garlic_686a6a714284a.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 305 KiB |
Loading…
x
Reference in New Issue
Block a user