Merge branch 'develop' into trunk

This commit is contained in:
Noah Lancien 2026-04-09 10:24:36 +02:00
commit 2f6948bf5e
19 changed files with 193 additions and 101 deletions

View file

@ -8,6 +8,8 @@ use App\Http\Requests\Bo\Carousels\UpdateCarouselRequest;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Carousel;
use App\Models\Section;
use App\Traits\Controllers\UpdateModelOrder;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
@ -15,6 +17,9 @@ use Illuminate\Support\Str;
class CarouselController extends Controller
{
use UpdateModelOrder;
use UpdateModelPublished;
/**
* Create the controller instance.
*/

View file

@ -278,7 +278,7 @@ class StaticPageController extends Controller
$testimonialSection = $sectionModels->where('section', PageSectionEnum::hire_grow)->first();
$paritySection = $sectionModels->where('section', PageSectionEnum::hire_parity)->first();
$findSection = $sectionModels->where('section', PageSectionEnum::hire_find)->first();
$carouselSectionParity = Carousel::query()->where('section_id', PageSectionEnum::hire_parity)->where('published', true)->get();
$carouselSectionParity = Carousel::query()->where('section_id', PageSectionEnum::hire_parity)->where('published', true)->orderBy('order')->get();
$navLinks = Sticky::query()->where('type', StaticPageType::we_hire)->get();
return view('fo.pages.hire', \compact(

View file

@ -94,9 +94,11 @@ class Aisle extends Model
private static function assertLimitPublishedModels(self $model, bool $isDeletion = false)
{
$count = self::query()->where('published', true)->where('network_id', $model->network_id)->count();
$updatePublished = !$isDeletion && $model->published && !$model->getOriginal('published');
if (
(!$model->published && $count <= 1) ||
($model->published && $count >= 4) ||
($updatePublished && $count >= 4) ||
($isDeletion && $count === 1)
) {
throw ValidationException::withMessages([

View file

@ -89,11 +89,13 @@ class Brand extends Model
*/
private static function assertLimitPublishedModels(self $model, bool $isDeletion = false)
{
$count = self::query()->where('published', true)->count();
$count = self::query()->where('published', true)->where('aisle_id', $model->aisle_id)->count();
$updatePublished = !$isDeletion && $model->published && !$model->getOriginal('published');
if (
$count !== 0 &&
((!$model->published && $count <= 1) ||
($model->published && $count >= 5) ||
($updatePublished && $count >= 5) ||
($isDeletion && $count === 1))
) {
throw ValidationException::withMessages([

View file

@ -5,6 +5,7 @@ namespace App\Models;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\ActivityLog;
use App\Traits\Models\Orderable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -21,6 +22,7 @@ class Carousel extends Model
{
use ActivityLog;
use HasFactory;
use Orderable;
/**
* The attributes that are mass assignable.
@ -78,28 +80,14 @@ class Carousel extends Model
// * METHODS
/**
* Set order after the last element of the list.
* Group ordering by section_id.
*
* @param self $model
* @param boolean $isDeletion
* @return void
* @return string
*/
private static function setOrder(self $model, bool $isDeletion = false): void
public function getOrderGroupField(): string
{
$maxOrder = \intval(self::query()->where('section_id', $model->section_id)->max('order'));
if ($isDeletion) {
$request = Carousel::query()
->where('section_id', $model->section_id)
->whereNot('id', $model->id)
->whereBetween('order', [$model->order, $maxOrder]);
$request->decrement('order', 1);
return;
}
$model->order = $maxOrder + 1;
return 'section_id';
}

View file

@ -138,9 +138,11 @@ class Network extends Model
private static function assertLimitPublishedModels(self $model, bool $isDeletion = false)
{
$count = self::query()->where('published', true)->count();
$updatePublished = !$isDeletion && $model->published && !$model->getOriginal('published');
if (
(!$model->published && $count <= 1) ||
($model->published && $count >= 5) ||
($updatePublished && $count >= 5) ||
($isDeletion && $count === 1)
) {
throw ValidationException::withMessages([

View file

@ -5,6 +5,7 @@ namespace App\Providers;
use App\Http\Middleware\VerifyCsrfToken;
use App\Models\Aisle;
use App\Models\Brand;
use App\Models\Carousel;
use App\Models\Network;
use App\Models\News;
use App\Models\Strip;
@ -53,6 +54,7 @@ class RouteServiceProvider extends ServiceProvider
Route::model('network', Network::class);
Route::model('aisle', Aisle::class);
Route::model('brand', Brand::class);
Route::model('carousel', Carousel::class);
Route::bind('publishedNews', function ($value) {
return News::where(function ($q) use ($value) {
$q->where('slug', $value);

View file

@ -303,6 +303,7 @@ return [
'link' => 'lien de l\'ancre',
],
'media' => 'média',
'anchor' => 'ancre'
'anchor' => 'ancre',
'link' => 'lien',
],
];

View file

@ -16,6 +16,18 @@
}
}
.swiper-button-prev-custom {
top: 10%;
left: 1rem;
transform: translateY(-50%) rotate(180deg);
}
.swiper-button-next-custom {
top: 10%;
right: 1rem;
transform: translateY(-50%);
}
.history {
#historical-milestone-carousel {
.carousel-container {

View file

@ -159,11 +159,15 @@ document.addEventListener("DOMContentLoaded", function () {
},
},
"swiper-year": {
modules: [Keyboard],
modules: [Keyboard, Navigation],
grabCursor: true,
slidesPerView: 1.5,
centeredSlides: true,
slideToClickedSlide: true,
navigation: {
nextEl: ".year-next",
prevEl: ".year-prev",
},
keyboard: {
enabled: true,
},
@ -346,42 +350,6 @@ document.addEventListener("DOMContentLoaded", function () {
},
},
},
"swiper-carousels": {
modules: [Pagination],
grabCursor: true,
slidesPerView: 1,
spaceBetween: 15,
centeredSlides: true,
initialSlide: 0,
speed: 750,
pagination: {
el: ".swiper-carousels-pagination",
clickable: true,
renderBullet: function (index: number, className: string) {
return "<button class=\"btn btn-tertiary rounded-0 border-0 w-100 mx-2 p-0 " + className + "\" title=\"Aller à la slide " + (index + 1) + "\"></button>";
},
},
breakpoints: {
576: {
slidesPerView: 1.5,
},
768: {
slidesPerView: 2,
},
992: {
slidesPerView: 2.5,
},
},
on: {
beforeInit(swiper: Swiper) {
const slideCount = swiper.el.querySelectorAll(".swiper-slide").length;
if (slideCount !== 2) {
swiper.params.initialSlide = Math.floor(slideCount / 2);
}
}
}
},
};
const swiperVol = document.getElementsByClassName("swiper") as HTMLCollectionOf<HTMLElement>;

View file

@ -22,4 +22,5 @@ import "./base/sticky";
// PAGES
import "./pages/unveil";
import "./pages/site";
import "./pages/hire";

View file

@ -0,0 +1,63 @@
import Swiper from "swiper";
import { Navigation, Pagination } from "swiper/modules";
document.addEventListener("DOMContentLoaded", function () {
const hire = document.querySelector("section.we-hire");
if (hire === null) {
return;
}
const swiperContainers = document.querySelectorAll(".swiper-container");
swiperContainers.forEach((container, index) => {
const carousel = container.querySelector(".swiper-carousels") as HTMLElement,
btnPrev = container.querySelector(".carousel-prev") as HTMLElement,
btnNext = container.querySelector(".carousel-next") as HTMLElement,
pagination = carousel?.querySelector(".swiper-carousels-pagination") as HTMLElement;
carousel.classList.add(`swiper-${index}`);
pagination.classList.add(`swiper-pagination-${index}`);
btnPrev.classList.add(`prev-${index}`);
btnNext.classList.add(`next-${index}`);
new Swiper(carousel, {
modules: [Pagination, Navigation],
grabCursor: true,
slidesPerView: 1,
spaceBetween: 15,
centeredSlides: true,
initialSlide: 0,
speed: 750,
pagination: {
el: `.swiper-pagination-${index}`,
clickable: true,
renderBullet: function (idx, className) {
return `<button class="btn btn-tertiary rounded-0 border-0 w-100 mx-2 p-0 ${className}" title="Aller à la slide ${idx + 1}"></button>`;
},
},
navigation: {
prevEl: `.prev-${index}`,
nextEl: `.next-${index}`,
},
on: {
beforeInit(swiper: Swiper) {
const slideCount = swiper.el.querySelectorAll(".swiper-slide").length;
if (slideCount !== 2) {
swiper.params.initialSlide = Math.floor(slideCount / 2);
}
},
},
breakpoints: {
576: {
slidesPerView: 1.5
},
768: {
slidesPerView: 2
},
992: {
slidesPerView: 2.5
},
},
});
});
});

View file

@ -12,7 +12,7 @@
@method('PATCH')
<div class="mb-3">
<label for="order-{{ $model->getKey() }}" class="form-label">{{ __('Nouvel ordre') }}</label>
<input type="number" class="form-input" id="order-{{ $model->id }}" placeholder="1" name="order" min="1">
<input type="number" class="form-input" id="order-{{ $model->getKey() }}" placeholder="1" name="order" min="1">
</div>
<button type="submit" class="btn btn-primary">{{ _('Sauvegarder') }}</button>
</form>

View file

@ -34,13 +34,13 @@
{{-- LINK --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ Str::ucfirst(__('validation.attributes.link')) }}</legend>
<legend>{{ Str::ucfirst(__('validation.custom.link')) }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.varchar', [
'id' => 'brandLink',
'label' => __(':field :inter:model', [
'field' => Str::ucfirst(__('validation.attributes.link')),
'field' => Str::ucfirst(__('validation.custom.link')),
'inter' => __('validation.custom.of_the.female'),
'model' => trans_choice('models.classes.brand', 1),
]),
@ -50,7 +50,7 @@
'model' => $brandModel ?? null,
'helper' => __('validation.between.string', [
'attribute' => __(':field :inter:model', [
'field' => __('validation.attributes.link'),
'field' => __('validation.custom.link'),
'inter' => __('validation.custom.of_the.female'),
'model' => trans_choice('models.classes.brand', 1),
]),

View file

@ -1,18 +1,39 @@
<table class="table-hover table-fix-action m-0 table">
@if (count($carouselModels) > 0)
<thead>
@include('bo.modules.table-col-sorter', [
'cols' => [
@php
$cols = [];
if (request()->section) {
$cols = array_merge($cols, [
'order' => Str::ucfirst(__('validation.custom.order')),
]);
}
$cols = array_merge($cols, [
'title' => Str::ucfirst(__('validation.attributes.title')),
'section_id' => Str::ucfirst(trans_choice('models.classes.section', 1)),
'published' => Str::ucfirst(__('validation.custom.activated')),
'updated_at' => Str::ucfirst(__('validation.attributes.updated_at')),
],
]);
@endphp
@include('bo.modules.table-col-sorter', [
$cols
])
</thead>
<tbody>
@foreach ($carouselModels as $carouselModel)
<tr class="border-bottom">
@php
$routeName = request()->route()->getName();
@endphp
@if (request()->section && empty(request()->search) && Session::get("$routeName.sort_col") === 'order')
@include('bo.modules.change-model-order-select', [
'routeName' => 'carousels',
'model' => $carouselModel,
])
@endif
<td class="text-center align-middle">{{ $carouselModel->title }}</td>
<td class="text-center">
<a class="btn btn-sm btn-primary" class="text-center align-middle"

View file

@ -102,6 +102,9 @@
</div>
@endforeach
</div>
<div
class="position-relative swiper-pagination swiper-networks-pagination swiper-pagination-clickable swiper-pagination-bullets swiper-pagination-horizontal d-flex justify-content-center align-items-center mt-5 flex-nowrap px-5">
</div>
</div>
<button class="swiper-button swiper-button-prev-custom swiper-networks-button-prev z-1 mx-3">
<img src="{{ Vite::asset('resources/assets/images/misc/blue-arrow.svg') }}"

View file

@ -189,6 +189,11 @@
@endphp
<div class="carousel slide carousel-fade pb-lg-5 pb-0" id="historical-milestone-carousel">
<div class="d-lg-none">
<button id="carousel-btn-prev" class="swiper-button swiper-button-prev-custom year-prev d-lg-none z-2"
data-bs-target="#historical-milestone-carousel" data-bs-slide-to="">
<img src="{{ Vite::asset('resources/assets/images/misc/blue-arrow.svg') }}"
title="{{ __('Navigation du slider (gauche)') }}" alt="{{ __('flèche vers la gauche') }}">
</button>
<div class="carousel-indicators justify-content-between">
<div class="swiper swiper-year w-100 h-fit overflow-visible" id="swiper-year">
<div class="swiper-wrapper">
@ -210,6 +215,11 @@
</div>
</div>
</div>
<button id="carousel-btn-next" class="swiper-button swiper-button-next-custom year-next d-lg-none z-2"
data-bs-target="#historical-milestone-carousel" data-bs-slide-to="">
<img src="{{ Vite::asset('resources/assets/images/misc/blue-arrow.svg') }}"
title="{{ __('Navigation du slider (droite)') }}" alt="{{ __('flèche vers la droite') }}">
</button>
</div>
<div class="d-none d-lg-block">
<div class="carousel-container justify-content-between">
@ -244,48 +254,48 @@
</div>
</div>
<div class="carousel-inner pb-lg-5 overflow-visible pb-4">
@foreach ($historicalMilestoneModels as $historicalMilestoneModel)
<div @class(['carousel-item bg-body', 'active' => $loop->first])>
{{-- We needed to include this picture twice because bootstrap took the picture height in its calculation for the carousel height --}}
@if ($historicalMilestoneModel->picture)
<img class="position-absolute picture d-none d-lg-block"
src="{{ $historicalMilestoneModel->picture }}"
title="{{ $historicalMilestoneModel->picture_title }}"
alt="{{ $historicalMilestoneModel->picture_alt }}">
@endif
<div class="row justify-content-center justify-content-lg-end">
<div class="col-12 col-lg-6">
<p class="year fw-bold text-{{ $colors[$loop->index % 4] }} pb-lg-0 mb-5 pb-5 text-center">
{{ $historicalMilestoneModel->year }}
</p>
</div>
@foreach ($historicalMilestoneModels as $historicalMilestoneModel)
<div @class(['carousel-item bg-body', 'active' => $loop->first])>
{{-- We needed to include this picture twice because bootstrap took the picture height in its calculation for the carousel height --}}
@if ($historicalMilestoneModel->picture)
<img class="position-relative picture d-lg-none" src="{{ $historicalMilestoneModel->picture }}"
<img class="position-absolute picture d-none d-lg-block"
src="{{ $historicalMilestoneModel->picture }}"
title="{{ $historicalMilestoneModel->picture_title }}"
alt="{{ $historicalMilestoneModel->picture_alt }}">
@endif
<div class="col-12 col-lg-3 text-lg-start d-flex flex-column justify-content-end">
<div class="slide-content mx-lg-0 p-lg-0 mx-auto w-fit rounded p-3">
<p class="fw-bold fs-5">
{{ $historicalMilestoneModel->title }}
<div class="row justify-content-center justify-content-lg-end">
<div class="col-12 col-lg-6">
<p class="year fw-bold text-{{ $colors[$loop->index % 4] }} pb-lg-0 mb-5 pb-5 text-center">
{{ $historicalMilestoneModel->year }}
</p>
@if ($historicalMilestoneModel->text)
<p>
{{ $historicalMilestoneModel->text }}
</div>
@if ($historicalMilestoneModel->picture)
<img class="position-relative picture d-lg-none" src="{{ $historicalMilestoneModel->picture }}"
title="{{ $historicalMilestoneModel->picture_title }}"
alt="{{ $historicalMilestoneModel->picture_alt }}">
@endif
<div class="col-12 col-lg-3 text-lg-start d-flex flex-column justify-content-end">
<div class="slide-content mx-lg-0 p-lg-0 mx-auto w-fit rounded p-3">
<p class="fw-bold fs-5">
{{ $historicalMilestoneModel->title }}
</p>
@if ($historicalMilestoneModel->text)
<p>
{{ $historicalMilestoneModel->text }}
</p>
@endif
</div>
@if ($historicalMilestoneModel->button_status)
<a class="btn btn-{{ $colors[$loop->index % 4] }} rounded-5 mt-3 text-inherit text-white"
href="{{ $historicalMilestoneModel->button_url }}"
@if ($historicalMilestoneModel->button_new_tab) target="_blank" @endif>
{{ $historicalMilestoneModel->button_label }}
</a>
@endif
</div>
@if ($historicalMilestoneModel->button_status)
<a class="btn btn-{{ $colors[$loop->index % 4] }} rounded-5 mt-3 text-inherit text-white"
href="{{ $historicalMilestoneModel->button_url }}"
@if ($historicalMilestoneModel->button_new_tab) target="_blank" @endif>
{{ $historicalMilestoneModel->button_label }}
</a>
@endif
</div>
</div>
</div>
@endforeach
@endforeach
</div>
</div>
</div>

View file

@ -1,6 +1,10 @@
@if (!empty($carousel))
<div class="row">
<div class="col-12 position-relative mb-5">
<div class="col-12 position-relative swiper-container mb-5">
<button class="swiper-button swiper-button-prev-custom carousel-prev z-2">
<img src="{{ Vite::asset('resources/assets/images/misc/blue-arrow.svg') }}"
title="{{ __('Navigation du slider (gauche)') }}" alt="{{ __('flèche vers la gauche') }}">
</button>
<div class="swiper swiper-carousels mt-lg-0 mt-5 overflow-visible" id="swiper-carousels">
<div class="swiper-wrapper h-fit">
@foreach ($carousel->media as $medias)
@ -18,6 +22,10 @@
class="position-relative swiper-pagination swiper-carousels-pagination swiper-pagination-clickable swiper-pagination-bullets swiper-pagination-horizontal d-flex justify-content-center align-items-center mt-5 flex-nowrap px-5">
</div>
</div>
<button class="swiper-button swiper-button-next-custom carousel-next z-2">
<img src="{{ Vite::asset('resources/assets/images/misc/blue-arrow.svg') }}"
title="{{ __('Navigation du slider (droite)') }}" alt="{{ __('flèche vers la droite') }}">
</button>
</div>
</div>
@endif

View file

@ -210,6 +210,10 @@ Route::middleware(['admin-secure'])->prefix('bo')
CarouselController::class,
'changePublished'
])->name('carousels.change-published');
Route::patch('/carousels/{carousel}/change-order', [
CarouselController::class,
'changeCustomOrder'
])->name('carousels.change-order');
// * BRANDS
Route::resource('brands', BrandController::class)->except('show')