feat ✨ final commit for respect earth page
This commit is contained in:
parent
797067520e
commit
00d9db93aa
72 changed files with 4210 additions and 467 deletions
30
app/Enums/Block/BlockContentTypeEnum.php
Normal file
30
app/Enums/Block/BlockContentTypeEnum.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Block;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum BlockContentTypeEnum: string
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case Cta = 'cta';
|
||||
case Actu = 'actu';
|
||||
case Both = 'both';
|
||||
|
||||
private const LABELS = [
|
||||
'Cta' => 'Bouton',
|
||||
'Actu' => 'Encart actu',
|
||||
'Both' => 'Les deux',
|
||||
];
|
||||
|
||||
public static function options(): array
|
||||
{
|
||||
return self::all();
|
||||
}
|
||||
|
||||
public static function validationRule(): string
|
||||
{
|
||||
return 'required|in:' . implode(',', array_column(self::cases(), 'value'));
|
||||
}
|
||||
}
|
||||
39
app/Enums/Block/BlockPositionEnum.php
Normal file
39
app/Enums/Block/BlockPositionEnum.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Block;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum BlockPositionEnum: string
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case Gauche = 'gauche';
|
||||
case Centre = 'centre';
|
||||
case Droite = 'droite';
|
||||
|
||||
const LABELS = [
|
||||
'Gauche' => 'Gauche',
|
||||
'Centre' => 'Centre',
|
||||
'Droite' => 'Droite',
|
||||
];
|
||||
|
||||
public static function options(): array
|
||||
{
|
||||
return self::all();
|
||||
}
|
||||
|
||||
public static function validationRule(): string
|
||||
{
|
||||
return 'required|in:' . implode(',', array_column(self::cases(), 'value'));
|
||||
}
|
||||
|
||||
public function toBootstrap(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Gauche => 'start',
|
||||
self::Centre => 'center',
|
||||
self::Droite => 'end',
|
||||
};
|
||||
}
|
||||
}
|
||||
85
app/Enums/BlockTypeEnum.php
Normal file
85
app/Enums/BlockTypeEnum.php
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Enums\Block\BlockContentTypeEnum;
|
||||
use App\Enums\Block\BlockPositionEnum;
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum BlockTypeEnum: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case redirection = 1;
|
||||
case picto = 2;
|
||||
|
||||
|
||||
/**
|
||||
* Optionnal labels definition.
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::redirection->name => 'Redirection',
|
||||
self::picto->name => 'Pictogramme',
|
||||
];
|
||||
|
||||
public function defaultConfig(): array
|
||||
{
|
||||
return match ($this) {
|
||||
self::redirection => [
|
||||
'type' => self::redirection,
|
||||
'data' => [
|
||||
'title' => [
|
||||
'label' => 'Titre',
|
||||
'inputType' => InputType::string->value,
|
||||
'value' => 'Redirections actualités',
|
||||
'validate' => 'nullable|string|between:3,255',
|
||||
'required' => false,
|
||||
],
|
||||
'position' => [
|
||||
'label' => 'Position',
|
||||
'inputType' => InputType::selectEnum->value,
|
||||
'options' => BlockPositionEnum::options(),
|
||||
'value' => 'gauche',
|
||||
'validate' => BlockPositionEnum::validationRule(),
|
||||
'required' => true,
|
||||
],
|
||||
'block_type' => [
|
||||
'label' => 'Type de contenu',
|
||||
'inputType' => InputType::selectEnum->value,
|
||||
'options' => BlockContentTypeEnum::options(),
|
||||
'value' => 'cta',
|
||||
'validate' => BlockContentTypeEnum::validationRule(),
|
||||
'required' => true,
|
||||
],
|
||||
'cta_url' => [
|
||||
'label' => 'URL du bouton',
|
||||
'inputType' => InputType::url->value,
|
||||
'value' => null,
|
||||
'validate' => 'required|url',
|
||||
'required' => true,
|
||||
'showWhen' => ['block_type' => [BlockContentTypeEnum::Cta->value, BlockContentTypeEnum::Both->value]],
|
||||
],
|
||||
'cta_label' => [
|
||||
'label' => 'Nom du CTA',
|
||||
'inputType' => InputType::string->value,
|
||||
'value' => null,
|
||||
'validate' => 'required|string|between:3,100',
|
||||
'required' => true,
|
||||
'showWhen' => ['block_type' => [BlockContentTypeEnum::Cta->value, BlockContentTypeEnum::Both->value]],
|
||||
],
|
||||
'article_id' => [
|
||||
'label' => 'Article associé',
|
||||
'inputType' => InputType::searchBelongsTo->value,
|
||||
'source' => \App\Models\News::class,
|
||||
'required' => true,
|
||||
'value' => null,
|
||||
'validate' => ['integer', 'exists:news,id'],
|
||||
'showWhen' => ['block_type' => [BlockContentTypeEnum::Actu->value, BlockContentTypeEnum::Both->value]],
|
||||
],
|
||||
],
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -26,4 +26,5 @@ enum InputType: int
|
|||
case video = 16;
|
||||
case slider = 17;
|
||||
case selectImageVideo = 18;
|
||||
case searchBelongsTo = 19;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ enum StaticPageType: int
|
|||
case ouiCBon = 24;
|
||||
case ourFarms = 25;
|
||||
case tdf = 26;
|
||||
case respectTheEarth = 27;
|
||||
|
||||
/**
|
||||
* Optionnal labels definition.
|
||||
|
|
@ -66,7 +67,8 @@ enum StaticPageType: int
|
|||
self::exitOutside->name => 'Sortir à l\'extérieur',
|
||||
self::ouiCBon->name => 'Oui c\'est bon',
|
||||
self::ourFarms->name => 'Nos élevages',
|
||||
self::tdf->name => 'Tour de France'
|
||||
self::tdf->name => 'Tour de France',
|
||||
self::respectTheEarth->name => 'Respecter la terre',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -96,4 +98,17 @@ enum StaticPageType: int
|
|||
->map(fn($page) => $page->value)
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all commitment pages type values.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function getCommitmentsPages(): array
|
||||
{
|
||||
return [
|
||||
self::respectTheEarth,
|
||||
// self::forLivingTogether
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
31
app/Http/Controllers/Bo/BlockController.php
Normal file
31
app/Http/Controllers/Bo/BlockController.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\BlockPage\UpdateBlockRequest;
|
||||
use App\Models\Block;
|
||||
use App\Services\StaticPage\BlockService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BlockController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BlockService $service
|
||||
) {}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(UpdateBlockRequest $request, Block $block): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
$this->service->update($block, $request->validated());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => __('Bloc mis à jour avec succès.'),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
199
app/Http/Controllers/Bo/PictoController.php
Normal file
199
app/Http/Controllers/Bo/PictoController.php
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Pictos\StorePictoRequest;
|
||||
use App\Http\Requests\Bo\UpdateChangeOrderRequest;
|
||||
use App\Models\Picto;
|
||||
use App\Services\Picto\PictoService;
|
||||
use App\Traits\Controllers\ChangesModelOrder;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Laravel\Octane\Exceptions\DdException;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
|
||||
class PictoController extends Controller
|
||||
{
|
||||
use ChangesModelOrder;
|
||||
|
||||
public function __construct(
|
||||
private readonly PictoService $pictoService
|
||||
) {
|
||||
$this->authorizeResource(Picto::class);
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
if ($request->ajax() && $request->has('section_id')) {
|
||||
$allowedSorts = ['title', 'description', 'updated_at'];
|
||||
|
||||
$query = Picto::where('section_id', $request->section_id);
|
||||
|
||||
$sortCol = in_array($request->sort_col, $allowedSorts) ? $request->sort_col : 'order';
|
||||
$sortWay = in_array($request->sort_way, ['asc', 'desc']) ? $request->sort_way : 'asc';
|
||||
|
||||
$query->orderBy($sortCol, $sortWay);
|
||||
|
||||
$pictos = $query->get();
|
||||
return view('bo.pages.pictos.index-list', compact('pictos'));
|
||||
}
|
||||
abort(404);
|
||||
}
|
||||
|
||||
|
||||
public function create(Picto $picto)
|
||||
{
|
||||
return view('bo.pages.pictos.create', [
|
||||
'pictoModel' => $picto,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store Picto.
|
||||
*
|
||||
* @param StorePictoRequest $request Request
|
||||
* @param PictoService $pictoService Service for Picto.
|
||||
* @return RedirectResponse
|
||||
* @throws DdException
|
||||
*/
|
||||
public function store(StorePictoRequest $request, PictoService $pictoService)
|
||||
{
|
||||
$result = $pictoService->store($request->validated());
|
||||
if (is_string($result)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => $result,
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => __(
|
||||
'crud.messages.has_been_updated',
|
||||
['model' => Str::ucfirst(trans_choice('models.classes.picto', 1))]
|
||||
),
|
||||
'data' => $result,
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified picto (AJAX returns a partial view).
|
||||
*
|
||||
* @param \App\Models\Picto $picto
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function edit(Picto $picto, Request $request)
|
||||
{
|
||||
if ($request->ajax()) {
|
||||
return view('bo.pages.pictos.edit', [
|
||||
'pictoModel' => $picto,
|
||||
'section' => $picto->section,
|
||||
]);
|
||||
}
|
||||
return view('bo.pages.pictos.edit', ['pictoModel' => $picto, 'section' => $picto->section]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified picto in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Pictos\StorePictoRequest $request
|
||||
* @param \App\Models\Picto $picto
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function update(StorePictoRequest $request, Picto $picto)
|
||||
{
|
||||
$result = $this->pictoService->update($picto, $request->validated());
|
||||
|
||||
if (is_string($result)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => $result,
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => __(
|
||||
'crud.messages.has_been_updated',
|
||||
['model' => Str::ucfirst(trans_choice('models.classes.picto', 1))]
|
||||
),
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save the picture width and height for a picto.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function savePictureSize(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'picture_width' => 'required|integer|min:1',
|
||||
'picture_height' => 'required|integer|min:1',
|
||||
]);
|
||||
|
||||
if ($request->has('picto_id') && $request->picto_id) {
|
||||
$picto = Picto::findOrFail($request->picto_id);
|
||||
$picto->update([
|
||||
'picture_width' => $request->picture_width,
|
||||
'picture_height' => $request->picture_height,
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the specified picto and return the refreshed picto list.
|
||||
*
|
||||
* @param \App\Models\Picto $picto
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function destroy(Picto $picto)
|
||||
{
|
||||
$picto->delete();
|
||||
|
||||
$pictos = Picto::with(['section.page'])->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => __('crud.messages.has_been_deleted', ['model' => Str::ucfirst(trans_choice('models.classes.picto', 1))]),
|
||||
'pictos' => $pictos,
|
||||
]);
|
||||
}
|
||||
|
||||
public function changeOrder(UpdateChangeOrderRequest $request, Picto $picto): \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$tmp = Picto::query()
|
||||
->where('section_id', $picto->section_id)
|
||||
->where('order', $request->direction ? '<' : '>', $picto->order)
|
||||
->orderBy('order', $request->direction ? 'DESC' : 'ASC')
|
||||
->first();
|
||||
|
||||
if (!$tmp) {
|
||||
return $request->ajax()
|
||||
? response()->json(['message' => trans('crud.messages.order_not_changed')], 422)
|
||||
: back()->with('error', trans('crud.messages.order_not_changed'));
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($picto, $tmp) {
|
||||
$newOrder = $tmp->order;
|
||||
$tmp->update(['order' => $picto->order]);
|
||||
$picto->update(['order' => $newOrder]);
|
||||
});
|
||||
|
||||
$msg = trans('crud.messages.order_changed');
|
||||
return $request->ajax()
|
||||
? response()->json(['message' => $msg])
|
||||
: back()->with('success', $msg);
|
||||
}
|
||||
}
|
||||
79
app/Http/Controllers/Bo/SectionPageController.php
Normal file
79
app/Http/Controllers/Bo/SectionPageController.php
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\SectionPages\UpdateSectionPageRequest;
|
||||
use App\Models\SectionPage;
|
||||
use App\Models\StaticPage;
|
||||
use App\Services\StaticPage\SectionPageService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class SectionPageController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SectionPageService $service
|
||||
) {}
|
||||
|
||||
public function index(StaticPage $page): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'data' => $this->service->getByPage($page),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(UpdateSectionPageRequest $request, StaticPage $page): JsonResponse
|
||||
{
|
||||
$section = $this->service->create($page, $request->validated());
|
||||
|
||||
return response()->json($section);
|
||||
}
|
||||
|
||||
public function show(SectionPage $section): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'data' => $section,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateSectionPageRequest $request, SectionPage $sectionPage): JsonResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
// Merge files in data
|
||||
if ($request->hasFile('data')) {
|
||||
$validated['data'] = array_replace_recursive(
|
||||
$validated['data'] ?? [],
|
||||
$request->file('data')
|
||||
);
|
||||
}
|
||||
|
||||
$this->service->update($sectionPage, $validated);
|
||||
|
||||
session()->flash('success', 'Section mise à jour avec succès.');
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Section mise à jour',
|
||||
'data' => $sectionPage,
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(SectionPage $section): JsonResponse
|
||||
{
|
||||
$this->service->delete($section);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Section supprimée',
|
||||
'data' => $section,
|
||||
]);
|
||||
}
|
||||
|
||||
public function toggleVisibility(SectionPage $section): JsonResponse
|
||||
{
|
||||
$section = $this->service->toggleVisibility($section);
|
||||
|
||||
return response()->json([
|
||||
'data' => $section,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -30,9 +30,22 @@ class StaticPageController extends Controller
|
|||
*/
|
||||
public function edit(StaticPage $static_page): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return view('bo.pages.static_pages.edit', ['staticPageModel' => $static_page]);
|
||||
$isCommitmentPage = in_array(
|
||||
$static_page->type,
|
||||
StaticPageType::getCommitmentsPages()
|
||||
);
|
||||
|
||||
if ($isCommitmentPage) {
|
||||
$static_page->load(['sections.blocks', 'sections.pictos']);
|
||||
}
|
||||
|
||||
return view('bo.pages.static_pages.edit', [
|
||||
'staticPageModel' => $static_page,
|
||||
'isCommitmentPage' => $isCommitmentPage
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
|
|
@ -71,6 +84,7 @@ class StaticPageController extends Controller
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the common section edit form for all breeding static pages.
|
||||
*
|
||||
|
|
@ -92,7 +106,7 @@ class StaticPageController extends Controller
|
|||
/**
|
||||
* Update the common section for all breeding static pages.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\StaticPage\UpdateCommonSectionRequest $request
|
||||
* @param \App\Http\Requests\Bo\StaticPages\UpdateCommonSectionRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function updateCommonSection(UpdateCommonSectionRequest $request): RedirectResponse
|
||||
|
|
@ -124,6 +138,59 @@ class StaticPageController extends Controller
|
|||
return redirect()
|
||||
->route('bo.static_page.sections.edit')
|
||||
->with('success', __('crud.messages.has_been_updated', [
|
||||
'model' => Str::ucfirst(__('validation.custom.common_section_part'))]));
|
||||
'model' => Str::ucfirst(__('validation.custom.common_section_part'))
|
||||
]));
|
||||
}
|
||||
|
||||
public function updateCommitmentsCommonSection(UpdateCommonSectionRequest $request): RedirectResponse
|
||||
{
|
||||
$pages = StaticPage::whereIn('type', StaticPageType::getCommitmentsPages())->get();
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($pages, $request) {
|
||||
foreach ($pages as $page) {
|
||||
$data = $page->data ?? [];
|
||||
|
||||
$data['common_section'] = [
|
||||
'title' => $request->validated('title'),
|
||||
'content' => $request->validated('content'),
|
||||
];
|
||||
|
||||
$page->data = $data;
|
||||
$page->saveOrFail();
|
||||
}
|
||||
});
|
||||
} catch (Throwable $e) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('error', __('crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(__('validation.custom.common_section_part'))
|
||||
]));
|
||||
} //end try
|
||||
|
||||
return redirect()
|
||||
->route('bo.static_page.commitmments.sections.edit')
|
||||
->with('success', __('crud.messages.has_been_updated', [
|
||||
'model' => Str::ucfirst(__('validation.custom.common_section_part'))
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the common section edit form for all breeding static pages.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function getCommitmentsCommonSection(): \Illuminate\Contracts\View\View
|
||||
{
|
||||
$commonSection = StaticPage::whereIn('type', StaticPageType::getCommitmentsPages())
|
||||
->get()
|
||||
->map(fn($page) => $page->data['common_section'] ?? null)
|
||||
->filter()
|
||||
->first();
|
||||
|
||||
return view('bo.pages.static_pages.common_section.commitments.edit', [
|
||||
'commonSection' => $commonSection,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
app/Http/Controllers/Fo/CommitmentController.php
Normal file
22
app/Http/Controllers/Fo/CommitmentController.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Fo;
|
||||
|
||||
use App\Enums\StaticPageType;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\StaticPage;
|
||||
|
||||
class CommitmentController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Display the given breeding page.
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function getRestpectEarthPage(): \Illuminate\Contracts\View\View
|
||||
{
|
||||
$staticPageModel = StaticPage::getCachedStaticPage(StaticPageType::respectTheEarth);
|
||||
$staticPageModel->load(['sections.blocks', 'sections.pictos']);
|
||||
return view('fo.pages.commitments.respect', ['staticPageModel' => $staticPageModel]);
|
||||
}
|
||||
}
|
||||
191
app/Http/Requests/Bo/BlockPage/UpdateBlockRequest.php
Normal file
191
app/Http/Requests/Bo/BlockPage/UpdateBlockRequest.php
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\BlockPage;
|
||||
|
||||
use App\Enums\Block\BlockContentTypeEnum;
|
||||
use App\Enums\BlockTypeEnum;
|
||||
use App\Http\Requests\Bo\UpdateMiniCrudRequest;
|
||||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateBlockRequest extends UpdateMiniCrudRequest
|
||||
{
|
||||
use HasPicture;
|
||||
|
||||
protected $modelName = 'block';
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::check('update', $this->route($this->modelName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$data = $this->input('data', []);
|
||||
$block = $this->route('block');
|
||||
|
||||
if ($block->type === BlockTypeEnum::redirection) {
|
||||
$fields = json_decode(json_encode($block->data), true);
|
||||
$data = $this->processBlockData($data, $fields);
|
||||
}
|
||||
|
||||
$this->merge(['data' => $data]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$parentRules = parent::rules();
|
||||
$block = $this->route('block');
|
||||
|
||||
$rules = [
|
||||
'data' => 'present|array',
|
||||
'data.*' => 'required|array',
|
||||
'data.block_type.value' => ['required', Rule::in(BlockContentTypeEnum::toValues())],
|
||||
];
|
||||
|
||||
if ($block->type === BlockTypeEnum::redirection) {
|
||||
$rules = array_merge($rules, $this->resolveRedirectionRules());
|
||||
}
|
||||
|
||||
return array_merge($parentRules, $rules);
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'data.cta_url.value' => 'redirection',
|
||||
'data.cta_label.value' => 'du bouton',
|
||||
'data.block_type.value' => 'type de bloc',
|
||||
'data.article_id.value' => 'article lié',
|
||||
'data.title.value' => 'titre',
|
||||
'data.position.value' => 'position',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected function failedValidation(Validator $validator)
|
||||
{
|
||||
$errors = collect($validator->errors()->all())->join("\n");
|
||||
|
||||
session()->flash('error', 'Liste des erreurs :' . "\n" . $errors);
|
||||
|
||||
throw new HttpResponseException(
|
||||
response()->json([
|
||||
'message' => 'Erreur de validation.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize input data and apply showWhen visibility logic for redirection blocks.
|
||||
*
|
||||
* @param array $data The raw input data from the request.
|
||||
* @param array $fields The block field configuration (including showWhen rules).
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
private function processBlockData(array $data, array $fields): array
|
||||
{
|
||||
foreach ($data as $key => $item) {
|
||||
if (!isset($item['value'])) {
|
||||
$data[$key] = ['value' => $item];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($fields as $fieldName => $fieldConfig) {
|
||||
if (!isset($fieldConfig['showWhen'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$shouldShow = true;
|
||||
foreach ($fieldConfig['showWhen'] as $dependentField => $allowedValues) {
|
||||
if (!in_array($data[$dependentField]['value'] ?? null, (array) $allowedValues)) {
|
||||
$shouldShow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$shouldShow) {
|
||||
$data[$fieldName] = ['validate' => 'nullable', 'required' => false, 'value' => null];
|
||||
} else {
|
||||
$data[$fieldName]['required'] = true;
|
||||
unset($data[$fieldName]['validate']);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve validation rules for redirection block fields dynamically.
|
||||
*
|
||||
* builds a rules array based on:
|
||||
* - The block's field config (validate key) as fallback.
|
||||
* - The field's required flag set during prepareForValidation to either
|
||||
* enforce "required" or replace it with "nullable".
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
private function resolveRedirectionRules(): array
|
||||
{
|
||||
$data = $this->input('data', []);
|
||||
$parentRules = parent::rules();
|
||||
$block = $this->route('block');
|
||||
$config = json_decode(json_encode($block->data), true);
|
||||
|
||||
$rules = [];
|
||||
|
||||
foreach ($data as $fieldName => $item) {
|
||||
if ($fieldName === 'block_type' || $fieldName === 'position') {
|
||||
continue;
|
||||
}
|
||||
$isRequired = $item['required'] ?? true;
|
||||
|
||||
$baseRules = $parentRules["data.{$fieldName}.value"]
|
||||
?? $config[$fieldName]['validate']
|
||||
?? [];
|
||||
|
||||
if (is_string($baseRules)) {
|
||||
$baseRules = explode('|', $baseRules);
|
||||
}
|
||||
|
||||
if (empty($baseRules)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$isRequired) {
|
||||
$baseRules = array_filter($baseRules, fn($r) => $r !== 'required');
|
||||
$baseRules[] = 'nullable';
|
||||
$baseRules = array_values($baseRules);
|
||||
} else {
|
||||
if (!in_array('required', $baseRules)) {
|
||||
array_unshift($baseRules, 'required');
|
||||
}
|
||||
}
|
||||
|
||||
$rules["data.{$fieldName}.value"] = $baseRules;
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
90
app/Http/Requests/Bo/Pictos/StorePictoRequest.php
Normal file
90
app/Http/Requests/Bo/Pictos/StorePictoRequest.php
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Pictos;
|
||||
|
||||
use App\Models\Picto;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use App\Rules\WysiwygLength;
|
||||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
|
||||
class StorePictoRequest extends FormRequest
|
||||
{
|
||||
use HasPicture;
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::check('create', Picto::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
|
||||
{
|
||||
$width = (int) ($this->input('picture_width') ?: Picto::MIN_WIDTH);
|
||||
$height = (int) ($this->input('picture_height') ?: Picto::MIN_HEIGHT);
|
||||
|
||||
|
||||
$rules = [
|
||||
'title' => 'required|string|between:3,255',
|
||||
'description' => ['required', 'string', new WysiwygLength(5)],
|
||||
'picture_alt' => 'required_with:picture|nullable|between:3,255',
|
||||
'picture_title' => 'required_with:picture|nullable|between:3,255',
|
||||
'section_id' => 'required|exists:section_pages,id',
|
||||
];
|
||||
|
||||
return array_merge(
|
||||
$rules,
|
||||
$this->pictureRules('picture', $width, $height, $width, $height)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attributes()
|
||||
{
|
||||
return [
|
||||
'title' => trans('validation.attributes.title'),
|
||||
'description' => trans('validation.attributes.description'),
|
||||
'picture' => trans('validation.attributes.image'),
|
||||
'picture_alt' => trans('validation.custom.attribute_alt'),
|
||||
'picture_title' => trans('validation.custom.attribute_title'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function failedValidation(Validator $validator)
|
||||
{
|
||||
$errors = collect($validator->errors()->all())->join("\n");
|
||||
|
||||
session()->flash('error', 'Liste des erreurs :' . "\n" . $errors);
|
||||
|
||||
throw new HttpResponseException(
|
||||
response()->json([
|
||||
'message' => 'Erreur de validation.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\SectionPages;
|
||||
|
||||
use App\Http\Requests\Bo\UpdateMiniCrudRequest;
|
||||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
|
||||
class UpdateSectionPageRequest extends UpdateMiniCrudRequest
|
||||
{
|
||||
use HasPicture;
|
||||
|
||||
protected $modelName = 'sectionPage';
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::check('update', $this->route($this->modelName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$data = $this->input('data', []);
|
||||
foreach ($data as $key => $item) {
|
||||
if (!isset($item['value'])) {
|
||||
$data[$key] = [
|
||||
'value' => $item
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->merge([
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(parent::rules(), [
|
||||
'visibility' => 'nullable|boolean',
|
||||
'data' => 'present|array',
|
||||
'data.*' => 'required|array',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function failedValidation(Validator $validator)
|
||||
{
|
||||
$errors = collect($validator->errors()->all())->join("\n");
|
||||
|
||||
session()->flash('error', 'Liste des erreurs :' . "\n" . $errors);
|
||||
|
||||
throw new HttpResponseException(
|
||||
response()->json([
|
||||
'message' => 'Erreur de validation.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -150,7 +150,8 @@ class UpdateStaticPageRequest extends UpdateMiniCrudRequest
|
|||
!$staticPageModel->type->equals(StaticPageType::accessibility) &&
|
||||
!$staticPageModel->type->equals(StaticPageType::ourFarms) &&
|
||||
!$staticPageModel->type->equals(StaticPageType::exitOutside) &&
|
||||
!$staticPageModel->type->equals(StaticPageType::ouiCBon)
|
||||
!$staticPageModel->type->equals(StaticPageType::ouiCBon) &&
|
||||
!$staticPageModel->type->equals(StaticPageType::respectTheEarth)
|
||||
) {
|
||||
$rules = \array_merge(
|
||||
$rules,
|
||||
|
|
@ -168,7 +169,8 @@ class UpdateStaticPageRequest extends UpdateMiniCrudRequest
|
|||
!$staticPageModel->type->equals(StaticPageType::contact) &&
|
||||
!$staticPageModel->type->equals(StaticPageType::search) &&
|
||||
!$staticPageModel->type->equals(StaticPageType::exitOutside) &&
|
||||
!$staticPageModel->type->equals(StaticPageType::ouiCBon)
|
||||
!$staticPageModel->type->equals(StaticPageType::ouiCBon) &&
|
||||
!$staticPageModel->type->equals(StaticPageType::respectTheEarth)
|
||||
) {
|
||||
$rules = \array_merge(
|
||||
$rules,
|
||||
|
|
|
|||
|
|
@ -144,7 +144,8 @@ abstract class UpdateMiniCrudRequest extends FormRequest
|
|||
];
|
||||
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => [
|
||||
"data.$iptName.value" => array_merge(
|
||||
[
|
||||
function (string $attribute, $value, callable $fail) use ($sizes, $input) {
|
||||
// Picture can be null.
|
||||
if ($input['required'] || $value !== null) {
|
||||
|
|
@ -167,6 +168,7 @@ abstract class UpdateMiniCrudRequest extends FormRequest
|
|||
]),
|
||||
$input
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
// Attributes are required if a picture is set.
|
||||
|
|
@ -179,18 +181,21 @@ abstract class UpdateMiniCrudRequest extends FormRequest
|
|||
break;
|
||||
case InputType::url->value:
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => [
|
||||
'required',
|
||||
'url',
|
||||
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
function ($attribute, $value, $fail) {
|
||||
"data.$iptName.value" => array_merge(
|
||||
$this->compileValidate(['required', 'url'], $input),
|
||||
[
|
||||
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
function ($attribute, $value, $fail) {
|
||||
if (empty($value)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ToolboxHelper::assertNoInternalBoUrls($attribute, $value);
|
||||
} catch (ValidationException $e) {
|
||||
$fail(collect($e->errors())->first()[0]);
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
)
|
||||
]);
|
||||
break;
|
||||
case InputType::boolean->value:
|
||||
|
|
@ -216,7 +221,10 @@ abstract class UpdateMiniCrudRequest extends FormRequest
|
|||
}
|
||||
break;
|
||||
case InputType::selectImageVideo->value:
|
||||
if (Arr::exists($this->input("data.$iptName.value"), 'picture')) {
|
||||
|
||||
$value = $this->input("data.$iptName.value", []);
|
||||
|
||||
if (Arr::exists($value, 'picture')) {
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value.picture" => "required|array",
|
||||
"data.$iptName.value.picture.path" => "required",
|
||||
|
|
@ -225,7 +233,7 @@ abstract class UpdateMiniCrudRequest extends FormRequest
|
|||
]);
|
||||
}
|
||||
|
||||
if (Arr::exists($this->input("data.$iptName.value"), 'video')) {
|
||||
if (Arr::exists($value, 'video')) {
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value.video" => "required|array",
|
||||
"data.$iptName.value.video.id" => "required|string",
|
||||
|
|
@ -259,6 +267,10 @@ abstract class UpdateMiniCrudRequest extends FormRequest
|
|||
if (!\in_array($validate, $inputTypeValidate)) {
|
||||
$inputTypeValidate[] = $validate;
|
||||
}
|
||||
// if required is hardcode
|
||||
if (\in_array('nullable', $inputTypeValidate)) {
|
||||
$inputTypeValidate = \array_filter($inputTypeValidate, fn($r) => $r !== 'required');
|
||||
}
|
||||
}
|
||||
return \collect($inputTypeValidate)->unique()->sort(function ($valueA, $valueB) {
|
||||
$valueA = $this->ruleHasToComeFirst($valueA) ? 0 : 2;
|
||||
|
|
|
|||
55
app/Models/Block.php
Normal file
55
app/Models/Block.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\BlockTypeEnum;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* Represents a content block belonging to a page section.
|
||||
*
|
||||
* @property int $id Primary key.
|
||||
* @property int $section_id Foreign key referencing the parent section.
|
||||
* @property BlockTypeEnum $type The type of the block.
|
||||
* @property object|null $data JSON data associated with the block.
|
||||
* @property int $order Display order within the section.
|
||||
* @property \Illuminate\Support\Carbon $created_at Timestamp when the block was created.
|
||||
* @property \Illuminate\Support\Carbon $updated_at Timestamp when the block was last updated.
|
||||
*
|
||||
* @property-read SectionPage $section The parent section this block belongs to.
|
||||
*/
|
||||
class Block extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'section_id',
|
||||
'type',
|
||||
'data',
|
||||
'order',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'type' => BlockTypeEnum::class,
|
||||
'data' => 'object',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the section that owns this block.
|
||||
*
|
||||
* @return BelongsTo<SectionPage, Block>
|
||||
*/
|
||||
public function section(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(SectionPage::class);
|
||||
}
|
||||
}
|
||||
62
app/Models/Picto.php
Normal file
62
app/Models/Picto.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Picto extends Model
|
||||
{
|
||||
const MIN_PICTOS = 4;
|
||||
const MAX_PICTOS = 6;
|
||||
const MIN_WIDTH = 60;
|
||||
const MIN_HEIGHT = 60;
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'description',
|
||||
'picture_alt',
|
||||
'picture_title',
|
||||
'picture',
|
||||
'picture_width',
|
||||
'picture_height',
|
||||
'section_id',
|
||||
'order'
|
||||
];
|
||||
|
||||
|
||||
public function section(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(SectionPage::class, 'section_id');
|
||||
}
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (self $picto) {
|
||||
$picto->picture = FileStorageHelper::storeFile($picto, $picto->picture, true);
|
||||
|
||||
$maxOrder = self::where('section_id', $picto->section_id)->max('order');
|
||||
$picto->order = $maxOrder !== null ? $maxOrder + 1 : 1;
|
||||
});
|
||||
|
||||
static::updating(function (self $picto) {
|
||||
$picto->picture = FileStorageHelper::storeFile($picto, $picto->picture, true);
|
||||
|
||||
if ($picto->isDirty('section_id')) {
|
||||
$maxOrder = self::where('section_id', $picto->section_id)->max('order');
|
||||
$picto->order = $maxOrder !== null ? $maxOrder + 1 : 1;
|
||||
}
|
||||
});
|
||||
|
||||
static::deleting(function (self $picto) {
|
||||
FileStorageHelper::storeFile($picto, $picto->picture, true);
|
||||
});
|
||||
|
||||
static::deleted(function (self $picto) {
|
||||
self::where('section_id', $picto->section_id)
|
||||
->where('order', '>', $picto->order)
|
||||
->decrement('order');
|
||||
});
|
||||
}
|
||||
}
|
||||
39
app/Models/SectionPage.php
Normal file
39
app/Models/SectionPage.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class SectionPage extends Model
|
||||
{
|
||||
protected $table = 'section_pages';
|
||||
|
||||
protected $fillable = [
|
||||
'page_id',
|
||||
'name',
|
||||
'visibility',
|
||||
'data',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'visibility' => 'boolean',
|
||||
'data' => 'object',
|
||||
];
|
||||
|
||||
public function page(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(StaticPage::class);
|
||||
}
|
||||
|
||||
public function blocks(): HasMany
|
||||
{
|
||||
return $this->hasMany(Block::class, 'section_id');
|
||||
}
|
||||
|
||||
public function pictos(): HasMany
|
||||
{
|
||||
return $this->hasMany(Picto::class, 'section_id')->orderBy('order');
|
||||
}
|
||||
}
|
||||
|
|
@ -301,4 +301,9 @@ class StaticPage extends Model implements SchemaOrgInterface, RedirectableInterf
|
|||
{
|
||||
return $this->hasMany(SectionApproach::class, 'page_id');
|
||||
}
|
||||
|
||||
public function sections(): HasMany
|
||||
{
|
||||
return $this->hasMany(SectionPage::class, 'page_id');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
70
app/Policies/BlockPolicy.php
Normal file
70
app/Policies/BlockPolicy.php
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\Block;
|
||||
use App\Models\Customer;
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserStaticRules;
|
||||
|
||||
class BlockPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
public function viewAny(User $authUser): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*/
|
||||
public function view(Customer $customer, Block $blockPage): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*/
|
||||
public function create(Customer $customer): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*/
|
||||
public function update(User $authUser, Block $blockPage): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*/
|
||||
public function delete(Customer $customer, Block $blockPage): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*/
|
||||
public function restore(Customer $customer, Block $blockPage): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*/
|
||||
public function forceDelete(Customer $customer, Block $blockPage): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
98
app/Policies/PictoPolicy.php
Normal file
98
app/Policies/PictoPolicy.php
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\Picto;
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserStaticRules;
|
||||
|
||||
class PictoPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
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): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete(User $authUser, Picto $picto): 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can publish/unpublish the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @return boolean
|
||||
*/
|
||||
public function changePublished(User $authUser): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can change the order of the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @return boolean
|
||||
*/
|
||||
public function changeOrder(User $authUser): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
}
|
||||
53
app/Policies/SectionPagePolicy.php
Normal file
53
app/Policies/SectionPagePolicy.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\Customer;
|
||||
use App\Models\SectionPage;
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserStaticRules;
|
||||
|
||||
class SectionPagePolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
public function viewAny(User $authUser): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*/
|
||||
public function update(User $authUser, SectionPage $sectionPage): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*/
|
||||
public function delete(Customer $customer, SectionPage $sectionPage): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*/
|
||||
public function restore(Customer $customer, SectionPage $sectionPage): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*/
|
||||
public function forceDelete(Customer $customer, SectionPage $sectionPage): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -137,6 +137,7 @@ class ViewServiceProvider extends ServiceProvider
|
|||
['name' => 'ouiCestBonStaticPageModel', 'value' => StaticPage::getCachedStaticPage(StaticPageType::ouiCBon)],
|
||||
['name' => 'ourFarmsStaticPageModel', 'value' => StaticPage::getCachedStaticPage(StaticPageType::ourFarms)],
|
||||
['name' => 'tdfStaticPageModel', 'value' => StaticPage::getCachedStaticPage(StaticPageType::tdf)],
|
||||
['name' => 'respecttheEarthStaticPageModel', 'value' => StaticPage::getCachedStaticPage(StaticPageType::respectTheEarth)],
|
||||
])->map(function ($array) {
|
||||
View::share($array['name'], (function () use ($array) {
|
||||
return $array['value'] ?: null;
|
||||
|
|
|
|||
49
app/Services/Picto/PictoService.php
Normal file
49
app/Services/Picto/PictoService.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Picto;
|
||||
|
||||
use App\Models\Picto;
|
||||
use App\Models\SectionPage;
|
||||
|
||||
class PictoService
|
||||
{
|
||||
/**
|
||||
* Create a new picto for the given section.
|
||||
*
|
||||
* Returns the created Picto on success, or an error message string
|
||||
* when the section has reached its maximum number of pictos.
|
||||
*
|
||||
* @param array $data
|
||||
* @return \App\Models\Picto|string
|
||||
*/
|
||||
public function store(array $data): Picto|string
|
||||
{
|
||||
$section = SectionPage::findOrFail($data['section_id']);
|
||||
|
||||
$sectionData = json_decode(json_encode($section->data), true) ?? [];
|
||||
$maxPictos = $sectionData['max_pictos'] ?? 5;
|
||||
|
||||
$count = Picto::where('section_id', $section->id)->count();
|
||||
|
||||
if ($count >= $maxPictos) {
|
||||
return "Le nombre maximum de pictos ($maxPictos) est atteint pour cette section.";
|
||||
}
|
||||
|
||||
return Picto::create($data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the given picto with the provided data.
|
||||
*
|
||||
* @param \App\Models\Picto $picto
|
||||
* @param array $data
|
||||
* @return \App\Models\Picto|string
|
||||
*/
|
||||
public function update(Picto $picto, array $data): Picto|string
|
||||
{
|
||||
$picto->update($data);
|
||||
|
||||
return $picto;
|
||||
}
|
||||
}
|
||||
95
app/Services/StaticPage/BlockService.php
Normal file
95
app/Services/StaticPage/BlockService.php
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\StaticPage;
|
||||
|
||||
use App\Enums\Block\BlockContentTypeEnum;
|
||||
use App\Enums\BlockTypeEnum;
|
||||
use App\Models\Block;
|
||||
|
||||
class BlockService
|
||||
{
|
||||
/**
|
||||
* Get the data object of a given block.
|
||||
*
|
||||
* @param Block $block The block to retrieve data from.
|
||||
* @return object
|
||||
*/
|
||||
public function getBlockData(Block $block): object
|
||||
{
|
||||
return $block->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and persist a new block with the given validated attributes.
|
||||
*
|
||||
* @param array $validated Validated input
|
||||
*/
|
||||
public function store(array $validated): Block
|
||||
{
|
||||
return Block::create([
|
||||
'section_id' => $validated['section_id'],
|
||||
'type' => $validated['type'],
|
||||
'data' => $validated['data'] ?? [],
|
||||
'order' => $validated['order'] ?? 0,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a block by merging existing data with new validated attributes.
|
||||
* Applies type-specific merge rules before persisting.
|
||||
*
|
||||
* @param Block $block The block to update.
|
||||
* @param array $validated Validated input containing type, data, order.
|
||||
* @return Block
|
||||
*/
|
||||
public function update(Block $block, array $validated): Block
|
||||
{
|
||||
$existingData = json_decode(json_encode($block->data), true) ?? [];
|
||||
$newData = $validated['data'] ?? [];
|
||||
|
||||
$mergedData = $this->mergeBlockDataByType($block, $existingData, $newData);
|
||||
|
||||
$block->update([
|
||||
'type' => $validated['type'] ?? $block->type,
|
||||
'data' => $mergedData,
|
||||
'order' => $validated['order'] ?? $block->order,
|
||||
]);
|
||||
|
||||
return $block->fresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge existing and new block data according to the block type rules.
|
||||
* For redirection blocks, mutually exclusive fields (CTA vs article) are cleared accordingly.
|
||||
*
|
||||
* @param Block $block The block being updated.
|
||||
* @param array $existingData Current block data.
|
||||
* @param array $newData Incoming data to merge.
|
||||
* @return array
|
||||
*/
|
||||
private function mergeBlockDataByType(Block $block, array $existingData, array $newData): array
|
||||
{
|
||||
$mergedData = array_replace_recursive($existingData, $newData);
|
||||
|
||||
if ($block->type === BlockTypeEnum::redirection) {
|
||||
$contentType = BlockContentTypeEnum::make($mergedData['block_type']['value']);
|
||||
|
||||
if (
|
||||
$contentType === BlockContentTypeEnum::Cta
|
||||
&& (!empty($mergedData['cta_url']['value']) || !empty($mergedData['cta_label']['value']))
|
||||
) {
|
||||
$mergedData['article_id']['value'] = null;
|
||||
}
|
||||
|
||||
if (
|
||||
$contentType === BlockContentTypeEnum::Actu
|
||||
&& !empty($mergedData['article_id']['value'])
|
||||
) {
|
||||
$mergedData['cta_url']['value'] = null;
|
||||
$mergedData['cta_label']['value'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $mergedData;
|
||||
}
|
||||
}
|
||||
144
app/Services/StaticPage/SectionPageService.php
Normal file
144
app/Services/StaticPage/SectionPageService.php
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\StaticPage;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\SectionPage;
|
||||
use App\Models\StaticPage;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class SectionPageService
|
||||
{
|
||||
public function getByPage(StaticPage $page, bool $onlyVisible = false): Collection
|
||||
{
|
||||
$query = $page->sections();
|
||||
|
||||
if ($onlyVisible) {
|
||||
$query->where('visibility', true);
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
public function create(StaticPage $page, array $data): SectionPage
|
||||
{
|
||||
return $page->sections()->create([
|
||||
'name' => $data['name'],
|
||||
'visibility' => $data['visibility'] ?? true,
|
||||
'data' => $data['data'] ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(SectionPage $section, array $data): SectionPage
|
||||
{
|
||||
if (isset($data['data'])) {
|
||||
$data['data'] = $this->processFiles($section->page, $data['data']);
|
||||
}
|
||||
|
||||
$section->update([
|
||||
'name' => $data['name'] ?? $section->name,
|
||||
'visibility' => $data['visibility'] ?? $section->visibility,
|
||||
'data' => $this->mergeData((array) json_decode(json_encode($section->data), true), $data['data'] ?? []),
|
||||
]);
|
||||
|
||||
return $section->fresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively traverses the $data array up to 4 levels deep
|
||||
* and replaces every UploadedFile instance with the stored file path.
|
||||
*
|
||||
* Supported structures :
|
||||
* - data[key][subKey] = UploadedFile (level 2)
|
||||
* - data[key][subKey][nestedKey][deepKey] = UploadedFile (level 4)
|
||||
*
|
||||
* Values that are not UploadedFile instances (strings, integers, null...)
|
||||
* are left untouched.
|
||||
*
|
||||
* @param StaticPage $page The page model used to determine the storage directory.
|
||||
* @param array $data The form data array (from $request->input('data')).
|
||||
*
|
||||
* @return array The $data array with UploadedFile instances replaced by their stored paths.
|
||||
*
|
||||
* @uses FileStorageHelper::storeFile()
|
||||
*/
|
||||
private function processFiles(StaticPage $page, array $data): array
|
||||
{
|
||||
foreach ($data as $key => &$field) {
|
||||
if (!is_array($field)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($field as $subKey => &$subValue) {
|
||||
if ($subValue instanceof \Illuminate\Http\UploadedFile) {
|
||||
$subValue = FileStorageHelper::storeFile($page, $subValue);
|
||||
} elseif (is_array($subValue)) {
|
||||
// case media.value.picture.path
|
||||
foreach ($subValue as $nestedKey => &$nestedValue) {
|
||||
if (is_array($nestedValue)) {
|
||||
foreach ($nestedValue as $deepKey => &$deepValue) {
|
||||
if ($deepValue instanceof \Illuminate\Http\UploadedFile) {
|
||||
$deepValue = FileStorageHelper::storeFile($page, $deepValue);
|
||||
}
|
||||
}
|
||||
unset($deepValue);
|
||||
}
|
||||
}
|
||||
unset($nestedValue);
|
||||
}
|
||||
}
|
||||
unset($subValue);
|
||||
}
|
||||
unset($field);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
private function mergeData(array $original, array $new): array
|
||||
{
|
||||
foreach ($new as $key => $field) {
|
||||
if (isset($original[$key]) && is_array($original[$key]) && is_array($field)) {
|
||||
$original[$key] = $this->deepMerge($original[$key], $field);
|
||||
} else {
|
||||
$original[$key] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
return $original;
|
||||
}
|
||||
|
||||
private function deepMerge(array $original, array $new): array
|
||||
{
|
||||
if (isset($new['video'])) {
|
||||
$original['picture'] = null;
|
||||
} elseif (isset($new['picture'])) {
|
||||
$original['video'] = null;
|
||||
}
|
||||
|
||||
foreach ($new as $key => $field) {
|
||||
if (isset($original[$key]) && is_array($original[$key]) && is_array($field)) {
|
||||
$original[$key] = $this->deepMerge($original[$key], $field);
|
||||
} else {
|
||||
$original[$key] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
return $original;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function delete(SectionPage $section): bool
|
||||
{
|
||||
return $section->delete();
|
||||
}
|
||||
|
||||
public function toggleVisibility(SectionPage $section): SectionPage
|
||||
{
|
||||
$section->update([
|
||||
'visibility' => ! $section->visibility,
|
||||
]);
|
||||
|
||||
return $section->fresh();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\InputType;
|
||||
use App\Enums\ReductionEmplacementType;
|
||||
use App\Enums\ReductionRedirectionType;
|
||||
use App\Enums\StaticPageType;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
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
|
||||
{
|
||||
$page = new StaticPage();
|
||||
$picture = FileStorageHelper::storeFile(
|
||||
$page,
|
||||
new \SplFileInfo(\resource_path('assets/images/commitments/commitment-5/banner.jpg'))
|
||||
);
|
||||
$picto = FileStorageHelper::storeFile(
|
||||
$page,
|
||||
new \SplFileInfo(\resource_path('assets/images/commitments/navigation/picto-life.svg'))
|
||||
);
|
||||
|
||||
$data = [
|
||||
'picto_banner' => [
|
||||
'label' => 'Banner : Picto standard',
|
||||
'inputType' => InputType::image->value,
|
||||
'dimensions' => [
|
||||
'width' => 42,
|
||||
'height' => 42
|
||||
],
|
||||
'alt' => 'Pictogramme d\'un serrage de main.',
|
||||
'title' => 'Pictogramme d\'un serrage de main.',
|
||||
'value' => $picto,
|
||||
'validate' => 'nullable',
|
||||
'required' => false
|
||||
],
|
||||
'common_section' => [
|
||||
'title' => 'découvrez plus d\'engagements le gaulois',
|
||||
'content' => '<p>Face aux enjeux de notre époque, Le Gaulois répond présent.<br>Chaque jour, nous avançons pas à pas avec <strong>les équipes, les partenaires et les consommateurs</strong> pour élaborer des produits gourmands, de qualité et ainsi créer de bons moments de convivialité à table et en cuisine. Découvrez nos 5 engagements et nos actions pour <strong>minimiser l’impact de notre activité sur l’environnement, veiller au bien-être animal et améliorer notre alimentation et notre qualité de vie</strong>.</p>'
|
||||
]
|
||||
];
|
||||
|
||||
$staticPage = StaticPage::where('type', StaticPageType::respectTheEarth)->first();
|
||||
if ($staticPage) {
|
||||
$staticPage->data = $data;
|
||||
$staticPage->saveQuietly();
|
||||
} else {
|
||||
StaticPage::query()->forceCreateQuietly(
|
||||
[
|
||||
'type' => StaticPageType::respectTheEarth->value(),
|
||||
'seo_title' => 'Respecter la terre',
|
||||
'seo_description' => 'Optimiser le transport, privilégier des fournisseurs locaux et réduire les emballages... quelques actions pour agir ensemble sur notre empreinte carbone !',
|
||||
'title' => 'Respecter la terre',
|
||||
'slug' => 'respecter-la-terre',
|
||||
'content' => '<p>Le Gaulois s\'engage à réduire son empreinte carbone : optimiser le transport, privilégier des fournisseurs locaux et réduire les emballages.</p>',
|
||||
'picture' => $picture,
|
||||
'picture_alt' => 'Forêt éclairé par la lumière du soleil.',
|
||||
'picture_title' => 'Bannière de la page engagement n°5',
|
||||
'data' => $data
|
||||
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('static_pages', function () {
|
||||
$staticPage = StaticPage::query()->where('type', StaticPageType::respectTheEarth)->first();
|
||||
$staticPage->data = [];
|
||||
$staticPage->saveQuietly();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('section_pages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('page_id')->constrained('static_pages')->cascadeOnDelete()->comment('Reference to the static page');
|
||||
$table->string('name')->comment('Section name');
|
||||
$table->boolean('visibility')->default(true)->comment('section visibility');
|
||||
$table->mediumText('data')->comment('content stored as JSON');
|
||||
$table->unique(['page_id', 'name']);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('section_pages');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?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('blocks', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedTinyInteger('type')->comment('Page type (BlockTypeEnum)');
|
||||
$table->foreignId('section_id')->constrained('section_pages')->cascadeOnDelete()->comment('section which belongs to');
|
||||
$table->mediumText('data')->comment('content stored as JSON');
|
||||
$table->unsignedInteger('order')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('blocks');
|
||||
}
|
||||
};
|
||||
290
database/migrations/2026_05_26_152751_add_data_for_blocks.php
Normal file
290
database/migrations/2026_05_26_152751_add_data_for_blocks.php
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\BlockTypeEnum;
|
||||
use App\Enums\InputType;
|
||||
use App\Enums\StaticPageType;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Block;
|
||||
use App\Models\StaticPage;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private function getSections(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => 'section 2',
|
||||
'data' => [
|
||||
'title' => [
|
||||
'label' => 'Section 2 : Titre',
|
||||
'inputType' => InputType::string->value,
|
||||
'value' => 'priorité à la réduction de notre empreinte carbone',
|
||||
'validate' => 'required|string|between:3,255',
|
||||
'required' => true
|
||||
],
|
||||
'description' => [
|
||||
'label' => 'Section 2 : Description',
|
||||
'inputType' => InputType::text->value,
|
||||
'value' => '<p>Chaque initiative joue un rôle crucial dans la <strong>lutte contre les gaz à effet de serre</strong>, contribuant ainsi à préserver l\'équilibre de la planète et la biodiversité. Ainsi, Le Gaulois met en en place des actions concrètes face au <strong>réchauffement climatique</strong>.</p>',
|
||||
'validate' => 'required|string|min:15',
|
||||
'required' => true
|
||||
],
|
||||
'media' => [
|
||||
'label' => 'Section 2 : Media',
|
||||
'inputType' => InputType::selectImageVideo->value,
|
||||
'dimensions' => [
|
||||
'width' => 479,
|
||||
'height' => 291
|
||||
],
|
||||
'required' => true,
|
||||
'validate' => 'required|array',
|
||||
'value' => [
|
||||
'picture' => [
|
||||
'path' => 'commitments/common/footprint.jpg',
|
||||
'alt' => 'Vue du dessus d\'une empreinte humaine de pied avec de l\'eau et entouré d\'une forêt verdoyante',
|
||||
'title' => 'Empreinte carbone de l\'activité humaine sur la Terre',
|
||||
]
|
||||
],
|
||||
],
|
||||
'media_picto' => [
|
||||
'label' => 'Section 2 : Picto du media',
|
||||
'inputType' => InputType::image->value,
|
||||
'dimensions' => [
|
||||
'width' => 74,
|
||||
'height' => 92
|
||||
],
|
||||
'alt' => 'Pictogramme d\'une empreinte digitale humaine, symbôle de l\'engagement de Le Gaulois à réduire son empreinte carbone sur la terre.',
|
||||
'title' => 'Pictogramme d\'une empreinte de doigt',
|
||||
'value' => 'commitments/pictograms/picto-human-footprint.svg',
|
||||
'validate' => 'required',
|
||||
'required' => true
|
||||
],
|
||||
|
||||
],
|
||||
'blocks' => [
|
||||
'reductionBlock' => true,
|
||||
'has_pictos' => true,
|
||||
'max_pictos' => 6,
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'section 3',
|
||||
'data' => [
|
||||
'title' => [
|
||||
'label' => 'Section 3 : Titre',
|
||||
'inputType' => InputType::string->value,
|
||||
'value' => 'stop à la déforestation, halte au soja non responsable',
|
||||
'validate' => 'required|string|between:3,255',
|
||||
'required' => true
|
||||
],
|
||||
'description' => [
|
||||
'label' => 'Section 3 : Description',
|
||||
'inputType' => InputType::text->value,
|
||||
'value' => '<p>Chez Le Gaulois, pour assurer <strong>l\'apport protéique</strong> et assurer un <strong>équilibre nutritionnel de nos volailles</strong>, nous les alimentons avec du <strong>soja</strong>. C’est la <strong>légumineuse la plus riche en protéines et en acides aminés essentiels !</strong> Conscient que la culture du Soja importée est assujettie à de nombreux questionnements, notamment son impact sur la <strong>déforestation.</strong></p>',
|
||||
'validate' => 'required|string|min:15',
|
||||
'required' => true
|
||||
],
|
||||
'media' => [
|
||||
'label' => 'Section 3 : Media',
|
||||
'inputType' => InputType::selectImageVideo->value,
|
||||
'dimensions' => [
|
||||
'width' => 479,
|
||||
'height' => 291
|
||||
],
|
||||
'required' => true,
|
||||
'validate' => 'required|array',
|
||||
'value' => [
|
||||
'picture' => [
|
||||
'path' => 'commitments/common/soja.jpg',
|
||||
'alt' => 'Graines de soja importées mais garanties zéro déforesation d\'ici 2025',
|
||||
'title' => 'Soja responsable et engagé contre la déforestation',
|
||||
]
|
||||
],
|
||||
],
|
||||
'media_picto' => [
|
||||
'label' => 'Section 3 : Picto du media',
|
||||
'inputType' => InputType::image->value,
|
||||
'dimensions' => [
|
||||
'width' => 116,
|
||||
'height' => 127
|
||||
],
|
||||
'alt' => 'Céréales cultivées en France pour nourrir les volailles Le Gaulois',
|
||||
'title' => 'Pictogramme de céréales pour l’alimentation des volailles Le Gaulois',
|
||||
'value' => 'commitments/pictograms/picto-cereals.svg',
|
||||
'validate' => 'required',
|
||||
'required' => true
|
||||
],
|
||||
'encare_beige_title' => [
|
||||
'label' => 'Section 3 : Titre Picto',
|
||||
'inputType' => InputType::string->value,
|
||||
'value' => '100% de soja',
|
||||
'validate' => 'required|string|between:3,255',
|
||||
'required' => true
|
||||
],
|
||||
'encare_beige_description' => [
|
||||
'label' => 'Section 3 : Description picto',
|
||||
'inputType' => InputType::text->value,
|
||||
'value' => ' <h6>garanti zéro déforestation</h6><p>conversion au plus tard d\'ici 2025.</p>',
|
||||
'validate' => 'required|string|min:15',
|
||||
'required' => true
|
||||
],
|
||||
],
|
||||
'blocks' => [
|
||||
'reductionBlock' => true,
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'section 4',
|
||||
'data' => [
|
||||
'title' => [
|
||||
'label' => 'Section 4 : Titre',
|
||||
'inputType' => InputType::string->value,
|
||||
'value' => 'réduire l\'impact de nos emballages sur l\'environnement',
|
||||
'validate' => 'required|string|between:3,255',
|
||||
'required' => true
|
||||
],
|
||||
'description' => [
|
||||
'label' => 'Section 4 : Description',
|
||||
'inputType' => InputType::text->value,
|
||||
'value' => '<p> Conscient de son rôle, Le Gaulois s ‘est engagé dans une démarche continue de réduction de ses emballages et d’amélioration de leur recyclabilité, tout en garantissant la sécurité alimentaire. <br> </p><ul><li> soit intégrant de la matière recyclée </li><li> soit en étant recyclables </li></ul><p> D’ici fin 2025, nous ambitionnons de passer à près de <strong>95% d’emballages responsables.</strong></p>',
|
||||
'validate' => 'required|string|min:15',
|
||||
'required' => true
|
||||
],
|
||||
'media' => [
|
||||
'label' => 'Section 4 : Media',
|
||||
'inputType' => InputType::selectImageVideo->value,
|
||||
'dimensions' => [
|
||||
'width' => 479,
|
||||
'height' => 291
|
||||
],
|
||||
'required' => true,
|
||||
'validate' => 'required|array',
|
||||
'value' => [
|
||||
'picture' => [
|
||||
'path' => 'commitments/common/box.jpg',
|
||||
'alt' => 'Colis en carton avec une estampille de recyclabilité',
|
||||
'title' => 'Emballages cartons issus de cartons recyclés ou certifiés FSC',
|
||||
]
|
||||
],
|
||||
],
|
||||
'media_picto' => [
|
||||
'label' => 'Section 4 : Picto du media',
|
||||
'inputType' => InputType::image->value,
|
||||
'dimensions' => [
|
||||
'width' => 89,
|
||||
'height' => 84
|
||||
],
|
||||
'alt' => 'Pictogramme de recyclabilité sur les emballages et la gestion des déchets',
|
||||
'title' => 'Pictogramme sur les emballages et la gestion des déchets',
|
||||
'value' => 'commitments/pictograms/picto-recycle.svg',
|
||||
'validate' => 'required',
|
||||
'required' => true
|
||||
],
|
||||
'encare_beige' => [
|
||||
'label' => 'Section 3 : Saviez-vous',
|
||||
'inputType' => InputType::text->value,
|
||||
'value' => '<hgroup><strong>Le saviez-vous ?</strong> 100% des emballages cartons Le Gaulois sont déjà issus de <strong>cartons recyclés et certifiés FSC</strong>, garantissant une gestion responsable des forêts, et <strong>plus de 60% des barquettes</strong> de volaille Le Gaulois sont recyclables ou intégrant <strong>au moins 50% de matière recyclée.</strong></hgroup>',
|
||||
'validate' => 'required|string|min:15',
|
||||
'required' => true
|
||||
],
|
||||
],
|
||||
'blocks' => [
|
||||
'reductionBlock' => true,
|
||||
'has_pictos' => true,
|
||||
'max_pictos' => 4,
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$pages = StaticPage::where('type', StaticPageType::respectTheEarth)->with('sections.blocks')->get();
|
||||
|
||||
foreach ($pages as $page) {
|
||||
foreach ($this->getSections() as $sectionConfig) {
|
||||
$section = $page->sections->firstWhere('name', $sectionConfig['name']);
|
||||
|
||||
if (!$section) {
|
||||
$section = $page->sections()->create([
|
||||
'name' => $sectionConfig['name'],
|
||||
'visibility' => true,
|
||||
'data' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
// handling images in data
|
||||
$sectionData = $sectionConfig['data'];
|
||||
|
||||
foreach ($sectionData as $key => &$value) {
|
||||
if (isset($value['inputType']) && $value['inputType'] === InputType::image->value) {
|
||||
$value['value'] = FileStorageHelper::storeFile(
|
||||
$page,
|
||||
new \SplFileInfo(resource_path('assets/images/' . $value['value']))
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($value['inputType']) && $value['inputType'] === InputType::selectImageVideo->value) {
|
||||
$value['value']['picture']['path'] = FileStorageHelper::storeFile(
|
||||
$page,
|
||||
new \SplFileInfo(resource_path('assets/images/' . $value['value']['picture']['path']))
|
||||
);
|
||||
}
|
||||
}
|
||||
unset($value);
|
||||
|
||||
// has_pictos / max_pictos
|
||||
if (!empty($sectionConfig['blocks']['has_pictos'])) {
|
||||
$sectionData['has_pictos'] = true;
|
||||
$sectionData['max_pictos'] = $sectionConfig['blocks']['max_pictos'] ?? 6;
|
||||
}
|
||||
|
||||
$section->update([
|
||||
'data' => $sectionData,
|
||||
'visibility' => true,
|
||||
]);
|
||||
|
||||
// Blocks
|
||||
$blocks = [];
|
||||
if (!empty($sectionConfig['blocks']['reductionBlock'])) {
|
||||
$blocks[] = BlockTypeEnum::redirection->defaultConfig();
|
||||
}
|
||||
|
||||
foreach ($blocks as $index => $blockConfig) {
|
||||
Block::updateOrCreate([
|
||||
'section_id' => $section->id,
|
||||
'type' => $blockConfig['type']->value,
|
||||
'data' => $blockConfig['data'],
|
||||
'order' => $index,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$pages = StaticPage::where('type', StaticPageType::respectTheEarth)->with('sections.blocks')->get();
|
||||
|
||||
foreach ($pages as $page) {
|
||||
foreach ($this->getSections() as $sectionConfig) {
|
||||
$section = $page->sections->firstWhere('name', $sectionConfig['name']);
|
||||
|
||||
if (!$section) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$section->blocks()->delete();
|
||||
$section->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Picto;
|
||||
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('pictos', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('title')->comment('title.');
|
||||
$table->string('description')->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->integer('picture_width')->default(Picto::MIN_WIDTH)->comment('Picture widht');
|
||||
$table->integer('picture_height')->default(Picto::MIN_HEIGHT)->comment('picture height');
|
||||
$table->foreignId('section_id')->constrained('section_pages')->cascadeOnDelete();
|
||||
$table->integer('order')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('pictos');
|
||||
}
|
||||
};
|
||||
162
database/migrations/2026_06_03_102408_add_data_for_pictos.php
Normal file
162
database/migrations/2026_06_03_102408_add_data_for_pictos.php
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\StaticPageType;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Picto;
|
||||
use App\Models\StaticPage;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private function getPictos(): array
|
||||
{
|
||||
return [
|
||||
'section 2' => [
|
||||
[
|
||||
'title' => 'COLISAGE OPTIMISÉ',
|
||||
'description' => 'avec des cartons pleins',
|
||||
'picture' => 'commitments/pictograms/picto-open-box.svg',
|
||||
'picture_alt' => 'Pictogramme d\'un carton ouvert pour optimiser les colisages',
|
||||
'picture_title' => 'Pictogramme d\'un colisage optimisé',
|
||||
],
|
||||
[
|
||||
'title' => 'FOURNISSEURS LOCAUX',
|
||||
'description' => 'privilégiés',
|
||||
'picture' => 'commitments/pictograms/picto-local-french-suppliers.svg',
|
||||
'picture_alt' => 'Pictogramme de la carte de France avec un emplacement de coq, illustrant les fournisseurs locaux privilégiés par Le Gaulois',
|
||||
'picture_title' => 'Pictogramme de fournisseurs français locaux privilégiés',
|
||||
],
|
||||
[
|
||||
'title' => 'RÉDUCTION DES DISTANCES',
|
||||
'description' => 'de transport des animaux',
|
||||
'picture' => 'commitments/pictograms/picto-terminal.svg',
|
||||
'picture_alt' => 'Pictogramme d\'une borne de distance kilométrique peu élevé pour réduire les distances de transport des volailles Le Gaulois',
|
||||
'picture_title' => 'Pictogramme de réduction des distance de transport des volailles',
|
||||
],
|
||||
[
|
||||
'title' => 'DES BOUCLES DE TRANSPORT VERTUEUSES',
|
||||
'description' => 'des produits',
|
||||
'picture' => 'commitments/pictograms/picto-map.svg',
|
||||
'picture_alt' => 'Pictogramme d\'une carte avec des boucles de transport plus vertueuses pour les produits Le Gaulois',
|
||||
'picture_title' => 'Pictogramme d\'une boucle de transport vertueux',
|
||||
],
|
||||
[
|
||||
'title' => 'AMÉLIORATION DE LA FLOTTE',
|
||||
'description' => 'dédiée au ramassage',
|
||||
'picture' => 'commitments/pictograms/picto-truck.svg',
|
||||
'picture_alt' => 'Pictogramme d\'un camion plus écologique pour la flotte dédiée au ramassage des volailles Le Gaulois',
|
||||
'picture_title' => 'Pictogramme d\'une flotte de transport dédié au ramassage plus écologique',
|
||||
],
|
||||
[
|
||||
'title' => 'BASSE CONSOMMATION',
|
||||
'description' => 'Des poulaillers à',
|
||||
'picture' => 'commitments/pictograms/picto-henhouse.svg',
|
||||
'picture_alt' => 'Poulaillers d\'élevage à basse consommation',
|
||||
'picture_title' => 'Pictogramme d\'un poulailler à basse consommation pour les volailles Le Gaulois',
|
||||
],
|
||||
],
|
||||
'section 4' => [
|
||||
[
|
||||
'title' => 'RÉDUCTION DES EMBALLAGES',
|
||||
'description' => 'épaisseurs, dimensions et poids',
|
||||
'picture' => 'commitments/pictograms/picto-reduction-packaging.svg',
|
||||
'picture_alt' => 'Pictogramme d\'un poids "-kg" en baisse sur les emballages grâce à l\'impact des réductions sur les épaisseurs, les dimensions et le poids',
|
||||
'picture_title' => 'Pictogramme sur la réduction du poids des emballages',
|
||||
],
|
||||
[
|
||||
'title' => '100% DES EMBALLAGES CARTON',
|
||||
'description' => 'issus de cartons recyclés ou certifiés FSC Mixte',
|
||||
'picture' => 'commitments/pictograms/picto-recycled-cardboard-packaging.svg',
|
||||
'picture_alt' => 'Pictogramme d\'un carton issus de cartons recyclés ou certifiés FSC Mixte',
|
||||
'picture_title' => 'Pictogramme d\'un emballage carton recyclé ou certifié FSC',
|
||||
],
|
||||
[
|
||||
'sustitle' => 'Mise au point d\'une',
|
||||
'title' => 'FILIÈRE DE RECYCLABILITÉ',
|
||||
'description' => 'des barquettes de volaille Le Gaulois',
|
||||
'picture' => 'commitments/pictograms/picto-recycling-industry.svg',
|
||||
'picture_alt' => 'Pictogramme d\'une filière de recyclabilité en France sur les barquettes de volailles Le Gaulois',
|
||||
'picture_title' => 'Pictogramme d\'une filière de recyclabilité des barquettes',
|
||||
],
|
||||
[
|
||||
'title' => 'MOINS DE PLASTIQUE',
|
||||
'description' => 'minimiser notre empreinte carbone et agir sur le changement climatique',
|
||||
'picture' => 'commitments/pictograms/picto-plastic-decrease.svg',
|
||||
'picture_alt' => 'Pictogramme d\'un sac plastique avec l\'estampille de recyclabilité',
|
||||
'picture_title' => 'Pictogramme sur la diminution de plastique',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$pages = StaticPage::where('type', StaticPageType::respectTheEarth)->with('sections')->get();
|
||||
|
||||
foreach ($pages as $page) {
|
||||
foreach ($this->getPictos() as $sectionName => $pictos) {
|
||||
$section = $page->sections->firstWhere('name', $sectionName);
|
||||
|
||||
if (!$section) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = (array) $section->data ?? [];
|
||||
|
||||
if (!($data['has_pictos'] ?? false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$maxPictos = $data['max_pictos'] ?? null;
|
||||
if ($maxPictos !== null && count($pictos) > $maxPictos) {
|
||||
throw new \Exception("Trop de pictos pour la section '{$sectionName}' (max: {$maxPictos}, fournis: " . count($pictos) . ")");
|
||||
}
|
||||
|
||||
foreach ($pictos as $order => $pictoConfig) {
|
||||
$picturePath = FileStorageHelper::storeFile(
|
||||
$page,
|
||||
new \SplFileInfo(resource_path('assets/images/' . $pictoConfig['picture']))
|
||||
);
|
||||
|
||||
Picto::create([
|
||||
'title' => $pictoConfig['title'],
|
||||
'description' => $pictoConfig['description'] ?? null,
|
||||
'picture' => $picturePath,
|
||||
'picture_alt' => $pictoConfig['picture_alt'],
|
||||
'picture_title' => $pictoConfig['picture_title'],
|
||||
'section_id' => $section->id,
|
||||
'order' => $order + 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$pages = StaticPage::where('type', StaticPageType::respectTheEarth)->with('sections')->get();
|
||||
|
||||
foreach ($pages as $page) {
|
||||
foreach (array_keys($this->getPictos()) as $sectionName) {
|
||||
$section = $page->sections->firstWhere('name', $sectionName);
|
||||
|
||||
if (!$section) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = (array) $section->data ?? [];
|
||||
if (!($data['has_pictos'] ?? false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Picto::where('section_id', $section->id)->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
resources/assets/images/commitments/commitment-5/picto-life.png
Normal file
BIN
resources/assets/images/commitments/commitment-5/picto-life.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -12,6 +12,7 @@ return [
|
|||
'edit' => 'mettre à jour',
|
||||
'view' => 'aperçu',
|
||||
'order_submodels' => 'ordonnancement des sous-élements',
|
||||
'back' => 'retour'
|
||||
],
|
||||
|
||||
'helpers' => [
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ return [
|
|||
'sell_item' => 'objet en vente|objets en vente',
|
||||
'rating' => 'note',
|
||||
'categoryApproach' => 'categorie|categories',
|
||||
'sectionApproach' => 'section|sections'
|
||||
'sectionApproach' => 'section|sections',
|
||||
'picto' => 'pictogramme|pictogrammes'
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -354,5 +354,6 @@ return [
|
|||
'picture_title' => 'titre de l\'image',
|
||||
'video_alt' => 'texte alternatif de la vidéo',
|
||||
'video_title' => 'titre de la vidéo',
|
||||
'section' => 'section|sections'
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,34 +1,52 @@
|
|||
import sweetalert from "../../modules/sweetalertConfirm";
|
||||
import { apiFetch, showFlashGlobal } from "../utilities/submit-form";
|
||||
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
/**
|
||||
* Popup delete item.
|
||||
*/
|
||||
const elements = document.getElementsByClassName(
|
||||
"confirmDeleteTS"
|
||||
) as HTMLCollectionOf<Element>;
|
||||
const elements = document.getElementsByClassName("confirmDeleteTS") as HTMLCollectionOf<Element>;
|
||||
|
||||
for (const element of elements) {
|
||||
element.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
const el = e.target;
|
||||
if (!el || !(el instanceof HTMLFormElement)) {
|
||||
throw new Error(
|
||||
"confirmDoneJS can only be executed on an exising form"
|
||||
);
|
||||
}
|
||||
sweetalert.methods.confirm(
|
||||
"Êtes-vous sûr ?",
|
||||
"Toutes les données vont être perdu",
|
||||
el as HTMLFormElement,
|
||||
function () {
|
||||
(el as HTMLFormElement).submit();
|
||||
},
|
||||
{ icon: "warning" }
|
||||
);
|
||||
return false;
|
||||
});
|
||||
const submitBtn = element.querySelector<HTMLButtonElement>('button[type="submit"]');
|
||||
|
||||
submitBtn?.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const form = (e.target as HTMLElement).closest<HTMLFormElement>('.confirmDeleteTS');
|
||||
if (!form) return;
|
||||
|
||||
const pictoId = form.dataset.picto;
|
||||
|
||||
sweetalert.methods.confirm(
|
||||
"Êtes-vous sûr ?",
|
||||
"Toutes les données vont être perdu",
|
||||
form,
|
||||
async function () {
|
||||
if (pictoId) {
|
||||
try {
|
||||
const data = await apiFetch(`/bo/pictos/${pictoId}`, 'POST', form, 'DELETE');
|
||||
form.closest('tr')?.remove();
|
||||
setTimeout(() => {
|
||||
showFlashGlobal(data?.message ?? 'Picto supprimé avec succès.', 'success');
|
||||
}, 300);
|
||||
} catch (err: any) {
|
||||
showFlashGlobal(err?.message ?? 'Erreur lors de la suppression.', 'danger');
|
||||
console.error(err);
|
||||
}
|
||||
} else {
|
||||
form.submit();
|
||||
}
|
||||
},
|
||||
{ icon: "warning" }
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Popup delete item.
|
||||
*/
|
||||
|
|
@ -61,3 +79,13 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ import "./utilities/bootstrap";
|
|||
import "./utilities/fontawesome";
|
||||
import "./utilities/vue-bo";
|
||||
window.initMediaSelector = initMediaSelector;
|
||||
import "./utilities/toogle-visibility";
|
||||
import "./utilities/submit-form";
|
||||
import "./utilities/toogle-block";
|
||||
import "./utilities/picto-picture-size";
|
||||
import "./utilities/picto-form";
|
||||
|
||||
// MODULES
|
||||
import "./../modules/sweetalert";
|
||||
|
|
|
|||
|
|
@ -4,31 +4,39 @@ export function initMediaSelector(id: string) {
|
|||
return;
|
||||
}
|
||||
|
||||
const mediaVideo = document.getElementById("mediaSelectorVideo") as HTMLInputElement,
|
||||
mediaImage = document.getElementById("mediaSelectorImage") as HTMLInputElement,
|
||||
mediaEmpty = document.getElementById("mediaSelectorEmpty") as HTMLInputElement;
|
||||
const mediaVideo = container.querySelector(`#${id}SelectorVideo`) as HTMLInputElement,
|
||||
mediaImage = container.querySelector(`#${id}SelectorImage`) as HTMLInputElement,
|
||||
mediaEmpty = container.querySelector(`#${id}SelectorEmpty`) as HTMLInputElement;
|
||||
|
||||
mediaVideo?.addEventListener("change", function() {
|
||||
hideFormPart(`.${id}Picture`);
|
||||
showFormPart(`.${id}Video`);
|
||||
hideFormPart(`.${id}None`);
|
||||
hideFormPart(container, `.${id}Picture`);
|
||||
showFormPart(container, `.${id}Video`);
|
||||
hideFormPart(container, `.${id}None`);
|
||||
});
|
||||
|
||||
mediaImage?.addEventListener("change", function() {
|
||||
hideFormPart(`.${id}Video`);
|
||||
showFormPart(`.${id}Picture`);
|
||||
hideFormPart(`.${id}None`);
|
||||
hideFormPart(container, `.${id}Video`);
|
||||
showFormPart(container, `.${id}Picture`);
|
||||
hideFormPart(container, `.${id}None`);
|
||||
});
|
||||
|
||||
mediaEmpty?.addEventListener("change", function() {
|
||||
hideFormPart(`.${id}Video`);
|
||||
hideFormPart(`.${id}Picture`);
|
||||
showFormPart(`.${id}None`);
|
||||
hideFormPart(container, `.${id}Video`);
|
||||
hideFormPart(container, `.${id}Picture`);
|
||||
showFormPart(container, `.${id}None`);
|
||||
});
|
||||
|
||||
if (mediaVideo?.checked) {
|
||||
mediaVideo.dispatchEvent(new Event("change"));
|
||||
} else if (mediaImage?.checked) {
|
||||
mediaImage.dispatchEvent(new Event("change"));
|
||||
} else if (mediaEmpty?.checked) {
|
||||
mediaEmpty.dispatchEvent(new Event("change"));
|
||||
}
|
||||
}
|
||||
|
||||
function hideFormPart(id: string) {
|
||||
const parts = document.querySelectorAll(id);
|
||||
function hideFormPart(container: HTMLElement, selector: string) {
|
||||
const parts = container.querySelectorAll(selector);
|
||||
parts.forEach(partToHide => {
|
||||
partToHide.classList.add("d-none");
|
||||
const inputs = partToHide.querySelectorAll("input");
|
||||
|
|
@ -37,8 +45,9 @@ function hideFormPart(id: string) {
|
|||
}
|
||||
});
|
||||
}
|
||||
function showFormPart(id: string) {
|
||||
const parts = document.querySelectorAll(id);
|
||||
|
||||
function showFormPart(container: HTMLElement, selector: string) {
|
||||
const parts = container.querySelectorAll(selector);
|
||||
parts.forEach(partToShow => {
|
||||
partToShow.classList.remove("d-none");
|
||||
const inputs = partToShow.querySelectorAll("input");
|
||||
|
|
|
|||
241
resources/ts/bo/utilities/picto-form.ts
Normal file
241
resources/ts/bo/utilities/picto-form.ts
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
import { apiFetch, initPictureSizeButtons, showFlashGlobal } from "./submit-form";
|
||||
import { initVueComponents } from "./vue-bo";
|
||||
import * as bootstrap from 'bootstrap';
|
||||
|
||||
|
||||
|
||||
function getBtnCreate(sectionId: string | undefined) {
|
||||
return document.querySelector<HTMLAnchorElement>(`.btn-create-picto[data-section="${sectionId}"]`);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// PICTO CREATE
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll<HTMLAnchorElement>('.btn-create-picto').forEach(btnCreate => {
|
||||
const sectionId = btnCreate.dataset.section;
|
||||
|
||||
const pictoList = document.querySelector<HTMLDivElement>(`.picto-list[data-section="${sectionId}"]`);
|
||||
const pictoForm = document.querySelector<HTMLDivElement>(`.picto-form-wrapper[data-section="${sectionId}"]`);
|
||||
const btnBack = pictoForm?.querySelector<HTMLButtonElement>('.btn-back-picto');
|
||||
|
||||
const originalHTML = pictoForm!.innerHTML;
|
||||
|
||||
btnCreate.addEventListener('click', (e: Event) => {
|
||||
e.preventDefault();
|
||||
|
||||
pictoForm!.innerHTML = originalHTML;
|
||||
initVueComponents(pictoForm!);
|
||||
initPictureSizeButtons(pictoForm!);
|
||||
|
||||
pictoForm!.querySelector('.btn-back-picto')?.addEventListener('click', () => {
|
||||
pictoForm!.style.display = 'none';
|
||||
pictoList!.style.display = 'block';
|
||||
btnCreate.style.display = '';
|
||||
});
|
||||
|
||||
pictoList!.style.display = 'none';
|
||||
pictoForm!.style.display = 'block';
|
||||
btnCreate.style.display = 'none';
|
||||
});
|
||||
|
||||
btnBack?.addEventListener('click', () => {
|
||||
pictoForm!.style.display = 'none';
|
||||
pictoList!.style.display = 'block';
|
||||
btnCreate.style.display = '';
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// PICTO EDIT
|
||||
document.addEventListener('click', async (e) => {
|
||||
const btn = (e.target as HTMLElement).closest<HTMLAnchorElement>('.btn-edit-picto');
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
|
||||
const pictoId = btn.dataset.picto;
|
||||
const sectionId = btn.dataset.section;
|
||||
const wrapper = document.querySelector<HTMLDivElement>(`.picto-form-wrapper[data-section="${sectionId}"]`);
|
||||
const list = document.querySelector<HTMLDivElement>(`.picto-list[data-section="${sectionId}"]`);
|
||||
const btnCreate = getBtnCreate(sectionId);
|
||||
if (!wrapper || !list) return;
|
||||
|
||||
const res = await fetch(`/bo/pictos/${pictoId}/edit`, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/html' }
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const html = await res.text();
|
||||
|
||||
const backBtn = wrapper.querySelector('.btn-back-picto')!.cloneNode(true);
|
||||
wrapper.innerHTML = '';
|
||||
wrapper.appendChild(backBtn);
|
||||
const formContainer = document.createElement('div');
|
||||
formContainer.innerHTML = html;
|
||||
wrapper.appendChild(formContainer);
|
||||
initVueComponents(formContainer);
|
||||
initPictureSizeButtons(formContainer);
|
||||
|
||||
list.style.display = 'none';
|
||||
wrapper.style.display = 'block';
|
||||
if (btnCreate) btnCreate.style.display = 'none';
|
||||
|
||||
// Rebind back button
|
||||
wrapper.querySelector('.btn-back-picto')?.addEventListener('click', () => {
|
||||
wrapper.style.display = 'none';
|
||||
list.style.display = 'block';
|
||||
if (btnCreate) btnCreate.style.display = '';
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// CHANGE ORDER
|
||||
const pendingOrders = new Set<string>();
|
||||
|
||||
document.addEventListener('submit', async (e) => {
|
||||
const form = (e.target as HTMLElement).closest<HTMLFormElement>('[data-change-order]');
|
||||
if (!form) return;
|
||||
e.preventDefault();
|
||||
|
||||
const sectionId = form.closest<HTMLDivElement>('.picto-list')?.dataset.section;
|
||||
if (!sectionId) return;
|
||||
|
||||
// Bloc refresh
|
||||
if (pendingOrders.has(sectionId)) return;
|
||||
pendingOrders.add(sectionId);
|
||||
|
||||
(document.activeElement as HTMLElement)?.blur();
|
||||
|
||||
form.closest('tr')?.querySelectorAll<HTMLElement>('[data-bs-toggle="tooltip"]').forEach(el => {
|
||||
bootstrap.Tooltip.getInstance(el)?.dispose();
|
||||
});
|
||||
|
||||
try {
|
||||
const data = await apiFetch(form.action, 'POST', form, 'PATCH');
|
||||
const flashContainer = document.querySelector<HTMLElement>(`.picto-list[data-section="${sectionId}"] .flash-container`);
|
||||
if (flashContainer) {
|
||||
flashContainer.innerHTML = `
|
||||
<div class="alert alert-success alert-dismissible fade show m-0 mb-3" role="alert">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
<strong>${data.message ?? 'Enregistré avec succès.'}</strong>
|
||||
<button class="btn-close" data-bs-dismiss="alert" type="button" aria-label="Fermer"></button>
|
||||
</div>`;
|
||||
setTimeout(() => { flashContainer.innerHTML = ''; }, 5000);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
|
||||
const freshContainer = document.querySelector<HTMLDivElement>(`.picto-list[data-section="${sectionId}"]`);
|
||||
if (!freshContainer) return;
|
||||
|
||||
freshContainer.querySelectorAll<HTMLElement>('[data-bs-toggle="tooltip"]').forEach(el => {
|
||||
bootstrap.Tooltip.getInstance(el)?.dispose();
|
||||
});
|
||||
|
||||
const res = await fetch(`/bo/pictos?section_id=${sectionId}`, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const tableWrapper = freshContainer.querySelector<HTMLDivElement>('.picto-table-wrapper');
|
||||
if (!tableWrapper) return;
|
||||
tableWrapper.innerHTML = await res.text();
|
||||
|
||||
freshContainer.querySelectorAll<HTMLElement>('[data-bs-toggle="tooltip"]').forEach(el => {
|
||||
const tt = new bootstrap.Tooltip(el);
|
||||
el.addEventListener('hidden.bs.tooltip', () => tt.dispose(), { once: true });
|
||||
});
|
||||
}
|
||||
} catch (e: any) {
|
||||
const flashContainer = document.querySelector<HTMLElement>(`.picto-list[data-section="${sectionId}"] .flash-container`);
|
||||
if (flashContainer) {
|
||||
flashContainer.innerHTML = `
|
||||
<div class="alert alert-danger alert-dismissible fade show m-0 mb-3" role="alert">
|
||||
<i class="fa-solid fa-circle-xmark"></i>
|
||||
<strong>${e.message ?? 'Une erreur est survenue.'}</strong>
|
||||
<button class="btn-close" data-bs-dismiss="alert" type="button" aria-label="Fermer"></button>
|
||||
</div>`;
|
||||
setTimeout(() => { flashContainer.innerHTML = ''; }, 4000);
|
||||
}
|
||||
console.error(e);
|
||||
} finally {
|
||||
pendingOrders.delete(sectionId);
|
||||
}
|
||||
});
|
||||
|
||||
// ORDER TABLE PICTOS
|
||||
document.addEventListener('click', async (e) => {
|
||||
const link = (e.target as HTMLElement).closest<HTMLAnchorElement>('#pictos-table thead a');
|
||||
if (!link) return;
|
||||
e.preventDefault();
|
||||
|
||||
const sectionId = link.closest<HTMLDivElement>('.picto-list')?.dataset.section;
|
||||
if (!sectionId) return;
|
||||
|
||||
const url = new URL(link.href);
|
||||
const sortCol = url.searchParams.get('sort_col');
|
||||
const sortWay = url.searchParams.get('sort_way');
|
||||
|
||||
const freshContainer = document.querySelector<HTMLDivElement>(`.picto-list[data-section="${sectionId}"]`);
|
||||
if (!freshContainer) return;
|
||||
|
||||
const params = new URLSearchParams({ section_id: sectionId });
|
||||
if (sortCol) params.set('sort_col', sortCol);
|
||||
if (sortWay) params.set('sort_way', sortWay);
|
||||
|
||||
const res = await fetch(`/bo/pictos?${params.toString()}`, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
freshContainer.querySelectorAll<HTMLElement>('[data-bs-toggle="tooltip"]').forEach(el => {
|
||||
bootstrap.Tooltip.getInstance(el)?.dispose();
|
||||
});
|
||||
const tableWrapper = freshContainer.querySelector<HTMLDivElement>('.picto-table-wrapper');
|
||||
if (!tableWrapper) return;
|
||||
tableWrapper.innerHTML = await res.text();
|
||||
|
||||
// Afficher le bouton reset si un tri est actif
|
||||
const resetBtn = freshContainer.querySelector<HTMLDivElement>('.picto-sort-reset');
|
||||
if (resetBtn && sortCol) resetBtn.classList.remove('d-none');
|
||||
|
||||
freshContainer.querySelectorAll<HTMLElement>('[data-bs-toggle="tooltip"]').forEach(el => {
|
||||
const tt = new bootstrap.Tooltip(el);
|
||||
el.addEventListener('hidden.bs.tooltip', () => tt.dispose(), { once: true });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// RESET SORT PICTOS
|
||||
document.addEventListener('click', async (e) => {
|
||||
const resetLink = (e.target as HTMLElement).closest<HTMLAnchorElement>('.picto-sort-reset a');
|
||||
if (!resetLink) return;
|
||||
e.preventDefault();
|
||||
|
||||
const sectionId = resetLink.closest<HTMLDivElement>('.picto-list')?.dataset.section;
|
||||
if (!sectionId) return;
|
||||
|
||||
const freshContainer = document.querySelector<HTMLDivElement>(`.picto-list[data-section="${sectionId}"]`);
|
||||
if (!freshContainer) return;
|
||||
|
||||
const res = await fetch(`/bo/pictos?section_id=${sectionId}`, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
freshContainer.querySelectorAll<HTMLElement>('[data-bs-toggle="tooltip"]').forEach(el => {
|
||||
bootstrap.Tooltip.getInstance(el)?.dispose();
|
||||
});
|
||||
|
||||
const tableWrapper = freshContainer.querySelector<HTMLDivElement>('.picto-table-wrapper');
|
||||
if (!tableWrapper) return;
|
||||
tableWrapper.innerHTML = await res.text();
|
||||
|
||||
freshContainer.querySelector('.picto-sort-reset')?.classList.add('d-none');
|
||||
|
||||
freshContainer.querySelectorAll<HTMLElement>('[data-bs-toggle="tooltip"]').forEach(el => {
|
||||
const tt = new bootstrap.Tooltip(el);
|
||||
el.addEventListener('hidden.bs.tooltip', () => tt.dispose(), { once: true });
|
||||
});
|
||||
}
|
||||
});
|
||||
113
resources/ts/bo/utilities/picto-picture-size.ts
Normal file
113
resources/ts/bo/utilities/picto-picture-size.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import { createApp } from 'vue';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import ImageInputComponent from '../components/ImageInputComponent.vue';
|
||||
|
||||
function remountImageInput(el: Element, json: object): void {
|
||||
const app = createApp(ImageInputComponent, { json: JSON.stringify(json) });
|
||||
app.config.globalProperties.window = window;
|
||||
app.config.globalProperties.document = document;
|
||||
app.component('FontAwesomeIcon', FontAwesomeIcon);
|
||||
app.mount(el);
|
||||
}
|
||||
|
||||
function findImageWrapperById(targetId: string): HTMLElement | null {
|
||||
return Array.from(
|
||||
document.querySelectorAll<HTMLElement>('.image-input[data-json]')
|
||||
).find(el => {
|
||||
try {
|
||||
return String(JSON.parse(el.getAttribute('data-json')!).id) === String(targetId);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}) ?? null;
|
||||
}
|
||||
|
||||
export function initPictureSizeButton(saveBtn: HTMLButtonElement): void {
|
||||
const targetId = saveBtn.dataset.targetImage;
|
||||
const widthInputId = saveBtn.dataset.widthInput;
|
||||
const heightInputId = saveBtn.dataset.heightInput;
|
||||
const url = saveBtn.dataset.url;
|
||||
|
||||
if (!targetId || !widthInputId || !heightInputId || !url) return;
|
||||
|
||||
const widthInput = document.getElementById(widthInputId) as HTMLInputElement | null;
|
||||
const heightInput = document.getElementById(heightInputId) as HTMLInputElement | null;
|
||||
|
||||
const applyDimensions = (width: string, height: string): void => {
|
||||
const preview = document.querySelector<HTMLImageElement>(`#${targetId}-preview`);
|
||||
if (preview && width && height) {
|
||||
preview.style.width = `${width}px`;
|
||||
preview.style.height = `${height}px`;
|
||||
}
|
||||
};
|
||||
|
||||
saveBtn.addEventListener('click', async (e: MouseEvent): Promise<void> => {
|
||||
e.preventDefault();
|
||||
|
||||
const width = widthInput?.value ?? '';
|
||||
const height = heightInput?.value ?? '';
|
||||
const csrf = document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')?.content;
|
||||
const pictoId = saveBtn.dataset.pictoId ?? null;
|
||||
|
||||
if (!width || !height) return;
|
||||
|
||||
// mounted component with new sizes
|
||||
const applyAndRemount = (): void => {
|
||||
const oldWrapper = findImageWrapperById(targetId);
|
||||
if (!oldWrapper) return;
|
||||
|
||||
const json = JSON.parse(oldWrapper.getAttribute('data-json')!);
|
||||
json.width = Number(width);
|
||||
json.height = Number(height);
|
||||
json.helper = `Doit être ${width}px (largeur) par ${height}px (hauteur)`;
|
||||
|
||||
const newWrapper = document.createElement('div');
|
||||
newWrapper.className = 'image-input';
|
||||
newWrapper.setAttribute('data-json', JSON.stringify(json));
|
||||
oldWrapper.replaceWith(newWrapper);
|
||||
|
||||
remountImageInput(newWrapper, json);
|
||||
applyDimensions(width, height);
|
||||
};
|
||||
|
||||
// handle helper when no pictoId
|
||||
if (!pictoId) {
|
||||
applyAndRemount();
|
||||
return;
|
||||
}
|
||||
|
||||
// in edit request
|
||||
if (!csrf) return;
|
||||
|
||||
try {
|
||||
await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrf,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
picture_width: width,
|
||||
picture_height: height,
|
||||
picto_id: pictoId,
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la sauvegarde des dimensions :', error);
|
||||
} finally {
|
||||
applyAndRemount();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
// when update dimension re upload img
|
||||
const pictureInput = document.querySelector<HTMLInputElement>(
|
||||
`#${targetId} input[name="picture"]`
|
||||
);
|
||||
pictureInput?.addEventListener('change', (): void => {
|
||||
applyDimensions(widthInput?.value ?? '', heightInput?.value ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
184
resources/ts/bo/utilities/submit-form.ts
Normal file
184
resources/ts/bo/utilities/submit-form.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
// Helpers
|
||||
|
||||
import { initPictureSizeButton } from "./picto-picture-size";
|
||||
|
||||
function clearFormErrors(form: HTMLFormElement) {
|
||||
form.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid'));
|
||||
form.querySelectorAll('.invalid-feedback').forEach(el => el.remove());
|
||||
}
|
||||
|
||||
function displayFormErrors(form: HTMLFormElement, errors: Record<string, string[]>) {
|
||||
clearFormErrors(form);
|
||||
Object.entries(errors).forEach(([field, messages]) => {
|
||||
const inputName = field.replace(/\.(\w+)/g, '[$1]');
|
||||
const input = form.querySelector(`[name="${inputName}"]`) as HTMLElement;
|
||||
if (!input) return;
|
||||
input.classList.add('is-invalid');
|
||||
const feedback = document.createElement('div');
|
||||
feedback.classList.add('invalid-feedback');
|
||||
feedback.textContent = messages[0];
|
||||
input.after(feedback);
|
||||
});
|
||||
}
|
||||
|
||||
function getFlashContainer(form: HTMLFormElement): HTMLElement {
|
||||
let container = form.previousElementSibling as HTMLElement;
|
||||
if (!container || !container.classList.contains('flash-container')) {
|
||||
container = document.createElement('div');
|
||||
container.classList.add('flash-container');
|
||||
form.parentElement!.insertBefore(container, form);
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
async function fetchFlash(container: HTMLElement) {
|
||||
const res = await fetch('/bo/flash', {
|
||||
headers: { 'Accept': 'application/json' }
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.html) {
|
||||
container.innerHTML = data.html;
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccess(form: HTMLFormElement, message: string) {
|
||||
const container = getFlashContainer(form);
|
||||
container.innerHTML = `
|
||||
<div class="alert alert-success alert-dismissible fade show m-0 mb-3" role="alert">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
<strong>${message}</strong>
|
||||
<button class="btn-close" data-bs-dismiss="alert" type="button" aria-label="Fermer"></button>
|
||||
</div>`;
|
||||
container.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
setTimeout(() => container.innerHTML = '', 5000);
|
||||
}
|
||||
|
||||
function showError(form: HTMLFormElement, message: string) {
|
||||
const container = getFlashContainer(form);
|
||||
container.innerHTML = `
|
||||
<div class="alert alert-danger alert-dismissible fade show m-0 mb-3" role="alert">
|
||||
<i class="fa-solid fa-circle-xmark"></i>
|
||||
<strong>${message}</strong>
|
||||
<button class="btn-close" data-bs-dismiss="alert" type="button" aria-label="Fermer"></button>
|
||||
</div>`;
|
||||
container.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
setTimeout(() => container.innerHTML = '', 5000);
|
||||
}
|
||||
|
||||
export function showFlashGlobal(message: string, type: 'success' | 'danger' = 'success') {
|
||||
let container = document.querySelector<HTMLElement>('.flash-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.classList.add('flash-container');
|
||||
document.body.prepend(container);
|
||||
}
|
||||
const icon = type === 'success' ? 'fa-circle-check' : 'fa-circle-xmark';
|
||||
container.innerHTML = `
|
||||
<div class="alert alert-${type} alert-dismissible fade show m-0 mb-3" role="alert">
|
||||
<i class="fa-solid ${icon}"></i>
|
||||
<strong>${message}</strong>
|
||||
<button class="btn-close" data-bs-dismiss="alert" type="button" aria-label="Fermer"></button>
|
||||
</div>`;
|
||||
container.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
setTimeout(() => { if (container) container.innerHTML = ''; }, 5000);
|
||||
}
|
||||
|
||||
|
||||
export async function handleSubmit(form: HTMLFormElement, action: (form: HTMLFormElement) => Promise<any>) {
|
||||
clearFormErrors(form);
|
||||
try {
|
||||
const data = await action(form);
|
||||
showSuccess(form, data.message ?? 'Enregistré avec succès.');
|
||||
} catch (e: any) {
|
||||
if (e.errors) displayFormErrors(form, e.errors);
|
||||
if (e.message && !e.errors) showError(form, e.message);
|
||||
console.error(e);
|
||||
const container = getFlashContainer(form);
|
||||
container.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
await fetchFlash(container);
|
||||
}
|
||||
}
|
||||
|
||||
export function initPictureSizeButtons(container: HTMLElement = document.body): void {
|
||||
container.querySelectorAll<HTMLButtonElement>('.submit-picture-size')
|
||||
.forEach(initPictureSizeButton);
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
export async function apiFetch(url: string, method: string, form: HTMLFormElement, override?: string) {
|
||||
const token = (form.querySelector('input[name="_token"]') as HTMLInputElement)?.value;
|
||||
const headers: Record<string, string> = {
|
||||
'X-CSRF-TOKEN': token,
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
|
||||
};
|
||||
|
||||
const isDelete = method === 'DELETE';
|
||||
const body = isDelete ? undefined : new FormData(form);
|
||||
if (!isDelete && override) body!.append('_method', override);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (!response.ok) throw data;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Handlers
|
||||
|
||||
document.addEventListener('submit', async (e) => {
|
||||
const form = (e.target as HTMLElement).closest<HTMLFormElement>('.section-form');
|
||||
if (!form) return;
|
||||
e.preventDefault();
|
||||
await handleSubmit(form, f => apiFetch(`/bo/sections/${f.dataset.id}`, 'POST', f, 'PATCH'));
|
||||
});
|
||||
|
||||
document.addEventListener('submit', async (e) => {
|
||||
const form = (e.target as HTMLElement).closest<HTMLFormElement>('.block-form');
|
||||
if (!form) return;
|
||||
e.preventDefault();
|
||||
await handleSubmit(form, f => apiFetch(`/bo/blocks/${f.dataset.id}`, 'POST', f, 'PUT'));
|
||||
});
|
||||
|
||||
document.addEventListener('submit', async (e) => {
|
||||
const form = (e.target as HTMLElement).closest<HTMLFormElement>('.picto-form');
|
||||
if (!form) return;
|
||||
e.preventDefault();
|
||||
await handleSubmit(form, async f => {
|
||||
const data = f.dataset.id
|
||||
? await apiFetch(`/bo/pictos/${f.dataset.id}`, 'POST', f, 'PUT')
|
||||
: await apiFetch('/bo/pictos', 'POST', f);
|
||||
|
||||
const sectionId = (f.querySelector('input[name="section_id"]') as HTMLInputElement)?.value;
|
||||
if (sectionId && !f.dataset.id) {
|
||||
const listContainer = document.querySelector<HTMLDivElement>(`.picto-list[data-section="${sectionId}"]`);
|
||||
const wrapper = document.querySelector<HTMLDivElement>(`.picto-form-wrapper[data-section="${sectionId}"]`);
|
||||
const btnCreate = document.querySelector<HTMLElement>(`.btn-create-picto[data-section="${sectionId}"]`);
|
||||
|
||||
if (listContainer) {
|
||||
const res = await fetch(`/bo/pictos?section_id=${sectionId}`, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
});
|
||||
if (res.ok) listContainer.innerHTML = await res.text();
|
||||
}
|
||||
|
||||
if (wrapper) wrapper.style.display = 'none';
|
||||
if (listContainer) listContainer.style.display = 'block';
|
||||
if (btnCreate) btnCreate.style.display = '';
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initPictureSizeButtons();
|
||||
});
|
||||
27
resources/ts/bo/utilities/toogle-block.ts
Normal file
27
resources/ts/bo/utilities/toogle-block.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// for show or hide fields (block reduction)
|
||||
document.querySelectorAll<HTMLSelectElement>('[id$="-block-type"]').forEach((sel) => {
|
||||
const prefix = sel.id.replace(/-block-type$/, '');
|
||||
const ctaDiv = document.getElementById(`${prefix}-fields-cta`);
|
||||
const actDiv = document.getElementById(`${prefix}-fields-actu`);
|
||||
|
||||
if (!ctaDiv || !actDiv) return;
|
||||
|
||||
function toggle(): void {
|
||||
const isCta = sel.value === 'cta';
|
||||
const isActu = sel.value === 'actu';
|
||||
const isBoth = sel.value === 'both';
|
||||
|
||||
ctaDiv!.classList.toggle('d-none', isActu);
|
||||
actDiv!.classList.toggle('d-none', isCta);
|
||||
|
||||
ctaDiv!.querySelectorAll<HTMLInputElement>('input, textarea, select').forEach(el => {
|
||||
el.disabled = isActu;
|
||||
});
|
||||
actDiv!.querySelectorAll<HTMLInputElement>('input, textarea, select').forEach(el => {
|
||||
el.disabled = isCta;
|
||||
});
|
||||
}
|
||||
|
||||
sel.addEventListener('change', toggle);
|
||||
toggle();
|
||||
});
|
||||
88
resources/ts/bo/utilities/toogle-visibility.ts
Normal file
88
resources/ts/bo/utilities/toogle-visibility.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
type ToggleResponse = {
|
||||
data: {
|
||||
visibility: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
const csrfToken = document
|
||||
.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')
|
||||
?.content;
|
||||
|
||||
if (!csrfToken) {
|
||||
throw new Error("CSRF token not found");
|
||||
}
|
||||
|
||||
function showAlert(container: HTMLElement, message: string, type: 'success' | 'danger') {
|
||||
const alert = document.createElement('div');
|
||||
alert.className = `alert alert-${type} alert-dismissible fade show`;
|
||||
alert.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
container.prepend(alert);
|
||||
setTimeout(() => alert.remove(), 3000);
|
||||
}
|
||||
|
||||
const toggles = document.querySelectorAll<HTMLInputElement>('.section-toggle');
|
||||
|
||||
toggles.forEach((toggle) => {
|
||||
toggle.addEventListener('change', async function () {
|
||||
const sectionId = this.dataset.id;
|
||||
const targetSelector = this.dataset.target;
|
||||
|
||||
if (!sectionId) return;
|
||||
|
||||
const status = document.getElementById(`status-${sectionId}`);
|
||||
const target = targetSelector
|
||||
? document.querySelector<HTMLElement>(targetSelector)
|
||||
: null;
|
||||
|
||||
if (target) {
|
||||
target.style.display = this.checked ? '' : 'none';
|
||||
}
|
||||
|
||||
this.disabled = true;
|
||||
const flashContainer = document.querySelector<HTMLElement>(`#flash-${sectionId}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/bo/sections/${sectionId}/toggle-visibility`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': csrfToken,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Request failed');
|
||||
}
|
||||
|
||||
const data: ToggleResponse = await response.json();
|
||||
|
||||
// sync with DB
|
||||
this.checked = data.data.visibility;
|
||||
|
||||
if (status) status.innerText = "OK";
|
||||
if (flashContainer) {
|
||||
showAlert(flashContainer, 'Visibilité de la section est mise à jour.', 'success');
|
||||
}
|
||||
} catch (error) {
|
||||
this.checked = !this.checked;
|
||||
if (target) {
|
||||
target.style.display = this.checked ? '' : 'none';
|
||||
}
|
||||
if (status) status.innerText = "KO";
|
||||
if (flashContainer) {
|
||||
showAlert(flashContainer, 'Une erreur est survenue.', 'danger');
|
||||
}
|
||||
console.error(error);
|
||||
} finally {
|
||||
this.disabled = false;
|
||||
if (status) {
|
||||
setTimeout(() => {
|
||||
status.innerText = "";
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -2,19 +2,18 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
|||
import type { App, Component } from "vue";
|
||||
import { createApp } from "vue";
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const setupApp = (app: App<Element>) => {
|
||||
app.config.globalProperties.window = window;
|
||||
app.config.globalProperties.document = document;
|
||||
app.component("FontAwesomeIcon", FontAwesomeIcon);
|
||||
};
|
||||
const setupApp = (app: App<Element>) => {
|
||||
app.config.globalProperties.window = window;
|
||||
app.config.globalProperties.document = document;
|
||||
app.component("FontAwesomeIcon", FontAwesomeIcon);
|
||||
};
|
||||
|
||||
// Register components
|
||||
/// <reference types="vite/client" />
|
||||
const vues = import.meta.glob("./../components/*.vue") as Record<
|
||||
string,
|
||||
() => Promise<{ default: Component }>
|
||||
>;
|
||||
const vues = import.meta.glob("./../components/*.vue") as Record<
|
||||
string,
|
||||
() => Promise<{ default: Component }>
|
||||
>;
|
||||
|
||||
export const initVueComponents = (root: HTMLElement | Document = document) => {
|
||||
for (const vueCptName in vues) {
|
||||
const vueCptPromise = vues[vueCptName];
|
||||
let htmlKey = vueCptName.split("/").pop();
|
||||
|
|
@ -29,21 +28,23 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
.replace(/^-/, "")
|
||||
.replace("-component", "");
|
||||
|
||||
const el = document.getElementById(htmlKey),
|
||||
elGroup = document.getElementsByClassName(htmlKey);
|
||||
// * Init using ID
|
||||
if (el) {
|
||||
vueCptPromise().then((vueParsedFile) => {
|
||||
const app = createApp(vueParsedFile.default, {
|
||||
json: el.dataset.json ?? {},
|
||||
// * Init using ID (seulement sur document)
|
||||
if (root === document) {
|
||||
const el = document.getElementById(htmlKey);
|
||||
if (el) {
|
||||
vueCptPromise().then((vueParsedFile) => {
|
||||
const app = createApp(vueParsedFile.default, {
|
||||
json: el.dataset.json ?? {},
|
||||
});
|
||||
setupApp(app);
|
||||
app.mount(el);
|
||||
});
|
||||
setupApp(app);
|
||||
app.mount(el);
|
||||
});
|
||||
}
|
||||
}
|
||||
// * Init Using class
|
||||
|
||||
// * Init using class
|
||||
const elGroup = root.getElementsByClassName(htmlKey);
|
||||
for (const grpEl of elGroup) {
|
||||
// * Hack to prevent loop weirdness
|
||||
vueCptPromise().then((vueParsedFile) => {
|
||||
setTimeout(() => {
|
||||
const app = createApp(vueParsedFile.default, {
|
||||
|
|
@ -55,4 +56,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
initVueComponents();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
@auth('backend')
|
||||
@php $approachType = intval(request()->route()->parameter('approach_type') ?: ((isset($approachModel)) ? $approachModel->type->value() : 0)); @endphp
|
||||
@php $newsClubType = intval(request()->route()->parameter('news_club_type') ?: ((isset($newsClubModel)) ? $newsClubModel->type->value() : 0)); @endphp
|
||||
<nav class="col-lg-4 col-xl-3 col-xxxl-2 d-lg-block bg-body-tertiary sidebar collapse overflow-x-visible" id="menuNavigation">
|
||||
<nav class="col-lg-4 col-xl-3 col-xxxl-2 d-lg-block bg-body-tertiary sidebar collapse overflow-x-visible"
|
||||
id="menuNavigation">
|
||||
<div class="sidemenu py-2">
|
||||
{{-- HOME --}}
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.dashboard') }}" title="{{ __('Accéder à la page d\'accueil du back-office') }}"
|
||||
role="button" @class([
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.dashboard') }}"
|
||||
title="{{ __('Accéder à la page d\'accueil du back-office') }}" role="button" @class([
|
||||
'btn btn-primary w-100 text-start',
|
||||
'fw-bold active' => request()->routeIs('bo.dashboard'),
|
||||
])>
|
||||
|
|
@ -67,7 +68,8 @@
|
|||
</li>
|
||||
{{-- MAIN MENU --}}
|
||||
<li>
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.general_settings.edit', $mainMenuSettingModel) }}"
|
||||
<a data-bs-toggle="tooltip"
|
||||
href="{{ route('bo.general_settings.edit', $mainMenuSettingModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $mainMenuSettingModel->type->label()]) }}"
|
||||
@class([
|
||||
'dropdown-item px-3 py-2',
|
||||
|
|
@ -96,7 +98,8 @@
|
|||
</li>
|
||||
{{-- HOT NEWS --}}
|
||||
<li>
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.general_settings.edit', $hotNewsSettingModel) }}"
|
||||
<a data-bs-toggle="tooltip"
|
||||
href="{{ route('bo.general_settings.edit', $hotNewsSettingModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $hotNewsSettingModel->type->label()]) }}"
|
||||
@class([
|
||||
'dropdown-item px-3 py-2',
|
||||
|
|
@ -297,7 +300,8 @@
|
|||
<ul class="dropdown-menu">
|
||||
{{-- RECRUITMENT PAGE --}}
|
||||
<li>
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $recruitmentStaticPageModel) }}"
|
||||
<a data-bs-toggle="tooltip"
|
||||
href="{{ route('bo.static_pages.edit', $recruitmentStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $recruitmentStaticPageModel->type->label()]) }}"
|
||||
@class([
|
||||
'dropdown-item px-3 py-2',
|
||||
|
|
@ -478,8 +482,8 @@
|
|||
{{-- RECIPE PAGE --}}
|
||||
@if (isset($recipeStaticPageModel))
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $recipeStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $recipeStaticPageModel->type->label()]) }}" role="button"
|
||||
@class([
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $recipeStaticPageModel->type->label()]) }}"
|
||||
role="button" @class([
|
||||
'btn btn-primary w-100 text-start mt-1',
|
||||
'fw-bold active' =>
|
||||
url()->current() ===
|
||||
|
|
@ -528,7 +532,8 @@
|
|||
<ul class="dropdown-menu">
|
||||
{{-- PRODUCTS PAGE --}}
|
||||
<li>
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $productsStaticPageModel) }}"
|
||||
<a data-bs-toggle="tooltip"
|
||||
href="{{ route('bo.static_pages.edit', $productsStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $productsStaticPageModel->type->label()]) }}"
|
||||
@class([
|
||||
'dropdown-item px-3 py-2',
|
||||
|
|
@ -566,7 +571,8 @@
|
|||
{{-- RANGES --}}
|
||||
<li>
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.ranges.index') }}"
|
||||
title="{{ str(__('crud.actions.order_submodels'))->ucFirst() }}" @class([
|
||||
title="{{ str(__('crud.actions.order_submodels'))->ucFirst() }}"
|
||||
@class([
|
||||
'dropdown-item px-3 py-2',
|
||||
'fw-bold text-bg-secondary' =>
|
||||
request()->routeIs('bo.ranges.*') &&
|
||||
|
|
@ -599,7 +605,8 @@
|
|||
{{-- ORDER RANGES/SUBRANGES --}}
|
||||
@if ($firstRange && $firstRange->subranges->count() >= 2)
|
||||
<li>
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.ranges.edit-subranges-order', $firstRange) }}"
|
||||
<a data-bs-toggle="tooltip"
|
||||
href="{{ route('bo.ranges.edit-subranges-order', $firstRange) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => __('models.classes.subrange')]) }}"
|
||||
@class([
|
||||
'dropdown-item px-3 py-2',
|
||||
|
|
@ -654,8 +661,10 @@
|
|||
{{-- ORDER MEATS/SUBMEATS --}}
|
||||
@if ($firstMeat && $firstMeat->submeats->count() >= 2)
|
||||
<li>
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.meats.edit-submeats-order', $firstMeat) }}"
|
||||
title="{{ str(__('crud.actions.order_submodels'))->ucFirst() }}" @class([
|
||||
<a data-bs-toggle="tooltip"
|
||||
href="{{ route('bo.meats.edit-submeats-order', $firstMeat) }}"
|
||||
title="{{ str(__('crud.actions.order_submodels'))->ucFirst() }}"
|
||||
@class([
|
||||
'dropdown-item px-3 py-2',
|
||||
'fw-bold text-bg-secondary' => request()->routeIs(
|
||||
'bo.meats.edit-submeats-order'),
|
||||
|
|
@ -705,7 +714,8 @@
|
|||
<ul class="dropdown-menu">
|
||||
{{-- PRODUCT PAGE --}}
|
||||
<li>
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $productStaticPageModel) }}"
|
||||
<a data-bs-toggle="tooltip"
|
||||
href="{{ route('bo.static_pages.edit', $productStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $productStaticPageModel->type->label()]) }}"
|
||||
@class([
|
||||
'dropdown-item px-3 py-2',
|
||||
|
|
@ -821,10 +831,11 @@
|
|||
</ul>
|
||||
</div>
|
||||
{{-- BREEDING --}}
|
||||
@if(isset($breedingPoultryStaticPageModel) || isset($breedingJobStaticPageModel) ||
|
||||
isset($breedingFoodStaticPageModel) || isset($breedingHealthStaticPageModel) ||
|
||||
isset($ourFarmsStaticPageModel)
|
||||
)
|
||||
@if (isset($breedingPoultryStaticPageModel) ||
|
||||
isset($breedingJobStaticPageModel) ||
|
||||
isset($breedingFoodStaticPageModel) ||
|
||||
isset($breedingHealthStaticPageModel) ||
|
||||
isset($ourFarmsStaticPageModel))
|
||||
<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',
|
||||
|
|
@ -875,8 +886,8 @@
|
|||
$breedingHealthStaticPageModel->type->value()) ||
|
||||
url()->current() ===
|
||||
route(
|
||||
'bo.static_pages.edit',
|
||||
$ourFarmsStaticPageModel->type->value()) ||
|
||||
'bo.static_pages.edit',
|
||||
$ourFarmsStaticPageModel->type->value()) ||
|
||||
// FIXME: uncomment when breeder map feature is ready
|
||||
// || url()->current() === route('bo.general_settings.edit', \App\Enums\Settings\SettingsType::breederMap->value())
|
||||
request()->routeIs('bo.news_breedings.*') ||
|
||||
|
|
@ -948,7 +959,8 @@
|
|||
title="{{ __('crud.helpers.update_model', ['model' => trans_choice('validation.custom.breeding_section_card', \INF)]) }}"
|
||||
@class([
|
||||
'dropdown-item px-3 py-2',
|
||||
'fw-bold text-bg-secondary' => request()->routeIs('bo.static_page.sections*'),
|
||||
'fw-bold text-bg-secondary' => request()->routeIs(
|
||||
'bo.static_page.sections*'),
|
||||
])>
|
||||
<i @class([
|
||||
'fa-solid fa-tag fa-fw',
|
||||
|
|
@ -961,7 +973,7 @@
|
|||
</div>
|
||||
@endif
|
||||
{{-- ACTUALITES --}}
|
||||
@if(isset($newsStaticPageModel))
|
||||
@if (isset($newsStaticPageModel))
|
||||
<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',
|
||||
|
|
@ -988,12 +1000,13 @@
|
|||
<ul class="dropdown-menu">
|
||||
{{-- ACTUS PAGE STATIC --}}
|
||||
<li>
|
||||
<a href="{{ route('bo.static_pages.edit', $newsStaticPageModel) }}" @class([
|
||||
'dropdown-item px-3 py-2',
|
||||
'fw-bold text-bg-secondary' =>
|
||||
url()->current() ===
|
||||
route('bo.static_pages.edit', $newsStaticPageModel->type->value()),
|
||||
])>
|
||||
<a href="{{ route('bo.static_pages.edit', $newsStaticPageModel) }}"
|
||||
@class([
|
||||
'dropdown-item px-3 py-2',
|
||||
'fw-bold text-bg-secondary' =>
|
||||
url()->current() ===
|
||||
route('bo.static_pages.edit', $newsStaticPageModel->type->value()),
|
||||
])>
|
||||
<i @class([
|
||||
'fa-solid fa-file fa-fw',
|
||||
'fa-lg' =>
|
||||
|
|
@ -1035,8 +1048,8 @@
|
|||
{{-- RECIPE PAGE --}}
|
||||
@if (isset($searchStaticPageModel))
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $searchStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $searchStaticPageModel->type->label()]) }}" role="button"
|
||||
@class([
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $searchStaticPageModel->type->label()]) }}"
|
||||
role="button" @class([
|
||||
'btn btn-primary w-100 text-start mt-1',
|
||||
'fw-bold active' =>
|
||||
url()->current() ===
|
||||
|
|
@ -1164,13 +1177,11 @@
|
|||
<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.*'),
|
||||
'fw active' => request()->routeIs('bo.approaches.*'),
|
||||
])>
|
||||
<i @class([
|
||||
'fa-solid fa-people-arrows fa-fw',
|
||||
'fa-lg' =>
|
||||
request()->routeIs('bo.approaches.*'),
|
||||
'fa-lg' => request()->routeIs('bo.approaches.*'),
|
||||
])></i>
|
||||
{{ __('Démarches') }}
|
||||
</button>
|
||||
|
|
@ -1178,12 +1189,13 @@
|
|||
{{-- 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),
|
||||
])>
|
||||
<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' =>
|
||||
|
|
@ -1197,13 +1209,13 @@
|
|||
{{-- 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),
|
||||
|
||||
])>
|
||||
<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' =>
|
||||
|
|
@ -1219,7 +1231,8 @@
|
|||
<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.*'),
|
||||
'fw-bold text-bg-secondary' => request()->routeIs(
|
||||
'bo.sections.approaches.*'),
|
||||
])>
|
||||
<i @class([
|
||||
'fa-solid fa-tags fa-fw',
|
||||
|
|
@ -1231,7 +1244,8 @@
|
|||
<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.*'),
|
||||
'fw-bold text-bg-secondary' => request()->routeIs(
|
||||
'bo.categories.approaches.*'),
|
||||
])>
|
||||
<i @class([
|
||||
'fa-solid fa-tags fa-fw',
|
||||
|
|
@ -1242,11 +1256,65 @@
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{{-- COMMITMENTS --}}
|
||||
<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.static_pages.*') ||
|
||||
request()->routeIs('bo.static_page.commitmments.sections.*'),
|
||||
])>
|
||||
<i @class([
|
||||
'fa-solid fa-handshake fa-fw',
|
||||
'fa-lg' => request()->routeIs('bo.static_pages.*'),
|
||||
])></i>
|
||||
{{ __('Nos engagement') }}
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{{-- PAGE RESPECTER LA TERRE --}}
|
||||
@if (isset($respecttheEarthStaticPageModel))
|
||||
<li>
|
||||
<a href="{{ route('bo.static_pages.edit', $respecttheEarthStaticPageModel) }}"
|
||||
@class([
|
||||
'dropdown-item px-3 py-2',
|
||||
'fw-bold text-bg-secondary' =>
|
||||
url()->current() ===
|
||||
route('bo.static_pages.edit', $respecttheEarthStaticPageModel),
|
||||
])>
|
||||
<i @class([
|
||||
'fa-solid fa-file fa-fw',
|
||||
'fa-lg' =>
|
||||
url()->current() ===
|
||||
route('bo.static_pages.edit', $respecttheEarthStaticPageModel),
|
||||
])></i>
|
||||
{{ str(__('Page Respecter la terre'))->ucFirst() }}
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_page.commitmments.sections.edit') }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => trans_choice('validation.custom.common_section_part', \INF)]) }}"
|
||||
@class([
|
||||
'dropdown-item px-3 py-2',
|
||||
'fw-bold text-bg-secondary' => request()->routeIs(
|
||||
'bo.static_page.*'),
|
||||
])>
|
||||
<i @class([
|
||||
'fa-solid fa-tag fa-fw',
|
||||
'fa-lg' => request()->routeIs('bo.static_page.*'),
|
||||
])></i>
|
||||
{{ str(trans_choice('validation.custom.common_section_part', \INF))->ucFirst() }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{{-- TDF PAGE --}}
|
||||
@if (isset($tdfStaticPageModel))
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $tdfStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $tdfStaticPageModel->type->label()]) }}" role="button"
|
||||
@class([
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $tdfStaticPageModel->type->label()]) }}"
|
||||
role="button" @class([
|
||||
'btn btn-primary w-100 text-start mt-1',
|
||||
'fw-bold active' =>
|
||||
url()->current() ===
|
||||
|
|
@ -1263,10 +1331,10 @@
|
|||
@endif
|
||||
<hr>
|
||||
{{-- CONTACT --}}
|
||||
@if(isset($contactStaticPageModel))
|
||||
@if (isset($contactStaticPageModel))
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $contactStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $contactStaticPageModel->type->label()]) }}" role="button"
|
||||
@class([
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $contactStaticPageModel->type->label()]) }}"
|
||||
role="button" @class([
|
||||
'btn btn-primary w-100 text-start',
|
||||
'fw-bold active' =>
|
||||
url()->current() ===
|
||||
|
|
@ -1282,10 +1350,10 @@
|
|||
</a>
|
||||
@endif
|
||||
{{-- LEGALS CGU --}}
|
||||
@if(isset($legalsStaticPageModel))
|
||||
@if (isset($legalsStaticPageModel))
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $legalsStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $legalsStaticPageModel->type->label()]) }}" role="button"
|
||||
@class([
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $legalsStaticPageModel->type->label()]) }}"
|
||||
role="button" @class([
|
||||
'btn btn-primary w-100 text-start mt-1',
|
||||
'fw-bold active' =>
|
||||
url()->current() ===
|
||||
|
|
@ -1301,10 +1369,10 @@
|
|||
</a>
|
||||
@endif
|
||||
{{-- CONFIDENTIALITY --}}
|
||||
@if(isset($confidentialityStaticPageModel))
|
||||
@if (isset($confidentialityStaticPageModel))
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $confidentialityStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $confidentialityStaticPageModel->type->label()]) }}" role="button"
|
||||
@class([
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $confidentialityStaticPageModel->type->label()]) }}"
|
||||
role="button" @class([
|
||||
'btn btn-primary w-100 text-start mt-1',
|
||||
'fw-bold active' =>
|
||||
url()->current() ===
|
||||
|
|
@ -1324,10 +1392,10 @@
|
|||
</a>
|
||||
@endif
|
||||
{{-- TERMS AND CONDITIONS --}}
|
||||
@if(isset($sellingStaticPageModel))
|
||||
@if (isset($sellingStaticPageModel))
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $sellingStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $sellingStaticPageModel->type->label()]) }}" role="button"
|
||||
@class([
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $sellingStaticPageModel->type->label()]) }}"
|
||||
role="button" @class([
|
||||
'btn btn-primary w-100 text-start mt-1',
|
||||
'fw-bold active' =>
|
||||
url()->current() ===
|
||||
|
|
@ -1343,10 +1411,10 @@
|
|||
</a>
|
||||
@endif
|
||||
{{-- ACCESSIBILITY --}}
|
||||
@if(isset($accessibilityStaticPageModel))
|
||||
@if (isset($accessibilityStaticPageModel))
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $accessibilityStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $accessibilityStaticPageModel->type->label()]) }}" role="button"
|
||||
@class([
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $accessibilityStaticPageModel->type->label()]) }}"
|
||||
role="button" @class([
|
||||
'btn btn-primary w-100 text-start mt-1',
|
||||
'fw-bold active' =>
|
||||
url()->current() ===
|
||||
|
|
@ -1368,22 +1436,18 @@
|
|||
{{-- SITEMAP --}}
|
||||
@if (isset($sitemapStaticPageModel))
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $sitemapStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $sitemapStaticPageModel->type->label()]) }}" role="button"
|
||||
@class([
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $sitemapStaticPageModel->type->label()]) }}"
|
||||
role="button" @class([
|
||||
'btn btn-primary w-100 text-start mt-1',
|
||||
'fw-bold active' =>
|
||||
url()->current() ===
|
||||
route(
|
||||
'bo.static_pages.edit',
|
||||
$sitemapStaticPageModel->type->value()),
|
||||
route('bo.static_pages.edit', $sitemapStaticPageModel->type->value()),
|
||||
])>
|
||||
<i @class([
|
||||
'fa-solid fa-file fa-fw',
|
||||
'fa-lg' =>
|
||||
url()->current() ===
|
||||
route(
|
||||
'bo.static_pages.edit',
|
||||
$sitemapStaticPageModel->type->value()),
|
||||
route('bo.static_pages.edit', $sitemapStaticPageModel->type->value()),
|
||||
])></i>
|
||||
{{ $sitemapStaticPageModel->type->label() }}
|
||||
</a>
|
||||
|
|
@ -1391,22 +1455,18 @@
|
|||
{{-- RULES --}}
|
||||
@if (isset($rulesStaticPageModel))
|
||||
<a data-bs-toggle="tooltip" href="{{ route('bo.static_pages.edit', $rulesStaticPageModel) }}"
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $rulesStaticPageModel->type->label()]) }}" role="button"
|
||||
@class([
|
||||
title="{{ __('crud.helpers.update_model', ['model' => $rulesStaticPageModel->type->label()]) }}"
|
||||
role="button" @class([
|
||||
'btn btn-primary w-100 text-start mt-1',
|
||||
'fw-bold active' =>
|
||||
url()->current() ===
|
||||
route(
|
||||
'bo.static_pages.edit',
|
||||
$rulesStaticPageModel->type->value()),
|
||||
route('bo.static_pages.edit', $rulesStaticPageModel->type->value()),
|
||||
])>
|
||||
<i @class([
|
||||
'fa-solid fa-file fa-fw',
|
||||
'fa-lg' =>
|
||||
url()->current() ===
|
||||
route(
|
||||
'bo.static_pages.edit',
|
||||
$rulesStaticPageModel->type->value()),
|
||||
route('bo.static_pages.edit', $rulesStaticPageModel->type->value()),
|
||||
])></i>
|
||||
{{ $rulesStaticPageModel->type->label() }}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,49 @@
|
|||
<td class="text-center align-middle">
|
||||
@can('changeOrder', $model)
|
||||
<div class="d-flex justify-content-center align-items-center">
|
||||
<form @class(['invisible' => $loop->first and $models->onFirstPage()])
|
||||
<form data-change-order @class([
|
||||
'invisible' =>
|
||||
$loop->first and
|
||||
(method_exists($models, 'onFirstPage')
|
||||
? $models->onFirstPage()
|
||||
: $loop->first),
|
||||
])
|
||||
action="{{ route('bo.' . $routeName . '.change-order', [str($routeName)->singular()->value => $model, 'direction' => 'up']) }}"
|
||||
method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<button data-bs-toggle="tooltip" type="submit" title="{{ __('crud.other.up') }}" @class([
|
||||
'btn btn-sm btn-outline-secondary',
|
||||
'disabled' => $loop->first and $models->onFirstPage(),
|
||||
'disabled' =>
|
||||
$loop->first and
|
||||
(method_exists($models, 'onFirstPage')
|
||||
? $models->onFirstPage()
|
||||
: $loop->first),
|
||||
])>
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
</button>
|
||||
</form>
|
||||
<form @class([
|
||||
|
||||
<form data-change-order @class([
|
||||
'invisible' =>
|
||||
$loop->last and $models->currentPage() === $models->lastPage(),
|
||||
$loop->last and
|
||||
(method_exists($models, 'lastPage')
|
||||
? $models->currentPage() === $models->lastPage()
|
||||
: $loop->last),
|
||||
])
|
||||
action="{{ route('bo.' . $routeName . '.change-order', [str($routeName)->singular()->value => $model, 'direction' => 'down']) }}"
|
||||
method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<button data-bs-toggle="tooltip" type="submit" title="{{ __('crud.other.down') }}" @class([
|
||||
'btn btn-sm btn-outline-secondary ms-1',
|
||||
'disabled' =>
|
||||
$loop->last and $models->currentPage() === $models->lastPage(),
|
||||
])>
|
||||
<button data-bs-toggle="tooltip" type="submit" title="{{ __('crud.other.down') }}"
|
||||
@class([
|
||||
'btn btn-sm btn-outline-secondary ms-1',
|
||||
'disabled' =>
|
||||
$loop->last and
|
||||
(method_exists($models, 'lastPage')
|
||||
? $models->currentPage() === $models->lastPage()
|
||||
: $loop->last),
|
||||
])>
|
||||
<i class="fas fa-arrow-down"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@
|
|||
{{-- PICTURE_ALT --}}
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => str($input['label'])->slug() . '-alt',
|
||||
'id' => ($idPrefix ?? '') . '-' . str($input['label'])->slug() . '-alt',
|
||||
'label' => __('crud.minicrud.picture_alt_label'),
|
||||
'hasWordCounter' => true,
|
||||
'name' => 'data[' . $k . '][alt]',
|
||||
|
|
@ -146,7 +146,7 @@
|
|||
{{-- PICTURE_TITLE --}}
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => str($input['label'])->slug() . '-title',
|
||||
'id' => ($idPrefix ?? '') . '-' . str($input['label'])->slug() . '-title',
|
||||
'label' => __('crud.minicrud.picture_title_label'),
|
||||
'hasWordCounter' => true,
|
||||
'name' => 'data[' . $k . '][title]',
|
||||
|
|
@ -168,7 +168,7 @@
|
|||
{{-- PICTURE --}}
|
||||
<div class="col-12 mb-3">
|
||||
@include('bo.partials.inputs.varchar-picture', [
|
||||
'id' => str($input['label'])->slug(),
|
||||
'id' => ($idPrefix ?? '') . '-' . str($input['label'])->slug(),
|
||||
'name' => "data[{$k}][value]",
|
||||
'width' => $input['dimensions']['width'],
|
||||
'height' => $input['dimensions']['height'],
|
||||
|
|
@ -253,7 +253,7 @@
|
|||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
@include('bo.partials.inputs.media-selector', [
|
||||
'id' => Str::camel($k),
|
||||
'id' => ($idPrefix ?? '') . Str::studly($k),
|
||||
'name' => $k,
|
||||
'value' => old("data.{$k}.value", $input['value'] ?? null),
|
||||
'required' => isset($input['required']) ? !empty($input['required']) : true,
|
||||
|
|
|
|||
|
|
@ -78,6 +78,12 @@
|
|||
title="{{ __('crud.other.sort_delete') }}">
|
||||
<i class="fa fa-eraser"></i>
|
||||
</a>
|
||||
@elseif (isset($ajaxSort))
|
||||
<a class="picto-sort-reset btn btn-sm btn-danger" data-bs-toggle="tooltip"
|
||||
href="{{ request()->fullUrlWithQuery(['sort_col' => null, 'sort_way' => null, 'rst' => true, 'search' => null]) }}"
|
||||
title="{{ __('crud.other.sort_delete') }}">
|
||||
<i class="fa fa-eraser"></i>
|
||||
</a>
|
||||
@else
|
||||
<span data-bs-toggle="tooltip" title="{{ __('crud.other.sort_arrow') }}">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
|
|
|
|||
24
resources/views/bo/pages/pictos/create.blade.php
Normal file
24
resources/views/bo/pages/pictos/create.blade.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
@php
|
||||
$condition = trans_choice('models.classes.picto', 1);
|
||||
|
||||
@endphp
|
||||
|
||||
<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">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash-container"></div>
|
||||
<form class="picto-form" action="{{ route('bo.pictos.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<input type="hidden" name="section_id" value="{{ $section->id }}">
|
||||
@include('bo.pages.pictos.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>
|
||||
25
resources/views/bo/pages/pictos/edit.blade.php
Normal file
25
resources/views/bo/pages/pictos/edit.blade.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
@php
|
||||
$condition = trans_choice('models.classes.picto', 1);
|
||||
|
||||
@endphp
|
||||
|
||||
<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">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash-container"></div>
|
||||
<form class="picto-form" action="{{ route('bo.pictos.update', $pictoModel) }}" method="POST" enctype="multipart/form-data" data-id="{{ $pictoModel->id }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="section_id" value="{{ $section->id }}">
|
||||
@include('bo.pages.pictos.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>
|
||||
176
resources/views/bo/pages/pictos/form.blade.php
Normal file
176
resources/views/bo/pages/pictos/form.blade.php
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
@php
|
||||
$inter = __('validation.custom.of_the.male');
|
||||
$model = __(trans_choice('models.classes.picto', 1));
|
||||
$idPrefix = $idPrefix ?? 'picto';
|
||||
@endphp
|
||||
<div class="row">
|
||||
{{-- TITLE --}}
|
||||
<div class="col-12 col-lg-6 mb-3">
|
||||
<fieldset class="h-100 border p-3">
|
||||
<legend>{{ str(__('validation.attributes.title'))->ucfirst() }}</legend>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => $idPrefix . '-pictoName',
|
||||
'label' => str(
|
||||
__(':field :inter:model', [
|
||||
'field' => __('validation.attributes.title'),
|
||||
'inter' => $inter,
|
||||
'model' => $model,
|
||||
]))->ucfirst(),
|
||||
'hasWordCounter' => true,
|
||||
'name' => 'title',
|
||||
'required' => true,
|
||||
'model' => $pictoModel ?? 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.description'))->ucfirst() }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
@include('bo.partials.inputs.text-html', [
|
||||
'id' => $idPrefix . '-pictoContent',
|
||||
'label' => __(':field :inter:model', [
|
||||
'field' => str(__('validation.attributes.description'))->ucfirst(),
|
||||
'inter' => __('validation.custom.of_the.male'),
|
||||
'model' => $model,
|
||||
]),
|
||||
'name' => 'description',
|
||||
'editorConfig' => \App\Enums\CkEditorConfigName::basic->label(),
|
||||
'model' => $pictoModel ?? 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' => $idPrefix . '-pictoPictureAlt',
|
||||
'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' => $pictoModel ?? 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' => $idPrefix . '-pictoPictureTitle',
|
||||
'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' => $pictoModel ?? 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>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-6 col-lg-4">
|
||||
@include('bo.partials.inputs.int-number', [
|
||||
'id' => $idPrefix . '-pictoWidth',
|
||||
'name' => 'picture_width',
|
||||
'model' => $pictoModel ?? null,
|
||||
// 'label' => str(__('validation.custom.width'))->ucFirst(),
|
||||
'helper' => __('Largeur'),
|
||||
])
|
||||
</div>
|
||||
<div class="col-6 col-lg-4">
|
||||
@include('bo.partials.inputs.int-number', [
|
||||
'id' => $idPrefix . '-pictoHeight',
|
||||
'name' => 'picture_height',
|
||||
'model' => $pictoModel ?? null,
|
||||
// 'label' => str(__('validation.custom.height'))->ucFirst(),
|
||||
'helper' => __('Hauteur'),
|
||||
])
|
||||
</div>
|
||||
<div class="col-6 col-lg-4">
|
||||
<button class="btn btn-primary submit-picture-size"
|
||||
id="{{ $idPrefix }}-submitPictureSize"
|
||||
data-url="{{ route('bo.pictos.savePictureSize') }}"
|
||||
data-target-image="{{ $idPrefix }}-pictoPicture"
|
||||
data-width-input="{{ $idPrefix }}-pictoWidth"
|
||||
data-height-input="{{ $idPrefix }}-pictoHeight" data-bs-toggle="tooltip"
|
||||
data-picto-id="{{ $pictoModel?->id }}"
|
||||
type="button" title="{{ __('save size') }}">
|
||||
{{ str(__('sauvegarder taille'))->ucfirst() }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{-- PICTURE --}}
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
@include('bo.partials.inputs.varchar-picture', [
|
||||
'id' => $idPrefix . '-pictoPicture',
|
||||
'name' => 'picture',
|
||||
'width' => $pictoModel?->picture_width ?? App\Models\Picto::MIN_WIDTH,
|
||||
'height' => $pictoModel?->picture_height ?? App\Models\Picto::MIN_HEIGHT,
|
||||
'model' => $pictoModel ?? null,
|
||||
'required' => true,
|
||||
'preview' => true,
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="col-12 text-center">
|
||||
<p><b>{{ __('* Champs obligatoires') }}</b></p>
|
||||
</div>
|
||||
</div>
|
||||
76
resources/views/bo/pages/pictos/index-list.blade.php
Normal file
76
resources/views/bo/pages/pictos/index-list.blade.php
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<table id="pictos-table" class="table-hover table-fix-action m-0 table">
|
||||
@if (count($pictos) > 0)
|
||||
<thead>
|
||||
@include('bo.modules.table-col-sorter', [
|
||||
'cols' => [
|
||||
'name' => str(__('validation.attributes.title'))->ucfirst(),
|
||||
'description' => str(__('validation.attributes.description'))->ucfirst(),
|
||||
'picture' => str(__('validation.custom.picture'))->ucfirst(),
|
||||
'updated_at' => str(__('validation.attributes.updated_at'))->ucfirst(),
|
||||
'order' => str(__('validation.custom.order'))->ucfirst(),
|
||||
],
|
||||
'ajaxSort' => true,
|
||||
])
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($pictos as $picto)
|
||||
<tr class="border-bottom">
|
||||
<td class="text-center align-middle">{{ $picto->title }}</td>
|
||||
<td class="text-center align-middle">{!! clean($picto->description) !!}</td>
|
||||
|
||||
<td class="text-center align-middle">
|
||||
<img class="image-preview" src="{{ asset($picto->picture) }}" title="{{ $picto->picture_title }}"
|
||||
alt="{{ $picto->picture_alt }}">
|
||||
</td>
|
||||
<td class="text-center align-middle">
|
||||
<span class="badge text-bg-secondary">{{ $picto->updated_at->isoFormat('LLLL') }}</span>
|
||||
</td>
|
||||
@include('bo.modules.change-model-order', [
|
||||
'routeName' => 'pictos',
|
||||
'model' => $picto,
|
||||
'models' => $pictos,
|
||||
])
|
||||
<td class="text-end align-middle">
|
||||
@canAny(['duplicate', 'update', 'delete'], $picto)
|
||||
<form class="btn-group confirmDeleteTS" action="{{ route('bo.pictos.destroy', $picto) }}"
|
||||
method="POST" novalidate data-picto="{{ $picto->id }}"
|
||||
data-section="{{ $picto->section_id }}">
|
||||
@can('update', $picto)
|
||||
<a class="btn btn-sm btn-primary btn-edit-picto" data-bs-toggle="tooltip" href="#"
|
||||
data-picto="{{ $picto->id }}" data-section="{{ $picto->section_id }}"
|
||||
title="{{ str(__('crud.actions.update'))->ucfirst() }}">
|
||||
<i class="fa-solid fa-pencil"></i>
|
||||
</a>
|
||||
@endcan
|
||||
{{-- @can('duplicate', $picto)
|
||||
<a class="btn btn-sm btn-secondary" data-bs-toggle="tooltip"
|
||||
href="{{ route('bo.pictos.duplicate', $picto) }}"
|
||||
title="{{ str(__('crud.actions.duplicate'))->ucfirst() }}">
|
||||
<i class="fa-solid fa-copy"></i>
|
||||
</a>
|
||||
@endcan --}}
|
||||
@can('delete', $picto)
|
||||
@csrf
|
||||
<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.picto', 2)]) }}
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
</table>
|
||||
28
resources/views/bo/pages/pictos/index.blade.php
Normal file
28
resources/views/bo/pages/pictos/index.blade.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
@php
|
||||
$currentCount = $section->pictos->count();
|
||||
@endphp
|
||||
@can('create', \App\Models\Picto::class)
|
||||
<a class="btn btn-primary float-right btn-create-picto"
|
||||
data-section="{{ $section->id }}" href="#" @disabled($currentCount >= $maxPictos)>
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
<div class="picto-list table-responsive mb-3" data-section="{{ $section->id }}">
|
||||
<div class="flash-container"></div>
|
||||
<div class="picto-table-wrapper">
|
||||
@include('bo.pages.pictos.index-list', ['pictos' => $section->pictos])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="picto-form-wrapper data-section" data-section="{{ $section->id }}" style="display:none;">
|
||||
<button type="button" class="btn btn-primary btn-back-picto"
|
||||
data-section="{{ $section->id }}">
|
||||
<i class="fa-solid fa-arrow-left"></i>
|
||||
</button>
|
||||
@include('bo.pages.pictos.create', [
|
||||
'pictoModel' => new \App\Models\Picto(),
|
||||
'section' => $section,
|
||||
'idPrefix' => 'picto-' . $section->id,
|
||||
])
|
||||
</div>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
@extends('bo.layout', ['brParam' => (object) $commonSection])
|
||||
|
||||
@section('title', __('Édition section commune d\'une :model', ['model' => trans_choice('models.classes.static_page', 1)]))
|
||||
@section('description', __('Édition de la séction commune dans les :model déjà existant', ['model' => trans_choice('models.classes.static_page', 2)]))
|
||||
@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">
|
||||
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => (object) $commonSection])
|
||||
</div>
|
||||
<div class="mb-md-0 mb-2">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" id="formSubmitClone" data-bs-toggle="tooltip" type="submit"
|
||||
title="{{ __('crud.helpers.save_model', ['model' => trans_choice('models.classes.static_page', 1)]) }}">
|
||||
{{ str(__('crud.actions.save'))->ucfirst() }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form action="{{ route('bo.static_page.commitmments.sections.update') }}" method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
@include('bo.pages.static_pages.common_section.form')
|
||||
@include('bo.partials.script-button-clone')
|
||||
<div class="row">
|
||||
<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.news_breeding', 1)]) }}">
|
||||
{{ str(__('crud.actions.save'))->ucfirst() }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
|
|
@ -32,4 +32,35 @@
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{{-- Sections & block --}}
|
||||
@if($isCommitmentPage)
|
||||
@include('bo.pages.static_pages.sections.form_section', [
|
||||
'sections' => $staticPageModel->sections
|
||||
])
|
||||
@endif
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const sectionId = params.get('section');
|
||||
const tab = params.get('tab') ?? 't3';
|
||||
|
||||
if (!sectionId) return;
|
||||
|
||||
const collapseEl = document.getElementById('collapse-' + sectionId);
|
||||
if (!collapseEl) return;
|
||||
|
||||
// Ouvrir l'accordéon
|
||||
const bsCollapse = new bootstrap.Collapse(collapseEl, { toggle: false });
|
||||
bsCollapse.show();
|
||||
|
||||
// Activer le tab une fois l'accordéon ouvert
|
||||
collapseEl.addEventListener('shown.bs.collapse', function () {
|
||||
const tabTrigger = document.querySelector(`[data-bs-target="#s${sectionId}-${tab}"]`);
|
||||
if (tabTrigger) bootstrap.Tab.getOrCreateInstance(tabTrigger).show();
|
||||
}, { once: true });
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -121,7 +121,8 @@
|
|||
!$staticPageModel->type->equals(StaticPageType::selling) &&
|
||||
!$staticPageModel->type->equals(StaticPageType::ourFarms) &&
|
||||
!$staticPageModel->type->equals(StaticPageType::exitOutside) &&
|
||||
!$staticPageModel->type->equals(StaticPageType::ouiCBon))
|
||||
!$staticPageModel->type->equals(StaticPageType::ouiCBon) &&
|
||||
!$staticPageModel->type->equals(StaticPageType::respectTheEarth))
|
||||
{{-- SUBTITLE --}}
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
<fieldset class="h-100 border p-3">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
@use('\App\Enums\StaticPageType', 'StaticPageType')
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="accordion" id="mainAccordion">
|
||||
|
||||
@foreach ($sections as $index => $section)
|
||||
@php
|
||||
$collapseId = 'collapse-' . $section->id;
|
||||
$toggleId = 'toggle-' . $section->id;
|
||||
$isVisible = $section->visibility ?? true;
|
||||
|
||||
$sectionData = json_decode(json_encode($section->data), true) ?? [];
|
||||
$hasPictos = !empty($sectionData['has_pictos']);
|
||||
$hasBlocks = $section->blocks->isNotEmpty();
|
||||
$maxPictos = $sectionData['max_pictos'] ?? 5;
|
||||
@endphp
|
||||
|
||||
<div class="accordion-item border rounded-3 mb-3 shadow-sm">
|
||||
|
||||
{{-- Header --}}
|
||||
<div class="accordion-header d-flex align-items-center px-3 py-2">
|
||||
<button class="accordion-button collapsed flex-grow-1 border-0 bg-transparent shadow-none px-0 py-1"
|
||||
type="button" data-bs-toggle="collapse" data-bs-target="#{{ $collapseId }}"
|
||||
aria-expanded="false" aria-controls="{{ $collapseId }}">
|
||||
<span class="btn btn-outline-primary ms-2">{{ $section->name }}</span>
|
||||
</button>
|
||||
|
||||
<div class="d-flex align-items-center gap-2 ms-3 flex-shrink-0">
|
||||
<div class="section-flash" id="flash-{{ $section->id }}"></div>
|
||||
<small class="toggle-label text-muted">Visible</small>
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input class="form-check-input section-toggle" type="checkbox" role="switch"
|
||||
id="{{ $toggleId }}" data-target="#{{ $collapseId }}"
|
||||
data-id="{{ $section->id }}" {{ $isVisible ? 'checked' : '' }}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Body + Tabs --}}
|
||||
<div id="{{ $collapseId }}" class="accordion-collapse collapse" data-bs-parent="#mainAccordion">
|
||||
<div class="accordion-body">
|
||||
<ul class="nav nav-tabs mb-3" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" data-bs-toggle="tab"
|
||||
data-bs-target="#s{{ $section->id }}-t1" type="button" role="tab">
|
||||
<i class="bi bi-eye me-1"></i> Générale
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@if ($hasBlocks)
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab"
|
||||
data-bs-target="#s{{ $section->id }}-t2" type="button" role="tab">
|
||||
<i class="bi bi-bar-chart me-1"></i> Redirection
|
||||
</button>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if ($hasPictos)
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab"
|
||||
data-bs-target="#s{{ $section->id }}-t3" type="button" role="tab">
|
||||
<i class="bi bi-gear me-1"></i> Pictogrammes
|
||||
</button>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
|
||||
<div class="tab-content mx-4 my-4">
|
||||
|
||||
{{-- Tab --}}
|
||||
<div class="tab-pane fade show active" id="s{{ $section->id }}-t1" role="tabpanel">
|
||||
<form class="section-form" data-id="{{ $section->id }}" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="form_section" value="section_{{ $section->id }}">
|
||||
@include('bo.modules.minicrud-form-inputs', [
|
||||
'data' => $sectionData,
|
||||
'model' => $staticPageModel,
|
||||
'idPrefix' => 's' . $section->id,
|
||||
])
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ str(__('crud.actions.save'))->ucfirst() }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- Tab Redirection --}}
|
||||
@if ($hasBlocks)
|
||||
<div class="tab-pane fade" id="s{{ $section->id }}-t2" role="tabpanel">
|
||||
@foreach ($section->blocks as $blockIndex => $block)
|
||||
<form class="block-form" data-id="{{ $block->id }}" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="form_block" value="block_{{ $block->id }}">
|
||||
@include('bo.partials.blocks.redirection-block-form', [
|
||||
'data' => json_decode(json_encode($block->data), true),
|
||||
'model' => $staticPageModel,
|
||||
'idPrefix' => 's' . $section->id . '-b' . $block->id,
|
||||
])
|
||||
<div class="row mt-4">
|
||||
<div class="col text-center">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ str(__('crud.actions.save'))->ucfirst() }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@if (!$loop->last)
|
||||
<hr class="my-4">
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Tab Pictogrammes --}}
|
||||
@if ($hasPictos)
|
||||
<div class="tab-pane fade" id="s{{ $section->id }}-t3" role="tabpanel">
|
||||
@include('bo.pages.pictos.index', [
|
||||
'section' => $section,
|
||||
'idPrefix' => 's' . $section->id,
|
||||
'maxPictos' => $maxPictos,
|
||||
])
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
@php
|
||||
use \App\Enums\Block\BlockPositionEnum;
|
||||
use \App\Enums\Block\BlockContentTypeEnum;
|
||||
|
||||
@endphp
|
||||
{{-- Titre --}}
|
||||
<div class="mb-3">
|
||||
<fieldset class="border p-3">
|
||||
<legend>{{ __('Titre') }}</legend>
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => $idPrefix . '-title',
|
||||
'label' => __('Titre'),
|
||||
'hasWordCounter' => true,
|
||||
'name' => 'data[title][value]',
|
||||
'value' => old('data.title.value', $data['title']['value'] ?? null),
|
||||
'required' => false,
|
||||
'model' => $model ?? null,
|
||||
'helper' => __('validation.between.string', ['attribute' => __('Titre'), 'min' => 3, 'max' => 150]),
|
||||
])
|
||||
@include('modules.input-error', ['inputName' => 'data.title.value'])
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
{{-- Position --}}
|
||||
<div class="mb-3">
|
||||
<fieldset class="border p-3">
|
||||
<legend>{{ __('Position') }} *</legend>
|
||||
<select class="form-select" name="data[position][value]" required>
|
||||
<option value="" {{ ($data['position']['value'] ?? '') === '' ? 'selected' : '' }}>
|
||||
{{ __('-- Choisissez une position --') }}
|
||||
</option>
|
||||
@foreach (BlockPositionEnum::cases() as $case)
|
||||
<option value="{{ $case->value }}"
|
||||
{{ ($data['position']['value'] ?? '') === $case->value ? 'selected' : '' }}>
|
||||
{{ __($case->label()) }}
|
||||
</option>
|
||||
@endforeach
|
||||
|
||||
</select>
|
||||
<small class="form-text text-muted">{{ __('Choisissez la position du bloc sur la page.') }}</small>
|
||||
@include('modules.input-error', ['inputName' => 'data.position.value'])
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
{{-- Type de contenu --}}
|
||||
<div class="mb-3">
|
||||
<fieldset class="border p-3">
|
||||
<legend>{{ __('Type de contenu') }} *</legend>
|
||||
<select class="form-select" name="data[block_type][value]" id="{{ $idPrefix }}-block-type" required>
|
||||
@foreach (BlockContentTypeEnum::cases() as $case)
|
||||
<option value="{{ $case->value }}"
|
||||
{{ ($data['block_type']['value'] ?? '') === $case->value ? 'selected' : '' }}>
|
||||
{{ __($case->label()) }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<small
|
||||
class="form-text text-muted">{{ __('Sélectionnez le type de contenu à afficher dans ce bloc.') }}</small>
|
||||
@include('modules.input-error', ['inputName' => 'data.block_type.value'])
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
{{-- Champs CTA --}}
|
||||
<div id="{{ $idPrefix }}-fields-cta" class="row">
|
||||
@php
|
||||
$ctaFields = [
|
||||
'cta_url' => $data['cta_url'] ?? ['label' => 'URL du bouton', 'value' => null, 'required' => false],
|
||||
'cta_label' => $data['cta_label'] ?? ['label' => 'Nom du CTA', 'value' => null, 'required' => false],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@foreach ($ctaFields as $k => $input)
|
||||
<div class="minicrud-input col-12 col-md-6 mb-3">
|
||||
<fieldset class="h-100 border p-3">
|
||||
<legend>{{ $input['label'] }} {{ !empty($input['required']) ? '*' : '' }}</legend>
|
||||
<div class="row">
|
||||
<div class="col-12 mb-3">
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => $idPrefix . '-' . str($input['label'])->slug(),
|
||||
'label' => $input['label'],
|
||||
'hasWordCounter' => true,
|
||||
'name' => 'data[' . $k . '][value]',
|
||||
'value' => old("data.{$k}.value", $input['value'] ?? null),
|
||||
'required' => !empty($input['required']),
|
||||
'model' => $model ?? null,
|
||||
'helper' =>
|
||||
$k === 'cta_url'
|
||||
? __('validation.url', ['attribute' => $input['label']])
|
||||
: __('validation.between.string', [
|
||||
'attribute' => $input['label'],
|
||||
'min' => 3,
|
||||
'max' => 100,
|
||||
]),
|
||||
])
|
||||
@include('modules.input-error', ['inputName' => "data.{$k}.value"])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{{-- Champs Actu --}}
|
||||
<div id="{{ $idPrefix }}-fields-actu" class="row d-none">
|
||||
<div class="col-12 mb-3">
|
||||
<fieldset class="h-100 border p-3">
|
||||
<legend>{{ str(trans_choice('models.classes.news', 1))->ucFirst() }}</legend>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('bo.partials.inputs.search-belongs-to-dropdown', [
|
||||
'id' => $idPrefix . '-article-id',
|
||||
'label' => __(':model associé', [
|
||||
'model' => str(trans_choice('models.classes.news', 1))->ucFirst(),
|
||||
]),
|
||||
'name' => 'data[article_id][value]',
|
||||
'relation' => null,
|
||||
'model' => $model ?? null,
|
||||
'targetClass' => '\App\Models\News',
|
||||
'modelListPaginate' => \App\Models\News::where('published',1)->paginate(10),
|
||||
'routeName' => 'bo.news.json-paginate',
|
||||
'fieldName' => 'title',
|
||||
'multiple' => false,
|
||||
'required' => true,
|
||||
'disabled' => false,
|
||||
'selectedIds' =>
|
||||
isset($data['article_id']['value']) && $data['article_id']['value']
|
||||
? [$data['article_id']['value']]
|
||||
: [],
|
||||
'placeholder' => __('Rechercher un :model', [
|
||||
'model' => trans_choice('models.classes.news', 1),
|
||||
]),
|
||||
'helper' => __(
|
||||
'Sélectionner un :model qui sera associé parmi la liste (un seul et unique :model doit être choisi).',
|
||||
['model' => trans_choice('models.classes.news', 1)]),
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,17 +1,24 @@
|
|||
<div id="{{ sprintf("%sSection", $id)}}" class="row">
|
||||
@php
|
||||
$value = $value ?? $model->data[$name]['value'] ?? null;
|
||||
@endphp
|
||||
<div id="{{ sprintf('%sSection', $id) }}" class="row">
|
||||
{{-- BUTTONS SELECTOR --}}
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
<div class="input-group justify-content-center align-items-center h-100">
|
||||
<input class="btn-check" id="mediaSelectorVideo" name="mediaSelector" type="radio" autocomplete="off"
|
||||
@checked(isset($model->data[$name]['value']['video']))>
|
||||
<label class="btn btn-secondary rounded-start" for="mediaSelectorVideo">{{ __('Vidéo') }}</label>
|
||||
<input class="btn-check" id="mediaSelectorImage" name="mediaSelector" type="radio" autocomplete="off"
|
||||
@checked(isset($model->data[$name]['value']['picture']))>
|
||||
<label class="btn btn-secondary" for="mediaSelectorImage">{{ __('Image') }}</label>
|
||||
<input class="btn-check" id="{{ $id }}SelectorVideo" name="mediaSelector_{{ $id }}"
|
||||
type="radio" autocomplete="off" @checked(isset($value['video']))>
|
||||
<label class="btn btn-secondary rounded-start"
|
||||
for="{{ $id }}SelectorVideo">{{ __('Vidéo') }}</label>
|
||||
|
||||
<input class="btn-check" id="{{ $id }}SelectorImage" name="mediaSelector_{{ $id }}"
|
||||
type="radio" autocomplete="off" @checked(isset($value['picture']))>
|
||||
<label class="btn btn-secondary" for="{{ $id }}SelectorImage">{{ __('Image') }}</label>
|
||||
|
||||
@if (!$required)
|
||||
<input class="btn-check" id="mediaSelectorEmpty" name="mediaSelector" type="radio" autocomplete="off"
|
||||
@checked(!$required && empty($model->data[$name]['value']['video']) && empty($model->data[$name]['value']['picture']))>
|
||||
<label class="btn btn-secondary" for="mediaSelectorEmpty">{{ __('Rien') }}</label>
|
||||
<input class="btn-check" id="{{ $id }}SelectorEmpty" name="mediaSelector_{{ $id }}"
|
||||
type="radio" autocomplete="off"
|
||||
@checked(empty($value['video']) && empty($value['picture']))>
|
||||
<label class="btn btn-secondary" for="{{ $id }}SelectorEmpty">{{ __('Rien') }}</label>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -22,77 +29,83 @@
|
|||
{{-- VIDEO --}}
|
||||
<div @class([
|
||||
sprintf('%sVideo', $id) . ' col-12 col-md-6 mb-3',
|
||||
'd-none' => empty($model->data[$name]['value']['video']),
|
||||
'd-none' => empty($value['video']),
|
||||
])>
|
||||
{{-- VIDEO ID --}}
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => sprintf("%sVideoId", $id),
|
||||
'id' => sprintf('%sVideoId', $id),
|
||||
'label' => Str::ucFirst(__('validation.custom.video_id')),
|
||||
'hasWordCounter' => true,
|
||||
'name' => "data[$name][value][video][id]",
|
||||
'required' => true,
|
||||
'disabled' => empty($model->data[$name]['value']['video']),
|
||||
'value' => $model->data[$name]['value']['video']['id'] ?? null,
|
||||
'disabled' => empty($value['video']),
|
||||
'value' => $value['video']['id'] ?? null,
|
||||
'helper' => Str::ucFirst(__('validation.custom.video_id')),
|
||||
'placeholder' => Str::ucFirst(__('validation.custom.video_id')),
|
||||
])
|
||||
</div>
|
||||
|
||||
{{-- VIDEO_ALT --}}
|
||||
<div @class([
|
||||
sprintf('%sVideo', $id) . ' col-12 col-md-6 mb-3',
|
||||
'd-none' => empty($model->data[$name]['value']['video']),
|
||||
'd-none' => empty($value['video']),
|
||||
])>
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => sprintf("%sVideoAlt", $id),
|
||||
'id' => sprintf('%sVideoAlt', $id),
|
||||
'label' => Str::ucFirst(__('validation.custom.video_alt')),
|
||||
'hasWordCounter' => true,
|
||||
'name' => "data[$name][value][video][alt]",
|
||||
'required' => true,
|
||||
'model' => $model ?? null,
|
||||
'disabled' => empty($model->data[$name]['value']['video']['alt']),
|
||||
'value' => $model->data[$name]['value']['video']['alt'] ?? '',
|
||||
'helper' => __('crud.minicrud.video_alt_helper') . ' ' . __('validation.between.string', [
|
||||
'attribute' => Str::ucFirst(__('validation.custom.video_alt')),
|
||||
'min' => 6,
|
||||
'max' => 255
|
||||
]),
|
||||
'disabled' => empty($value['video']['alt']),
|
||||
'value' => $value['video']['alt'] ?? '',
|
||||
'helper' =>
|
||||
__('crud.minicrud.video_alt_helper') .
|
||||
' ' .
|
||||
__('validation.between.string', [
|
||||
'attribute' => Str::ucFirst(__('validation.custom.video_alt')),
|
||||
'min' => 6,
|
||||
'max' => 255,
|
||||
]),
|
||||
'placeholder' => Str::ucFirst(__('validation.custom.video_alt')),
|
||||
])
|
||||
</div>
|
||||
|
||||
{{-- VIDEO_TITLE --}}
|
||||
<div @class([
|
||||
sprintf('%sVideo', $id) . ' col-12 col-md-6 mb-3',
|
||||
'd-none' => empty($model->data[$name]['value']['video']),
|
||||
'd-none' => empty($value['video']),
|
||||
])>
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => sprintf("%sVideoTitle", $id),
|
||||
'id' => sprintf('%sVideoTitle', $id),
|
||||
'label' => Str::ucFirst(__('validation.custom.video_title')),
|
||||
'hasWordCounter' => true,
|
||||
'name' => "data[$name][value][video][title]",
|
||||
'required' => true,
|
||||
'model' => $model ?? null,
|
||||
'disabled' => empty($model->data[$name]['value']['video']['title']),
|
||||
'value' => $model->data[$name]['value']['video']['title'] ?? '',
|
||||
'helper' => __('crud.minicrud.video_title_helper') . ' ' . __('validation.between.string', [
|
||||
'attribute' => Str::ucFirst(__('validation.custom.video_title')),
|
||||
'min' => 6,
|
||||
'max' => 255
|
||||
]),
|
||||
'disabled' => empty($value['video']['title']),
|
||||
'value' => $value['video']['title'] ?? '',
|
||||
'helper' =>
|
||||
__('crud.minicrud.video_title_helper') .
|
||||
' ' .
|
||||
__('validation.between.string', [
|
||||
'attribute' => Str::ucFirst(__('validation.custom.video_title')),
|
||||
'min' => 6,
|
||||
'max' => 255,
|
||||
]),
|
||||
'placeholder' => Str::ucFirst(__('validation.custom.video_title')),
|
||||
])
|
||||
</div>
|
||||
|
||||
{{-- PICTURE --}}
|
||||
<div @class([
|
||||
sprintf('%sPicture', $id) . ' col-12 col-md-6 mb-3',
|
||||
'd-none' => empty($model->data[$name]['value']['picture']),
|
||||
'd-none' => empty($value['picture']),
|
||||
])>
|
||||
@include('bo.partials.inputs.varchar-picture', [
|
||||
'id' => sprintf("%sPicture", $id),
|
||||
'id' => sprintf('%sPicture', $id),
|
||||
'name' => "data[$name][value][picture][path]",
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'model' => $model ?? null,
|
||||
'value' => $model->data[$name]['value']['picture']['path'] ?? null,
|
||||
'value' => $value['picture']['path'] ?? null,
|
||||
'helper' => __('crud.minicrud.dimensions_must_be', [
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
|
|
@ -101,54 +114,61 @@
|
|||
'preview' => false,
|
||||
])
|
||||
</div>
|
||||
|
||||
{{-- PICTURE_ALT --}}
|
||||
<div @class([
|
||||
sprintf('%sPicture', $id) . ' col-12 col-md-6 mb-3',
|
||||
'd-none' => empty($model->data[$name]['value']['picture']),
|
||||
'd-none' => empty($value['picture']),
|
||||
])>
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => sprintf("%sPictureAlt", $id),
|
||||
'id' => sprintf('%sPictureAlt', $id),
|
||||
'label' => Str::ucFirst(__('validation.custom.picture_alt')),
|
||||
'hasWordCounter' => true,
|
||||
'name' => "data[$name][value][picture][alt]",
|
||||
'required' => true,
|
||||
'model' => $model ?? null,
|
||||
'disabled' => empty($model->data[$name]['value']['picture']['alt']),
|
||||
'value' => $model->data[$name]['value']['picture']['alt'] ?? '',
|
||||
'helper' => __('crud.minicrud.picture_alt_helper') . ' ' . __('validation.between.string', [
|
||||
'attribute' => Str::ucFirst(__('validation.custom.picture_alt')),
|
||||
'min' => 6,
|
||||
'max' => 255
|
||||
]),
|
||||
'disabled' => empty($value['picture']['alt']),
|
||||
'value' => $value['picture']['alt'] ?? '',
|
||||
'helper' =>
|
||||
__('crud.minicrud.picture_alt_helper') .
|
||||
' ' .
|
||||
__('validation.between.string', [
|
||||
'attribute' => Str::ucFirst(__('validation.custom.picture_alt')),
|
||||
'min' => 6,
|
||||
'max' => 255,
|
||||
]),
|
||||
'placeholder' => Str::ucFirst(__('validation.custom.picture_alt')),
|
||||
])
|
||||
</div>
|
||||
|
||||
{{-- PICTURE_TITLE --}}
|
||||
<div @class([
|
||||
sprintf('%sPicture', $id) . ' col-12 col-md-6 mb-3',
|
||||
'd-none' => empty($model->data[$name]['value']['picture']),
|
||||
'd-none' => empty($value['picture']),
|
||||
])>
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => sprintf("%sPictureTitle", $id),
|
||||
'id' => sprintf('%sPictureTitle', $id),
|
||||
'label' => Str::ucFirst(__('validation.custom.picture_title')),
|
||||
'hasWordCounter' => true,
|
||||
'name' => "data[$name][value][picture][title]",
|
||||
'required' => true,
|
||||
'model' => $model ?? null,
|
||||
'disabled' => empty($model->data[$name]['value']['picture']['title']),
|
||||
'value' => $model->data[$name]['value']['picture']['title'] ?? '',
|
||||
'helper' => __('crud.minicrud.picture_title_helper') . ' ' . __('validation.between.string', [
|
||||
'attribute' => Str::ucFirst(__('validation.custom.picture_title')),
|
||||
'min' => 6,
|
||||
'max' => 255
|
||||
]),
|
||||
'disabled' => empty($value['picture']['title']),
|
||||
'value' => $value['picture']['title'] ?? '',
|
||||
'helper' =>
|
||||
__('crud.minicrud.picture_title_helper') .
|
||||
' ' .
|
||||
__('validation.between.string', [
|
||||
'attribute' => Str::ucFirst(__('validation.custom.picture_title')),
|
||||
'min' => 6,
|
||||
'max' => 255,
|
||||
]),
|
||||
'placeholder' => Str::ucFirst(__('validation.custom.picture_title')),
|
||||
])
|
||||
</div>
|
||||
|
||||
{{-- NOTHING SELECTED --}}
|
||||
<div @class([
|
||||
sprintf('%sNone', $id) . ' col-12 col-md-6',
|
||||
'd-none' => !empty($model->data[$name]['value']['picture']) || !empty($model->data[$name]['value']['video']),
|
||||
'd-none' => !empty($value['picture']) || !empty($value['video']),
|
||||
])>
|
||||
<p class="text-center text-body-secondary m-0">{{ __('Aucun média n\'est sélectionné') }}</p>
|
||||
</div>
|
||||
|
|
@ -156,7 +176,7 @@
|
|||
|
||||
@push('scripts')
|
||||
<script nonce="{{ $nonce }}">
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
initMediaSelector("{{ $id }}");
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,16 @@
|
|||
<b>{{ $label }} {{ $required ? '*' : '' }}</b>
|
||||
</label>
|
||||
@php
|
||||
$modelSelected = old($name)
|
||||
? $targetClass::find(old($name, []))
|
||||
: $model->{(isset($relation)) ? $relation : $name};
|
||||
$modelSelected = null;
|
||||
|
||||
if (old($name)) {
|
||||
$modelSelected = $targetClass::find(old($name, []));
|
||||
} elseif (isset($relation) && $relation) {
|
||||
$modelSelected = $model->{$relation};
|
||||
} elseif (isset($selectedIds) && $selectedIds) {
|
||||
$modelSelected = $targetClass::find($selectedIds);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'id' => $id,
|
||||
'name' => $name,
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@
|
|||
</div>
|
||||
@endif
|
||||
<div class="d-flex align-items-center">
|
||||
@if (isset($badge) && !empty($picto))
|
||||
<img class="img-fluid my-3 me-2" id="header-banner-picto" src="{{ $picto }}" title="{{ $picto_title }}"
|
||||
alt="{{ $picto_alt }}">
|
||||
@if (isset($badge) && !empty($picto_baner))
|
||||
<img class="img-fluid my-3 me-2" id="header-banner-picto" src="{{ asset($picto_baner['value']) }}" title="{{ $picto_baner['title'] }}"
|
||||
alt="{{ $picto_baner['alt'] }}">
|
||||
@endif
|
||||
<h1 class="my-3">{{ $staticPageModel->title }}</h1>
|
||||
</div>
|
||||
<p>{!! clean($staticPageModel->subtitle) !!}</p>
|
||||
<p>{!! clean($staticPageModel->content) !!}</p>
|
||||
</hgroup>
|
||||
</div>
|
||||
<div class="d-none d-xl-block col-6 col-xxl-4">
|
||||
|
|
|
|||
|
|
@ -1,20 +1,26 @@
|
|||
@php
|
||||
$commitmentId = 5;
|
||||
$staticPageModel = new stdClass();
|
||||
$staticPageModel->seo_title = str('respecter la terre')->ucFirst();
|
||||
$staticPageModel->seo_description =
|
||||
'Optimiser le transport, privilégier des fournisseurs locaux et réduire les emballages... quelques actions pour agir ensemble sur notre empreinte carbone !';
|
||||
$staticPageModel->title = str('respecter la terre')->upper();
|
||||
$staticPageModel->subtitle =
|
||||
'Le Gaulois s’engage à réduire son empreinte carbone : optimiser le transport, privilégier des fournisseurs locaux et réduire les emballages.';
|
||||
$staticPageModel->picture = Vite::asset("resources/assets/images/commitments/commitment-{$commitmentId}/banner.jpg");
|
||||
$staticPageModel->picture_alt = 'Forêt éclairé par la lumière du soleil.';
|
||||
$staticPageModel->picture_title = "Bannière de la page engagement n°{$commitmentId}";
|
||||
$badge = new stdClass();
|
||||
$badge->label = str("engagement n°{$commitmentId}")->upper();
|
||||
$badge->color = '#006633';
|
||||
|
||||
$section_2_model = $staticPageModel->sections[0];
|
||||
$section_2 = $section_2_model->data;
|
||||
$section_2_pictos = $section_2_model->pictos;
|
||||
$reduction_2 = $section_2_model->blocks[0]->data;
|
||||
|
||||
$section_3_model = $staticPageModel->sections[1];
|
||||
$section_3 = $section_3_model->data;
|
||||
$reduction_3 = $section_3_model->blocks[0]->data;
|
||||
|
||||
$section_4_model = $staticPageModel->sections[2];
|
||||
$section_4 = $section_4_model->data;
|
||||
$section_4_pictos = $section_4_model->pictos;
|
||||
$reduction_4 = $section_4_model->blocks[0]->data;
|
||||
|
||||
@endphp
|
||||
|
||||
|
||||
@extends('fo.layout', ['brParam' => $staticPageModel])
|
||||
|
||||
@section('title', str($staticPageModel->seo_title)->ucFirst())
|
||||
|
|
@ -27,198 +33,74 @@
|
|||
@include('fo.partials.commitments.navigation')
|
||||
{{-- HEADER --}}
|
||||
@include('fo.layouts.header-banner', [
|
||||
'picto' => Vite::asset('resources/assets/images/commitments/navigation/picto-life.svg'),
|
||||
'picto_alt' => __('Pictogramme d’un serrage de main.'),
|
||||
'picto_title' => __('Pictogramme d’un serrage de main.'),
|
||||
'picto' => asset($staticPageModel->picture),
|
||||
'picto_alt' => __($staticPageModel->picture_alt),
|
||||
'picto_title' => __($staticPageModel->picture_title),
|
||||
'picto_baner' => $staticPageModel->data['picto_banner'],
|
||||
])
|
||||
|
||||
{{-- PARAGRAPH WITH VISUAL AND COMPLEX ITEMS --}}
|
||||
@php
|
||||
// Item 1
|
||||
$item1 = new stdClass();
|
||||
$item1->title = str(__('colisage optimisé'))->upper();
|
||||
$item1->subtitle = __('avec des cartons pleins');
|
||||
$item1->picto = 'resources/assets/images/commitments/pictograms/picto-open-box.svg';
|
||||
$item1->picto_alt = __('Pictogramme d’un carton ouvert pour optimiser les colisages');
|
||||
$item1->picto_title = __('Pictogramme d’un colisage optimisé');
|
||||
// Item 2
|
||||
$item2 = new stdClass();
|
||||
$item2->title = str(__('fournisseurs locaux'))->upper();
|
||||
$item2->subtitle = __('privilégiés');
|
||||
$item2->picto = 'resources/assets/images/commitments/pictograms/picto-local-french-suppliers.svg';
|
||||
$item2->picto_alt = __(
|
||||
'Pictogramme de la carte de France avec un emplacement de coq, illustrant les fournisseurs locaux privilégiés par Le Gaulois',
|
||||
);
|
||||
$item2->picto_title = __('Pictogramme de fournisseurs français locaux privilégiés');
|
||||
// Item 3
|
||||
$item3 = new stdClass();
|
||||
$item3->title = str(__('réduction des distances'))->upper();
|
||||
$item3->subtitle = __('de transport des animaux');
|
||||
$item3->picto = 'resources/assets/images/commitments/pictograms/picto-terminal.svg';
|
||||
$item3->picto_alt = __(
|
||||
'Pictogramme d’une borne de distance kilométrique peu élevé pour réduire les distances de transport des volailles Le Gaulois',
|
||||
);
|
||||
$item3->picto_title = __('Pictogramme de réduction des distance de transport des volailles');
|
||||
// Item 4
|
||||
$item4 = new stdClass();
|
||||
$item4->title = str(__('des boucles de transport vertueuses'))->upper();
|
||||
$item4->subtitle = __('des produits');
|
||||
$item4->picto = 'resources/assets/images/commitments/pictograms/picto-map.svg';
|
||||
$item4->picto_alt = __('Pictogramme d’une carte avec des boucles de transport plus vertueuses pour les produits Le Gaulois');
|
||||
$item4->picto_title = __('Pictogramme d’une boucle de transport vertueux');
|
||||
// Item 5
|
||||
$item5 = new stdClass();
|
||||
$item5->title = str(__('amélioration de la flotte'))->upper();
|
||||
$item5->subtitle = __('dédiée au ramassage');
|
||||
$item5->picto = 'resources/assets/images/commitments/pictograms/picto-truck.svg';
|
||||
$item5->picto_alt = __('Pictogramme d’un camion plus écologique pour la flotte dédiée au ramassage des volailles Le Gaulois');
|
||||
$item5->picto_title = __('Pictogramme d’une flotte de transport dédié au ramassage plus écologique');
|
||||
// Item 6
|
||||
$item6 = new stdClass();
|
||||
$item6->sustitle = str(__('des poulaillers à'))->ucFirst();
|
||||
$item6->title = str(__('basse consommation'))->upper();
|
||||
$item6->picto = 'resources/assets/images/commitments/pictograms/picto-henhouse.svg';
|
||||
$item6->picto_alt = __('Poulaillers d’élevage à basse consommation');
|
||||
$item6->picto_title = __('Pictogramme d’un poulailler à basse consommation pour les volailles Le Gaulois');
|
||||
// All
|
||||
$collectionItems = collect([$item1, $item2, $item3, $item4, $item5, $item6]);
|
||||
@endphp
|
||||
<section class="line-border-large bg-light">
|
||||
@if ($section_2_model->visibility)
|
||||
@include('fo.partials.commitments.paragraph-with-visual-complex-flex-items', [
|
||||
'title' => __('priorité à la réduction de notre empreinte carbone'),
|
||||
'text' => nl2br(
|
||||
__(
|
||||
'Chaque initiative joue un rôle crucial dans la :strongStartlutte contre les gaz à effet de serre:strongEnd, contribuant ainsi à préserver l’équilibre de la planète et la biodiversité. Ainsi, Le Gaulois met en en place des actions concrètes face au :strongStartréchauffement climatique:strongEnd.',
|
||||
[
|
||||
'strongStart' => '<strong>',
|
||||
'strongEnd' => '</strong>',
|
||||
])),
|
||||
'collection' => $collectionItems,
|
||||
'picture' => Vite::asset('resources/assets/images/commitments/common/footprint.jpg'),
|
||||
'picture_alt' => __('Vue du dessus d’une empreinte humaine de pied avec de l’eau et entouré d’une forêt verdoyante'),
|
||||
'picture_title' => __('Empreinte carbone de l’activité humaine sur la Terre'),
|
||||
'picto' => Vite::asset('resources/assets/images/commitments/pictograms/picto-human-footprint.svg'),
|
||||
'picto_alt' => __(
|
||||
'Pictogramme d’une empreinte digitale humaine, symbôle de l’engagement de Le Gaulois à réduire son empreinte carbone sur la terre.'),
|
||||
'picto_title' => __('Pictogramme d’une empreinte de doigt'),
|
||||
'title' => __($section_2->title->value),
|
||||
'text' => nl2br(__($section_2->description->value)),
|
||||
'collection' => $section_2_pictos,
|
||||
'picture' => asset($section_2->media->value->picture->path),
|
||||
'picture_alt' => $section_2->media->value->picture->alt,
|
||||
'picture_title' => $section_2->media->value->picture->title,
|
||||
'picto' => asset($section_2->media_picto->value),
|
||||
'picto_alt' => __($section_2->media_picto->alt),
|
||||
'picto_title' => __($section_2->media_picto->title),
|
||||
'reduction' => $reduction_2,
|
||||
])
|
||||
</section>
|
||||
@endif
|
||||
|
||||
{{-- PARAGRAPH WITH PICTURE --}}
|
||||
@include('fo.partials.commitments.paragraph-with-visual', [
|
||||
'title' => __('stop à la déforestation, halte au soja non responsable'),
|
||||
'text' => nl2br(
|
||||
__(
|
||||
'Chez Le Gaulois, pour assurer :strongStartl’apport protéique:strongEnd et assurer un :strongStartéquilibre nutritionnel de nos volailles:strongEnd, nous les alimentons avec du :strongStartsoja:strongEnd. C’est la :strongStartlégumineuse la plus riche en protéines et en acides aminés essentiels !:strongEnd Conscient que la culture du Soja importée est assujettie à de nombreux questionnements, notamment son impact sur la :strongStartdéforestation.:strongEnd',
|
||||
[
|
||||
'strongStart' => '<strong>',
|
||||
'strongEnd' => '</strong>',
|
||||
])),
|
||||
'background_color' => 'light',
|
||||
'picture' => Vite::asset('resources/assets/images/commitments/common/soja.jpg'),
|
||||
'picture_alt' => __('Graines de soja importées mais garanties zéro déforesation d’ici 2025'),
|
||||
'picture_title' => __('Soja responsable et engagé contre la déforestation'),
|
||||
'picto' => Vite::asset('resources/assets/images/commitments/pictograms/picto-cereals.svg'),
|
||||
'picto_alt' => __('Céréales cultivées en France pour nourrir les volailles Le Gaulois'),
|
||||
'picto_title' => __('Pictogramme de céréales pour l’alimentation des volailles Le Gaulois'),
|
||||
'card_sustitle' => str('Le Gaulois s’engage')->upper(),
|
||||
'card_title' => '100% de soja',
|
||||
'card_subtitle' => 'garanti zéro déforestation',
|
||||
'card_text' => 'conversion au plus tard d’ici 2025.',
|
||||
'card_picto' => Vite::asset('resources/assets/images/commitments/pictograms/picto-target.svg'),
|
||||
'card_picto_alt' => __('Objectif 100% de soja, garanti zéro déforestation'),
|
||||
'card_picto_title' => __('Pictogramme d’une cible pour l’engagement de Le Gaulois'),
|
||||
])
|
||||
@if ($staticPageModel->sections[1]->visibility)
|
||||
@include('fo.partials.commitments.paragraph-with-visual', [
|
||||
'title' => __($section_3->title->value),
|
||||
'text' => __($section_3->description->value),
|
||||
'background_color' => 'light',
|
||||
'picture' => asset($section_3->media->value->picture->path),
|
||||
'picture_alt' => $section_3->media->value->picture->alt,
|
||||
'picture_title' => $section_3->media->value->picture->title,
|
||||
'picto' => asset($section_3->media_picto->value),
|
||||
'picto_alt' => __($section_3->media_picto->alt),
|
||||
'picto_title' => __($section_3->media_picto->title),
|
||||
'card_title' => __($section_3->encare_beige_title->value),
|
||||
'card_text' => __($section_3->encare_beige_description->value),
|
||||
'reduction' => $reduction_3,
|
||||
])
|
||||
@endif
|
||||
|
||||
{{-- PARAGRAPH WITH PICTURE --}}
|
||||
@include('fo.partials.commitments.paragraph-with-visual', [
|
||||
'title' => __('réduire l’impact de nos emballages sur l’environnement'),
|
||||
'text' => nl2br(
|
||||
__(
|
||||
'Conscient de son rôle, Le Gaulois s ‘est engagé dans une démarche continue de réduction de ses emballages et d’amélioration de leur recyclabilité, tout en garantissant la sécurité alimentaire.') .
|
||||
'</br>' .
|
||||
__(
|
||||
'<ul class="mb-3 p-0">Depuis fin 2023, près de :strongStart80% de nos emballages sont responsables:strongEnd :<li class="ms-4">soit intégrant de la matière recyclée</li><li class="ms-4">soit en étant recyclables</li></ul>',
|
||||
['strongStart' => '<strong>', 'strongEnd' => '</strong>']) .
|
||||
__(
|
||||
'<p>D’ici fin 2025, nous ambitionnons de passer à près de :strongStart95% d’emballages responsables.:strongEnd</p>',
|
||||
[
|
||||
'strongStart' => '<strong>',
|
||||
'strongEnd' => '</strong>',
|
||||
]) .
|
||||
'</br>' .
|
||||
__(
|
||||
':divStart:image:hgroupStart:strongStartLe saviez-vous ?:strongEnd 100% des emballages cartons Le Gaulois sont déjà issus de :strongStartcartons recyclés et certifiés FSC:strongEnd, garantissant une gestion responsable des forêts, et :strongStartplus de 60% des barquettes:strongEnd de volaille Le Gaulois sont recyclables ou intégrant :strongStartau moins 50% de matière recyclée.:hgroupEnd:divEnd:strongEnd',
|
||||
[
|
||||
'image' =>
|
||||
'<img src="' .
|
||||
Vite::asset('resources/assets/images/commitments/pictograms/picto-question.svg') .
|
||||
'" class="me-2" alt="Pictogramme d’une ampoule pour informer sur un fait marquant à retenir" title="Pictogramme d’une ampoule">',
|
||||
'divStart' => '<div class="card-text-part d-flex justify-content-start align-items-center">',
|
||||
'divEnd' => '</div>',
|
||||
'hgroupStart' => '<hgroup>',
|
||||
'hgroupEnd' => '</hgroup>',
|
||||
'strongStart' => '<strong>',
|
||||
'strongEnd' => '</strong>',
|
||||
])),
|
||||
'background_color' => 'light',
|
||||
'picture' => Vite::asset('resources/assets/images/commitments/common/box.jpg'),
|
||||
'picture_alt' => __('Colis en carton avec une estampille de recyclabilité'),
|
||||
'picture_title' => __('Emballages cartons issus de cartons recyclés ou certifiés FSC'),
|
||||
'picto' => Vite::asset('resources/assets/images/commitments/pictograms/picto-recycle.svg'),
|
||||
'picto_alt' => __('Pictogramme de recyclabilité sur les emballages et la gestion des déchets'),
|
||||
'picto_title' => __('Pictogramme sur les emballages et la gestion des déchets'),
|
||||
'reversed' => true,
|
||||
])
|
||||
{{-- COMPLEX FLEX ITEMS --}}
|
||||
@php
|
||||
// Item 1
|
||||
$complexItem1 = new stdClass();
|
||||
$complexItem1->title = str(__('réduction des emballages'))->upper();
|
||||
$complexItem1->text = __('épaisseurs, dimensions et poids');
|
||||
$complexItem1->picto = 'resources/assets/images/commitments/pictograms/picto-reduction-packaging.svg';
|
||||
$complexItem1->picto_alt = __(
|
||||
'Pictogramme d’un poids "-kg" en baisse sur les emballages grâce à l’impact des réductions sur les épaisseurs, les dimensions et le poids',
|
||||
);
|
||||
$complexItem1->picto_title = __('Pictogramme sur la réduction du poids des emballages');
|
||||
// Item 2
|
||||
$complexItem2 = new stdClass();
|
||||
$complexItem2->title = str(__('100% des emballages carton'))->upper();
|
||||
$complexItem2->text = __('issus de cartons recyclés ou certifiés FSC Mixte');
|
||||
$complexItem2->picto = 'resources/assets/images/commitments/pictograms/picto-recycled-cardboard-packaging.svg';
|
||||
$complexItem2->picto_alt = __('Pictogramme d’un carton issus de cartons recyclés ou certifiés FSC Mixte');
|
||||
$complexItem2->picto_title = __('Pictogramme d’un emballage carton recyclé ou certifié FSC');
|
||||
// Item 3
|
||||
$complexItem3 = new stdClass();
|
||||
$complexItem3->sustext = str(__('Mise au point d’une'))->ucFirst();
|
||||
$complexItem3->title = str(__('filière de recyclabilié'))->upper();
|
||||
$complexItem3->text = __('des barquettes de volaille Le Gaulois');
|
||||
$complexItem3->picto = 'resources/assets/images/commitments/pictograms/picto-recycling-industry.svg';
|
||||
$complexItem3->picto_alt = __('Pictogramme d’une filière de recyclabilité en France sur les barquettes de volailles Le Gaulois');
|
||||
$complexItem3->picto_title = __('Pictogramme d’une filière de recyclabilité des barquettes');
|
||||
// Item 4
|
||||
$complexItem4 = new stdClass();
|
||||
$complexItem4->title = str(__('moins de plastique'))->upper();
|
||||
$complexItem4->text = str(__('minimiser notre empreinte carbone et agir sur le changement climatique'));
|
||||
$complexItem4->picto = 'resources/assets/images/commitments/pictograms/picto-plastic-decrease.svg';
|
||||
$complexItem4->picto_alt = __('Pictogramme d’un sac plastique avec l’estampille de recyclabilité');
|
||||
$complexItem4->picto_title = __('Pictogramme sur la diminution de plastique');
|
||||
// All
|
||||
$collectionComplexItems = collect([$complexItem1, $complexItem2, $complexItem3, $complexItem4]);
|
||||
@endphp
|
||||
@include('fo.partials.commitments.complex-flex-items', [
|
||||
'background_color' => 'quint',
|
||||
'collection' => $collectionComplexItems,
|
||||
])
|
||||
@if ($section_4_model->visibility)
|
||||
@include('fo.partials.commitments.paragraph-with-visual', [
|
||||
'title' => __($section_4->title->value),
|
||||
'text' => __($section_4->description->value),
|
||||
'encart' => $section_4->encare_beige->value,
|
||||
'background_color' => 'light',
|
||||
'picture' => asset($section_4->media->value->picture->path),
|
||||
'picture_alt' => __($section_4->media->value->picture->alt),
|
||||
'picture_title' => __($section_4->media->value->picture->title),
|
||||
'picto' => asset($section_4->media_picto->value),
|
||||
'picto_alt' => __($section_4->media_picto->alt),
|
||||
'picto_title' => __($section_4->media_picto->title),
|
||||
'reversed' => true,
|
||||
'reduction' => $reduction_4,
|
||||
])
|
||||
|
||||
{{-- COMPLEX FLEX ITEMS --}}
|
||||
@include('fo.partials.commitments.complex-flex-items', [
|
||||
'background_color' => 'quint',
|
||||
'collection' => $section_4_pictos,
|
||||
])
|
||||
@endif
|
||||
|
||||
{{-- OTHER COMMITMENTS --}}
|
||||
@include('fo.partials.commitments.other-commitments', [
|
||||
'number' => $commitmentId,
|
||||
'title' => 'Découvrez plus d’engagements Le Gaulois',
|
||||
'text' => nl2br(
|
||||
__('Face aux enjeux de notre époque, Le Gaulois répond présent.') .
|
||||
'</br>' .
|
||||
__(
|
||||
'Chaque jour, nous avançons pas à pas avec :strongStartles équipes, les partenaires et les consommateurs:strongEnd pour élaborer des produits gourmands, de qualité et ainsi créer de bons moments de convivialité à table et en cuisine. Découvrez nos 5 engagements et nos actions pour :strongStartminimiser l’impact de notre activité sur l’environnement, veiller au bien-être animal et améliorer notre alimentation et notre qualité de vie:strongEnd.',
|
||||
[
|
||||
'strongStart' => '<strong>',
|
||||
'strongEnd' => '</strong>',
|
||||
])),
|
||||
'title' => $staticPageModel->data['common_section']['title'],
|
||||
'text' => $staticPageModel->data['common_section']['content'],
|
||||
])
|
||||
@endsection
|
||||
|
|
|
|||
48
resources/views/fo/partials/block/reduction-card.blade.php
Normal file
48
resources/views/fo/partials/block/reduction-card.blade.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
@php
|
||||
use App\Enums\Block\BlockContentTypeEnum;
|
||||
$pos = \App\Enums\Block\BlockPositionEnum::from($position)->toBootstrap();
|
||||
$hasCta = in_array($blockType, [BlockContentTypeEnum::Cta->value, BlockContentTypeEnum::Both->value]);
|
||||
$hasActu = in_array($blockType, [BlockContentTypeEnum::Actu->value, BlockContentTypeEnum::Both->value]);
|
||||
@endphp
|
||||
|
||||
<div class="row justify-content-{{ $pos }}">
|
||||
|
||||
@if ($hasActu && $news)
|
||||
<div class="col-12 {{ $hasCta ? 'col-md-4' : 'col-md-6' }} order-1">
|
||||
<a href="{{ route('fo.news.show', $news->slug) }}" class="text-decoration-none text-dark">
|
||||
<div class="card bg-white shadow-lg border-0">
|
||||
<div class="row g-0">
|
||||
<div class="col-4">
|
||||
<img src="{{ asset($news->picture) }}" class="img-fluid rounded-start h-100 object-fit-cover"
|
||||
alt="{{ $news->picture_alt }}" title="{{ $news->picture_title }}">
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div class="card-body py-2">
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center gap-1 gap-md-2 mb-1">
|
||||
<span class="badge text-uppercase align-self-start"
|
||||
style="background-color: {{ $news->newsCategory->color }}">
|
||||
{!! clean($news->newsCategory->title) !!}
|
||||
</span>
|
||||
<small class="text-muted ps-md-1"
|
||||
style="padding-left: 5px;">{{ \Carbon\Carbon::parse($news->updated_at)->format('d/m/Y') }}</small>
|
||||
|
||||
</div>
|
||||
|
||||
<p class="card-text fw-bold text-uppercase mb-0" style="font-size: 0.85rem;">
|
||||
{{ $news->title }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($hasCta && $ctaUrl && $ctaLabel)
|
||||
<div class="col-12 col-md-auto order-2 order-md-1 d-flex align-items-center mt-3 mt-md-0">
|
||||
<a href="{{ $ctaUrl }}" class="btn btn-secondary btn-lg">{{ $ctaLabel }}</a>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
|
@ -20,8 +20,8 @@
|
|||
<div class="col-6 col-lg-4 card mb-3 border-0 bg-transparent">
|
||||
<div class="row g-0">
|
||||
<div class="col-12 d-flex justify-content-center align-items-center">
|
||||
<img class="img-fluid" src="{{ Vite::asset($item->picto) }}" title="{{ $item->picto_title }}"
|
||||
alt="{{ $item->picto_alt }}">
|
||||
<img class="img-fluid" src="{{ asset($item->picture) }}" title="{{ $item->picture_title }}"
|
||||
alt="{{ $item->picture_alt }}">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="card-body text-center">
|
||||
|
|
@ -29,8 +29,8 @@
|
|||
<p class="card-text m-0">{{ $item->sustext }}</p>
|
||||
@endif
|
||||
<h5 class="card-title m-0">{{ $item->title }}</h5>
|
||||
@if (isset($item->text))
|
||||
<p class="card-text">{!! clean($item->text) !!}</p>
|
||||
@if (isset($item->description))
|
||||
<p class="card-text">{!! clean($item->description) !!}</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
@php
|
||||
$blockType = $reduction->block_type->value;
|
||||
$position = $reduction->position->value;
|
||||
$ctaUrl = $reduction->cta_url->value ?? null;
|
||||
$ctaLabel = $reduction->cta_label->value ?? null;
|
||||
$articleId = $reduction->article_id->value ?? null;
|
||||
$reductionNews = $articleId ? \App\Models\News::find($articleId) : null;
|
||||
@endphp
|
||||
<section class="position-relative bg-white-wood-big py-5" id="commitments-paragraph-with-visual-complex-flex-items">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
|
@ -7,20 +15,21 @@
|
|||
])>
|
||||
<div class="position-relative pt-md-0 mx-auto my-5">
|
||||
@if (isset($picto) && !empty($picto))
|
||||
<img class="img-fluid position-absolute pictogram-reversed z-1" src="{{ $picto }}" title="{{ $picto_title }}"
|
||||
alt="{{ $picto_alt }}">
|
||||
<img class="img-fluid position-absolute pictogram-reversed z-1" src="{{ $picto }}"
|
||||
title="{{ $picto_title }}" alt="{{ $picto_alt }}">
|
||||
@endif
|
||||
<div class="d-flex justify-content-start align-items-start bottom-left-shadow">
|
||||
<div class="line-border-small rotate-right w-100 m-lg-0 mx-auto h-fit bg-white p-2">
|
||||
<img class="img-fluid line-border-small w-100" src="{{ $picture }}" title="{{ $picture_title }}"
|
||||
alt="{{ $picture_alt }}">
|
||||
<img class="img-fluid line-border-small w-100" src="{{ $picture }}"
|
||||
title="{{ $picture_title }}" alt="{{ $picture_alt }}">
|
||||
</div>
|
||||
</div>
|
||||
@if (isset($card_title) && !empty($card_title))
|
||||
<div class="custom-card bottom-left-shadow mt-md-0 z-1 mx-auto mt-5 w-fit">
|
||||
<img class="position-absolute z-2" src="{{ $card_picto }}" title="{{ $card_picto_title }}"
|
||||
alt="{{ $card_picto_alt }}">
|
||||
<p class="position-absolute text-light bg-secondary rotate-left h6 z-1 start-0 m-0 p-2 ps-5">
|
||||
<p
|
||||
class="position-absolute text-light bg-secondary rotate-left h6 z-1 start-0 m-0 p-2 ps-5">
|
||||
{{ $card_sustitle }}</p>
|
||||
<div class="bg-tertiary line-border-small p-4 text-center">
|
||||
<h2>{{ $card_title }}</h2>
|
||||
|
|
@ -36,22 +45,30 @@
|
|||
<div class="col-12 col-md-6 d-flex justify-content-center align-items-center">
|
||||
<div class="row d-flex justify-content-center align-items-start">
|
||||
@foreach ($collection as $item)
|
||||
<div class="col-6 col-md-4 d-flex justify-content-center align-items-center flex-column p-3 text-center">
|
||||
<div
|
||||
class="col-6 col-md-4 d-flex justify-content-center align-items-center flex-column p-3 text-center">
|
||||
<div class="pictogram-content d-flex justify-content-center align-items-center">
|
||||
<img class="img-fluid" src="{{ Vite::asset($item->picto) }}" title="{{ $item->picto_title }}"
|
||||
alt="{{ $item->picto_alt }}">
|
||||
<img class="img-fluid" src="{{ asset($item->picture) }}"
|
||||
title="{{ $item->picture_title }}" alt="{{ $item->picture_alt }}">
|
||||
</div>
|
||||
@if (isset($item->sustitle) && !empty($item->sustitle))
|
||||
<p class="m-0">{{ $item->sustitle }}</p>
|
||||
@endif
|
||||
<p class="h6 m-0">{{ $item->title }}</p>
|
||||
@if (isset($item->subtitle) && !empty($item->subtitle))
|
||||
<p class="m-0">{{ $item->subtitle }}</p>
|
||||
@if (isset($item->description) && !empty($item->description))
|
||||
<p class="m-0">{{ $item->description }}</p>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@include('fo.partials.block.reduction-card', [
|
||||
'news' => $reductionNews,
|
||||
'ctaUrl' => $ctaUrl ?? null,
|
||||
'ctaLabel' => $ctaLabel ?? null,
|
||||
'position' => $position,
|
||||
'blockType' => $blockType,
|
||||
])
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,12 @@
|
|||
(isset($reversedMobile) && $reversedMobile),
|
||||
])>
|
||||
<h3>{{ $title }}</h3>
|
||||
<p class="mt-3">{!! $text !!}</p>
|
||||
<p class="mt-3">{!! clean($text) !!}</p>
|
||||
@if (isset($encart) && !empty($encart))
|
||||
<div class="card-text-part d-flex justify-content-start align-items-center">
|
||||
{!! $encart !!}
|
||||
</div>
|
||||
@endif
|
||||
@if (isset($subtext) && !empty($subtext))
|
||||
<p>{{ $subtext }}</p>
|
||||
@endif
|
||||
|
|
@ -28,7 +33,8 @@
|
|||
@include('fo.partials.news-card-tiny', ['model' => $newsModel])
|
||||
@endif
|
||||
@if (isset($btnLabel) && !empty($btnLabel))
|
||||
<a class="btn btn-{{ $btn_color }}" href="{{ $btnLink }}" title="{{ __('Accéder à la page') }}"
|
||||
<a class="btn btn-{{ $btn_color }}" href="{{ $btnLink }}"
|
||||
title="{{ __('Accéder à la page') }}"
|
||||
aria-label="{{ __('Accéder à la page') }}">{{ $btnLabel }}</a>
|
||||
@endif
|
||||
</div>
|
||||
|
|
@ -42,10 +48,11 @@
|
|||
'ratio ratio-16x9' => isset($video_id),
|
||||
])>
|
||||
@if (isset($picto) && !empty($picto))
|
||||
<img src="{{ $picto }}" title="{{ $picto_title }}" alt="{{ $picto_alt }}" @class([
|
||||
'img-fluid position-absolute z-1',
|
||||
isset($reversed) && $reversed ? 'pictogram-reversed' : 'pictogram',
|
||||
])>
|
||||
<img src="{{ $picto }}" title="{{ $picto_title }}" alt="{{ $picto_alt }}"
|
||||
@class([
|
||||
'img-fluid position-absolute z-1',
|
||||
isset($reversed) && $reversed ? 'pictogram-reversed' : 'pictogram',
|
||||
])>
|
||||
@endif
|
||||
<div class="bottom-left-shadow">
|
||||
<div @class([
|
||||
|
|
@ -69,8 +76,9 @@
|
|||
<div class="position-relative w-100 h-100">
|
||||
@if (isset($video_id) && \App\Rules\IsValidYoutubeId::validates($video_id))
|
||||
@if (isset($isVideoModal) && isset($video_id) && \App\Rules\IsValidYoutubeId::validates($video_id))
|
||||
<div class="position-absolute z-1 w-100 h-100 start-0 top-0 bg-transparent" data-bs-toggle="modal"
|
||||
data-bs-target="#mecenat-chirurgie-video-modal" role="button"></div>
|
||||
<div class="position-absolute z-1 w-100 h-100 start-0 top-0 bg-transparent"
|
||||
data-bs-toggle="modal" data-bs-target="#mecenat-chirurgie-video-modal"
|
||||
role="button"></div>
|
||||
@endif
|
||||
<div class="video-inlaid w-100 h-100" type="youtube"
|
||||
href="https://www.youtube.com/embed/{{ $video_id }}?autoplay=0&loop=1">
|
||||
|
|
@ -84,26 +92,36 @@
|
|||
</div>
|
||||
|
||||
@if (!isset($video_id))
|
||||
<img class="img-fluid line-border-small w-100" src="{{ $picture }}" title="{{ $picture_title }}"
|
||||
alt="{{ $picture_alt }}">
|
||||
<img class="img-fluid line-border-small w-100" src="{{ $picture }}"
|
||||
title="{{ $picture_title }}" alt="{{ $picture_alt }}">
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if (isset($card_title) && !empty($card_title))
|
||||
<div class="custom-card bottom-left-shadow mt-md-0 z-1 mx-auto mt-5 w-fit">
|
||||
<img class="position-absolute z-2" src="{{ $card_picto }}" title="{{ $card_picto_title }}"
|
||||
alt="{{ $card_picto_alt }}">
|
||||
<p class="position-absolute text-light bg-secondary rotate-left h6 z-1 start-0 m-0 p-2 ps-5">
|
||||
{{ $card_sustitle }}</p>
|
||||
<div class="bg-tertiary line-border-small p-4 text-center">
|
||||
<h2>{{ $card_title }}</h2>
|
||||
<p class="h6 m-0">{{ $card_subtitle }}</p>
|
||||
<p class="m-0">{{ $card_text }}</p>
|
||||
<p class="m-0">{!! clean($card_text) !!}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@php
|
||||
$blockType = $reduction->block_type->value;
|
||||
$position = $reduction->position->value;
|
||||
$ctaUrl = $reduction->cta_url->value ?? null;
|
||||
$ctaLabel = $reduction->cta_label->value ?? null;
|
||||
$articleId = $reduction->article_id->value ?? null;
|
||||
$reductionNews = $articleId ? \App\Models\News::find($articleId) : null;
|
||||
@endphp
|
||||
@include('fo.partials.block.reduction-card', [
|
||||
'news' => $reductionNews,
|
||||
'ctaUrl' => $ctaUrl ?? null,
|
||||
'ctaLabel' => $ctaLabel ?? null,
|
||||
'position' => $position,
|
||||
'blockType' => $blockType,
|
||||
])
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
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') }}';
|
||||
window.__SYSTEM._routes['bo.news.json-paginate'] = '{{ route('bo.news.json-paginate') }}';
|
||||
|
||||
// FO
|
||||
window.__SYSTEM._routes['fo.home.products'] = '{{ route('fo.home.products', ['legend_slug' => 'LEGENDSLUG']) }}';
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ use App\Models\NewsCategory;
|
|||
use App\Models\NewsClub;
|
||||
use App\Models\NewsCorporate;
|
||||
use App\Models\Offer;
|
||||
use App\Models\Picto;
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductType;
|
||||
use App\Models\Range;
|
||||
|
|
@ -835,3 +836,8 @@ Breadcrumbs::for('bo.sell_items.duplicate', function (Generator $trail) {
|
|||
$trail->parent('bo.sell_items.index');
|
||||
$trail->push(Str::ucfirst(trans('crud.actions.duplicate')));
|
||||
});
|
||||
|
||||
// * PICTOS
|
||||
Breadcrumbs::for('bo.pictos.edit', function (Generator $trail, Picto $picto) {
|
||||
$trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.pictos.edit', $picto));
|
||||
});
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use App\Http\Controllers\Bo\ActivityLogController;
|
||||
use App\Http\Controllers\Bo\ApproachController;
|
||||
use App\Http\Controllers\Bo\BlockController;
|
||||
use App\Http\Controllers\Bo\DemarcheSlideController;
|
||||
use App\Http\Controllers\Bo\BrandAdvantageController;
|
||||
use App\Http\Controllers\Bo\CategoryApproachesController;
|
||||
|
|
@ -21,6 +22,7 @@ use App\Http\Controllers\Bo\NewsClubController;
|
|||
use App\Http\Controllers\Bo\NewsController;
|
||||
use App\Http\Controllers\Bo\NewsCorporateController;
|
||||
use App\Http\Controllers\Bo\OfferController;
|
||||
use App\Http\Controllers\Bo\PictoController;
|
||||
use App\Http\Controllers\Bo\ProductController;
|
||||
use App\Http\Controllers\Bo\ProductTypeController;
|
||||
use App\Http\Controllers\Bo\ProfessionController;
|
||||
|
|
@ -37,6 +39,8 @@ use App\Http\Controllers\Bo\UserController;
|
|||
use App\Http\Controllers\Bo\VideoController;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Bo\SectionApproachController;
|
||||
use App\Http\Controllers\Bo\SectionPageController;
|
||||
use App\Models\SectionPage;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
|
|
@ -93,10 +97,16 @@ Route::middleware(['throttle:web', 'admin-secure'])->prefix('bo')->name('bo.')->
|
|||
->withoutMiddleware('htmlsanitize')
|
||||
->middleware('htmlsanitize:content,data');
|
||||
Route::get('/pages/common_section', [StaticPageController::class, 'getCommonSection'])->name('static_page.sections.edit');
|
||||
|
||||
Route::patch('/pages/common_section/update', [StaticPageController::class, 'updateCommonSection'])
|
||||
->name('static_page.sections.update')
|
||||
->withoutMiddleware('htmlsanitize');
|
||||
|
||||
// COMMUN SECTION FOR COMMITMENTS PAGES
|
||||
Route::get('/pages/engagements/common_section', [StaticPageController::class, 'getCommitmentsCommonSection'])->name('static_page.commitmments.sections.edit');
|
||||
Route::patch('/pages/engagements/common_section/update', [StaticPageController::class, 'updateCommitmentsCommonSection'])
|
||||
->name('static_page.commitmments.sections.update')
|
||||
->withoutMiddleware('htmlsanitize');
|
||||
// * MENU NODE DATA
|
||||
Route::resource('menu_node_data', MenuNodeDataController::class)
|
||||
// * Nasty hack to prevent unwanted modifications form singular lib
|
||||
|
|
@ -508,6 +518,8 @@ Route::middleware(['throttle:web', 'admin-secure'])->prefix('bo')->name('bo.')->
|
|||
NewsController::class,
|
||||
'duplicate'
|
||||
])->name('news.duplicate');
|
||||
Route::get('/news/json/paginate', [NewsController::class, 'jsonPaginateModels'])
|
||||
->name('news.json-paginate');
|
||||
|
||||
// * NEWS CATEGORIES.
|
||||
Route::resource('news_categories', NewsCategoryController::class);
|
||||
|
|
@ -602,6 +614,36 @@ Route::middleware(['throttle:web', 'admin-secure'])->prefix('bo')->name('bo.')->
|
|||
Route::get('/activity_logs/user/{user:id}', [ActivityLogController::class, 'index'])
|
||||
->name('activity_logs.user');
|
||||
});
|
||||
|
||||
// SECTION PAGES
|
||||
Route::patch('/sections/{sectionPage}', [SectionPageController::class, 'update'])
|
||||
->name('sections.update')
|
||||
->withoutMiddleware('htmlsanitize')
|
||||
->middleware('htmlsanitize:content,data');
|
||||
Route::post('/sections/{section}/toggle-visibility', [SectionPageController::class, 'toggleVisibility'])
|
||||
->name('sections.toggleVisibility');
|
||||
|
||||
// BLOCKS
|
||||
Route::put('/blocks/{block}', [BlockController::class, 'update'])
|
||||
->withoutMiddleware('htmlsanitize')
|
||||
->middleware('htmlsanitize:content,data')
|
||||
->name('blocks.update');
|
||||
|
||||
// PICTOS
|
||||
Route::resource('pictos', PictoController::class)
|
||||
->withoutMiddleware('htmlsanitize')
|
||||
->middleware('htmlsanitize:content');
|
||||
Route::post('/pictos/save-picture-size', [PictoController::class, 'savePictureSize'])->name('pictos.savePictureSize');
|
||||
Route::patch('/pictos/{picto}/change-order/{direction}', [
|
||||
PictoController::class,
|
||||
'changeOrder'
|
||||
])->where('direction', 'up|down')->name('pictos.change-order');
|
||||
|
||||
Route::get('/flash', function () {
|
||||
return response()->json([
|
||||
'html' => view('modules.flash-messages')->render()
|
||||
]);
|
||||
});
|
||||
});
|
||||
// * Others routes
|
||||
Route::post('theme/set', [Controller::class, 'setTheme'])->name('theme.set');
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use App\Http\Controllers\Fo\BreedingController;
|
||||
use App\Http\Controllers\Fo\BreederController;
|
||||
use App\Http\Controllers\Fo\CommitmentController;
|
||||
use App\Http\Controllers\Fo\ConsentController;
|
||||
use App\Http\Controllers\Fo\ContactController;
|
||||
use App\Http\Controllers\Fo\ExportController;
|
||||
|
|
@ -129,7 +130,7 @@ Route::middleware(['throttle:web'])->name('fo.')->group(function () {
|
|||
Route::view('/elever-durablement', 'fo.pages.commitments.raise')->name('raise');
|
||||
Route::view('/bien-nourrir-les-francais', 'fo.pages.commitments.eat')->name('eat');
|
||||
Route::view('/favoriser-le-mieux-vivre-ensemble', 'fo.pages.commitments.life')->name('life');
|
||||
Route::view('/respecter-la-terre', 'fo.pages.commitments.respect')->name('respect');
|
||||
Route::get('/respecter-la-terre', [CommitmentController::class, 'getRestpectEarthPage'])->name('respect');
|
||||
});
|
||||
|
||||
// * BREEDING
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue