Merge branch 'develop' into trunk

This commit is contained in:
Noah Lancien 2026-04-22 16:52:34 +02:00
commit 49c926cc8b
53 changed files with 2452 additions and 253 deletions

View file

@ -19,5 +19,6 @@ enum ApproachTypeEnum: int
private const LABELS = [
self::label->name => 'validation.custom.label',
self::demarche->name => 'validation.custom.demarche',
];
}

View file

@ -30,6 +30,8 @@ enum StaticPageType: int
case search = 20;
case sitemap = 21;
case rules = 22;
case exitOutside = 23;
case ouiCBon = 24;
/**
* Optionnal labels definition.
@ -59,6 +61,8 @@ enum StaticPageType: int
self::search->name => 'Recherche',
self::sitemap->name => 'Plan du site',
self::rules->name => 'Règlement du jeu',
self::exitOutside->name => 'Sortir à l\'extérieur',
self::ouiCBon->name => 'Oui c\'est bon'
];
/**

View file

@ -0,0 +1,200 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\CategoryApproaches\CategoryApproachRequest;
use App\Models\CategoryApproach;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class CategoryApproachesController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(CategoryApproach::class, 'categories_approach');
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
$categories = CategoryApproach::query();
$count = CategoryApproach::count();
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$categories,
$search,
null,
'name',
);
}
$searchFields = \implode(', ', [
trans('validation.attributes.name'),
]);
/** Sort columns with a query */
$this->sortQuery($categories);
$categories = $categories->getModels();
return view(
'bo.pages.categories_approaches.index',
compact('categories', 'count', 'searchFields', 'search')
);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\CategoryApproach $categoryApproaches
* @return \Illuminate\Contracts\View\View
*/
public function create(CategoryApproach $categoryApproaches)
{
$count = CategoryApproach::count();
if ($count >= CategoryApproach::MAX_CATEGORIES) {
return redirect()
->route('bo.categories.approaches.index')
->with('error', "Limite atteinte : vous ne pouvez pas créer plus de " . CategoryApproach::MAX_CATEGORIES . " catégories.");
}
return view('bo.pages.categories_approaches.create', ['categoryApproachesModel' => $categoryApproaches]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\CategoryApproaches\CategoryApproachRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(CategoryApproachRequest $request): \Illuminate\Http\RedirectResponse
{
if (CategoryApproach::count() >= CategoryApproach::MAX_CATEGORIES) {
throw ValidationException::withMessages([
'name' => "Limite atteinte : maximum " . CategoryApproach::MAX_CATEGORIES . " catégories."
]);
}
return DB::transaction(function () use ($request) {
$category = new CategoryApproach();
$category->fill($request->validated());
if ($category->saveOrFail()) {
return redirect()->route('bo.categories.approaches.index')
->with('success', trans('crud.messages.has_been_created', [
'model' => Str::ucfirst(trans('validation.custom.category'))
]));
}
});
return redirect()->back()
->with('error', trans('crud.messages.cannot_be_created', [
'model' => Str::ucfirst(trans('validation.custom.category'))
]));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\CategoryApproach $categories_approach
* @return \Illuminate\Contracts\View\View
*/
public function edit(CategoryApproach $categories_approach): \Illuminate\Contracts\View\View
{
return view('bo.pages.categories_approaches.edit', [
'categoryApproachesModel' => $categories_approach
]);
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\CategoryApproach $categories_approach
* @return \Illuminate\Contracts\View\View
*/
public function show(CategoryApproach $categories_approach): \Illuminate\Contracts\View\View
{
return view('bo.pages.categories_approaches.show', [
'categoryApproachesModel' => $categories_approach
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Request\Bo\CategoryApproaches\CategoryApproachRequest $request
* @param \App\Models\CategoryApproach $categories_approach
* @return \Illuminate\Http\RedirectResponse
*/
public function update(CategoryApproachRequest $request, CategoryApproach $categories_approach)
{
if ($categories_approach->update($request->validated())) {
return redirect()
->route('bo.categories.approaches.index')
->with('success', trans('crud.messages.has_been_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.categoryApproach', 1))]));
}
return redirect()->route('bo.categories.approaches.edit', $categories_approach)
->with('error', trans('crud.messages.cannot_be_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.categoryApproach', 1))
]));
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\CategoryApproach $categories_approach
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(CategoryApproach $categories_approach): \Illuminate\Http\RedirectResponse
{
if (CategoryApproach::count() === CategoryApproach::MIN_CATEGORIES) {
return redirect()
->route('bo.categories.approaches.index')
->with('error', "Limite atteinte : vous ne pouvez pas supprimer toutes les catégories (une catégorie minimum).");
}
if ($categories_approach->delete()) {
return redirect()
->route('bo.categories.approaches.index')
->with('success', trans('crud.messages.has_been_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.categoryApproach', 1))]));
}
return redirect()->back()
->with('success', trans('crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.categoryApproach', 1))]));
}
/**
* Remove the specified resource from storage.
*
* @param CategoryApproach $section_approach
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(CategoryApproach $categories_approach)
{
return $this->create($categories_approach->replicate());
}
/**
* Get a paginate resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function jsonPaginateCategory(Request $request): \Illuminate\Http\JsonResponse
{
$categoryApproachModel = $request->targetModel::query()->orderBy('name', 'ASC')->paginate();
return \response()->json($categoryApproachModel);
}
}

View file

@ -0,0 +1,202 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\SectionApproaches\SectionApproachRequest;
use App\Models\SectionApproach;
use App\Services\SectionApproach\SectionApproachService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class SectionApproachController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct(
private readonly SectionApproachService $sectionApproachService
) {
$this->authorizeResource(SectionApproach::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
$sections = SectionApproach::with(['category', 'page']);
$formData = $this->sectionApproachService->getFormData();
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$sections,
$search,
null,
'title',
);
}
$searchFields = \implode(', ', [
trans_choice('models.classes.sectionApproach', 1),
]);
$this->sortQuery($sections);
$sections = $this->paginate($sections);
return view(
'bo.pages.sections_approaches.index',
compact('sections', 'search', 'searchFields') + $formData
);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\SectionApproach $section_approach
* @return \Illuminate\Contracts\View\View
*/
public function create(SectionApproach $section_approach): \Illuminate\Contracts\View\View
{
$formData = $this->sectionApproachService->getFormData();
return view('bo.pages.sections_approaches.create', [
'sectionApproachModel' => $section_approach,
'categoryApproachModel' => $formData['categories'],
'staticPageModel' => $formData['pages'],
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\SectionApproaches\SectionApproachRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(SectionApproachRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
try {
$section = $this->sectionApproachService->store($request->validated());
return redirect()
->route('bo.sections.approaches.edit', $section)
->with('success', trans('crud.messages.has_been_created', [
'model' => Str::ucfirst(trans_choice('models.classes.sectionApproach', 1)),
]));
} catch (\Exception $e) {
return redirect()->back()->withInput()->withErrors(['category_id' => $e->getMessage()]);
}
});
}
/**
* Show the specified resource.
*
* @param \App\Models\SectionApproach $section_approach
* @return \Illuminate\Contracts\View\View
*/
public function show(SectionApproach $section_approach): \Illuminate\Contracts\View\View
{
return view(
'bo.pages.sections_approaches.show',
['sectionApproachModel' => $section_approach]
);
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\SectionApproach $section_approach
* @return \Illuminate\Contracts\View\View
*/
public function edit(SectionApproach $section_approach): \Illuminate\Contracts\View\View
{
$formData = $this->sectionApproachService->getFormData();
return view(
'bo.pages.sections_approaches.edit',
['sectionApproachModel' => $section_approach]
);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\SectionApproaches\SectionApproachRequest $request
* @param \App\Models\SectionApproach $section_approach
* @return \Illuminate\Http\RedirectResponse
*/
public function update(SectionApproachRequest $request, SectionApproach $section_approach): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $section_approach) {
try {
$this->sectionApproachService->update($section_approach, $request->validated());
return redirect()
->route('bo.sections.approaches.edit', $section_approach)
->with('success', trans('crud.messages.has_been_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.sectionApproach', 1)),
]));
} catch (\Exception $e) {
return redirect()->back()->withInput()->withErrors(['category_id' => $e->getMessage()]);
}
});
}
/**
* Remove the specified resource from storage.
*
* @param SectionApproach $section_approach
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(SectionApproach $section_approach): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($section_approach) {
try {
$this->sectionApproachService->destroy($section_approach);
return redirect()
->route('bo.sections.approaches.index')
->with('success', trans('crud.messages.has_been_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.sectionApproach', 1)),
]));
} catch (\Exception $e) {
return redirect()->back()->withErrors(['delete' => $e->getMessage()]);
}
});
}
/**
* Duplicate the specified section approach by pre-filling the create form.
*
* @param SectionApproach $section_approach
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(SectionApproach $section_approach): \Illuminate\Contracts\View\View
{
return $this->create($section_approach->replicate());
}
/**
* Get a paginate resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function jsonPaginatePage(Request $request): \Illuminate\Http\JsonResponse
{
$staticPageModel = $request->targetModel::whereIn('type', [
StaticPageType::ouiCBon,
StaticPageType::exitOutside,
])->paginate();
return response()->json($staticPageModel);
}
}

View file

@ -4,10 +4,8 @@ namespace App\Http\Controllers\Club;
use App\Http\Controllers\Controller;
use App\Http\Requests\Club\MesBr\PrintCouponsRequest;
use App\Models\Customer;
use App\Models\Discount;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
class DiscountController extends Controller
{

View file

@ -27,7 +27,7 @@ class MessageController extends Controller
* @return \Illuminate\Contracts\View\View
*/
public function index(): \Illuminate\Contracts\View\View
{
{
return view('club.pages.messages.index');
}
@ -63,7 +63,7 @@ class MessageController extends Controller
->choose();
return $conversation;
})->all();
// * Notifications
$notifications = $customer->notifications()
->where('notifiable_type', Message::class)

View file

@ -2,9 +2,13 @@
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Lib\Helpers\CollectionHelper;
use App\Models\CategoryApproach;
use App\Models\NewsBreeding;
use App\Models\OcbBreeder;
use App\Models\SectionApproach;
use App\Models\StaticPage;
use Illuminate\Database\Eloquent\Builder;
@ -63,4 +67,25 @@ class BreedingController extends Controller
'asideNewsModels' => $asideNewsModels
]);
}
/**
* Display the given breeding page.
*
* @return \Illuminate\Contracts\View\View
*/
public function exitOutsidePage(): \Illuminate\Contracts\View\View
{
$staticPage = StaticPage::query()->where('type', StaticPageType::exitOutside)->first();
$sliderImages = CollectionHelper::mapSlidesToFields($staticPage->data);
$categoryApproachModels = CategoryApproach::all();
$sections = SectionApproach::query()->where('page_id', $staticPage->id)->get();
$sectionApproachModels = CollectionHelper::mapItemsToCards($sections->toArray(), $categoryApproachModels->toArray());
return view('fo.pages.breeding.sortant-a-l-exterieur', [
'staticPageModel' => $staticPage,
'sliderImages' => $sliderImages,
'categoryApproachModels' => $categoryApproachModels,
'sectionApproachModels' => $sectionApproachModels
]);
}
}

View file

@ -2,9 +2,7 @@
namespace App\Http\Controllers\Fo;
use App\Models\StaticPage;
use App\Models\OptinConsent;
use Illuminate\Http\Request;
use LDC\LDCCRM\Models\SingleOptin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\ConsentRequest;
@ -23,7 +21,6 @@ class ConsentController extends Controller
* @throws \LDC\LDCCRM\Exceptions\CrmException CrmException
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException ApiErrorException
*/
public function consent(ConsentRequest $request): \Illuminate\Contracts\View\View
{
$isSubscribed = false;

View file

@ -54,13 +54,13 @@ class ContactController extends Controller
if (ContactFormType::make($validatedData['type'])->equals(ContactFormType::complain)) {
// Send to Passerelle if it is a complain about a product.
\dispatch(new SendPasserelleMail($data, $joinFilePath));
} else if (isset($validatedData['locale']) && ContactFormType::make($validatedData['locale'])->equals(ContactFormType::nl)) {
} elseif (isset($validatedData['locale']) && ContactFormType::make($validatedData['locale'])->equals(ContactFormType::nl)) {
// Send to Export Mailing List if it is a complain from NL page.
app()->setLocale('nl');
\dispatch(new SendNLMail($data, $joinFilePath));
} else {
// Send to the CRM.
if ($joinFilePath) {
if ($joinFilePath) {
$relativePath = str_replace(storage_path('app/public') . '/', '', $joinFilePath);
$binary = Storage::disk('public')->get($relativePath);
$base64 = base64_encode($binary);
@ -83,7 +83,7 @@ class ContactController extends Controller
// Send mail.
\dispatch(new SendClaimMail($data, $joinFilePath));
}
}//end if
return back()->with('success', trans('contact.message_contact_send'));
} catch (AbortContactMailer $e) {

View file

@ -35,8 +35,8 @@ class HomeController extends Controller
*/
public function index(): \Illuminate\Contracts\View\View
{
$productCacheKey = 'HomeProductsViews' . (auth('club')->user() ? '_' . auth('club')->user()->id : '');
$recipeCacheKey = 'HomeRecipesViews' . (auth('club')->user() ? '_' . auth('club')->user()->id : '');
$productCacheKey = 'HomeProductsViews' . (auth('club')->user() ? '_' . auth('club')->user()->id : '');
$recipeCacheKey = 'HomeRecipesViews' . (auth('club')->user() ? '_' . auth('club')->user()->id : '');
$staticPageModel = StaticPage::getCachedStaticPage(StaticPageType::home);
$mainSlideModels = MainSlide::getPublishedOrderedWithApproaches();
$demarcheSlideModels = DemarcheSlide::getPublishedOrdered();

View file

@ -0,0 +1,42 @@
<?php
namespace App\Http\Requests\Bo\CategoryApproaches;
use App\Models\CategoryApproach;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class CategoryApproachRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return Gate::check('create', CategoryApproach::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => 'nullable|string|between:3,255',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'name' => trans('validation.attributes.name'),
];
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace App\Http\Requests\Bo\SectionApproaches;
use App\Models\SectionApproach;
use App\Rules\WysiwygLength;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class SectionApproachRequest extends FormRequest
{
use HasPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', SectionApproach::class);
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->mergePicture('picture');
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
$rules = [
'title' => 'required|string|between:3,255',
'content' => ['required','string',new WysiwygLength()],
'picture_alt' => 'required_with:picture|nullable|between:3,255',
'picture_title' => 'required_with:picture|nullable|between:3,255',
'category_id' => 'required|exists:category_approaches,id',
'page_id' => 'required|exists:static_pages,id'
];
return \array_merge(
$rules,
$this->pictureRules('picture', 75, 45, 90, 90)
);
}
public function attributes()
{
return [
'name' => trans('validation.attributes.name'),
'picture' => trans('validation.attributes.image'),
'picture_alt' => trans('validation.custom.attribute_alt'),
'picture_title' => trans('validation.custom.attribute_title'),
'category_id' => trans('validattion.custom.category'),
'page_id' => trans('validattion.custom.static_page_id'),
];
}
}

View file

@ -67,6 +67,19 @@ class UpdateStaticPageRequest extends UpdateMiniCrudRequest
) {
$this->mergePicture('picture');
}
// check nullable value of image's slider
if ($staticPageModel->type->equals(StaticPageType::exitOutside)) {
$test = $this->data;
foreach ($test as $key => $value) {
if (Str::startsWith($key, 'slider_image_')) {
$checkValue = isset($value['value']) ? $value['value'] : null;
$test[$key]['value'] = $checkValue;
$test[$key]['alt'] = (!is_null($checkValue) ? $value['alt'] : null);
$test[$key]['title'] = (!is_null($checkValue) ? $value['title'] : null);
}
}
$this->merge(['data' => $test]);
}
parent::prepareForValidation();
}
@ -133,7 +146,9 @@ class UpdateStaticPageRequest extends UpdateMiniCrudRequest
!$staticPageModel->type->equals(StaticPageType::legals) &&
!$staticPageModel->type->equals(StaticPageType::confidentiality) &&
!$staticPageModel->type->equals(StaticPageType::selling) &&
!$staticPageModel->type->equals(StaticPageType::accessibility)
!$staticPageModel->type->equals(StaticPageType::accessibility) &&
!$staticPageModel->type->equals(StaticPageType::exitOutside) &&
!$staticPageModel->type->equals(StaticPageType::ouiCBon)
) {
$rules = \array_merge(
$rules,
@ -149,7 +164,9 @@ class UpdateStaticPageRequest extends UpdateMiniCrudRequest
!$staticPageModel->type->equals(StaticPageType::product) &&
!$staticPageModel->type->equals(StaticPageType::home) &&
!$staticPageModel->type->equals(StaticPageType::contact) &&
!$staticPageModel->type->equals(StaticPageType::search)
!$staticPageModel->type->equals(StaticPageType::search) &&
!$staticPageModel->type->equals(StaticPageType::exitOutside) &&
!$staticPageModel->type->equals(StaticPageType::ouiCBon)
) {
$rules = \array_merge(
$rules,

View file

@ -102,4 +102,105 @@ class CollectionHelper
return view($path, [$key => $item])->render();
})->toArray();
}
public static function mapSlidesToFields(array $fields): array
{
return collect($fields)
->filter(fn($field, $key) => str_starts_with($key, 'slider_image_'))
->filter(fn($field) => !empty($field['value']))
->sortKeys()
->map(fn($field) => [
'image' => [
'path' => $field['value'],
'title' => $field['title'] ?? '',
'alt' => $field['alt'] ?? '',
],
])
->values()
->toArray();
}
/**
* Transform and merge 2 arrays to get homogeneous result
*
* @param array $items sections array
* @param array $categories categories array
* @return array
*/
public static function mapItemsToCards(array $items, array $categories): array
{
$categoriesById = collect($categories)->keyBy('id');
$result = [];
foreach ($items as $item) {
$category = $categoriesById->get($item['category_id']);
if (!$category) {
continue;
}
$categoryKey = $category['name'];
$parsed = self::parseContent($item['content']);
$result[$categoryKey][] = [
'category' => $item['title'],
'icon' => [
'path' => $item['picture'],
'alt' => $item['picture_alt'],
'description' => $item['picture_title'],
],
'content' => [
[
'title' => $parsed['title'],
'points' => $parsed['points'],
'asterisc' => $parsed['asterisc'],
],
],
'class' => 'card-template-sm',
];
}
return $result;
}
private static function parseContent(string $html): array
{
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
// Extract h3
$title = '';
$h3 = $dom->getElementsByTagName('h3');
if ($h3->length > 0) {
$title = $h3->item(0)->textContent;
}
// Extract li
$points = [];
$liNodes = $dom->getElementsByTagName('li');
foreach ($liNodes as $li) {
$points[] = trim($li->textContent);
}
// Extract last <p> *
$lastAsterisc = null;
$pNodes = $dom->getElementsByTagName('p');
foreach ($pNodes as $p) {
$text = trim($p->textContent);
if (str_starts_with($text, '*')) {
$lastAsterisc = $text;
}
}
// if nothing to extract
if (empty($title) && empty($points)) {
$body = $dom->getElementsByTagName('body')->item(0);
$rawText = trim($body ? $body->textContent : strip_tags($html));
if (!empty($rawText)) {
$points[] = $rawText;
}
}
return [
'title' => $title,
'points' => $points,
'asterisc' => $lastAsterisc ?? '',
];
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* CategoryApproach Model
*
* @property string $name Name of the category approach.
* @property-read \Carbon\Carbon $created_at Creation timestamp.
* @property-read \Carbon\Carbon $updated_at Last update timestamp.
*/
class CategoryApproach extends Model
{
/**
* Maximum number of category approaches allowed in the system.
*
* @var int
*/
public const MAX_CATEGORIES = 5;
/**
* Minimum number of category approaches required in the system.
*
* @var int
*/
public const MIN_CATEGORIES = 1;
/**
* The attributes that are mass assignable.
*
* Defines which attributes can be filled via mass assignment operations.
*
* @var array<int, string>
*/
protected $fillable = [
'name'
];
/**
* Validate that the maximum number of category approaches has not been exceeded.
*
* @return void
* @throws \Exception If the maximum number of categories has been exceeded.
*/
protected function validateMaxCategoryCount(): void
{
if (static::count() > self::MAX_CATEGORIES) {
throw new \Exception('Cannot create more than ' . self::MAX_CATEGORIES . ' category approaches.');
}
}
/**
* Validate that the minimum number of category approaches is maintained.
*
* @return void
* @throws \Exception If deletion would violate the minimum category requirement.
*/
protected static function validateMinCategoryCount(): void
{
if (static::count() <= self::MIN_CATEGORIES) {
throw new \Exception('At least ' . self::MIN_CATEGORIES . ' category approach is required.');
}
}
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booting(): void
{
static::creating(function (self $model) {
$model->validateMaxCategoryCount();
});
static::updating(function (self $model) {
$model->validateMaxCategoryCount();
});
static::deleting(function (self $model) {
static::validateMinCategoryCount();
});
}
/**
* Get all section approaches associated with this category approach.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function sectionsApproaches(): HasMany
{
return $this->hasMany(SectionApproach::class, 'category_id');
}
}

View file

@ -4,7 +4,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* @property string $email Email of the customer.
* @property string $hashcode Hashcode of the customer.

View file

@ -0,0 +1,133 @@
<?php
namespace App\Models;
use App\Lib\Helpers\FileStorageHelper;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* SectionApproach Model
*
* @property int $id Unique identifier of the section
* @property string $title Title of the section
* @property string $content Textual content of the section
* @property string|null $picture Storage path of the image
* @property string|null $picture_alt Alternative text of the image (accessibility)
* @property string|null $picture_title Title of the image
* @property int $category_id Identifier of the associated category
* @property int $page_id Identifier of the associated static page
* @property-read \Carbon\Carbon $created_at Creation timestamp.
* @property-read \Carbon\Carbon $updated_at Last update timestamp.
*
* @property-read CategoryApproach $category Associated category
* @property-read StaticPage $page Associated static page
*/
class SectionApproach extends Model
{
/**
* Maximum number of sections allowed
*
* @var int
*/
public const MAX_SECTIONS = 6;
/**
* Minimum number of sections required
*
* @var int
*/
public const MIN_SECTIONS = 2;
/**
* The attributes that are mass assignable
*
* @var array<int, string>
*/
protected $fillable = [
'title',
'content',
'picture',
'picture_alt',
'picture_title',
'category_id',
'page_id'
];
/**
* Validates that the maximum number of sections has not been exceeded.
*
* @return void
* @throws \Exception If the maximum number of sections has been exceeded.
*/
protected function validateMaxSectionCount(): void
{
$sectionCount = static::where('category_id', $this->category_id)->where('page_id', $this->page_id)->count();
if ($sectionCount >= self::MAX_SECTIONS) {
throw new \Exception('Cannot create more than ' . self::MAX_SECTIONS . ' sections per category.');
}
}
/**
* Validates that the minimum number of sections is maintained.
*
* @return void
* @throws \Exception If deletion would violate the minimum section requirement.
*/
protected static function validateMinSectionCount(self $approach): void
{
$sectionCount = static::where('category_id', $approach->category_id)->where('page_id', $approach->page_id)->count();
if ($sectionCount <= self::MIN_SECTIONS) {
throw new \Exception('At least ' . self::MIN_SECTIONS . ' sections are required per category.');
}
}
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted(): void
{
static::creating(function (self $approach) {
$approach->validateMaxSectionCount();
$approach->picture = FileStorageHelper::storeFile($approach, $approach->picture, true);
});
static::updating(function (self $approach) {
$approach->picture = FileStorageHelper::storeFile($approach, $approach->picture, true);
});
static::updated(function (self $approach) {
FileStorageHelper::removeOldFile($approach, 'picture');
});
static::deleting(function (self $approach) {
static::validateMinSectionCount($approach);
});
static::deleted(function (self $approach) {
FileStorageHelper::removeFile($approach, 'picture');
});
}
/**
* Relationship: belongs to a category approach
*
* @return BelongsTo Returns the associated category
*/
public function category(): BelongsTo
{
return $this->belongsTo(CategoryApproach::class, 'category_id');
}
/**
* Relationship: belongs to a static page
*
* @return BelongsTo Returns the associated static page
*/
public function page(): BelongsTo
{
return $this->belongsTo(StaticPage::class);
}
}

View file

@ -9,6 +9,7 @@ use App\Traits\Models\ActivityLog;
use App\Traits\Models\MiniCrudModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Spatie\SchemaOrg\Schema;
@ -295,4 +296,9 @@ class StaticPage extends Model implements SchemaOrgInterface, RedirectableInterf
{
return $this->hasMany(NewsBreeding::class);
}
public function sectionsApproaches(): HasMany
{
return $this->hasMany(SectionApproach::class, 'page_id');
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\CategoryApproach;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
class CategoryApproachPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(): bool
{
return UserStaticRules::everyOne();
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function view(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
}
/**
* Determine whether the user can create a new model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function create(User $authUser): bool
{
if (CategoryApproach::count() <= CategoryApproach::MAX_CATEGORIES) {
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
}
return false;
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function update(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\CategoryApproach $category
* @return boolean
*/
public function delete(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
}
/**
* Determine whether the user can duplicate the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function duplicate(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\SectionApproach;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
class SectionApproachPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(): bool
{
return UserStaticRules::everyOne();
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function view(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
}
/**
* Determine whether the user can create a new model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function create(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function update(User $authUser, SectionApproach $section_approaches): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\SectionApproaches $section
* @return boolean
*/
public function delete(User $authUser, SectionApproach $section_approaches): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
}
/**
* Determine whether the user can duplicate the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function duplicate(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
}
}

View file

@ -133,6 +133,8 @@ class ViewServiceProvider extends ServiceProvider
['name' => 'breedingHealthStaticPageModel', 'value' => StaticPage::getCachedStaticPage(StaticPageType::breedingHealth)],
['name' => 'newsStaticPageModel', 'value' => StaticPage::getCachedStaticPage(StaticPageType::news)],
['name' => 'searchStaticPageModel', 'value' => StaticPage::getCachedStaticPage(StaticPageType::search)],
['name' => 'exitOutsideStaticPageModel', 'value' => StaticPage::getCachedStaticPage(StaticPageType::exitOutside)],
['name' => 'ouiCestBonStaticPageModel', 'value' => StaticPage::getCachedStaticPage(StaticPageType::ouiCBon)],
])->map(function ($array) {
View::share($array['name'], (function () use ($array) {
return $array['value'] ?: null;

View file

@ -0,0 +1,140 @@
<?php
namespace App\Services\SectionApproach;
use App\Models\CategoryApproach;
use App\Models\SectionApproach;
use App\Models\StaticPage;
use App\Enums\StaticPageType;
class SectionApproachService
{
/**
* Retrieve the data needed to populate create/edit forms.
* Returns paginated categories and eligible static pages.
*/
public function getFormData(): array
{
return [
'categories' => CategoryApproach::query()->orderBy('name', 'ASC')->paginate(),
'pages' => StaticPage::whereIn('type', [StaticPageType::ouiCBon, StaticPageType::exitOutside])->paginate(),
];
}
/**
* Create and persist a new section approach.
* Throws an exception if the maximum number of sections per category/page is reached.
*
* @param array $validated rules validated in request
* @throws \Exception
*/
public function store(array $validated): SectionApproach
{
$pageId = (int) $validated['page_id'];
$categoryId = (int) $validated['category_id'];
$page = StaticPage::findOrFail($pageId);
$category = CategoryApproach::findOrFail($categoryId);
$count = SectionApproach::query()
->where('page_id', $pageId)
->where('category_id', $categoryId)
->lockForUpdate()
->count();
if ($count >= SectionApproach::MAX_SECTIONS) {
throw new \Exception(
"Limite atteinte : La catégorie ''{$category->name}'' dans la page ''{$page->title}'' contient déjà " . SectionApproach::MAX_SECTIONS . " sections."
);
}
$section = new SectionApproach();
$section->fill($validated);
$section->save();
return $section;
}
/**
* Update an existing section approach
*
* @param \App\Models\SectionApproach $section_approach model
* @param array $validated rules validated in request
* @throws \Exception
*/
public function update(SectionApproach $section_approach, array $validated): SectionApproach
{
$section = SectionApproach::query()
->whereKey($section_approach->id)
->lockForUpdate()
->firstOrFail();
$oldPageId = (int) $section->page_id;
$oldCategoryId = (int) $section->category_id;
$newPageId = (int) ($validated['page_id'] ?? $oldPageId);
$newCategoryId = (int) ($validated['category_id'] ?? $oldCategoryId);
$isMoving = ($oldPageId !== $newPageId) || ($oldCategoryId !== $newCategoryId);
if ($isMoving) {
// Checkcategory retains the minimum number of sections
$sourceCount = SectionApproach::query()
->where('page_id', $oldPageId)
->where('category_id', $oldCategoryId)
->lockForUpdate()
->count();
if ($sourceCount <= SectionApproach::MIN_SECTIONS) {
throw new \Exception(
"Déplacement impossible : la catégorie d'origine doit garder au minimum " . SectionApproach::MIN_SECTIONS . " sections."
);
}
// Check category has not reached the maximum number of sections
$targetCount = SectionApproach::query()
->where('page_id', $newPageId)
->where('category_id', $newCategoryId)
->lockForUpdate()
->count();
if ($targetCount >= SectionApproach::MAX_SECTIONS) {
throw new \Exception(
"Déplacement impossible : la catégorie cible contient déjà " . SectionApproach::MAX_SECTIONS . " sections pour cette page."
);
}
}//end if
$section->fill($validated);
$section->save();
return $section;
}
/**
* Delete a section approach.
* Throws an exception if the category would fall below the minimum required sections.
*
* @param App\Models\SectionApproach $section_approach
* @throws \Exception
*/
public function destroy(SectionApproach $section_approach): void
{
$section = SectionApproach::query()
->whereKey($section_approach->id)
->lockForUpdate()
->firstOrFail();
$count = SectionApproach::query()
->where('page_id', $section->page_id)
->where('category_id', $section->category_id)
->lockForUpdate()
->count();
if ($count <= SectionApproach::MIN_SECTIONS) {
throw new \Exception(
"Suppression impossible : une catégorie doit contenir au minimum " . SectionApproach::MIN_SECTIONS . " sections dans une page."
);
}
$section->delete();
}
}

View file

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('category_approaches', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('category_approaches');
}
};

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('section_approaches', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content')->comment('content.');
$table->string('picture')->comment('picture path.');
$table->string('picture_alt')->comment('Picture alt attribute.');
$table->string('picture_title')->comment('Picture title attribute.');
$table->foreignId('category_id')->constrained('category_approaches')->cascadeOnDelete();
$table->foreignId('page_id')->constrained('static_pages')->cascadeOnDelete();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('section_approaches');
}
};

View file

@ -0,0 +1,148 @@
<?php
use App\Enums\StaticPageType;
use App\Models\StaticPage;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
//SORTIR A L' EXTERIEUR
StaticPage::updateOrCreate(
['type' => StaticPageType::exitOutside],
['data' => [
'title' => [
'label' => 'Cahier de charge : Titre',
'inputType' => 11,
'value' => '',
'validate' => 'required|string|between:3,255',
'required' => true
],
'description' => [
'label' => 'Cahier de charge : Description',
'inputType' => 12,
'value' => '',
'validate' => 'required|string|min:15',
'required' => true
],
'picto' => [
'label' => 'Cahier de charge : Picto',
'inputType' => 4,
'dimensions' => [
'width' => 90,
'height' => 90
],
'alt' => '',
'title' => '',
'value' => '',
'validate' => 'required',
'required' => true
],
'galery_title' => [
'label' => 'Slider : Titre',
'inputType' => 11,
'value' => '',
'validate' => 'required|string|between:3,255',
'required' => true
],
'slider_image_1' => [
'label' => 'Slider : Image 1',
'inputType' => 4,
'dimensions' => [
'width' => 338,
'height' => 222
],
'alt' => '',
'title' => '',
'value' => '',
'validate' => 'required',
'required' => true
],
'slider_image_2' => [
'label' => 'Slider : Image 2',
'inputType' => 4,
'dimensions' => [
'width' => 338,
'height' => 222
],
'alt' => '',
'title' => '',
'value' => '',
'validate' => 'required',
'required' => true
],
'slider_image_3' => [
'label' => 'Slider : Image 3',
'inputType' => 4,
'dimensions' => [
'width' => 338,
'height' => 222
],
'alt' => '',
'title' => '',
'value' => '',
'validate' => 'required',
'required' => true
],
'slider_image_4' => [
'label' => 'Slider : Image 4',
'inputType' => 4,
'dimensions' => [
'width' => 338,
'height' => 222
],
'alt' => '',
'title' => '',
'value' => '',
'validate' => 'nullable',
'required' => false
],
'slider_image_5' => [
'label' => 'Slider : Image 5',
'inputType' => 4,
'dimensions' => [
'width' => 338,
'height' => 222
],
'alt' => '',
'title' => '',
'value' => '',
'validate' => 'nullable',
'required' => false
],
'slider_image_6' => [
'label' => 'Slider : Image 6',
'inputType' => 4,
'dimensions' => [
'width' => 338,
'height' => 222
],
'alt' => '',
'title' => '',
'value' => '',
'validate' => 'nullable',
'required' => false
]
]]
);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('static_pages', function () {
$staticPage = StaticPage::query()->where('type', StaticPageType::exitOutside)->first();
if ($staticPage) {
$staticPage->data = [];
$staticPage->saveQuietly();
}
});
}
};

View file

@ -40,5 +40,7 @@ return [
'discount' => 'réduction',
'sell_item' => 'objet en vente|objets en vente',
'rating' => 'note',
'categoryApproach' => 'categorie|categories',
'sectionApproach' => 'section|sections'
]
];

View file

@ -344,6 +344,6 @@ return [
'saturated_fatty_acids' => 'dont Acides Gras Saturés',
'dietary_fiber' => 'fibres alimentaires',
'salt' => 'sel',
]
],
],
];

View file

@ -131,9 +131,7 @@
padding: 0.5rem 0.75rem;
}
#chicken-button-outside,
#chicken-button,
#turkey-button {
.category-btn {
--bs-btn-padding-y: .25rem;
--bs-btn-padding-x: .5rem;
--bs-btn-font-size: .75rem;

View file

@ -1,48 +1,41 @@
document.addEventListener("DOMContentLoaded", function () {
if (!document.querySelector("#oui-c-est-bon")) {
return;
}
const buttons = document.querySelectorAll<HTMLButtonElement>(".category-btn");
const chickenButton = document.getElementById("chicken-button");
const turkeyButton = document.getElementById("turkey-button");
if (!buttons.length) return;
function toggleCards(selectedType: "chicken" | "turkey") {
const chickenCards = document.querySelectorAll(".chicken-card");
const turkeyCards = document.querySelectorAll(".turkey-card");
const groups = document.querySelectorAll<HTMLElement>("[data-category]");
// Here I toggle visibility of cards
chickenCards.forEach(card => card.classList.toggle("d-none", selectedType !== "chicken"));
turkeyCards.forEach(card => card.classList.toggle("d-none", selectedType !== "turkey"));
function toggleCards(selectedCategory: string) {
groups.forEach(group => {
if (group.dataset.category === selectedCategory) {
group.style.display = "contents";
} else {
group.style.display = "none";
}
});
}
function handleButtonClick(event: MouseEvent) {
const target = event.target as HTMLElement;
const selectedType = target.id === "chicken-button" ? "chicken" : "turkey";
const isChicken = selectedType === "chicken";
if (chickenButton) {
chickenButton.classList.toggle("btn-secondary", isChicken);
chickenButton.classList.toggle("btn-light", !isChicken);
}
// Here we toggle buttons classes
if (turkeyButton) {
turkeyButton.classList.toggle("btn-secondary", !isChicken);
turkeyButton.classList.toggle("btn-light", isChicken);
}
const target = event.target as HTMLButtonElement;
const selectedCategory = target.dataset.category ?? "";
buttons.forEach(btn => {
btn.classList.toggle("btn-secondary", btn.dataset.category === selectedCategory);
btn.classList.toggle("btn-light", btn.dataset.category !== selectedCategory);
});
// Show/Hide cards
toggleCards(selectedType);
toggleCards(selectedCategory);
}
if (chickenButton) {
chickenButton.addEventListener("click", handleButtonClick);
}
buttons.forEach(btn => btn.addEventListener("click", handleButtonClick));
if (turkeyButton) {
turkeyButton.addEventListener("click", handleButtonClick);
}
const firstCategory = buttons[0].dataset.category ?? "";
// Set default card to chicken
// things will change if data come from db
toggleCards("chicken");
});
buttons.forEach(btn => {
btn.classList.toggle("btn-secondary", btn.dataset.category === firstCategory);
btn.classList.toggle("btn-light", btn.dataset.category !== firstCategory);
});
toggleCards(firstCategory);
});

View file

@ -1130,6 +1130,88 @@
</li>
</ul>
</div>
{{-- DEMARCHES --}}
<div class="dropdown">
<button data-bs-toggle="dropdown" type="button" aria-expanded="false" @class([
'btn btn-primary text-start w-100 dropdown-toggle mt-1',
'fw active' =>
request()->routeIs('bo.approaches.*'),
])>
<i @class([
'fa-solid fa-people-arrows fa-fw',
'fa-lg' =>
request()->routeIs('bo.approaches.*'),
])></i>
{{ __('Démarches') }}
</button>
<ul class="dropdown-menu">
{{-- PAGE SORTIR-A-L-EXTERIEUR --}}
@if (isset($exitOutsideStaticPageModel))
<li>
<a href="{{ route('bo.static_pages.edit', $exitOutsideStaticPageModel) }}" @class([
'dropdown-item px-3 py-2',
'fw-bold text-bg-secondary' =>
url()->current() ===
route('bo.static_pages.edit', $exitOutsideStaticPageModel),
])>
<i @class([
'fa-solid fa-file fa-fw',
'fa-lg' =>
url()->current() ===
route('bo.static_pages.edit', $exitOutsideStaticPageModel),
])></i>
{{ str(__("Page Sortir à l'éxterieur"))->ucFirst() }}
</a>
</li>
@endif
{{-- OUI-C-BON --}}
@if (isset($ouiCestBonStaticPageModel))
<li>
<a href="{{ route('bo.static_pages.edit', $ouiCestBonStaticPageModel ) }}" @class([
'dropdown-item px-3 py-2',
'fw-bold text-bg-secondary' =>
url()->current() ===
route('bo.static_pages.edit', $ouiCestBonStaticPageModel),
])>
<i @class([
'fa-solid fa-file fa-fw fa-fw',
'fa-lg' =>
url()->current() ===
route('bo.static_pages.edit', $ouiCestBonStaticPageModel),
])></i>
{{ str(__("Page Oui c'est bon"))->ucFirst() }}
</a>
</li>
@endif
<hr>
{{-- crud sections & categories --}}
<li>
<a href="{{ route('bo.sections.approaches.index') }}" @class([
'dropdown-item px-3 py-2',
'fw-bold text-bg-secondary' => request()->routeIs('bo.sections.approaches.*'),
])>
<i @class([
'fa-solid fa-tags fa-fw',
'fa-lg' => request()->routeIs('bo.sections.approaches.*'),
])></i>
{{ str(__('sections'))->ucFirst() }}
</a>
</li>
<li>
<a href="{{ route('bo.categories.approaches.index') }}" @class([
'dropdown-item px-3 py-2',
'fw-bold text-bg-secondary' => request()->routeIs('bo.categories.approaches.*'),
])>
<i @class([
'fa-solid fa-tags fa-fw',
'fa-lg' => request()->routeIs('bo.categories.approaches.*'),
])></i>
{{ str(__('catégories'))->ucFirst() }}
</a>
</li>
</ul>
</div>
<hr>
{{-- CONTACT --}}
@if(isset($contactStaticPageModel))

View file

@ -0,0 +1,45 @@
@extends('bo.layout', ['brParam' => $categoryApproachesModel])
@php
$inter = __('validation.custom.of_the.female');
$model = __(trans_choice('models.classes.categoryApproach', 1));
@endphp
@section('title', __('Création :inter :model', ['model' => trans_choice('models.classes.categoryApproach', 1), 'inter' => $inter]))
@section('description', __('Création d\'un tout nouveau :model', ['model' => trans_choice('models.classes.categoryApproach', 1)]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-3">
<div class="d-flex align-items-start flex-row">
<a class="btn btn-primary text-decoration-none m-0 me-2" data-bs-toggle="tooltip"
href="{{ route('bo.categories.approaches.index') }}"
title="{{ __('crud.helpers.list_all', ['model' => str(trans_choice('models.classes.categoryApproach', 2))->plural()]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@include('modules.breadcrumbs.breadcrumb-body', [
'brParam' => $categoryApproachesModel])
</div>
<div class="mb-md-0 mb-2">
<button class="btn btn-primary" id="formSubmitClone" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => __(trans_choice('models.classes.categoryApproach', 1))]) }}">
{{ str(__('crud.actions.save'))->ucfirst() }}
</button>
</div>
</div>
<form
action="{{ route('bo.categories.approaches.store') }}"
method="POST" enctype="multipart/form-data">
@csrf
@include('bo.pages.categories_approaches.form')
@include('bo.partials.script-button-clone')
<div class="row mt-3">
<div class="col text-center">
<button class="btn btn-primary" id="formSubmit" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => trans_choice('models.classes.categoryApproach', 1)]) }}">
{{ str(__('crud.actions.save'))->ucfirst() }}
</button>
</div>
</div>
</form>
@endsection

View file

@ -0,0 +1,45 @@
@extends('bo.layout', ['brParam' => $categoryApproachesModel])
@php
$inter = __('validation.custom.of_the.female');
$model = __(trans_choice('models.classes.categoryApproach', 1));
@endphp
@section('title', __('Édition :inter :model', ['model' => trans_choice('models.classes.categoryApproach', 1), 'inter' => $inter]))
@section('description', __('Création d\'un tout nouveau :model', ['model' => trans_choice('models.classes.categoryApproach', 1)]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-3">
<div class="d-flex align-items-start flex-row">
<a class="btn btn-primary text-decoration-none m-0 me-2" data-bs-toggle="tooltip"
href="{{ route('bo.categories.approaches.index') }}"
title="{{ __('crud.helpers.list_all', ['model' => str(trans_choice('models.classes.categoryApproach', 2))]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@include('modules.breadcrumbs.breadcrumb-body', [
'brParam' => $categoryApproachesModel])
</div>
<div class="mb-md-0 mb-2">
<button class="btn btn-primary" id="formSubmitClone" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => __(trans_choice('models.classes.categoryApproach', 1))]) }}">
{{ str(__('crud.actions.save'))->ucfirst() }}
</button>
</div>
</div>
<form
action="{{ route('bo.categories.approaches.update', $categoryApproachesModel) }}"
method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
@include('bo.pages.categories_approaches.form')
@include('bo.partials.script-button-clone')
<div class="row mt-3">
<div class="col text-center">
<button class="btn btn-primary" id="formSubmit" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => trans_choice('models.classes.categoryApproach', 1)]) }}">
{{ str(__('crud.actions.save'))->ucfirst() }}
</button>
</div>
</div>
</form>
@endsection

View file

@ -0,0 +1,34 @@
<div class="row">
<div class="col-12 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ str(__('validation.attributes.name'))->ucfirst() }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.varchar', [
'id' => 'approachName',
'label' => str(
__(':field :inter:model', [
'field' => __('validation.attributes.name'),
'inter' => $inter,
'model' => trans_choice('models.classes.categoryApproach', 1),
]))->ucfirst(),
'hasWordCounter' => true,
'name' => 'name',
'required' => true,
'model' => $categoryApproachesModel ?? null,
'helper' => __('validation.between.string', [
'attribute' => __(':field :inter:model', [
'field' => __('validation.attributes.name'),
'inter' => $inter,
'model' => $model,
]),
'min' => 3,
'max' => 255,
]),
'placeholder' => __('Un beau titre'),
])
</div>
</div>
</fieldset>
</div>
</div>

View file

@ -0,0 +1,69 @@
<table class="table-hover table-fix-action m-0 table">
@if (count($categories) > 0)
<thead>
@include('bo.modules.table-col-sorter', [
'cols' => [
'name' => str(__('validation.attributes.name'))->ucfirst(),
'updated_at' => str(__('validation.attributes.updated_at'))->ucfirst(),
],
])
</thead>
<tbody>
@foreach ($categories as $category)
<tr class="border-bottom">
<td class="text-center align-middle">{{ $category->name }}</td>
<td class="text-center align-middle">
<span class="badge text-bg-secondary">{{ $category->updated_at->isoFormat('LLLL') }}</span>
</td>
<td class="text-end align-middle">
@canAny(['view', 'duplicate', 'update', 'delete'], $category)
<form class="btn-group confirmDeleteTS"
action="{{ route('bo.categories.approaches.destroy', $category) }}" method="POST"
novalidate>
@can('view', $category)
<a class="btn btn-sm btn-warning" data-bs-toggle="tooltip"
href="{{ route('bo.categories.approaches.show', $category) }}"
title="{{ str(__('crud.actions.update'))->ucfirst() }}">
<i class="fa-solid fa-eye"></i>
</a>
@endcan
@can('update', $category)
<a class="btn btn-sm btn-primary" data-bs-toggle="tooltip"
href="{{ route('bo.categories.approaches.edit', $category) }}"
title="{{ str(__('crud.actions.update'))->ucfirst() }}">
<i class="fa-solid fa-pencil"></i>
</a>
@endcan
@can('duplicate', $category)
<a class="btn btn-sm btn-secondary" data-bs-toggle="tooltip"
href="{{ route('bo.categories.approaches.duplicate', $category) }}"
title="{{ str(__('crud.actions.duplicate'))->ucfirst() }}">
<i class="fa-solid fa-copy"></i>
</a>
@endcan
@can('delete', $category)
@csrf
@method('DELETE')
<button class="btn btn-sm btn-danger" data-bs-toggle="tooltip" type="submit"
title="{{ str(__('crud.actions.delete'))->ucfirst() }}">
<i class="fa-solid fa-trash"></i>
</button>
@endcan
</form>
@else
<span class="text-danger" data-bs-toggle="tooltip" title="{{ __('list.right') }}">
<i class="fa-solid fa-ban"></i>
</span>
@endcan
</td>
</tr>
@endforeach
</tbody>
@else
<tr>
<td class="border-0">
{{ trans_choice('crud.other.no_model_found', 2, ['model' => trans_choice('models.classes.categoryApproach', 2)]) }}
</td>
</tr>
@endif
</table>

View file

@ -0,0 +1,21 @@
@extends('bo.layout')
@section('title', __(' :model', ['model' => trans_choice('models.classes.categoryApproach', 2)]))
@section('description', __('Liste de tous les :model déjà existant', ['model' => trans_choice('models.classes.categoryApproach', 1)]))
@section('breadcrumb')
@section('content')
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap">
@include('modules.breadcrumbs.breadcrumb-body')
@can('create', \App\Models\CategoryApproach::class)
<a class="btn btn-primary float-right" data-bs-toggle="tooltip"
href="{{ route('bo.categories.approaches.create') }}"
title="{{ __('crud.helpers.create_model', ['model' => trans_choice('models.classes.categoryApproach', 1)]) }}">
<i class="fa-solid fa-plus"></i>
</a>
@endcan
</div>
@include('bo.modules.search-bar')
<div class="table-responsive mb-3">
@include('bo.pages.categories_approaches.index-list')
</div>
@endsection

View file

@ -0,0 +1,74 @@
@extends('bo.layout', ['brParam' => $categoryApproachesModel])
@section('title', __('Aperçu d\'un :model', ['model' => trans_choice('models.classes.categoryApproach', 1)]))
@section('description', __('Détail d\'un :model déjà existant', ['model' => trans_choice('models.classes.categoryApproach', 1)]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-3">
<div class="d-flex align-items-start flex-row">
<a class="btn btn-primary text-decoration-none m-0 me-2" data-bs-toggle="tooltip"
href="{{ route('bo.categories.approaches.index', $categoryApproachesModel) }}"
title="{{ __('crud.helpers.list_all', ['model' => trans_choice('models.classes.categoryApproach', \INF)]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $categoryApproachesModel])
</div>
<div class="btn-group mb-md-0 mb-2">
@canAny(['view', 'duplicate', 'update', 'delete'], $categoryApproachesModel)
<form class="btn-group confirmDeleteTS" action="{{ route('bo.categories.approaches.destroy', $categoryApproachesModel) }}" method="POST" novalidate>
@can('duplicate', $categoryApproachesModel)
<a class="btn btn-secondary" data-bs-toggle="tooltip"
href="{{ route('bo.categories.approaches.duplicate', ['categories_approach' => $categoryApproachesModel]) }}"
title="{{ str(__('crud.actions.duplicate'))->ucfirst() }}">
<i class="fa-solid fa-copy"></i>
</a>
@endcan
@can('update', $categoryApproachesModel)
<a class="btn btn-primary" data-bs-toggle="tooltip" href="{{ route('bo.categories.approaches.edit', ['categories_approach' => $categoryApproachesModel]) }}"
title="{{ str(__('crud.actions.update'))->ucfirst() }}">
<i class="fa-solid fa-pencil"></i>
</a>
@endcan
@can('delete', $categoryApproachesModel)
@csrf
@method('DELETE')
<button class="btn btn-danger" data-bs-toggle="tooltip" type="submit" title="{{ str(__('crud.actions.delete'))->ucfirst() }}">
<i class="fa-solid fa-trash"></i>
</button>
@endcan
</form>
@endcan
</div>
</div>
<div class="row">
<div class="col-12">
<div class="table-responsive mb-3">
<table class="table-hover m-0 table">
<tbody>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.attributes.name'))->ucfirst() }}
</td>
<td class="w-50 text-center align-middle">
{{ $categoryApproachesModel->name }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">{{ str(__('validation.attributes.created_at'))->ucfirst() }}</td>
<td class="w-50 text-center align-middle">
{{ str($categoryApproachesModel->created_at->isoFormat('LLLL'))->ucFirst() }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">{{ str(__('validation.attributes.updated_at'))->ucfirst() }}</td>
<td class="w-50 text-center align-middle">
{{ str($categoryApproachesModel->updated_at->isoFormat('LLLL'))->ucFirst() }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,43 @@
@extends('bo.layout', ['brParam' => $sectionApproachModel])
<?php $condition = trans_choice('models.classes.sectionApproach', 1); ?>
@section('title', __('Création d\'une :model', ['model' => $condition]))
@section('description', __('Création d\'un tout nouveau :model', ['model' => trans_choice('models.classes.sectionApproach', 1)]))
@section('breadcrumb')
@section('content')
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-3">
<div class="d-flex align-items-start flex-row">
<a class="btn btn-primary text-decoration-none m-0 me-2" data-bs-toggle="tooltip"
href="{{ route('bo.sections.approaches.index') }}"
title="{{ __('crud.helpers.list_all', ['model' => trans_choice('models.classes.sectionApproach', 2)]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@include('modules.breadcrumbs.breadcrumb-body', [
'brParam' => $sectionApproachModel
])
</div>
<div class="mb-md-0 mb-2">
<button class="btn btn-primary" id="formSubmitClone" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => trans_choice('models.classes.sectionApproach', 1)]) }}">
{{ str(__('crud.actions.save'))->ucfirst() }}
</button>
</div>
</div>
<form
action="{{ route('bo.sections.approaches.store') }}"
method="POST" enctype="multipart/form-data">
@csrf
@include('bo.pages.sections_approaches.form')
@include('bo.partials.script-button-clone')
<div class="row mt-3">
<div class="col text-center">
<button class="btn btn-primary" id="formSubmit" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => trans_choice('models.classes.sectionApproach', 1) ]) }}">
{{ str(__('crud.actions.save'))->ucfirst() }}
</button>
</div>
</div>
</form>
@endsection

View file

@ -0,0 +1,45 @@
@extends('bo.layout', ['brParam' => $sectionApproachModel])
<?php $condition = trans_choice('models.classes.sectionApproach', 1); ?>
@section('title', __('Édition :inter :model', ['model' => $condition]))
@section('description', __('Édition :inter :model',
['model' => $condition, 'inter' => __('validation.custom.female') ]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-3">
<div class="d-flex align-items-start flex-row">
<a class="btn btn-primary text-decoration-none m-0 me-2" data-bs-toggle="tooltip"
href="{{ route('bo.sections.approaches.index') }}"
title="{{ __('crud.helpers.list_all', ['model' => str($condition)->plural()]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@include('modules.breadcrumbs.breadcrumb-body', [
'brParam' => $sectionApproachModel
])
</div>
<div class="mb-md-0 mb-2">
<button class="btn btn-primary" id="formSubmitClone" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => $condition]) }}">
{{ str(__('crud.actions.save'))->ucfirst() }}
</button>
</div>
</div>
<form
action="{{ route('bo.sections.approaches.update', $sectionApproachModel) }}"
method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
@include('bo.pages.sections_approaches.form')
@include('bo.partials.script-button-clone')
<div class="row mt-3">
<div class="col text-center">
<button class="btn btn-primary" id="formSubmit" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => $condition ]) }}">
{{ str(__('crud.actions.save'))->ucfirst() }}
</button>
</div>
</div>
</form>
@endsection

View file

@ -0,0 +1,200 @@
@php
$inter = $condition ? __('validation.custom.of_the.female') : __('validation.custom.of_the.male');
$model = __('models.classes.sectionApproach');
@endphp
<div class="row">
{{-- Page --}}
<div class="col-6 col-md-3 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ str(trans_choice('models.classes.static_page', 1))->ucFirst() }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.search-belongs-to-dropdown', [
'id' => 'pageId',
'label' => __(':model associé', [
'model' => str(trans_choice('models.classes.static_page', 1))->ucFirst(),
]),
'name' => 'page_id',
'relation' => 'page',
'model' => $sectionApproachModel,
'targetClass' => 'App\Models\StaticPage',
'modelListPaginate' => $staticPageModel ?? [],
'routeName' => 'bo.sections.approaches.json-paginate',
'fieldName' => 'title',
'multiple' => false,
'required' => true,
'disabled' => false,
'placeholder' => __('Rechercher un :model', [
'model' => trans_choice('models.classes.static_page', 1),
]),
'helper' => __(
'Sélectionner une :model qui sera associé parmis la liste (une seul et unique :model doit être choisi).',
['model' => trans_choice('models.classes.static_page', 1)]),
])
</div>
</div>
</fieldset>
</div>
{{-- Category --}}
<div class="col-6 col-md-3 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ str(trans_choice('models.classes.static_page', 1))->ucFirst() }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.search-belongs-to-dropdown', [
'id' => 'categoryId',
'label' => __(':model associé', [
'model' => str(trans_choice('models.classes.categoryApproach', 1))->ucFirst(),
]),
'name' => 'category_id',
'relation' => 'category',
'model' => $sectionApproachModel,
'targetClass' => 'App\Models\CategoryApproach',
'modelListPaginate' => $categoryApproachModel ?? [],
'routeName' => 'bo.categories.approaches.json-paginate',
'fieldName' => 'name',
'multiple' => false,
'required' => true,
'disabled' => false,
'placeholder' => __('Rechercher un :model', [
'model' => trans_choice('models.classes.categoryApproach', 1),
]),
'helper' => __(
'Sélectionner une :model qui sera associé parmis la liste (une seul et unique :model doit être choisi).',
['model' => trans_choice('models.classes.categoryApproach', 1)]),
])
</div>
</div>
</fieldset>
</div>
{{-- TITLE --}}
<div class="col-12 col-lg-6 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ str(__('validation.attributes.name'))->ucfirst() }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.varchar', [
'id' => 'approachName',
'label' => str(
__(':field :inter:model', [
'field' => __('validation.attributes.title'),
'inter' => $inter,
'model' => __('section'),
]))->ucfirst(),
'hasWordCounter' => true,
'name' => 'title',
'required' => true,
'model' => $sectionApproachModel ?? null,
'helper' => __('validation.between.string', [
'attribute' => __(':field :inter:model', [
'field' => __('validation.attributes.title'),
'inter' => $inter,
'model' => $model,
]),
'min' => 3,
'max' => 255,
]),
'placeholder' => __('Un beau titre'),
])
</div>
</div>
</fieldset>
</div>
{{-- CONTENT --}}
<div class="col-12 mb-3">
<fieldset class="border p-3">
<legend>{{ str(__('validation.attributes.content'))->ucfirst() }}</legend>
<div class="row mb-3">
<div class="col-12">
@include('bo.partials.inputs.text-html', [
'id' => 'sectionContent',
'label' => __(':field :inter:model', [
'field' => str(__('validation.attributes.content'))->ucfirst(),
'inter' => __('validation.custom.of_the.female'),
'model' => trans_choice('section', 1),
]),
'name' => 'content',
'editorConfig' => \App\Enums\CkEditorConfigName::basic->label(),
'model' => $sectionApproachModel ?? null,
'required' => true,
'helper' => __('crud.minicrud.text_warning'),
])
</div>
</div>
</fieldset>
</div>
{{-- PICTURE --}}
<div class="col-12 pb-3">
<fieldset class="h-100 border p-3">
<legend>{{ str(__('validation.attributes.image'))->ucfirst() }}</legend>
<div class="row mb-3">
{{-- PICTURE_ALT --}}
<div class="col-12 col-lg-6 m-lg-0 mb-3">
@include('bo.partials.inputs.varchar', [
'id' => 'approachPictureAlt',
'label' => __(':field1 :inter1:field2 :inter2:model', [
'field1' => str(__('validation.custom.attribute_alt'))->ucfirst(),
'inter1' => __('validation.custom.of_the.vowel'),
'field2' => __('validation.attributes.image'),
'inter2' => $inter,
'model' => $model,
]),
'hasWordCounter' => true,
'name' => 'picture_alt',
'required' => true,
'model' => $sectionApproachModel ?? null,
'helper' => __('crud.minicrud.picture_alt_helper'),
'placeholder' => __(':field1 :inter1:field2 :inter2:model', [
'field1' => str(__('validation.custom.attribute_alt'))->ucfirst(),
'inter1' => __('validation.custom.of_the.vowel'),
'field2' => __('validation.attributes.image'),
'inter2' => $inter,
'model' => $model,
]),
])
</div>
{{-- PICTURE_TITLE --}}
<div class="col-12 col-lg-6 mb-3">
@include('bo.partials.inputs.varchar', [
'id' => 'approachPictureTitle',
'label' => __(':field1 :inter1:field2 :inter2:model', [
'field1' => str(__('validation.custom.attribute_title'))->ucfirst(),
'inter1' => __('validation.custom.of_the.vowel'),
'field2' => __('validation.attributes.image'),
'inter2' => $inter,
'model' => $model,
]),
'hasWordCounter' => true,
'name' => 'picture_title',
'required' => true,
'model' => $sectionApproachModel ?? null,
'helper' => __('crud.minicrud.picture_title_helper'),
'placeholder' => __(':field1 :inter1:field2 :inter2:model', [
'field1' => str(__('validation.custom.attribute_title'))->ucfirst(),
'inter1' => __('validation.custom.of_the.vowel'),
'field2' => __('validation.attributes.image'),
'inter2' => $inter,
'model' => $model,
]),
])
</div>
{{-- PICTURE --}}
<div class="col-12">
@include('bo.partials.inputs.varchar-picture', [
'id' => 'sectionApproachPicture',
'name' => 'picture',
'width' => 80,
'height' => 75,
'model' => $sectionApproachModel ?? null,
'required' => true,
'preview' => true,
])
</div>
</div>
</fieldset>
</div>
<div class="col-12 text-center">
<p><b>{{ __('* Champs obligatoires') }}</b></p>
</div>
</div>

View file

@ -0,0 +1,86 @@
<table class="table-hover table-fix-action m-0 table">
@if (count($sections) > 0)
<thead>
@include('bo.modules.table-col-sorter', [
'cols' => [
'name' => str(__('section'))->ucfirst(),
'category_id' => str(__('validation.custom.category'))->ucfirst(),
'page_id' => str(__('page'))->ucfirst(),
'created_at' => str(__('validation.attributes.updated_at'))->ucfirst(),
],
])
</thead>
<tbody>
@foreach ($sections as $section)
<tr class="border-bottom">
<td class="text-center align-middle">{{ $section->title }}</td>
<td class="text-center align-middle">
@if ($section->category)
<a href="{{ route('bo.categories.approaches.show', $section->category) }}"
class="badge text-bg-secondary text-decoration-none">
{{ $section->category->name }}
</a>
@endif
</td>
<td class="text-center align-middle">
@if ($section->page)
<a href="{{ route('bo.static_pages.edit', $section->page) }}"
class="badge text-bg-secondary text-decoration-none">
{{ $section->page->title }}
</a>
@endif
</td>
<td class="text-center align-middle">
<span class="badge text-bg-secondary">{{ $section->updated_at->isoFormat('LLLL') }}</span>
</td>
<td class="text-end align-middle">
@canAny(['view', 'duplicate', 'update', 'delete'], $section)
<form class="btn-group confirmDeleteTS"
action="{{ route('bo.sections.approaches.destroy', $section) }}" method="POST" novalidate>
@can('view', $section)
<a class="btn btn-sm btn-warning" data-bs-toggle="tooltip"
href="{{ route('bo.sections.approaches.show', $section) }}"
title="{{ str(__('crud.actions.show'))->ucfirst() }}">
<i class="fa-solid fa-eye"></i>
</a>
@endcan
@can('duplicate', $section)
<a class="btn btn-sm btn-secondary" data-bs-toggle="tooltip"
href="{{ route('bo.sections.approaches.duplicate', $section) }}"
title="{{ str(__('crud.actions.duplicate'))->ucfirst() }}">
<i class="fa-solid fa-copy"></i>
</a>
@endcan
@can('update', $section)
<a class="btn btn-sm btn-primary" data-bs-toggle="tooltip"
href="{{ route('bo.sections.approaches.edit', $section) }}"
title="{{ str(__('crud.actions.update'))->ucfirst() }}">
<i class="fa-solid fa-pencil"></i>
</a>
@endcan
@can('delete', $section)
@csrf
@method('DELETE')
<button class="btn btn-sm btn-danger" data-bs-toggle="tooltip" type="submit"
title="{{ str(__('crud.actions.delete'))->ucfirst() }}">
<i class="fa-solid fa-trash"></i>
</button>
@endcan
</form>
@else
<span class="text-danger" data-bs-toggle="tooltip" title="{{ __('list.right') }}">
<i class="fa-solid fa-ban"></i>
</span>
@endcan
</td>
</tr>
@endforeach
</tbody>
@else
<tr>
<td class="border-0">
{{ trans_choice('crud.other.no_model_found', 2, ['model' => trans_choice('models.classes.sectionApproach', 1)]) }}
</td>
</tr>
@endif
</table>

View file

@ -0,0 +1,27 @@
@extends('bo.layout', ['brParam' => $sections])
@section('title',str(trans_choice('models.classes.sectionApproach', 1))->plural()->ucfirst())
@section('description', __('Liste de tous les :model', ['model' => trans_choice('models.classes.sectionApproach', 2)]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap">
@include('modules.breadcrumbs.breadcrumb-body', [
'brParam' => $sections,
])
@can('create', \App\Models\SectionApproach::class)
<a class="btn btn-primary float-right" data-bs-toggle="tooltip"
href="{{ route('bo.sections.approaches.create') }}"
title="{{ __('crud.helpers.create_model', ['model' => trans_choice('models.classes.sectionApproach', 1)]) }}">
<i class="fa-solid fa-plus"></i>
</a>
@endcan
</div>
@include('bo.modules.search-bar')
<div class="table-responsive mb-3">
@include('bo.pages.sections_approaches.index-list')
</div>
{{ $sections->links() }}
@endsection

View file

@ -0,0 +1,107 @@
@extends('bo.layout', ['brParam' => $sectionApproachModel])
@section('title', __('Aperçu d\'un :model', ['model' => trans_choice('models.classes.sectionApproach', 1)]))
@section('description', __('Édition d\'un :model déjà existant', ['model' => trans_choice('models.classes.sectionApproach', 1)]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-3">
<div class="d-flex align-items-start flex-row">
<a class="btn btn-primary text-decoration-none m-0 me-2" data-bs-toggle="tooltip"
href="{{ route('bo.sections.approaches.index') }}"
title="{{ __('crud.helpers.list_all', ['model' => trans_choice('models.classes.sectionApproach',2)]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $sectionApproachModel])
</div>
<div class="btn-group mb-md-0 mb-2">
@canAny(['view', 'duplicate', 'update', 'delete'], $sectionApproachModel)
<form class="btn-group confirmDeleteTS" action="{{ route('bo.approaches.destroy', $sectionApproachModel) }}" method="POST" novalidate>
@can('duplicate', $sectionApproachModel)
<a class="btn btn-secondary" data-bs-toggle="tooltip"
href="{{ route('bo.sections.approaches.duplicate', ['section_approach' => $sectionApproachModel]) }}"
title="{{ str(__('crud.actions.duplicate'))->ucfirst() }}">
<i class="fa-solid fa-copy"></i>
</a>
@endcan
@can('update', $sectionApproachModel)
<a class="btn btn-primary" data-bs-toggle="tooltip" href="{{ route('bo.sections.approaches.edit', ['section_approach' => $sectionApproachModel]) }}"
title="{{ str(__('crud.actions.update'))->ucfirst() }}">
<i class="fa-solid fa-pencil"></i>
</a>
@endcan
@can('delete', $sectionApproachModel)
@csrf
@method('DELETE')
<button class="btn btn-danger" data-bs-toggle="tooltip" type="submit" title="{{ str(__('crud.actions.delete'))->ucfirst() }}">
<i class="fa-solid fa-trash"></i>
</button>
@endcan
</form>
@endcan
</div>
</div>
<div class="row">
<div class="col-12">
<div class="table-responsive mb-3">
<table class="table-hover m-0 table">
<tbody>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.attributes.title'))->ucfirst() }}
</td>
<td class="w-50 text-center align-middle">
{{ $sectionApproachModel->title }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.attributes.content'))->ucfirst() }}
</td>
<td class="w-50 text-center align-middle">
{{ $sectionApproachModel->content }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.attributes.image'))->ucfirst() }}
</td>
<td class="w-50 text-center align-middle">
<img src="{{ $sectionApproachModel->picture }}" title="{{ $sectionApproachModel->picture_title }}"
alt="{{ $sectionApproachModel->picture_alt }}">
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.custom.attribute_alt'))->ucfirst() }}
</td>
<td class="w-50 text-center align-middle">
{{ $sectionApproachModel->picture_alt }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">
{{ str(__('validation.custom.attribute_title'))->ucfirst() }}
</td>
<td class="w-50 text-center align-middle">
{{ $sectionApproachModel->picture_title }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">{{ str(__('validation.attributes.created_at'))->ucfirst() }}</td>
<td class="w-50 text-center align-middle">
{{ str($sectionApproachModel->created_at->isoFormat('LLLL'))->ucFirst() }}
</td>
</tr>
<tr class="border-bottom">
<td class="w-50 fw-bold text-center align-middle">{{ str(__('validation.attributes.updated_at'))->ucfirst() }}</td>
<td class="w-50 text-center align-middle">
{{ str($sectionApproachModel->updated_at->isoFormat('LLLL'))->ucFirst() }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
@endsection

View file

@ -118,7 +118,9 @@
!$staticPageModel->type->equals(StaticPageType::legals) &&
!$staticPageModel->type->equals(StaticPageType::confidentiality) &&
!$staticPageModel->type->equals(StaticPageType::accessibility) &&
!$staticPageModel->type->equals(StaticPageType::selling))
!$staticPageModel->type->equals(StaticPageType::selling) &&
!$staticPageModel->type->equals(StaticPageType::exitOutside) &&
!$staticPageModel->type->equals(StaticPageType::ouiCBon))
{{-- SUBTITLE --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="h-100 border p-3">
@ -161,7 +163,9 @@
!$staticPageModel->type->equals(StaticPageType::recipe) &&
!$staticPageModel->type->equals(StaticPageType::product) &&
!$staticPageModel->type->equals(StaticPageType::home) &&
!$staticPageModel->type->equals(StaticPageType::search))
!$staticPageModel->type->equals(StaticPageType::search) &&
!$staticPageModel->type->equals(StaticPageType::exitOutside) &&
!$staticPageModel->type->equals(StaticPageType::ouiCBon))
{{-- CONTENT --}}
<div class="col-12 mb-3">
<fieldset class="border p-3">

View file

@ -30,7 +30,7 @@
@foreach ($associatedModels as $associatedModel)
<option
value="{{ $associatedModel->value}}"
@selected(old($name, $model->{$name}->value ?? $value ?? 1) == $associatedModel->value)>
@selected(old($name, $model->{$name} ?? $value ?? 1) == $associatedModel->value)>
{{ $associatedModel->label }}
</option>
@endforeach

View file

@ -1,6 +1,6 @@
<div @class(['mt-md-0 mt-5', $class ?? ''])>
<div class="light-card">
<img class="img-fluid light-card-icon" src="{{ Vite::asset($icon['path']) }}" title="{{ $icon['description'] }}"
<img class="img-fluid light-card-icon" src="{{ asset($icon['path']) }}" title="{{ $icon['description'] }}"
alt="{{ $icon['alt'] }}">
<span class="text-uppercase bg-secondary text-light fw-semibold right-rotate-sm">{{ $category }}</span>

View file

@ -1,4 +1,4 @@
@props(['title' => '', 'subtitle' => ''])
@props(['title' => '', 'subtitle' => '', 'image' => ''])
@php
$translatedTitle = __($title);
@ -30,15 +30,10 @@
</div>
<div class="d-none d-xl-block col-6 col-xxl-4">
<div class="header-image-wrapper breeding-header-translate-right">
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/breeding/haystack.jpg') }}"
title="{{ __('titre de l\'image') }}" alt="{{ __('alt de l\'image') }}">
</div>
</div>
<div class="col-12 col-md-8 position-relative z-1 mx-auto mt-4">
<div class="d-xl-none header-image-wrapper">
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/breeding/haystack.jpg') }}"
<img class="img-fluid" src="{{ asset($image) }}"
title="{{ __('titre de l\'image') }}" alt="{{ __('alt de l\'image') }}">
</div>
</div>
</div>
</section>

View file

@ -1,124 +1,7 @@
@php
$items = [
'chicken' => [
[
'category' => 'Accès à l\'extérieur',
'icon' => [
'path' => 'resources/assets/images/breeding/sortant-a-l-exterieur/chicken-outside-sun.svg',
'alt' => "Pictogramme d'un accès à l'eau et l'alimenttaion en continu",
'description' => "Pictogramme d'une goutte d'eau et d'un épi de blé entouré de d'un clycle de deux flèches",
],
'content' => [
[
'title' => 'Des volailles qui sortent à l’Extérieur',
'points' => [
nl2br(__('Nos poulets ont accès quotidiennement à un :strongStartparcours extérieur pendant au minimum 8 heures*:strongEnd', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
nl2br(__('Les trappes sont ouvertes toute la journée : les volailles peuvent donc :strongStartsortir dans les prés:strongEnd ou :strongStartrester dans le poulailler:strongEnd comme elles le souhaitent ! Cela permet de leur assurer des conditions d’élevage les plus proches possible de leur milieu naturel, à la lumière naturelle le jour, et à l’obscurité toute la nuit.', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
],
'asterisc' => '*Durant au moins la moitié de leur élevage',
],
],
'class' => 'card-template-sm',
],
[
'category' => 'Amélioration du bien-être animal',
'icon' => [
'path' => 'resources/assets/images/breeding/sortant-a-l-exterieur/chicken-heart.svg',
'alt' => "Pictogramme d'une volaille en bonne santé",
'description' => "Pictogramme d'une volaille avec un coeur pour valoriser le bien-être animal dans les élevages",
],
'content' => [
[
'title' => 'Le bien-être animal au cœur des préoccupations',
'text' => 'Nos éleveurs partenaires ont des convictions fortes :',
],
[
'points' => [
nl2br(__('Elever des :strongStartvolailles 100% françaises:strongEnd', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
nl2br(__('Privilégiez des poulets à croissance lente élevées pendant :strongStart56 jours minimum.:strongEnd', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
nl2br(__('Utiliser une :strongStartmédecine douce:strongEnd pour soigner leurs volailles. L’utilisation d’antibiotiques n’est faite qu’en dernier recours et après autorisation d’un vétérinaire indépendant*.', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
],
'asterisc' => '*Conformément aux exigences réglementaires',
],
],
'class' => 'card-template-md',
],
[
'category' => 'Environnement',
'icon' => [
'path' => 'resources/assets/images/breeding/sortant-a-l-exterieur/chicken-arrow-left-right.svg',
'alt' => "Pictogramme de volailles avec un accès à l'extérieur",
'description' => 'Pictogramme de volailles dans un champs ensoleillé',
],
'content' => [
[
'title' => 'Plus d\'espace à l\'extérieur mais aussi à l\'intérieur !',
'points' => [
nl2br(__('En moyenne :strongStart30% d’espace supplémentaire dans le poulailler:strongEnd par rapport aux élevages conventionnels pour garantir plus d’espaces aux poulets pour se déplacer et exprimer leur comportement naturel.', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
nl2br(__('Les volailles ont à disposition des jouets comme des :strongStartballots de paille et des pierres à piquer.:strongEnd', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
],
],
],
'class' => 'card-template-md',
],
[
'category' => 'Alimentation',
'icon' => [
'path' => 'resources/assets/images/breeding/sortant-a-l-exterieur/chicken-outside-food.svg',
'alt' => "Pictogramme d'une volaille avec plus d'espace",
'description' => "Pictogramme d'une volaille avec deux flèches opposées pour indiquer plus d'espace à l'intérieur et à l'extérieur du poulailler",
],
'content' => [
[
'title' => 'Un accès constant à l\'eau et à la nourriture',
'points' => [
nl2br(__('Nos volailles sont nourries avec des :strongStartcéréales entières 100% françaises:strongEnd, sélectionnées par des nutritionnistes et :strongStartgaranties SANS OGM:strongEnd (<0,9%).', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
nl2br(__('Nos volailles ont :strongStartaccès toute la journée à des abreuvoirs d’eau et à leur nourriture dans les mangeoires.:strongEnd', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
],
],
],
'class' => 'card-template-sm',
],
],
];
$items = $sections;
@endphp
@props(['onglets' => [], 'sections' => [], 'staticPageModel'])
<section class="position-relative line-border-large z-1 overflow-hidden">
<div class="position-absolute h-100 w-100 top-0 z-0">
<div class="bg-green-wood h-100 w-100" id="demarche-bg"></div>
@ -128,33 +11,50 @@
<div class="col-12 mb-2">
<div class="d-md-flex justify-content-md-center gap-md-3">
<h2 class="h1 f-intro-head-h-base align-self-md-center text-center">
{{ __('Notre cahier des charges') }}
{{ __($staticPageModel->data['title']['value']) }}
</h2>
{{-- Oui c'est bon Logo --}}
<div class="d-flex justify-content-center mb-3">
<div>
<img class="img-fluid center-block"
src="{{ Vite::asset('resources/assets/images/breeding/sortant-a-l-exterieur/logo-sortant-a-l-exterieur.png') }}"
alt="{{ __('Logo de la démarche sortant à l\'extérieur') }}">
src="{{ asset($staticPageModel->data['picto']['value']) }}"
alt="{{ __($staticPageModel->data['picto']['alt']) }}">
</div>
</div>
</div>
<p class="w-lg-50 mx-auto text-center">
{{ __('Notre démarche inédite Le Gaulois ’’Sortant à l’extérieur’’ s’engage à ce que les volailles sortent à l’extérieur, tous les jours, dans les prés. Découvrez la démarche développée avec les éleveurs partenaires mobilisés chaque jour pour élever des volailles, nées et élevées en France.') }}
{{ __(strip_tags($staticPageModel->data['description']['value'])) }}
</p>
</div>
</div>
{{-- Buttons --}}
<div class="d-flex justify-content-center gap-md-4 gap-5">
<button class="btn btn-secondary fs-6 d-block" id="chicken-button-outside" type="button">
{{ __('Poulet') }}
</button>
@foreach ($items as $categoryKey => $categoryItems)
<button
class="btn btn-secondary fs-6 d-block category-btn"
type="button"
data-category="{{ $categoryKey }}"
>
{{ $categoryKey }}
</button>
@endforeach
</div>
{{-- Cards --}}
<div class="row mb-5 mt-5" id="cards-container-outside">
<div class="card-grid">
@foreach ($items['chicken'] as $item)
<x-fo.breeding.light-card :category="$item['category']" :icon="$item['icon']" :content="$item['content']" :class="$item['class'] . ' chicken-card'" />
@foreach ($items as $categoryKey => $categoryItems)
<div style="display: contents" data-category="{{ $categoryKey }}">
@foreach ($categoryItems as $item)
<x-fo.breeding.light-card
:category="$item['category']"
:icon="$item['icon']"
:content="$item['content']"
:class="$item['class']"
data-category="{{ $categoryKey }}"
/>
@endforeach
</div>
@endforeach
</div>
</div>

View file

@ -1,54 +1,4 @@
@php
$slides = [
[
'image' => [
'path' => Vite::asset(
'resources/assets/images/breeding/sortant-a-l-exterieur/Elevage-volailles-poulailler-Sortant-a-l-exterieur-Le-Gaulois.jpg',
),
'title' => "Elevage Le Gaulois respectant le cahier des charges ’’Sortant à l'extérieur’’",
'alt' => 'Volailles à l\'intérieur du poulailler avec des trappes pour accéder à l\'extérieur toute la journée',
],
],
[
'image' => [
'path' => Vite::asset(
'resources/assets/images/breeding/sortant-a-l-exterieur/Eleveur-alimentation-volailles-exterieur-champs-Le-Gaulois.jpg',
),
'title' => 'Éleveur marchant vers ses volailles pour les nourrir',
'alt' => 'Éleveur Le Gaulois marchant avec un sceau, nourrissant les volailles à l\'extérieur, dans son champ',
],
],
[
'image' => [
'path' => Vite::asset(
'resources/assets/images/breeding/sortant-a-l-exterieur/Eleveur-poulets-exterieur-champs-demarche-elevage-Le-Gaulois.jpg',
),
'title' => 'Éleveur reposant une volaille dans son champs',
'alt' =>
'Éleveur Le Gaulois souriant reposant une volaille sur l\'herbe de son exploitation et entouré de trois autres poulets',
],
],
[
'image' => [
'path' => Vite::asset(
'resources/assets/images/breeding/sortant-a-l-exterieur/Poulets-herbes-champs-exterieur-Le-Gaulois.jpg',
),
'title' => 'Volailles à l\'extérieur dans le pré',
'alt' => 'Poulets à l\'extérieur dans l\'herbe d\un champ',
],
],
[
'image' => [
'path' => Vite::asset(
'resources/assets/images/breeding/sortant-a-l-exterieur/Poulets-sortent-a-l-exterieur-poulailler-champs-Le-Gaulois.jpg',
),
'title' => 'Poulets sortant à l\'extérieur du poulailler',
'alt' => 'Poulets qui sortent tous les jours dans les prés grâce aux trappes du poulailler d\'élevage',
],
],
];
@endphp
@props(['sliderImages' => []])
<section class="position-relative negative-margin-sm overflow-hidden">
<div class="position-absolute h-100 w-100 top-0 z-0">
<div class="bg-white-wood-reverse h-100 w-100" id="slider-galery-outside-bg"></div>
@ -66,11 +16,11 @@
<section class="swiper container-fluid position-relative container" id="swiper-breeding-galery-slides">
<div class="swiper-wrapper w-fit">
@foreach ($slides as $slide)
@foreach ($sliderImages as $slide)
<div class="swiper-slide">
<div class="d-flex justify-content-center w-100 bg-light position-relative line-border-small p-2">
<div class="w-100 swiper-breeding-galery-img-container bg-light position-relative line-border-small">
<img class="w-100 h-100 block-center object-fit-cover" src="{{ $slide['image']['path'] }}"
<img class="w-100 h-100 block-center object-fit-cover" src="{{ asset($slide['image']['path']) }}"
title="{{ $slide['image']['title'] }}" alt="{{ $slide['image']['alt'] }}">
</div>
</div>

View file

@ -1,14 +1,13 @@
@extends('fo.layout')
@section('title', str('La marque 100% volaille française')->upper())
@section('description', str('La marque 100% volaille française')->ucFirst())
@section('bg-offset', true)
@section('content')
<x-fo.breeding.section.header-section title="{{ __('Démarche sortant à l’extérieur') }}" />
<x-fo.breeding.section.header-section title="{{ __($staticPageModel->title) }}" :image="$staticPageModel->picture"/>
<section id="sortant-a-l-exterieur">
<x-fo.breeding.section.sortant-a-l-exterieur.cahier-des-charges-section />
<x-fo.breeding.section.sortant-a-l-exterieur.galery-section />
<x-fo.breeding.section.sortant-a-l-exterieur.cahier-des-charges-section :staticPageModel="$staticPageModel" :onglets="$categoryApproachModels" :sections="$sectionApproachModels" />
<x-fo.breeding.section.sortant-a-l-exterieur.galery-section :sliderImages="$sliderImages" />
</section>
{{-- <section class="pb-3 pt-5" id="breeder-map-section">
<div class="pt-5">
@ -16,3 +15,4 @@
</div>
</section> --}}
@endsection

View file

@ -26,6 +26,9 @@
window.__SYSTEM._routes['fo.breeding.index'] = '{{ route('fo.breeding.index') }}';
window.__SYSTEM._routes['fo.recruitment.show'] = '{{ route('fo.recruitment.show') }}';
window.__SYSTEM._routes['fo.news.index'] = '{{ route('fo.news.index') }}';
window.__SYSTEM._routes['bo.sections.approaches.json-paginate'] = '{{ route('bo.sections.approaches.json-paginate') }}';
window.__SYSTEM._routes['bo.categories.approaches.json-paginate'] = '{{ route('bo.categories.approaches.json-paginate') }}';
// FO
window.__SYSTEM._routes['fo.home.products'] = '{{ route('fo.home.products', ['legend_slug' => 'LEGENDSLUG']) }}';
window.__SYSTEM._routes['fo.contact.show'] = '{{ route('fo.contact.show') }}';

View file

@ -9,6 +9,7 @@ use App\Models\DemarcheSlide;
use App\Models\KeyNumber;
use App\Models\Profession;
use App\Models\BrandAdvantage;
use App\Models\CategoryApproach;
use App\Models\Claim;
use App\Models\ConsumptionMoment;
use App\Models\Discount;
@ -26,6 +27,7 @@ use App\Models\Product;
use App\Models\ProductType;
use App\Models\Range;
use App\Models\Recipe;
use App\Models\SectionApproach;
use App\Models\SellItem;
use App\Models\StaticPage;
use App\Models\Submeat;
@ -37,6 +39,7 @@ use App\Models\Video;
use Diglactic\Breadcrumbs\Breadcrumbs;
use Diglactic\Breadcrumbs\Generator;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;
// * DASHBOARD
Breadcrumbs::for('bo.dashboard', function (Generator $trail) {
@ -433,6 +436,55 @@ Breadcrumbs::for('bo.approaches.duplicate', function (Generator $trail, int $app
$trail->push(Str::ucfirst(trans('crud.actions.duplicate')));
});
// * CATEGORIES APPROACHES
Breadcrumbs::for('bo.categories.approaches.index', function (Generator $trail) {
$trail->push(Str::ucfirst(trans('categories')), route('bo.categories.approaches.index'));
});
Breadcrumbs::for('bo.categories.approaches.show', function (Generator $trail, CategoryApproach $category) {
$trail->parent('bo.categories.approaches.index');
$trail->push(Str::ucfirst(trans('crud.actions.view')), route('bo.categories.approaches.show', $category));
});
Breadcrumbs::for('bo.categories.approaches.create', function (Generator $trail) {
$trail->parent('bo.categories.approaches.index');
$trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.categories.approaches.create'));
});
Breadcrumbs::for('bo.categories.approaches.edit', function (Generator $trail, CategoryApproach $category) {
$trail->parent('bo.categories.approaches.index');
$trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.categories.approaches.edit', $category));
});
// * SECTIONS APPROACHES
Breadcrumbs::for('bo.sections.approaches.index', function (Generator $trail) {
$trail->push(Str::ucfirst(trans('sections')), route('bo.sections.approaches.index'));
});
// Optionnel : index filtré par page (si tu veux un breadcrumb différent)
Breadcrumbs::for('bo.sections.approaches.indexByPage', function (Generator $trail, ?StaticPage $page) {
$trail->parent('bo.sections.approaches.index');
$trail->push($page->title, route('bo.sections.approaches.index'));
});
Breadcrumbs::for('bo.sections.approaches.show', function (Generator $trail, SectionApproach $section) {
$trail->parent('bo.sections.approaches.index');
$trail->push(Str::ucfirst(trans('crud.actions.view')), route('bo.sections.approaches.show', $section));
});
Breadcrumbs::for('bo.sections.approaches.create', function (Generator $trail, ?StaticPage $page = null) {
$trail->parent('bo.sections.approaches.index');
if ($page) {
$trail->push($page->title, route('bo.sections.approaches.index', ['page_id' => $page->id]));
}
$trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.sections.approaches.create'));
});
Breadcrumbs::for('bo.sections.approaches.edit', function (Generator $trail, SectionApproach $section) {
$trail->parent('bo.sections.approaches.index');
$trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.sections.approaches.edit', $section));
});
// * CONSUMPTION_MOMENTS
Breadcrumbs::for('bo.consumption_moments.index', function (Generator $trail) {
$trail->push(

View file

@ -4,6 +4,7 @@ use App\Http\Controllers\Bo\ActivityLogController;
use App\Http\Controllers\Bo\ApproachController;
use App\Http\Controllers\Bo\DemarcheSlideController;
use App\Http\Controllers\Bo\BrandAdvantageController;
use App\Http\Controllers\Bo\CategoryApproachesController;
use App\Http\Controllers\Bo\ClaimController;
use App\Http\Controllers\Bo\ConsumptionMomentController;
use App\Http\Controllers\Bo\DashboardController;
@ -35,6 +36,7 @@ use App\Http\Controllers\Bo\UploadController;
use App\Http\Controllers\Bo\UserController;
use App\Http\Controllers\Bo\VideoController;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Bo\SectionApproachController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
@ -323,6 +325,32 @@ Route::middleware(['throttle:web', 'admin-secure'])->prefix('bo')->name('bo.')->
Route::get('/approaches/json/paginate', [ApproachController::class, 'jsonPaginateDemarches'])
->name('approaches.json-paginate');
// * CATEGORY APPROACHES
Route::resource('categories-approaches', CategoryApproachesController::class)
->names('categories.approaches');
Route::get(
'/categories-approaches/{categories_approach}/duplicate',
[CategoryApproachesController::class, 'duplicate']
)->name('categories.approaches.duplicate');
// * SECTION APPROACHES
Route::resource('sections-approaches', SectionApproachController::class)
->names('sections.approaches')
->parameters(['sections-approaches' => 'section_approach'])
->withoutMiddleware('htmlsanitize')
->middleware('htmlsanitize:content');
Route::get(
'/sections-approaches/{section_approach}/duplicate',
[SectionApproachController::class, 'duplicate']
)->name('sections.approaches.duplicate');
Route::get('/sections-approaches/json/paginate', [SectionApproachController::class, 'jsonPaginatePage'])
->name('sections.approaches.json-paginate');
Route::get('/categories-approaches/json/paginate', [CategoryApproachesController::class, 'jsonPaginateCategory'])
->name('categories.approaches.json-paginate');
// * CONSUMPTION MOMENT.
Route::resource('consumption_moments', ConsumptionMomentController::class)
->withoutMiddleware('htmlsanitize')

View file

@ -12,7 +12,6 @@ use App\Http\Controllers\Fo\RatingController;
use App\Http\Controllers\Fo\RecipeController;
use App\Http\Controllers\Fo\SearchController;
use App\Http\Controllers\Fo\StaticPageController;
use App\Models\StaticPage;
use Illuminate\Support\Facades\Route;
Route::middleware(['throttle:web'])->name('fo.')->group(function () {
@ -138,7 +137,7 @@ Route::middleware(['throttle:web'])->name('fo.')->group(function () {
->group(function () {
Route::view('/', 'fo.pages.breeding.index')->name('index');
Route::get('/oui-c-est-bon', [BreedingController::class, 'ocbBreederPage'])->name('oui-c-est-bon');
Route::view('/sortant-a-l-exterieur', 'fo.pages.breeding.sortant-a-l-exterieur')
Route::get('/sortant-a-l-exterieur', [BreedingController::class, 'exitOutsidePage'])
->name('sortant-a-l-exterieur');
Route::get('/{breeding_static_page:slug}', [BreedingController::class, 'breedingPage'])
->name('show');