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:
commit
1021dcbf2f
40 changed files with 1249 additions and 900 deletions
53
app/Enums/OrdersEnum.php
Normal file
53
app/Enums/OrdersEnum.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
319
app/Http/Controllers/Front/PostsController.php
Normal file
319
app/Http/Controllers/Front/PostsController.php
Normal 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()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
41
app/Http/Requests/Front/PostsRequest.php
Normal file
41
app/Http/Requests/Front/PostsRequest.php
Normal 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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class NewsCategory extends Model implements TranslatableModel
|
|||
News::class,
|
||||
'newsable',
|
||||
'newsables',
|
||||
'news_id',
|
||||
'news_id',
|
||||
'newsable_id'
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -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.'
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
1
resources/ts/back/types/global.d.ts
vendored
1
resources/ts/back/types/global.d.ts
vendored
|
|
@ -210,6 +210,7 @@ type DocumentTagModel = {
|
|||
id: number;
|
||||
name: TranslationField;
|
||||
slug: TranslationField;
|
||||
color: string;
|
||||
documents_count?: number;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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" />
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
44
resources/views/components/front/posts/news.blade.php
Normal file
44
resources/views/components/front/posts/news.blade.php
Normal 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
|
||||
250
resources/views/components/front/posts/posts-index.blade.php
Normal file
250
resources/views/components/front/posts/posts-index.blade.php
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue