Compare commits

...

2 Commits

Author SHA1 Message Date
58bd2bee3c Merge pull request 'Create pages for recipe on Admin Panel' (#8) from TIST-19 into develop
Reviewed-on: #8
Reviewed-by: greendavid004 <davidkatrinka1995@gmail.com>
2025-06-16 21:59:12 +02:00
abbf111b4e Create pages for recipe on Admin Panel 2025-06-16 21:46:01 +02:00
22 changed files with 532 additions and 39 deletions

View File

@ -38,6 +38,7 @@ abstract class AdminSingleController extends AdminBaseController
protected $field_title = 'field_id';
protected $edit_title_template = 'Edit [:verbose] "[:field]"';
protected $can_save = true;
protected $is_new = false;
/**
* Function names with $object attribute.
@ -53,6 +54,7 @@ abstract class AdminSingleController extends AdminBaseController
*/
public function __construct($is_new = false)
{
$this->is_new = $is_new;
$this->context['is_new'] = $is_new;
}

View File

@ -0,0 +1,21 @@
<?php
namespace Lycoreco\Apps\Admin\Controllers;
use Lycoreco\Includes\Model\ValidationError;
use Lycoreco\Apps\Recipes\Models\CategoryModel;
class AdminCategoryController extends Abstract\AdminSingleController
{
protected $model_сlass_name = "Lycoreco\Apps\Recipes\Models\CategoryModel";
protected $field_title = 'field_name';
protected $verbose_name = 'category';
protected $object_router_name = 'admin:category';
protected $fields = array(
[
'model_field' => 'name',
'input_type' => 'text',
'input_attrs' => ['required']
]
);
}

View File

@ -0,0 +1,17 @@
<?php
namespace Lycoreco\Apps\Admin\Controllers;
use Lycoreco\Includes\Model\ValidationError;
use Lycoreco\Apps\Recipes\Models\CategoryModel;
class AdminCategoryListController extends Abstract\AdminListController
{
protected $model_сlass_name = "Lycoreco\Apps\Recipes\Models\CategoryModel";
protected $table_fields = array(
'Name' => 'field_name'
);
protected $single_router_name = 'admin:category';
protected $create_router_name = 'admin:category-new';
protected $verbose_name = "category";
protected $verbose_name_multiply = "categories";
}

View File

@ -9,6 +9,49 @@ use Lycoreco\Apps\Users\Models\UserModel;
*/
class AdminDeleteController extends Abstract\AdminBaseController
{
const DELETE_MODELS = [
[
'url_name' => 'users',
'model' => 'Lycoreco\Apps\Users\Models\UserModel',
'field_display' => 'field_username',
'back_to' => 'admin:user',
'success_to' => 'admin:user-list'
],
[
'url_name' => 'user-banlist',
'model' => 'Lycoreco\Apps\Users\Models\BanlistModel',
'field_display' => 'field_reason',
'back_to' => 'admin:ban'
],
[
'url_name' => 'recipes',
'model' => 'Lycoreco\Apps\Recipes\Models\RecipeModel',
'field_display' => 'field_title',
'back_to' => 'admin:recipe',
'success_to' => 'admin:recipe-list'
],
[
'url_name' => 'ingredients',
'model' => 'Lycoreco\Apps\Recipes\Models\IngredientModel',
'field_display' => 'field_name',
'back_to' => 'admin:ingredient',
'success_to' => 'admin:ingredient-list'
],
[
'url_name' => 'categories',
'model' => 'Lycoreco\Apps\Recipes\Models\CategoryModel',
'field_display' => 'field_name',
'back_to' => 'admin:category',
'success_to' => 'admin:category-list'
],
[
'url_name' => 'recipe-ingredients',
'model' => 'Lycoreco\Apps\Recipes\Models\IngredientInRecipeModel',
'field_display' => 'ingredient_name',
'back_to' => 'admin:ing-cat-rel',
],
];
protected $template_name = APPS_PATH . '/Admin/Templates/delete.php';
/**
@ -22,17 +65,15 @@ class AdminDeleteController extends Abstract\AdminBaseController
$id = $this->url_context['url_2'];
$model_class = '';
switch ($this->url_context['url_1']) {
case 'users':
$model_class = "Lycoreco\Apps\Users\Models\UserModel";
foreach (self::DELETE_MODELS as $delete_model) {
if($this->url_context['url_1'] == $delete_model['url_name']) {
$model_class = $delete_model['model'];
break;
case 'user-banlist':
$model_class = "Lycoreco\Apps\Users\Models\BanlistModel";
break;
default:
return null;
}
}
if(empty($model_class))
return null;
return $model_class::get(array(
[
'name' => 'obj.id',
@ -52,16 +93,12 @@ class AdminDeleteController extends Abstract\AdminBaseController
// Display field to show what's model
$field = '';
$back_url = '';
switch ($this->url_context['url_1']) {
case 'users':
$back_url = get_permalink('admin:user', [$model->get_id()]);
$field = 'field_username';
break;
case 'user-banlist':
$back_url = get_permalink('admin:ban', [$model->get_id()]);
$field = 'field_reason';
break;
foreach (self::DELETE_MODELS as $delete_model) {
if($this->url_context['url_1'] == $delete_model['url_name']) {
$back_url = get_permalink($delete_model['back_to'], [$model->get_id()]);
$field = $delete_model['field_display'];
}
}
$context['back_url'] = $back_url;
@ -80,15 +117,15 @@ class AdminDeleteController extends Abstract\AdminBaseController
$model->delete();
// Redirect after delete
$type_model = $this->url_context['url_1'];
switch ($type_model) {
case 'users':
$link = get_permalink('admin:user-list');
break;
default:
$link = get_permalink('admin:home');
foreach (self::DELETE_MODELS as $delete_model) {
if($this->url_context['url_1'] == $delete_model['url_name']) {
if(isset($delete_model['success_to'])) {
$link = get_permalink($delete_model['success_to']);
}
break;
}
}
redirect_to($link);
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace Lycoreco\Apps\Admin\Controllers;
use Lycoreco\Includes\Model\ValidationError;
use Lycoreco\Apps\Recipes\Models\{
RecipeModel,
CategoryModel
};
class AdminRecipeController extends Abstract\AdminSingleController
{
protected $model_сlass_name = "Lycoreco\Apps\Recipes\Models\RecipeModel";
protected $field_title = 'field_title';
protected $verbose_name = 'recipe';
protected $object_router_name = 'admin:recipe';
protected $component_widgets = ['the_recipe_author', 'the_recipe_ingredients'];
protected $fields = array(
[
'model_field' => 'title',
'input_type' => 'text',
'input_attrs' => ['required']
],
[
'model_field' => 'instruction',
'input_type' => 'textarea',
'input_attrs' => ['required']
],
[
'model_field' => 'image_url',
'input_type' => 'image',
'input_label' => 'Image',
],
[
'model_field' => 'estimated_time',
'input_type' => 'number',
'input_attrs' => ['required'],
'input_label' => 'Estimated time (min)'
],
[
'model_field' => 'estimated_price',
'input_type' => 'number',
'input_attrs' => ['required'],
'input_label' => 'Estimated price ($)'
],
[
'model_field' => 'status',
'input_type' => 'select',
'input_attrs' => ['required'],
'input_values' => RecipeModel::STATUS
],
[
'model_field' => 'created_at',
'input_type' => 'text',
'dynamic_save' => false,
'input_label' => 'Created at',
'input_attrs' => ['disabled']
]
);
protected function before_save(&$object)
{
if($this->is_new)
{
$object->field_author_id = CURRENT_USER->get_id();
}
}
public function __construct($is_new = false) {
parent::__construct($is_new);
$this->fields[] = [
'model_field' => 'category_id',
'input_type' => 'select',
'input_label' => 'Categories',
'input_attrs' => ['required'],
'input_values' => CategoryModel::get_cat_values()
];
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Lycoreco\Apps\Admin\Controllers;
use Lycoreco\Includes\Model\ValidationError;
use Lycoreco\Apps\Recipes\Models\RecipeModel;
class AdminRecipeListController extends Abstract\AdminListController
{
protected $model_сlass_name = "Lycoreco\Apps\Recipes\Models\RecipeModel";
protected $table_fields = array(
'Title' => 'field_title',
'Price' => 'get_price()',
'Status ' => 'get_status()',
'Creaated at' => 'field_created_at',
);
protected $single_router_name = 'admin:recipe';
protected $create_router_name = 'admin:recipe-new';
protected $verbose_name = "recipe";
protected $verbose_name_multiply = "recipes";
}

View File

@ -0,0 +1,27 @@
<?php
namespace Lycoreco\Apps\Admin\Controllers;
use Lycoreco\Includes\Model\ValidationError;
use Lycoreco\Apps\Recipes\Models\IngredientModel;
class IngredientController extends Abstract\AdminSingleController
{
protected $model_сlass_name = "Lycoreco\Apps\Recipes\Models\IngredientModel";
protected $field_title = 'field_name';
protected $verbose_name = 'ingredient';
protected $object_router_name = 'admin:ingredient';
protected $fields = array(
[
'model_field' => 'name',
'input_type' => 'text',
'input_attrs' => ['required']
],
[
'model_field' => 'unit_name',
'input_type' => 'text',
'input_attrs' => ['required'],
'input_label' => 'Unit name'
]
);
}

View File

@ -0,0 +1,18 @@
<?php
namespace Lycoreco\Apps\Admin\Controllers;
use Lycoreco\Includes\Model\ValidationError;
use Lycoreco\Apps\Recipes\Models\IngredientModel;
class IngredientListController extends Abstract\AdminListController
{
protected $model_сlass_name = "Lycoreco\Apps\Recipes\Models\IngredientModel";
protected $table_fields = array(
'Name' => 'field_name',
'Unit' => 'field_unit_name'
);
protected $single_router_name = 'admin:ingredient';
protected $create_router_name = 'admin:ingredient-new';
protected $verbose_name = "ingredient";
protected $verbose_name_multiply = "ingredients";
}

View File

@ -0,0 +1,50 @@
<?php
namespace Lycoreco\Apps\Admin\Controllers;
use Lycoreco\Apps\Recipes\Models\{
IngredientInRecipeModel,
IngredientModel
};
class IngredientRecipeRelController extends Abstract\AdminSingleController
{
protected $model_сlass_name = "Lycoreco\Apps\Recipes\Models\IngredientInRecipeModel";
protected $field_title = 'ingredient_name';
protected $object_router_name = 'admin:ing-cat-rel';
protected $verbose_name = "ingredient in recipe";
protected $component_widgets = ['the_recipe_ingredients_relation'];
protected $fields = array(
[
'model_field' => 'recipe_id',
'input_type' => 'number',
'dynamic_save' => false,
'input_label' => 'Recipe id',
'input_attrs' => ['required', 'disabled']
],
[
'model_field' => 'amount',
'input_type' => 'text',
'input_attrs' => ['required']
],
);
protected function get_model()
{
$model = parent::get_model();
if($this->context['is_new'])
$model->field_recipe_id = (int)$this->url_context['url_1'];
return $model;
}
public function __construct($is_new = false) {
parent::__construct($is_new);
$this->fields[] = [
'model_field' => 'ingredient_id',
'input_type' => 'select',
'input_label' => 'Ingedient',
'input_attrs' => ['required'],
'input_values' => IngredientModel::get_ing_values()
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Lycoreco\Apps\Admin\Controllers;
class IngredientRecipeRelListController extends Abstract\AdminListController
{
protected $model_сlass_name = "Lycoreco\Apps\Recipes\Models\IngredientInRecipeModel";
protected $table_fields = array(
'Name' => 'ingredient_name',
'Amount' => 'get_count()'
);
protected $single_router_name = 'admin:ing-cat-rel';
protected $verbose_name = "ingredients in recipe";
protected $verbose_name_multiply = "ingredients in recipe";
public function custom_filter_fields()
{
$recipe_id = $this->url_context['url_1'];
return array(
[
'name' => 'obj.recipe_id',
'type' => '=',
'value' => $recipe_id
]
);
}
}

View File

@ -1,5 +1,33 @@
<?php
use Lycoreco\Includes\Routing\Router;
$sidebar_links = [
[
'name' => 'Dashboard',
'icon' => 'fa-solid fa-house',
'router_name' => 'admin:home',
],
[
'name' => 'Users',
'icon' => 'fa-solid fa-users',
'router_name' => 'admin:user-list',
],
[
'name' => 'Recipes',
'icon' => 'fa-solid fa-bowl-food',
'router_name' => 'admin:recipe-list'
],
[
'name' => 'Categories',
'icon' => 'fa-solid fa-tag',
'router_name' => 'admin:category-list'
],
[
'name' => 'Ingredients',
'icon' => 'fa-solid fa-carrot',
'router_name' => 'admin:ingredient-list'
]
];
?>
<!DOCTYPE html>
@ -45,19 +73,16 @@ use Lycoreco\Includes\Routing\Router;
<div class="wrapper-admin">
<aside class="admin-sidebar">
<a href="<?php the_permalink('admin:product-new') ?>" class="btn btn-primary"><i class="fa-solid fa-plus"></i> New Product</a>
<a href="<?php the_permalink('admin:recipe-new') ?>" class="btn btn-primary"><i class="fa-solid fa-plus"></i> New recipe</a>
<hr>
<ul class="admin-sidebar__list">
<?php foreach ($sidebar_links as $link): ?>
<li>
<a class="<?php echo Router::$current_router_name == 'admin:home' ? "active" : "" ?>" href="<?php the_permalink('admin:home') ?>">
<i class="fa-solid fa-house"></i> Dashboard
</a>
</li>
<li>
<a class="<?php echo Router::$current_router_name == 'admin:user-list' ? "active" : "" ?>" href="<?php the_permalink('admin:user-list') ?>">
<i class="fa-solid fa-users"></i> Users
<a class="<?php echo Router::$current_router_name == $link['router_name'] ? "active" : "" ?>" href="<?php the_permalink($link['router_name']) ?>">
<i class="<?= $link['icon'] ?>"></i> <?= $link['name'] ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</aside>
<div class="wrapper-admin__content">

View File

@ -0,0 +1,22 @@
<div class="admin-block">
<div class="admin-block__title">Ingredients</div>
<div class="admin-block__content">
<div class="admin-block__table">
<?php if (!empty($ingredients)): ?>
<?php foreach ($ingredients as $ing): ?>
<div class="row">
<div class="column"><a href="<?php the_permalink('admin:ing-cat-rel', [$ing->get_id()]) ?>"><?php echo $ing->ingredient_name ?></a></div>
<div class="column"><?php echo $ing->get_count() ?></div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="nothing">No ingredients</div>
<?php endif ?>
</div>
<div class="btn-control">
<a href="<?php the_permalink('admin:ing-cat-rel-list', [$recipe->get_id()]) ?>" class="btn">Show all</a>
<a href="<?php the_permalink('admin:ing-cat-rel-new', [$recipe->get_id()]) ?>" class="btn btn-primary">Add ingredient</a>
</div>
</div>
</div>

View File

@ -0,0 +1,10 @@
<div class="admin-block">
<div class="admin-block__title">Model relations</div>
<div class="admin-block__content">
<div class="admin-block__subtitle">Ingredient: </div>
<div><a href="<?php the_permalink('admin:ingredient', array($ingredient->get_id())) ?>"><?= $ingredient->field_name ?></a></div>
<div class="admin-block__subtitle">Recipe: </div>
<div><a href="<?php the_permalink('admin:recipe', array($recipe->get_id())) ?>"><?= $recipe->field_title ?></a></div>
</div>
</div>

View File

@ -0,0 +1,6 @@
<div class="admin-block">
<div class="admin-block__title">Author</div>
<div class="admin-block__content">
<a href="<?php the_permalink('admin:user', [$author->get_id()]) ?>"><?php echo $author->field_username ?></a>
</div>
</div>

View File

@ -4,6 +4,11 @@ use Lycoreco\Apps\Users\Models\{
UserModel,
BanlistModel
};
use Lycoreco\Apps\Recipes\Models\{
RecipeModel,
IngredientModel,
IngredientInRecipeModel
};
function the_admin_header(string $title)
{
@ -31,3 +36,48 @@ function the_user_banlist(UserModel $user)
require APPS_PATH . '/Admin/Templates/widgets/user_banlist.php';
}
function the_recipe_author(RecipeModel $recipe)
{
$author = UserModel::get(array(
[
'name' => 'obj.id',
'type' => '=',
'value' => $recipe->field_author_id
]
));
require APPS_PATH . '/Admin/Templates/widgets/recipe_author.php';
}
function the_recipe_ingredients(RecipeModel $recipe)
{
$ingredients = IngredientInRecipeModel::filter(array(
[
'name' => 'obj.recipe_id',
'type' => '=',
'value' => $recipe->get_id()
]
));
require APPS_PATH . '/Admin/Templates/widgets/ingredients_in_recipe.php';
}
function the_recipe_ingredients_relation(IngredientInRecipeModel $relation)
{
$ingredient = IngredientModel::get(array(
[
'name' => 'obj.id',
'type' => '=',
'value' => $relation->field_ingredient_id
]
));
$recipe = RecipeModel::get(array(
[
'name' => 'obj.id',
'type' => '=',
'value' => $relation->field_recipe_id
]
));
require APPS_PATH . '/Admin/Templates/widgets/ingredients_in_recipe_relation.php';
}

View File

@ -9,6 +9,9 @@ $admin_urls = [
// Lists
new Path('/admin/users', new Controllers\AdminUserListController(), 'user-list'),
new Path('/admin/recipes',new Controllers\AdminRecipeListController(), 'recipe-list'),
new Path('/admin/categories',new Controllers\AdminCategoryListController(), 'category-list'),
new Path('/admin/ingredients',new Controllers\IngredientListController(), 'ingredient-list'),
////// Single object ///////
// User
@ -20,6 +23,23 @@ $admin_urls = [
new Path('/admin/user/[:int]/ban/new', new Controllers\AdminBanController(true), 'ban-new'),
new Path('/admin/ban/[:int]', new Controllers\AdminBanController(false), 'ban'),
// Recipe
new Path('/admin/recipe/[:int]', new Controllers\AdminRecipeController(), 'recipe'),
new Path('/admin/recipe/new', new Controllers\AdminRecipeController(true), 'recipe-new'),
// Category
new Path('/admin/category/[:int]', new Controllers\AdminCategoryController(), 'category'),
new Path('/admin/category/new', new Controllers\AdminCategoryController(true), 'category-new'),
// Ingredient
new Path('/admin/ingredient/[:int]', new Controllers\IngredientController(), 'ingredient'),
new Path('/admin/ingredient/new', new Controllers\IngredientController(true), 'ingredient-new'),
// Recipe ingedient relation
new Path('/admin/recipe/[:int]/ingredients', new Controllers\IngredientRecipeRelListController(), 'ing-cat-rel-list'),
new Path('/admin/recipe/ingredient/[:int]', new Controllers\IngredientRecipeRelController(), 'ing-cat-rel'),
new Path('/admin/recipe/[:int]/ingredient/new', new Controllers\IngredientRecipeRelController(true), 'ing-cat-rel-new'),
// Dynamic delete for every object type
new Path('/admin/[:string]/[:int]/delete', new Controllers\AdminDeleteController(), 'delete')
];

View File

@ -7,6 +7,7 @@ class CategoryModel extends BaseModel
{
public $field_name;
static protected $search_fields = ['obj.name'];
static protected $table_name = 'categories';
static protected $table_fields = [
'id' => 'int',
@ -21,4 +22,14 @@ class CategoryModel extends BaseModel
);');
return $result;
}
public static function get_cat_values()
{
$cat_list = self::filter(array());
$result = array();
foreach($cat_list as $cat) {
$result[] = [ $cat->get_id(), $cat->field_name ];
}
return $result;
}
}

View File

@ -6,14 +6,27 @@ use Lycoreco\Includes\Model\BaseModel;
class IngredientInRecipeModel extends BaseModel
{
public $field_ingredient_id;
public $field_receipt_id;
public $field_recipe_id;
public $field_amount;
static protected $search_fields = ['tb1.name'];
public $ingredient_name;
public $ingredient_unit;
static protected $additional_fields = array(
[
'field' => [
'tb1.name AS ingredient_name',
'tb1.unit_name AS ingredient_unit'
],
'join_table' => 'ingredients tb1 ON tb1.id = obj.ingredient_id'
]
);
static protected $table_name = 'recipe_ingredients';
static protected $table_fields = [
'id' => 'int',
'ingredient_id' => 'int',
'receipt_id' => 'int',
'recipe_id' => 'int',
'amount' => 'int'
];
public static function init_table()
@ -22,12 +35,16 @@ class IngredientInRecipeModel extends BaseModel
id INT AUTO_INCREMENT PRIMARY KEY,
ingredient_id INT NOT NULL,
receipt_id INT NOT NULL,
recipe_id INT NOT NULL,
amount INT NOT NULL,
FOREIGN KEY (ingredient_id) REFERENCES ingredients(id) ON DELETE CASCADE,
FOREIGN KEY (receipt_id) REFERENCES recipes(id) ON DELETE CASCADE
FOREIGN KEY (recipe_id) REFERENCES recipes(id) ON DELETE CASCADE
);');
return $result;
}
public function get_count()
{
return $this->field_amount . ' ' . $this->ingredient_unit;
}
}

View File

@ -7,6 +7,8 @@ class IngredientModel extends BaseModel
{
public $field_name;
public $field_unit_name;
static protected $search_fields = ['obj.name'];
static protected $table_name = 'ingredients';
static protected $table_fields = [
@ -24,4 +26,13 @@ class IngredientModel extends BaseModel
);');
return $result;
}
public static function get_ing_values()
{
$ing_list = self::filter(array());
$result = array();
foreach($ing_list as $ing) {
$result[] = [ $ing->get_id(), $ing->field_name . ' ('. $ing->field_unit_name .')' ];
}
return $result;
}
}

View File

@ -7,6 +7,7 @@ class RecipeModel extends BaseModel
{
public $field_title;
public $field_instruction;
public $field_image_url;
public $field_estimated_time;
public $field_estimated_price;
public $field_category_id;
@ -14,13 +15,18 @@ class RecipeModel extends BaseModel
public $field_status;
public $field_created_at;
const STATUS = [[ 'publish', 'Publish' ], [ 'pending', 'Pending' ]];
static protected $search_fields = ['obj.title'];
static protected $table_name = 'recipes';
static protected $table_fields = [
'id' => 'int',
'title' => 'string',
'instruction' => 'string',
'image_url' => 'string',
'estimated_time' => 'int',
'estimated_price' => 'int',
'estimated_price' => 'float',
'category_id' => 'int',
'author_id' => 'int',
'status' => 'string',
'created_at' => 'DateTime'
@ -32,14 +38,31 @@ class RecipeModel extends BaseModel
title VARCHAR(255) NOT NULL,
instruction TEXT NOT NULL,
image_url VARCHAR(255) NULL,
estimated_time INT NOT NULL,
estimated_price INT NOT NULL,
category_id INT NOT NULL,
author_id INT NOT NULL,
status VARCHAR(20) NOT NULL CHECK (status IN (\'publish\', \'pending\')),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE
FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
);');
return $result;
}
public function get_price()
{
return $this->field_estimated_price . '$';
}
public function get_status()
{
return ucfirst($this->field_status);
}
public function get_image_url() {
if($this->field_image_url)
return MEDIA_URL . $this->field_image_url;
return null;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB