feat: reorder recipes form and add filds for sections

This commit is contained in:
Abdel LAMRANI 2026-10-14 15:50:46 +02:00
parent 12f22bf593
commit 46fd50d5da
24 changed files with 606 additions and 197 deletions

View file

@ -71,6 +71,8 @@ class RecipeController extends Controller
*/
public function show(Recipe $recipe): View
{
$recipe->load('consumptionMoments');
$productModels = Product::query()->where('published', true)->get();
return view('bo.pages.recipes.show', [
@ -103,10 +105,11 @@ class RecipeController extends Controller
{
return DB::transaction(function () use ($request) {
$recipe = new Recipe();
$recipe->fill(Arr::except($request->validated(), ['products']));
$recipe->fill(Arr::except($request->validated(), ['products', 'consumption_moments']));
if ($recipe->saveOrFail()) {
$recipe->products()->sync($request->products);
$recipe->consumptionMoments()->sync($request->validated('consumption_moments', []));
return redirect()->route('bo.recipes.edit', $recipe)
->with('success', trans('crud.messages.has_been_created', [
@ -144,9 +147,10 @@ class RecipeController extends Controller
public function update(UpdateRecipeRequest $request, Recipe $recipe): RedirectResponse
{
return DB::transaction(function () use ($request, $recipe) {
$recipe->fill(Arr::except($request->validated(), ['products']));
$recipe->fill(Arr::except($request->validated(), ['products', 'consumption_moments']));
$recipe->products()->sync([]);
$recipe->products()->sync($request->products);
$recipe->consumptionMoments()->sync($request->consumption_moments ?? []);
if ($recipe->saveOrFail()) {
return redirect()->route('bo.recipes.edit', $recipe)

View file

@ -153,7 +153,7 @@ class CustomerController extends Controller
*/
public function indexRecipes(): View
{
$models = Recipe::query()->with('products', 'consumptionMoment')
$models = Recipe::query()->with('products', 'consumptionMoments')
->where('published', true)->take(15)->paginate(9);
return view('club.pages.customers.favorites.recipes', compact('models'));

View file

@ -88,7 +88,7 @@ class ProductController extends Controller
/** @var \App\Models\Recipe|null $recipeModel */
$recipeModel = $productModel->recipes->isNotEmpty()
? $productModel->recipes()->with(['products', 'consumptionMoment'])->first()
? $productModel->recipes()->with(['products', 'consumptionMoments'])->first()
: Recipe::query()->whereHas('products', function (Builder $q) use ($productModel) {
$q->when($productModel->subrange, function (Builder $q) use ($productModel) {
$q->whereHas('subrange', function (Builder $q) use ($productModel) {
@ -99,7 +99,7 @@ class ProductController extends Controller
$q->where('id', $productModel->range->getKey());
});
});
})->with(['products', 'consumptionMoment'])->first();
})->with(['products', 'consumptionMoments'])->first();
/** @var \App\Models\Video $videoModel */
$videoModel = $productModel->videos->sortByDesc('created_at')->first();
@ -502,7 +502,7 @@ class ProductController extends Controller
/** @var \Illuminate\Support\Collection<\App\Models\Recipe> $recipeModels Recipes by same product */
$recipeModels = Recipe::query()
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment'])
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoments'])
->where('published', true)
->whereHas('products', function (Builder $q) use ($productModel) {
$q->where('id', $productModel->getKey());
@ -512,7 +512,7 @@ class ProductController extends Controller
if (count($recipeModels) < $nbrMaxRecipes) {
/** @var \Illuminate\Support\Collection<\App\Models\Recipe> $recipeModels Recipes by same product range */
$recipeModels = $recipeModels->merge(Recipe::query()
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment'])
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoments'])
->where('published', true)
->whereHas('products', function (Builder $q) use ($productModel) {
$q->when(! is_null($productModel->range), function (Builder $q) use ($productModel) {

View file

@ -43,7 +43,7 @@ class RecipeController extends Controller
): View {
/** @var \Illuminate\Pagination\LengthAwarePaginator $recipeModels */
$recipeModels = $recipeModelsFiltered ?? Recipe::query()
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment'])
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoments'])
->where('published', true)
->orderBy('slug')
->paginate($this->recipeModelsPerPage);
@ -99,7 +99,7 @@ class RecipeController extends Controller
*/
public function getRecipeModelsTitles(?string $search = null): LengthAwarePaginator
{
$query = Recipe::query()->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment']);
$query = Recipe::query()->with(['products', 'products.meat', 'products.submeat', 'consumptionMoments']);
return $this->getModelsTitles($query, 'title', $search);
}
@ -120,7 +120,7 @@ class RecipeController extends Controller
{
/** @var \Illuminate\Database\Eloquent\Builder $recipeModels */
$recipeModels = Recipe::query()
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment'])
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoments'])
->where('published', true)
->orderBy('slug');
@ -161,9 +161,11 @@ class RecipeController extends Controller
->where('slug', $request->model_slug)
->first();
$recipeModels = Recipe::query()
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment'])
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoments'])
->when(! is_null($consumptionMomentModel), function (Builder $q) use ($consumptionMomentModel) {
$q->where('consumption_moment_id', $consumptionMomentModel->getKey());
$q->whereHas('consumptionMoments', function (Builder $q) use ($consumptionMomentModel) {
$q->whereKey($consumptionMomentModel->getKey());
});
})
->where('published', true)
->orderBy('slug')
@ -201,7 +203,7 @@ class RecipeController extends Controller
->orderBy('order')
->get();
$recipeModels = Recipe::query()
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment'])
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoments'])
->where('published', true)
->when(! is_null($meatModel), function (Builder $q) use ($meatModel) {
$q->whereHas('products', function (Builder $q) use ($meatModel) {
@ -254,7 +256,7 @@ class RecipeController extends Controller
->orderBy('order')
->get();
$recipeModels = Recipe::query()
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment'])
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoments'])
->where('published', true)
->when(! is_null($submeatModel), function (Builder $q) use ($submeatModel) {
$q->whereHas('products', function (Builder $q) use ($submeatModel) {
@ -294,7 +296,7 @@ class RecipeController extends Controller
$recipeProducts = $recipeModel->products->pluck('id')->toArray();
/** @var \Illuminate\Support\Collection<\App\Models\Recipe> $recipeWithProducts Recipe query builder for products relation */
$recipeWithProducts = Recipe::query()
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment'])
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoments'])
->whereNot('id', $recipeModel->getKey())
->where('published', true)
->whereHas('products', function (Builder $q) use ($recipeProducts) {
@ -318,7 +320,7 @@ class RecipeController extends Controller
});
/** @var \Illuminate\Support\Collection<\App\Models\Recipe> $recipeWithMeats Recipe query builder for products relation */
$recipeWithMeats = Recipe::query()
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment'])
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoments'])
->whereNot('id', $recipeModel->getKey())
->where('published', true)
->whereHas('products', function (Builder $q) use ($recipeProducts, $recipeProductsSubmeats) {
@ -340,12 +342,17 @@ class RecipeController extends Controller
$recipeModels = $recipeWithProducts->merge($recipeWithMeats);
if (count($recipeModels) < $nbrMaxRecipes) {
/** @var array<int> $consumptionMomentIds */
$consumptionMomentIds = $recipeModel->consumptionMoments->modelKeys();
/** @var \Illuminate\Support\Collection<\App\Models\Recipe> $recipeWithConsumptionMoment Recipe query builder for consumption moment relation */
$recipeWithConsumptionMoment = Recipe::query()
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment'])
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoments'])
->whereNot('id', $recipeModel->getKey())
->where('published', true)
->where('consumption_moment_id', $recipeModel->consumption_moment_id)
->whereHas('consumptionMoments', function (Builder $q) use ($consumptionMomentIds) {
$q->whereKey($consumptionMomentIds);
})
->get();
$recipeModels = $recipeModels->merge($recipeWithConsumptionMoment);
}

View file

@ -9,6 +9,7 @@ use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Enum;
use App\Rules\WysiwygLength;
class StoreRecipeRequest extends FormRequest
{
@ -28,7 +29,7 @@ class StoreRecipeRequest extends FormRequest
protected function prepareForValidation(): void
{
$arrayIngredientList = [];
foreach ($this->input('ingredients_list') as $key => $value) {
foreach ($this->input('ingredients_list', []) as $key => $value) {
$value = array_merge(
$value,
['is_proportionnal' => filter_var($value['is_proportionnal'] ?? false, FILTER_VALIDATE_BOOLEAN)]
@ -39,8 +40,22 @@ class StoreRecipeRequest extends FormRequest
'highlight' => $this->boolean('highlight'),
'published' => $this->boolean('published'),
'ingredients_list' => $arrayIngredientList,
'consumption_moments' => collect($this->input('consumption_moments', []))
->pluck('id')
->filter()
->values()
->all(),
]);
$this->mergePicture('picture');
foreach (['advice_picto', 'tips_picto'] as $picto) {
$this->mergePicture($picto);
// icon removed: mergePicture() returns [] instead of null
if ($this->input($picto) === []) {
$this->merge([$picto => null]);
}
}
}
/**
@ -52,7 +67,6 @@ class StoreRecipeRequest extends FormRequest
'seo_title' => 'required|min:45|max:70',
'seo_description' => 'required|min:50|max:160',
'title' => 'required|string|between:3,255',
'tips' => 'nullable|string|between:3,255',
'picture_alt' => 'required_with:picture|nullable|between:3,255',
'picture_title' => 'required_with:picture|nullable|between:3,255',
'ingredients_list.*' => 'required|array',
@ -67,9 +81,10 @@ class StoreRecipeRequest extends FormRequest
'ingredients_list.*.label_value' => 'required|string|between:3,255',
'ingredients_list.*.is_proportionnal' => 'required|boolean',
'preparation' => 'required|string|min:15',
'advice' => 'nullable|string|min:15',
'preparation_title' => 'required|string|between:3,255',
'video_id' => 'sometimes|nullable|integer|exists:videos,id',
'consumption_moment_id' => 'required|integer|exists:consumption_moments,id',
'consumption_moments' => 'required|array|min:1|max:4',
'consumption_moments.*' => 'required|integer|distinct|exists:consumption_moments,id',
'products' => 'required|array',
'products.*' => 'required|integer|exists:products,id',
'preparation_time' => 'required|numeric|between:1,1000',
@ -78,11 +93,21 @@ class StoreRecipeRequest extends FormRequest
'difficulty' => ['required', new Enum(DifficultyEnum::class)],
'highlight' => 'required|boolean',
'published' => 'required|boolean',
'advice_title' => 'nullable|string|between:3,255',
'advice' => ['nullable', 'string', new WysiwygLength(min: 15, max: 500)],
'advice_picto_alt' => 'required_with:advice_picto|nullable|between:3,255',
'advice_picto_title' => 'required_with:advice_picto|nullable|between:3,255',
'tips_title' => 'nullable|string|between:3,255',
'tips' => ['nullable', 'string', new WysiwygLength(min: 15, max: 500)],
'tips_picto_alt' => 'required_with:tips_picto|nullable|between:3,255',
'tips_picto_title' => 'required_with:tips_picto|nullable|between:3,255',
];
return \array_merge(
$rules,
$this->pictureRules('picture', 720, 405, 720, 405),
$this->pictureRules('advice_picto', 45, 45, 45, 45, nullable: true),
$this->pictureRules('tips_picto', 45, 45, 45, 45, nullable: true),
);
}
@ -96,6 +121,10 @@ class StoreRecipeRequest extends FormRequest
'seo_description' => trans('validation.custom.seo_description'),
'title' => trans('validation.attributes.title'),
'tips' => trans('validation.custom.tips'),
'tips_title' => trans('validation.custom.tips_title'),
'tips_picto' => trans('validation.custom.tips_picto'),
'tips_picto_alt' => trans('validation.custom.attribute_alt'),
'tips_picto_title' => trans('validation.custom.attribute_title'),
'picture' => trans('validation.attributes.image'),
'picture_alt' => trans('validation.custom.attribute_alt'),
'picture_title' => trans('validation.custom.attribute_title'),
@ -104,9 +133,15 @@ class StoreRecipeRequest extends FormRequest
'ingredients_list.*.label_value' => trans('validation.custom.ingredients_list'),
'ingredients_list.*.is_proportionnal' => trans('validation.custom.ingredients_list'),
'preparation' => trans('validation.custom.preparation'),
'preparation_title' => trans('validation.custom.preparation_title'),
'advice' => trans('validation.custom.advice'),
'advice_title' => trans('validation.custom.advice_title'),
'advice_picto' => trans('validation.custom.advice_picto'),
'advice_picto_alt' => trans('validation.custom.attribute_alt'),
'advice_picto_title' => trans('validation.custom.attribute_title'),
'video_id' => trans('validation.custom.video_id'),
'consumption_moment_id' => trans('models.classes.consumption_moment'),
'consumption_moments' => trans_choice('models.classes.consumption_moment', 2),
'consumption_moments.*' => trans_choice('models.classes.consumption_moment', 1),
'products' => Str::plural(trans('models.classes.product')),
'preparation_time' => trans('validation.custom.preparation_time'),
'cooking_time' => trans('validation.custom.cooking_time'),

View file

@ -23,7 +23,7 @@ class RecipeCommericalSheetResource extends JsonResource
'preparation' => '',
'advice' => '',
'video_id' => null,
'consumption_moment_id' => null,
'consumption_moments' => [],
'preparation_time' => '',
'cooking_time' => '',
'nb_guests' => 0,
@ -61,8 +61,10 @@ class RecipeCommericalSheetResource extends JsonResource
'video_id' => (! is_null($this->recipeModel->video)) ?
(new VideoResource($this->recipeModel->video))->toArray($request)
: null,
'consumption_moment_id' => (new ConsumptionMomentResource($this->recipeModel->consumptionMoment))
->toArray($request),
'consumption_moments' => $this->recipeModel->consumptionMoments
->map(fn($moment) => (new ConsumptionMomentResource($moment))->toArray($request))
->values()
->all(),
'preparation_time' => $this->recipeModel->preparation_time,
'cooking_time' => $this->recipeModel->cooking_time,
'nb_guests' => $this->recipeModel->nb_guests,

View file

@ -8,6 +8,7 @@ use App\Traits\Models\ActivityLog;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
@ -101,4 +102,14 @@ class ConsumptionMoment extends Model
return self::query()->orderBy('slug')->get();
});
}
// * RELATIONSHIPS
/**
* Get all of the recipes for the consumption moment.
*/
public function recipes(): BelongsToMany
{
return $this->belongsToMany(Recipe::class);
}
}

View file

@ -460,7 +460,7 @@ class Customer extends Authenticatable implements MustVerifyEmail
*/
public function recipes(): MorphToMany
{
return $this->morphedByMany(Recipe::class, 'customerable')->with('products', 'consumptionMoment')->withTimestamps();
return $this->morphedByMany(Recipe::class, 'customerable')->with('products', 'consumptionMoments')->withTimestamps();
}
/**

View file

@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Facades\Cache;
@ -38,7 +39,6 @@ use Uwil\SitemapGenerator\Lib\SitemapHelper;
* @property string $preparation Recipe preparation.
* @property array<array<string>> $ingredients_list Recipe list of ingredients.
* @property \App\Models\Video|null $video_id Recipe video.
* @property \App\Models\ConsumptionMoment $consumption_moment_id Recipe consumption moment.
* @property int $preparation_time Recipe preparation time.
* @property int $cooking_time Recipe cooking time.
* @property int $nb_guests Number of reference persons for
@ -76,7 +76,6 @@ class Recipe extends Model implements RedirectableInterface, SchemaOrgInterface,
'preparation',
'ingredients_list',
'video_id',
'consumption_moment_id',
'product_id',
'preparation_time',
'cooking_time',
@ -84,6 +83,15 @@ class Recipe extends Model implements RedirectableInterface, SchemaOrgInterface,
'difficulty',
'highlight',
'published',
'preparation_title',
'advice_title',
'advice_picto',
'advice_picto_alt',
'advice_picto_title',
'tips_title',
'tips_picto',
'tips_picto_alt',
'tips_picto_title',
];
/**
@ -138,11 +146,21 @@ class Recipe extends Model implements RedirectableInterface, SchemaOrgInterface,
});
static::updated(function (self $model) {
FileStorageHelper::removeOldFile($model, 'picture');
foreach (['advice_picto', 'tips_picto'] as $picto) {
if (filled($model->getOriginal($picto))) {
FileStorageHelper::removeOldFile($model, $picto);
}
}
self::resetCache();
Rating::resetCache($model);
});
static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'picture');
foreach (['advice_picto', 'tips_picto'] as $picto) {
if (filled($model->getOriginal($picto))) {
FileStorageHelper::removeOldFile($model, $picto);
}
}
self::resetCache();
Rating::resetCache($model);
});
@ -157,6 +175,12 @@ class Recipe extends Model implements RedirectableInterface, SchemaOrgInterface,
{
$model->slug = Str::slug($model->title);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
// Optional icons only if specified
foreach (['advice_picto', 'tips_picto'] as $picto) {
if (filled($model->{$picto})) {
$model->{$picto} = FileStorageHelper::storeFile($model, $model->{$picto}, true);
}
}
}
/**
@ -243,7 +267,7 @@ class Recipe extends Model implements RedirectableInterface, SchemaOrgInterface,
'products',
'products.meat',
'products.submeat',
'consumptionMoment',
'consumptionMoments',
])->limit(9)->get();
});
}
@ -272,7 +296,7 @@ class Recipe extends Model implements RedirectableInterface, SchemaOrgInterface,
public function toSchemaOrg(bool $asReference = false): \Spatie\SchemaOrg\Recipe
{
// @phpcs:enable
$model = $this->load(['products', 'consumptionMoment', 'video', 'ratings']);
$model = $this->load(['products', 'consumptionMoments', 'video', 'ratings']);
// Main.
$schema = Schema::Recipe()
@ -282,7 +306,7 @@ class Recipe extends Model implements RedirectableInterface, SchemaOrgInterface,
->inLanguage(config('schema-org.website.languages'))
->keywords('recette')
->recipeCategory([
$model->consumptionMoment->title,
...$model->consumptionMoments->pluck('title')->all(),
$model->difficulty->label(),
])
->recipeCuisine('Française')
@ -389,6 +413,35 @@ class Recipe extends Model implements RedirectableInterface, SchemaOrgInterface,
return $schema;
}
/**
* Build list from the preparation WYSIWYG content
*
* @return array<int, \Spatie\SchemaOrg\HowToStep>|string|null
*/
public function getPreparationStepsSchema(): array|string|null
{
// multi-line content accepted
preg_match_all('/<li[^>]*>(.*?)<\/li>/is', (string) $this->preparation, $matches);
$steps = collect($matches[1])
->map(fn(string $step) => trim(html_entity_decode(strip_tags($step))))
->filter()
->values();
if ($steps->isNotEmpty()) {
return $steps
->map(fn(string $text, int $index) => Schema::howToStep()
->position($index + 1)
->text($text))
->all();
}
// no list in the WYSIWYG returning raw text
$text = trim(html_entity_decode(strip_tags((string) $this->preparation)));
return $text !== '' ? $text : null;
}
// * REDIRECTIONS.
/**
@ -403,11 +456,11 @@ class Recipe extends Model implements RedirectableInterface, SchemaOrgInterface,
// * RELATIONSHIPS
/**
* Get all of the consumption moment for the recipe.
* Get all of the consumption moments for the recipe.
*/
public function consumptionMoment(): BelongsTo
public function consumptionMoments(): BelongsToMany
{
return $this->belongsTo(ConsumptionMoment::class);
return $this->belongsToMany(ConsumptionMoment::class);
}
/**
@ -433,4 +486,28 @@ class Recipe extends Model implements RedirectableInterface, SchemaOrgInterface,
{
return $this->morphMany(Rating::class, 'rating');
}
/**
* Determine whether the advice section should be displayed.
*/
public function hasAdvice(): bool
{
return filled($this->advice_title) && self::hasText($this->advice);
}
/**
* Determine whether the nutrition tip section.
*/
public function hasTips(): bool
{
return filled($this->tips_title) && self::hasText($this->tips);
}
/**
* Determine whether a WYSIWYG HTML content contains visible text.
*/
private static function hasText(?string $html): bool
{
return filled(trim(html_entity_decode(strip_tags((string) $html))));
}
}

View file

@ -4,6 +4,7 @@ namespace Database\Factories;
use App\Enums\Recipes\DifficultyEnum;
use App\Models\ConsumptionMoment;
use App\Models\Recipe;
use App\Models\Video;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
@ -42,7 +43,6 @@ class RecipeFactory extends Factory
'preparation' => fake()->sentence(),
'advice' => fake()->sentence(),
'video_id' => fake()->boolean(50) ? \collect(Video::all())->random() : null,
'consumption_moment_id' => \collect(ConsumptionMoment::all())->random(),
'preparation_time' => fake()->numberBetween(1, 100),
'cooking_time' => fake()->numberBetween(1, 100),
'nb_guests' => fake()->numberBetween(1, 10),
@ -51,4 +51,17 @@ class RecipeFactory extends Factory
'published' => fake()->boolean(75),
];
}
public function configure(): static
{
return $this->afterCreating(function (Recipe $recipe) {
$moments = ConsumptionMoment::all();
if ($moments->isNotEmpty()) {
$recipe->consumptionMoments()->attach(
$moments->random(min(random_int(1, 2), $moments->count()))->modelKeys()
);
}
});
}
}

View file

@ -0,0 +1,44 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('recipes', function (Blueprint $table) {
$table->string('preparation_title')->nullable()->after('preparation');
$table->string('advice_title')->nullable()->after('advice');
$table->string('advice_picto')->nullable()->after('advice_title');
$table->string('advice_picto_alt')->nullable()->after('advice_picto');
$table->string('advice_picto_title')->nullable()->after('advice_picto_alt');
$table->text('tips')->nullable()->change();
$table->string('tips_title')->nullable()->after('tips');
$table->string('tips_picto')->nullable()->after('tips_title');
$table->string('tips_picto_alt')->nullable()->after('tips_picto');
$table->string('tips_picto_title')->nullable()->after('tips_picto_alt');
});
}
public function down(): void
{
Schema::table('recipes', function (Blueprint $table) {
$table->dropColumn([
'preparation_title',
'advice_title',
'advice_picto',
'advice_picto_alt',
'advice_picto_title',
'tips_title',
'tips_picto',
'tips_picto_alt',
'tips_picto_title',
]);
$table->string('tips', 255)->nullable()->change();
});
}
};

View file

@ -0,0 +1,52 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('consumption_moment_recipe', function (Blueprint $table) {
$table->foreignId('consumption_moment_id')->constrained()->cascadeOnDelete();
$table->foreignId('recipe_id')->constrained()->cascadeOnDelete();
$table->primary(['consumption_moment_id', 'recipe_id']);
});
// each recipe retains its current consumption timing
DB::table('consumption_moment_recipe')->insertUsing(
['consumption_moment_id', 'recipe_id'],
DB::table('recipes')
->select('consumption_moment_id', 'id')
->whereNotNull('consumption_moment_id'),
);
Schema::table('recipes', function (Blueprint $table) {
$table->dropConstrainedForeignId('consumption_moment_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('recipes', function (Blueprint $table) {
$table->foreignId('consumption_moment_id')->nullable()->after('video_id')->constrained();
});
DB::table('recipes')->update([
'consumption_moment_id' => DB::table('consumption_moment_recipe')
->select('consumption_moment_id')
->whereColumn('recipe_id', 'recipes.id')
->orderBy('consumption_moment_id')
->limit(1),
]);
Schema::dropIfExists('consumption_moment_recipe');
}
};

View file

@ -79,6 +79,7 @@ return [
'video_alt_helper' => 'Must describe your video for search engines and blind people (5 to 6 words).',
'video_alt_label' => 'Alt attribute of the video',
'text_warning' => 'Proofread your content before publishing it',
'optional_section_helper' => 'Optional section: if the text is empty, the section (including its title and icon) is not displayed on the website.',
],
'other' => [

View file

@ -393,5 +393,10 @@ return [
'video_title' => 'video title',
'vimeo_video_id' => 'vimeo video id',
'youtube_video_id' => 'youtube video id',
'preparation_title' => 'preparation title',
'advice_title' => 'advice title',
'advice_picto' => 'advice icon',
'tips_title' => 'nutrition tip title',
'tips_picto' => 'nutrition tip icon',
],
];

View file

@ -84,6 +84,7 @@ return [
'video_alt_label' => 'Attribut Alt de la vidéo',
'text_warning' => 'Relisez votre contenu avant de le publier.',
'update_size_picture_helper' => 'Les dimensions de l\'image peuvent être modifiées après la création.',
'optional_section_helper' => 'Section facultative : elle n\'est pas affichée sur le site si le titre ou le texte est vide.',
],
'pagination' => [

View file

@ -412,5 +412,10 @@ return [
'video_title' => 'titre de la vidéo',
'vimeo_video_id' => 'ID de la vidéo viméo',
'youtube_video_id' => 'ID de la vidéo youtube',
'preparation_title' => 'titre de la préparation',
'advice_title' => 'titre du conseil',
'advice_picto' => 'picto du conseil',
'tips_title' => 'titre de l\'astuce nutrition',
'tips_picto' => 'picto de l\'astuce nutrition',
],
];

View file

@ -112,32 +112,6 @@
</div>
</fieldset>
</div>
{{-- DIFFICULTY --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ str(__('validation.custom.difficulty'))->ucfirst() }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.tinyint-enum', [
'id' => 'recipeDifficulty',
'label' => sprintf(
'%s %s %s',
str(__('validation.custom.difficulty'))->ucfirst(),
__('validation.custom.of_the.female'),
__('models.classes.recipe')),
'name' => 'difficulty',
'model' => $recipeModel ?? null,
'required' => true,
'value' => $recipeModel->difficulty,
'associatedModels' => \App\Enums\Recipes\DifficultyEnum::toArray(),
'helper' => __('crud.minicrud.select_entity', [
'entity' => sprintf('une seule et unique %s', __('validation.custom.difficulty')),
]),
])
</div>
</div>
</fieldset>
</div>
{{-- PICTURE --}}
<div class="col-12 pb-3">
<fieldset class="h-100 border p-3">
@ -256,6 +230,32 @@
</div>
</fieldset>
</div>
{{-- DIFFICULTY --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ str(__('validation.custom.difficulty'))->ucfirst() }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.tinyint-enum', [
'id' => 'recipeDifficulty',
'label' => sprintf(
'%s %s %s',
str(__('validation.custom.difficulty'))->ucfirst(),
__('validation.custom.of_the.female'),
__('models.classes.recipe')),
'name' => 'difficulty',
'model' => $recipeModel ?? null,
'required' => true,
'value' => $recipeModel->difficulty,
'associatedModels' => \App\Enums\Recipes\DifficultyEnum::toArray(),
'helper' => __('crud.minicrud.select_entity', [
'entity' => sprintf('une seule et unique %s', __('validation.custom.difficulty')),
]),
])
</div>
</div>
</fieldset>
</div>
{{-- INGREDIENTS --}}
<div class="col-12 mb-3">
<fieldset class="h-100 border p-3">
@ -315,45 +315,28 @@
</div>
</fieldset>
</div>
{{-- TIPS --}}
<div class="col-12 mb-3">
<fieldset class="border p-3">
<legend>{{ str(__('validation.custom.tips'))->ucfirst() }}</legend>
<div class="row mb-3">
<div class="col-12">
@include('bo.partials.inputs.varchar-multiline', [
'id' => 'recipeTips',
'label' => __(':field :inter:model', [
'field' => str(__('validation.custom.tips'))->ucfirst(),
'inter' => __('validation.custom.of_the.female'),
'model' => __('models.classes.recipe'),
]),
'name' => 'tips',
'model' => $recipeModel ?? null,
'required' => false,
'helper' => __('validation.between.string', [
'attribute' => __(':field :inter:model', [
'field' => __('validation.custom.tips'),
'inter' => __('validation.custom.of_the.female'),
'model' => __('models.classes.recipe'),
]),
'min' => 3,
'max' => 500,
]),
'placeholder' => __(':field :inter:model', [
'field' => str(__('validation.custom.tips'))->ucfirst(),
'inter' => __('validation.custom.of_the.female'),
'model' => __('models.classes.recipe'),
]),
])
</div>
</div>
</fieldset>
</div>
{{-- PREPARATION --}}
<div class="col-12 mb-3">
<fieldset class="border p-3">
<legend>{{ str(__('validation.custom.preparation'))->ucfirst() }}</legend>
<legend>{{ str(__('validation.custom.preparation'))->ucFirst() }}</legend>
<div class="row mb-3">
<div class="col-12">
@include('bo.partials.inputs.varchar', [
'id' => 'recipePreparationTitle',
'label' => str(__('validation.custom.preparation_title'))->ucFirst(),
'name' => 'preparation_title',
'required' => true,
'model' => $recipeModel ?? null,
'value' => 'Préparation',
'helper' => __('validation.between.string', [
'attribute' => __('validation.custom.preparation_title'),
'min' => 3,
'max' => 255,
]),
'placeholder' => str(__('validation.custom.preparation_title'))->ucFirst(),
])
</div>
</div>
<div class="row mb-3">
<div class="col-12">
@include('bo.partials.inputs.text-html', [
@ -380,34 +363,19 @@
</fieldset>
</div>
{{-- ADVICE --}}
<div class="col-12 mb-3">
<fieldset class="border p-3">
<legend>{{ str(__('validation.custom.advice'))->ucfirst() }}</legend>
<div class="row mb-3">
<div class="col-12">
@include('bo.partials.inputs.text-html', [
'id' => 'recipeAdvice',
'label' => str(
__(':field :inter:model', [
'field' => __('validation.custom.advice'),
'inter' => __('validation.custom.of_the.female'),
'model' => __('models.classes.recipe'),
]))->ucfirst(),
'name' => 'advice',
'model' => $recipeModel ?? null,
'required' => false,
'helper' =>
__('crud.minicrud.text_warning') .
' ' .
__('validation.min.string', [
'attribute' => __('validation.custom.advice'),
'min' => 15,
]),
])
</div>
</div>
</fieldset>
</div>
@include('bo.pages.recipes.partials.optional-section', [
'prefix' => 'advice',
'idPrefix' => 'recipeAdvice',
'legend' => __('validation.custom.advice'),
'gender' => 'male',
])
{{-- TIPS --}}
@include('bo.pages.recipes.partials.optional-section', [
'prefix' => 'tips',
'idPrefix' => 'recipeTips',
'legend' => __('validation.custom.tips'),
'gender' => 'female',
])
{{-- VIDEO_ID --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="h-100 border p-3">
@ -443,30 +411,33 @@
{{-- CONSUMPTION_MOMENT_ID --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ str(trans_choice('models.classes.consumption_moment', 1))->ucFirst() }}</legend>
<legend>{{ str(trans_choice('models.classes.consumption_moment', 2))->ucFirst() }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.search-belongs-to-dropdown', [
'id' => 'recipeConsumptionMomentId',
'label' => __(':model associé', [
'model' => str(trans_choice('models.classes.consumption_moment', 1))->ucFirst(),
'id' => 'recipeConsumptionMoments',
'label' => __(':model associés', [
'model' => str(trans_choice('models.classes.consumption_moment', 2))->ucFirst(),
]),
'name' => 'consumption_moment_id',
'relation' => 'consumptionMoment',
'name' => 'consumption_moments',
'relation' => 'consumptionMoments',
'model' => $recipeModel,
'targetClass' => 'App\Models\ConsumptionMoment',
'modelListPaginate' => $consumptionMomentModels ?? [],
'routeName' => 'bo.recipes.json-paginate',
'fieldName' => 'title',
'multiple' => false,
'multiple' => true,
'limits' => [1, 4],
'required' => true,
'disabled' => false,
'placeholder' => __('Rechercher un :model', [
'model' => trans_choice('models.classes.consumption_moment', 1),
]),
'helper' => __(
'Sélectionner un :model qui sera associé parmis la liste (un seul et unique :model doit être choisi).',
['model' => trans_choice('models.classes.consumption_moment', 1)]),
'helper' => __('Sélectionner de :min à :max :model parmi la liste.', [
'min' => 1,
'max' => 4,
'model' => trans_choice('models.classes.consumption_moment', 2),
]),
])
</div>
</div>

View file

@ -0,0 +1,107 @@
{{--
$prefix || 'advice' | 'tips' || string || REQUIRED
$idPrefix || 'recipeAdvice' | 'recipeTips' || string || REQUIRED
$legend || Libellé de la section || string || REQUIRED
$gender || 'male' | 'female' (accord "du/de la") || string || REQUIRED
--}}
@php
$sectionLabel = __(':inter:field', [
'inter' => __('validation.custom.of_the.' . $gender),
'field' => $legend,
]);
$pictoLabel = fn (string $attribute) => __(':field1 :inter1:field2 :inter2', [
'field1' => str(__('validation.custom.' . $attribute))->ucFirst(),
'inter1' => __('validation.custom.of_the.male'),
'field2' => __('validation.custom.picto'),
'inter2' => $sectionLabel,
]);
@endphp
<div class="col-12 mb-3">
<fieldset class="border p-3">
<legend>{{ str($legend)->ucFirst() }}</legend>
<p class="text-muted small mb-3">
{{ __('crud.minicrud.optional_section_helper') }}
</p>
{{-- TITLE --}}
<div class="row mb-3">
<div class="col-12">
@include('bo.partials.inputs.varchar', [
'id' => $idPrefix . 'Title',
'label' => str(__('validation.custom.' . $prefix . '_title'))->ucFirst(),
'name' => $prefix . '_title',
'required' => false,
'model' => $recipeModel ?? null,
'helper' => __('validation.between.string', [
'attribute' => __('validation.custom.' . $prefix . '_title'),
'min' => 3,
'max' => 255,
]),
'placeholder' => str(__('validation.custom.' . $prefix . '_title'))->ucFirst(),
])
</div>
</div>
{{-- CONTENT (WYSIWYG) --}}
<div class="row mb-3">
<div class="col-12">
@include('bo.partials.inputs.text-html', [
'id' => $idPrefix,
'label' => str(__(':field :inter:model', [
'field' => $legend,
'inter' => __('validation.custom.of_the.female'),
'model' => __('models.classes.recipe'),
]))->ucFirst(),
'name' => $prefix,
'model' => $recipeModel ?? null,
'required' => false,
'helper' => __('crud.minicrud.text_warning') . ' ' .
__('validation.min.string', ['attribute' => $legend, 'min' => 15]),
])
</div>
</div>
{{-- PICTO --}}
<div class="row mb-3">
{{-- PICTO_ALT --}}
<div class="col-12 col-lg-6 m-md-0 mb-3">
@include('bo.partials.inputs.varchar', [
'id' => $idPrefix . 'PictoAlt',
'label' => $pictoLabel('attribute_alt'),
'hasWordCounter' => true,
'name' => $prefix . '_picto_alt',
'required' => false,
'model' => $recipeModel ?? null,
'helper' => __('crud.minicrud.picture_alt_helper'),
'placeholder' => $pictoLabel('attribute_alt'),
])
</div>
{{-- PICTO_TITLE --}}
<div class="col-12 col-lg-6 mb-3">
@include('bo.partials.inputs.varchar', [
'id' => $idPrefix . 'PictoTitle',
'label' => $pictoLabel('attribute_title'),
'hasWordCounter' => true,
'name' => $prefix . '_picto_title',
'required' => false,
'model' => $recipeModel ?? null,
'helper' => __('crud.minicrud.picture_title_helper'),
'placeholder' => $pictoLabel('attribute_title'),
])
</div>
{{-- PICTO_FILE --}}
<div class="col-12">
@include('bo.partials.inputs.varchar-picture', [
'id' => $idPrefix . 'Picto',
'name' => $prefix . '_picto',
'width' => 45,
'height' => 45,
'model' => $recipeModel ?? null,
'required' => false,
'preview' => true,
])
</div>
</div>
</fieldset>
</div>

View file

@ -54,13 +54,6 @@
{{ $recipeModel->title }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.custom.tips'))->ucFirst() }}</td>
<td class="w-50 text-center align-middle">
{{ $recipeModel->tips }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.attributes.image'))->ucFirst() }}</td>
@ -68,12 +61,26 @@
<img class="img-fluid" src="{{ asset($recipeModel->picture) }}"
title="{{ $recipeModel->picture_title }}" alt="{{ $recipeModel->picture_alt }}">
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.custom.preparation_time'))->ucFirst() }}</td>
<td class="w-50 text-center align-middle">
{{ $recipeModel->preparation_time }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.custom.nb_guests'))->ucFirst() }}</td>
{{ str(__('validation.custom.cooking_time'))->ucFirst() }}</td>
<td class="w-50 text-center align-middle">
{{ $recipeModel->nb_guests }}
{{ $recipeModel->cooking_time }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.custom.difficulty'))->ucFirst() }}</td>
<td class="w-50 text-center align-middle">
{{ $recipeModel->difficulty->label() }}
</td>
</tr>
<tr class="border-bottom">
@ -94,10 +101,19 @@
</ul>
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.custom.nb_guests'))->ucFirst() }}</td>
<td class="w-50 text-center align-middle">
{{ $recipeModel->nb_guests }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.custom.preparation'))->ucFirst() }}</td>
<td class="w-50 text-center align-middle">
<p class="fw-bold mb-2">{{ $recipeModel->preparation_title ?: '--' }}</p>
<br>
<div class="ck-content">
{!! clean($recipeModel->preparation) !!}
</div>
@ -107,40 +123,61 @@
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.custom.advice'))->ucFirst() }}</td>
<td class="w-50 text-center align-middle">
<div class="ck-content">
{!! clean($recipeModel->advice) !!}
</div>
@if (blank($recipeModel->advice_title) && blank($recipeModel->advice) && blank($recipeModel->advice_picto))
--
@else
@if (filled($recipeModel->advice_picto))
<img src="{{ asset($recipeModel->advice_picto) }}"
alt="{{ $recipeModel->advice_picto_alt }}"
title="{{ $recipeModel->advice_picto_title }}" width="45" height="45"
class="d-block mx-auto mb-1">
<small class="d-block text-muted mb-2">
<b>Alt :</b> {{ $recipeModel->advice_picto_alt ?: '--' }} ·
<br>
<b>Title :</b> {{ $recipeModel->advice_picto_title ?: '--' }}
</small>
@endif
@if (filled($recipeModel->advice_title))
<p class="fw-bold mb-2">{{ $recipeModel->advice_title }}</p>
@endif
@if (filled($recipeModel->advice))
<div class="ck-content text-start">
{!! clean($recipeModel->advice) !!}
</div>
@endif
@endif
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.custom.preparation_time'))->ucFirst() }}</td>
{{ str(__('validation.custom.tips'))->ucFirst() }}</td>
<td class="w-50 text-center align-middle">
{{ $recipeModel->preparation_time }}
@if (blank($recipeModel->tips_title) && blank($recipeModel->tips) && blank($recipeModel->tips_picto))
—
@else
@if (filled($recipeModel->tips_picto))
<img src="{{ asset($recipeModel->tips_picto) }}"
alt="{{ $recipeModel->tips_picto_alt }}"
title="{{ $recipeModel->tips_picto_title }}" width="45" height="45"
class="d-block mx-auto mb-1">
<small class="d-block text-muted mb-2">
<b>Alt :</b> {{ $recipeModel->tips_picto_alt ?: '--' }} ·
<br>
<b>Title :</b> {{ $recipeModel->tips_picto_title ?: '--' }}
</small>
@endif
@if (filled($recipeModel->tips_title))
<p class="fw-bold mb-2">{{ $recipeModel->tips_title }}</p>
@endif
@if (filled($recipeModel->tips))
<div class="ck-content text-start">
{!! clean($recipeModel->tips) !!}
</div>
@endif
@endif
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.custom.cooking_time'))->ucFirst() }}</td>
<td class="w-50 text-center align-middle">
{{ $recipeModel->cooking_time }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.custom.difficulty'))->ucFirst() }}</td>
<td class="w-50 text-center align-middle">
{{ $recipeModel->difficulty->label() }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(trans_choice('models.classes.consumption_moment', 1))->ucFirst() }}</td>
<td class="w-50 text-center align-middle">
{{ $recipeModel->consumptionMoment->title }}
</td>
</tr>
@if($recipeModel->video)
@if ($recipeModel->video)
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('models.classes.video'))->ucFirst() }}</td>
@ -149,6 +186,14 @@
</td>
</tr>
@endif
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(trans_choice('models.classes.consumption_moment', $recipeModel->consumptionMoments->count()))->ucFirst() }}
</td>
<td class="w-50 text-center align-middle">
{{ $recipeModel->consumptionMoments->pluck('title')->join(', ') ?: '--' }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('models.classes.product'))->ucFirst() }}</td>

View file

@ -246,8 +246,8 @@
@endif
<tr width="100%">
<td>
<p>{{ str(sprintf("%s associé", trans_choice('models.classes.consumption_moment', 1)))->ucFirst() }}</p>
<p class="text-bold">{{ $recipeModel->consumption_moment_id->title }}</p><br><br>
<p>{{ str(sprintf("%s associé", trans_choice('models.classes.consumption_moment', $recipeModel->consumptionMoments->count())))->ucFirst() }}</p>
<p class="text-bold">{{ $recipeModel->consumptionMoments->pluck('title')->join(', ') }}</p><br><br>
</td>
</tr>
</table>

View file

@ -376,12 +376,16 @@
</div>
</div>
@elseif ($recipeModel)
<div class="col-4" data-aos="fade-rotate-right" data-aos-easing="ease-in-out" data-aos-duration="750"
data-aos-delay="1250" data-aos-once="true" data-aos-anchor-placement="bottom-bottom">
@php($consumptionMoment = $recipeModel->consumptionMoments->first())
<div class="col-4" data-aos="fade-rotate-right" data-aos-easing="ease-in-out"
data-aos-duration="750" data-aos-delay="1250" data-aos-once="true"
data-aos-anchor-placement="bottom-bottom">
<div class="part-left rotate-left">
<img class="img-fluid rotate-right" src="{{ asset($recipeModel->consumptionMoment->picture) }}"
title="{{ $recipeModel->consumptionMoment->picture_title }}"
alt="{{ $recipeModel->consumptionMoment->picture_alt }}">
@if ($consumptionMoment)
<img class="img-fluid rotate-right" src="{{ asset($consumptionMoment->picture) }}"
title="{{ $consumptionMoment->picture_title }}"
alt="{{ $consumptionMoment->picture_alt }}">
@endif
<div class="rotate-custom ps-4 pt-5">
<p class="h3 fs-5 text-center">
{{ $staticPageModel->data['animation_text_1']['value'] }}

View file

@ -45,12 +45,6 @@
])
</li> --}}
</ul>
<div class="d-flex justify-content-start align-items-center">
<img src="{{ Vite::asset('resources/assets/images/recipe/tips.svg') }}" title="{{ __('Icône de l\'astuce nutrition') }}"
alt="{{ __('Des couverts') }}">
<h4 class="m-0">{{ $staticPageModel->data['tips_title']['value'] }}</h4>
</div>
<p class="m-0">{{ $recipeModel->tips }}</p>
</div>
{{-- RECIPE VIDEO --}}
<div class="col-12 col-md-6 position-relative mt-md-0 mt-4">
@ -154,18 +148,47 @@
</div>
</div>
<div class="recipe-preparation mb-1 mt-4">
<h4>{{ str(__('préparation'))->upper() }}</h4>
{!! clean($recipeModel->preparation) !!}
</div>
@if ($recipeModel->advice)
<div class="d-flex justify-content-start align-items-center mb-1 mt-4">
<img src="{{ Vite::asset('resources/assets/images/recipe/advice.svg') }}"
title="{{ __('Icône du conseil Le Gaulois') }}" alt="{{ __('Des couverts') }}">
<h4 class="m-0">{{ __('le conseil le gaulois') }}</h4>
</div>
@endif
{!! clean($recipeModel->advice) !!}
<h4 class="text-uppercase">{{ $recipeModel->preparation_title ?: __('préparation') }}</h4>
{!! clean($recipeModel->preparation) !!}
</div>
@if ($recipeModel->hasAdvice())
<div class="recipe-advice mb-1 mt-4">
@if (filled($recipeModel->advice_picto) || filled($recipeModel->advice_title))
<div class="d-flex justify-content-start align-items-center">
@if (filled($recipeModel->advice_picto))
<img width="45" height="45" loading="lazy"
src="{{ asset($recipeModel->advice_picto) }}"
alt="{{ $recipeModel->advice_picto_alt }}"
@if (filled($recipeModel->advice_picto_title)) title="{{ $recipeModel->advice_picto_title }}" @endif>
@endif
@if (filled($recipeModel->advice_title))
<h4 class="m-0">{{ $recipeModel->advice_title }}</h4>
@endif
</div>
@endif
{!! clean($recipeModel->advice) !!}
</div>
@endif
@if ($recipeModel->hasTips())
<div class="recipe-tips mb-1 mt-4">
@if (filled($recipeModel->tips_picto) || filled($recipeModel->tips_title))
<div class="d-flex justify-content-start align-items-center">
@if (filled($recipeModel->tips_picto))
<img width="45" height="45" loading="lazy"
src="{{ asset($recipeModel->tips_picto) }}"
alt="{{ $recipeModel->tips_picto_alt }}"
@if (filled($recipeModel->tips_picto_title)) title="{{ $recipeModel->tips_picto_title }}" @endif>
@endif
@if (filled($recipeModel->tips_title))
<h4 class="m-0">{{ $recipeModel->tips_title }}</h4>
@endif
</div>
@endif
{!! clean($recipeModel->tips) !!}
</div>
@endif
</div>
</section>
{{-- RATINGS--}}

View file

@ -6,7 +6,7 @@
$items = array_merge($items, isset($relation) && !is_null($relation) ? [$relation->only('label')['label']] : []);
}
@endphp
@foreach (array_merge($items, [$model->consumptionMoment->title]) as $item)
@foreach (array_merge($items, $model->consumptionMoments->pluck('title')->all()) as $item)
<p class="{{ $class }}">
{{ $item }}
</p>

View file

@ -377,6 +377,8 @@ Route::middleware(['throttle:web', 'admin-secure'])->prefix('bo')->name('bo.')->
ConsumptionMomentController::class,
'duplicate',
])->name('consumption_moments.duplicate');
Route::get('/consumption-moments/json/paginate', [ConsumptionMomentController::class, 'jsonPaginateModels'])
->name('consumption-moments.json-paginate');
// * VIDEOS.
Route::resource('videos', VideoController::class);