From 25e9b53a3d6e60b53839f9e2bf56dae26ee46949 Mon Sep 17 00:00:00 2001 From: Corentin Huet Date: Thu, 8 Jan 2026 16:40:07 +0100 Subject: [PATCH 1/6] feat: :sparkles: add field color to document_tags --- .../DocumentTag/StoreDocumentTagRequest.php | 3 +- app/Models/DocumentTag.php | 10 +- ...01_08_114841_alter_document_tags_table.php | 28 ++++++ resources/ts/back/Pages/DocumentTag/Form.tsx | 45 ++++++--- resources/ts/back/Pages/DocumentTag/Index.tsx | 81 +++++++++++----- resources/ts/back/Pages/DocumentTag/Show.tsx | 97 +++++++++++++------ resources/ts/back/types/global.d.ts | 1 + 7 files changed, 198 insertions(+), 67 deletions(-) create mode 100644 database/migrations/2026_01_08_114841_alter_document_tags_table.php diff --git a/app/Http/Requests/Back/DocumentTag/StoreDocumentTagRequest.php b/app/Http/Requests/Back/DocumentTag/StoreDocumentTagRequest.php index e57787b6..0ed26bca 100644 --- a/app/Http/Requests/Back/DocumentTag/StoreDocumentTagRequest.php +++ b/app/Http/Requests/Back/DocumentTag/StoreDocumentTagRequest.php @@ -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', ]; } } diff --git a/app/Models/DocumentTag.php b/app/Models/DocumentTag.php index d11c5ef9..b4f3e160 100644 --- a/app/Models/DocumentTag.php +++ b/app/Models/DocumentTag.php @@ -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' ]; /** diff --git a/database/migrations/2026_01_08_114841_alter_document_tags_table.php b/database/migrations/2026_01_08_114841_alter_document_tags_table.php new file mode 100644 index 00000000..448853e0 --- /dev/null +++ b/database/migrations/2026_01_08_114841_alter_document_tags_table.php @@ -0,0 +1,28 @@ +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'); + }); + } +}; diff --git a/resources/ts/back/Pages/DocumentTag/Form.tsx b/resources/ts/back/Pages/DocumentTag/Form.tsx index 4ddf33b3..9308ca9b 100644 --- a/resources/ts/back/Pages/DocumentTag/Form.tsx +++ b/resources/ts/back/Pages/DocumentTag/Form.tsx @@ -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} /> +
- + })} + />
); diff --git a/resources/ts/back/Pages/DocumentTag/Index.tsx b/resources/ts/back/Pages/DocumentTag/Index.tsx index 5ea8c8f9..a4318320 100644 --- a/resources/ts/back/Pages/DocumentTag/Index.tsx +++ b/resources/ts/back/Pages/DocumentTag/Index.tsx @@ -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 } }) => ( ), cell: ({ row }: { row: Row }) => { - return renderTranslationCell( - row.getValue("name") - ) + return renderTranslationCell(row.getValue("name")); + }, + }, + { + accessorKey: "color", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => { + return ( + + +
+
+ {row.getValue("color")} +
+ ); }, }, { accessorKey: "documents", header: () => ( -

{str(useTrans(`validation.custom.documentTag.documents`)).ucFirst().value()}

+

+ {str(useTrans(`validation.custom.documentTag.documents`)) + .ucFirst() + .value()} +

), cell: ({ row }: { row: Row }) => (
@@ -75,7 +106,9 @@ export default function Index({ accessorKey: "updated_at", header: ({ column }: { column: { id: string } }) => ( ), @@ -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 (
- {create && } - tooltipContent={

{useTrans("back.crud_add_entry")}

} - />} + {create && ( + } + tooltipContent={ +

{useTrans("back.crud_add_entry")}

+ } + /> + )}
); }; return ( - + { - 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] - : (

{useTrans('back.table_not_known')}

), + cell: ({ value }: { value: TranslationField }) => + value[appEnv.locale] ? ( + value[appEnv.locale] + ) : ( +

+ {useTrans("back.table_not_known")} +

+ ), + }, + { + accessorKey: "color", + header: str(useTrans("validation.custom.color")).ucFirst().value(), + cell: ({ value }: { value: string }) => { + return ( + + +
+
+ {value} +
+ ); + }, }, { 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 ( {useFormatedDate(value)} - ) + ); }, }, { 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 }) => ( {useFormatedDate(value)} ), @@ -75,22 +113,23 @@ export default function Show({ data, modelName, documents }: { data: SingleResou ]; const beforeNode = () => { - return viewAny && ( -
- } - tooltipContent={

{useTrans("back.crud_back_to_list")}

} - /> -
+ return ( + viewAny && ( +
+ } + tooltipContent={ +

{useTrans("back.crud_back_to_list")}

+ } + /> +
+ ) ); }; return ( - + Date: Thu, 8 Jan 2026 16:41:24 +0100 Subject: [PATCH 2/6] feat: :sparkles: add ajax filtering for documents and news + refactor some blade files --- app/Enums/OrdersEnum.php | 53 ++++ app/Enums/YearsEnum.php | 14 + app/Helpers/CollectionHelper.php | 75 +++++ .../Controllers/Front/PostsController.php | 290 ++++++++++++++++++ app/Http/Requests/Front/PostsRequest.php | 41 +++ app/Models/Document.php | 33 +- app/Models/DocumentTag.php | 19 ++ app/Models/News.php | 7 +- app/Models/NewsCategory.php | 2 +- app/Models/NewsTag.php | 27 +- .../Front/News/IndexNewsComponent.php | 51 +-- .../Front/News/NewsNextEventComponent.php | 33 +- lang/en/enums.php | 4 + lang/en/front.php | 9 +- lang/fr/enums.php | 4 + lang/fr/front.php | 9 +- .../components/front/filter-posts.blade.php | 128 -------- .../front/news/index-news-component.blade.php | 31 +- .../news/news-next-event-component.blade.php | 15 +- .../finance/assemblees-generales.blade.php | 167 +--------- .../front/pages/finance/finance-4.blade.php | 145 ++------- .../front/pages/finance/finance-6.blade.php | 178 +---------- .../front/pages/finance/graphics.blade.php | 2 +- .../front/posts/documents.blade.php | 9 + .../front/posts/filters-dropdowns.blade.php | 25 ++ .../components/front/posts/news.blade.php | 44 +++ .../front/posts/posts-index.blade.php | 237 ++++++++++++++ .../front/posts/selected-filters.blade.php | 10 + .../accordion/document-accordion.blade.php | 14 - .../components/front/sections/news.blade.php | 38 --- .../views/front/pages/press-space.blade.php | 14 +- routes/web-front.php | 12 +- 32 files changed, 1001 insertions(+), 739 deletions(-) create mode 100644 app/Enums/OrdersEnum.php create mode 100644 app/Helpers/CollectionHelper.php create mode 100644 app/Http/Controllers/Front/PostsController.php create mode 100644 app/Http/Requests/Front/PostsRequest.php delete mode 100644 resources/views/components/front/filter-posts.blade.php create mode 100644 resources/views/components/front/posts/documents.blade.php create mode 100644 resources/views/components/front/posts/filters-dropdowns.blade.php create mode 100644 resources/views/components/front/posts/news.blade.php create mode 100644 resources/views/components/front/posts/posts-index.blade.php create mode 100644 resources/views/components/front/posts/selected-filters.blade.php delete mode 100644 resources/views/components/front/sections/accordion/document-accordion.blade.php delete mode 100644 resources/views/components/front/sections/news.blade.php diff --git a/app/Enums/OrdersEnum.php b/app/Enums/OrdersEnum.php new file mode 100644 index 00000000..4930a075 --- /dev/null +++ b/app/Enums/OrdersEnum.php @@ -0,0 +1,53 @@ +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; + } +} diff --git a/app/Enums/YearsEnum.php b/app/Enums/YearsEnum.php index 0718e3c1..2165a1f4 100644 --- a/app/Enums/YearsEnum.php +++ b/app/Enums/YearsEnum.php @@ -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; + } } diff --git a/app/Helpers/CollectionHelper.php b/app/Helpers/CollectionHelper.php new file mode 100644 index 00000000..80e09a7e --- /dev/null +++ b/app/Helpers/CollectionHelper.php @@ -0,0 +1,75 @@ +map(fn($enum) => $enum->value) + ->unique() + ->values(); + } + + /** + * Extract unique years from date field in a query builder. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $field + * + * @return \Illuminate\Support\Collection + */ + function getUniqueEnumValuesFromQuery(Builder $query, string $field): Collection + { + return $query->pluck($field) + ->map(fn($enum) => $enum->value ?? '') + ->unique() + ->values(); + } + + /** + * Extract unique years from date field in a query builder. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $field + * + * @return \Illuminate\Support\Collection + */ + function getUniqueYearsFromQuery(Builder $query, string $field): Collection + { + return $query->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(); + } +} diff --git a/app/Http/Controllers/Front/PostsController.php b/app/Http/Controllers/Front/PostsController.php new file mode 100644 index 00000000..4a86ecdb --- /dev/null +++ b/app/Http/Controllers/Front/PostsController.php @@ -0,0 +1,290 @@ +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 === "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 = $collectionHelper->getMatchingValues( + $collectionHelper->getUniqueYearsFromQuery($query, 'published_at'), + $enumYears, + 'value' + ); + + if ($type === "documents") { + $filters[] = [ + 'title' => ucfirst(__('front.news.order')), + 'name' => 'order', + 'fields' => $orders, + ]; + } + + $filters[] = [ + 'title' => $type === "documents" ? ucfirst(__('front.news.document')) : ucfirst(__('front.news.theme')), + 'name' => 'tags', + 'fields' => $tags, + ]; + + $filters[] = [ + 'title' => $type === "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']); + $locale = app()->getLocale(); + + $publishedNewsTags = NewsTag::getTagsByCategoryInCurrentLocale($category); + + /** @var \Illuminate\Database\Eloquent\Builder<\App\Models\News> $query */ + $query = 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()) { + $query->whereHas('tags', function ($q) use ($selectedTags) { + $q->whereIn('newsable_id', $selectedTags->map->id->all()); + }); + + $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()) { + foreach ($selectedYears->map->value->all() as $value) { + $query->whereYear('published_at', $value); + } + + $selectedFilters['years'] = $selectedYears; + } + + /** Sort columns with a query */ + $this->sortQuery($query); + + $filters = $this->getFilters('news', $category, $publishedNewsTags, $query); + + /** Custom pagination */ + $newsModel = $query->paginate($perPage, ['*'], 'page', $data['page'] ?? 1); + + 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']); + $locale = app()->getLocale(); + + $publishedDocumentsTags = DocumentTag::getTagsByCategoryInCurrentLocale($category); + + /** @var \Illuminate\Database\Eloquent\Builder<\App\Models\Document> $query */ + $query = 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()) { + $query->whereHas('tags', function ($q) use ($selectedTags) { + $q->whereIn('documentable_id', $selectedTags->map->id->all()); + }); + + $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()) { + foreach ($selectedYears->map->value->all() as $value) { + $query->where('exercice', $value); + } + + $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()) { + $query->orderBy('published_at', $selectedOrder->map->tag->first()); + + $selectedFilters['order'] = $selectedOrder->all(); + } + + /** Sort columns with a query */ + $this->sortQuery($query); + + $filters = $this->getFilters('documents', $category, $publishedDocumentsTags, $query); + + /** Custom pagination */ + $documentsModel = $query->paginate($perPage, ['*'], 'page', $data['page'] ?? 1); + + 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() + ]); + } +} diff --git a/app/Http/Requests/Front/PostsRequest.php b/app/Http/Requests/Front/PostsRequest.php new file mode 100644 index 00000000..6ceac820 --- /dev/null +++ b/app/Http/Requests/Front/PostsRequest.php @@ -0,0 +1,41 @@ +|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'], + ]; + } +} diff --git a/app/Models/Document.php b/app/Models/Document.php index 37df56f6..53223364 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -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 { @@ -240,4 +241,16 @@ class Document extends Model implements TranslatableModel $attributes = Arr::except($attributes, ['tags', 'categories']); $this->traitInertiaFill($attributes); } + + public static function getPublishedDocumentsByCategoryInCurrentLocale($category) + { + $locale = App::getLocale(); + // ici mettre le cache avec la relation catégorie + return Document::query() + ->with('categories', 'tags') + ->whereNotNull("path->$locale") + ->whereHas('categories', function ($q) use ($locale, $category) { + $q->where("slug->$locale", $category); + }); + } } diff --git a/app/Models/DocumentTag.php b/app/Models/DocumentTag.php index b4f3e160..acd6c99b 100644 --- a/app/Models/DocumentTag.php +++ b/app/Models/DocumentTag.php @@ -54,4 +54,23 @@ class DocumentTag extends Model implements TranslatableModel 'document_id' ); } + + /** + * Get all Tags by category. + * + * @return \Illuminate\Database\Eloquent\Collection + */ + public static function getTagsByCategoryInCurrentLocale($category): \Illuminate\Database\Eloquent\Collection + { + $locale = App::getLocale(); + + return DocumentTag::query() + ->whereHas('documents', function ($q) use ($locale) { + $q->whereNotNull("path->$locale"); + }) + ->whereHas('documents.categories', function ($q) use ($locale, $category) { + $q->where("slug->$locale", $category); + }) + ->get(); + } } diff --git a/app/Models/News.php b/app/Models/News.php index f42cb2aa..16660608 100644 --- a/app/Models/News.php +++ b/app/Models/News.php @@ -340,12 +340,15 @@ class News extends Model implements TranslatableModel, LocalizedUrlRoutable )->withTimestamps(); } - public static function getPublishedNewsInCurrentLocale() + public static function getPublishedNewsByCategoryInCurrentLocale($category) { $locale = App::getLocale(); // ici mettre le cache avec la relation catégorie return News::query() ->with('categories', 'tags') - ->whereNotNull("slug->$locale"); + ->whereNotNull("slug->$locale") + ->whereHas('categories', function ($q) use ($locale, $category) { + $q->where("slug->$locale", $category); + }); } } diff --git a/app/Models/NewsCategory.php b/app/Models/NewsCategory.php index 435e3413..8b59b438 100644 --- a/app/Models/NewsCategory.php +++ b/app/Models/NewsCategory.php @@ -91,7 +91,7 @@ class NewsCategory extends Model implements TranslatableModel News::class, 'newsable', 'newsables', - 'news_id', + 'news_id', 'newsable_id' ); } diff --git a/app/Models/NewsTag.php b/app/Models/NewsTag.php index a239b36d..bd112f80 100644 --- a/app/Models/NewsTag.php +++ b/app/Models/NewsTag.php @@ -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. @@ -62,5 +64,24 @@ 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::query() + ->whereHas('news', function ($q) use ($locale) { + $q->whereNotNull("slug->$locale"); + }) + ->whereHas('news.categories', function ($q) use ($locale, $category) { + $q->where("slug->$locale", $category); + }) + ->get(); + } } diff --git a/app/View/Components/Front/News/IndexNewsComponent.php b/app/View/Components/Front/News/IndexNewsComponent.php index 2c77a3ad..50a4ba91 100644 --- a/app/View/Components/Front/News/IndexNewsComponent.php +++ b/app/View/Components/Front/News/IndexNewsComponent.php @@ -3,20 +3,14 @@ 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\View\Component; +use Illuminate\Contracts\View\View; +use App\Helpers\BladeComponentHelper; use Illuminate\Support\Facades\Route; -use Illuminate\Support\Str; class IndexNewsComponent extends Component { - public $categories; - public $years; - public $newsModels; public $postContainer; /** @@ -24,51 +18,19 @@ class IndexNewsComponent extends Component */ 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); + $locale = config('app.locale'); /** * 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 + 'category' => $locale === "fr" ? 'actualites' : 'news', ]; @@ -99,9 +61,6 @@ class IndexNewsComponent extends Component 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 ]); } diff --git a/app/View/Components/Front/News/NewsNextEventComponent.php b/app/View/Components/Front/News/NewsNextEventComponent.php index d6f7e484..d752232d 100644 --- a/app/View/Components/Front/News/NewsNextEventComponent.php +++ b/app/View/Components/Front/News/NewsNextEventComponent.php @@ -2,10 +2,14 @@ namespace App\View\Components\Front\News; -use App\Models\News; use Closure; -use Illuminate\Contracts\View\View; +use App\Models\News; +use App\Models\NewsCategory; use Illuminate\View\Component; +use App\Enums\News\NewsStatusEnum; +use Illuminate\Contracts\View\View; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\App; class NewsNextEventComponent extends Component { @@ -16,13 +20,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(); + $locale = App::getLocale(); + + $eventCategoryId = Cache::remember('eventCategoryId', 86400, function () { + return NewsCategory::where('slug->fr', 'evenement') + ->whereNull('deleted_at') + ->value('id'); #FIXME: REPLACE CATEGORY PAR TAG + }); + + $this->nextEvents = Cache::remember("{$locale}_newsNextEvents", 86400, function () use ($eventCategoryId) { + return News::getPublishedNewsInCurrentLocale() + ->where('status', NewsStatusEnum::published)->with('categories') + ->whereHas('categories', function ($query) use ($eventCategoryId) { + $query->where('category_id', $eventCategoryId); #FIXME: WHERE CATEGORY IS 'Actualites' AND WHERE TAG IS '3 (evenement)' + }) + ->latest() + ->take(3) + ->get(); + }); } /** diff --git a/lang/en/enums.php b/lang/en/enums.php index 2d91d003..69e424c6 100644 --- a/lang/en/enums.php +++ b/lang/en/enums.php @@ -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.' + ], ]; diff --git a/lang/en/front.php b/lang/en/front.php index 89d1079b..3c95255e 100644 --- a/lang/en/front.php +++ b/lang/en/front.php @@ -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", diff --git a/lang/fr/enums.php b/lang/fr/enums.php index 13f86bef..b06f7ebd 100644 --- a/lang/fr/enums.php +++ b/lang/fr/enums.php @@ -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.' + ], ]; diff --git a/lang/fr/front.php b/lang/fr/front.php index ab9de292..577aebbd 100644 --- a/lang/fr/front.php +++ b/lang/fr/front.php @@ -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", diff --git a/resources/views/components/front/filter-posts.blade.php b/resources/views/components/front/filter-posts.blade.php deleted file mode 100644 index 8bef9bad..00000000 --- a/resources/views/components/front/filter-posts.blade.php +++ /dev/null @@ -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)) -
-
-
-
-
- @if (isset($filters)) - @foreach ($filters as $filter) - - @endforeach - @endif -
- {{ ucfirst(__('front.news.results')) }} : - - - {{ ucfirst(__('front.news.on_document_count', ['current' => 0, 'total' => 10])) }} - - -
-
-
-
-
-
-
-
- ✖ - {{ ucfirst(__('front.buttons.clear_filters')) }} -
-
-
-
-
-
-
-
- @if ($postContainer['list_type'] === 'news') -
-
- @if (isset($news)) - @foreach ($news as $new) - - @endforeach - @endif -
-
- -
-
- @else -
- {{-- # FIXME : Les IDs sont surement à remplacer. A voir en fonction de l'élement (ex: ajax-load-more-2 ou ajax-load-more-4) --}} -
-
- @foreach ($postContainer['documents'] as $document) - @if ($postContainer['accordeon']) - - @else - - {{ $document['title'] }} - - @endif - @endforeach -
-
- -
-
-
- @endif -
-
-
-
-@endif diff --git a/resources/views/components/front/news/index-news-component.blade.php b/resources/views/components/front/news/index-news-component.blade.php index 0046fc78..e2f427f7 100644 --- a/resources/views/components/front/news/index-news-component.blade.php +++ b/resources/views/components/front/news/index-news-component.blade.php @@ -1,30 +1 @@ -@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 - - + diff --git a/resources/views/components/front/news/news-next-event-component.blade.php b/resources/views/components/front/news/news-next-event-component.blade.php index bfcd8754..fbd08f46 100644 --- a/resources/views/components/front/news/news-next-event-component.blade.php +++ b/resources/views/components/front/news/news-next-event-component.blade.php @@ -1,20 +1,23 @@ +@php + $locale = config('app.locale'); + $param = "?tags=" . ($locale === "fr" ? "evenement" : "event"); +@endphp +
-

Prochains évènements

+

{{ ucfirst(__('front.next_events')) }}

- @foreach ($nextEvents as $event) - - @endforeach +
@if (isset($pageActualites)) - @endif
diff --git a/resources/views/components/front/pages/finance/assemblees-generales.blade.php b/resources/views/components/front/pages/finance/assemblees-generales.blade.php index 8aa545a0..b9221e3f 100644 --- a/resources/views/components/front/pages/finance/assemblees-generales.blade.php +++ b/resources/views/components/front/pages/finance/assemblees-generales.blade.php @@ -1,6 +1,19 @@ +@php + $locale = config('app.locale'); + + $postContainer = [ + 'class' => 'posts', + 'secondaryClass' => 'filter_doc', + 'tertiaryClass' => 'fw-container no-top-padding', + 'slug' => 'docs', + 'category' => $locale === "fr" ? 'assemblees-generales' : 'general-meetings', + 'load_more' => ucfirst(__('front.buttons.see_more_docs')), + ]; +@endphp +
+ id="{{ data_get($data, 'string_title') ? Str::slug(data_get($data, 'string_title')) : '' }}" + class="textblock fw fw-container assemblees-generales half-bottom-padding">
@@ -31,152 +44,4 @@ class="textblock fw fw-container assemblees-generales half-bottom-padding">
-{{-- # FIXME: Faire un composant générique avec les scripts associés (filtrer / voir plus) --}} -{{-- --}} + diff --git a/resources/views/components/front/pages/finance/finance-4.blade.php b/resources/views/components/front/pages/finance/finance-4.blade.php index 1dd2b0ca..e8ea0581 100644 --- a/resources/views/components/front/pages/finance/finance-4.blade.php +++ b/resources/views/components/front/pages/finance/finance-4.blade.php @@ -1,133 +1,32 @@ @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', + $locale = config('app.locale'); - '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' => $locale === "fr" ? 'presentation-investisseurs' : 'investor-presentation', + 'load_more' => ucfirst(__('front.buttons.see_more_docs')), ]; @endphp -@if (!empty($finance)) -
-
-
-
- - @if (!empty($data['string_content'])) -
-
-
-

{{ $data['string_content'] }}

-
+
+
+
+
+ + @if (!empty($data['string_content'])) +
+
+
+

{{ $data['string_content'] }}

- @endif -
+
+ @endif
- {{-- --}}
+
-@endif +
diff --git a/resources/views/components/front/pages/finance/finance-6.blade.php b/resources/views/components/front/pages/finance/finance-6.blade.php index be309f2b..fcd505ee 100644 --- a/resources/views/components/front/pages/finance/finance-6.blade.php +++ b/resources/views/components/front/pages/finance/finance-6.blade.php @@ -1,172 +1,12 @@ @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', - ], - ], - ], + $locale = config('app.locale'); + + $postContainer = [ + 'class' => 'posts', + 'secondaryClass' => 'filter_doc', + 'slug' => 'docs', + 'category' => $locale === "fr" ? 'documents' : 'documents', + 'load_more' => ucfirst(__('front.buttons.see_more_docs')), ]; @endphp @@ -184,6 +24,6 @@
- {{-- --}} +
diff --git a/resources/views/components/front/pages/finance/graphics.blade.php b/resources/views/components/front/pages/finance/graphics.blade.php index f25524b6..f81d363b 100644 --- a/resources/views/components/front/pages/finance/graphics.blade.php +++ b/resources/views/components/front/pages/finance/graphics.blade.php @@ -22,7 +22,7 @@ class="kfar">
- +
diff --git a/resources/views/components/front/posts/documents.blade.php b/resources/views/components/front/posts/documents.blade.php new file mode 100644 index 00000000..e3937712 --- /dev/null +++ b/resources/views/components/front/posts/documents.blade.php @@ -0,0 +1,9 @@ +@if (!empty($documentsList)) + @foreach ($documentsList as $documents) + + {{ $documents['name'] }} + + @endforeach +@endif diff --git a/resources/views/components/front/posts/filters-dropdowns.blade.php b/resources/views/components/front/posts/filters-dropdowns.blade.php new file mode 100644 index 00000000..6c900485 --- /dev/null +++ b/resources/views/components/front/posts/filters-dropdowns.blade.php @@ -0,0 +1,25 @@ +@if (isset($filters)) + @foreach ($filters as $filter) + @if (collect($filter['fields'])->isNotEmpty()) + + @endif + @endforeach +@endif diff --git a/resources/views/components/front/posts/news.blade.php b/resources/views/components/front/posts/news.blade.php new file mode 100644 index 00000000..3caa3ed0 --- /dev/null +++ b/resources/views/components/front/posts/news.blade.php @@ -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) +
+
+ {{ isset($news['picture_alt']) ? $news['picture_alt'] : '' }} + +
+
+ @if (isset($news->tag_labels)) +
+
+ {{ $news->tag_labels }} +
+
+ @endif +
{{ $news['title'] }}
+
+ {{ $news->publishedAtFormatted() }} +
+
+ @if ($isButtonStyle) + + @else + + {{ ucfirst(__('front.news.read_article')) }} + + @endif +
+
+ @endforeach +@endif diff --git a/resources/views/components/front/posts/posts-index.blade.php b/resources/views/components/front/posts/posts-index.blade.php new file mode 100644 index 00000000..c7a02f4d --- /dev/null +++ b/resources/views/components/front/posts/posts-index.blade.php @@ -0,0 +1,237 @@ +@php + $wrapperId = 'wrapper-' . uniqid(); +@endphp + +@if (isset($postContainer)) +
+
+
+
+
+
+ {{ ucfirst(__('front.news.results')) }} : + + + + {{ __('front.news.on_document_count') }} + + + +
+
+
+
+
+
+ ✖ + {{ ucfirst(__('front.buttons.clear_filters')) }} +
+
+
+
+
+
+
+
$postContainer['slug'] === "news", + 'list-items_doc' => $postContainer['slug'] === "docs", + ])> + +
+
+
+ +
+
+
+
+
+
+ @push('scripts') + + @endpush +@endif diff --git a/resources/views/components/front/posts/selected-filters.blade.php b/resources/views/components/front/posts/selected-filters.blade.php new file mode 100644 index 00000000..e9d4c724 --- /dev/null +++ b/resources/views/components/front/posts/selected-filters.blade.php @@ -0,0 +1,10 @@ +@if (isset($selectedFilters)) + @foreach ($selectedFilters as $key => $filters) + @foreach ($filters as $filter) +
+ ✖ + {{ ucfirst($filter['title']) }} +
+ @endforeach + @endforeach +@endif diff --git a/resources/views/components/front/sections/accordion/document-accordion.blade.php b/resources/views/components/front/sections/accordion/document-accordion.blade.php deleted file mode 100644 index b34d298c..00000000 --- a/resources/views/components/front/sections/accordion/document-accordion.blade.php +++ /dev/null @@ -1,14 +0,0 @@ -@if(!empty($document)) -
-
{{ $document['title'] }}
- -
-@endif diff --git a/resources/views/components/front/sections/news.blade.php b/resources/views/components/front/sections/news.blade.php deleted file mode 100644 index 8875db3b..00000000 --- a/resources/views/components/front/sections/news.blade.php +++ /dev/null @@ -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)) -
-
- {{ isset($news['picture_alt']) ? $news['picture_alt'] : '' }} - -
-
- @if (isset($news->tag_labels)) -
-
- {{ $news->tag_labels }} -
-
- @endif -
{{ $news['title'] }}
-
- {{ $news->publishedAtFormatted() }} -
-
- @if ($isButtonStyle) - - @else - - {{ ucfirst(__('front.news.read_article')) }} - - @endif -
-
-@endif diff --git a/resources/views/front/pages/press-space.blade.php b/resources/views/front/pages/press-space.blade.php index 848c1a92..baca6188 100644 --- a/resources/views/front/pages/press-space.blade.php +++ b/resources/views/front/pages/press-space.blade.php @@ -12,7 +12,19 @@
- + @php + $locale = app()->getLocale(); + + $postContainer = [ + 'class' => 'posts', + 'tertiaryClass' => 'fw-container no-top-padding', + 'slug' => 'news', + 'category' => $locale === "fr" ? 'espace-presse' : 'press-space', + 'load_more' => ucfirst(__('front.buttons.see_more_docs')), + ]; + @endphp + +
diff --git a/routes/web-front.php b/routes/web-front.php index b6294c51..ee1d1f81 100644 --- a/routes/web-front.php +++ b/routes/web-front.php @@ -1,9 +1,10 @@ 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'); } ); From 34f5b105c8e761ba9a0f7f9b4ee956fef5d64883 Mon Sep 17 00:00:00 2001 From: Corentin Huet Date: Fri, 9 Jan 2026 12:12:01 +0100 Subject: [PATCH 3/6] fix: :bug: remove color field to news category --- .../Requests/Back/NewsCategory/StoreNewsCategoryRequest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/Http/Requests/Back/NewsCategory/StoreNewsCategoryRequest.php b/app/Http/Requests/Back/NewsCategory/StoreNewsCategoryRequest.php index 61690810..acd3204e 100644 --- a/app/Http/Requests/Back/NewsCategory/StoreNewsCategoryRequest.php +++ b/app/Http/Requests/Back/NewsCategory/StoreNewsCategoryRequest.php @@ -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'), ]; } } From d3fe0076d889b233654a54e8714578966fdb1514 Mon Sep 17 00:00:00 2001 From: Corentin Huet Date: Fri, 9 Jan 2026 12:38:53 +0100 Subject: [PATCH 4/6] fix: :bug: fix news not displaying on join us page --- app/Models/News.php | 1 + .../Front/News/NewsNextEventComponent.php | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/app/Models/News.php b/app/Models/News.php index 16660608..a26bb555 100644 --- a/app/Models/News.php +++ b/app/Models/News.php @@ -345,6 +345,7 @@ class News extends Model implements TranslatableModel, LocalizedUrlRoutable $locale = App::getLocale(); // ici mettre le cache avec la relation catégorie return News::query() + ->where('status', NewsStatusEnum::published) ->with('categories', 'tags') ->whereNotNull("slug->$locale") ->whereHas('categories', function ($q) use ($locale, $category) { diff --git a/app/View/Components/Front/News/NewsNextEventComponent.php b/app/View/Components/Front/News/NewsNextEventComponent.php index d752232d..73894e72 100644 --- a/app/View/Components/Front/News/NewsNextEventComponent.php +++ b/app/View/Components/Front/News/NewsNextEventComponent.php @@ -4,12 +4,13 @@ namespace App\View\Components\Front\News; use Closure; 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\Cache; use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Cache; class NewsNextEventComponent extends Component { @@ -22,22 +23,24 @@ class NewsNextEventComponent extends Component { $locale = App::getLocale(); - $eventCategoryId = Cache::remember('eventCategoryId', 86400, function () { - return NewsCategory::where('slug->fr', 'evenement') + $newsCategory = Cache::remember('newsCategory', 86400, function () { + return NewsCategory::where('slug->fr', 'actualites') ->whereNull('deleted_at') - ->value('id'); #FIXME: REPLACE CATEGORY PAR TAG + ->value('slug'); }); - $this->nextEvents = Cache::remember("{$locale}_newsNextEvents", 86400, function () use ($eventCategoryId) { - return News::getPublishedNewsInCurrentLocale() - ->where('status', NewsStatusEnum::published)->with('categories') - ->whereHas('categories', function ($query) use ($eventCategoryId) { - $query->where('category_id', $eventCategoryId); #FIXME: WHERE CATEGORY IS 'Actualites' AND WHERE TAG IS '3 (evenement)' - }) - ->latest() - ->take(3) - ->get(); + $eventTagId = Cache::remember('eventTagId', 86400, function () { + return NewsTag::where('slug->fr', 'evenement') + ->value('id'); }); + + $this->nextEvents = News::getPublishedNewsByCategoryInCurrentLocale($newsCategory) + ->whereHas('tags', function ($query) use ($eventTagId) { + $query->where('newsable_id', $eventTagId); + }) + ->latest() + ->take(3) + ->get(); } /** From ece720a5d95139b186352e7fdc20714115afd2c2 Mon Sep 17 00:00:00 2001 From: Corentin Huet Date: Fri, 9 Jan 2026 17:07:50 +0100 Subject: [PATCH 5/6] refactor: :recycle: improve articles and documents fetching using HasCache trait --- app/Helpers/CollectionHelper.php | 75 ------------- .../Controllers/Front/PostsController.php | 103 +++++++++++------- app/Models/Document.php | 32 ++++-- app/Models/DocumentTag.php | 28 +++-- app/Models/News.php | 50 ++++++--- app/Models/NewsTag.php | 24 ++-- .../Front/News/NewsNextEventComponent.php | 30 +++-- .../front/posts/posts-index.blade.php | 45 +++++--- 8 files changed, 203 insertions(+), 184 deletions(-) delete mode 100644 app/Helpers/CollectionHelper.php diff --git a/app/Helpers/CollectionHelper.php b/app/Helpers/CollectionHelper.php deleted file mode 100644 index 80e09a7e..00000000 --- a/app/Helpers/CollectionHelper.php +++ /dev/null @@ -1,75 +0,0 @@ -map(fn($enum) => $enum->value) - ->unique() - ->values(); - } - - /** - * Extract unique years from date field in a query builder. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param string $field - * - * @return \Illuminate\Support\Collection - */ - function getUniqueEnumValuesFromQuery(Builder $query, string $field): Collection - { - return $query->pluck($field) - ->map(fn($enum) => $enum->value ?? '') - ->unique() - ->values(); - } - - /** - * Extract unique years from date field in a query builder. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param string $field - * - * @return \Illuminate\Support\Collection - */ - function getUniqueYearsFromQuery(Builder $query, string $field): Collection - { - return $query->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(); - } -} diff --git a/app/Http/Controllers/Front/PostsController.php b/app/Http/Controllers/Front/PostsController.php index 4a86ecdb..a57a1a49 100644 --- a/app/Http/Controllers/Front/PostsController.php +++ b/app/Http/Controllers/Front/PostsController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Front; +use Carbon\Carbon; use App\Models\News; use App\Models\NewsTag; use App\Enums\YearsEnum; @@ -9,19 +10,46 @@ use App\Models\Document; use App\Enums\OrdersEnum; use App\Models\DocumentTag; use Illuminate\Support\Str; -use App\Helpers\CollectionHelper; -use App\Enums\News\NewsStatusEnum; -use Illuminate\Support\Facades\App; -use App\Helpers\BladeComponentHelper; -use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Collection; use App\Http\Requests\Front\PostsRequest; -use Illuminate\Database\Eloquent\Builder; -use App\Enums\Document\DocumentStatusEnum; -use Illuminate\Database\Eloquent\Collection; use Uwil\LaravelCommon\Controllers\Controller; +use Uwil\LaravelCommon\Helpers\CollectionHelper; class PostsController extends Controller { + /** + * 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 * @@ -30,14 +58,13 @@ class PostsController extends Controller * @param string $type * @param string $category * @param \Illuminate\Database\Eloquent\Collection $publishedTags - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Collection $model * * @return array */ - private function getFilters(string $type, string $category, Collection $publishedTags, Builder $query): array + private function getFilters(string $type, string $category, Collection $publishedTags, Collection $model): array { $filters = []; - $collectionHelper = new CollectionHelper(); $tags = collect($publishedTags) ->map(fn($tag) => [ @@ -70,8 +97,8 @@ class PostsController extends Controller ->values(); /** Sort displayed years */ - $years = $collectionHelper->getMatchingValues( - $collectionHelper->getUniqueYearsFromQuery($query, 'published_at'), + $years = $this->getMatchingValues( + $this->getUniqueYearsFromCollection($model, 'published_at'), $enumYears, 'value' ); @@ -119,8 +146,7 @@ class PostsController extends Controller $publishedNewsTags = NewsTag::getTagsByCategoryInCurrentLocale($category); - /** @var \Illuminate\Database\Eloquent\Builder<\App\Models\News> $query */ - $query = News::getPublishedNewsByCategoryInCurrentLocale($category); + $newsModel = News::getPublishedNewsByCategoryInCurrentLocale($category); $requestedTags = collect($data['filters']['tags'] ?? '') ->filter() @@ -138,8 +164,10 @@ class PostsController extends Controller ->values(); if ($selectedTags->isNotEmpty()) { - $query->whereHas('tags', function ($q) use ($selectedTags) { - $q->whereIn('newsable_id', $selectedTags->map->id->all()); + $newsModel = $newsModel->filter(function ($news) use ($selectedTags) { + return $news->tags->contains( + fn ($tag) => $selectedTags->contains('id', $tag->id) + ); }); $selectedFilters['tags'] = $selectedTags; @@ -157,20 +185,19 @@ class PostsController extends Controller ->values(); if ($selectedYears->isNotEmpty()) { - foreach ($selectedYears->map->value->all() as $value) { - $query->whereYear('published_at', $value); - } + $newsModel = $newsModel->filter(function ($news) use ($selectedYears) { + return $selectedYears->contains('value', $news->published_at->year); + }); $selectedFilters['years'] = $selectedYears; } - /** Sort columns with a query */ - $this->sortQuery($query); + $newsModel = $newsModel->sortByDesc('published_at'); - $filters = $this->getFilters('news', $category, $publishedNewsTags, $query); + $filters = $this->getFilters('news', $category, $publishedNewsTags, $newsModel); /** Custom pagination */ - $newsModel = $query->paginate($perPage, ['*'], 'page', $data['page'] ?? 1); + $newsModel = CollectionHelper::paginate($newsModel, $perPage); return response()->json([ 'pagination' => [ @@ -205,8 +232,7 @@ class PostsController extends Controller $publishedDocumentsTags = DocumentTag::getTagsByCategoryInCurrentLocale($category); - /** @var \Illuminate\Database\Eloquent\Builder<\App\Models\Document> $query */ - $query = Document::getPublishedDocumentsByCategoryInCurrentLocale($category); + $documentsModel = Document::getPublishedDocumentsByCategoryInCurrentLocale($category); $requestedTags = collect($data['filters']['tags'] ?? '') ->filter() @@ -224,8 +250,10 @@ class PostsController extends Controller ->values(); if ($selectedTags->isNotEmpty()) { - $query->whereHas('tags', function ($q) use ($selectedTags) { - $q->whereIn('documentable_id', $selectedTags->map->id->all()); + $documentsModel = $documentsModel->filter(function ($documents) use ($selectedTags) { + return $documents->tags->contains( + fn ($tag) => $selectedTags->contains('id', $tag->id) + ); }); $selectedFilters['tags'] = $selectedTags; @@ -243,9 +271,9 @@ class PostsController extends Controller ->values(); if ($selectedYears->isNotEmpty()) { - foreach ($selectedYears->map->value->all() as $value) { - $query->where('exercice', $value); - } + $documentsModel = $documentsModel->filter(function ($documents) use ($selectedYears) { + return $selectedYears->contains('value', $documents->exercice); + }); $selectedFilters['years'] = $selectedYears; } @@ -261,18 +289,19 @@ class PostsController extends Controller ->values(); if ($selectedOrder->isNotEmpty()) { - $query->orderBy('published_at', $selectedOrder->map->tag->first()); + $documentsModel = $selectedOrder->map->tag->first() === 'desc' + ? $documentsModel->sortByDesc('published_at') + : $documentsModel->sortBy('published_at'); $selectedFilters['order'] = $selectedOrder->all(); + } else { + $documentsModel = $documentsModel->sortByDesc('published_at'); } - /** Sort columns with a query */ - $this->sortQuery($query); - - $filters = $this->getFilters('documents', $category, $publishedDocumentsTags, $query); + $filters = $this->getFilters('documents', $category, $publishedDocumentsTags, $documentsModel); /** Custom pagination */ - $documentsModel = $query->paginate($perPage, ['*'], 'page', $data['page'] ?? 1); + $documentsModel = CollectionHelper::paginate($documentsModel, $perPage); return response()->json([ 'pagination' => [ diff --git a/app/Models/Document.php b/app/Models/Document.php index 53223364..d66bc8f8 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -71,6 +71,16 @@ class Document extends Model implements TranslatableModel 'path', ]; + /** + * Relations should be included in the cache. + * + * @var array + */ + protected static $cacheRelations = [ + 'categories', + 'tags', + ]; + /** * Perform any actions required after the model boots. * @@ -242,15 +252,23 @@ class Document extends Model implements TranslatableModel $this->traitInertiaFill($attributes); } - public static function getPublishedDocumentsByCategoryInCurrentLocale($category) + /** + * Get all published news by category. + * + * @param string $category + * + * @return \Illuminate\Database\Eloquent\Collection + */ + public static function getPublishedDocumentsByCategoryInCurrentLocale(string $category) { $locale = App::getLocale(); - // ici mettre le cache avec la relation catégorie - return Document::query() - ->with('categories', 'tags') - ->whereNotNull("path->$locale") - ->whereHas('categories', function ($q) use ($locale, $category) { - $q->where("slug->$locale", $category); + + 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 + ); }); } } diff --git a/app/Models/DocumentTag.php b/app/Models/DocumentTag.php index acd6c99b..61420948 100644 --- a/app/Models/DocumentTag.php +++ b/app/Models/DocumentTag.php @@ -39,6 +39,15 @@ class DocumentTag extends Model implements TranslatableModel 'slug' ]; + /** + * Relations should be included in the cache. + * + * @var array + */ + protected static $cacheRelations = [ + 'documents', + ]; + /** * Relation to Document * @@ -58,19 +67,20 @@ class DocumentTag extends Model implements TranslatableModel /** * Get all Tags by category. * + * @param string $category + * * @return \Illuminate\Database\Eloquent\Collection */ - public static function getTagsByCategoryInCurrentLocale($category): \Illuminate\Database\Eloquent\Collection + public static function getTagsByCategoryInCurrentLocale(string $category): \Illuminate\Database\Eloquent\Collection { $locale = App::getLocale(); - return DocumentTag::query() - ->whereHas('documents', function ($q) use ($locale) { - $q->whereNotNull("path->$locale"); - }) - ->whereHas('documents.categories', function ($q) use ($locale, $category) { - $q->where("slug->$locale", $category); - }) - ->get(); + 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(); + }); } } diff --git a/app/Models/News.php b/app/Models/News.php index a26bb555..a23ee9cc 100644 --- a/app/Models/News.php +++ b/app/Models/News.php @@ -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 + */ + protected static $cacheRelations = [ + 'categories', + 'tags', + ]; + /** * Perform any actions required after the model boots. * @@ -340,16 +351,23 @@ class News extends Model implements TranslatableModel, LocalizedUrlRoutable )->withTimestamps(); } - public static function getPublishedNewsByCategoryInCurrentLocale($category) + /** + * 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() + + return News::getModelsCached() ->where('status', NewsStatusEnum::published) - ->with('categories', 'tags') - ->whereNotNull("slug->$locale") - ->whereHas('categories', function ($q) use ($locale, $category) { - $q->where("slug->$locale", $category); + ->filter(function ($news) use ($category, $locale) { + return !empty($news->getTranslation('slug', $locale, false)) && $news->categories->contains( + fn ($cat) => $cat->slug === $category + ); }); } } diff --git a/app/Models/NewsTag.php b/app/Models/NewsTag.php index bd112f80..c33afaa0 100644 --- a/app/Models/NewsTag.php +++ b/app/Models/NewsTag.php @@ -49,6 +49,15 @@ class NewsTag extends Model implements TranslatableModel 'slug' ]; + /** + * Relations should be included in the cache. + * + * @var array + */ + protected static $cacheRelations = [ + 'news', + ]; + /** * Relation to Document * @@ -74,14 +83,13 @@ class NewsTag extends Model implements TranslatableModel { $locale = App::getLocale(); - return NewsTag::query() - ->whereHas('news', function ($q) use ($locale) { - $q->whereNotNull("slug->$locale"); - }) - ->whereHas('news.categories', function ($q) use ($locale, $category) { - $q->where("slug->$locale", $category); - }) - ->get(); + 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(); + }); } } diff --git a/app/View/Components/Front/News/NewsNextEventComponent.php b/app/View/Components/Front/News/NewsNextEventComponent.php index 73894e72..1f9a4cad 100644 --- a/app/View/Components/Front/News/NewsNextEventComponent.php +++ b/app/View/Components/Front/News/NewsNextEventComponent.php @@ -21,26 +21,24 @@ class NewsNextEventComponent extends Component */ public function __construct() { - $locale = App::getLocale(); + $newsCategory = NewsCategory::getModelsCached() + ->filter(fn($category) => $category->getTranslation('slug', 'fr') === "actualites") + ->first(); - $newsCategory = Cache::remember('newsCategory', 86400, function () { - return NewsCategory::where('slug->fr', 'actualites') - ->whereNull('deleted_at') - ->value('slug'); - }); + $eventTag = NewsTag::getTagsByCategoryInCurrentLocale($newsCategory->slug) + ->filter(fn($tag) => $tag->getTranslation('slug', 'fr') === "evenement") + ->first(); - $eventTagId = Cache::remember('eventTagId', 86400, function () { - return NewsTag::where('slug->fr', 'evenement') - ->value('id'); - }); + $eventTagId = $eventTag ? $eventTag->id : null; - $this->nextEvents = News::getPublishedNewsByCategoryInCurrentLocale($newsCategory) - ->whereHas('tags', function ($query) use ($eventTagId) { - $query->where('newsable_id', $eventTagId); + $this->nextEvents = News::getPublishedNewsByCategoryInCurrentLocale($newsCategory->slug) + ->filter(function ($news) use ($eventTagId) { + return $news->tags->contains( + fn ($tag) => $eventTagId === $tag->id + ); }) - ->latest() - ->take(3) - ->get(); + ->sortByDesc('published_at') + ->take(3); } /** diff --git a/resources/views/components/front/posts/posts-index.blade.php b/resources/views/components/front/posts/posts-index.blade.php index c7a02f4d..d8aa3014 100644 --- a/resources/views/components/front/posts/posts-index.blade.php +++ b/resources/views/components/front/posts/posts-index.blade.php @@ -29,7 +29,7 @@
- ✖ {{ ucfirst(__('front.buttons.clear_filters')) }}
@@ -72,6 +72,7 @@ const loader = wrapper.querySelector("#loader"); const section = wrapper.querySelector("#section"); const tblFilters = {}; + let hasLoaded = false; function isLoading(value = false) { if (value) { @@ -143,19 +144,27 @@ initSelectedFilters(); } - document.addEventListener('DOMContentLoaded', function() { - ajaxLoadPosts(); + document.addEventListener('scroll', function () { + if (hasLoaded) return; - loadMoreBtn.addEventListener('click', function() { - loadMoreBtn.classList.add('loading'); - loadMoreBtn.disabled = true; + const wrapperPos = wrapper.getBoundingClientRect(); + const isVisible = wrapperPos.top < window.innerHeight * 1.5; - setTimeout(() => { - ajaxLoadPosts(); - loadMoreBtn.classList.remove('loading'); - loadMoreBtn.disabled = false; - }, 3000); - }); + 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() { @@ -206,7 +215,7 @@ } function initSelectedFilters() { - const items = document.querySelectorAll('.active-filters__item-remove'); + const items = wrapper.querySelectorAll('.active-filters__item-remove'); items.forEach(item => { item.addEventListener('click', function (e) { @@ -220,11 +229,15 @@ if (filterName === 'clear-all') { Object.keys(tblFilters[category]).forEach(key => { - tblFilters[category][key] = []; + tblFilters[category][key] = Array.isArray(tblFilters[category][key]) ? [] : null; }); } else if (filterName && value) { - const values = tblFilters[category][filterName] ?? []; - tblFilters[category][filterName] = values.filter(v => v !== 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]); From a937ada4ee8fb1a16e8fca765c3427182dc06d0d Mon Sep 17 00:00:00 2001 From: K Muller Date: Tue, 13 Jan 2026 11:14:47 +0100 Subject: [PATCH 6/6] fix: some elements --- .../Controllers/Front/PostsController.php | 12 ++-- .../Front/News/IndexNewsComponent.php | 67 ------------------- .../front/news/index-news-component.blade.php | 1 - .../finance/assemblees-generales.blade.php | 2 +- .../front/pages/finance/finance-4.blade.php | 4 +- .../front/pages/finance/finance-6.blade.php | 4 +- resources/views/front/pages/news.blade.php | 13 +++- .../views/front/pages/press-space.blade.php | 51 +++++++------- routes/breadcrumbs-front.php | 1 - 9 files changed, 45 insertions(+), 110 deletions(-) delete mode 100644 app/View/Components/Front/News/IndexNewsComponent.php delete mode 100644 resources/views/components/front/news/index-news-component.blade.php diff --git a/app/Http/Controllers/Front/PostsController.php b/app/Http/Controllers/Front/PostsController.php index a57a1a49..bb29cbc4 100644 --- a/app/Http/Controllers/Front/PostsController.php +++ b/app/Http/Controllers/Front/PostsController.php @@ -17,6 +17,8 @@ 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. * @@ -80,7 +82,7 @@ class PostsController extends Controller ->map(fn($year) => [ 'title' => trans($year->label()), 'tag' => Str::replace('/', '-', $year->label()), - 'value' => $type === "news" ? explode('/', $year->label())[1] : $year->value, + 'value' => $type === self::NEWS ? explode('/', $year->label())[1] : $year->value, 'category' => $category, ]) ->sortDesc() @@ -103,7 +105,7 @@ class PostsController extends Controller 'value' ); - if ($type === "documents") { + if ($type === self::DOCUMENTS) { $filters[] = [ 'title' => ucfirst(__('front.news.order')), 'name' => 'order', @@ -112,13 +114,13 @@ class PostsController extends Controller } $filters[] = [ - 'title' => $type === "documents" ? ucfirst(__('front.news.document')) : ucfirst(__('front.news.theme')), + 'title' => $type === self::DOCUMENTS ? ucfirst(__('front.news.document')) : ucfirst(__('front.news.theme')), 'name' => 'tags', 'fields' => $tags, ]; $filters[] = [ - 'title' => $type === "documents" ? ucfirst(__('front.news.exercise')) : ucfirst(__('front.news.year')), + 'title' => $type === self::DOCUMENTS ? ucfirst(__('front.news.exercise')) : ucfirst(__('front.news.year')), 'name' => 'years', 'fields' => $years, ]; @@ -142,7 +144,6 @@ class PostsController extends Controller $category = $data['category']; app()->setLocale($data['locale']); - $locale = app()->getLocale(); $publishedNewsTags = NewsTag::getTagsByCategoryInCurrentLocale($category); @@ -228,7 +229,6 @@ class PostsController extends Controller $category = $data['category']; app()->setLocale($data['locale']); - $locale = app()->getLocale(); $publishedDocumentsTags = DocumentTag::getTagsByCategoryInCurrentLocale($category); diff --git a/app/View/Components/Front/News/IndexNewsComponent.php b/app/View/Components/Front/News/IndexNewsComponent.php deleted file mode 100644 index 50a4ba91..00000000 --- a/app/View/Components/Front/News/IndexNewsComponent.php +++ /dev/null @@ -1,67 +0,0 @@ -postContainer = [ - 'secondaryClass' => 'fw-container no-bottom-padding', - 'tertiaryClass' => 'tagged-posts', - 'slug' => 'news', - 'category' => $locale === "fr" ? 'actualites' : 'news', - ]; - - - 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', [ - 'postContainer' => $this->postContainer - ]); - } -} diff --git a/resources/views/components/front/news/index-news-component.blade.php b/resources/views/components/front/news/index-news-component.blade.php deleted file mode 100644 index e2f427f7..00000000 --- a/resources/views/components/front/news/index-news-component.blade.php +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/views/components/front/pages/finance/assemblees-generales.blade.php b/resources/views/components/front/pages/finance/assemblees-generales.blade.php index b9221e3f..98966f46 100644 --- a/resources/views/components/front/pages/finance/assemblees-generales.blade.php +++ b/resources/views/components/front/pages/finance/assemblees-generales.blade.php @@ -6,7 +6,7 @@ 'secondaryClass' => 'filter_doc', 'tertiaryClass' => 'fw-container no-top-padding', 'slug' => 'docs', - 'category' => $locale === "fr" ? 'assemblees-generales' : 'general-meetings', + 'category' => \App\Models\DocumentCategory::where('slug->fr', 'assemblees-generales')->first()?->slug ?? '', 'load_more' => ucfirst(__('front.buttons.see_more_docs')), ]; @endphp diff --git a/resources/views/components/front/pages/finance/finance-4.blade.php b/resources/views/components/front/pages/finance/finance-4.blade.php index e8ea0581..641bbce2 100644 --- a/resources/views/components/front/pages/finance/finance-4.blade.php +++ b/resources/views/components/front/pages/finance/finance-4.blade.php @@ -1,12 +1,10 @@ @php - $locale = config('app.locale'); - $postContainer = [ 'class' => 'posts', 'secondaryClass' => 'filter_doc', 'tertiaryClass' => 'fw-container no-top-padding', 'slug' => 'docs', - 'category' => $locale === "fr" ? 'presentation-investisseurs' : 'investor-presentation', + 'category' => \App\Models\DocumentCategory::where('slug->fr', 'presentation-investisseurs')->first()?->slug ?? '', 'load_more' => ucfirst(__('front.buttons.see_more_docs')), ]; @endphp diff --git a/resources/views/components/front/pages/finance/finance-6.blade.php b/resources/views/components/front/pages/finance/finance-6.blade.php index fcd505ee..7aaa2768 100644 --- a/resources/views/components/front/pages/finance/finance-6.blade.php +++ b/resources/views/components/front/pages/finance/finance-6.blade.php @@ -1,11 +1,9 @@ @php - $locale = config('app.locale'); - $postContainer = [ 'class' => 'posts', 'secondaryClass' => 'filter_doc', 'slug' => 'docs', - 'category' => $locale === "fr" ? 'documents' : 'documents', + 'category' => \App\Models\DocumentCategory::where('slug->fr', 'documents')->first()?->slug ?? '', 'load_more' => ucfirst(__('front.buttons.see_more_docs')), ]; @endphp diff --git a/resources/views/front/pages/news.blade.php b/resources/views/front/pages/news.blade.php index a4820e6e..024a812b 100644 --- a/resources/views/front/pages/news.blade.php +++ b/resources/views/front/pages/news.blade.php @@ -4,5 +4,16 @@ @section('description', $dynamicPageModel->content['textarea_seo_description']) @section('bodyClass', 'archive category category-news category-9') @section('content') - + + @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 + + @endsection diff --git a/resources/views/front/pages/press-space.blade.php b/resources/views/front/pages/press-space.blade.php index baca6188..65cad855 100644 --- a/resources/views/front/pages/press-space.blade.php +++ b/resources/views/front/pages/press-space.blade.php @@ -5,41 +5,38 @@ @section('bodyClass', 'page-template page-template-page-press page-template-page-press-php page page-id-14') @section('content') -
-
-
- {!! $dynamicPageModel->content['wysiwyg_content'] !!} +
+
+
+ {!! $dynamicPageModel->content['wysiwyg_content'] !!} +
-
- @php - $locale = app()->getLocale(); + @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 - $postContainer = [ - 'class' => 'posts', - 'tertiaryClass' => 'fw-container no-top-padding', - 'slug' => 'news', - 'category' => $locale === "fr" ? 'espace-presse' : 'press-space', - 'load_more' => ucfirst(__('front.buttons.see_more_docs')), - ]; - @endphp + - +
+
+
+ -
-
-
- - -
-
- @include('front.pages.contact.partials.press') +
+
+ @include('front.pages.contact.partials.press') +
-
@endsection diff --git a/routes/breadcrumbs-front.php b/routes/breadcrumbs-front.php index c022d7d6..c1bbdea1 100644 --- a/routes/breadcrumbs-front.php +++ b/routes/breadcrumbs-front.php @@ -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); }); }