feat: ✨ implement carousel CRUD + add fields to parity section + set fields as nullable on parity and formation sections
This commit is contained in:
parent
17b35bb20c
commit
5d77c5d158
28 changed files with 1752 additions and 74 deletions
222
app/Http/Controllers/Bo/CarouselController.php
Normal file
222
app/Http/Controllers/Bo/CarouselController.php
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Carousels\StoreCarouselRequest;
|
||||
use App\Http\Requests\Bo\Carousels\UpdateCarouselRequest;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Carousel;
|
||||
use App\Models\Section;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CarouselController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create the controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(Carousel::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index(Request $request): \Illuminate\Contracts\View\View
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $carouselModels */
|
||||
$carouselModels = Carousel::query()->with('section');
|
||||
$sectionModels = Section::query()->whereHas('carousels')->get();
|
||||
|
||||
$selectedSection = intval($request->section);
|
||||
|
||||
$carouselModels = $carouselModels->when($selectedSection, function ($query) use ($selectedSection) {
|
||||
$query->where('section_id', $selectedSection);
|
||||
});
|
||||
|
||||
/** @var string $search Search field */
|
||||
$search = $request->input('search');
|
||||
if ($search) {
|
||||
$this->searchQuery(
|
||||
$carouselModels,
|
||||
$search,
|
||||
null,
|
||||
'title',
|
||||
);
|
||||
}
|
||||
$searchFields = \implode(', ', [
|
||||
Str::ucFirst(trans('validation.attributes.title')),
|
||||
]);
|
||||
|
||||
/** Sort columns with a query */
|
||||
$this->sortQuery($carouselModels);
|
||||
|
||||
/** Custom pagination */
|
||||
$carouselModels = $this->paginate($carouselModels);
|
||||
|
||||
return view('bo.pages.carousels.index', compact(
|
||||
'carouselModels',
|
||||
'sectionModels',
|
||||
'selectedSection',
|
||||
'search',
|
||||
'searchFields'
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @param \App\Models\Carousel $carousel
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function create(Carousel $carousel): \Illuminate\Contracts\View\View
|
||||
{
|
||||
$sectionModels = Section::all();
|
||||
|
||||
return view('bo.pages.carousels.create', [
|
||||
'carouselModel' => $carousel,
|
||||
'sectionModels' => $sectionModels
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Carousels\StoreCarouselRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(StoreCarouselRequest $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$carousel = new Carousel();
|
||||
$carousel->fill($request->validated());
|
||||
|
||||
if (!Arr::exists($request->validated(), 'media')) {
|
||||
$carousel->media = [];
|
||||
}
|
||||
|
||||
// Set the new pictures files in the pictures array.
|
||||
collect($request->only(['media.pictures.*.path']))
|
||||
->each(
|
||||
function (mixed $value, string $attribute) use ($carousel) {
|
||||
foreach ($value['pictures']['*']['path'] as $index => $file) {
|
||||
if ($file instanceof \Illuminate\Http\UploadedFile) {
|
||||
$carousel->setAttribute(
|
||||
"{$attribute}->pictures->{$index}->path",
|
||||
FileStorageHelper::storeFile($carousel, $file, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if ($carousel->saveOrFail()) {
|
||||
return redirect()->route('bo.carousels.edit', $carousel)
|
||||
->with('success', trans('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(trans_choice('models.classes.carousel', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', trans('crud.messages.cannot_be_created', [
|
||||
'model' => Str::ucfirst(trans_choice('models.classes.carousel', 1))
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\Carousel $carousel
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function edit(Carousel $carousel): \Illuminate\Contracts\View\View
|
||||
{
|
||||
$sectionModels = Section::all();
|
||||
|
||||
return view('bo.pages.carousels.edit', [
|
||||
'carouselModel' => $carousel,
|
||||
'sectionModels' => $sectionModels
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Carousels\UpdateCarouselRequest $request
|
||||
* @param \App\Models\Carousel $carousel
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(UpdateCarouselRequest $request, Carousel $carousel): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request, $carousel) {
|
||||
$carousel->fill($request->validated());
|
||||
if (!Arr::exists($request->validated(), 'media')) {
|
||||
$carousel->media = [];
|
||||
}
|
||||
|
||||
// Set the new pictures files in the pictures array.
|
||||
collect($request->only(['media.pictures.*.path']))
|
||||
->each(
|
||||
function (mixed $value, string $attribute) use ($carousel) {
|
||||
foreach ($value['pictures']['*']['path'] as $index => $file) {
|
||||
if ($file instanceof \Illuminate\Http\UploadedFile) {
|
||||
$carousel->setAttribute(
|
||||
"{$attribute}->pictures->{$index}->path",
|
||||
FileStorageHelper::storeFile($carousel, $file, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
if ($carousel->saveOrFail()) {
|
||||
return redirect()->route('bo.carousels.edit', $carousel)
|
||||
->with('success', trans('crud.messages.has_been_updated', [
|
||||
'model' => Str::ucfirst(trans_choice('models.classes.carousel', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->route('bo.carousels.edit', $carousel)
|
||||
->with('error', trans('crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(trans_choice('models.classes.carousel', 1))
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Carousel $carousel
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Carousel $carousel): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
if ($carousel->deleteOrFail()) {
|
||||
return redirect()->route('bo.carousels.index')
|
||||
->with('success', trans('crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(trans_choice('models.classes.carousel', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', trans('crud.messages.cannot_be_deleted', [
|
||||
'model' => Str::ucfirst(trans_choice('models.classes.carousel', 1))
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate the specified resource.
|
||||
*
|
||||
* @param \App\Models\Carousel $carousel
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function duplicate(Carousel $carousel): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return $this->create($carousel->replicate());
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ use App\Http\Controllers\Controller;
|
|||
use App\Http\Requests\Fo\ListenFormRequest;
|
||||
use App\Mail\SendConfirmationMail;
|
||||
use App\Mail\SendContactMail;
|
||||
use App\Models\Carousel;
|
||||
use App\Models\HistoricalMilestone;
|
||||
use App\Models\JobVideo;
|
||||
use App\Models\Network;
|
||||
|
|
@ -266,15 +267,16 @@ class StaticPageController extends Controller
|
|||
*/
|
||||
public function hire(): \Illuminate\Contracts\View\View
|
||||
{
|
||||
$staticPageModel = StaticPage::query()->where('type', StaticPageType::we_hire)->first();
|
||||
$sectionModels = Section::query()->where('page_id', $staticPageModel->id)->get();
|
||||
$videoModels = JobVideo::query()->inRandomOrder()->simplePaginate(4);
|
||||
$testimonialModels = Testimonial::query()->where('type', TypeEnum::hire)->get();
|
||||
$compagnyLifeSection = $sectionModels->where('section', PageSectionEnum::hire_life)->first();
|
||||
$formationSection = $sectionModels->where('section', PageSectionEnum::hire_formation)->first();
|
||||
$testimonialSection = $sectionModels->where('section', PageSectionEnum::hire_grow)->first();
|
||||
$paritySection = $sectionModels->where('section', PageSectionEnum::hire_parity)->first();
|
||||
$findSection = $sectionModels->where('section', PageSectionEnum::hire_find)->first();
|
||||
$staticPageModel = StaticPage::query()->where('type', StaticPageType::we_hire)->first();
|
||||
$sectionModels = Section::query()->where('page_id', $staticPageModel->id)->get();
|
||||
$videoModels = JobVideo::query()->inRandomOrder()->simplePaginate(4);
|
||||
$testimonialModels = Testimonial::query()->where('type', TypeEnum::hire)->get();
|
||||
$compagnyLifeSection = $sectionModels->where('section', PageSectionEnum::hire_life)->first();
|
||||
$formationSection = $sectionModels->where('section', PageSectionEnum::hire_formation)->first();
|
||||
$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();
|
||||
$navLinks = Sticky::query()->where('type', StaticPageType::we_hire)->get();
|
||||
|
||||
return view('fo.pages.hire', \compact(
|
||||
|
|
@ -287,6 +289,7 @@ class StaticPageController extends Controller
|
|||
'testimonialSection',
|
||||
'paritySection',
|
||||
'findSection',
|
||||
'carouselSectionParity',
|
||||
'navLinks'
|
||||
));
|
||||
}
|
||||
|
|
|
|||
127
app/Http/Requests/Bo/Carousels/StoreCarouselRequest.php
Normal file
127
app/Http/Requests/Bo/Carousels/StoreCarouselRequest.php
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Carousels;
|
||||
|
||||
use App\Models\Section;
|
||||
use App\Rules\IsMaxChildrenReached;
|
||||
use App\Rules\WysiwygLength;
|
||||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreCarouselRequest extends FormRequest
|
||||
{
|
||||
use HasPicture;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth('backend')->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if ($this->input('media.pictures')) {
|
||||
foreach ($this->input('media.pictures') as $index => $pictureArray) {
|
||||
$this->mergePicture("media.pictures.$index.path");
|
||||
}
|
||||
}
|
||||
|
||||
$this->merge([
|
||||
'published' => $this->boolean('published'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'title' => 'required|string|between:3,255',
|
||||
'section_id' => [
|
||||
'required',
|
||||
Rule::exists('sections', 'id'),
|
||||
new IsMaxChildrenReached(new Section(), 'carousels', 5)
|
||||
],
|
||||
'media' => 'required|array',
|
||||
'media.pictures.*.alt' => 'sometimes|string|between:3,255',
|
||||
'media.pictures.*.title' => 'sometimes|string|between:3,255',
|
||||
'media.pictures.*.legend' => [
|
||||
'sometimes',
|
||||
'string',
|
||||
new WysiwygLength(3, 255),
|
||||
],
|
||||
'published' => 'required|boolean',
|
||||
];
|
||||
if ($this->input('media')) {
|
||||
if (Arr::exists($this->input('media'), 'pictures')) {
|
||||
foreach ($this->input('media')['pictures'] as $pictureIndex => $pictureArray) {
|
||||
$rules = array_merge(
|
||||
$rules,
|
||||
[
|
||||
"media.pictures.$pictureIndex.alt" => 'required|string|between:3,255',
|
||||
"media.pictures.$pictureIndex.title" => 'required|string|between:3,255',
|
||||
"media.pictures.$pictureIndex.legend" => [
|
||||
'sometimes',
|
||||
'string',
|
||||
new WysiwygLength(3, 255),
|
||||
],
|
||||
],
|
||||
$this->pictureRules("media.pictures.$pictureIndex.path", 500, 500, 500, 500, false),
|
||||
);
|
||||
}
|
||||
} //end if
|
||||
} //end if
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
$attributes = [
|
||||
'title' => trans('validation.attributes.title'),
|
||||
];
|
||||
if ($this->input('media') && array_key_exists('pictures', $this->input('media'))) {
|
||||
foreach ($this->input('media.pictures') as $index => $pictureArray) {
|
||||
$attributes = array_merge(
|
||||
$attributes,
|
||||
[
|
||||
"media.pictures.$index.alt" => trans(':field :inter:model', [
|
||||
'field' => trans('validation.custom.alt_attribute'),
|
||||
'inter' => trans('validation.custom.of_the.vowel'),
|
||||
'model' => sprintf('%s n°%d', trans('validation.custom.picture'), $index + 1),
|
||||
]),
|
||||
"media.pictures.$index.title" => trans(':field :inter:model', [
|
||||
'field' => trans('validation.custom.title_attribute'),
|
||||
'inter' => trans('validation.custom.of_the.vowel'),
|
||||
'model' => sprintf('%s n°%d', trans('validation.custom.picture'), $index + 1),
|
||||
]),
|
||||
"media.pictures.$index.legend" => trans(':field :inter:model', [
|
||||
'field' => trans('validation.custom.legend'),
|
||||
'inter' => trans('validation.custom.of_the.vowel'),
|
||||
'model' => sprintf('%s n°%d', trans('validation.custom.picture'), $index + 1),
|
||||
]),
|
||||
],
|
||||
);
|
||||
} //end foreach
|
||||
} //end if
|
||||
return $attributes;
|
||||
}
|
||||
}
|
||||
7
app/Http/Requests/Bo/Carousels/UpdateCarouselRequest.php
Normal file
7
app/Http/Requests/Bo/Carousels/UpdateCarouselRequest.php
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Carousels;
|
||||
|
||||
class UpdateCarouselRequest extends StoreCarouselRequest
|
||||
{
|
||||
}
|
||||
117
app/Models/Carousel.php
Normal file
117
app/Models/Carousel.php
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use App\Traits\Models\ActivityLog;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property-read int $id Id.
|
||||
* @property string $title Title.
|
||||
* @property array $media Medias of the carousel.
|
||||
* @property-read \Carbon\Carbon $created_at Created date.
|
||||
* @property-read \Carbon\Carbon $updated_at Updated date.
|
||||
*
|
||||
* @property-read \App\Models\ActivityLog $activityLogs Activities logs One-to-many relationship.
|
||||
*/
|
||||
class Carousel extends Model
|
||||
{
|
||||
use ActivityLog;
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'section_id',
|
||||
'title',
|
||||
'media',
|
||||
'order',
|
||||
'published',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'media' => 'array',
|
||||
'published' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
$otherCarousels = self::query()
|
||||
->whereRelation('section', 'id', $model->section->id)
|
||||
->count();
|
||||
if ($otherCarousels > 4) {
|
||||
throw new \RuntimeException('Creating more than 5 carousels is not allowed');
|
||||
}
|
||||
self::setOrder($model);
|
||||
});
|
||||
static::updated(function (self $model) {
|
||||
FileStorageHelper::removeOldFiles(
|
||||
data_get(ToolboxHelper::getOriginal($model, 'media'), 'pictures.*.path', ['']),
|
||||
data_get($model->media, 'pictures.*.path', [''])
|
||||
);
|
||||
});
|
||||
static::deleted(function (self $model) {
|
||||
FileStorageHelper::removeOldFiles(
|
||||
data_get($model->media, 'pictures.*.path', ['']),
|
||||
['']
|
||||
);
|
||||
self::setOrder($model, true);
|
||||
});
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
|
||||
/**
|
||||
* Set order after the last element of the list.
|
||||
*
|
||||
* @param self $model
|
||||
* @param boolean $isDeletion
|
||||
* @return void
|
||||
*/
|
||||
private static function setOrder(self $model, bool $isDeletion = false): void
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* Get the sections that owns the Carousel
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function section(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Section::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -486,4 +486,14 @@ class Section extends Model
|
|||
{
|
||||
return $this->belongsTo(StaticPage::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sections that own the carousel.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function carousels(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Carousel::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
98
app/Policies/CarouselPolicy.php
Normal file
98
app/Policies/CarouselPolicy.php
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserStaticRules;
|
||||
|
||||
class CarouselPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @return boolean
|
||||
*/
|
||||
public function viewAny(User $authUser): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 models.
|
||||
*
|
||||
* @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): 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);
|
||||
}
|
||||
}
|
||||
59
app/Rules/IsMaxChildrenReached.php
Normal file
59
app/Rules/IsMaxChildrenReached.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Check if the max count of children element is reached.
|
||||
*
|
||||
* @package App\Rules
|
||||
*/
|
||||
class IsMaxChildrenReached implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Construct method.
|
||||
*
|
||||
* @param Model $parentModel The parent Model.
|
||||
* @param string $relationName The name of the relation (used in whereRelation()).
|
||||
* @param integer $max The max count of children allowed.
|
||||
* @param integer|null $idToIgnore The id of the children model to ignore.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
private Model $parentModel,
|
||||
private string $relationName,
|
||||
private int $max,
|
||||
private int|null $idToIgnore = null
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @param \Closure(string):\Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
* @return void
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, \Closure $fail): void
|
||||
{
|
||||
$children = $this->parentModel->where('id', $value)->first()->{$this->relationName};
|
||||
if (!is_null($this->idToIgnore)) {
|
||||
$children = $children->where('id', '!==', $this->idToIgnore);
|
||||
}
|
||||
if ($children->count() >= $this->max) {
|
||||
$parentName = str($this->parentModel->getTable())->singular();
|
||||
$childrenName = str($this->parentModel->{$this->relationName}()->getModel()->getTable())->singular();
|
||||
|
||||
$fail(trans('crud.messages.rule_max_reached', [
|
||||
'parent' => str(trans_choice('models.classes.' . $parentName, 1))->title(),
|
||||
'max' => $this->max,
|
||||
'children' => str(trans_choice('models.classes.' . $childrenName, 1))->title()->plural()
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Sections\PageSectionEnum;
|
||||
use App\Models\Section;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private function registerMacros(): void
|
||||
{
|
||||
Collection::macro('insertAfter', function ($target, $value, $new_key = null) {
|
||||
if (is_int($target)) {
|
||||
$this->items = array_merge(array_slice($this->items, 0, $target + 1), [$value], array_slice($this->items, $target + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->items as $key => $old) {
|
||||
$new[$key] = $old;
|
||||
if ($key === $target && is_null($new_key)) {
|
||||
$new[] = $value;
|
||||
} elseif ($key === $target) {
|
||||
$new[$new_key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items = $new;
|
||||
});
|
||||
|
||||
Collection::macro('insertBefore', function ($target, $value, $new_key = null) {
|
||||
if (is_int($target)) {
|
||||
$this->items = array_merge(array_slice($this->items, 0, $target), [$value], array_slice($this->items, $target));
|
||||
return;
|
||||
}
|
||||
foreach ($this->items as $key => $old) {
|
||||
if ($key === $target && is_null($new_key)) {
|
||||
$new[] = $value;
|
||||
} elseif ($key === $target) {
|
||||
$new[$new_key] = $value;
|
||||
}
|
||||
$new[$key] = $old;
|
||||
}
|
||||
$this->items = $new;
|
||||
});
|
||||
|
||||
Collection::macro('replaceItem', function ($target, $value, $new_key = null) {
|
||||
$new = [];
|
||||
foreach ($this->items as $key => $old) {
|
||||
if ($key === $target) {
|
||||
$new[$new_key ?? $key] = $value;
|
||||
} else {
|
||||
$new[$key] = $old;
|
||||
}
|
||||
}
|
||||
$this->items = $new;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->registerMacros();
|
||||
|
||||
$hireParitySection = Section::query()->where('section', PageSectionEnum::hire_parity)->first();
|
||||
if ($hireParitySection) {
|
||||
$data = collect($hireParitySection->data);
|
||||
$contentImage = collect(Section::emptyPictureBloc(
|
||||
[],
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
1400,
|
||||
788,
|
||||
false,
|
||||
'Image n°1'
|
||||
))->first();
|
||||
|
||||
$data->replaceItem(
|
||||
"bloc_image_n1",
|
||||
$contentImage
|
||||
);
|
||||
|
||||
$contentTitle2 = collect(Section::emptyStringBloc(
|
||||
[],
|
||||
'Titre de la section n°2',
|
||||
'L\'humain au cœur de nos préoccupations',
|
||||
true
|
||||
))->first();
|
||||
|
||||
$data->insertBefore(
|
||||
"bloc_contenu_n2_content",
|
||||
$contentTitle2,
|
||||
"bloc_title_n2"
|
||||
);
|
||||
|
||||
$data->forget("bloc_image_n2");
|
||||
|
||||
$contentTitle3 = collect(Section::emptyStringBloc(
|
||||
[],
|
||||
'Titre de la section n°3',
|
||||
'Ensemble, on passe de bons moments',
|
||||
true
|
||||
))->first();
|
||||
|
||||
$data->insertBefore(
|
||||
"bloc_contenu_n3_content",
|
||||
$contentTitle3,
|
||||
"bloc_title_n3"
|
||||
);
|
||||
|
||||
$contentTitle4 = collect(Section::emptyStringBloc(
|
||||
[],
|
||||
'Titre de la section n°4',
|
||||
'Évènements conviviaux organisés par les sites :',
|
||||
true
|
||||
))->first();
|
||||
|
||||
$data->insertAfter(
|
||||
"bloc_contenu_n3_content",
|
||||
$contentTitle4,
|
||||
"bloc_title_n4"
|
||||
);
|
||||
|
||||
$contentDesc = collect(Section::emptyWysiwygBloc(
|
||||
[],
|
||||
'Contenu n°4',
|
||||
clean(\fake()->randomHtml()),
|
||||
))->first();
|
||||
|
||||
$data->insertAfter(
|
||||
"bloc_title_n4",
|
||||
$contentDesc,
|
||||
"bloc_contenu_n4_content"
|
||||
);
|
||||
|
||||
$hireParitySection->data = $data->all();
|
||||
$hireParitySection->saveQuietly();
|
||||
}
|
||||
|
||||
$hireFormationSection = Section::query()->where('section', PageSectionEnum::hire_formation)->first();
|
||||
if ($hireFormationSection) {
|
||||
$data = collect($hireFormationSection->data);
|
||||
$contentImage = collect(Section::emptyPictureBloc(
|
||||
[],
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
1400,
|
||||
788,
|
||||
false,
|
||||
'Image n°1'
|
||||
))->first();
|
||||
|
||||
$data->replaceItem(
|
||||
"bloc_image_n1",
|
||||
$contentImage
|
||||
);
|
||||
|
||||
$hireFormationSection->data = $data->all();
|
||||
$hireFormationSection->saveQuietly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->registerMacros();
|
||||
|
||||
$hireParitySection = Section::query()->where('section', PageSectionEnum::hire_parity)->first();
|
||||
if ($hireParitySection) {
|
||||
$data = collect($hireParitySection->data)->except(
|
||||
"bloc_title_n2",
|
||||
"bloc_title_n3",
|
||||
"bloc_title_n4",
|
||||
"bloc_contenu_n4_content",
|
||||
);
|
||||
|
||||
$contentImage = collect(Section::emptyPictureBloc(
|
||||
[],
|
||||
'storage/modelfiles/sections/25/09/19/c2/20241013-103605-68cd8a6285dac.jpg',
|
||||
'Mobilisation de l\'ensemble de nos équipes lors de la campagne Octobre Rose 2023',
|
||||
'Mobilisation de l\'ensemble de nos équipes lors de la campagne Octobre Rose 2023',
|
||||
1400,
|
||||
788,
|
||||
true,
|
||||
'Image n°1'
|
||||
))->first();
|
||||
|
||||
$data->replaceItem(
|
||||
"bloc_image_n1",
|
||||
$contentImage
|
||||
);
|
||||
|
||||
$contentImage2 = collect(Section::emptyPictureBloc(
|
||||
[],
|
||||
'storage/modelfiles/sections/25/09/19/c1/dsc01088-68cd8c5dc85bd.jpg',
|
||||
'Animations sommeils : Gerer le sommeil quand on est en horaires décalés',
|
||||
'Animations sommeils : Gerer le sommeil quand on est en horaires décalés',
|
||||
1400,
|
||||
788,
|
||||
true,
|
||||
'Image n°2'
|
||||
))->first();
|
||||
|
||||
$data->insertBefore(
|
||||
"bloc_contenu_n2_content",
|
||||
$contentImage2,
|
||||
"bloc_image_n2"
|
||||
);
|
||||
|
||||
$hireParitySection->data = $data->all();
|
||||
$hireParitySection->saveQuietly();
|
||||
}
|
||||
|
||||
$hireFormationSection = Section::query()->where('section', PageSectionEnum::hire_formation)->first();
|
||||
if ($hireFormationSection) {
|
||||
$data = collect($hireFormationSection->data);
|
||||
$contentImage = collect(Section::emptyPictureBloc(
|
||||
[],
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
1400,
|
||||
788,
|
||||
true,
|
||||
'Image n°1'
|
||||
))->first();
|
||||
|
||||
$data->replaceItem(
|
||||
"bloc_image_n1",
|
||||
$contentImage
|
||||
);
|
||||
|
||||
$hireFormationSection->data = $data->all();
|
||||
$hireFormationSection->saveQuietly();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?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('carousels', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('section_id')->constrained('sections')->comment('Id of the section associated');
|
||||
$table->string('title')->comment('Title of the carousel');
|
||||
$table->json('media')->comment('0-5 images + alt/title/legend');
|
||||
$table->integer('order')->comment('Position of the carousels');
|
||||
$table->boolean('published')->comment('Is the carousel published ?');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('carousels');
|
||||
}
|
||||
};
|
||||
|
|
@ -247,7 +247,7 @@ class SectionSeeder extends Seeder
|
|||
new \SplFileInfo(\resource_path(
|
||||
'../database/factories/assets/sections/default-picture-1400x788.png'
|
||||
))
|
||||
), fake()->sentence(), fake()->sentence(), 1400, 788, true, 'Bannière');
|
||||
), fake()->sentence(), fake()->sentence(), 1400, 788, false, 'Bannière');
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$section->emptyWysiwygBloc([], "Contenu n°1", clean(\fake()->randomHtml()))
|
||||
|
|
@ -257,21 +257,31 @@ class SectionSeeder extends Seeder
|
|||
new \SplFileInfo(\resource_path(
|
||||
'../database/factories/assets/sections/default-picture-1400x788.png'
|
||||
))
|
||||
), fake()->sentence(), fake()->sentence(), 1400, 788, true, 'Image n°1'));
|
||||
), fake()->sentence(), fake()->sentence(), 1400, 788, false, 'Image n°1'));
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$section->emptyStringBloc([], "Titre de la section n°2", \fake()->text(50))
|
||||
);
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$section->emptyWysiwygBloc([], "Contenu n°2", clean(\fake()->randomHtml()))
|
||||
);
|
||||
$data = array_merge($data, $section->emptyPictureBloc([], FileStorageHelper::storeFile(
|
||||
new Section(),
|
||||
new \SplFileInfo(\resource_path(
|
||||
'../database/factories/assets/sections/default-picture-1400x788.png'
|
||||
))
|
||||
), fake()->sentence(), fake()->sentence(), 1400, 788, true, 'Image n°2'));
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$section->emptyStringBloc([], "Titre de la section n°3", \fake()->text(50))
|
||||
);
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$section->emptyWysiwygBloc([], "Contenu n°3", clean(\fake()->randomHtml()))
|
||||
);
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$section->emptyStringBloc([], "Titre de la section n°4", \fake()->text(50))
|
||||
);
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$section->emptyWysiwygBloc([], "Contenu n°4", clean(\fake()->randomHtml()))
|
||||
);
|
||||
$data = array_merge($data, $section->emptyButtonBooleanBloc([], 'On Bouge Ensemble'));
|
||||
|
||||
return $data;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ return [
|
|||
'right' => 'Vous n\'avez pas les droits pour accéder à cette partie !',
|
||||
'theme_updated' => 'Le thème a été mis à jour !',
|
||||
'user_unpublished' => 'Vous ne pouvez pas désactiver votre propre compte !',
|
||||
'rule_max_reached' => 'L\'élement :parent ne peut pas avoir plus de :max :children',
|
||||
],
|
||||
'minicrud' => [
|
||||
'visibility_label' => 'Visibilité',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ return [
|
|||
'network' => 'réseau|réseaux',
|
||||
'aisle' => 'rayon',
|
||||
'brand' => 'marque',
|
||||
'carousel' => 'carrousel|carrousels'
|
||||
'sticky' => 'menu d\'ancre|menu d\'ancres'
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -29,3 +29,4 @@
|
|||
// * PARTIALS
|
||||
@import "./partials/slider";
|
||||
@import "./partials/testimonial-card";
|
||||
@import "./partials/carousel";
|
||||
|
|
|
|||
31
resources/sass/fo/partials/_carousel.scss
Normal file
31
resources/sass/fo/partials/_carousel.scss
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
@use "sass:color";
|
||||
|
||||
.swiper-carousels {
|
||||
.swiper-carousels-pagination {
|
||||
bottom: 2rem;
|
||||
left: 5%;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
top: auto;
|
||||
bottom: .5rem;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.swiper-pagination-bullet {
|
||||
width: 3rem;
|
||||
max-width: 10rem;
|
||||
height: .5rem;
|
||||
background-color: color.adjust($color-dark, $alpha: -0.8);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.swiper-pagination-bullet-active {
|
||||
background-color: $color-primary;
|
||||
}
|
||||
}
|
||||
306
resources/ts/bo/components/ListItemComponent.vue
Normal file
306
resources/ts/bo/components/ListItemComponent.vue
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
<template>
|
||||
<div
|
||||
:class="`list-item-component list-item-${inputParameters.name}`"
|
||||
ref="listItem"
|
||||
>
|
||||
<table
|
||||
v-if="itemParameters.list.length > 0"
|
||||
class="table text-center table-sm table-hover table-bordered m-0"
|
||||
ref="tableElement"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
v-for="(column, columnIndex) of itemParameters.columns"
|
||||
:key="columnIndex"
|
||||
scope="col"
|
||||
>
|
||||
{{ column[1] }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
v-if="buttonsDisplayParameters.remove"
|
||||
class="table-action"
|
||||
>
|
||||
<!-- Laisser vide -->
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-group-divider">
|
||||
<tr
|
||||
v-for="(item, itemIndex) of itemParameters.list"
|
||||
:key="itemIndex"
|
||||
>
|
||||
<td
|
||||
class="align-top"
|
||||
v-for="(column, columnIndex) of itemParameters.columns"
|
||||
:key="columnIndex"
|
||||
>
|
||||
<div
|
||||
v-if="itemParameters.type[columnIndex] == 'boolean'"
|
||||
class="form-check form-switch d-flex justify-content-center align-items-center p-0"
|
||||
>
|
||||
<input
|
||||
class="form-check-input m-0"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
:name="`${inputParameters.name}[${String(itemIndex)}][${column[0]}]`"
|
||||
:checked="Boolean(item[column[0] as keyof typeof item])"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="itemParameters.type[columnIndex] == 'wysiwyg'"
|
||||
:class="(getErrorMessage(itemIndex, column) !== null) ? 'is-invalid' : ''"
|
||||
>
|
||||
<CkEditorComponent
|
||||
:name="`${inputParameters.name}[${String(itemIndex)}][${column[0]}]`"
|
||||
:placeholder="inputParameters.placeholder[columnIndex]"
|
||||
:dbtext="String(item[column[0] as keyof typeof item] ?? '')"
|
||||
:required="true"
|
||||
:errors="allErrors"
|
||||
:editor-config="wysiwygConfig"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="itemParameters.type[columnIndex] == 'picture'"
|
||||
:class="(getErrorMessage(itemIndex, column) !== null) ? 'is-invalid' : ''"
|
||||
>
|
||||
<ImageInputComponent
|
||||
:id="'picture-input-' + id + itemIndex"
|
||||
:required="true"
|
||||
:show-labels="true"
|
||||
:name="`${inputParameters.name}[${String(itemIndex)}][${column[0]}]`"
|
||||
:helper="trans.methods.__('crud.minicrud.dimensions_must_be', {
|
||||
widthpx: (imageSizes[0] ?? String(500)) + 'px',
|
||||
heightpx: (imageSizes[1] ?? String(500)) + 'px',
|
||||
})"
|
||||
:width="imageSizes[0] ?? 500"
|
||||
:height="imageSizes[1] ?? 500"
|
||||
:value="item[column[0] as keyof typeof item] ? String(item[column[0] as keyof typeof item]) : undefined"
|
||||
:preview="false"
|
||||
:browse-event="false"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
v-else
|
||||
:class="['form-control form-control-sm', (getErrorMessage(itemIndex, column) !== null) ? 'is-invalid' : '']"
|
||||
:name="`${inputParameters.name}[${String(itemIndex)}][${column[0]}]`"
|
||||
:type="itemParameters.type[columnIndex]"
|
||||
v-model="item[column[0] as keyof typeof item]"
|
||||
:placeholder="inputParameters.placeholder[columnIndex]"
|
||||
:min="itemParameters.type[columnIndex] == 'number' ? '0' : undefined"
|
||||
>
|
||||
<small class="text-danger m-0">
|
||||
{{ getErrorMessage(itemIndex, column) }}
|
||||
</small>
|
||||
</td>
|
||||
<td
|
||||
v-if="buttonsDisplayParameters.remove"
|
||||
class="align-bottom"
|
||||
>
|
||||
<button
|
||||
@click.prevent="removeLine(itemIndex)"
|
||||
class="btn btn-sm btn-danger"
|
||||
:title="trans.methods.__('Supprimer la ligne')"
|
||||
data-bs-toggle="tooltip"
|
||||
>
|
||||
<i class="fa-solid fa-trash" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-else>
|
||||
<p>{{ trans.methods.__("Aucun élément renseigné(s).") }}</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="buttonsDisplayParameters.add"
|
||||
class="text-center mt-1"
|
||||
>
|
||||
<button
|
||||
@click.prevent="addLine"
|
||||
class="btn btn-sm btn-primary"
|
||||
type="button"
|
||||
:title="trans.methods.__('Ajouter une ligne')"
|
||||
data-bs-toggle="tooltip"
|
||||
>
|
||||
<i class="fa-solid fa-plus" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineOptions, onMounted, reactive, ref, useAttrs } from "vue";
|
||||
import { Tooltips } from "./../../modules/bsTooltips";
|
||||
import parseLaravelErrorMessages from "./../../modules/parseLaravelErrorMessages";
|
||||
import trans from "./../../modules/trans";
|
||||
import CkEditorComponent from "./CkEditorComponent.vue";
|
||||
import ImageInputComponent from "./ImageInputComponent.vue";
|
||||
|
||||
defineOptions({
|
||||
name: "ListItemComponent"
|
||||
});
|
||||
|
||||
// * ATTRIBUTES
|
||||
const attrs = useAttrs();
|
||||
|
||||
// * REFS
|
||||
const listItem = ref<HTMLDivElement|null>(null);
|
||||
const tableElement = ref<HTMLTableElement|null>(null);
|
||||
|
||||
// * DATA
|
||||
const tooltips = ref<Tooltips | null>(null);
|
||||
const allErrors = ref<LaravelValidationErrors>({});
|
||||
const wysiwygConfig = ref<string>();
|
||||
const id = ref<string>();
|
||||
|
||||
/** Input parameters. */
|
||||
const inputParameters = reactive<{
|
||||
name: string,
|
||||
placeholder: Array<string>,
|
||||
}>({
|
||||
name: "",
|
||||
placeholder: [""],
|
||||
});
|
||||
|
||||
/** Item parameters. */
|
||||
const itemParameters = reactive<{
|
||||
list: Array<Object>,
|
||||
columns: Array<string[]>,
|
||||
limits: Array<number>,
|
||||
type: Array<string>,
|
||||
}>({
|
||||
list: [{}],
|
||||
columns: [[""]],
|
||||
limits: [0, 0],
|
||||
type: [""],
|
||||
});
|
||||
|
||||
/** Image Sizes. */
|
||||
let imageSizes = [] as Array<number|null>;
|
||||
|
||||
/** Buttons parameters. */
|
||||
const buttonsDisplayParameters = reactive<{
|
||||
add: boolean,
|
||||
remove: boolean,
|
||||
}>({
|
||||
add: true,
|
||||
remove: true,
|
||||
});
|
||||
|
||||
// * COMPUTED
|
||||
const isUsedWithProps = computed<boolean>(() => attrs.json === undefined);
|
||||
|
||||
// * LIFECYCLE
|
||||
onMounted(() => {
|
||||
const json = String(attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
if (!isUsedWithProps.value) {
|
||||
/** Input parameters. */
|
||||
inputParameters.name = data.inputName;
|
||||
inputParameters.placeholder = data.inputPlaceholder;
|
||||
/** Item parameters. */
|
||||
itemParameters.list = data.itemList;
|
||||
itemParameters.columns = data.itemColumns;
|
||||
itemParameters.limits = data.itemLimit;
|
||||
itemParameters.type = data.itemType;
|
||||
imageSizes = data.imageSizes;
|
||||
/** Global parameters */
|
||||
allErrors.value = data.errors;
|
||||
id.value = data.id ?? "";
|
||||
|
||||
/** Ckeditor specific parameter */
|
||||
wysiwygConfig.value = String(data.wysiwygConfig);
|
||||
}
|
||||
setDefaultLines();
|
||||
checkLengthList();
|
||||
initTooltips();
|
||||
});
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Set default line(s)
|
||||
* according to the minimum limit defined.
|
||||
*/
|
||||
function setDefaultLines(): void {
|
||||
if (itemParameters.list.length < 1) {
|
||||
for (let index = 0; index < itemParameters.limits[0]; index++) {
|
||||
addLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new line to the <table>,
|
||||
* add a new element to the array,
|
||||
* show remove line button.
|
||||
*/
|
||||
function addLine(): void {
|
||||
itemParameters.list.push({});
|
||||
if (buttonsDisplayParameters.remove == false) {
|
||||
buttonsDisplayParameters.remove = true;
|
||||
}
|
||||
checkLengthList();
|
||||
tooltips.value?.closeBootstrapTooltip();
|
||||
initTooltips();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a specific line from the <table>,
|
||||
* remove a specific element to the array,
|
||||
* show adding line button.
|
||||
*/
|
||||
function removeLine(itemIndex: number): void {
|
||||
itemParameters.list.splice(itemIndex, 1);
|
||||
if (buttonsDisplayParameters.add == false) {
|
||||
buttonsDisplayParameters.add = true;
|
||||
}
|
||||
checkLengthList();
|
||||
tooltips.value?.closeBootstrapTooltip();
|
||||
initTooltips();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the length of the list,
|
||||
* update variables accordingly.
|
||||
*/
|
||||
function checkLengthList(): void {
|
||||
/** Length minimum */
|
||||
if (itemParameters.list.length <= itemParameters.limits[0]) {
|
||||
buttonsDisplayParameters.remove = false;
|
||||
}
|
||||
/** Length maximum */
|
||||
if (itemParameters.list.length >= itemParameters.limits[1]) {
|
||||
buttonsDisplayParameters.add = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Return error message or null. */
|
||||
function getErrorMessage(itemIndex: number, column: string[]): string|null {
|
||||
return parseLaravelErrorMessages.methods.parseValidationInput(
|
||||
`${inputParameters.name}.${String(itemIndex)}.${column[0]}`,
|
||||
allErrors.value
|
||||
);
|
||||
}
|
||||
|
||||
/** Initialise all tooltips in the component. */
|
||||
function initTooltips(): void {
|
||||
setTimeout(() => {
|
||||
tooltips.value = Tooltips.make({
|
||||
type: "dom",
|
||||
elements: listItem.value!.querySelectorAll("[data-bs-toggle=\"tooltip\"]")
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.list-item-component .table-action {
|
||||
width: 2rem;
|
||||
}
|
||||
.list-item-component .table {
|
||||
table-layout: fixed;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -346,6 +346,42 @@ 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>;
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@
|
|||
{{-- WE HIRE --}}
|
||||
<li class="nav-item">
|
||||
<div class="dropdown">
|
||||
<a class="nav-link @if (url()->current() === route('bo.static_pages.edit', $hireStaticPageModel->type->value()) || $isActiveHire || request()->routeIs('bo.job_video.*') || request()->query('search') === $respectSlides->label() || request()->routeIs('bo.testimonials.*')) active @endif dropdown-toggle" data-bs-toggle="dropdown" type="button"
|
||||
<a class="nav-link @if (url()->current() === route('bo.static_pages.edit', $hireStaticPageModel->type->value()) || $isActiveHire || request()->routeIs('bo.job_video.*') || request()->query('search') === $respectSlides->label() || request()->routeIs('bo.testimonials.*') || request()->routeIs('bo.carousels.*')) active @endif dropdown-toggle" data-bs-toggle="dropdown" type="button"
|
||||
aria-expanded="false">
|
||||
{{ $hireStaticPageModel->type->label() }}
|
||||
</a>
|
||||
|
|
@ -152,6 +152,15 @@
|
|||
</a>
|
||||
</li>
|
||||
@endcan
|
||||
@can('viewAny', \App\Models\Carousel::class)
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if (request()->routeIs('bo.carousels.*')) active @endif" data-bs-toggle="tooltip"
|
||||
href="{{ route('bo.carousels.index') }}"
|
||||
title="{{ __('crud.helpers.list_all', ['model' => trans_choice('models.classes.carousel', \INF)]) }}">
|
||||
{{ Str::ucfirst(trans_choice('models.classes.carousel', \INF)) }}
|
||||
</a>
|
||||
</li>
|
||||
@endcan
|
||||
@can('viewAny', \App\Models\Testimonial::class)
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if (request()->routeIs('bo.testimonials.*')) active @endif" data-bs-toggle="tooltip"
|
||||
|
|
|
|||
36
resources/views/bo/pages/carousels/create.blade.php
Normal file
36
resources/views/bo/pages/carousels/create.blade.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
@extends('bo.layout', ['brParam' => $carouselModel])
|
||||
|
||||
@section('title', __('Création d\'un :model', ['model' => trans_choice('models.classes.carousel', 1)]))
|
||||
@section('description', __('Création d\'un tout nouveau :model', ['model' => trans_choice('models.classes.carousel', 1)]))
|
||||
@section('breadcrumb', request()->route()->getName())
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-3">
|
||||
<div class="d-flex align-items-start flex-row">
|
||||
<a class="btn btn-primary text-decoration-none m-0" data-bs-toggle="tooltip" href="{{ route('bo.carousels.index') }}"
|
||||
title="{{ __('crud.helpers.list_all', ['model' => trans_choice('models.classes.carousel', 1)]) }}">
|
||||
<i class="fa-solid fa-arrow-left"></i>
|
||||
</a>
|
||||
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $carouselModel])
|
||||
</div>
|
||||
<div class="mb-md-0 mb-2">
|
||||
<button class="btn btn-primary" id="formSubmitClone" data-bs-toggle="tooltip" type="submit"
|
||||
title="{{ __('crud.helpers.save_model', ['model' => trans_choice('models.classes.carousel', 1)]) }}">
|
||||
{{ Str::ucfirst(__('crud.actions.save')) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form id="create-or-edit-form" action="{{ route('bo.carousels.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@include('bo.pages.carousels.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.carousel', 1)]) }}">
|
||||
{{ Str::ucfirst(__('crud.actions.save')) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
52
resources/views/bo/pages/carousels/edit.blade.php
Normal file
52
resources/views/bo/pages/carousels/edit.blade.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
@extends('bo.layout', ['brParam' => $carouselModel])
|
||||
|
||||
@section('title', __('Édition d\'un :model', ['model' => trans_choice('models.classes.carousel', 1)]))
|
||||
@section('description', __('Édition d\'un :model déjà existant', ['model' => trans_choice('models.classes.carousel', 1)]))
|
||||
@section('breadcrumb', request()->route()->getName())
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-3">
|
||||
<div class="d-flex align-items-start flex-row">
|
||||
<a class="btn btn-primary text-decoration-none m-0" data-bs-toggle="tooltip" href="{{ route('bo.carousels.index') }}"
|
||||
title="{{ __('crud.helpers.list_all', ['model' => trans_choice('models.classes.carousel', '*')]) }}">
|
||||
<i class="fa-solid fa-arrow-left"></i>
|
||||
</a>
|
||||
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $carouselModel])
|
||||
</div>
|
||||
<div class="mb-md-0 mb-2">
|
||||
<form class="btn-group confirmDeleteTS" action="{{ route('bo.carousels.destroy', $carouselModel) }}" method="POST" novalidate>
|
||||
@can('duplicate', $carouselModel)
|
||||
<a class="btn btn-secondary" data-bs-toggle="tooltip" href="{{ route('bo.carousels.duplicate', ['carousel' => $carouselModel]) }}"
|
||||
title="{{ Str::ucfirst(__('crud.actions.duplicate')) }}">
|
||||
<i class="fa-solid fa-copy"></i>
|
||||
</a>
|
||||
@endcan
|
||||
<button class="btn btn-primary" id="formSubmitClone" data-bs-toggle="tooltip" type="submit"
|
||||
title="{{ __('crud.helpers.save_model', ['model' => trans_choice('models.classes.carousel', 1)]) }}">
|
||||
{{ Str::ucfirst(__('crud.actions.save')) }}
|
||||
</button>
|
||||
@can('delete', $carouselModel)
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button class="btn btn-danger" data-bs-toggle="tooltip" type="submit" title="{{ Str::ucfirst(__('crud.actions.delete')) }}">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
@endcan
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<form action="{{ route('bo.carousels.update', $carouselModel) }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
@include('bo.pages.carousels.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.carousel', 1)]) }}">
|
||||
{{ Str::ucfirst(__('crud.actions.save')) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
103
resources/views/bo/pages/carousels/form.blade.php
Normal file
103
resources/views/bo/pages/carousels/form.blade.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<div class="row">
|
||||
{{-- TITLE --}}
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
<fieldset class="h-100 border p-3">
|
||||
<legend>{{ Str::ucfirst(__('validation.attributes.title')) }}</legend>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => 'carouselTitle',
|
||||
'label' => __(':field :inter:model', [
|
||||
'field' => Str::ucfirst(__('validation.attributes.title')),
|
||||
'inter' => __('validation.custom.of_the.male'),
|
||||
'model' => trans_choice('models.classes.carousel', 1),
|
||||
]),
|
||||
'hasWordCounter' => true,
|
||||
'name' => 'title',
|
||||
'required' => true,
|
||||
'model' => $carouselModel ?? null,
|
||||
'helper' => __('validation.between.string', [
|
||||
'attribute' => __(':field :inter:model', [
|
||||
'field' => __('validation.attributes.title'),
|
||||
'inter' => __('validation.custom.of_the.male'),
|
||||
'model' => trans_choice('models.classes.carousel', 1),
|
||||
]),
|
||||
'min' => 3,
|
||||
'max' => 100,
|
||||
]),
|
||||
'placeholder' => __('Un nom clair et concis'),
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
{{-- SECTION_ID --}}
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
<fieldset class="h-100 border p-3">
|
||||
<legend>{{ Str::ucfirst(trans_choice('models.classes.section', 1)) }}</legend>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('bo.partials.inputs.bigint-foreign-id', [
|
||||
'id' => 'carouselSection',
|
||||
'label' => __(':field du :model', [
|
||||
'field' => Str::ucFirst(trans_choice('models.classes.section', '1')),
|
||||
'model' => trans_choice('models.classes.carousel', 1),
|
||||
]),
|
||||
'name' => 'section_id',
|
||||
'required' => true,
|
||||
'model' => $carouselModel ?? null,
|
||||
'associatedModels' => $sectionModels ?? null,
|
||||
'foreignLabel' => 'h2_title',
|
||||
'helper' => __('crud.minicrud.enum_select', [
|
||||
'enum' => trans_choice('models.classes.section', '1'),
|
||||
]),
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
{{-- MEDIA --}}
|
||||
<div class="col-12 mb-3">
|
||||
<fieldset class="h-100 border p-3">
|
||||
<legend>{{ str(__('validation.custom.media'))->ucFirst()->plural() }} *</legend>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('bo.partials.inputs.text-list-items', [
|
||||
'id' => 'carouselMediaPictures',
|
||||
'name' => 'media[pictures]',
|
||||
'label' => __('Liste d\':field', ['field' => str(__('validation.attributes.image'))->plural()]),
|
||||
'model' => $carouselModel ?? null,
|
||||
'value' => $carouselModel->media['pictures'] ?? [],
|
||||
'itemColumns' => [['path', 'Image'], ['alt', 'Texte alternatif'], ['title', 'Titre'], ['legend', 'Légende']],
|
||||
'itemType' => ['picture', 'text', 'text', 'wysiwyg'],
|
||||
'inputPlaceholder' => ['', 'Un texte clair et concis', 'Un texte clair et concis', 'Un texte clair et concis'],
|
||||
'itemLimit' => [1, 5],
|
||||
'imageSizes' => [500, 500],
|
||||
'required' => true,
|
||||
'helper' => __('Insérez les images liées à cet évènement ainsi que leur texte alternatif, titre et légende.'),
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
{{-- PUBLISHED --}}
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
<fieldset class="h-100 border p-3">
|
||||
<legend>{{ Str::ucfirst(__('crud.minicrud.visibility_label')) }}</legend>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('bo.partials.inputs.tinyint-boolean', [
|
||||
'id' => 'carouselPublished',
|
||||
'name' => 'published',
|
||||
'model' => $carouselModel ?? null,
|
||||
'label' => __('crud.minicrud.visibility_label'),
|
||||
'helper' => __('crud.minicrud.visibility_helper'),
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="col-12 text-center">
|
||||
<p><b>{{ __('validation.custom.required_fields') }}</b></p>
|
||||
</div>
|
||||
</div>
|
||||
71
resources/views/bo/pages/carousels/index-list.blade.php
Normal file
71
resources/views/bo/pages/carousels/index-list.blade.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<table class="table-hover table-fix-action m-0 table">
|
||||
@if (count($carouselModels) > 0)
|
||||
<thead>
|
||||
@include('bo.modules.table-col-sorter', [
|
||||
'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')),
|
||||
],
|
||||
])
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($carouselModels as $carouselModel)
|
||||
<tr class="border-bottom">
|
||||
<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"
|
||||
href="{{ route('bo.sections.edit', $carouselModel->section) }}"
|
||||
@cannot('edit', $carouselModel->section) disabled @endcannot>
|
||||
{{ $carouselModel->section->h2_title }}</a>
|
||||
</td>
|
||||
@include('bo.modules.change-published-status', [
|
||||
'routeName' => 'carousels',
|
||||
'model' => $carouselModel,
|
||||
])
|
||||
<td class="text-center align-middle">
|
||||
<span class="badge bg-secondary">{{ $carouselModel->updated_at->isoFormat('LLLL') }}</span>
|
||||
</td>
|
||||
<td class="text-end align-middle">
|
||||
@canAny(['delete', 'duplicate', 'update'], $carouselModel)
|
||||
<form class="btn-group confirmDeleteTS" action="{{ route('bo.carousels.destroy', $carouselModel) }}" method="POST" novalidate>
|
||||
@can('duplicate', $carouselModel)
|
||||
<a class="btn btn-sm btn-secondary" data-bs-toggle="tooltip"
|
||||
href="{{ route('bo.carousels.duplicate', ['carousel' => $carouselModel]) }}"
|
||||
title="{{ Str::ucfirst(__('crud.actions.duplicate')) }}">
|
||||
<i class="fa-solid fa-copy"></i>
|
||||
</a>
|
||||
@endcan
|
||||
@can('update', $carouselModel)
|
||||
<a class="btn btn-sm btn-primary" data-bs-toggle="tooltip"
|
||||
href="{{ route('bo.carousels.edit', ['carousel' => $carouselModel]) }}"
|
||||
title="{{ Str::ucfirst(__('crud.actions.update')) }}">
|
||||
<i class="fa-solid fa-pencil"></i>
|
||||
</a>
|
||||
@endcan
|
||||
@can('delete', $carouselModel)
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button class="btn btn-sm btn-danger" data-bs-toggle="tooltip" type="submit"
|
||||
title="{{ Str::ucfirst(__('crud.actions.delete')) }}">
|
||||
<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', 1, ['model' => trans_choice('models.classes.carousel', 1)]) }}
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
</table>
|
||||
42
resources/views/bo/pages/carousels/index.blade.php
Normal file
42
resources/views/bo/pages/carousels/index.blade.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
@extends('bo.layout')
|
||||
|
||||
@section('title', Str::ucfirst(trans_choice('models.classes.carousel', 1)))
|
||||
@section('description', __('Liste de tous les :model', ['model' => trans_choice('models.classes.carousel', '*')]))
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap">
|
||||
@include('modules.breadcrumbs.breadcrumb-body')
|
||||
<form id="carouselSection" action="{{ route('bo.carousels.index') }}">
|
||||
<div class="input-group justify-content-end">
|
||||
<select class="form-select w-fit" id="selectCarouselSection" name="section">
|
||||
<option value="" @if ($selectedSection === null) selected @endif>
|
||||
{{ __('Tous les carrousels') }}
|
||||
</option>
|
||||
@foreach ($sectionModels as $sectionModel)
|
||||
<option value="{{ $sectionModel->id }}" @if ($selectedSection === $sectionModel->id) selected @endif>
|
||||
{{ $sectionModel->h2_title }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@push('scripts')
|
||||
<script nonce="{{ $nonce }}">
|
||||
document.getElementById('selectCarouselSection').addEventListener('change', function(e) {
|
||||
document.getElementById('carouselSection').submit();
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@can('create', \App\Models\Carousel::class)
|
||||
<a class="btn btn-primary float-right" data-bs-toggle="tooltip" href="{{ route('bo.carousels.create') }}"
|
||||
title="{{ __('crud.helpers.create_model', ['model' => trans_choice('models.classes.carousel', 1)]) }}">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@include('bo.modules.search-bar')
|
||||
<div class="table mb-3">
|
||||
@include('bo.pages.carousels.index-list')
|
||||
</div>
|
||||
{{ $carouselModels->links() }}
|
||||
@endsection
|
||||
|
|
@ -1,31 +1,21 @@
|
|||
<div class="container p-0 m-0">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label for="{{ $name }}" class="col-form-label">
|
||||
<b>{{ $label }} {{ $required ? '*' : '' }}</b>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@php
|
||||
$data = [
|
||||
'itemList' => old($name, $model->{$name} ?? []) ?? [],
|
||||
'itemColumns' => $itemColumns,
|
||||
'itemType' => $itemType,
|
||||
'itemLimit' => $itemLimit ?? [],
|
||||
'inputName' => $name,
|
||||
'inputPlaceholder' => $inputPlaceholder,
|
||||
'header' => $header ?? false,
|
||||
'errors' => $errors->getBag('default')->getMessages() ?? []
|
||||
];
|
||||
@endphp
|
||||
<div class="list-item" data-json='@json($data)'></div>
|
||||
@include('modules.input-error', [
|
||||
'inputName' => $name,
|
||||
'helperId' => "{$id}Help",
|
||||
'helper' => $helper,
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@php
|
||||
$data = [
|
||||
'id' => uniqId(),
|
||||
'itemList' => old($name, $model->{$name} ?? ($value ?? [])) ?? [],
|
||||
'itemColumns' => $itemColumns,
|
||||
'itemType' => $itemType,
|
||||
'itemLimit' => $itemLimit ?? [],
|
||||
'imageSizes' => $imageSizes ?? [],
|
||||
'inputName' => $name,
|
||||
'inputPlaceholder' => $inputPlaceholder,
|
||||
'wysiwygConfig' => $wysiwygConfig ?? 'basic',
|
||||
'header' => $header ?? false,
|
||||
'errors' => $errors->getBag('default')->getMessages() ?? [],
|
||||
];
|
||||
@endphp
|
||||
<div class="list-item" data-json='@json($data)'></div>
|
||||
@include('modules.input-error', [
|
||||
'inputName' => $name,
|
||||
'helperId' => "{$id}Help",
|
||||
'helper' => $helper,
|
||||
])
|
||||
|
|
|
|||
|
|
@ -174,11 +174,14 @@
|
|||
'wysiwyg' => true,
|
||||
])
|
||||
</div>
|
||||
<div class="col-12 mt-1 text-center">
|
||||
<img class="rounded-3 img-fluid" src="{{ asset($formationSection->data['bloc_image_n1']['value']) }}"
|
||||
alt="{{ $formationSection->data['bloc_image_n1']['alt'] }}"
|
||||
alt="{{ $formationSection->data['bloc_image_n1']['title'] }}">
|
||||
</div>
|
||||
@php $formationImg = $formationSection->data['bloc_image_n1'] @endphp
|
||||
@if ($formationImg['value'] && $formationImg['alt'] && $formationImg['title'])
|
||||
<div class="col-12 mt-1 text-center">
|
||||
<img class="rounded-3 img-fluid" src="{{ asset($formationImg['value']) }}"
|
||||
alt="{{ $formationImg['alt'] }}"
|
||||
title="{{ $formationImg['title'] }}">
|
||||
</div>
|
||||
@endif
|
||||
<div class="col-12 mb-1 mt-4 text-center">
|
||||
<a class="btn btn-primary rounded-pill fw-bold text-initial" href="#we-testimonials"
|
||||
title="{{ __('Lire nos témoignages') }}">{{ __('On témoigne') }}</a>
|
||||
|
|
@ -216,11 +219,6 @@
|
|||
{!! clean($formationSection->data['bloc_n3_content']['value']) !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 mt-1 text-center">
|
||||
<img class="rounded-3 img-fluid" src="{{ asset($formationSection->data['bloc_image_n2']['value']) }}"
|
||||
alt="{{ $formationSection->data['bloc_image_n2']['alt'] }}"
|
||||
alt="{{ $formationSection->data['bloc_image_n2']['title'] }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -292,28 +290,44 @@
|
|||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 my-3">
|
||||
<div class="card bg-quad-light">
|
||||
<img class="card-img-top rounded-top-4 p-2" src="{{ $paritySection->data['bloc_image_n1']['value'] }}"
|
||||
alt="{{ $paritySection->data['bloc_image_n1']['alt'] }}">
|
||||
<div class="card-body text-center">
|
||||
<p class="card-text">{{ $paritySection->data['bloc_image_n1']['title'] }}</p>
|
||||
@php $parityImg = $paritySection->data['bloc_image_n1'] @endphp
|
||||
@if ($parityImg['value'] && $parityImg['alt'] && $parityImg['title'])
|
||||
<div class="card bg-quad-light">
|
||||
<img class="card-img-top rounded-top-4 p-2" src="{{ $parityImg['value'] }}" alt="{{ $parityImg['alt'] }}">
|
||||
<div class="card-body text-center">
|
||||
<p class="card-text">{{ $parityImg['title'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ck-content my-1">
|
||||
@endif
|
||||
<h2 class="text-sixt fw-bold text-center my-4">
|
||||
{{ $paritySection->data['bloc_title_n2']['value'] }}
|
||||
</h2>
|
||||
<div class="ck-content mb-4">
|
||||
{!! clean($paritySection->data['bloc_contenu_n2_content']['value']) !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 my-3">
|
||||
<div class="card bg-tertiary-light">
|
||||
<img class="card-img-top rounded-top-4 p-2" src="{{ $paritySection->data['bloc_image_n2']['value'] }}"
|
||||
alt="{{ $paritySection->data['bloc_image_n2']['alt'] }}">
|
||||
<div class="card-body text-center">
|
||||
<p class="card-text">{{ $paritySection->data['bloc_image_n2']['title'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ck-content my-1">
|
||||
@include('fo.partials.hire.carousel', [
|
||||
'carousel' => $carouselSectionParity->first()
|
||||
])
|
||||
<h2 class="text-sixt fw-bold text-center my-4">
|
||||
{{ $paritySection->data['bloc_title_n3']['value'] }}
|
||||
</h2>
|
||||
<div class="ck-content mb-4">
|
||||
{!! clean($paritySection->data['bloc_contenu_n3_content']['value']) !!}
|
||||
</div>
|
||||
@include('fo.partials.hire.carousel', [
|
||||
'carousel' => $carouselSectionParity->get(1)
|
||||
])
|
||||
<h2 class="text-sixt fw-bold text-center my-4">
|
||||
{{ $paritySection->data['bloc_title_n4']['value'] }}
|
||||
</h2>
|
||||
<div class="ck-content mb-4">
|
||||
{!! clean($paritySection->data['bloc_contenu_n4_content']['value']) !!}
|
||||
</div>
|
||||
@include('fo.partials.hire.carousel', [
|
||||
'carousel' => $carouselSectionParity->get(2)
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
@if ($paritySection->data['bloc_button_on_bouge_ensemble']['value'])
|
||||
|
|
|
|||
23
resources/views/fo/partials/hire/carousel.blade.php
Normal file
23
resources/views/fo/partials/hire/carousel.blade.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
@if (!empty($carousel))
|
||||
<div class="row">
|
||||
<div class="col-12 position-relative mb-5">
|
||||
<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)
|
||||
@foreach ($medias as $media)
|
||||
<div class="swiper-slide">
|
||||
<img class="rounded-3 w-100" src="{{ asset($media['path']) }}" alt="{{ $media['alt'] }}" title="{{ $media['title'] }}" />
|
||||
<div class="ck-content mt-3 text-center">
|
||||
{!! $media['legend'] !!}
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</div>
|
||||
<div
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
use App\Models\ActivityLog;
|
||||
use App\Models\Aisle;
|
||||
use App\Models\Brand;
|
||||
use App\Models\Carousel;
|
||||
use App\Models\HistoricalMilestone;
|
||||
use App\Models\JobVideo;
|
||||
use App\Models\Logo;
|
||||
|
|
@ -278,6 +279,26 @@ Breadcrumbs::for('bo.aisles.duplicate', function (Generator $trail) {
|
|||
$trail->push(Str::ucfirst(trans('crud.actions.duplicate')));
|
||||
});
|
||||
|
||||
// * CAROUSELS
|
||||
Breadcrumbs::for('bo.carousels.index', function (Generator $trail) {
|
||||
$trail->push(
|
||||
Str::ucfirst(trans_choice('models.classes.carousel', \INF)),
|
||||
route('bo.carousels.index', ['sort_col' => 'updated_at', 'sort_way' => 'desc'])
|
||||
);
|
||||
});
|
||||
Breadcrumbs::for('bo.carousels.create', function (Generator $trail) {
|
||||
$trail->parent('bo.carousels.index');
|
||||
$trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.carousels.create'));
|
||||
});
|
||||
Breadcrumbs::for('bo.carousels.edit', function (Generator $trail, Carousel $carousel) {
|
||||
$trail->parent('bo.carousels.index');
|
||||
$trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.carousels.edit', $carousel));
|
||||
});
|
||||
Breadcrumbs::for('bo.carousels.duplicate', function (Generator $trail) {
|
||||
$trail->parent('bo.carousels.index');
|
||||
$trail->push(Str::ucfirst(trans('crud.actions.duplicate')));
|
||||
});
|
||||
|
||||
// * BRANDS
|
||||
Breadcrumbs::for('bo.brands.index', function (Generator $trail) {
|
||||
$trail->push(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
use App\Http\Controllers\Bo\ActivityLogController;
|
||||
use App\Http\Controllers\Bo\AisleController;
|
||||
use App\Http\Controllers\Bo\BrandController;
|
||||
use App\Http\Controllers\Bo\CarouselController;
|
||||
use App\Http\Controllers\Bo\DashboardController;
|
||||
use App\Http\Controllers\Bo\HistoricalMilestoneController;
|
||||
use App\Http\Controllers\Bo\JobVideoController;
|
||||
|
|
@ -197,6 +198,19 @@ Route::middleware(['admin-secure'])->prefix('bo')
|
|||
'changeCustomOrder'
|
||||
])->name('aisles.change-order');
|
||||
|
||||
// * CAROUSELS
|
||||
Route::resource('carousels', CarouselController::class)->except('show')
|
||||
->withoutMiddleware('htmlsanitize')
|
||||
->middleware('htmlsanitize:media');
|
||||
Route::get('/carousels/{carousel}/duplicate', [
|
||||
CarouselController::class,
|
||||
'duplicate'
|
||||
])->name('carousels.duplicate');
|
||||
Route::patch('/carousels/{carousel}/change-published', [
|
||||
CarouselController::class,
|
||||
'changePublished'
|
||||
])->name('carousels.change-published');
|
||||
|
||||
// * BRANDS
|
||||
Route::resource('brands', BrandController::class)->except('show')
|
||||
->withoutMiddleware('htmlsanitize')
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue