Merge branch 'feat/filter-posts' into 'develop'

feat: ✨ add ajax filtering for documents and news + refactor some blade files

See merge request ldc-web/projets/ldc!109
This commit is contained in:
karl.muller 2026-01-14 15:58:02 +00:00
commit 7d0c57a3c6
40 changed files with 1249 additions and 900 deletions

53
app/Enums/OrdersEnum.php Normal file
View file

@ -0,0 +1,53 @@
<?php
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum OrdersEnum: int
{
use BaseEnum;
case asc = 1;
case desc = 2;
/**
* Labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::asc->name => 'enums.orders.asc',
self::desc->name => 'enums.orders.desc',
];
/**
* Get the orders to display using the app locale
*
* @return bool
*/
public function ordersWithLocal(): bool
{
$locale = config('app.locale');
return match (true) {
$this === self::asc => true,
$this === self::desc => true,
default => $locale === "fr" ? true : false,
};
}
/**
* Optional factory from label.
*/
public static function makeFromLabel(string $name): ?self
{
foreach (self::cases() as $case) {
if ($case->name() === $name) {
return $case;
}
}
return null;
}
}

View file

@ -42,4 +42,18 @@ enum YearsEnum: int
self::fifteen->name => '2015/2016',
self::fourteen->name => '2014/2015',
];
/**
* Optional factory from label.
*/
public static function makeFromLabel(string $label): ?self
{
foreach (self::cases() as $case) {
if ($case->label() === $label) {
return $case;
}
}
return null;
}
}

View file

@ -0,0 +1,319 @@
<?php
namespace App\Http\Controllers\Front;
use Carbon\Carbon;
use App\Models\News;
use App\Models\NewsTag;
use App\Enums\YearsEnum;
use App\Models\Document;
use App\Enums\OrdersEnum;
use App\Models\DocumentTag;
use Illuminate\Support\Str;
use Illuminate\Support\Collection;
use App\Http\Requests\Front\PostsRequest;
use Uwil\LaravelCommon\Controllers\Controller;
use Uwil\LaravelCommon\Helpers\CollectionHelper;
class PostsController extends Controller
{
protected const DOCUMENTS = "documents";
protected const NEWS = "news";
/**
* Extract unique years from date field in a collection.
*
* @param \Illuminate\Support\Collection $query
* @param string $field
*
* @return \Illuminate\Support\Collection
*/
private function getUniqueYearsFromCollection(Collection $model, string $field): Collection
{
return $model->pluck($field)
->map(fn($date) => Carbon::parse($date)->year)
->unique()
->values();
}
/**
* Filter input collection to only keep items whose value exists in a reference collection.
*
* @param \Illuminate\Support\Collection $array1
* @param \Illuminate\Support\Collection $array2
* @param string|null $field
*
* @return array
*/
function getMatchingValues(Collection $array1, Collection $array2, ?string $field = null): array
{
return $array2
->filter(fn ($item) => $array1->contains($field ? $item[$field] : $item))
->values()
->toArray();
}
/**
* Build the common filters configuration for front-end listing pages
*
* If the type is "documents" the "order" filter is included
*
* @param string $type
* @param string $category
* @param \Illuminate\Database\Eloquent\Collection $publishedTags
* @param \Illuminate\Database\Eloquent\Collection $model
*
* @return array
*/
private function getFilters(string $type, string $category, Collection $publishedTags, Collection $model): array
{
$filters = [];
$tags = collect($publishedTags)
->map(fn($tag) => [
'title' => $tag->name,
'tag' => $tag->slug,
'category' => $category,
])
->values();
$enumYears = collect(YearsEnum::toArray())
->map(fn($year) => YearsEnum::make((int) $year->value))
->map(fn($year) => [
'title' => trans($year->label()),
'tag' => Str::replace('/', '-', $year->label()),
'value' => $type === self::NEWS ? explode('/', $year->label())[1] : $year->value,
'category' => $category,
])
->sortDesc()
->values();
$orders = collect(OrdersEnum::toArray())
->map(fn($order) => OrdersEnum::make((int) $order->value))
->filter(fn($orderLocal) => $orderLocal->ordersWithLocal())
->map(fn($orderLocal) => [
'title' => ucfirst(trans($orderLocal->label())),
'tag' => Str::slug($orderLocal->name),
'category' => $category,
])
->values();
/** Sort displayed years */
$years = $this->getMatchingValues(
$this->getUniqueYearsFromCollection($model, 'published_at'),
$enumYears,
'value'
);
if ($type === self::DOCUMENTS) {
$filters[] = [
'title' => ucfirst(__('front.news.order')),
'name' => 'order',
'fields' => $orders,
];
}
$filters[] = [
'title' => $type === self::DOCUMENTS ? ucfirst(__('front.news.document')) : ucfirst(__('front.news.theme')),
'name' => 'tags',
'fields' => $tags,
];
$filters[] = [
'title' => $type === self::DOCUMENTS ? ucfirst(__('front.news.exercise')) : ucfirst(__('front.news.year')),
'name' => 'years',
'fields' => $years,
];
return $filters;
}
/**
* Returns news in JSON format filtered by category and language
* Optionally, the results can be further filtered by tags, and years
*
* @param \App\Http\Requests\Front\PostsRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function ajaxLoadNews(PostsRequest $request): \Illuminate\Http\JsonResponse
{
$data = $request->validated();
$perPage = 9;
$selectedFilters = [];
$category = $data['category'];
app()->setLocale($data['locale']);
$publishedNewsTags = NewsTag::getTagsByCategoryInCurrentLocale($category);
$newsModel = News::getPublishedNewsByCategoryInCurrentLocale($category);
$requestedTags = collect($data['filters']['tags'] ?? '')
->filter()
->unique()
->values();
$selectedTags = $publishedNewsTags->filter(fn ($tag) => $requestedTags->contains($tag->slug ?? null))
->map(fn ($tag) => [
'id' => $tag->id,
'title' => $tag->name,
'tag' => $tag->slug,
'color' => $tag->color,
'category' => $category,
])
->values();
if ($selectedTags->isNotEmpty()) {
$newsModel = $newsModel->filter(function ($news) use ($selectedTags) {
return $news->tags->contains(
fn ($tag) => $selectedTags->contains('id', $tag->id)
);
});
$selectedFilters['tags'] = $selectedTags;
}
$selectedYears = collect($data['filters']['years'] ?? '')
->map(fn (string $label) => YearsEnum::makeFromLabel(Str::replace('-', '/', $label)))
->filter()
->map(fn($year) => [
'title' => $year->label(),
'tag' => Str::replace('/', '-', $year->label()),
'value' => explode('/', $year->label())[1],
'category' => $category,
])
->values();
if ($selectedYears->isNotEmpty()) {
$newsModel = $newsModel->filter(function ($news) use ($selectedYears) {
return $selectedYears->contains('value', $news->published_at->year);
});
$selectedFilters['years'] = $selectedYears;
}
$newsModel = $newsModel->sortByDesc('published_at');
$filters = $this->getFilters('news', $category, $publishedNewsTags, $newsModel);
/** Custom pagination */
$newsModel = CollectionHelper::paginate($newsModel, $perPage);
return response()->json([
'pagination' => [
'total' => $newsModel->total(),
'nbItems' => count($newsModel->items()),
'currentPage' => $newsModel->currentPage(),
'lastPage' => $newsModel->lastPage(),
'hasMore' => $newsModel->currentPage() < $newsModel->lastPage(),
],
'filters' => view('components.front.posts.filters-dropdowns', ['filters' => $filters])->render(),
'selectedFilters' => view('components.front.posts.selected-filters', ['selectedFilters' => $selectedFilters])->render(),
'news' => view('components.front.posts.news', ['newsList' => $newsModel->items()])->render()
]);
}
/**
* Returns documents in JSON format filtered by category and language
* Optionally, the results can be further filtered by order, tags, and years
*
* @param \App\Http\Requests\Front\PostsRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function ajaxLoadDocuments(PostsRequest $request): \Illuminate\Http\JsonResponse
{
$data = $request->validated();
$perPage = 3;
$selectedFilters = [];
$category = $data['category'];
app()->setLocale($data['locale']);
$publishedDocumentsTags = DocumentTag::getTagsByCategoryInCurrentLocale($category);
$documentsModel = Document::getPublishedDocumentsByCategoryInCurrentLocale($category);
$requestedTags = collect($data['filters']['tags'] ?? '')
->filter()
->unique()
->values();
$selectedTags = $publishedDocumentsTags->filter(fn ($tag) => $requestedTags->contains($tag->slug ?? null))
->map(fn ($tag) => [
'id' => $tag->id,
'title' => $tag->name,
'tag' => $tag->slug,
'color' => $tag->color,
'category' => $category,
])
->values();
if ($selectedTags->isNotEmpty()) {
$documentsModel = $documentsModel->filter(function ($documents) use ($selectedTags) {
return $documents->tags->contains(
fn ($tag) => $selectedTags->contains('id', $tag->id)
);
});
$selectedFilters['tags'] = $selectedTags;
}
$selectedYears = collect($data['filters']['years'] ?? '')
->map(fn (string $label) => YearsEnum::makeFromLabel(Str::replace('-', '/', $label)))
->filter()
->map(fn($year) => [
'title' => $year->label(),
'tag' => Str::replace('/', '-', $year->label()),
'value' => $year->value,
'category' => $category,
])
->values();
if ($selectedYears->isNotEmpty()) {
$documentsModel = $documentsModel->filter(function ($documents) use ($selectedYears) {
return $selectedYears->contains('value', $documents->exercice);
});
$selectedFilters['years'] = $selectedYears;
}
$selectedOrder = collect($data['filters']['order'] ?? '')
->map(fn (string $label) => OrdersEnum::makeFromLabel($label))
->filter()
->map(fn($order) => [
'title' => trans($order->label()),
'tag' => $order->name(),
'category' => $category,
])
->values();
if ($selectedOrder->isNotEmpty()) {
$documentsModel = $selectedOrder->map->tag->first() === 'desc'
? $documentsModel->sortByDesc('published_at')
: $documentsModel->sortBy('published_at');
$selectedFilters['order'] = $selectedOrder->all();
} else {
$documentsModel = $documentsModel->sortByDesc('published_at');
}
$filters = $this->getFilters('documents', $category, $publishedDocumentsTags, $documentsModel);
/** Custom pagination */
$documentsModel = CollectionHelper::paginate($documentsModel, $perPage);
return response()->json([
'pagination' => [
'total' => $documentsModel->total(),
'nbItems' => count($documentsModel->items()),
'currentPage' => $documentsModel->currentPage(),
'lastPage' => $documentsModel->lastPage(),
'hasMore' => $documentsModel->currentPage() < $documentsModel->lastPage(),
],
'filters' => view('components.front.posts.filters-dropdowns', ['filters' => $filters])->render(),
'selectedFilters' => view('components.front.posts.selected-filters', ['selectedFilters' => $selectedFilters])->render(),
'news' => view('components.front.posts.documents', ['documentsList' => $documentsModel->items()])->render()
]);
}
}

View file

@ -25,7 +25,8 @@ class StoreDocumentTagRequest extends FormRequest
{
return [
'name' => 'required|string|between:3,255',
'slug' => 'required|string|between:3,255'
'slug' => 'required|string|between:3,255',
'color' => 'required|string|hex_color',
];
}
}

View file

@ -26,7 +26,6 @@ class StoreNewsCategoryRequest extends FormRequest
return [
'slug' => 'required|string|between:3,255',
'name' => 'required|string|between:3,255',
'color' => 'required|string|hex_color',
];
}
@ -39,7 +38,6 @@ class StoreNewsCategoryRequest extends FormRequest
{
return [
'name' => trans('validation.custom.name'),
'color' => trans('validation.custom.color'),
];
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace App\Http\Requests\Front;
use Illuminate\Foundation\Http\FormRequest;
class PostsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'page' => ['required', 'integer'],
'category' => ['required', 'string'],
'locale' => ['required', 'string'],
'filters' => ['nullable', 'array'],
'filters.order' => ['nullable', 'string', 'in:asc,desc'],
'filters.tags' => ['nullable', 'array'],
'filters.tags.*'=> ['string'],
'filters.years' => ['nullable', 'array'],
'filters.years.*' => ['string'],
];
}
}

View file

@ -2,18 +2,19 @@
namespace App\Models;
use App\Contracts\TranslatableModel;
use App\Enums\Document\DocumentStatusEnum;
use App\Enums\YearsEnum;
use App\Helpers\TranslationHelper;
use App\Traits\Models\HasCache;
use App\Traits\Models\HasTranslations;
use App\Traits\Models\HasUwilImages;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Carbon\Carbon;
use App\Enums\YearsEnum;
use Illuminate\Support\Arr;
use App\Traits\Models\HasCache;
use App\Helpers\TranslationHelper;
use Illuminate\Support\Facades\App;
use App\Contracts\TranslatableModel;
use App\Traits\Models\HasUwilImages;
use App\Traits\Models\HasTranslations;
use Illuminate\Database\Eloquent\Model;
use App\Enums\Document\DocumentStatusEnum;
use Uwil\LaravelActivityLogs\Traits\ActivityLog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Document extends Model implements TranslatableModel
{
@ -70,6 +71,16 @@ class Document extends Model implements TranslatableModel
'path',
];
/**
* Relations should be included in the cache.
*
* @var array<string, string>
*/
protected static $cacheRelations = [
'categories',
'tags',
];
/**
* Perform any actions required after the model boots.
*
@ -240,4 +251,24 @@ class Document extends Model implements TranslatableModel
$attributes = Arr::except($attributes, ['tags', 'categories']);
$this->traitInertiaFill($attributes);
}
/**
* Get all published news by category.
*
* @param string $category
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getPublishedDocumentsByCategoryInCurrentLocale(string $category)
{
$locale = App::getLocale();
return Document::getModelsCached()
->where('status', DocumentStatusEnum::published)
->filter(function ($documents) use ($category, $locale) {
return !empty($documents->getTranslation('path', $locale, false)) && $documents->categories->contains(
fn ($cat) => $cat->slug === $category
);
});
}
}

View file

@ -2,13 +2,14 @@
namespace App\Models;
use App\Contracts\TranslatableModel;
use App\Traits\Models\HasCache;
use Illuminate\Support\Facades\App;
use App\Contracts\TranslatableModel;
use App\Traits\Models\HasTranslations;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Uwil\LaravelActivityLogs\Traits\ActivityLog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class DocumentTag extends Model implements TranslatableModel
{
@ -24,7 +25,8 @@ class DocumentTag extends Model implements TranslatableModel
*/
protected $fillable = [
'name',
'slug'
'slug',
'color'
];
/**
@ -37,6 +39,15 @@ class DocumentTag extends Model implements TranslatableModel
'slug'
];
/**
* Relations should be included in the cache.
*
* @var array<string, string>
*/
protected static $cacheRelations = [
'documents',
];
/**
* Relation to Document
*
@ -52,4 +63,24 @@ class DocumentTag extends Model implements TranslatableModel
'document_id'
);
}
/**
* Get all Tags by category.
*
* @param string $category
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getTagsByCategoryInCurrentLocale(string $category): \Illuminate\Database\Eloquent\Collection
{
$locale = App::getLocale();
return DocumentTag::getModelsCached()->load('documents.categories')->filter(function ($tag) use ($locale, $category) {
return $tag->documents->filter(function ($documents) use ($locale, $category) {
return !empty($documents->getTranslation('path', $locale, false)) && $documents->categories->contains(
fn ($cat) => $cat->slug === $category
);
})->isNotEmpty();
});
}
}

View file

@ -2,21 +2,22 @@
namespace App\Models;
use App\Contracts\TranslatableModel;
use Carbon\Carbon;
use App\Models\NewsTag;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use App\Traits\Models\HasCache;
use App\Enums\News\NewsStatusEnum;
use App\Helpers\TranslationHelper;
use App\Traits\Models\HasCache;
use App\Traits\Models\HasTranslations;
use Illuminate\Support\Facades\App;
use App\Contracts\TranslatableModel;
use App\Traits\Models\HasUwilImages;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use App\Traits\Models\HasTranslations;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\SoftDeletes;
use Uwil\LaravelActivityLogs\Traits\ActivityLog;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Mcamara\LaravelLocalization\Interfaces\LocalizedUrlRoutable;
/**
@ -137,6 +138,16 @@ class News extends Model implements TranslatableModel, LocalizedUrlRoutable
'unpublished_at' => 'datetime',
];
/**
* Relations should be included in the cache.
*
* @var array<string, string>
*/
protected static $cacheRelations = [
'categories',
'tags',
];
/**
* Perform any actions required after the model boots.
*
@ -340,12 +351,23 @@ class News extends Model implements TranslatableModel, LocalizedUrlRoutable
)->withTimestamps();
}
public static function getPublishedNewsInCurrentLocale()
/**
* Get all published news by category.
*
* @param string $category
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getPublishedNewsByCategoryInCurrentLocale(string $category)
{
$locale = App::getLocale();
// ici mettre le cache avec la relation catégorie
return News::query()
->with('categories', 'tags')
->whereNotNull("slug->$locale");
return News::getModelsCached()
->where('status', NewsStatusEnum::published)
->filter(function ($news) use ($category, $locale) {
return !empty($news->getTranslation('slug', $locale, false)) && $news->categories->contains(
fn ($cat) => $cat->slug === $category
);
});
}
}

View file

@ -91,7 +91,7 @@ class NewsCategory extends Model implements TranslatableModel
News::class,
'newsable',
'newsables',
'news_id',
'news_id',
'newsable_id'
);
}

View file

@ -2,14 +2,16 @@
namespace App\Models;
use App\Contracts\TranslatableModel;
use App\Traits\Models\HasCache;
use Illuminate\Support\Facades\App;
use App\Contracts\TranslatableModel;
use Illuminate\Support\Facades\Cache;
use App\Traits\Models\HasTranslations;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Uwil\LaravelActivityLogs\Traits\ActivityLog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* @property int $news_id News focused.
@ -47,6 +49,15 @@ class NewsTag extends Model implements TranslatableModel
'slug'
];
/**
* Relations should be included in the cache.
*
* @var array<string, string>
*/
protected static $cacheRelations = [
'news',
];
/**
* Relation to Document
*
@ -62,5 +73,23 @@ class NewsTag extends Model implements TranslatableModel
'news_id'
);
}
/**
* Get all Tags by category.
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getTagsByCategoryInCurrentLocale($category): \Illuminate\Database\Eloquent\Collection
{
$locale = App::getLocale();
return NewsTag::getModelsCached()->load('news.categories')->filter(function ($tag) use ($locale, $category) {
return $tag->news->filter(function ($news) use ($locale, $category) {
return !empty($news->getTranslation('slug', $locale, false)) && $news->categories->contains(
fn ($cat) => $cat->slug === $category
);
})->isNotEmpty();
});
}
}

View file

@ -1,108 +0,0 @@
<?php
namespace App\View\Components\Front\News;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
use App\Enums\News\NewsYearsEnum;
use App\Helpers\BladeComponentHelper;
use App\Models\News;
use App\Models\NewsCategory;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
class IndexNewsComponent extends Component
{
public $categories;
public $years;
public $newsModels;
public $postContainer;
/**
* Create a new component instance.
*/
public function __construct()
{
// Rajouter ici un filtre pour ne récupérer que le status published
$bladeHelper = new BladeComponentHelper();
$this->categories = collect(NewsCategory::all())
->map(fn($categoryLocal) => [
'title' => $categoryLocal->name,
'tag' => $categoryLocal->slug,
'category' => 'news',
])
->values()
->toArray();
$this->years = collect(NewsYearsEnum::toArray())
->map(fn($year) => NewsYearsEnum::make((int) $year->value))
->filter(fn($yearLocal) => $yearLocal->yearsWithLocal())
->map(fn($yearLocal) => [
'title' => trans($yearLocal->label()),
'tag' => Str::slug($yearLocal->value),
'category' => 'news',
])
->values()
->toArray();
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\News> $query */
$query = News::getPublishedNewsInCurrentLocale();
/** Sort columns with a query */
$bladeHelper->runSortQuery($query);
/** Custom pagination */
$this->newsModels = $bladeHelper->runPaginate($query);
/**
* Cette section est à revoir mais c'est pour coller et être iso au site actuellement en prod
*/
$currentRoute = Route::currentRouteName();
// Common values
$this->postContainer = [
'secondaryClass' => 'fw-container no-bottom-padding',
'tertiaryClass' => 'tagged-posts',
'list_type' => 'news',
'id' => 'news',
'post_id' => '9', // FIXME: probablement plus utile
'slug' => 'news',
'localized' => 'ajax_load_more_news_vars', // FIXME: probablement pas utile
'object' => 'ajax_load_more_news', // FIXME: probablement pas utile
];
switch ($currentRoute) {
case 'front.news.index':
$this->postContainer['load_more'] = ucfirst(__('front.buttons.see_more_news'));
break;
case 'front.dynamic-pages.espace-presse':
$this->postContainer = array_merge($this->postContainer, [
'class' => 'fw-container no-top-padding',
'load_more' => ucfirst(__('front.buttons.see_more_docs')),
'type_button' => true
]);
break;
default:
$this->postContainer['load_more'] = ucfirst(__('front.buttons.see_more_news'));
break;
}
}
/**
* Get the view / contents that represent the component.
*
* @return View|Closure|string
*/
public function render(): View|Closure|string
{
return view('components.front.news.index-news-component', [
'categories' => $this->categories,
'years' => $this->years,
'newsModels' => $this->newsModels,
'postContainer' => $this->postContainer
]);
}
}

View file

@ -2,10 +2,15 @@
namespace App\View\Components\Front\News;
use App\Models\News;
use Closure;
use Illuminate\Contracts\View\View;
use App\Models\News;
use App\Models\NewsTag;
use App\Models\NewsCategory;
use Illuminate\View\Component;
use App\Enums\News\NewsStatusEnum;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
class NewsNextEventComponent extends Component
{
@ -16,13 +21,24 @@ class NewsNextEventComponent extends Component
*/
public function __construct()
{
$this->nextEvents = News::with('categories')
->whereHas('categories', function ($query) {
$query->where('slug', 'event'); #FIXME: ici c'est en dure et ça sera à fixer plus tard
})
->latest()
->take(3)
->get();
$newsCategory = NewsCategory::getModelsCached()
->filter(fn($category) => $category->getTranslation('slug', 'fr') === "actualites")
->first();
$eventTag = NewsTag::getTagsByCategoryInCurrentLocale($newsCategory->slug)
->filter(fn($tag) => $tag->getTranslation('slug', 'fr') === "evenement")
->first();
$eventTagId = $eventTag ? $eventTag->id : null;
$this->nextEvents = News::getPublishedNewsByCategoryInCurrentLocale($newsCategory->slug)
->filter(function ($news) use ($eventTagId) {
return $news->tags->contains(
fn ($tag) => $eventTagId === $tag->id
);
})
->sortByDesc('published_at')
->take(3);
}
/**

View file

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('document_tags', function (Blueprint $table) {
$table->string('color')->default("#ff4e4e")->after('slug')->comment('Color of the news category.');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('document_tags', function (Blueprint $table) {
$table->dropColumn('color');
});
}
};

View file

@ -123,4 +123,8 @@ return [
'company' => Str::ucFirst(trans_choice('models.classes.company', 1)),
'document' => Str::ucFirst(trans_choice('models.classes.document', 1)),
],
'orders' => [
'asc' => 'From oldest to newest.',
'desc' => 'From the most recent to the oldest.'
],
];

View file

@ -26,18 +26,23 @@ return [
"index" => "news",
"read_article" => "read more",
"results" => "results",
"on_document_count" => ":current documents of :total",
"on_document_count" => " documents of ",
"theme" => "theme",
"year" => "year",
'document' => 'document',
'exercise' => 'exercise',
'order' => 'display',
],
"finance" => "finance",
'espace-presse' => 'press space',
'next_events' => 'upcoming events',
'buttons' => [
'send' => 'send',
'back_to_contact' => 'return to choice of contact',
'see_more_news' => 'load more news',
'see_more_docs' => 'load more documentary resources',
"clear_filters" => "clear les filtres"
"clear_filters" => "clear les filtres",
'see_more_events' => 'discover all our events, forums and job dating'
],
'contact' => [
"index" => "Contact us",

View file

@ -123,4 +123,8 @@ return [
'company' => Str::ucFirst(trans_choice('models.classes.company', 1)),
'document' => Str::ucFirst(trans_choice('models.classes.document', 1)),
],
'orders' => [
'asc' => 'du plus ancien au plus récent.',
'desc' => 'du plus récent au plus ancien.'
],
];

View file

@ -26,19 +26,24 @@ return [
"index" => "actualités",
"read_article" => "lire l'article",
"results" => "résultat",
"on_document_count" => ":current documents sur :total",
"on_document_count" => " documents sur ",
"theme" => "thématique",
"year" => "année",
'document' => 'document',
'exercise' => 'exercice',
'order' => 'afficher',
],
"finance" => "finance",
'search' => 'rechercher',
'espace-presse' => 'espace presse',
'next_events' => 'prochains évènements',
'buttons' => [
'send' => 'envoyer',
'back_to_contact' => 'retour au choix de contact',
'see_more_news' => 'voir plus d\'actualités',
'see_more_docs' => 'voir plus de ressources documentaires',
"clear_filters" => "effacer les filtres"
"clear_filters" => "effacer les filtres",
'see_more_events' => 'voir tous nos évènements, forums et job dating'
],
'contact' => [
"index" => "Nous contacter",

View file

@ -11,6 +11,7 @@ import useTrans from "@/hooks/use-translations";
import str from "@/hooks/use-string";
import { useCrudSubmit } from "@/hooks/use-crud-submit";
import SubmitButton from "@/back/Components/SubmitButton";
import SimpleColorPicker from "@/back/Components/Inputs/SimpleColorPicker";
import Slugifycator from "@/back/Components/Inputs/Slugifycator";
export default function Form({
@ -28,12 +29,12 @@ export default function Form({
}) {
const { locale } = usePage().props.appEnv as AppEnv;
/** Form section */
const { post, setData, data } = useForm({
_method: method === Methods.POST ? 'post' : 'put',
_method: method === Methods.POST ? "post" : "put",
name: model?.name ? model?.name[locale] : "",
slug: model?.slug ? model?.slug[locale] : ""
slug: model?.slug ? model?.slug[locale] : "",
color: model?.color ? model?.color : "",
});
const submit = useCrudSubmit({
@ -42,7 +43,7 @@ export default function Form({
modelName,
action,
model,
post
post,
});
return (
@ -59,11 +60,15 @@ export default function Form({
label={str(
useTrans("back.form_associate_to", {
params: {
attribute: useTrans("validation.attributes.name"),
attribute: useTrans(
"validation.attributes.name",
),
},
choice: 1
choice: 1,
}),
).ucFirst().value()}
)
.ucFirst()
.value()}
required={true}
placeholder="ex: John Doe"
value={data.name ?? ""}
@ -74,14 +79,32 @@ export default function Form({
targetValue={data.name}
setData={setData}
/>
<SimpleColorPicker
name="color"
label={str(
useTrans("back.form_associate_to", {
params: {
attribute: useTrans(
"validation.custom.color",
),
},
choice: 1,
}),
)
.ucFirst()
.value()}
setData={setData}
value={data.color}
required={true}
/>
</div>
</FieldsetWrapper>
<div className="text-center">
<SubmitButton modelTrans={
useTrans(`models.classes.${modelName}`, {
<SubmitButton
modelTrans={useTrans(`models.classes.${modelName}`, {
choice: 1,
})
} />
})}
/>
</div>
</form>
);

View file

@ -3,6 +3,11 @@ import { route } from "ziggy-js";
import { usePage } from "@inertiajs/react";
// * Components shadcn
import { Badge } from "@/components/ui/badge";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
// * Other
import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout";
import DataTable from "@/back/Components/Table/DataTable";
@ -28,7 +33,7 @@ export default function Index({
documentTagModel,
search,
searchFields,
modelName
modelName,
}: {
documentTagModel: Resource;
search: string;
@ -40,30 +45,56 @@ export default function Index({
appEnv: AppEnv;
};
const { create } = auth.policies.documentTags;
const actionModels = [
Views.VISUALIZATION,
Views.EDITION,
Views.DELETION,
];
const actionModels = [Views.VISUALIZATION, Views.EDITION, Views.DELETION];
const columns = [
{
accessorKey: "name",
header: ({ column }: { column: { id: string } }) => (
<DataTableFilterButton
label={str(useTrans(`validation.attributes.${column.id}`)).ucFirst().value()}
label={str(useTrans(`validation.attributes.${column.id}`))
.ucFirst()
.value()}
field={column.id}
/>
),
cell: ({ row }: { row: Row<DocumentTagModel> }) => {
return renderTranslationCell(
row.getValue("name")
)
return renderTranslationCell(row.getValue("name"));
},
},
{
accessorKey: "color",
header: ({ column }: { column: { id: string } }) => (
<DataTableFilterButton
label={str(useTrans(`validation.custom.${column.id}`))
.ucFirst()
.value()}
field={column.id}
/>
),
cell: ({ row }: { row: Row<DocumentTagModel> }) => {
return (
<Tooltip>
<TooltipTrigger asChild>
<div
className="w-8 h-8 rounded-md mx-auto border"
style={{
backgroundColor: row.getValue("color"),
}}
></div>
</TooltipTrigger>
<TooltipContent>{row.getValue("color")}</TooltipContent>
</Tooltip>
);
},
},
{
accessorKey: "documents",
header: () => (
<p className="w-full text-center">{str(useTrans(`validation.custom.documentTag.documents`)).ucFirst().value()}</p>
<p className="w-full text-center">
{str(useTrans(`validation.custom.documentTag.documents`))
.ucFirst()
.value()}
</p>
),
cell: ({ row }: { row: Row<DocumentTagModel> }) => (
<div className="w-full flex justify-center">
@ -75,7 +106,9 @@ export default function Index({
accessorKey: "updated_at",
header: ({ column }: { column: { id: string } }) => (
<DataTableFilterButton
label={str(useTrans(`validation.attributes.${column.id}`)).ucFirst().value()}
label={str(useTrans(`validation.attributes.${column.id}`))
.ucFirst()
.value()}
field={column.id}
/>
),
@ -86,26 +119,30 @@ export default function Index({
),
},
];
const transModel = useTrans(`models.classes.${str(modelName).singular().value()}`, { choice: 2 });
const transModel = useTrans(
`models.classes.${str(modelName).singular().value()}`,
{ choice: 2 },
);
const transTitle = str(transModel).ucFirst().value();
const afterNode = () => {
return (
<div className="flex justify-end items-center gap-x-1.5">
{create && <BtnOptionnalLinkWithTooltip
link={route(`back.${modelName}.create`)}
content={<Plus />}
tooltipContent={<p>{useTrans("back.crud_add_entry")}</p>}
/>}
{create && (
<BtnOptionnalLinkWithTooltip
link={route(`back.${modelName}.create`)}
content={<Plus />}
tooltipContent={
<p>{useTrans("back.crud_add_entry")}</p>
}
/>
)}
</div>
);
};
return (
<AuthenticatedLayout
title={transTitle}
afterNode={afterNode()}
>
<AuthenticatedLayout title={transTitle} afterNode={afterNode()}>
<DataTableSearch
modelRoute={modelName}
searchText={search}

View file

@ -3,6 +3,7 @@ import { usePage } from "@inertiajs/react";
import { route } from "ziggy-js";
// * Components shadcn
import { Badge } from "@/components/ui/badge";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
// * Other
import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout";
import DetailsTable from "@/back/Components/Table/DetailsTable";
@ -24,50 +25,87 @@ import { ArrowLeft } from "lucide-react";
import AssociatedDocumentList from "@/back/Components/AssociatedDocumentList";
import { renderTranslationCell } from "@/lib/translationCellFallback";
export default function Show({ data, modelName, documents }: { data: SingleResource, modelName: string, documents: Resource }) {
export default function Show({
data,
modelName,
documents,
}: {
data: SingleResource;
modelName: string;
documents: Resource;
}) {
const { auth, appEnv } = usePage().props as unknown as {
auth: AuthProp;
appEnv: AppEnv;
};
const { viewAny } = auth.policies.documentTags;
const docsModel = data.data as DocumentTagModel;
const actionModels = [
Views.EDITION,
Views.DELETION,
];
const actionModels = [Views.EDITION, Views.DELETION];
const singularModelName = str(modelName).singular().value();
const transTitle = useTrans("crud.helpers.visualization_model", {
params: { model: useTrans(`models.classes.${singularModelName}`, { choice: 1 }) },
params: {
model: useTrans(`models.classes.${singularModelName}`, {
choice: 1,
}),
},
});
const columns = [
{
accessorKey: "name",
header: str(useTrans("validation.attributes.name")).ucFirst().value(),
header: str(useTrans("validation.attributes.name"))
.ucFirst()
.value(),
cell: ({ value }: { value: TranslationField }) => {
return renderTranslationCell(
value
)
}
return renderTranslationCell(value);
},
},
{
accessorKey: "slug",
header: str(useTrans("validation.custom.slug")).ucFirst().value(),
cell: ({ value }: { value: TranslationField }) => value[appEnv.locale]
? value[appEnv.locale]
: (<p className="text-muted-foreground italic m-0">{useTrans('back.table_not_known')}</p>),
cell: ({ value }: { value: TranslationField }) =>
value[appEnv.locale] ? (
value[appEnv.locale]
) : (
<p className="text-muted-foreground italic m-0">
{useTrans("back.table_not_known")}
</p>
),
},
{
accessorKey: "color",
header: str(useTrans("validation.custom.color")).ucFirst().value(),
cell: ({ value }: { value: string }) => {
return (
<Tooltip>
<TooltipTrigger asChild>
<div
className="w-8 h-8 rounded-md border"
style={{
backgroundColor: value,
}}
></div>
</TooltipTrigger>
<TooltipContent>{value}</TooltipContent>
</Tooltip>
);
},
},
{
accessorKey: "created_at",
header: str(useTrans("validation.attributes.created_at")).ucFirst().value(),
header: str(useTrans("validation.attributes.created_at"))
.ucFirst()
.value(),
cell: ({ value }: { value: string }) => {
return (
<Badge variant="secondary">{useFormatedDate(value)}</Badge>
)
);
},
},
{
accessorKey: "updated_at",
header: str(useTrans("validation.attributes.updated_at")).ucFirst().value(),
header: str(useTrans("validation.attributes.updated_at"))
.ucFirst()
.value(),
cell: ({ value }: { value: string }) => (
<Badge variant="secondary">{useFormatedDate(value)}</Badge>
),
@ -75,22 +113,23 @@ export default function Show({ data, modelName, documents }: { data: SingleResou
];
const beforeNode = () => {
return viewAny && (
<div className="flex justify-end items-center gap-x-1.5">
<BtnOptionnalLinkWithTooltip
link={route(`back.${modelName}.index`)}
content={<ArrowLeft />}
tooltipContent={<p>{useTrans("back.crud_back_to_list")}</p>}
/>
</div>
return (
viewAny && (
<div className="flex justify-end items-center gap-x-1.5">
<BtnOptionnalLinkWithTooltip
link={route(`back.${modelName}.index`)}
content={<ArrowLeft />}
tooltipContent={
<p>{useTrans("back.crud_back_to_list")}</p>
}
/>
</div>
)
);
};
return (
<AuthenticatedLayout
title={transTitle}
beforeNode={beforeNode()}
>
<AuthenticatedLayout title={transTitle} beforeNode={beforeNode()}>
<ModelActions
actions={actionModels}
modelRoute={modelName}

View file

@ -210,6 +210,7 @@ type DocumentTagModel = {
id: number;
name: TranslationField;
slug: TranslationField;
color: string;
documents_count?: number;
created_at: Date;
updated_at: Date;

View file

@ -1,128 +0,0 @@
{{-- # FIXME : Il manque les scripts JS pour filtrer et voir plus --}}
@php
// FIXME: a retirer une fois le composant corriger
$currentRoute = \Illuminate\Support\Facades\Route::currentRouteName();
$newsModels = $newsModels ?? null;
$news = $currentRoute === 'front.finance' ? null : ($newsModels ? $newsModels->items() : null);
$maxPosts = $currentRoute === 'front.finance' ? 3 : ($newsModels ? $newsModels->perPage() : null);
@endphp
@if (isset($postContainer))
<div class="{{ isset($postContainer['class']) ? $postContainer['class'] : '' }} filter-posts">
<div class="filter fw {{ isset($postContainer['secondaryClass']) ? $postContainer['secondaryClass'] : '' }}"
data-limit="{{ $maxPosts }}">
<div class="filter__wrapper wrapper">
<div class="filter-menu">
<div class="filter__select-wrap filter-select-wrap">
@if (isset($filters))
@foreach ($filters as $filter)
<ul class="filter-select">
<li>
<a title="{{ $filter['title'] }}">{{ $filter['title'] }}</a>
<ul>
@foreach ($filter['fields'] as $field)
<li>
<a class="tax-filter {{ isset($field['class']) ? $field['class'] : '' }}"
@if (isset($field['tag'])) data-tag="{{ $field['tag'] }}" @endif
@if (isset($field['year'])) data-year="{{ $field['year'] }}" @endif
@if (isset($field['order'])) data-year="{{ $field['order'] }}" @endif
data-category="{{ $field['category'] }}"
title="{{ $field['title'] }}">
{{ $field['title'] }}
</a>
</li>
@endforeach
</ul>
</li>
</ul>
@endforeach
@endif
<div class="filter-results-num">
{{ ucfirst(__('front.news.results')) }} :
<span class="filter-results-num__wrap">
<span class="filter-results-num__current">
{{ ucfirst(__('front.news.on_document_count', ['current' => 0, 'total' => 10])) }}
</span>
</span>
</div>
</div>
</div>
<div class="active-filters">
<div class="active-filters__list">
</div>
<div class="active-filters__clear-wrap">
<div class="active-filters__item active-filters__item_clear">
<a class="active-filters__item-remove clear-all"
title="{{ ucfirst(__('front.buttons.clear_filters')) }}">✖</a>
{{ ucfirst(__('front.buttons.clear_filters')) }}
</div>
</div>
</div>
</div>
</div>
<div class="posts-list fw {{ isset($postContainer['tertiaryClass']) ? $postContainer['tertiaryClass'] : '' }}">
<div class="posts-list__wrapper wrapper">
<div id="posts-container" class="list-items posts-container">
@if ($postContainer['list_type'] === 'news')
<div id="ajax-load-more" class="ajax-load-more-wrap infinite fading-circles alm-0 alm-is-loaded"
data-slug="{{ $postContainer['slug'] }}">
<div class="alm-listing alm-ajax wrapper" aria-live="polite" aria-atomic="true"
data-container-type="div" data-loading-style="infinite fading-circles"
data-theme-repeater="default" data-post-type="post"
data-category="{{ $postContainer['slug'] }}" data-order="DESC" data-orderby="date"
data-offset="0" data-posts-per-page="{{ $maxPosts }}" data-lang="fr"
data-max-pages="1" data-button-label="{{ $postContainer['load_more'] }}"
data-prev-button-label="Load Previous">
@if (isset($news))
@foreach ($news as $new)
<x-front.sections.news :borders="$postBorders ?? []" :news='$new' />
@endforeach
@endif
</div>
<div class="alm-btn-wrap" data-rel="ajax-load-more">
<button class="alm-load-more-btn more btn"
type="button">{{ $postContainer['load_more'] }}</button>
</div>
</div>
@else
<div class="list-items list-items_doc">
{{-- # FIXME : Les IDs sont surement à remplacer. A voir en fonction de l'élement (ex: ajax-load-more-2 ou ajax-load-more-4) --}}
<div id="ajax-load-more"
class="ajax-load-more-wrap infinite fading-circles alm-0 alm-is-loaded"
data-id="{{ $postContainer['id'] }}" data-alm-id="0"
data-slug="{{ $postContainer['slug'] }}"
data-post-id="{{ $postContainer['post_id'] }}"
data-localized="{{ $postContainer['localized'] }}"
data-alm-object="{{ $postContainer['object'] }}" data-total-posts="15">
<div class="alm-listing alm-ajax wrapper" aria-live="polite" aria-atomic="true"
data-container-type="div" data-loading-style="infinite fading-circles"
data-theme-repeater="list-item-presentation" data-post-type="post"
data-category="{{ $postContainer['slug'] }}" data-order="DESC" data-orderby="date"
data-offset="0" data-posts-per-page="{{ $maxPosts }}" data-lang="fr"
data-max-pages="1" data-button-label="{{ $postContainer['load_more'] }}"
data-prev-button-label="Load Previous">
@foreach ($postContainer['documents'] as $document)
@if ($postContainer['accordeon'])
<x-front.sections.accordion.document-accordion :document='$document' />
@else
<a class="posts-list__item list-item-doc" href="{{ $document['url'] }}"
title="{{ $document['title'] }}" download=""
style="opacity: 1; transition: 250ms;">
{{ $document['title'] }}
</a>
@endif
@endforeach
</div>
<div class="alm-btn-wrap" data-rel="ajax-load-more" style="visibility: visible;">
<button class="alm-load-more-btn more btn"
type="button">{{ $postContainer['load_more'] }}</button>
</div>
</div>
</div>
@endif
</div>
</div>
</div>
</div>
@endif

View file

@ -1,30 +0,0 @@
@php
$newsBorders = [
'desktop' => [
'left' => false,
'right' => false,
'top' => false,
'bottom' => true,
],
'mobile' => [
'left' => false,
'right' => false,
'top' => false,
'bottom' => true,
],
];
$filters = [
[
'title' => ucfirst(__('front.news.theme')),
'fields' => $categories,
],
[
'title' => ucfirst(__('front.news.year')),
'fields' => $years,
],
];
@endphp
<x-front.filter-posts :postContainer="$postContainer" :newsModels="$newsModels" :postBorders="$newsBorders" :filters="$filters" />

View file

@ -1,20 +1,23 @@
@php
$locale = config('app.locale');
$param = "?tags=" . ($locale === "fr" ? "evenement" : "event");
@endphp
<div class="events-prof fw fw-container no-top-padding">
<div class="recent-posts__wrapper wrapper">
<h3 class="events-prof__sutbitle subtitle">Prochains évènements</h3>
<h3 class="events-prof__sutbitle subtitle">{{ ucfirst(__('front.next_events')) }}</h3>
<div class="events-list fw-container no-bottom-padding">
<div class="posts-list fw tagged-posts">
<div class="posts-list__wrapper">
<div class="list-items list-items_events">
@foreach ($nextEvents as $event)
<x-front.sections.news :news='$event' />
@endforeach
<x-front.posts.news :newsList='$nextEvents' />
</div>
</div>
</div>
</div>
@if (isset($pageActualites))
<x-front.cta title="Voir tous nos évènements, forums et job dating"
url="{{ \App\Helpers\RouteHelper::routeNameChecker(sprintf('front.dynamic-pages.%s', $pageActualites->id_key)) }}"
<x-front.cta title="{{ ucfirst(__('front.buttons.see_more_events')) }}"
url="{{ \App\Helpers\RouteHelper::routeNameChecker(sprintf('front.dynamic-pages.%s', $pageActualites->id_key)) . $param }}"
buttonIcon="btn__icon_arrow-right" />
@endif
</div>

View file

@ -1,6 +1,19 @@
@php
$locale = config('app.locale');
$postContainer = [
'class' => 'posts',
'secondaryClass' => 'filter_doc',
'tertiaryClass' => 'fw-container no-top-padding',
'slug' => 'docs',
'category' => \App\Models\DocumentCategory::where('slug->fr', 'assemblees-generales')->first()?->slug ?? '',
'load_more' => ucfirst(__('front.buttons.see_more_docs')),
];
@endphp
<div
id="{{ data_get($data, 'string_title') ? Str::slug(data_get($data, 'string_title')) : '' }}"
class="textblock fw fw-container assemblees-generales half-bottom-padding">
id="{{ data_get($data, 'string_title') ? Str::slug(data_get($data, 'string_title')) : '' }}"
class="textblock fw fw-container assemblees-generales half-bottom-padding">
<div class="textblock__wrapper wrapper">
<x-front.sections.title :title="data_get($data, 'string_title')" :subtitle="data_get($data, 'string_subtitle')" />
<div class="textblock__container textblock__container_row">
@ -31,152 +44,4 @@ class="textblock fw fw-container assemblees-generales half-bottom-padding">
</div>
</div>
</div>
{{-- # FIXME: Faire un composant générique avec les scripts associés (filtrer / voir plus) --}}
{{-- <div class="posts filter-posts">
<div class="filter filter_doc fw" data-posts="#$category" data-list="list-item-doc">
<div class="filter__wrapper wrapper">
<div class="filter-menu">
<div class="filter__select-wrap filter-select-wrap">
<ul class="filter-select">
<li>
<a title="Afficher">Afficher</a>
<ul>
<li>
<a class="tax-filter parida-filter" data-order="ASC" data-category="meetings"
title="Du plus ancien au plus récent.">Du plus ancien au plus récent.</a>
</li>
<li>
<a class="tax-filter parida-filter" data-order="DESC" data-category="meetings"
title="Du plus récent au plus ancien.">Du plus récent au plus ancien.</a>
</li>
</ul>
</li>
</ul>
<ul class="filter-select">
<li>
<a title="Document">Document</a>
<ul>
<li>
<a class="tax-filter" data-tag="assemblee-generale-des-actionnaires"
data-category="meetings">Assemblée générale des actionnaires</a>
</li>
<li>
<a class="tax-filter" data-tag="finance" data-category="meetings">Finance</a>
</li>
<li>
<a class="tax-filter" data-tag="statuts-de-la-sa-ldc"
data-category="meetings">Statuts de la SA LDC</a>
</li>
<li>
<a class="tax-filter" data-tag="rapports-financiers-annuels-et-semestriels"
data-category="meetings">Rapports financiers annuels et semestriels</a>
</li>
</ul>
</li>
</ul>
<ul class="filter-select">
<li>
<a title="Exercice">Exercice</a>
<ul>
<li>
<a class="tax-filter" data-year="2025/2026" data-category="meetings">2025/2026</a>
</li>
<li>
<a class="tax-filter" data-year="2024/2025" data-category="meetings">2024/2025</a>
</li>
<li>
<a class="tax-filter" data-year="2023/2024" data-category="meetings">2023/2024</a>
</li>
<li>
<a class="tax-filter" data-year="2022/2023" data-category="meetings">2022/2023</a>
</li>
<li>
<a class="tax-filter" data-year="2021/2022" data-category="meetings">2021/2022</a>
</li>
<li>
<a class="tax-filter" data-year="2020/2021" data-category="meetings">2020/2021</a>
</li>
<li>
<a class="tax-filter" data-year="2019/2020" data-category="meetings">2019/2020</a>
</li>
<li>
<a class="tax-filter" data-year="2018/2019" data-category="meetings">2018/2019</a>
</li>
<li>
<a class="tax-filter" data-year="2017/2018" data-category="meetings">2017/2018</a>
</li>
<li>
<a class="tax-filter" data-year="2016/2017" data-category="meetings">2016/2017</a>
</li>
<li>
<a class="tax-filter" data-year="2015/2016" data-category="meetings">2015/2016</a>
</li>
<li>
<a class="tax-filter" data-year="2014/2015" data-category="meetings">2014/2015</a>
</li>
</ul>
</li>
</ul>
<div class="filter-results-num">
Résultat : <span class="filter-results-num__wrap"><span
class="filter-results-num__current">0</span> documents sur <span
class="filter-results-num__total">173</span></span>
</div>
</div>
</div>
<div class="active-filters">
<div class="active-filters__list"></div>
<div class="active-filters__clear-wrap">
<div class="active-filters__item active-filters__item_clear">
<a class="active-filters__item-remove clear-all" data-category="meetings"
title="Effacer les filtres">✖</a> Effacer les filtres
</div>
</div>
</div>
</div>
</div>
<div class="posts-list finance-page-posts-list fw fw-container no-top-padding">
<div class="posts-list__wrapper wrapper">
<div class="posts-container">
<div class="list-items list-items_doc">
<div id="ajax-load-more-3" class="ajax-load-more-wrap infinite fading-circles alm-2 alm-is-loaded"
data-id="finance_7" data-alm-id="2"
data-canonical-url="/declaration-annuelle-dindependance-du-commissaire-aux-comptes-pour-lexercice-clos-le-28-02-2015/"
data-slug="declaration-annuelle-dindependance-du-commissaire-aux-comptes-pour-lexercice-clos-le-28-02-2015"
data-post-id="4957" data-localized="ajax_load_more_finance_7_vars"
data-alm-object="ajax_load_more_finance_7" data-total-posts="173">
<div aria-live="polite" aria-atomic="true" class="alm-listing alm-ajax wrapper"
data-container-type="div" data-loading-style="infinite fading-circles"
data-theme-repeater="list-item-doc" data-post-type="post" data-category="meetings"
data-order="DESC" data-orderby="date" data-offset="0" data-posts-per-page="3"
data-lang="fr" data-max-pages="1" data-button-label="Voir plus de documents"
data-prev-button-label="Load Previous">
<a class="posts-list__item list-item-doc"
href="/assets/uploads/2025/08/Resultats-du-vote-Assemblee-Generale-LDC-du-21.08.2025.pdf"
title="Résultats du vote Assemblée Générale LDC du 21.08.2025" download=""
style="opacity: 1; transition: 250ms;">
Résultats du vote Assemblée Générale LDC du 21.08.2025
</a>
<a class="posts-list__item list-item-doc"
href="/assets/uploads/2025/08/2025.-LDC_Politique-de-remuneration-des-mandataires-sociaux.pdf"
title="Politique de rémunération des mandataires sociaux – AG 21/08/2025"
download="" style="opacity: 1; transition: 250ms;">
Politique de rémunération des mandataires sociaux – AG 21/08/2025
</a>
<a class="posts-list__item list-item-doc"
href="/assets/uploads/2025/07/LDC-SA-Attestation-remunerations-2025.pdf"
title="LDC SA – Attestation rémunérations 2025" download=""
style="opacity: 1; transition: 250ms;">
LDC SA – Attestation rémunérations 2025
</a>
</div>
<div class="alm-btn-wrap" data-rel="ajax-load-more-3" style="visibility: visible;">
<button class="alm-load-more-btn more btn" type="button">Voir plus de documents</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div> --}}
<x-front.posts.posts-index :postContainer="$postContainer" />

View file

@ -1,133 +1,30 @@
@php
// FIXME: les filtres et les docs
$finance = [
'postContainer' => [
'class' => 'posts',
'secondaryClass' => 'filter_presentation',
'tertiaryClass' => 'fw-container no-top-padding',
'list_type' => 'documents',
'accordeon' => 1,
'id' => 'finance_4',
'post_id' => '6088',
'slug' => 'resultats-annuels-2015-2016-2',
'url' => '/resultats-annuels-2015-2016-2/#FIXME',
'localized' => 'ajax_load_more_finance_4_vars',
'object' => 'ajax_load_more_finance_4',
'max_posts' => '9',
'load_more' => 'Voir plus de documents',
'filters' => [
[
'title' => 'Afficher',
'fields' => [
[
'title' => 'Du plus ancien au plus récent.',
'category' => 'presentation',
'order' => 'ASC',
'class' => 'parida-filter',
],
[
'title' => 'Du plus récent au plus ancien.',
'category' => 'presentation',
'order' => 'DESC',
'class' => 'parida-filter',
],
],
],
[
'title' => 'Exercice',
'fields' => [
[
'title' => '2024/2025',
'year' => '2024/2025',
'category' => 'presentation',
],
[
'title' => '2023/2024',
'year' => '2023/2024',
'category' => 'presentation',
],
[
'title' => '2022/2023',
'year' => '2022/2023',
'category' => 'presentation',
],
[
'title' => '2021/2022',
'year' => '2021/2022',
'category' => 'presentation',
],
[
'title' => '2020/2021',
'year' => '2020/2021',
'category' => 'presentation',
],
[
'title' => '2019/2020',
'year' => '2019/2020',
'category' => 'presentation',
],
[
'title' => '2018/2019',
'year' => '2018/2019',
'category' => 'presentation',
],
[
'title' => '2017/2018',
'year' => '2017/2018',
'category' => 'presentation',
],
[
'title' => '2016/2017',
'year' => '2016/2017',
'category' => 'presentation',
],
[
'title' => '2015/2016',
'year' => '2015/2016',
'category' => 'presentation',
],
],
],
],
'documents' => [
[
'title' => 'Présentation des résultats annuels 2024/2025',
'url' => 'https://e.issuu.com/embed.html?d=pr_sentation_ldc_ra_2024-2025_vdef&u=ldc-digital',
],
[
'title' => 'Présentation des résultats semestriels 2024/2025',
'url' =>
'https://e.issuu.com/embed.html?d=pr_sentation_ldc_rs_2024-2025_vdef&pageLayout=singlePage&u=ldc-digital',
],
[
'title' => 'Présentation des résultats annuels 2023/2024',
'url' =>
'https://e.issuu.com/embed.html?d=pr_sentation_des_r_sultats_annuels_2023_2024&u=ldc-digital',
],
],
],
$postContainer = [
'class' => 'posts',
'secondaryClass' => 'filter_doc',
'tertiaryClass' => 'fw-container no-top-padding',
'slug' => 'docs',
'category' => \App\Models\DocumentCategory::where('slug->fr', 'presentation-investisseurs')->first()?->slug ?? '',
'load_more' => ucfirst(__('front.buttons.see_more_docs')),
];
@endphp
@if (!empty($finance))
<div id="{{ data_get($data, 'string_title') ? Str::slug(data_get($data, 'string_title')) : '' }}">
<div class="presentation gray-bg">
<div class="textblock fw fw-container textblock_bg">
<div class="textblock__wrapper wrapper">
<x-front.sections.title :title="$data['string_title']" :subtitle="$data['string_subtitle']" />
@if (!empty($data['string_content']))
<div class="textblock__container">
<div class="textblock__info textblock__block">
<div class="textblock__info-description description">
<p>{{ $data['string_content'] }}</p>
</div>
<div id="{{ data_get($data, 'string_title') ? Str::slug(data_get($data, 'string_title')) : '' }}">
<div class="presentation gray-bg">
<div class="textblock fw fw-container textblock_bg">
<div class="textblock__wrapper wrapper">
<x-front.sections.title :title="$data['string_title']" :subtitle="$data['string_subtitle']" />
@if (!empty($data['string_content']))
<div class="textblock__container">
<div class="textblock__info textblock__block">
<div class="textblock__info-description description">
<p>{{ $data['string_content'] }}</p>
</div>
</div>
@endif
</div>
</div>
@endif
</div>
{{-- <x-front.filter-posts :newsModel="null" :postContainer="$finance['postContainer']" /> --}}
</div>
<x-front.posts.posts-index :postContainer="$postContainer" />
</div>
@endif
</div>

View file

@ -1,172 +1,10 @@
@php
$finance = [
'postContainer' => [
'class' => 'posts',
'secondaryClass' => 'filter_doc',
'list_type' => 'documents',
'accordeon' => 0,
'id' => 'finance_6',
'post_id' => '4025',
'slug' => 'societe-la-toque-angevine-projet-de-regroupement-de-lensemble-des-activites-sandwichs-sur-le-site-de-segre',
'url' => '/societe-la-toque-angevine-projet-de-regroupement-de-lensemble-des-activites-sandwichs-sur-le-site-de-segre/#FIXME',
'localized' => 'ajax_load_more_finance_6_vars',
'object' => 'ajax_load_more_finance_6',
'max_posts' => '3',
'load_more' => 'Voir plus de documents',
'filters' => [
[
'title' => 'Afficher',
'fields' => [
[
'title' => 'Du plus ancien au plus récent.',
'category' => 'presentation',
'order' => 'ASC',
'class' => 'parida-filter',
],
[
'title' => 'Du plus récent au plus ancien.',
'category' => 'presentation',
'order' => 'DESC',
'class' => 'parida-filter',
],
],
],
[
'title' => 'Document',
'fields' => [
[
'title' => 'Descriptif de programme de rachats d\'actions',
'category' => 'documents',
'tag' => 'descriptif-de-programme-de-rachats-dactions',
],
[
'title' => 'Opérations sur contrat de liquidité',
'category' => 'documents',
'tag' => 'operations-sur-contrat-de-liquidite',
],
[
'title' => 'Déclaration mensuelles du nombre d’actions et de droits de vote',
'category' => 'documents',
'tag' => 'declaration-mensuelles-du-nombre-dactions-et-de-droits-de-vote',
],
[
'title' => 'Communiqués et Avis financiers',
'category' => 'documents',
'tag' => 'communiques-et-avis-financiers',
],
[
'title' => 'Rapports financiers annuels et semestriels',
'category' => 'documents',
'tag' => 'rapports-financiers-annuels-et-semestriels',
],
[
'title' => 'Information Trimestrielle',
'category' => 'documents',
'tag' => 'information-trimestrielle',
],
[
'title' => 'Publication',
'category' => 'documents',
'tag' => 'publication',
],
[
'title' => 'Finance',
'category' => 'documents',
'tag' => 'finance',
],
[
'title' => 'Assemblée générale des actionnaires',
'category' => 'documents',
'tag' => 'assemblee-generale-des-actionnaires',
],
[
'title' => 'Déclaration des rachats d\'actions',
'category' => 'documents',
'tag' => 'declaration-des-rachats-dactions',
],
[
'title' => 'Statuts de la SA LDC',
'category' => 'documents',
'tag' => 'statuts-de-la-sa-ldc',
],
],
],
[
'title' => 'Exercice',
'fields' => [
[
'title' => '2024/2025',
'year' => '2024/2025',
'category' => 'presentation',
],
[
'title' => '2023/2024',
'year' => '2023/2024',
'category' => 'presentation',
],
[
'title' => '2022/2023',
'year' => '2022/2023',
'category' => 'presentation',
],
[
'title' => '2021/2022',
'year' => '2021/2022',
'category' => 'presentation',
],
[
'title' => '2020/2021',
'year' => '2020/2021',
'category' => 'presentation',
],
[
'title' => '2019/2020',
'year' => '2019/2020',
'category' => 'presentation',
],
[
'title' => '2018/2019',
'year' => '2018/2019',
'category' => 'presentation',
],
[
'title' => '2017/2018',
'year' => '2017/2018',
'category' => 'presentation',
],
[
'title' => '2016/2017',
'year' => '2016/2017',
'category' => 'presentation',
],
[
'title' => '2015/2016',
'year' => '2015/2016',
'category' => 'presentation',
],
[
'title' => '2014/2015',
'year' => '2014/2015',
'category' => 'presentation',
],
],
],
],
'documents' => [
[
'title' => 'Descriptif du programme de rachat d\'actions AG 21 08 2025',
'url' => '/assets/uploads/2025/08/Descriptif-du-prog-rachat-dactions-AG-21-08-2025-VDEF.pdf',
],
[
'title' => 'Bilan semestriel du contrat de liquidité au 30 juin 2025',
'url' => '/assets/uploads/2025/07/LDC-Contrat-de-liquidite-bilan-semestriel-au-30-06-2025.pdf',
],
[
'title' => 'Droits de vote au 30.06.2025',
'url' => '/assets/uploads/2025/07/DDV-LDC-JUIN-2025.pdf',
],
],
],
$postContainer = [
'class' => 'posts',
'secondaryClass' => 'filter_doc',
'slug' => 'docs',
'category' => \App\Models\DocumentCategory::where('slug->fr', 'documents')->first()?->slug ?? '',
'load_more' => ucfirst(__('front.buttons.see_more_docs')),
];
@endphp
@ -184,6 +22,6 @@
</div>
</div>
</div>
{{-- <x-front.filter-posts :postContainer="$finance['postContainer']" /> --}}
<x-front.posts.posts-index :postContainer="$postContainer" />
</div>
</div>

View file

@ -22,7 +22,7 @@
class="kfar">
<div class="textblock fw fw-container">
<div class="textblock__wrapper wrapper">
<x-front.sections.title :title="$data['string_title']" :subtitle="$data['string_subtitle']" />
<x-front.sections.title :title="data_get($data, 'string_title')" :subtitle="data_get($data, 'string_title')" />
<div class="textblock__container">
<div class="textblock__info textblock__block"></div>
</div>

View file

@ -0,0 +1,9 @@
@if (!empty($documentsList))
@foreach ($documentsList as $documents)
<a class="posts-list__item list-item-doc" href="{{ $documents['path'] }}"
title="{{ $documents['name'] }}" download=""
style="opacity: 1; transition: 250ms;">
{{ $documents['name'] }}
</a>
@endforeach
@endif

View file

@ -0,0 +1,25 @@
@if (isset($filters))
@foreach ($filters as $filter)
@if (collect($filter['fields'])->isNotEmpty())
<ul id="dropdown" data-name="{{ $filter['name'] }}" class="filter-select">
<li>
<a title="{{ $filter['title'] }}">{{ $filter['title'] }}</a>
<ul id="{{ $filter['name'] }}">
@if (!empty($filter['fields']))
@foreach ($filter['fields'] as $field)
<li>
<a class="tax-filter"
data-category="{{ $field['category'] }}"
data-value="{{ $field['tag'] }}"
title="{{ $field['title'] }}">
{{ $field['title'] }}
</a>
</li>
@endforeach
@endif
</ul>
</li>
</ul>
@endif
@endforeach
@endif

View file

@ -0,0 +1,44 @@
@php
$locale = config('app.locale');
$isButtonStyle = Route::is("front.dynamic-pages.espace-presse.$locale");
$borders = [
'desktop' => ['left' => false, 'right' => false, 'top' => false, 'bottom' => true],
'mobile' => ['left' => false, 'right' => false, 'top' => false, 'bottom' => true],
];
@endphp
@if (!empty($newsList))
@foreach ($newsList as $news)
<div class="posts-list__item list-item" style="opacity: 1; transition: 250ms;">
<div class="list-item__image image-border-wrap image-borders">
<img src="{{ asset($news['picture']) }}" class="attachment-post-thumbnail size-post-thumbnail wp-post-image"
alt="{{ isset($news['picture_alt']) ? $news['picture_alt'] : '' }}" decoding="async" loading="lazy"
srcset="{{ $news['picture'] }}">
<x-front.borders :borders="!empty($borders) ? $borders : []" />
</div>
<div class="list-item__description description">
@if (isset($news->tag_labels))
<div class="list-item__tags tags">
<div class="single-news__tag tag {{ $news->first_tag_data['category_classes'] ?? '' }}">
{{ $news->tag_labels }}
</div>
</div>
@endif
<div class="list-item__title posts-list__item-title subtitle">{{ $news['title'] }}</div>
<div class="list-item__date date {{ $news->first_category_data['date_classes'] ?? 'date_no-tags' }}">
{{ $news->publishedAtFormatted() }}
</div>
<div class="list-item__text ellipsis description"></div>
@if ($isButtonStyle)
<x-front.cta title="{{ ucfirst(__('front.news.read_article')) }}" class="list-item__readmore"
url="{{ route("front.news.show", $news) }}" buttonIcon="btn__icon_arrow-right" />
@else
<a class="list-item__readmore" href="{{ route("front.news.show", $news) }}" title="{{ ucfirst(__('front.news.read_article')) }}">
{{ ucfirst(__('front.news.read_article')) }}
</a>
@endif
</div>
</div>
@endforeach
@endif

View file

@ -0,0 +1,250 @@
@php
$wrapperId = 'wrapper-' . uniqid();
@endphp
@if (isset($postContainer))
<div id="{{ $wrapperId }}" data-current-locale="{{ app()->getLocale() }}" @class([
'filter-posts',
$postContainer['class'] ?? null,
])>
<div @class([
'filter fw',
$postContainer['secondaryClass'] ?? null
])>
<div class="filter__wrapper wrapper">
<div class="filter-menu">
<div id="filters" class="filter__select-wrap filter-select-wrap"></div>
<div class="filter-results-num">
{{ ucfirst(__('front.news.results')) }} :
<span class="filter-results-num__wrap">
<span class="filter-results-num__wrap">
<span id="results" class="filter-results-num__current"></span>
{{ __('front.news.on_document_count') }}
<span id="all-results" class="filter-results-num__total"></span>
</span>
</span>
</div>
</div>
<div class="active-filters">
<div id="selected-filters" class="active-filters__list"></div>
<div class="active-filters__clear-wrap">
<div class="active-filters__item active-filters__item_clear">
<a class="active-filters__item-remove" data-category="{{ $postContainer['category'] }}" data-param="clear-all"
title="{{ ucfirst(__('front.buttons.clear_filters')) }}">✖</a>
{{ ucfirst(__('front.buttons.clear_filters')) }}
</div>
</div>
</div>
</div>
</div>
<div @class([
'posts-list fw',
$postContainer['tertiaryClass'] ?? null
])>
<div class="posts-list__wrapper wrapper">
<div @class([
'list-items',
'posts-container' => $postContainer['slug'] === "news",
'list-items_doc' => $postContainer['slug'] === "docs",
])>
<div id="loader" class="lds-ellipsis hidden"><div></div><div></div><div></div><div></div></div>
<div id="section" class="ajax-load-more-wrap infinite fading-circles alm-0 alm-is-loaded">
<div id="posts" class="alm-listing alm-ajax wrapper" data-slug="{{ $postContainer['slug'] }}" data-category="{{ $postContainer['category'] }}"></div>
<div class="alm-btn-wrap">
<button id="load-more" class="alm-load-more-btn more btn" data-page="1" type="button">{{ $postContainer['load_more'] }}</button>
</div>
</div>
</div>
</div>
</div>
</div>
@push('scripts')
<script nonce="{{ $nonce }}">
(function() {
const wrapper = document.getElementById('{{ $wrapperId }}');
const currentLocale = wrapper.dataset.currentLocale;
const postsContainer = wrapper.querySelector('#posts');
const filtersContainer = wrapper.querySelector('#filters');
const nbResults = wrapper.querySelector('#results');
const allResults = wrapper.querySelector('#all-results');
const loadMoreBtn = wrapper.querySelector('#load-more');
const sFiltersContainer = wrapper.querySelector('#selected-filters');
const loader = wrapper.querySelector("#loader");
const section = wrapper.querySelector("#section");
const tblFilters = {};
let hasLoaded = false;
function isLoading(value = false) {
if (value) {
loader.classList.remove('hidden');
section.classList.add('hidden');
} else {
loader.classList.add('hidden');
section.classList.remove('hidden');
}
}
function ajaxLoadPosts(clear = false, filters = {}) {
const currentFilters = new URLSearchParams(window.location.search).toString();
const nextPage = clear ? 1 : parseInt(loadMoreBtn.dataset.page);
const { slug, category } = postsContainer.dataset;
let endpoints = null;
isLoading(true);
if (slug === "news") {
endpoint = "news";
} else if (slug === "docs") {
endpoint = "documents";
}
if (!endpoint) return;
fetch(`/posts/load/${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({
page: nextPage,
category: category,
filters: filters,
locale: currentLocale
})
})
.then(response => response.json())
.then(data => updatePosts(data, clear))
.finally(function () {
isLoading(false);
});
}
function updatePosts(data, clear) {
const pagination = data.pagination;
if (clear) {
postsContainer.innerHTML = data.news;
} else {
postsContainer.insertAdjacentHTML('beforeend', data.news);
}
sFiltersContainer.innerHTML = data.selectedFilters;
filtersContainer.innerHTML = data.filters;
nbResults.innerHTML = postsContainer.childElementCount ?? 0;
allResults.innerHTML = pagination.total ?? 0;
if (pagination.hasMore) {
loadMoreBtn.style.visibility = 'visible';
loadMoreBtn.dataset.page = pagination.currentPage + 1;
} else {
loadMoreBtn.style.visibility = 'hidden';
}
initFilters();
initSelectedFilters();
}
document.addEventListener('scroll', function () {
if (hasLoaded) return;
const wrapperPos = wrapper.getBoundingClientRect();
const isVisible = wrapperPos.top < window.innerHeight * 1.5;
if (isVisible) {
hasLoaded = true;
ajaxLoadPosts();
}
});
loadMoreBtn.addEventListener('click', function() {
loadMoreBtn.classList.add('loading');
loadMoreBtn.disabled = true;
setTimeout(() => {
ajaxLoadPosts();
loadMoreBtn.classList.remove('loading');
loadMoreBtn.disabled = false;
}, 1000);
});
function initFilters() {
const filters = filtersContainer.querySelectorAll('.filter-select');
filters.forEach(filter => {
const filterName = filter.dataset.name;
filter.querySelectorAll('li a.tax-filter').forEach(link => {
const value = link.dataset.value;
const category = link.dataset.category;
if (!tblFilters[category]) {
tblFilters[category] = {
tags: [],
years: [],
order: null
};
}
const values = tblFilters?.[category]?.[filterName] ?? [];
if (values.includes(value)) {
link.classList.add('disabled');
} else {
link.classList.remove('disabled');
}
link.addEventListener('click', function(e) {
e.preventDefault();
const state = tblFilters[category];
if (filterName === 'order') {
state.order = value;
this.classList.add('disabled');
} else {
if (!state[filterName].includes(value)) {
state[filterName].push(value);
this.classList.add('disabled');
}
}
ajaxLoadPosts(true, state);
});
});
});
}
function initSelectedFilters() {
const items = wrapper.querySelectorAll('.active-filters__item-remove');
items.forEach(item => {
item.addEventListener('click', function (e) {
e.preventDefault();
const category = item.dataset.category;
const filterName = item.dataset.param;
const value = item.dataset.value;
if (!category || !tblFilters[category]) return;
if (filterName === 'clear-all') {
Object.keys(tblFilters[category]).forEach(key => {
tblFilters[category][key] = Array.isArray(tblFilters[category][key]) ? [] : null;
});
} else if (filterName && value) {
const tblItem = tblFilters[category][filterName];
if (Array.isArray(tblItem)) {
tblFilters[category][filterName] = tblItem.filter(v => v !== value);
} else if (tblItem === value) {
tblFilters[category][filterName] = null;
}
}
ajaxLoadPosts(true, tblFilters[category]);
});
});
}
})();
</script>
@endpush
@endif

View file

@ -0,0 +1,10 @@
@if (isset($selectedFilters))
@foreach ($selectedFilters as $key => $filters)
@foreach ($filters as $filter)
<div class="active-filters__item active-filters__item_default" style="background-color: {{ isset($filter['color']) ? $filter['color'] : '' }}">
<a class="active-filters__item-remove" data-category="{{ $filter['category'] }}" data-param="{{ $key }}" data-value="{{ $filter['tag'] }}" title="{{ ucfirst($filter['title']) }}">✖</a>
{{ ucfirst($filter['title']) }}
</div>
@endforeach
@endforeach
@endif

View file

@ -1,14 +0,0 @@
@if(!empty($document))
<div class="posts-list__item list-item-presentation accordeon__item accordeon-item" style="opacity: 1; transition: 250ms;">
<div class="accordeon-item__title">{{ $document['title'] }}</div>
<div class="accordeon-item__info" style="display: none;">
<div class="accordeon-item__info-inner">
<div class="accordeon-item__text description">
<div style="position: relative; padding-top: max(60%,326px); height: 0; width: 100%;">
<iframe style="position: absolute; border: none; width: 100%; height: 100%; left: 0; right: 0; top: 0; bottom: 0;" src="{{ $document['url'] }}" sandbox="allow-top-navigation allow-top-navigation-by-user-activation allow-downloads allow-scripts allow-same-origin allow-popups allow-modals allow-popups-to-escape-sandbox allow-forms" allowfullscreen="allowfullscreen"></iframe>
</div>
</div>
</div>
</div>
</div>
@endif

View file

@ -1,38 +0,0 @@
@php
$isButtonStyle = Route::is('front.dynamic-pages.espace-presse');
$url = Route::is('front.dynamic-pages.nous-rejoindre') ? '' : route('front.news.show', $news);
@endphp
@if (!empty($news))
<div class="posts-list__item list-item" style="opacity: 1; transition: 250ms;">
<div class="list-item__image image-border-wrap image-borders">
<img src="{{ asset($news['picture']) }}" class="attachment-post-thumbnail size-post-thumbnail wp-post-image"
alt="{{ isset($news['picture_alt']) ? $news['picture_alt'] : '' }}" decoding="async" loading="lazy"
srcset="{{ $news['picture'] }}">
<x-front.borders :borders="!empty($borders) ? $borders : []" />
</div>
<div class="list-item__description description">
@if (isset($news->tag_labels))
<div class="list-item__tags tags">
<div class="single-news__tag tag {{ $news->first_tag_data['category_classes'] ?? '' }}">
{{ $news->tag_labels }}
</div>
</div>
@endif
<div class="list-item__title posts-list__item-title subtitle">{{ $news['title'] }}</div>
<div class="list-item__date date {{ $news->first_tag_data['date_classes'] ?? 'date_no-tags' }}">
{{ $news->publishedAtFormatted() }}
</div>
<div class="list-item__text ellipsis description"></div>
@if ($isButtonStyle)
<x-front.cta title="Lire l'article" class="list-item__readmore"
url="{{ $url }}" buttonIcon="btn__icon_arrow-right" />
@else
<a class="list-item__readmore" href="{{ $url }}" title="Lire l'article">
{{ ucfirst(__('front.news.read_article')) }}
</a>
@endif
</div>
</div>
@endif

View file

@ -4,5 +4,16 @@
@section('description', $dynamicPageModel->content['textarea_seo_description'])
@section('bodyClass', 'archive category category-news category-9')
@section('content')
<x-front.news.index-news-component />
@php
$postContainer = [
'secondaryClass' => 'fw-container no-bottom-padding',
'tertiaryClass' => 'tagged-posts',
'slug' => 'news',
'load_more' => ucfirst(__('front.buttons.see_more_news')),
'category' => \App\Models\NewsCategory::where('slug->fr', 'actualites')->first()?->slug ?? '',
];
@endphp
<x-front.posts.posts-index :postContainer="$postContainer" />
@endsection

View file

@ -5,29 +5,38 @@
@section('bodyClass', 'page-template page-template-page-press page-template-page-press-php page page-id-14')
@section('content')
<div class="page-press">
<div class="page-press-description fw fw-container">
<div class="page-press-description__wrapper wrapper description">
{!! $dynamicPageModel->content['wysiwyg_content'] !!}
<div class="page-press">
<div class="page-press-description fw fw-container">
<div class="page-press-description__wrapper wrapper description">
{!! $dynamicPageModel->content['wysiwyg_content'] !!}
</div>
</div>
</div>
<x-front.news.index-news-component />
@php
$postContainer = [
'class' => 'posts',
'tertiaryClass' => 'fw-container no-top-padding',
'slug' => 'news',
'category' => \App\Models\NewsCategory::where('slug->fr', 'espace-presse')->first()?->slug ?? '',
'load_more' => ucfirst(__('front.buttons.see_more_docs')),
];
@endphp
<div id="page-press-form">
<div class="page-press-form fw fw-container gray-bg">
<div class="page-press-form__wrapper wrapper">
<x-front.sections.title secondaryClass="page-press-form__subtitle" class="page-press-form__title"
title="{{ ucfirst(__('front.contact_our')) }}"
subtitle="{{ __('front.press_department') }}" />
<x-front.posts.posts-index :postContainer="$postContainer" />
<div class="fw-container no-bottom-padding">
<div class="wpcf7-form">
@include('front.pages.contact.partials.press')
<div id="page-press-form">
<div class="page-press-form fw fw-container gray-bg">
<div class="page-press-form__wrapper wrapper">
<x-front.sections.title secondaryClass="page-press-form__subtitle" class="page-press-form__title"
title="{{ ucfirst(__('front.contact_our')) }}" subtitle="{{ __('front.press_department') }}" />
<div class="fw-container no-bottom-padding">
<div class="wpcf7-form">
@include('front.pages.contact.partials.press')
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -74,7 +74,6 @@ if ($pageEspacePresse) {
if ($pageHome) {
$trail->parent("front.dynamic-pages.accueil.$locale");
}
$trail->parent("front.dynamic-pages.accueil");
$trail->push($pageEspacePresse->title);
});
}

View file

@ -1,9 +1,10 @@
<?php
use App\Http\Controllers\Front\HomeController;
use App\Http\Controllers\Front\ContactController;
use App\Http\Controllers\Front\NewsController;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Front\HomeController;
use App\Http\Controllers\Front\NewsController;
use App\Http\Controllers\Front\PostsController;
use App\Http\Controllers\Front\ContactController;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
Route::group(
@ -27,6 +28,11 @@ Route::group(
return view('front.pages.search');
})->name('search');
Route::prefix('posts')->group(function () {
Route::post('/load/news', [PostsController::class, 'ajaxLoadNews'])->name('posts.ajax.news');
Route::post('/load/documents', [PostsController::class, 'ajaxLoadDocuments'])->name('posts.ajax.documents');
});
Route::get(LaravelLocalization::transRoute('routes.news'), [NewsController::class, 'show'])->name('news.show');
}
);