feat: reorder recipes form and add filds for sections
This commit is contained in:
parent
12f22bf593
commit
46fd50d5da
24 changed files with 606 additions and 197 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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))));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
|
|
@ -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' => [
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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' => [
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'] }}
|
||||
|
|
|
|||
|
|
@ -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--}}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue