diff --git a/app/Http/Controllers/Bo/RecipeController.php b/app/Http/Controllers/Bo/RecipeController.php index e605b9ce..4f9208c5 100644 --- a/app/Http/Controllers/Bo/RecipeController.php +++ b/app/Http/Controllers/Bo/RecipeController.php @@ -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) diff --git a/app/Http/Controllers/Club/CustomerController.php b/app/Http/Controllers/Club/CustomerController.php index 11e377e6..0c82aae9 100644 --- a/app/Http/Controllers/Club/CustomerController.php +++ b/app/Http/Controllers/Club/CustomerController.php @@ -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')); diff --git a/app/Http/Controllers/Fo/ProductController.php b/app/Http/Controllers/Fo/ProductController.php index 28ad72f7..873272e0 100644 --- a/app/Http/Controllers/Fo/ProductController.php +++ b/app/Http/Controllers/Fo/ProductController.php @@ -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) { diff --git a/app/Http/Controllers/Fo/RecipeController.php b/app/Http/Controllers/Fo/RecipeController.php index 8eabd73c..8ad3a1c4 100644 --- a/app/Http/Controllers/Fo/RecipeController.php +++ b/app/Http/Controllers/Fo/RecipeController.php @@ -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 $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); } diff --git a/app/Http/Requests/Bo/Recipes/StoreRecipeRequest.php b/app/Http/Requests/Bo/Recipes/StoreRecipeRequest.php index 2de697b3..a3b138ac 100644 --- a/app/Http/Requests/Bo/Recipes/StoreRecipeRequest.php +++ b/app/Http/Requests/Bo/Recipes/StoreRecipeRequest.php @@ -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'), diff --git a/app/Http/Resources/Fo/Recipes/RecipeCommericalSheetResource.php b/app/Http/Resources/Fo/Recipes/RecipeCommericalSheetResource.php index a1ea2a38..719bc795 100644 --- a/app/Http/Resources/Fo/Recipes/RecipeCommericalSheetResource.php +++ b/app/Http/Resources/Fo/Recipes/RecipeCommericalSheetResource.php @@ -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, diff --git a/app/Models/ConsumptionMoment.php b/app/Models/ConsumptionMoment.php index e6b1f487..c6a4778c 100644 --- a/app/Models/ConsumptionMoment.php +++ b/app/Models/ConsumptionMoment.php @@ -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); + } } diff --git a/app/Models/Customer.php b/app/Models/Customer.php index 86e7b826..aa40a844 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -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(); } /** diff --git a/app/Models/Recipe.php b/app/Models/Recipe.php index 1827b662..2d960585 100644 --- a/app/Models/Recipe.php +++ b/app/Models/Recipe.php @@ -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> $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|string|null + */ + public function getPreparationStepsSchema(): array|string|null + { + // multi-line content accepted + preg_match_all('/]*>(.*?)<\/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)))); + } } diff --git a/database/factories/RecipeFactory.php b/database/factories/RecipeFactory.php index a1f2ed8b..170bac5b 100644 --- a/database/factories/RecipeFactory.php +++ b/database/factories/RecipeFactory.php @@ -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() + ); + } + }); + } } diff --git a/database/migrations/2026_10_13_170539_add_config_for_recipes_tables.php b/database/migrations/2026_10_13_170539_add_config_for_recipes_tables.php new file mode 100644 index 00000000..c2894e03 --- /dev/null +++ b/database/migrations/2026_10_13_170539_add_config_for_recipes_tables.php @@ -0,0 +1,44 @@ +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(); + }); + } +}; diff --git a/database/migrations/2026_10_14_092647_create_consumption_moment_recipe_table.php b/database/migrations/2026_10_14_092647_create_consumption_moment_recipe_table.php new file mode 100644 index 00000000..7d0e03bf --- /dev/null +++ b/database/migrations/2026_10_14_092647_create_consumption_moment_recipe_table.php @@ -0,0 +1,52 @@ +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'); + } +}; diff --git a/resources/lang/en/crud.php b/resources/lang/en/crud.php index 6ffb1652..5f11e57f 100644 --- a/resources/lang/en/crud.php +++ b/resources/lang/en/crud.php @@ -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' => [ diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 26c5d541..c8e85267 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -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', ], ]; diff --git a/resources/lang/fr/crud.php b/resources/lang/fr/crud.php index eeb53e44..8bc08f70 100644 --- a/resources/lang/fr/crud.php +++ b/resources/lang/fr/crud.php @@ -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' => [ diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php index a88d0ab7..82b5f104 100644 --- a/resources/lang/fr/validation.php +++ b/resources/lang/fr/validation.php @@ -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', ], ]; diff --git a/resources/views/bo/pages/recipes/form.blade.php b/resources/views/bo/pages/recipes/form.blade.php index 0b7be71a..a6ef8b35 100644 --- a/resources/views/bo/pages/recipes/form.blade.php +++ b/resources/views/bo/pages/recipes/form.blade.php @@ -112,32 +112,6 @@ - {{-- DIFFICULTY --}} -
-
- {{ str(__('validation.custom.difficulty'))->ucfirst() }} -
-
- @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')), - ]), - ]) -
-
-
-
{{-- PICTURE --}}
@@ -256,6 +230,32 @@
+ {{-- DIFFICULTY --}} +
+
+ {{ str(__('validation.custom.difficulty'))->ucfirst() }} +
+
+ @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')), + ]), + ]) +
+
+
+
{{-- INGREDIENTS --}}
@@ -315,45 +315,28 @@
- {{-- TIPS --}} -
-
- {{ str(__('validation.custom.tips'))->ucfirst() }} -
-
- @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'), - ]), - ]) -
-
-
-
{{-- PREPARATION --}}
- {{ str(__('validation.custom.preparation'))->ucfirst() }} + {{ str(__('validation.custom.preparation'))->ucFirst() }} +
+
+ @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(), + ]) +
+
@include('bo.partials.inputs.text-html', [ @@ -380,34 +363,19 @@
{{-- ADVICE --}} -
-
- {{ str(__('validation.custom.advice'))->ucfirst() }} -
-
- @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, - ]), - ]) -
-
-
-
+ @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 --}}
@@ -443,30 +411,33 @@ {{-- CONSUMPTION_MOMENT_ID --}}
- {{ str(trans_choice('models.classes.consumption_moment', 1))->ucFirst() }} + {{ str(trans_choice('models.classes.consumption_moment', 2))->ucFirst() }}
@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), + ]), ])
diff --git a/resources/views/bo/pages/recipes/partials/optional-section.blade.php b/resources/views/bo/pages/recipes/partials/optional-section.blade.php new file mode 100644 index 00000000..0528b93b --- /dev/null +++ b/resources/views/bo/pages/recipes/partials/optional-section.blade.php @@ -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 + +
+
+ {{ str($legend)->ucFirst() }} +

+ {{ __('crud.minicrud.optional_section_helper') }} +

+ + {{-- TITLE --}} +
+
+ @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(), + ]) +
+
+ + {{-- CONTENT (WYSIWYG) --}} +
+
+ @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]), + ]) +
+
+ + {{-- PICTO --}} +
+ {{-- PICTO_ALT --}} +
+ @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'), + ]) +
+ {{-- PICTO_TITLE --}} +
+ @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'), + ]) +
+ {{-- PICTO_FILE --}} +
+ @include('bo.partials.inputs.varchar-picture', [ + 'id' => $idPrefix . 'Picto', + 'name' => $prefix . '_picto', + 'width' => 45, + 'height' => 45, + 'model' => $recipeModel ?? null, + 'required' => false, + 'preview' => true, + ]) +
+
+
+
\ No newline at end of file diff --git a/resources/views/bo/pages/recipes/show.blade.php b/resources/views/bo/pages/recipes/show.blade.php index 718dc8d3..5ae3bd19 100644 --- a/resources/views/bo/pages/recipes/show.blade.php +++ b/resources/views/bo/pages/recipes/show.blade.php @@ -54,13 +54,6 @@ {{ $recipeModel->title }} - - - {{ str(__('validation.custom.tips'))->ucFirst() }} - - {{ $recipeModel->tips }} - - {{ str(__('validation.attributes.image'))->ucFirst() }} @@ -68,12 +61,26 @@ {{ $recipeModel->picture_alt }} + + + + {{ str(__('validation.custom.preparation_time'))->ucFirst() }} + + {{ $recipeModel->preparation_time }} + - {{ str(__('validation.custom.nb_guests'))->ucFirst() }} + {{ str(__('validation.custom.cooking_time'))->ucFirst() }} - {{ $recipeModel->nb_guests }} + {{ $recipeModel->cooking_time }} + + + + + {{ str(__('validation.custom.difficulty'))->ucFirst() }} + + {{ $recipeModel->difficulty->label() }} @@ -94,10 +101,19 @@ + + + {{ str(__('validation.custom.nb_guests'))->ucFirst() }} + + {{ $recipeModel->nb_guests }} + + {{ str(__('validation.custom.preparation'))->ucFirst() }} +

{{ $recipeModel->preparation_title ?: '--' }}

+
{!! clean($recipeModel->preparation) !!}
@@ -107,40 +123,61 @@ {{ str(__('validation.custom.advice'))->ucFirst() }} -
- {!! clean($recipeModel->advice) !!} -
+ @if (blank($recipeModel->advice_title) && blank($recipeModel->advice) && blank($recipeModel->advice_picto)) + -- + @else + @if (filled($recipeModel->advice_picto)) + {{ $recipeModel->advice_picto_alt }} + + Alt : {{ $recipeModel->advice_picto_alt ?: '--' }} · +
+ Title : {{ $recipeModel->advice_picto_title ?: '--' }} +
+ @endif + @if (filled($recipeModel->advice_title)) +

{{ $recipeModel->advice_title }}

+ @endif + @if (filled($recipeModel->advice)) +
+ {!! clean($recipeModel->advice) !!} +
+ @endif + @endif - {{ str(__('validation.custom.preparation_time'))->ucFirst() }} + {{ str(__('validation.custom.tips'))->ucFirst() }} - {{ $recipeModel->preparation_time }} + @if (blank($recipeModel->tips_title) && blank($recipeModel->tips) && blank($recipeModel->tips_picto)) + — + @else + @if (filled($recipeModel->tips_picto)) + {{ $recipeModel->tips_picto_alt }} + + Alt : {{ $recipeModel->tips_picto_alt ?: '--' }} · +
+ Title : {{ $recipeModel->tips_picto_title ?: '--' }} +
+ @endif + @if (filled($recipeModel->tips_title)) +

{{ $recipeModel->tips_title }}

+ @endif + @if (filled($recipeModel->tips)) +
+ {!! clean($recipeModel->tips) !!} +
+ @endif + @endif - - - {{ str(__('validation.custom.cooking_time'))->ucFirst() }} - - {{ $recipeModel->cooking_time }} - - - - - {{ str(__('validation.custom.difficulty'))->ucFirst() }} - - {{ $recipeModel->difficulty->label() }} - - - - - {{ str(trans_choice('models.classes.consumption_moment', 1))->ucFirst() }} - - {{ $recipeModel->consumptionMoment->title }} - - - @if($recipeModel->video) + @if ($recipeModel->video) {{ str(__('models.classes.video'))->ucFirst() }} @@ -149,6 +186,14 @@ @endif + + + {{ str(trans_choice('models.classes.consumption_moment', $recipeModel->consumptionMoments->count()))->ucFirst() }} + + + {{ $recipeModel->consumptionMoments->pluck('title')->join(', ') ?: '--' }} + + {{ str(__('models.classes.product'))->ucFirst() }} diff --git a/resources/views/fo/documents/recipe-commercial-sheet.blade.php b/resources/views/fo/documents/recipe-commercial-sheet.blade.php index 1a267fe0..985ae0ef 100644 --- a/resources/views/fo/documents/recipe-commercial-sheet.blade.php +++ b/resources/views/fo/documents/recipe-commercial-sheet.blade.php @@ -246,8 +246,8 @@ @endif -

{{ str(sprintf("%s associé", trans_choice('models.classes.consumption_moment', 1)))->ucFirst() }}

-

{{ $recipeModel->consumption_moment_id->title }}



+

{{ str(sprintf("%s associé", trans_choice('models.classes.consumption_moment', $recipeModel->consumptionMoments->count())))->ucFirst() }}

+

{{ $recipeModel->consumptionMoments->pluck('title')->join(', ') }}



diff --git a/resources/views/fo/pages/products/show.blade.php b/resources/views/fo/pages/products/show.blade.php index 90abbe66..4ca7dd84 100644 --- a/resources/views/fo/pages/products/show.blade.php +++ b/resources/views/fo/pages/products/show.blade.php @@ -376,12 +376,16 @@
@elseif ($recipeModel) -
+ @php($consumptionMoment = $recipeModel->consumptionMoments->first()) +
- {{ $recipeModel->consumptionMoment->picture_alt }} + @if ($consumptionMoment) + {{ $consumptionMoment->picture_alt }} + @endif

{{ $staticPageModel->data['animation_text_1']['value'] }} diff --git a/resources/views/fo/pages/recipes/show.blade.php b/resources/views/fo/pages/recipes/show.blade.php index ebf89202..39562dfb 100644 --- a/resources/views/fo/pages/recipes/show.blade.php +++ b/resources/views/fo/pages/recipes/show.blade.php @@ -45,12 +45,6 @@ ]) --}} -

- {{ __('Des couverts') }} -

{{ $staticPageModel->data['tips_title']['value'] }}

-
-

{{ $recipeModel->tips }}

{{-- RECIPE VIDEO --}}
@@ -154,18 +148,47 @@
-

{{ str(__('préparation'))->upper() }}

- {!! clean($recipeModel->preparation) !!} -
- @if ($recipeModel->advice) -
- {{ __('Des couverts') }} -

{{ __('le conseil le gaulois') }}

-
- @endif - {!! clean($recipeModel->advice) !!} +

{{ $recipeModel->preparation_title ?: __('préparation') }}

+ {!! clean($recipeModel->preparation) !!}
+ + @if ($recipeModel->hasAdvice()) +
+ @if (filled($recipeModel->advice_picto) || filled($recipeModel->advice_title)) +
+ @if (filled($recipeModel->advice_picto)) + {{ $recipeModel->advice_picto_alt }}advice_picto_title)) title="{{ $recipeModel->advice_picto_title }}" @endif> + @endif + @if (filled($recipeModel->advice_title)) +

{{ $recipeModel->advice_title }}

+ @endif +
+ @endif + {!! clean($recipeModel->advice) !!} +
+ @endif + + @if ($recipeModel->hasTips()) +
+ @if (filled($recipeModel->tips_picto) || filled($recipeModel->tips_title)) +
+ @if (filled($recipeModel->tips_picto)) + {{ $recipeModel->tips_picto_alt }}tips_picto_title)) title="{{ $recipeModel->tips_picto_title }}" @endif> + @endif + @if (filled($recipeModel->tips_title)) +

{{ $recipeModel->tips_title }}

+ @endif +
+ @endif + {!! clean($recipeModel->tips) !!} +
+ @endif
{{-- RATINGS--}} diff --git a/resources/views/fo/partials/recipe-badges.blade.php b/resources/views/fo/partials/recipe-badges.blade.php index 81757114..5601360d 100644 --- a/resources/views/fo/partials/recipe-badges.blade.php +++ b/resources/views/fo/partials/recipe-badges.blade.php @@ -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)

{{ $item }}

diff --git a/routes/web-bo.php b/routes/web-bo.php index 93e6cb03..76e2c37e 100644 --- a/routes/web-bo.php +++ b/routes/web-bo.php @@ -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);