From c00caa1dc32c0c30ffc6d47acbb9efd01db913ba Mon Sep 17 00:00:00 2001 From: Noah Lancien Date: Wed, 21 Jan 2026 14:51:03 +0100 Subject: [PATCH] feat: Added network part in 'produce' page + networks, aisles and brands models --- app/Enums/Sections/PageSectionEnum.php | 8 +- app/Http/Controllers/Bo/AisleController.php | 177 ++++++++++++ app/Http/Controllers/Bo/BrandController.php | 177 ++++++++++++ app/Http/Controllers/Bo/NetworkController.php | 171 ++++++++++++ app/Http/Controllers/Fo/NetworkController.php | 27 ++ .../Controllers/Fo/StaticPageController.php | 10 +- .../Requests/Bo/Aisles/StoreAisleRequest.php | 61 +++++ .../Requests/Bo/Aisles/UpdateAisleRequest.php | 8 + .../Requests/Bo/Brands/StoreBrandRequest.php | 61 +++++ .../Requests/Bo/Brands/UpdateBrandRequest.php | 8 + .../Bo/Networks/StoreNetworkRequest.php | 58 ++++ .../Bo/Networks/UpdateNetworkRequest.php | 8 + app/Models/Aisle.php | 123 +++++++++ app/Models/Brand.php | 115 ++++++++ app/Models/Network.php | 161 +++++++++++ app/Policies/AislePolicy.php | 120 ++++++++ app/Policies/BrandPolicy.php | 120 ++++++++ app/Policies/NetworkPolicy.php | 120 ++++++++ app/Providers/RouteServiceProvider.php | 11 + database/factories/assets/1400x450.png | Bin 0 -> 16059 bytes database/factories/assets/1920x1080.png | Bin 0 -> 29688 bytes database/factories/assets/250x250.png | Bin 0 -> 2917 bytes ...026_01_19_171504_create_networks_table.php | 70 +++++ ...6_01_20_094642_insert_networks_section.php | 39 +++ .../2026_01_20_114502_create_aisles_table.php | 56 ++++ .../2026_01_20_154059_create_brands_table.php | 70 +++++ resources/lang/fr/models.php | 3 + resources/lang/fr/validation.php | 1 + resources/ts/fo/base/common.ts | 24 ++ resources/views/bo/layouts/nav-list.blade.php | 37 ++- .../views/bo/pages/aisles/create.blade.php | 36 +++ .../views/bo/pages/aisles/edit.blade.php | 52 ++++ .../views/bo/pages/aisles/form.blade.php | 208 ++++++++++++++ .../bo/pages/aisles/index-list.blade.php | 66 +++++ .../views/bo/pages/aisles/index.blade.php | 21 ++ .../views/bo/pages/brands/create.blade.php | 36 +++ .../views/bo/pages/brands/edit.blade.php | 52 ++++ .../views/bo/pages/brands/form.blade.php | 207 ++++++++++++++ .../bo/pages/brands/index-list.blade.php | 66 +++++ .../views/bo/pages/brands/index.blade.php | 21 ++ .../views/bo/pages/networks/create.blade.php | 36 +++ .../views/bo/pages/networks/edit.blade.php | 52 ++++ .../views/bo/pages/networks/form.blade.php | 258 ++++++++++++++++++ .../bo/pages/networks/index-list.blade.php | 75 +++++ .../views/bo/pages/networks/index.blade.php | 21 ++ .../views/bo/pages/sections/form.blade.php | 7 +- .../views/fo/pages/networks/show.blade.php | 107 ++++++++ .../views/fo/pages/produce/show.blade.php | 33 +++ .../partials/networks/slider-card.blade.php | 8 + routes/breadcrumbs-bo.php | 63 +++++ routes/web-bo.php | 46 ++++ routes/web-fo.php | 2 + 52 files changed, 3312 insertions(+), 5 deletions(-) create mode 100644 app/Http/Controllers/Bo/AisleController.php create mode 100644 app/Http/Controllers/Bo/BrandController.php create mode 100644 app/Http/Controllers/Bo/NetworkController.php create mode 100644 app/Http/Controllers/Fo/NetworkController.php create mode 100644 app/Http/Requests/Bo/Aisles/StoreAisleRequest.php create mode 100644 app/Http/Requests/Bo/Aisles/UpdateAisleRequest.php create mode 100644 app/Http/Requests/Bo/Brands/StoreBrandRequest.php create mode 100644 app/Http/Requests/Bo/Brands/UpdateBrandRequest.php create mode 100644 app/Http/Requests/Bo/Networks/StoreNetworkRequest.php create mode 100644 app/Http/Requests/Bo/Networks/UpdateNetworkRequest.php create mode 100644 app/Models/Aisle.php create mode 100644 app/Models/Brand.php create mode 100644 app/Models/Network.php create mode 100644 app/Policies/AislePolicy.php create mode 100644 app/Policies/BrandPolicy.php create mode 100644 app/Policies/NetworkPolicy.php create mode 100644 database/factories/assets/1400x450.png create mode 100644 database/factories/assets/1920x1080.png create mode 100644 database/factories/assets/250x250.png create mode 100644 database/migrations/2026_01_19_171504_create_networks_table.php create mode 100644 database/migrations/2026_01_20_094642_insert_networks_section.php create mode 100644 database/migrations/2026_01_20_114502_create_aisles_table.php create mode 100644 database/migrations/2026_01_20_154059_create_brands_table.php create mode 100644 resources/views/bo/pages/aisles/create.blade.php create mode 100644 resources/views/bo/pages/aisles/edit.blade.php create mode 100644 resources/views/bo/pages/aisles/form.blade.php create mode 100644 resources/views/bo/pages/aisles/index-list.blade.php create mode 100644 resources/views/bo/pages/aisles/index.blade.php create mode 100644 resources/views/bo/pages/brands/create.blade.php create mode 100644 resources/views/bo/pages/brands/edit.blade.php create mode 100644 resources/views/bo/pages/brands/form.blade.php create mode 100644 resources/views/bo/pages/brands/index-list.blade.php create mode 100644 resources/views/bo/pages/brands/index.blade.php create mode 100644 resources/views/bo/pages/networks/create.blade.php create mode 100644 resources/views/bo/pages/networks/edit.blade.php create mode 100644 resources/views/bo/pages/networks/form.blade.php create mode 100644 resources/views/bo/pages/networks/index-list.blade.php create mode 100644 resources/views/bo/pages/networks/index.blade.php create mode 100644 resources/views/fo/pages/networks/show.blade.php create mode 100644 resources/views/fo/partials/networks/slider-card.blade.php diff --git a/app/Enums/Sections/PageSectionEnum.php b/app/Enums/Sections/PageSectionEnum.php index 15197e3..5dec1af 100644 --- a/app/Enums/Sections/PageSectionEnum.php +++ b/app/Enums/Sections/PageSectionEnum.php @@ -38,6 +38,7 @@ enum PageSectionEnum: int case produce_quality = 28; case produce_linking = 29; case listening_study = 30; + case produce_networks = 31; /** * Optionnal labels definition. @@ -75,6 +76,7 @@ enum PageSectionEnum: int self::produce_quality->name => 'Qualité', self::produce_linking->name => 'Liens', self::listening_study->name => 'On étudie', + self::produce_networks->name => 'Nos réseaux', ]; /** @@ -114,7 +116,8 @@ enum PageSectionEnum: int self::produce_rhd, self::produce_export, self::produce_quality, - self::listening_study => false, + self::listening_study, + self::produce_networks => false, }; //end match } @@ -155,7 +158,8 @@ enum PageSectionEnum: int self::produce_quality, self::listening_study => \App\Enums\CkEditorConfigName::advanced->label(), self::produce_linking, - self::hire_grow, => null, + self::hire_grow, + self::produce_networks => null, }; //end match } } diff --git a/app/Http/Controllers/Bo/AisleController.php b/app/Http/Controllers/Bo/AisleController.php new file mode 100644 index 0000000..21d7193 --- /dev/null +++ b/app/Http/Controllers/Bo/AisleController.php @@ -0,0 +1,177 @@ +authorizeResource(Aisle::class); + } + + /** + * Display a listing of the resource. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\View\View + */ + public function index(Request $request): \Illuminate\Contracts\View\View + { + /** @var \Illuminate\Database\Eloquent\Builder $aisleModels */ + $aisleModels = Aisle::query()->with('network'); + + /** @var string $search Search field */ + $search = $request->input('search'); + if ($search) { + $this->searchQuery( + $aisleModels, + $search, + null, + 'name', + ); + } + $searchFields = \implode(', ', [ + Str::ucfirst(trans('validation.attributes.name')), + ]); + + /** Sort columns with a query */ + $this->sortQuery($aisleModels); + + /** Custom pagination */ + $aisleModels = $this->paginate($aisleModels); + + return view('bo.pages.aisles.index', compact('aisleModels', 'search', 'searchFields')); + } + + + /** + * Show the form for creating a new resource. + * + * @param \App\Models\Aisle $aisle + * @return \Illuminate\Contracts\View\View + */ + public function create(Aisle $aisle): \Illuminate\Contracts\View\View + { + $networkModels = Network::all(); + + return view('bo.pages.aisles.create', [ + 'aisleModel' => $aisle, + 'networkModels' => $networkModels + ]); + } + + /** + * Store a newly created resource in storage. + * + * @param \App\Http\Requests\Bo\Aisles\StoreAisleRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(StoreAisleRequest $request): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($request) { + $aisle = new Aisle(); + $aisle->fill($request->validated()); + + if ($aisle->saveOrFail()) { + return redirect()->route('bo.aisles.edit', $aisle) + ->with('success', trans('crud.messages.has_been_created', [ + 'model' => Str::ucfirst(trans('models.classes.aisle')) + ])); + } + return redirect()->back() + ->with('error', trans('crud.messages.cannot_be_created', [ + 'model' => Str::ucfirst(trans('models.classes.aisle')) + ])); + }); + } + + /** + * Show the form for editing the specified resource. + * + * @param \App\Models\Aisle $aisle + * @return \Illuminate\Contracts\View\View + */ + public function edit(Aisle $aisle): \Illuminate\Contracts\View\View + { + $networkModels = Network::all(); + + return view('bo.pages.aisles.edit', [ + 'aisleModel' => $aisle, + 'networkModels' => $networkModels + ]); + } + + /** + * Update the specified resource in storage. + * + * @param \App\Http\Requests\Bo\Aisles\UpdateAisleRequest $request + * @param \App\Models\Aisle $aisle + * @return \Illuminate\Http\RedirectResponse + */ + public function update(UpdateAisleRequest $request, Aisle $aisle): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($request, $aisle) { + $aisle->fill($request->validated()); + + if ($aisle->saveOrFail()) { + return redirect()->route('bo.aisles.edit', $aisle) + ->with('success', trans('crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans('models.classes.aisle')) + ])); + } + return redirect()->route('bo.aisles.edit', $aisle) + ->with('error', trans('crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans('models.classes.aisle')) + ])); + }); + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Aisle $aisle + * @return \Illuminate\Http\RedirectResponse + */ + public function destroy(Aisle $aisle): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($aisle) { + if ($aisle->deleteOrFail()) { + return redirect()->route('bo.aisles.index') + ->with('success', trans('crud.messages.has_been_deleted', [ + 'model' => Str::ucfirst(trans('models.classes.aisle')) + ])); + } + return redirect()->back() + ->with('error', trans('crud.messages.cannot_be_deleted', [ + 'model' => Str::ucfirst(trans('models.classes.aisle')) + ])); + }); + } + + /** + * Duplicate the specified resource. + * + * @param \App\Models\Aisle $aisle + * @return \Illuminate\Contracts\View\View + */ + public function duplicate(Aisle $aisle): \Illuminate\Contracts\View\View + { + return $this->create($aisle->replicate()); + } +} diff --git a/app/Http/Controllers/Bo/BrandController.php b/app/Http/Controllers/Bo/BrandController.php new file mode 100644 index 0000000..f36df49 --- /dev/null +++ b/app/Http/Controllers/Bo/BrandController.php @@ -0,0 +1,177 @@ +authorizeResource(Brand::class); + } + + /** + * Display a listing of the resource. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\View\View + */ + public function index(Request $request): \Illuminate\Contracts\View\View + { + /** @var \Illuminate\Database\Eloquent\Builder $brandModels */ + $brandModels = Brand::query()->with('aisle'); + + /** @var string $search Search field */ + $search = $request->input('search'); + if ($search) { + $this->searchQuery( + $brandModels, + $search, + null, + 'name', + ); + } + $searchFields = \implode(', ', [ + Str::ucfirst(trans('validation.attributes.name')), + ]); + + /** Sort columns with a query */ + $this->sortQuery($brandModels); + + /** Custom pagination */ + $brandModels = $this->paginate($brandModels); + + return view('bo.pages.brands.index', compact('brandModels', 'search', 'searchFields')); + } + + + /** + * Show the form for creating a new resource. + * + * @param \App\Models\Brand $brand + * @return \Illuminate\Contracts\View\View + */ + public function create(Brand $brand): \Illuminate\Contracts\View\View + { + $aisleModels = Aisle::all(); + + return view('bo.pages.brands.create', [ + 'brandModel' => $brand, + 'aisleModels' => $aisleModels + ]); + } + + /** + * Store a newly created resource in storage. + * + * @param \App\Http\Requests\Bo\Brands\StoreBrandRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(StoreBrandRequest $request): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($request) { + $brand = new Brand(); + $brand->fill($request->validated()); + + if ($brand->saveOrFail()) { + return redirect()->route('bo.brands.edit', $brand) + ->with('success', trans('crud.messages.has_been_created', [ + 'model' => Str::ucfirst(trans('models.classes.brand')) + ])); + } + return redirect()->back() + ->with('error', trans('crud.messages.cannot_be_created', [ + 'model' => Str::ucfirst(trans('models.classes.brand')) + ])); + }); + } + + /** + * Show the form for editing the specified resource. + * + * @param \App\Models\Brand $brand + * @return \Illuminate\Contracts\View\View + */ + public function edit(Brand $brand): \Illuminate\Contracts\View\View + { + $aisleModels = Aisle::all(); + + return view('bo.pages.brands.edit', [ + 'brandModel' => $brand, + 'aisleModels' => $aisleModels + ]); + } + + /** + * Update the specified resource in storage. + * + * @param \App\Http\Requests\Bo\Brands\UpdateBrandRequest $request + * @param \App\Models\Brand $brand + * @return \Illuminate\Http\RedirectResponse + */ + public function update(UpdateBrandRequest $request, Brand $brand): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($request, $brand) { + $brand->fill($request->validated()); + + if ($brand->saveOrFail()) { + return redirect()->route('bo.brands.edit', $brand) + ->with('success', trans('crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans('models.classes.brand')) + ])); + } + return redirect()->route('bo.brands.edit', $brand) + ->with('error', trans('crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans('models.classes.brand')) + ])); + }); + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Brand $brand + * @return \Illuminate\Http\RedirectResponse + */ + public function destroy(Brand $brand): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($brand) { + if ($brand->deleteOrFail()) { + return redirect()->route('bo.brands.index') + ->with('success', trans('crud.messages.has_been_deleted', [ + 'model' => Str::ucfirst(trans('models.classes.brand')) + ])); + } + return redirect()->back() + ->with('error', trans('crud.messages.cannot_be_deleted', [ + 'model' => Str::ucfirst(trans('models.classes.brand')) + ])); + }); + } + + /** + * Duplicate the specified resource. + * + * @param \App\Models\Brand $brand + * @return \Illuminate\Contracts\View\View + */ + public function duplicate(Brand $brand): \Illuminate\Contracts\View\View + { + return $this->create($brand->replicate()); + } +} diff --git a/app/Http/Controllers/Bo/NetworkController.php b/app/Http/Controllers/Bo/NetworkController.php new file mode 100644 index 0000000..1349780 --- /dev/null +++ b/app/Http/Controllers/Bo/NetworkController.php @@ -0,0 +1,171 @@ +authorizeResource(Network::class); + } + + /** + * Display a listing of the resource. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\View\View + */ + public function index(Request $request): \Illuminate\Contracts\View\View + { + /** @var \Illuminate\Database\Eloquent\Builder $networkModels */ + $networkModels = Network::query(); + + /** @var string $search Search field */ + $search = $request->input('search'); + if ($search) { + $this->searchQuery( + $networkModels, + $search, + null, + 'name', + ); + } + $searchFields = \implode(', ', [ + Str::ucfirst(trans('validation.attributes.name')), + ]); + + /** Sort columns with a query */ + $this->sortQuery($networkModels); + + /** Custom pagination */ + $networkModels = $this->paginate($networkModels); + + return view('bo.pages.networks.index', compact('networkModels', 'search', 'searchFields')); + } + + + /** + * Show the form for creating a new resource. + * + * @param \App\Models\Network $network + * @return \Illuminate\Contracts\View\View + */ + public function create(Network $network): \Illuminate\Contracts\View\View + { + return view('bo.pages.networks.create', [ + 'networkModel' => $network, + ]); + } + + /** + * Store a newly created resource in storage. + * + * @param \App\Http\Requests\Bo\Networks\StoreNetworkRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(StoreNetworkRequest $request): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($request) { + $network = new Network(); + $network->fill($request->validated()); + + if ($network->saveOrFail()) { + return redirect()->route('bo.networks.edit', $network) + ->with('success', trans('crud.messages.has_been_created', [ + 'model' => Str::ucfirst(trans_choice('models.classes.network', 1)) + ])); + } + return redirect()->back() + ->with('error', trans('crud.messages.cannot_be_created', [ + 'model' => Str::ucfirst(trans_choice('models.classes.network', 1)) + ])); + }); + } + + /** + * Show the form for editing the specified resource. + * + * @param \App\Models\Network $network + * @return \Illuminate\Contracts\View\View + */ + public function edit(Network $network): \Illuminate\Contracts\View\View + { + return view('bo.pages.networks.edit', [ + 'networkModel' => $network, + ]); + } + + /** + * Update the specified resource in storage. + * + * @param \App\Http\Requests\Bo\Networks\UpdateNetworkRequest $request + * @param \App\Models\Network $network + * @return \Illuminate\Http\RedirectResponse + */ + public function update(UpdateNetworkRequest $request, Network $network): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($request, $network) { + $network->fill($request->validated()); + + if ($network->saveOrFail()) { + return redirect()->route('bo.networks.edit', $network) + ->with('success', trans('crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans_choice('models.classes.network', 1)) + ])); + } + return redirect()->route('bo.networks.edit', $network) + ->with('error', trans('crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans_choice('models.classes.network', 1)) + ])); + }); + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Network $network + * @return \Illuminate\Http\RedirectResponse + */ + public function destroy(Network $network): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($network) { + if ($network->deleteOrFail()) { + return redirect()->route('bo.networks.index') + ->with('success', trans('crud.messages.has_been_deleted', [ + 'model' => Str::ucfirst(trans_choice('models.classes.network', 1)) + ])); + } + return redirect()->back() + ->with('error', trans('crud.messages.cannot_be_deleted', [ + 'model' => Str::ucfirst(trans_choice('models.classes.network', 1)) + ])); + }); + } + + /** + * Duplicate the specified resource. + * + * @param \App\Models\Network $network + * @return \Illuminate\Contracts\View\View + */ + public function duplicate(Network $network): \Illuminate\Contracts\View\View + { + return $this->create($network->replicate()); + } +} diff --git a/app/Http/Controllers/Fo/NetworkController.php b/app/Http/Controllers/Fo/NetworkController.php new file mode 100644 index 0000000..b703b00 --- /dev/null +++ b/app/Http/Controllers/Fo/NetworkController.php @@ -0,0 +1,27 @@ +load(['aisles', 'aisles.brands']); + $networkModels = Network::query() + ->whereNot('id', $network->id) + ->where('published', true) + ->orderBy('order') + ->get(); + + return view('fo.pages.networks.show', [ + 'networkModel' => $network, + 'networkModels' => $networkModels, + ]); + } +} diff --git a/app/Http/Controllers/Fo/StaticPageController.php b/app/Http/Controllers/Fo/StaticPageController.php index d3552e4..37bbcd0 100644 --- a/app/Http/Controllers/Fo/StaticPageController.php +++ b/app/Http/Controllers/Fo/StaticPageController.php @@ -13,6 +13,7 @@ use App\Mail\SendConfirmationMail; use App\Mail\SendContactMail; use App\Models\HistoricalMilestone; use App\Models\JobVideo; +use App\Models\Network; use App\Models\News; use App\Models\Section; use App\Models\Site; @@ -196,21 +197,28 @@ class StaticPageController extends Controller */ public function produce(): \Illuminate\Contracts\View\View { + // MODELS $staticPageModel = StaticPage::query()->where('type', StaticPageType::we_produce)->first(); $sectionModels = Section::query()->where('page_id', $staticPageModel->id)->get(); + $networkModels = Network::query()->where('published', true)->orderBy('order')->get(); + + // SECTIONS $localSection = $sectionModels->where('section', PageSectionEnum::produce_local)->first(); $gmsSection = $sectionModels->where('section', PageSectionEnum::produce_gms)->first(); $rhdSection = $sectionModels->where('section', PageSectionEnum::produce_rhd)->first(); $exportSection = $sectionModels->where('section', PageSectionEnum::produce_export)->first(); $qualitySection = $sectionModels->where('section', PageSectionEnum::produce_quality)->first(); + $networkSection = $sectionModels->where('section', PageSectionEnum::produce_networks)->first(); return view('fo.pages.produce.show', compact( 'staticPageModel', + 'networkModels', 'localSection', 'gmsSection', 'rhdSection', 'exportSection', - 'qualitySection' + 'qualitySection', + 'networkSection', )); } diff --git a/app/Http/Requests/Bo/Aisles/StoreAisleRequest.php b/app/Http/Requests/Bo/Aisles/StoreAisleRequest.php new file mode 100644 index 0000000..257763c --- /dev/null +++ b/app/Http/Requests/Bo/Aisles/StoreAisleRequest.php @@ -0,0 +1,61 @@ +check(); + } + + /** + * Prepare the data for validation. + * + * @return void + */ + protected function prepareForValidation(): void + { + $this->merge([ + 'published' => $this->boolean('published') + ]); + $this->mergePicture('picture'); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + $rules = [ + 'name' => 'required|string|between:3,255', + 'title' => 'required|string|between:3,255', + 'text' => [ + 'required', + 'string', + new WysiwygLength(3, 500), + ], + 'picture_alt' => 'required|string|between:3,255', + 'picture_title' => 'required|string|between:3,255', + 'network_id' => 'required|exists:networks,id', + 'published' => 'required|boolean', + ]; + return \array_merge( + $rules, + $this->pictureRules('picture', 1400, 450, 1400, 450, false), + ); + } +} diff --git a/app/Http/Requests/Bo/Aisles/UpdateAisleRequest.php b/app/Http/Requests/Bo/Aisles/UpdateAisleRequest.php new file mode 100644 index 0000000..35af4ff --- /dev/null +++ b/app/Http/Requests/Bo/Aisles/UpdateAisleRequest.php @@ -0,0 +1,8 @@ +check(); + } + + /** + * Prepare the data for validation. + * + * @return void + */ + protected function prepareForValidation(): void + { + $this->merge([ + 'published' => $this->boolean('published') + ]); + $this->mergePicture('picture'); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + $rules = [ + 'name' => 'required|string|between:3,255', + 'text' => [ + 'required', + 'string', + new WysiwygLength(3, 255), + ], + 'picture_alt' => 'required|string|between:3,255', + 'picture_title' => 'required|string|between:3,255', + 'link' => 'nullable|url:https', + 'aisle_id' => 'required|exists:aisles,id', + 'published' => 'required|boolean', + ]; + return \array_merge( + $rules, + $this->pictureRules('picture', 250, 250, 250, 250, false), + ); + } +} diff --git a/app/Http/Requests/Bo/Brands/UpdateBrandRequest.php b/app/Http/Requests/Bo/Brands/UpdateBrandRequest.php new file mode 100644 index 0000000..2acae2d --- /dev/null +++ b/app/Http/Requests/Bo/Brands/UpdateBrandRequest.php @@ -0,0 +1,8 @@ +check(); + } + + /** + * Prepare the data for validation. + * + * @return void + */ + protected function prepareForValidation(): void + { + $this->merge([ + 'published' => $this->boolean('published') + ]); + $this->mergePicture('picture'); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + $rules = [ + 'seo_title' => 'required|string|between:45,70', + 'seo_description' => 'required|string|between:50,160', + 'name' => 'required|string|between:3,255', + 'title' => 'required|string|between:3,255', + 'subtitle' => 'required|string|between:3,255', + 'picture_alt' => 'required|string|between:3,255', + 'picture_title' => 'required|string|between:3,255', + 'published' => 'required|boolean', + + ]; + return \array_merge( + $rules, + $this->pictureRules('picture', 1920, 1080, 1920, 1080, false), + ); + } +} diff --git a/app/Http/Requests/Bo/Networks/UpdateNetworkRequest.php b/app/Http/Requests/Bo/Networks/UpdateNetworkRequest.php new file mode 100644 index 0000000..5e0705a --- /dev/null +++ b/app/Http/Requests/Bo/Networks/UpdateNetworkRequest.php @@ -0,0 +1,8 @@ + + */ + protected $fillable = [ + 'name', + 'title', + 'text', + 'picture', + 'picture_alt', + 'picture_title', + 'published', + 'network_id', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'published' => 'boolean', + ]; + + /** + * Perform any actions required after the model boots. + * + * @return void + */ + protected static function booted(): void + { + static::creating(function (self $model) { + self::manageFields($model); + }); + static::updating(function (self $model) { + self::manageFields($model); + }); + static::updated(function (self $model) { + FileStorageHelper::removeOldFile($model, 'picture'); + }); + static::deleting(function (self $model) { + self::assertLimitPublishedModels($model, true); + }); + static::deleted(function (self $model) { + FileStorageHelper::removeFile($model, 'picture'); + }); + } + + // * METHODS + + /** + * Manage model fields. + * + * @param self $model + * @return void + */ + private static function manageFields(self $model): void + { + // * ASSERTIONS + self::assertLimitPublishedModels($model); + + $model->picture = FileStorageHelper::storeFile($model, $model->picture, true); + } + + /** + * Check if the limit of published models is reached. + * + * @param self $model + * @param boolean $isDeletion + * @return void + */ + private static function assertLimitPublishedModels(self $model, $isDeletion = false) + { + $count = self::query()->where('published', true)->where('network_id', $model->network_id)->count(); + if ( + (!$model->published && $count <= 1) || + ($model->published && $count >= 4) || + ($isDeletion && $count === 1) + ) { + throw ValidationException::withMessages([ + 'Il doit y avoir entre 1 et 4 éléments attachés à chaque réseau.' + ]); + } + } + + // * RELATIONS + + /** + * The network that belongs to the logo. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function network(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(Network::class); + } + + /** + * Associated brands. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function brands(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(Brand::class); + } +} diff --git a/app/Models/Brand.php b/app/Models/Brand.php new file mode 100644 index 0000000..5b8d03d --- /dev/null +++ b/app/Models/Brand.php @@ -0,0 +1,115 @@ + + */ + protected $fillable = [ + 'name', + 'text', + 'picture', + 'picture_alt', + 'picture_title', + 'link', + 'published', + 'aisle_id' + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'published' => 'bool', + ]; + + /** + * Perform any actions required after the model boots. + * + * @return void + */ + protected static function booted(): void + { + static::creating(function (self $model) { + self::manageFields($model); + }); + static::updating(function (self $model) { + self::manageFields($model); + }); + static::updated(function (self $model) { + FileStorageHelper::removeOldFile($model, 'picture'); + }); + static::deleting(function (self $model) { + self::assertLimitPublishedModels($model, true); + }); + static::deleted(function (self $model) { + FileStorageHelper::removeFile($model, 'picture'); + }); + } + + // * METHODS + + /** + * Manage model fields. + * + * @param self $model + * @return void + */ + private static function manageFields(self $model): void + { + // * ASSERTIONS + self::assertLimitPublishedModels($model); + + // * TRANSFORMATIONS + $model->picture = FileStorageHelper::storeFile($model, $model->picture, true); + } + + /** + * Check if the limit of published models is reached. + * + * @param self $model + * @param boolean $isDeletion + * @return void + */ + private static function assertLimitPublishedModels(self $model, $isDeletion = false) + { + $count = self::query()->where('published', true)->count(); + if ( + !$count === 0 && + ((!$model->published && $count <= 1) || + ($model->published && $count >= 5) || + ($isDeletion && $count === 1)) + ) { + throw ValidationException::withMessages([ + 'Il doit y avoir entre 1 et 5 éléments publiés simultanément.' + ]); + } + } + + // * RELATIONS + + /** + * Associated aisle. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function aisle(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(Aisle::class); + } +} diff --git a/app/Models/Network.php b/app/Models/Network.php new file mode 100644 index 0000000..3fd0c6e --- /dev/null +++ b/app/Models/Network.php @@ -0,0 +1,161 @@ + + */ + protected $fillable = [ + 'seo_title', + 'seo_description', + 'name', + 'slug', + 'title', + 'subtitle', + 'picture', + 'picture_alt', + 'picture_title', + 'order', + 'published', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'published' => 'bool', + ]; + + /** + * Perform any actions required after the model boots. + * + * @return void + */ + protected static function booted(): void + { + static::creating(function (self $model) { + self::setOrder($model); + self::manageFields($model); + }); + static::updating(function (self $model) { + self::manageFields($model); + }); + static::updated(function (self $model) { + FileStorageHelper::removeOldFile($model, 'picture'); + }); + static::deleting(function (self $model) { + self::assertLimitPublishedModels($model, true); + }); + static::deleted(function (self $model) { + FileStorageHelper::removeFile($model, 'picture'); + }); + } + + // * METHODS + + /** + * Manage model fields. + * + * @param self $model + * @return void + */ + private static function manageFields(self $model): void + { + // * ASSERTIONS + self::assertLimitPublishedModels($model); + self::assertFieldIsUnique('name', $model->name, $model->id); + self::assertFieldIsUnique('slug', Str::slug($model->name), $model->id); + + // * TRANSFORMATIONS + self::setSlug($model, $model->name); + $model->picture = FileStorageHelper::storeFile($model, $model->picture, true); + } + + /** + * Set the slug. + * + * @param self $model + * @param string $original + * + * @return void + */ + private static function setSlug(self $model, string $original): void + { + $model->slug = Str::slug($original); + } + + /** + * Set order after the last element of the list. + * + * @param self $model + * @return void + */ + private static function setOrder(self $model): void + { + $model->order = \intval(self::query()->max('order')) + 1; + } + + /** + * Asserts using validation that the field is unique. + * + * @param string $nameField + * @param string $valueField + * @param integer|null $id + * @return void + */ + private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null): void + { + $table = (new self())->getTable(); + ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id); + } + + /** + * Check if the limit of published models is reached. + * + * @param self $model + * @param boolean $isDeletion + * @return void + */ + private static function assertLimitPublishedModels(self $model, $isDeletion = false) + { + $count = self::query()->where('published', true)->count(); + if ( + (!$model->published && $count <= 1) || + ($model->published && $count >= 5) || + ($isDeletion && $count === 1) + ) { + throw ValidationException::withMessages([ + 'Il doit y avoir entre 1 et 5 éléments publiés simultanément.' + ]); + } + } + + // * RELATIONS + + /** + * The aisles that belong to the logo. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function aisles(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(Aisle::class); + } +} diff --git a/app/Policies/AislePolicy.php b/app/Policies/AislePolicy.php new file mode 100644 index 0000000..0efd808 --- /dev/null +++ b/app/Policies/AislePolicy.php @@ -0,0 +1,120 @@ +where('slug', $value); })->where('published', true)->firstOrFail(); }); + Route::bind('publishedNetwork', function ($value) { + return Network::where(function ($q) use ($value) { + $q->where('slug', $value); + })->where('published', true)->firstOrFail(); + }); } /** diff --git a/database/factories/assets/1400x450.png b/database/factories/assets/1400x450.png new file mode 100644 index 0000000000000000000000000000000000000000..b93bfacf0231185f47d261ff10230d9ebfe548ae GIT binary patch literal 16059 zcmeHu`9IWc`!^}2MB7F7T&_Y{LP!%@NhlFzS23~-AqHd2l;Y}=rO3XtSdy(UGnj>P zN!H0anK3g;b~9t0!C0Piy6*3NfB%H%c|G?JUOum0`5fnYoX2?_@8f;EKTm9~nD5>x zyHiL=Xt%|s3${W++rou}gg@;N1^;6I+bcuxOYGhy#}FZ*UHb(;!a`X&2Ze+V30Yh? zdp$gdMPJW4a52wdn72muNDFlo-Mi=6wF@f;N!u=L{X1#fK*F6#UL>_V7rU)w>aQVS2I-aWj9zC~tS$#XOAtaRgeXW7v{_7DP{QDL+!4q2dh_JTc;fzcQ zT3Yb%@~K7RF2RG%=RbzG2_Dq$?3>*pcu3U|3l|nVv}J512!0EBS^f7l|Mks(%51_ole*KSE#HjuwgxHcB;QL zrX@*9>2&z)@Mc~*rPAHBHjG96zA&G^xizekDiJ?tqHj_xma+1ak4mHajgZl*7A|L!Nu)2 zLar`;XI?5kiqb>jd2^kEn``7vdC{85WVibJlf$N)3#M0xxJH4}S}rHWALN@LBg6Bj zkWq~XbuZnRj>v52=Z9lo@B{PgEqNnJreT58vcd9j_K;C+)XMxv=H>=32F1srAl(b| zU~x=Ila-$-F_om%so5<3OFL6vfYMdUU0s@P+A~_9++p@GhtX2ynWSh;<;yF)kU2z5 zD&cB$cSx+yRFfqjr}Sx4;i^aTpOM?Vxh8C*OB1<+b@`}`m5=61$i6aG?;H^v$Sv=a zymCg%?heBfkkF~?7zEjLV=jr^Qo|CWS%`e<{u$AWCti(A)b&^yO?Ow6IE0IGJ2Knp zF-*-NLy4Q}0fp*IT}c*q)v_vj1xa z>+AH!v&>F@M>3;$Axq{{W=X0<2nQ=PrmVMay0JX+j+cFH3u7pym?X{9b!z0hZ!WpV zmC}8QA)KSP0xzGsDJGKoct3D~E8AV~1Tgh1eJ*6Y%N{kTWIVk7Jx=Le$LY$!h_=9Y zaJbE!WLqx#T>dZ|wLhy5(X9`v1=pp_ z{x|!6cwK9RY}Vsdwi7a8&#imMWVIC258YR{R1RT&>FK#fHBmsxbu`xZRp24F8xf;z zFW5Y$I%k1M<0EyyetUDj+v0|jW<;;+%d$kY^v%bQtRs{ImY%B(mrtj=NBh*@>c1o$ zcuHyV(IR^X>nMh6mfx9O{~+XO%lmSFw*@+!85(Hn(E;p>64Y<>@)`Isv?1kIsl@?J zFOQ!LB7#nne0lxQ@HQ!J%Tnjf$~n{fT40_7(`qoJ%a)#NP-WuP zQ?#^~pQh-N3OI+bz9)VVG#01G8NX zZ1y#kM~UjYzT%tg+QF)gu7AyNO0yK(rR7>*lMyV_cp?;TzAJp8g2H#h51! z-C_0cv5zAhUW4Y*0vlM-`MEtVT^5YNqFT}7R14*)`7K_ch

57p^Sik}wdY(#IY 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 literal 0 HcmV?d00001 diff --git a/database/factories/assets/1920x1080.png b/database/factories/assets/1920x1080.png new file mode 100644 index 0000000000000000000000000000000000000000..ae3045ab67832678814d48d74dfdcda6412600b0 GIT binary patch literal 29688 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

jBgx@R|WKm>^}6 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

5?i`B?bC0S^y@%JAV zf6!;Rdpf6G?oaEQ$7cCwGeV~1lD*C{Ruf!z>0<37!RlPP93C!Nml$U?LzG+ov~>u2%9e+x z8L&Yt4;4QPcKFX5fo!O-y}|*()f7fh7-2c`Dm+2q2?|e8z?1(GMw^gV1aS5tWVSkH zKJLf+og9Oe#@2|YuJrlUK(sFuBZUnRQrJLY17e#}_`$z#gUwFpb9$Zcs2C}1fY5)} j2K&@#%gB;t_5U}bm}f7BuZ^n@B&O)%?7pwiiFWm$0Y>I7 literal 0 HcmV?d00001 diff --git a/database/factories/assets/250x250.png b/database/factories/assets/250x250.png new file mode 100644 index 0000000000000000000000000000000000000000..744ada5b2abb93041fe67c99e540533d74afd62f GIT binary patch literal 2917 zcmeH}=~ohn8piE*$EAdFT1**q$~sL`X-y^}h~*N~B36^TU?;ac<`PHArmhrnf zsE!9CIB9yike|4&Q$#A~hAZdfv!YvLa^#1#H9d|A&`~HRBV|r5@qYqb=h1_5pW3>h zfDZ6eW6en-lfK{9IlO?CrvIHUgQw||vAX3cVJU(#ZXjhF+Ot5pT?hJ zqn3nhkRv5ZiYN@{q!BwT#y@e1E+P#nK0cm}iJw(WcVyLzOHMPU4-q3-c-hDhe|me8 zh*R~<|qpd+Lk*N75Hkonl_#rMu==4ZW#jC{vnFjVT6?{1Zmb!6k3{tW0zWD>b| zxYB~jNZupO)Na3td(^*HNu2C-3wgtH zHo|tVbILX_ciVtt6ZMs_1#azmXHA#HbxlYW2G@YzQbQdAM?%o(5;#d!rNP6ajz%t< z8)mw4=i0^P;R0Z{cVBeUZ(g}Ad}LG7M&xr}UiXQnd;>{#r%fQ6?2 z620JuRM*wjfw_19tSB|(2t>#P)WH3gv&BiJuT~6IoQOx$=UT8(WW-smK`k5StKu%G z)$J^o{){}>h+3^42PZRnCtC?gb27Z}H!p1|)9^F#N>yA44Vf>wf#*I0RmH|X;?!?| zU%%}WZ@-Upr3&7i39TVlN(L{`qS+^8u;U@hs8}_-X}w3evorS^o=c2!4VzsUYsA@& z)z#KokMB0lme4gMse`gN=67&l*tZC(b3aJ-d_~A+_7njAXw!2*CB&+U*mr}w^$U}# z@%Qm1IpmaSQ9{p+KQB9+O1@@jL=~SJbYD1%JnV>2=jF$Q^nMb_la;PF2}X9fH7p4AUZ9u{Et z#Ys;Jn@00(h|u%5((sV#JPFqiPMta>J8L7<1EB#!^%rOq3Bl+~{z1j;%4ZhEz!f90 z<@7`rqVQ@&8|XSCd>B|(!-`Mri3&k4AhLABIjfXZ*Q*ZIt3CcHiUVCr>C)64+F)TQ zN(}dJSXfLc)r3p4o8h5u2~vl%ff=}+rOC>R4b-pkNU~#GP20`>U72HZ^L7?eT zb<7nn(-3o6!E4;jFiC$``$@j2A zlOZV^l(C8fRm5B{A#U`NY(k(k;hQ_(E&8Gj`io z5ko55dEDpPEtddxUP#g1%>q7kl$Y_;F9TJA|H&4%-fMD?5wH$N-SpiivBYpao6v= zt_9X)rd|G+2Z0k~L)XcPKL^~A;&aG4*5l6J2*Qzm6Fpr{3o}19bK$7>!M|!m9}|nr z=uZU^hf9KH{P^9r3f`!)8j)7*J66W`UCO%N1AXJdNVS-9LJuD}PJ5caBNk8n)qeFP zuSuQ&QIOSQ2BLA2H`}{-xd-w#dCWiehNsG8XLH<2G(1c zgXoO-3m65aO$QLnjoNwhF97{Kk3Q^FN-#qpS$EbANs@cQuFnZC@gR);%M+zxIne<= zMdK6pf6>A;g(UP0hK7+Cj}(qH6MUjCiF0_|-lYVsyH=D>t3TzaM^RDH@G^1v zljjX-qu@NxNiooY=zW-$0BL*WalRYYrkuR}SV{kghcTTKR<^*;pMO!7PnMK*TY=>I zi8p70h)#>Rj;mU^RjKG!vm%g)XFP5eDqfe`W4)XyRnrpp`D6yQ`OVn-mVG^cF<;`vV}Q) zE9os??DU^ZKew?=$n9>_Jpzc%$YfTj XX}vyJ;TOB{H=CXFMftq(x_0mXj&ap7 literal 0 HcmV?d00001 diff --git a/database/migrations/2026_01_19_171504_create_networks_table.php b/database/migrations/2026_01_19_171504_create_networks_table.php new file mode 100644 index 0000000..55193e3 --- /dev/null +++ b/database/migrations/2026_01_19_171504_create_networks_table.php @@ -0,0 +1,70 @@ +id(); + $table->string('seo_title')->comment('SeoTitle of the network'); + $table->string('seo_description')->comment('SeoDescription of the network'); + $table->string('name')->comment('Name of the network'); + $table->string('slug')->comment('Slug of the network'); + $table->string('title')->comment('Title of the network page'); + $table->string('subtitle')->comment('Subtitle of the network page'); + $table->string('picture')->comment('Picture of the network'); + $table->string('picture_alt')->comment('Alt attribute of the picture'); + $table->string('picture_title')->comment('Title attribute of the picture'); + $table->integer('order')->unique()->comment('Position of the networks'); + $table->boolean('published')->comment('Is the network published ?'); + $table->timestamps(); + }); + + $this->createNetwork('RHD'); + $this->createNetwork('GMS'); + $this->createNetwork('Export'); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('networks'); + } + + /** + * Create fake network + * + * @param string $name + * @return void + */ + private function createNetwork(string $name) + { + $network = new Network(); + $network->seo_title = sprintf('Notre réseau %s', $name); + $network->seo_description = sprintf('Découvrez en plus sur notre réseau %s', $name); + $network->name = $name; + $network->title = 'Lorem ipsum dolor sit amet'; + $network->subtitle = sprintf('Notre réseau %s', $name); + $network->picture = FileStorageHelper::storeFile( + new Network(), + new \SplFileInfo(\resource_path( + '../database/factories/assets/1920x1080.png' + )) + ); + $network->picture_alt = 'Default picture'; + $network->picture_title = 'Default picture'; + $network->published = true; + $network->saveOrFail(); + } +}; diff --git a/database/migrations/2026_01_20_094642_insert_networks_section.php b/database/migrations/2026_01_20_094642_insert_networks_section.php new file mode 100644 index 0000000..8319413 --- /dev/null +++ b/database/migrations/2026_01_20_094642_insert_networks_section.php @@ -0,0 +1,39 @@ +where('section', PageSectionEnum::produce_networks)->firstOrNew( + [ + 'section' => PageSectionEnum::produce_networks, + ], + [ + 'h2_title' => 'Retrouvez-nous dans nos réseaux', + 'text' => '', + 'data' => [], + 'page_id' => StaticPage::query()->where('type', StaticPageType::we_produce)->firstOrFail()->id + ] + ); + $networkSection->saveQuietly(); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Section::query()->where('section', PageSectionEnum::produce_networks)->delete(); + } +}; diff --git a/database/migrations/2026_01_20_114502_create_aisles_table.php b/database/migrations/2026_01_20_114502_create_aisles_table.php new file mode 100644 index 0000000..af2dd17 --- /dev/null +++ b/database/migrations/2026_01_20_114502_create_aisles_table.php @@ -0,0 +1,56 @@ +id(); + $table->string('name'); + $table->string('title'); + $table->text('text'); + $table->string('picture'); + $table->string('picture_alt'); + $table->string('picture_title'); + $table->foreignId('network_id')->constrained(); + $table->boolean('published')->comment('Is the aisle published ?'); + $table->timestamps(); + }); + + Network::query()->each(function ($network) { + $aisle = new Aisle(); + $aisle->name = 'Rayon 1'; + $aisle->title = 'Nous sommes présent au rayon 1'; + $aisle->text = 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis minima minus maiores delectus nesciunt eveniet voluptatum, saepe veniam beatae, tempora voluptate quidem unde odio dolores ut distinctio ea vero cumque?'; + $aisle->picture = FileStorageHelper::storeFile( + new Aisle(), + new \SplFileInfo(\resource_path( + '../database/factories/assets/1400x450.png' + )) + ); + $aisle->picture_alt = 'Image décorative pour le rayon 1'; + $aisle->picture_title = 'Image décorative pour le rayon 1'; + $aisle->published = true; + $aisle->network()->associate($network); + $aisle->saveOrFail(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('aisles'); + } +}; diff --git a/database/migrations/2026_01_20_154059_create_brands_table.php b/database/migrations/2026_01_20_154059_create_brands_table.php new file mode 100644 index 0000000..a2e67cd --- /dev/null +++ b/database/migrations/2026_01_20_154059_create_brands_table.php @@ -0,0 +1,70 @@ +id(); + $table->string('name'); + $table->text('text'); + $table->string('picture'); + $table->string('picture_alt'); + $table->string('picture_title'); + $table->string('link')->nullable(); + $table->foreignId('aisle_id')->constrained(); + $table->boolean('published')->comment('Is the brand published ?'); + $table->timestamps(); + }); + + Aisle::query()->each(function ($aisle) { + for ($i = 0; $i < 5; $i++) { + $this->createBrand($aisle, $i); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('brands'); + } + + /** + * Create fake brand + * + * @param Aisle $aisle + * @param int $number + * @return void + */ + private function createBrand(Aisle $aisle, int $number) + { + $brand = new Brand(); + $brand->name = sprintf('Marque %d', $number); + $brand->text = 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis minima minus maiores delectus nesciunt eveniet voluptatum, saepe veniam beatae, tempora voluptate quidem unde odio dolores ut distinctio ea vero cumque?'; + $brand->picture = FileStorageHelper::storeFile( + new Aisle(), + new \SplFileInfo(\resource_path( + '../database/factories/assets/250x250.png' + )) + ); + $brand->picture_alt = 'Image décorative pour la marque'; + $brand->picture_title = 'Image décorative pour la marque'; + $brand->link = 'https://www.example.com'; + $brand->published = true; + $brand->aisle()->associate($aisle); + $brand->saveOrFail(); + } +}; diff --git a/resources/lang/fr/models.php b/resources/lang/fr/models.php index 84209d7..b7ec1b0 100644 --- a/resources/lang/fr/models.php +++ b/resources/lang/fr/models.php @@ -14,5 +14,8 @@ return [ 'job_video' => 'vidéo métier|vidéos métiers', 'strip' => 'bandeau d\'information|bandeaux d\'information', 'testimonial' => 'témoignage', + 'network' => 'réseau|réseaux', + 'aisle' => 'rayon', + 'brand' => 'marque', ] ]; diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php index 6c7f2d1..d8d8285 100644 --- a/resources/lang/fr/validation.php +++ b/resources/lang/fr/validation.php @@ -295,5 +295,6 @@ return [ 'object' => 'objet', 'message' => 'message', 'banner' => 'bannière', + 'subtitle' => 'sous-titre', ], ]; diff --git a/resources/ts/fo/base/common.ts b/resources/ts/fo/base/common.ts index fba862e..4d26a9a 100644 --- a/resources/ts/fo/base/common.ts +++ b/resources/ts/fo/base/common.ts @@ -322,6 +322,30 @@ document.addEventListener("DOMContentLoaded", function () { }, }, }, + "swiper-networks": { + modules: [Pagination, Navigation], + grabCursor: true, + slidesPerView: 1.1, + spaceBetween: 15, + centeredSlides: true, + speed: 750, + pagination: { + el: ".swiper-networks-pagination", + clickable: true, + renderBullet: function (index: number, className: string) { + return ""; + }, + }, + navigation: { + nextEl: ".swiper-networks-button-next", + prevEl: ".swiper-networks-button-prev", + }, + breakpoints: { + 576: { + slidesPerView: 1.3, + }, + }, + }, }; const swiperVol = document.getElementsByClassName("swiper") as HTMLCollectionOf; diff --git a/resources/views/bo/layouts/nav-list.blade.php b/resources/views/bo/layouts/nav-list.blade.php index 8645367..f89335e 100644 --- a/resources/views/bo/layouts/nav-list.blade.php +++ b/resources/views/bo/layouts/nav-list.blade.php @@ -248,7 +248,15 @@ {{-- WE PRODUCE --}}

@endforeach @endcan + @can('viewAny', \App\Models\Network::class) + + @endcan + @can('viewAny', \App\Models\Aisle::class) + + @endcan + @can('viewAny', \App\Models\Brand::class) + + @endcan diff --git a/resources/views/bo/pages/aisles/create.blade.php b/resources/views/bo/pages/aisles/create.blade.php new file mode 100644 index 0000000..d318d25 --- /dev/null +++ b/resources/views/bo/pages/aisles/create.blade.php @@ -0,0 +1,36 @@ +@extends('bo.layout', ['brParam' => $aisleModel]) + +@section('title', __('Création d\'un :model', ['model' => trans_choice('models.classes.aisle', 1)])) +@section('description', __('Création d\'un tout nouveau :model', ['model' => trans_choice('models.classes.aisle', 1)])) +@section('breadcrumb', request()->route()->getName()) + +@section('content') +
+
+ + + + @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $aisleModel]) +
+
+ +
+
+
+ @csrf + @include('bo.pages.aisles.form') + @include('bo.partials.script-button-clone') +
+
+ +
+
+
+@endsection diff --git a/resources/views/bo/pages/aisles/edit.blade.php b/resources/views/bo/pages/aisles/edit.blade.php new file mode 100644 index 0000000..1788831 --- /dev/null +++ b/resources/views/bo/pages/aisles/edit.blade.php @@ -0,0 +1,52 @@ +@extends('bo.layout', ['brParam' => $aisleModel]) + +@section('title', __('Édition d\'un :model', ['model' => trans_choice('models.classes.aisle', 1)])) +@section('description', __('Édition d\'un :model déjà existant', ['model' => trans_choice('models.classes.aisle', 1)])) +@section('breadcrumb', request()->route()->getName()) + +@section('content') +
+
+ + + + @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $aisleModel]) +
+
+
+ @can('duplicate', $aisleModel) + + + + @endcan + + @can('delete', $aisleModel) + @csrf + @method('DELETE') + + @endcan +
+
+
+
+ @csrf + @method('PUT') + @include('bo.pages.aisles.form') + @include('bo.partials.script-button-clone') +
+
+ +
+
+
+@endsection diff --git a/resources/views/bo/pages/aisles/form.blade.php b/resources/views/bo/pages/aisles/form.blade.php new file mode 100644 index 0000000..4fa31a8 --- /dev/null +++ b/resources/views/bo/pages/aisles/form.blade.php @@ -0,0 +1,208 @@ +
+ {{-- NAME --}} +
+
+ {{ Str::ucfirst(__('validation.attributes.name')) }} +
+
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'aisleName', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.attributes.name')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.aisle', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'name', + 'required' => true, + 'model' => $aisleModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.attributes.name'), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.aisle', 1), + ]), + 'min' => 3, + 'max' => 100, + ]), + 'placeholder' => __('Un nom clair et concis'), + ]) +
+
+
+
+ {{-- TITLE --}} +
+
+ {{ Str::ucfirst(__('validation.attributes.title')) }} +
+
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'aisleTitle', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.attributes.title')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.aisle', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'title', + 'required' => true, + 'model' => $aisleModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.attributes.title'), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.aisle', 1), + ]), + 'min' => 3, + 'max' => 100, + ]), + 'placeholder' => __('Un nom clair et concis'), + ]) +
+
+
+
+ {{-- NETWORK_ID --}} +
+
+ {{ Str::ucfirst(trans_choice('models.classes.network', 1)) }} +
+
+ @include('bo.partials.inputs.bigint-foreign-id', [ + 'id' => 'aisleNetwork', + 'label' => __(':field du :model', [ + 'field' => str(trans_choice('models.classes.network', '1'))->ucFirst(), + 'model' => __('models.classes.aisle'), + ]), + 'name' => 'network_id', + 'required' => true, + 'model' => $aisleModel ?? null, + 'associatedModels' => $networkModels ?? null, + 'foreignLabel' => 'name', + 'helper' => __('crud.minicrud.enum_select', [ + 'enum' => trans_choice('models.classes.network', '1'), + ]), + ]) +
+
+
+
+ {{-- PUBLISHED --}} +
+
+ {{ Str::ucfirst(__('crud.minicrud.visibility_label')) }} +
+
+ @include('bo.partials.inputs.tinyint-boolean', [ + 'id' => 'aislePublished', + 'name' => 'published', + 'model' => $aisleModel ?? null, + 'label' => __('crud.minicrud.visibility_label'), + 'helper' => __('crud.minicrud.visibility_helper'), + ]) +
+
+
+
+ {{-- TEXT --}} +
+
+ {{ Str::ucfirst(__('validation.attributes.content')) }} +
+
+ @include('bo.partials.inputs.text-html', [ + 'id' => 'aisleText', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.custom.first_content')), + 'inter' => __('validation.custom.of_the.female'), + 'model' => trans_choice('models.classes.aisle', 1), + ]), + 'name' => 'text', + 'model' => $aisleModel ?? null, + 'required' => true, + // 'editorConfig' => $aisleModel->aisle->getFirstWysiwygType(), + 'helper' => __('crud.minicrud.text_warning'), + ]) +
+
+
+
+ {{-- PICTURE --}} +
+
+ {{ Str::ucfirst(__('validation.custom.picture')) }} +
+ {{-- PICTURE_ALT --}} +
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'aislePictureAlt', + 'label' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => Str::ucfirst(__('validation.custom.alt_attribute')), + 'inter1' => __('validation.custom.of_the.male'), + 'field2' => __('validation.custom.picture'), + 'inter2' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.aisle', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'picture_alt', + 'required' => true, + 'model' => $aisleModel ?? null, + 'helper' => __('crud.minicrud.picture_alt_helper'), + 'placeholder' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => Str::ucfirst(__('validation.custom.alt_attribute')), + 'inter1' => __('validation.custom.of_the.male'), + 'field2' => __('validation.custom.picture'), + 'inter2' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.aisle', 1), + ]), + ]) +
+ {{-- PICTURE_TITLE --}} +
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'aislePictureTitle', + 'label' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => Str::ucfirst(__('validation.custom.title_attribute')), + 'inter1' => __('validation.custom.of_the.male'), + 'field2' => __('validation.custom.picture'), + 'inter2' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.aisle', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'picture_title', + 'required' => true, + 'model' => $aisleModel ?? null, + 'helper' => __('crud.minicrud.picture_title_helper'), + 'placeholder' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => Str::ucfirst(__('validation.custom.title_attribute')), + 'inter1' => __('validation.custom.of_the.male'), + 'field2' => __('validation.custom.picture'), + 'inter2' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.aisle', 1), + ]), + ]) +
+ {{-- PICTURE --}} +
+ @include('bo.partials.inputs.varchar-picture', [ + 'id' => 'aislePicture', + 'name' => 'picture', + 'width' => 1400, + 'height' => 450, + 'model' => $aisleModel ?? null, + 'helper' => __('crud.minicrud.dimensions_must_be', [ + 'widthpx' => '1400px', + 'heightpx' => '450px', + ]), + 'required' => true, + 'preview' => true, + ]) +
+
+
+
+
+

{{ __('validation.custom.required_fields') }}

+
+
diff --git a/resources/views/bo/pages/aisles/index-list.blade.php b/resources/views/bo/pages/aisles/index-list.blade.php new file mode 100644 index 0000000..64500fa --- /dev/null +++ b/resources/views/bo/pages/aisles/index-list.blade.php @@ -0,0 +1,66 @@ + + @if (count($aisleModels) > 0) + + @include('bo.modules.table-col-sorter', [ + 'cols' => [ + 'name' => Str::ucfirst(__('validation.attributes.name')), + 'network_id' => Str::ucfirst(trans_choice('models.classes.network', 1)), + 'published' => Str::ucfirst(__('validation.custom.activated')), + 'updated_at' => Str::ucfirst(__('validation.attributes.updated_at')), + ], + ]) + + + @foreach ($aisleModels as $aisleModel) + + + + @include('bo.modules.change-published-status', [ + 'routeName' => 'aisles', + 'model' => $aisleModel, + ]) + + + + @endforeach + + @else + + + + @endif +
{{ $aisleModel->name }}{{ $aisleModel->network->name }} + {{ $aisleModel->updated_at->isoFormat('LLLL') }} + + @canAny(['delete', 'duplicate', 'update'], $aisleModel) +
+ @can('duplicate', $aisleModel) + + + + @endcan + @can('update', $aisleModel) + + + + @endcan + @can('delete', $aisleModel) + @csrf + @method('DELETE') + + @endcan +
+ @else + + + + @endcan +
{{ trans_choice('crud.other.no_model_found', 1, ['model' => trans_choice('models.classes.aisle', 1)]) }} +
diff --git a/resources/views/bo/pages/aisles/index.blade.php b/resources/views/bo/pages/aisles/index.blade.php new file mode 100644 index 0000000..986b46b --- /dev/null +++ b/resources/views/bo/pages/aisles/index.blade.php @@ -0,0 +1,21 @@ +@extends('bo.layout') + +@section('title', Str::ucfirst(trans_choice('models.classes.aisle', 1))) +@section('description', __('Liste de tous les :model', ['model' => trans_choice('models.classes.aisle', '*')])) + +@section('content') +
+ @include('modules.breadcrumbs.breadcrumb-body') + @can('create', \App\Models\Aisle::class) + + + + @endcan +
+ @include('bo.modules.search-bar') +
+ @include('bo.pages.aisles.index-list') +
+ {{ $aisleModels->links() }} +@endsection diff --git a/resources/views/bo/pages/brands/create.blade.php b/resources/views/bo/pages/brands/create.blade.php new file mode 100644 index 0000000..cdd0261 --- /dev/null +++ b/resources/views/bo/pages/brands/create.blade.php @@ -0,0 +1,36 @@ +@extends('bo.layout', ['brParam' => $brandModel]) + +@section('title', __('Création d\'un :model', ['model' => trans_choice('models.classes.brand', 1)])) +@section('description', __('Création d\'un tout nouveau :model', ['model' => trans_choice('models.classes.brand', 1)])) +@section('breadcrumb', request()->route()->getName()) + +@section('content') +
+
+ + + + @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $brandModel]) +
+
+ +
+
+
+ @csrf + @include('bo.pages.brands.form') + @include('bo.partials.script-button-clone') +
+
+ +
+
+
+@endsection diff --git a/resources/views/bo/pages/brands/edit.blade.php b/resources/views/bo/pages/brands/edit.blade.php new file mode 100644 index 0000000..1bd17f6 --- /dev/null +++ b/resources/views/bo/pages/brands/edit.blade.php @@ -0,0 +1,52 @@ +@extends('bo.layout', ['brParam' => $brandModel]) + +@section('title', __('Édition d\'un :model', ['model' => trans_choice('models.classes.brand', 1)])) +@section('description', __('Édition d\'un :model déjà existant', ['model' => trans_choice('models.classes.brand', 1)])) +@section('breadcrumb', request()->route()->getName()) + +@section('content') +
+
+ + + + @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $brandModel]) +
+
+
+ @can('duplicate', $brandModel) + + + + @endcan + + @can('delete', $brandModel) + @csrf + @method('DELETE') + + @endcan +
+
+
+
+ @csrf + @method('PUT') + @include('bo.pages.brands.form') + @include('bo.partials.script-button-clone') +
+
+ +
+
+
+@endsection diff --git a/resources/views/bo/pages/brands/form.blade.php b/resources/views/bo/pages/brands/form.blade.php new file mode 100644 index 0000000..ed73c4c --- /dev/null +++ b/resources/views/bo/pages/brands/form.blade.php @@ -0,0 +1,207 @@ +
+ {{-- NAME --}} +
+
+ {{ Str::ucfirst(__('validation.attributes.name')) }} +
+
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'brandName', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.attributes.name')), + 'inter' => __('validation.custom.of_the.female'), + 'model' => trans_choice('models.classes.brand', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'name', + 'required' => true, + 'model' => $brandModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.attributes.name'), + 'inter' => __('validation.custom.of_the.female'), + 'model' => trans_choice('models.classes.brand', 1), + ]), + 'min' => 3, + 'max' => 100, + ]), + 'placeholder' => __('Un nom clair et concis'), + ]) +
+
+
+
+ {{-- LINK --}} +
+
+ {{ Str::ucfirst(__('validation.attributes.link')) }} +
+
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'brandLink', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.attributes.link')), + 'inter' => __('validation.custom.of_the.female'), + 'model' => trans_choice('models.classes.brand', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'link', + 'required' => false, + 'model' => $brandModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.attributes.link'), + 'inter' => __('validation.custom.of_the.female'), + 'model' => trans_choice('models.classes.brand', 1), + ]), + 'min' => 3, + 'max' => 100, + ]), + 'placeholder' => __('https://www.example.com'), + ]) +
+
+
+
+ {{-- NETWORK_ID --}} +
+
+ {{ Str::ucfirst(trans_choice('models.classes.aisle', 1)) }} +
+
+ @include('bo.partials.inputs.bigint-foreign-id', [ + 'id' => 'brandNetwork', + 'label' => __(':field du :model', [ + 'field' => str(trans_choice('models.classes.aisle', '1'))->ucFirst(), + 'model' => __('models.classes.brand'), + ]), + 'name' => 'aisle_id', + 'required' => true, + 'model' => $brandModel ?? null, + 'associatedModels' => $aisleModels ?? null, + 'foreignLabel' => 'name', + 'helper' => __('crud.minicrud.enum_select', [ + 'enum' => trans_choice('models.classes.aisle', '1'), + ]), + ]) +
+
+
+
+ {{-- PUBLISHED --}} +
+
+ {{ Str::ucfirst(__('crud.minicrud.visibility_label')) }} +
+
+ @include('bo.partials.inputs.tinyint-boolean', [ + 'id' => 'brandPublished', + 'name' => 'published', + 'model' => $brandModel ?? null, + 'label' => __('crud.minicrud.visibility_label'), + 'helper' => __('crud.minicrud.visibility_helper'), + ]) +
+
+
+
+ {{-- TEXT --}} +
+
+ {{ Str::ucfirst(__('validation.attributes.content')) }} +
+
+ @include('bo.partials.inputs.text-html', [ + 'id' => 'brandText', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.custom.first_content')), + 'inter' => __('validation.custom.of_the.female'), + 'model' => trans_choice('models.classes.brand', 1), + ]), + 'name' => 'text', + 'model' => $brandModel ?? null, + 'required' => true, + 'helper' => __('crud.minicrud.text_warning'), + ]) +
+
+
+
+ {{-- PICTURE --}} +
+
+ {{ Str::ucfirst(__('validation.custom.picture')) }} +
+ {{-- PICTURE_ALT --}} +
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'brandPictureAlt', + 'label' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => Str::ucfirst(__('validation.custom.alt_attribute')), + 'inter1' => __('validation.custom.of_the.female'), + 'field2' => __('validation.custom.picture'), + 'inter2' => __('validation.custom.of_the.female'), + 'model' => trans_choice('models.classes.brand', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'picture_alt', + 'required' => true, + 'model' => $brandModel ?? null, + 'helper' => __('crud.minicrud.picture_alt_helper'), + 'placeholder' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => Str::ucfirst(__('validation.custom.alt_attribute')), + 'inter1' => __('validation.custom.of_the.female'), + 'field2' => __('validation.custom.picture'), + 'inter2' => __('validation.custom.of_the.female'), + 'model' => trans_choice('models.classes.brand', 1), + ]), + ]) +
+ {{-- PICTURE_TITLE --}} +
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'brandPictureTitle', + 'label' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => Str::ucfirst(__('validation.custom.title_attribute')), + 'inter1' => __('validation.custom.of_the.female'), + 'field2' => __('validation.custom.picture'), + 'inter2' => __('validation.custom.of_the.female'), + 'model' => trans_choice('models.classes.brand', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'picture_title', + 'required' => true, + 'model' => $brandModel ?? null, + 'helper' => __('crud.minicrud.picture_title_helper'), + 'placeholder' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => Str::ucfirst(__('validation.custom.title_attribute')), + 'inter1' => __('validation.custom.of_the.female'), + 'field2' => __('validation.custom.picture'), + 'inter2' => __('validation.custom.of_the.female'), + 'model' => trans_choice('models.classes.brand', 1), + ]), + ]) +
+ {{-- PICTURE --}} +
+ @include('bo.partials.inputs.varchar-picture', [ + 'id' => 'brandPicture', + 'name' => 'picture', + 'width' => 250, + 'height' => 250, + 'model' => $brandModel ?? null, + 'helper' => __('crud.minicrud.dimensions_must_be', [ + 'widthpx' => '250px', + 'heightpx' => '250px', + ]), + 'required' => true, + 'preview' => true, + ]) +
+
+
+
+
+

{{ __('validation.custom.required_fields') }}

+
+
diff --git a/resources/views/bo/pages/brands/index-list.blade.php b/resources/views/bo/pages/brands/index-list.blade.php new file mode 100644 index 0000000..038f99e --- /dev/null +++ b/resources/views/bo/pages/brands/index-list.blade.php @@ -0,0 +1,66 @@ + + @if (count($brandModels) > 0) + + @include('bo.modules.table-col-sorter', [ + 'cols' => [ + 'name' => Str::ucfirst(__('validation.attributes.name')), + 'aisle_id' => Str::ucfirst(trans_choice('models.classes.aisle', 1)), + 'published' => Str::ucfirst(__('validation.custom.activated')), + 'updated_at' => Str::ucfirst(__('validation.attributes.updated_at')), + ], + ]) + + + @foreach ($brandModels as $brandModel) + + + + @include('bo.modules.change-published-status', [ + 'routeName' => 'brands', + 'model' => $brandModel, + ]) + + + + @endforeach + + @else + + + + @endif +
{{ $brandModel->name }}{{ $brandModel->aisle->name }} + {{ $brandModel->updated_at->isoFormat('LLLL') }} + + @canAny(['delete', 'duplicate', 'update'], $brandModel) +
+ @can('duplicate', $brandModel) + + + + @endcan + @can('update', $brandModel) + + + + @endcan + @can('delete', $brandModel) + @csrf + @method('DELETE') + + @endcan +
+ @else + + + + @endcan +
{{ trans_choice('crud.other.no_model_found', 1, ['model' => trans_choice('models.classes.brand', 1)]) }} +
diff --git a/resources/views/bo/pages/brands/index.blade.php b/resources/views/bo/pages/brands/index.blade.php new file mode 100644 index 0000000..6f14745 --- /dev/null +++ b/resources/views/bo/pages/brands/index.blade.php @@ -0,0 +1,21 @@ +@extends('bo.layout') + +@section('title', Str::ucfirst(trans_choice('models.classes.brand', 1))) +@section('description', __('Liste de tous les :model', ['model' => trans_choice('models.classes.brand', '*')])) + +@section('content') +
+ @include('modules.breadcrumbs.breadcrumb-body') + @can('create', \App\Models\Brand::class) + + + + @endcan +
+ @include('bo.modules.search-bar') +
+ @include('bo.pages.brands.index-list') +
+ {{ $brandModels->links() }} +@endsection diff --git a/resources/views/bo/pages/networks/create.blade.php b/resources/views/bo/pages/networks/create.blade.php new file mode 100644 index 0000000..c92d27d --- /dev/null +++ b/resources/views/bo/pages/networks/create.blade.php @@ -0,0 +1,36 @@ +@extends('bo.layout', ['brParam' => $networkModel]) + +@section('title', __('Création d\'un :model', ['model' => trans_choice('models.classes.network', 1)])) +@section('description', __('Création d\'un tout nouveau :model', ['model' => trans_choice('models.classes.network', 1)])) +@section('breadcrumb', request()->route()->getName()) + +@section('content') +
+
+ + + + @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $networkModel]) +
+
+ +
+
+
+ @csrf + @include('bo.pages.networks.form') + @include('bo.partials.script-button-clone') +
+
+ +
+
+
+@endsection diff --git a/resources/views/bo/pages/networks/edit.blade.php b/resources/views/bo/pages/networks/edit.blade.php new file mode 100644 index 0000000..b37fd2a --- /dev/null +++ b/resources/views/bo/pages/networks/edit.blade.php @@ -0,0 +1,52 @@ +@extends('bo.layout', ['brParam' => $networkModel]) + +@section('title', __('Édition d\'un :model', ['model' => trans_choice('models.classes.network', 1)])) +@section('description', __('Édition d\'un :model déjà existant', ['model' => trans_choice('models.classes.network', 1)])) +@section('breadcrumb', request()->route()->getName()) + +@section('content') +
+
+ + + + @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $networkModel]) +
+
+
+ @can('duplicate', $networkModel) + + + + @endcan + + @can('delete', $networkModel) + @csrf + @method('DELETE') + + @endcan +
+
+
+
+ @csrf + @method('PUT') + @include('bo.pages.networks.form') + @include('bo.partials.script-button-clone') +
+
+ +
+
+
+@endsection diff --git a/resources/views/bo/pages/networks/form.blade.php b/resources/views/bo/pages/networks/form.blade.php new file mode 100644 index 0000000..9877f3f --- /dev/null +++ b/resources/views/bo/pages/networks/form.blade.php @@ -0,0 +1,258 @@ +
+ {{-- SEO --}} +
+
+ {{ Str::ucfirst(__('validation.custom.seo')) }} +
+ {{-- SEO_TITLE --}} +
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'networkSeoTitle', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.custom.seo_title')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'seo_title', + 'required' => true, + 'model' => $networkModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.custom.seo_title'), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + 'min' => 45, + 'max' => 70, + ]), + 'placeholder' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.custom.seo_title')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + ]) +
+ {{-- SEO_DESCRIPTION --}} +
+ @include('bo.partials.inputs.varchar-multiline', [ + 'id' => 'networkSeoDescription', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.custom.seo_description')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'seo_description', + 'required' => true, + 'model' => $networkModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.custom.seo_description'), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + 'min' => 50, + 'max' => 160, + ]), + 'placeholder' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.custom.seo_description')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + ]) +
+
+
+
+ {{-- NAME --}} +
+
+ {{ Str::ucfirst(__('validation.attributes.name')) }} +
+
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'networkName', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.attributes.name')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'name', + 'required' => true, + 'model' => $networkModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.attributes.name'), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + 'min' => 3, + 'max' => 100, + ]), + 'placeholder' => __('Un nom clair et concis'), + ]) +
+
+
+
+ {{-- PUBLISHED --}} +
+
+ {{ Str::ucfirst(__('crud.minicrud.visibility_label')) }} +
+
+ @include('bo.partials.inputs.tinyint-boolean', [ + 'id' => 'networkPublished', + 'name' => 'published', + 'model' => $networkModel ?? null, + 'label' => __('crud.minicrud.visibility_label'), + 'helper' => __('crud.minicrud.visibility_helper'), + ]) +
+
+
+
+ {{-- TITLE --}} +
+
+ {{ Str::ucfirst(__('validation.attributes.title')) }} +
+
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'networkTitle', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.attributes.title')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'title', + 'required' => true, + 'model' => $networkModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.attributes.title'), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + 'min' => 3, + 'max' => 100, + ]), + 'placeholder' => __('Un nom clair et concis'), + ]) +
+
+
+
+ {{-- SUBTITLE --}} +
+
+ {{ Str::ucfirst(__('validation.custom.subtitle')) }} +
+
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'networkSubtitle', + 'label' => __(':field :inter:model', [ + 'field' => Str::ucfirst(__('validation.custom.subtitle')), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'subtitle', + 'required' => true, + 'model' => $networkModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.custom.subtitle'), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + 'min' => 3, + 'max' => 100, + ]), + 'placeholder' => __('Un sous-titre clair et concis'), + ]) +
+
+
+
+ {{-- PICTURE --}} +
+
+ {{ Str::ucfirst(__('validation.custom.picture')) }} +
+ {{-- PICTURE_ALT --}} +
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'networkPictureAlt', + 'label' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => Str::ucfirst(__('validation.custom.alt_attribute')), + 'inter1' => __('validation.custom.of_the.male'), + 'field2' => __('validation.custom.picture'), + 'inter2' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'picture_alt', + 'required' => true, + 'model' => $networkModel ?? null, + 'helper' => __('crud.minicrud.picture_alt_helper'), + 'placeholder' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => Str::ucfirst(__('validation.custom.alt_attribute')), + 'inter1' => __('validation.custom.of_the.male'), + 'field2' => __('validation.custom.picture'), + 'inter2' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + ]) +
+ {{-- PICTURE_TITLE --}} +
+ @include('bo.partials.inputs.varchar', [ + 'id' => 'networkPictureTitle', + 'label' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => Str::ucfirst(__('validation.custom.title_attribute')), + 'inter1' => __('validation.custom.of_the.male'), + 'field2' => __('validation.custom.picture'), + 'inter2' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'picture_title', + 'required' => true, + 'model' => $networkModel ?? null, + 'helper' => __('crud.minicrud.picture_title_helper'), + 'placeholder' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => Str::ucfirst(__('validation.custom.title_attribute')), + 'inter1' => __('validation.custom.of_the.male'), + 'field2' => __('validation.custom.picture'), + 'inter2' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.network', 1), + ]), + ]) +
+ {{-- PICTURE --}} +
+ @include('bo.partials.inputs.varchar-picture', [ + 'id' => 'networkPicture', + 'name' => 'picture', + 'width' => 1920, + 'height' => 1080, + 'model' => $networkModel ?? null, + 'helper' => __('crud.minicrud.dimensions_must_be', [ + 'widthpx' => '1920px', + 'heightpx' => '1080px', + ]), + 'required' => true, + 'preview' => true, + ]) +
+
+
+
+
+

{{ __('validation.custom.required_fields') }}

+
+
diff --git a/resources/views/bo/pages/networks/index-list.blade.php b/resources/views/bo/pages/networks/index-list.blade.php new file mode 100644 index 0000000..dc21dd9 --- /dev/null +++ b/resources/views/bo/pages/networks/index-list.blade.php @@ -0,0 +1,75 @@ + + @if (count($networkModels) > 0) + + @include('bo.modules.table-col-sorter', [ + 'cols' => [ + 'name' => Str::ucfirst(__('validation.attributes.name')), + 'order' => Str::ucfirst(__('validation.custom.order')), + 'published' => Str::ucfirst(__('validation.custom.activated')), + 'updated_at' => Str::ucfirst(__('validation.attributes.updated_at')), + ], + ]) + + + @foreach ($networkModels as $networkModel) + + + @php + $routeName = request()->route()->getName(); + @endphp + @if (empty(request()->search) && Session::get("$routeName.sort_col") === 'order') + @include('bo.modules.change-model-order', [ + 'routeName' => 'networks', + 'model' => $networkModel, + 'models' => $networkModels, + ]) + @endif + @include('bo.modules.change-published-status', [ + 'routeName' => 'networks', + 'model' => $networkModel, + ]) + + + + @endforeach + + @else + + + + @endif +
{{ $networkModel->name }} + {{ $networkModel->updated_at->isoFormat('LLLL') }} + + @canAny(['delete', 'duplicate', 'update'], $networkModel) +
+ @can('duplicate', $networkModel) + + + + @endcan + @can('update', $networkModel) + + + + @endcan + @can('delete', $networkModel) + @csrf + @method('DELETE') + + @endcan +
+ @else + + + + @endcan +
{{ trans_choice('crud.other.no_model_found', 1, ['model' => trans_choice('models.classes.network', 1)]) }} +
diff --git a/resources/views/bo/pages/networks/index.blade.php b/resources/views/bo/pages/networks/index.blade.php new file mode 100644 index 0000000..d50af54 --- /dev/null +++ b/resources/views/bo/pages/networks/index.blade.php @@ -0,0 +1,21 @@ +@extends('bo.layout') + +@section('title', Str::ucfirst(trans_choice('models.classes.network', 1))) +@section('description', __('Liste de tous les :model', ['model' => trans_choice('models.classes.network', '*')])) + +@section('content') +
+ @include('modules.breadcrumbs.breadcrumb-body') + @can('create', \App\Models\Network::class) + + + + @endcan +
+ @include('bo.modules.search-bar') +
+ @include('bo.pages.networks.index-list') +
+ {{ $networkModels->links() }} +@endsection diff --git a/resources/views/bo/pages/sections/form.blade.php b/resources/views/bo/pages/sections/form.blade.php index 4b57bea..dfd4de8 100644 --- a/resources/views/bo/pages/sections/form.blade.php +++ b/resources/views/bo/pages/sections/form.blade.php @@ -76,7 +76,12 @@ - @if ($sectionModel->section !== \App\Enums\Sections\PageSectionenum::hire_grow) + @if ( + !in_array($sectionModel->section, [ + \App\Enums\Sections\PageSectionenum::hire_grow, + \App\Enums\Sections\PageSectionenum::produce_networks, + ]) + ) {{-- CONTENT --}}
diff --git a/resources/views/fo/pages/networks/show.blade.php b/resources/views/fo/pages/networks/show.blade.php new file mode 100644 index 0000000..3731769 --- /dev/null +++ b/resources/views/fo/pages/networks/show.blade.php @@ -0,0 +1,107 @@ +@extends('fo.layout') + +@section('title', __($networkModel->seo_title)) +@section('description', __($networkModel->seo_description)) +@section('breadcrumb', request()->route()->getName()) + +@section('banner') + @include('fo.layouts.banner', [ + 'h1' => __($networkModel->name), + 'asset' => asset($networkModel->picture), + 'title' => __($networkModel->picture_title), + 'alt' => __($networkModel->picture_alt), + 'displayArrow' => true, + 'buttonLink' => '#network', + ]) +@endsection + +@section('content') +
+
+
+

{{ $networkModel->title }}

+
+
+ + {{-- AISLES --}} + @foreach ($networkModel->aisles as $aisleModel) +
+
+
+

{{ $aisleModel->title }}

+ {{ $aisleModel->picture_alt }} +
+
+
{!! clean($aisleModel->text) !!}
+
+
+
+
+ @foreach($aisleModel->brands as $brandModel) +
+
$loop->even + ])> + + {{ $brandModel->picture_alt }} + +
+
$loop->even + ])> +

{{ $brandModel->name }} :

+

{!! clean($brandModel->text) !!}

+
+
+ @endforeach +
+
+
+ @endforeach +
+ {{-- SLIDER --}} +
+
+
+
+

{{ __('Découvrez d\'autres réseaux') }}

+
+
+
+
+
+ @foreach ($networkModels as $networkModel) +
+ @include('fo.partials.networks.slider-card', [ + 'networkModel' => $networkModel + ]) +
+ @endforeach +
+
+ + +
+
+
+
+ +
+@endsection diff --git a/resources/views/fo/pages/produce/show.blade.php b/resources/views/fo/pages/produce/show.blade.php index 81b23a5..e505b67 100644 --- a/resources/views/fo/pages/produce/show.blade.php +++ b/resources/views/fo/pages/produce/show.blade.php @@ -132,6 +132,39 @@ --}} + {{-- NETWORKS --}} +
+
+
+
+

{{ $networkSection->h2_title }}

+
+
+
+
+ @foreach ($networkModels as $networkModel) +
+ @include('fo.partials.networks.slider-card', [ + 'networkModel' => $networkModel + ]) +
+ @endforeach +
+
+ + +
+
+
+
+
+
{{-- QUALITY --}} {{--
--}}
diff --git a/resources/views/fo/partials/networks/slider-card.blade.php b/resources/views/fo/partials/networks/slider-card.blade.php new file mode 100644 index 0000000..30b5862 --- /dev/null +++ b/resources/views/fo/partials/networks/slider-card.blade.php @@ -0,0 +1,8 @@ + diff --git a/routes/breadcrumbs-bo.php b/routes/breadcrumbs-bo.php index e95ce9b..d7e5411 100644 --- a/routes/breadcrumbs-bo.php +++ b/routes/breadcrumbs-bo.php @@ -1,9 +1,12 @@ parent('bo.sections.index', ''); $trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.sections.edit', $section)); }); + +// * NETWORKS +Breadcrumbs::for('bo.networks.index', function (Generator $trail) { + $trail->push( + Str::of(trans_choice('models.classes.network', \INF))->ucfirst(), + route('bo.networks.index', ['sort_col' => 'updated_at', 'sort_way' => 'desc']) + ); +}); +Breadcrumbs::for('bo.networks.create', function (Generator $trail) { + $trail->parent('bo.networks.index'); + $trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.networks.create')); +}); +Breadcrumbs::for('bo.networks.edit', function (Generator $trail, Network $network) { + $trail->parent('bo.networks.index'); + $trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.networks.edit', $network)); +}); +Breadcrumbs::for('bo.networks.duplicate', function (Generator $trail) { + $trail->parent('bo.networks.index'); + $trail->push(Str::ucfirst(trans('crud.actions.duplicate'))); +}); + +// * AISLES +Breadcrumbs::for('bo.aisles.index', function (Generator $trail) { + $trail->push( + Str::of(trans('models.classes.aisle'))->plural()->ucfirst(), + route('bo.aisles.index', ['sort_col' => 'updated_at', 'sort_way' => 'desc']) + ); +}); +Breadcrumbs::for('bo.aisles.create', function (Generator $trail) { + $trail->parent('bo.aisles.index'); + $trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.aisles.create')); +}); +Breadcrumbs::for('bo.aisles.edit', function (Generator $trail, Aisle $aisle) { + $trail->parent('bo.aisles.index'); + $trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.aisles.edit', $aisle)); +}); +Breadcrumbs::for('bo.aisles.duplicate', function (Generator $trail) { + $trail->parent('bo.aisles.index'); + $trail->push(Str::ucfirst(trans('crud.actions.duplicate'))); +}); + +// * BRANDS +Breadcrumbs::for('bo.brands.index', function (Generator $trail) { + $trail->push( + Str::of(trans('models.classes.brand'))->plural()->ucfirst(), + route('bo.brands.index', ['sort_col' => 'updated_at', 'sort_way' => 'desc']) + ); +}); +Breadcrumbs::for('bo.brands.create', function (Generator $trail) { + $trail->parent('bo.brands.index'); + $trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.brands.create')); +}); +Breadcrumbs::for('bo.brands.edit', function (Generator $trail, Brand $brand) { + $trail->parent('bo.brands.index'); + $trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.brands.edit', $brand)); +}); +Breadcrumbs::for('bo.brands.duplicate', function (Generator $trail) { + $trail->parent('bo.brands.index'); + $trail->push(Str::ucfirst(trans('crud.actions.duplicate'))); +}); diff --git a/routes/web-bo.php b/routes/web-bo.php index 27f01c0..eda9c71 100644 --- a/routes/web-bo.php +++ b/routes/web-bo.php @@ -1,10 +1,13 @@ prefix('bo') 'duplicate' ])->name('testimonials.duplicate'); + // * NETWORKS + Route::resource('networks', NetworkController::class)->except('show') + ->withoutMiddleware('htmlsanitize') + ->middleware('htmlsanitize:content'); + Route::get('/networks/{network}/duplicate', [ + NetworkController::class, + 'duplicate' + ])->name('networks.duplicate'); + Route::patch('/networks/{network}/change-order/{direction}', [ + NetworkController::class, + 'changeOrder' + ])->where('direction', 'up|down')->name('networks.change-order'); + Route::patch('/networks/{network}/change-published', [ + NetworkController::class, + 'changePublished' + ])->name('networks.change-published'); + + // * AISLES + Route::resource('aisles', AisleController::class)->except('show') + ->withoutMiddleware('htmlsanitize') + ->middleware('htmlsanitize:content'); + Route::get('/aisles/{aisle}/duplicate', [ + AisleController::class, + 'duplicate' + ])->name('aisles.duplicate'); + Route::patch('/aisles/{aisle}/change-published', [ + AisleController::class, + 'changePublished' + ])->name('aisles.change-published'); + + // * BRANDS + Route::resource('brands', BrandController::class)->except('show') + ->withoutMiddleware('htmlsanitize') + ->middleware('htmlsanitize:content'); + Route::get('/brands/{brand}/duplicate', [ + BrandController::class, + 'duplicate' + ])->name('brands.duplicate'); + Route::patch('/brands/{brand}/change-published', [ + BrandController::class, + 'changePublished' + ])->name('brands.change-published'); + Route::middleware('can:isConceptor')->group(function () { // * ACTIVITY LOGS. Route::resource('activity_logs', ActivityLogController::class)->only(['index', 'show']); diff --git a/routes/web-fo.php b/routes/web-fo.php index db425d3..59aad33 100644 --- a/routes/web-fo.php +++ b/routes/web-fo.php @@ -1,5 +1,6 @@ name('fo.')->group(function () { Route::get('/on-bouge/{publishedNews:slug}', [NewsController::class, 'show'])->name('news.show'); Route::get('/on-embauche', [StaticPageController::class, 'hire'])->name('hire.show'); Route::get('/on-produit', [StaticPageController::class, 'produce'])->name('produce.show'); + Route::get('/nos-reseaux/{publishedNetwork:slug}', [NetworkController::class, 'show'])->name('networks.show'); // * RGPD Route::get('/mentions-legales', [StaticPageController::class, 'legals'])->name('legals');