feat: ✨ implement CRUD stickies

This commit is contained in:
Corentin Huet 2026-03-05 10:35:06 +01:00
parent f3a4e2fc80
commit a9201415c9
25 changed files with 878 additions and 53 deletions

View file

@ -0,0 +1,157 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Stickies\StoreStickyRequest;
use App\Http\Requests\Bo\Stickies\UpdateStickyRequest;
use App\Models\Sticky;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class StickyController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Sticky::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 $stickyModels */
$stickyModels = Sticky::query();
/** @var string $search Search field */
$search = $request->input('search');
if ($search) {
$this->searchQuery(
$stickyModels,
$search,
null,
'title',
);
}
$searchFields = \implode(', ', [
Str::ucfirst(trans('validation.attributes.title')),
]);
/** Sort columns with a query */
$this->sortQuery($stickyModels);
/** Custom pagination */
$stickyModels = $this->paginate($stickyModels);
return view('bo.pages.stickies.index', compact('stickyModels', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Sticky $sticky
* @return \Illuminate\Contracts\View\View
*/
public function create(Sticky $sticky): \Illuminate\Contracts\View\View
{
return view('bo.pages.stickies.create', ['stickyModel' => $sticky]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Stickies\StoreStickyRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreStickyRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$sticky = new Sticky();
if ($sticky->fill($request->validated())->saveOrFail()) {
return redirect()->route('bo.stickies.edit', $sticky)
->with('success', trans('crud.messages.has_been_created', [
'model' => Str::ucfirst(trans_choice('models.classes.sticky', 0))
]));
}
return redirect()->back()
->with('error', trans('crud.messages.cannot_be_created', [
'model' => Str::ucfirst(trans_choice('models.classes.sticky', 0))
]));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Sticky $sticky
* @return \Illuminate\Contracts\View\View
*/
public function edit(Sticky $sticky): \Illuminate\Contracts\View\View
{
return view('bo.pages.stickies.edit', ['stickyModel' => $sticky]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Stickies\UpdateStickyRequest $request
* @param \App\Models\Sticky $sticky
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateStickyRequest $request, Sticky $sticky): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $sticky) {
if ($sticky->fill($request->validated())->saveOrFail()) {
return redirect()->route('bo.stickies.edit', $sticky)
->with('success', trans('crud.messages.has_been_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.sticky', 0))
]));
}
return redirect()->route('bo.stickies.edit', $sticky)
->with('error', trans('crud.messages.cannot_be_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.sticky', 0))
]));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Sticky $sticky
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Sticky $sticky): \Illuminate\Http\RedirectResponse
{
if ($sticky->deleteOrFail()) {
return redirect()->route('bo.stickies.index')
->with('success', trans('crud.messages.has_been_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.sticky', 0))
]));
}
return redirect()->back()
->with('error', trans('crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.sticky', 0))
]));
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\Sticky $sticky
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(Sticky $sticky): \Illuminate\Contracts\View\View
{
return $this->create($sticky->replicate());
}
}

View file

@ -19,6 +19,7 @@ use App\Models\Section;
use App\Models\Site;
use App\Models\Slide;
use App\Models\StaticPage;
use App\Models\Sticky;
use App\Models\Strip;
use App\Models\Testimonial;
use Illuminate\Database\Eloquent\Builder;
@ -274,6 +275,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();
$navLinks = Sticky::query()->where('type', StaticPageType::we_hire)->get();
return view('fo.pages.hire', \compact(
'staticPageModel',
@ -284,7 +286,8 @@ class StaticPageController extends Controller
'formationSection',
'testimonialSection',
'paritySection',
'findSection'
'findSection',
'navLinks'
));
}

View file

@ -0,0 +1,56 @@
<?php
namespace App\Http\Requests\Bo\Stickies;
use App\Enums\StaticPageType;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
class StoreStickyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return auth('backend')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'type' => [
Rule::requiredIf($this->user()->can('create', $this->sticky)),
new Enum(StaticPageType::class)
],
'title' => 'required|string|between:3,255',
'anchor' => [
Rule::requiredIf($this->user()->can('create', $this->sticky)),
'string',
'between:3,255'
],
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'type' => trans('validation.custom.type'),
'title' => trans('validation.attributes.title'),
'anchor' => trans('validation.custom.anchor'),
];
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace App\Http\Requests\Bo\Stickies;
class UpdateStickyRequest extends StoreStickyRequest
{
}

69
app/Models/Sticky.php Normal file
View file

@ -0,0 +1,69 @@
<?php
namespace App\Models;
use App\Enums\StaticPageType;
use App\Enums\Users\RoleEnum;
use App\Traits\Models\ActivityLog;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property \App\Enums\StaticPageType $type Page type (StaticPageEnum).
* @property string $title Title for the sticky.
* @property string $anchor Anchor for the sticky.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
*
* @property-read \App\Models\ActivityLog $activityLogs Activities logs One-to-many relationship.
*/
class Sticky extends Model
{
use ActivityLog;
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'type',
'title',
'anchor',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'type' => StaticPageType::class,
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted(): void
{
static::creating(function (self $model) {
// * Prevent user elevation privileges
throw_if(
auth('backend')->user() and auth('backend')->user()->role->value() < RoleEnum::conceptor->value(),
AuthorizationException::class
);
});
static::deleting(function (self $model) {
// // * Prevent user elevation privileges
throw_if(
auth('backend')->user() and auth('backend')->user()->role->value() < RoleEnum::conceptor->value(),
AuthorizationException::class
);
});
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
class StickyPolicy
{
/**
* 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::conceptor);
}
/**
* 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::conceptor);
}
/**
* 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::conceptor);
}
/**
* 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::conceptor);
}
/**
* 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);
}
}

View file

@ -0,0 +1,36 @@
<?php
use Database\Seeders\Mandatory\StickySeeder;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('stickies', function (Blueprint $table) {
$table->id();
$table->unsignedTinyInteger('type')->comment('Type of the static page (StaticPageEnum).');
$table->string('title')->comment('Title of the sticky');
$table->string('anchor')->comment('Anchor of the sticky');
$table->timestamps();
});
Artisan::call('db:seed', [
'class' => StickySeeder::class
]);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('stickies');
}
};

View file

@ -0,0 +1,52 @@
<?php
namespace Database\Seeders\Mandatory;
use App\Enums\StaticPageType;
use App\Models\Sticky;
use Illuminate\Database\Seeder;
class StickySeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
Sticky::query()->updateOrCreate(
[
'type' => StaticPageType::we_hire,
'title' => 'La vie d\'entreprise',
'anchor' => 'company-life'
],
);
Sticky::query()->updateOrCreate(
[
'type' => StaticPageType::we_hire,
'title' => 'L\'alternance et la formation',
'anchor' => 'formation'
],
);
Sticky::query()->updateOrCreate(
[
'type' => StaticPageType::we_hire,
'title' => 'Nos collaborateurs témoignent',
'anchor' => 'we-testimonials'
],
);
Sticky::query()->updateOrCreate(
[
'type' => StaticPageType::we_hire,
'title' => 'La parité',
'anchor' => 'parity'
],
);
Sticky::query()->updateOrCreate(
[
'type' => StaticPageType::we_hire,
'title' => 'En savoir plus',
'anchor' => 'on-se-retrouve'
],
);
}
}

View file

@ -3,6 +3,7 @@
namespace Database\Seeders;
use Database\Seeders\Mandatory\StaticPageSeeder;
use Database\Seeders\Mandatory\StickySeeder;
use Illuminate\Database\Seeder;
class MandatorySeeder extends Seeder
@ -15,7 +16,8 @@ class MandatorySeeder extends Seeder
public function run(): void
{
$this->call([
StaticPageSeeder::class
StaticPageSeeder::class,
StickySeeder::class
]);
}
}

View file

@ -17,5 +17,6 @@ return [
'network' => 'réseau|réseaux',
'aisle' => 'rayon',
'brand' => 'marque',
'sticky' => 'menu d\'ancre|menu d\'ancres'
]
];

View file

@ -302,5 +302,6 @@ return [
'title' => 'titre de l\'ancre',
'link' => 'lien de l\'ancre',
],
'anchor' => 'ancre'
],
];

View file

@ -281,6 +281,47 @@ body {
> main {
flex-grow: 1;
flex-shrink: 0;
.sticky-navigation {
@include media-breakpoint-up(md) {
position: fixed;
z-index: 10;
bottom: 4rem;
left: 4rem;
max-width: 15rem;
&-wrapper {
height: 0;
opacity: 0;
transform: translateY(25%);
transition: .3s;
visibility: hidden;
&.show {
height: auto;
opacity: 1;
transform: translateY(0);
visibility: visible;
}
}
}
.col-md-12 {
@include media-breakpoint-up(md) {
width: fit-content;
}
.badge {
width: .5rem;
height: .5rem;
@include media-breakpoint-up(md) {
width: 1rem;
height: 1rem;
}
}
}
}
}
> footer {

View file

@ -56,44 +56,3 @@
}
}
}
.commit-navigation {
@include media-breakpoint-up(md) {
position: fixed;
z-index: 10;
bottom: 4rem;
left: 4rem;
max-width: 15rem;
&-wrapper {
height: 0;
opacity: 0;
transform: translateY(25%);
transition: .3s;
visibility: hidden;
&.show {
height: 14rem;
opacity: 1;
transform: translateY(0);
visibility: visible;
}
}
}
.col-md-12 {
@include media-breakpoint-up(md) {
width: fit-content;
}
.badge {
width: .5rem;
height: .5rem;
@include media-breakpoint-up(md) {
width: 1rem;
height: 1rem;
}
}
}
}

View file

@ -1,16 +1,16 @@
document.addEventListener("DOMContentLoaded", function () {
const commit = document.querySelector("section.commit");
if (commit === null) {
const stickyNav = document.querySelector("div.sticky-navigation");
if (stickyNav === null) {
return;
}
const button = document.querySelector(".commit-navigation .btn") as HTMLButtonElement;
const navWrapper = document.querySelectorAll(".commit-navigation-wrapper ");
const button = document.querySelector(".sticky-navigation .btn") as HTMLButtonElement;
const navWrapper = document.querySelectorAll(".sticky-navigation-wrapper ");
button.addEventListener("click", () => {
navWrapper.forEach((element: Element) => {
element.classList.toggle("show");
});
document.querySelectorAll(".commit-navigation .btn svg").forEach(icon => {
document.querySelectorAll(".sticky-navigation .btn svg").forEach(icon => {
icon.classList.toggle("d-none");
});
});

View file

@ -17,9 +17,9 @@ import "./utilities/vue-fo";
// CUSTOM
import "./base/common";
import "./base/layout";
import "./base/sticky";
// PAGES
import "./pages/commitments";
import "./pages/unveil";
import "./pages/site";

View file

@ -345,7 +345,7 @@
{{-- MORE --}}
<li class="nav-item">
<div class="dropdown">
<a class="nav-link @if (request()->routeIs('bo.logos.*') || request()->is('bo/pages/2/edit') || request()->is('bo/pages/3/edit')) active @endif dropdown-toggle" data-bs-toggle="dropdown" type="button"
<a class="nav-link @if (request()->routeIs('bo.logos.*') || request()->routeIs('bo.stickies.*') || request()->is('bo/pages/2/edit') || request()->is('bo/pages/3/edit')) active @endif dropdown-toggle" data-bs-toggle="dropdown" type="button"
aria-expanded="false">
{{ Str::of(__('Voir plus'))->ucfirst() }}
</a>
@ -360,6 +360,16 @@
</a>
</li>
@endcan
{{-- STICKY --}}
@can('viewAny', \App\Models\Sticky::class)
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.stickies.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.stickies.index') }}"
title="{{ __('crud.helpers.list_all', ['model' => Str::of(trans_choice('models.classes.sticky', \INF))]) }}">
{{ Str::of(trans_choice('models.classes.sticky', \INF))->ucfirst() }}
</a>
</li>
@endcan
{{-- CONFIDENTIALITY --}}
@can('viewAny', \App\Models\StaticPage::class)
<li class="nav-item">

View file

@ -0,0 +1,36 @@
@extends('bo.layout', ['brParam' => $stickyModel])
@section('title', __('Création d\'un :model', ['model' => trans_choice('models.classes.sticky', 1)]))
@section('description', __('Création d\'un tout nouveau :model', ['model' => trans_choice('models.classes.sticky', 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.stickies.index') }}"
title="{{ __('crud.helpers.list_all', ['model' => trans_choice('models.classes.sticky', 1)]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $stickyModel])
</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.sticky', 1)]) }}">
{{ Str::ucfirst(__('crud.actions.save')) }}
</button>
</div>
</div>
<form action="{{ route('bo.stickies.store') }}" method="POST" enctype="multipart/form-data">
@csrf
@include('bo.pages.stickies.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.sticky', 1)]) }}">
{{ Str::ucfirst(__('crud.actions.save')) }}
</button>
</div>
</div>
</form>
@endsection

View file

@ -0,0 +1,52 @@
@extends('bo.layout', ['brParam' => $stickyModel])
@section('title', __('Édition d\'un :model', ['model' => trans_choice('models.classes.sticky', 1)]))
@section('description', __('Édition d\'un :model déjà existant', ['model' => trans_choice('models.classes.sticky', 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.stickies.index') }}"
title="{{ __('crud.helpers.list_all', ['model' => trans_choice('models.classes.sticky', '*')]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $stickyModel])
</div>
<div class="mb-md-0 mb-2">
<form class="btn-group confirmDeleteTS" action="{{ route('bo.stickies.destroy', $stickyModel) }}" method="POST" novalidate>
@can('duplicate', $stickyModel)
<a class="btn btn-secondary" data-bs-toggle="tooltip" href="{{ route('bo.stickies.duplicate', ['sticky' => $stickyModel]) }}"
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.sticky', 1)]) }}">
{{ Str::ucfirst(__('crud.actions.save')) }}
</button>
@can('delete', $stickyModel)
@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.stickies.update', $stickyModel) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
@include('bo.pages.stickies.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.sticky', 1)]) }}">
{{ Str::ucfirst(__('crud.actions.save')) }}
</button>
</div>
</div>
</form>
@endsection

View file

@ -0,0 +1,92 @@
<div class="row">
{{-- TYPE --}}
@can('create', \App\Models\Sticky::class)
<div class="col-12 col-md-6 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ Str::ucfirst(__('validation.custom.type')) }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.tinyint-enum', [
'id' => 'stickyType',
'label' => __('Associer un type'),
'name' => 'type',
'model' => $stickyModel ?? null,
'required' => true,
'associatedModels' => \App\Enums\StaticPageType::toArray(),
'helper' => __('Sélectionnez un type de page dans la liste déroulante.'),
])
</div>
</div>
</fieldset>
</div>
@endcan
{{-- 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' => 'stickyTitle',
'label' => __(':field :inter:model', [
'field' => Str::ucfirst(__('validation.attributes.title')),
'inter' => __('validation.custom.of_the.male'),
'model' => trans_choice('models.classes.sticky', 1),
]),
'hasWordCounter' => true,
'name' => 'title',
'required' => true,
'model' => $stickyModel ?? null,
'helper' => __('validation.between.string', [
'attribute' => __(':field :inter:model', [
'field' => __('validation.attributes.title'),
'inter' => __('validation.custom.of_the.male'),
'model' => trans_choice('models.classes.sticky', 1),
]),
'min' => 3,
'max' => 255,
]),
'placeholder' => __('Un nom clair et concis'),
])
</div>
</div>
</fieldset>
</div>
{{-- ANCHOR --}}
@can('create', \App\Models\Sticky::class)
<div class="col-12 col-md-6 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ Str::ucfirst(__('validation.custom.anchor')) }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.varchar', [
'id' => 'stickyAnchor',
'label' => __(':field :inter:model', [
'field' => Str::ucfirst(__('validation.custom.anchor')),
'inter' => __('validation.custom.of_the.male'),
'model' => trans_choice('models.classes.sticky', 1),
]),
'hasWordCounter' => true,
'name' => 'anchor',
'required' => true,
'model' => $stickyModel ?? null,
'helper' => __('validation.between.string', [
'attribute' => __(':field :inter:model', [
'field' => __('validation.custom.anchor'),
'inter' => __('validation.custom.of_the.male'),
'model' => trans_choice('models.classes.sticky', 1),
]),
'min' => 3,
'max' => 255,
]),
'placeholder' => __('Un nom clair et concis'),
])
</div>
</div>
</fieldset>
</div>
@endcan
<div class="col-12 text-center">
<p><b>{{ __('validation.custom.required_fields') }}</b></p>
</div>
</div>

View file

@ -0,0 +1,62 @@
<table class="table-hover table-fix-action m-0 table">
@if (count($stickyModels) > 0)
<thead>
@include('bo.modules.table-col-sorter', [
'cols' => [
'type' => Str::ucfirst(__('validation.custom.type')),
'title' => Str::ucfirst(__('validation.attributes.title')),
'updated_at' => Str::ucfirst(__('validation.attributes.updated_at')),
],
])
</thead>
<tbody>
@foreach ($stickyModels as $stickyModel)
<tr class="border-bottom list-item">
<td class="text-center align-middle">{{ $stickyModel->type->label() }}</td>
<td class="text-center align-middle">{{ $stickyModel->title }}</td>
<td class="text-center align-middle">
<span class="badge bg-secondary">{{ $stickyModel->updated_at->isoFormat('LLLL') }}</span>
</td>
<td class="text-end align-middle">
@canAny(['delete', 'duplicate', 'update'], $stickyModel)
<form class="btn-group confirmDeleteTS" action="{{ route('bo.stickies.destroy', $stickyModel) }}" method="POST"
novalidate>
@can('duplicate', $stickyModel)
<a class="btn btn-sm btn-secondary" data-bs-toggle="tooltip"
href="{{ route('bo.stickies.duplicate', ['sticky' => $stickyModel]) }}"
title="{{ Str::ucfirst(__('crud.actions.duplicate')) }}">
<i class="fa-solid fa-copy"></i>
</a>
@endcan
@can('update', $stickyModel)
<a class="btn btn-sm btn-primary" data-bs-toggle="tooltip"
href="{{ route('bo.stickies.edit', ['sticky' => $stickyModel]) }}"
title="{{ Str::ucfirst(__('crud.actions.update')) }}">
<i class="fa-solid fa-pencil"></i>
</a>
@endcan
@can('delete', $stickyModel)
@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.sticky', 1)]) }}
</td>
</tr>
@endif
</table>

View file

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

View file

@ -19,8 +19,8 @@
<section class="commit">
{{-- AMBITION --}}
<section class="ambition anchor-padding container my-4" id="introduction">
<div class="commit-navigation z-2">
<div class="commit-navigation-wrapper show row m-0 p-0">
<div class="sticky-navigation z-2">
<div class="sticky-navigation-wrapper show row m-0 p-0">
{{-- blade-formatter-disable --}}
@php
$commitNav = [

View file

@ -19,6 +19,47 @@
<section class="we-hire">
{{-- COMPAGNY LIFE --}}
<section class="company-life anchor-padding" id="company-life">
<div class="sticky-navigation z-2">
<div class="sticky-navigation-wrapper show row m-0 p-0">
@foreach ($navLinks as $key => $link)
<div class="col-6 col-md-12 px-md-0 py-md-1 p-2">
<div @class([
'rounded-5 shadow-sm h-100',
match($loop->index) {
0 => 'bg-primary-light',
1 => 'bg-secondary-light',
2 => 'bg-quad-light',
3 => 'bg-quint-light',
4 => 'bg-sixt-light',
default => 'bg-primary-light'
}
])>
<a class="d-flex justify-content-start align-items-center text-primary text-decoration-none p-sm-3 p-2 h-100"
data-bs-toggle="tooltip" href="#{{ $link->anchor }}"
title="{{ __('Accéder à la partie :name de la page', ['name' => $link->title]) }}">
<span @class([
'badge rounded-pill d-block me-2',
match($loop->index) {
0 => 'bg-primary',
1 => 'bg-secondary',
2 => 'bg-quad',
3 => 'bg-quint',
4 => 'bg-sixt',
default => 'bg-primary'
}
])></span>
<small>{{ $link->title }}</small>
</a>
</div>
</div>
@endforeach
</div>
<button class="d-none d-md-block btn btn-primary rounded-pill mt-1 w-fit px-3"
title="{{ __('Afficher/Masquer le menu des ancres') }}">
<i class="fa-solid fa-plus d-none"></i>
<i class="fa-solid fa-minus"></i>
</button>
</div>
<div class="container">
<div class="row position-relative">
<div class="col-12 text-center">
@ -114,7 +155,7 @@
</div>
</section>
{{-- FORMATION --}}
<section class="formation border-top-curved-upper-quad-light mb-5">
<section class="formation border-top-curved-upper-quad-light mb-5" id="formation">
<div class="container">
<div class="row position-relative">
<div class="col-12 text-center">

View file

@ -12,6 +12,7 @@ use App\Models\Section;
use App\Models\Site;
use App\Models\Slide;
use App\Models\StaticPage;
use App\Models\Sticky;
use App\Models\Strip;
use App\Models\Testimonial;
use App\Models\User;
@ -296,3 +297,23 @@ Breadcrumbs::for('bo.brands.duplicate', function (Generator $trail) {
$trail->parent('bo.brands.index');
$trail->push(Str::ucfirst(trans('crud.actions.duplicate')));
});
// * STICKIES
Breadcrumbs::for('bo.stickies.index', function (Generator $trail) {
$trail->push(
Str::of(trans_choice('models.classes.sticky', 1))->plural()->ucfirst(),
route('bo.stickies.index', ['sort_col' => 'updated_at', 'sort_way' => 'desc'])
);
});
Breadcrumbs::for('bo.stickies.create', function (Generator $trail) {
$trail->parent('bo.stickies.index');
$trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.stickies.create'));
});
Breadcrumbs::for('bo.stickies.edit', function (Generator $trail, Sticky $sticky) {
$trail->parent('bo.stickies.index');
$trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.stickies.edit', $sticky));
});
Breadcrumbs::for('bo.stickies.duplicate', function (Generator $trail) {
$trail->parent('bo.stickies.index');
$trail->push(Str::ucfirst(trans('crud.actions.duplicate')));
});

View file

@ -13,6 +13,7 @@ use App\Http\Controllers\Bo\SectionController;
use App\Http\Controllers\Bo\SiteController;
use App\Http\Controllers\Bo\SlideController;
use App\Http\Controllers\Bo\StaticPageController;
use App\Http\Controllers\Bo\StickyController;
use App\Http\Controllers\Bo\StripController;
use App\Http\Controllers\Bo\TestimonialController;
use App\Http\Controllers\Bo\UploadController;
@ -205,6 +206,13 @@ Route::middleware(['admin-secure'])->prefix('bo')
'changePublished'
])->name('brands.change-published');
// * stickies.
Route::resource('stickies', StickyController::class)->except('show');
Route::get('/stickies/{sticky}/duplicate', [
StickyController::class,
'duplicate'
])->name('stickies.duplicate');
Route::middleware('can:isConceptor')->group(function () {
// * ACTIVITY LOGS.
Route::resource('activity_logs', ActivityLogController::class)->only(['index', 'show']);