From 8caad34202fd2648d79d14c3b7631435a268980d Mon Sep 17 00:00:00 2001 From: Alexis Gousseau Date: Fri, 12 Jul 2024 12:51:54 +0200 Subject: [PATCH] feat: :sparkles: add offers CRUD --- app/Http/Controllers/Bo/OfferController.php | 176 ++++++++++++ .../Requests/Bo/Offers/StoreOfferRequest.php | 80 ++++++ .../Requests/Bo/Offers/UpdateOfferRequest.php | 18 ++ app/Models/Offer.php | 76 +++++ app/Policies/OfferPolicy.php | 86 ++++++ app/Providers/RouteServiceProvider.php | 2 + database/factories/OfferFactory.php | 27 ++ .../assets/offers/default-picture-300x200.png | Bin 0 -> 2538 bytes ...153957_add_minicrud_static_pages_table.php | 2 +- .../2024_07_12_115553_create_offers_table.php | 47 ++++ database/seeders/Development/OfferSeeder.php | 29 ++ resources/lang/en/models.php | 1 + resources/lang/en/validation.php | 5 +- resources/lang/fr/models.php | 1 + resources/lang/fr/validation.php | 5 +- resources/ts/bo/utilities/fontawesome.ts | 4 +- resources/views/bo/layouts/sidemenu.blade.php | 21 +- .../views/bo/pages/offers/create.blade.php | 36 +++ .../views/bo/pages/offers/edit.blade.php | 60 ++++ .../views/bo/pages/offers/form.blade.php | 259 ++++++++++++++++++ .../bo/pages/offers/index-list.blade.php | 84 ++++++ .../views/bo/pages/offers/index.blade.php | 21 ++ .../views/bo/pages/offers/show.blade.php | 112 ++++++++ routes/breadcrumbs-bo.php | 25 ++ routes/web-bo.php | 15 + 25 files changed, 1186 insertions(+), 6 deletions(-) create mode 100644 app/Http/Controllers/Bo/OfferController.php create mode 100644 app/Http/Requests/Bo/Offers/StoreOfferRequest.php create mode 100644 app/Http/Requests/Bo/Offers/UpdateOfferRequest.php create mode 100644 app/Models/Offer.php create mode 100644 app/Policies/OfferPolicy.php create mode 100644 database/factories/OfferFactory.php create mode 100644 database/factories/assets/offers/default-picture-300x200.png create mode 100644 database/migrations/2024_07_12_115553_create_offers_table.php create mode 100644 database/seeders/Development/OfferSeeder.php create mode 100644 resources/views/bo/pages/offers/create.blade.php create mode 100644 resources/views/bo/pages/offers/edit.blade.php create mode 100644 resources/views/bo/pages/offers/form.blade.php create mode 100644 resources/views/bo/pages/offers/index-list.blade.php create mode 100644 resources/views/bo/pages/offers/index.blade.php create mode 100644 resources/views/bo/pages/offers/show.blade.php diff --git a/app/Http/Controllers/Bo/OfferController.php b/app/Http/Controllers/Bo/OfferController.php new file mode 100644 index 00000000..3f14fb8b --- /dev/null +++ b/app/Http/Controllers/Bo/OfferController.php @@ -0,0 +1,176 @@ +authorizeResource(Offer::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 $offerModels */ + $offerModels = Offer::query(); + + /** @var string $search Search field */ + $search = $request->search; + if ($search) { + $this->searchQuery( + $offerModels, + $search, + null, + 'label', + ); + } + $searchFields = \implode(', ', [ + trans('validation.custom.label'), + ]); + + /** Sort columns with a query */ + $this->sortQuery($offerModels); + + /** Custom pagination */ + $offerModels = $this->paginate($offerModels); + + return view('bo.pages.offers.index', compact('offerModels', 'search', 'searchFields')); + } + + /** + * Show the specified resource. + * + * @param \App\Models\Offer $offer + * @return \Illuminate\Contracts\View\View + */ + public function show(Offer $offer): \Illuminate\Contracts\View\View + { + return view('bo.pages.offers.show', ['offerModel' => $offer]); + } + + /** + * Show the form for creating a new resource. + * + * @param \App\Models\Offer $offer + * @return \Illuminate\Contracts\View\View + */ + public function create(Offer $offer): \Illuminate\Contracts\View\View + { + return view('bo.pages.offers.create', ['offerModel' => $offer]); + } + + /** + * Store a newly created resource in storage. + * + * @param \App\Http\Requests\Bo\Offers\StoreOfferRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(StoreOfferRequest $request): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($request) { + $offer = new Offer(); + $offer->fill($request->validated()); + + if ($offer->saveOrFail()) { + return redirect()->route('bo.offers.edit', $offer) + ->with('success', trans('crud.messages.has_been_created', [ + 'model' => Str::ucfirst(trans('models.classes.offer')) + ])); + } + return redirect()->back() + ->with('error', trans('crud.messages.cannot_be_created', [ + 'model' => Str::ucfirst(trans('models.classes.offer')) + ])); + }); + } + + /** + * Show the form for editing the specified resource. + * + * @param \App\Models\Offer $offer + * @return \Illuminate\Contracts\View\View + */ + public function edit(Offer $offer): \Illuminate\Contracts\View\View + { + return view('bo.pages.offers.edit', ['offerModel' => $offer]); + } + + /** + * Update the specified resource in storage. + * + * @param \App\Http\Requests\Bo\Offers\UpdateOfferRequest $request + * @param \App\Models\Offer $offer + * @return \Illuminate\Http\RedirectResponse + */ + public function update(UpdateOfferRequest $request, Offer $offer): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($request, $offer) { + $offer->fill($request->validated()); + + if ($offer->saveOrFail()) { + return redirect()->route('bo.offers.edit', $offer) + ->with('success', trans('crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans('models.classes.offer')) + ])); + } + return redirect()->route('bo.offers.edit', $offer) + ->with('error', trans('crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans('models.classes.offer')) + ])); + }); + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Offer $offer + * @return \Illuminate\Http\RedirectResponse + */ + public function destroy(Offer $offer): \Illuminate\Http\RedirectResponse + { + /** @var array */ + $previousQueries = []; + \parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries); + if ($offer->deleteOrFail()) { + return redirect()->route('bo.offers.index', $previousQueries) + ->with('success', trans('crud.messages.has_been_deleted', [ + 'model' => Str::ucfirst(trans('models.classes.offer')) + ])); + } + return redirect()->back() + ->with('error', trans('crud.messages.cannot_be_deleted', [ + 'model' => Str::ucfirst(trans('models.classes.offer')) + ])); + } + + /** + * Duplicate the specified resource. + * + * @param \App\Models\Offer $offer + * @return \Illuminate\Contracts\View\View + */ + public function duplicate(Offer $offer): \Illuminate\Contracts\View\View + { + return $this->create($offer->replicate()); + } +} diff --git a/app/Http/Requests/Bo/Offers/StoreOfferRequest.php b/app/Http/Requests/Bo/Offers/StoreOfferRequest.php new file mode 100644 index 00000000..c4b38883 --- /dev/null +++ b/app/Http/Requests/Bo/Offers/StoreOfferRequest.php @@ -0,0 +1,80 @@ +merge([ + 'published' => $this->boolean('published') + ]); + $this->mergePicture('picture'); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + $rules = [ + 'title' => 'required|string|between:3,255', + 'location' => 'required|string|between:3,255', + 'contract' => 'required|string|between:3,255', + 'function' => 'required|string|between:3,255', + 'picture_alt' => 'required_with:picture|nullable|between:3,255', + 'picture_title' => 'required_with:picture|nullable|between:3,255', + 'description' => ['required', 'string', new WysiwygLength()], + 'published' => 'required|boolean', + ]; + return \array_merge( + $rules, + $this->pictureRules('picture', 300, 200, 300, 200, true), + ); + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'title' => trans('validation.attributes.title'), + 'location' => trans('validation.custom.location'), + 'contract' => trans('validation.custom.contract'), + 'function' => trans('validation.custom.function'), + 'picture' => trans('validation.attributes.image'), + 'picture_alt' => trans('validation.custom.attribute_alt'), + 'picture_title' => trans('validation.custom.attribute_title'), + 'description' => trans('validation.attributes.description'), + 'published' => trans('validation.attributes.published') + ]; + } +} diff --git a/app/Http/Requests/Bo/Offers/UpdateOfferRequest.php b/app/Http/Requests/Bo/Offers/UpdateOfferRequest.php new file mode 100644 index 00000000..0c3fd731 --- /dev/null +++ b/app/Http/Requests/Bo/Offers/UpdateOfferRequest.php @@ -0,0 +1,18 @@ +route('offer')); + } +} diff --git a/app/Models/Offer.php b/app/Models/Offer.php new file mode 100644 index 00000000..758d50d0 --- /dev/null +++ b/app/Models/Offer.php @@ -0,0 +1,76 @@ + + */ + protected $fillable = [ + 'title', + 'location', + 'contract', + 'function', + 'picture', + 'picture_alt', + 'picture_title', + 'description', + 'published', + ]; + + /** + * 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 $offer) { + $offer->picture = FileStorageHelper::storeFile($offer, $offer->picture, true); + }); + static::updating(function (self $offer) { + $offer->picture = FileStorageHelper::storeFile($offer, $offer->picture, true); + }); + static::updated(function (self $offer) { + FileStorageHelper::removeOldFile($offer, 'picture'); + }); + static::deleted(function (self $offer) { + FileStorageHelper::removeFile($offer, 'picture'); + }); + } +} diff --git a/app/Policies/OfferPolicy.php b/app/Policies/OfferPolicy.php new file mode 100644 index 00000000..29969062 --- /dev/null +++ b/app/Policies/OfferPolicy.php @@ -0,0 +1,86 @@ + + */ + public function definition() + { + return [ + 'title' => fake()->words(3, true), + 'location' => fake()->words(5, true), + 'contract' => fake()->words(5, true), + 'function' => fake()->words(5, true), + 'picture_alt' => fake()->words(5, true), + 'picture_title' => fake()->words(5, true), + 'description' => fake()->sentence(), + 'published' => fake()->boolean(75), + ]; + } +} diff --git a/database/factories/assets/offers/default-picture-300x200.png b/database/factories/assets/offers/default-picture-300x200.png new file mode 100644 index 0000000000000000000000000000000000000000..849a9826963e27d5debd23eaa631072d499c9287 GIT binary patch literal 2538 zcmeH}|2xy`AIGIrB9*?-B3r4G=1Wvg44d*nSw096F+_A?BwxcUq1BQ3oDa))&RVl0 zUzQj#vrA4#S|iDZ*~VF7*36eJGmZ29oa_4h^trCjUvPf8@B6y$>%Ono>v=yP_w{_} z?RkEOj-if*hQ1_k@jg9TQ#>Kn+>wcZQUtr{&UHZx4iZJr{FV4>}pqJDm#rnyxj=+=JhHGnU zI(HBHs2Zw9;h6mVCb8mW6EF4*l}e@an{%d8Afj#5LyxYPDJ>WY$zy-$%rQ+=$+9 zrf-uf9N};RYZ`~c5eF-3JDQuDJ3Cb+qK#}f(xpcK5Uw%C%pe~fACGe;_IRmEU+E5{ zZ%wd!rc!5h1cNQTq0^mljs7vTAd;6c1X4JE6$$N2@bvO>s2T{xW8=A8Zgg~X#CAU< z9Dx8E8^iFYmF^JVQd;2Pv7P5HH~P1ruR`?;`<7QTEeuZUl6X=S3MJt4q2z(VLEO&4 zpvK37Y>#1!eLCW#aT_*JWl-bSg@wt^`LVGvfuLHpC}St{Z_=JYtgNh}TgGnK80v0a zQ)jh|HE<zP>&aAIi%C*#KgO(mY%TpHf$G@W;&hT+WNG3E`@F%h7}2~()k>7?6D%C z1kYndfRNUYE*xE26!l$e{uLJz8oI&W7lr1Wju|@gCCOg(FN+dZ+2@7$BV`_QS~*%# z3>1|oH6!nN_}kLb(q!7O4WN=jR8e7s6>V&i=_R1iX!+R4$Yl)YDOVy6#TP=~O*TYx zo6X5d1c^I_JDacLWKaj@B<-p@>|ADfyQ zK>bjSq6U}|A3>o?3U8$8Ye;dOQ?T)4x(EUPa*GR+uo+s2?QZSchQ9p81 ztx{Z+x#u~D-}?Du2E_*n@h$l2^))1v^+K+KNtH9X&cuR(9qKfB{zp%ZT>>F7f$ zwc@!$9>Yloqi?W>C_V(Ao=IiN^RtJn?QV}RTA3T(w;~bA$E;>BV|y{6pR{4d2uH#KI0;x-Iv)Xp z4VmsbapHtd-o5oMK?Z7+ao@vX{W6QiD)Xoviv|2mKz^_01{^CoV6P3c4Ed!ytO!X) zWGW}&(?FL93G8>IvLW(RM@tJSmE=a6VuVQ&^{pyB7u@F16l14{3Pm!*U!vJN^*r^1 z5W;IK#N*{#atH*1R}EqZLK2}PJ1M#2nrrpYjbv_aj)d~s6z9szCkxAnPPkfQCvA+3 zyfURysb*aV`W5MnoSe;=o2Etmv2k%b^QQiLn!3=Whp(O=o>@G9r=2^?j~QRwYuNP; z{&k+CP2)*(a&l1AXwd!ckf@{|mrmxTmMelyUInCCjS753*I{2dk>q03X`n(|amDL{j;Z_KX zu4)(z*X>|3xiL|W;1%2T%U)hyg3GxZ>uX+C!6P@5XH{?O%h{j%`yWa=hi=GOB~}7> zIw{j*iX9E_Gk`lY_Rh9j6d*FPvYHCZuQivZX6o<6DZnYIskW^+7+%D1=%I%Itx@ga z@DW?;^z!O|$lF|w=9!wB?#3@wtZ78;!snLih#)vp+=dN9fp@5@%4s?ZCOO^Xu zgDe?deWC=6yp%O9pBKcn8x{P1H1xMNDU$p|IMYp|{m?(+3ZT>*IA|*fq&88hfa$hx z1|XWPIxeUpM~G8h)of)m@DJDdy+HG9*wj#FIxC=^2e-4kb+FHd4d6aAzi&Ahg{rQp zVXCCXKEiIy^1O@UWS(uAV&bZt;0+~sQu1H_kx0>cG4^HhwZat^!qCx!B# ArT_o{ literal 0 HcmV?d00001 diff --git a/database/migrations/2024_07_10_153957_add_minicrud_static_pages_table.php b/database/migrations/2024_07_10_153957_add_minicrud_static_pages_table.php index e62a4569..7cfb1e7b 100644 --- a/database/migrations/2024_07_10_153957_add_minicrud_static_pages_table.php +++ b/database/migrations/2024_07_10_153957_add_minicrud_static_pages_table.php @@ -26,7 +26,7 @@ return new class extends Migration $table->string('picture_alt')->nullable()->comment('Picture alt attribute.')->change(); $table->string('picture_title')->nullable()->comment('Picture title attribute.')->change(); $table->text('content')->nullable()->comment('Static page content.')->change(); - $table->mediumText('data')->nullable()->after('content')->comment('Static page content.'); + $table->mediumText('data')->nullable()->after('content')->comment('Static page data.'); }); // SAGA. diff --git a/database/migrations/2024_07_12_115553_create_offers_table.php b/database/migrations/2024_07_12_115553_create_offers_table.php new file mode 100644 index 00000000..259a58d1 --- /dev/null +++ b/database/migrations/2024_07_12_115553_create_offers_table.php @@ -0,0 +1,47 @@ +id(); + $table->string('title')->comment('Offer title.'); + $table->string('location')->comment('Offer location.'); + $table->string('contract')->comment('Offer contract.'); + $table->string('function')->comment('Offer function.'); + $table->string('picture')->comment('Offer picture.'); + $table->string('picture_alt')->comment('Picture alt attribute.'); + $table->string('picture_title')->comment('Picture title attribute.'); + $table->text('description')->comment('Offer description.'); + $table->boolean('published')->comment('Offer publish status.'); + $table->timestamps(); + }); + + // Call seeder. + Artisan::call('db:seed', [ + '--class' => OfferSeeder::class, + ]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::dropIfExists('offers'); + } +}; diff --git a/database/seeders/Development/OfferSeeder.php b/database/seeders/Development/OfferSeeder.php new file mode 100644 index 00000000..189c19c2 --- /dev/null +++ b/database/seeders/Development/OfferSeeder.php @@ -0,0 +1,29 @@ +make()->each(function (Offer $offer) { + $offer->picture = FileStorageHelper::storeFile( + $offer, + new \SplFileInfo(\resource_path( + '../database/factories/assets/offers/default-picture-300x200.png' + )) + ); + $offer->saveQuietly(); + }); + } +} diff --git a/resources/lang/en/models.php b/resources/lang/en/models.php index ff1087bd..8f05db64 100644 --- a/resources/lang/en/models.php +++ b/resources/lang/en/models.php @@ -25,5 +25,6 @@ return [ 'recipe' => 'recipe', 'product_type' => 'product type|product types', 'claim' => 'claim', + 'offer' => 'offer', ] ]; diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 126632b6..e84df2b1 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -245,11 +245,13 @@ return [ 'claim' => 'claim', 'color_block' => 'color block', 'color' => 'color', + 'contract' => 'contract', 'cooking_time' => 'cooking time (in minutes)', 'demarche' => 'demarche', 'details' => 'details', 'difficulty' => 'difficulty', 'event' => 'event', + 'function' => 'function', 'general_setting_type' => 'general setting type', 'highlight' => 'highlight', 'ingredient' => 'ingredient', @@ -258,6 +260,7 @@ return [ 'job' => 'job', 'jobs_list' => 'jobs list', 'label' => 'label', + 'location' => 'location', 'menu' => 'menu', 'model' => 'model', 'nb_guests' => 'number of reference persons for the list of ingredients', @@ -271,8 +274,8 @@ return [ 'product_id' => 'product ID', 'publication' => 'publication', 'publish_at' => 'publish at', - 'published' => 'published', 'published_at' => 'published at', + 'published' => 'published', 'route_name' => 'name of the route', 'seo_description' => 'seo description', 'seo_title' => 'seo title', diff --git a/resources/lang/fr/models.php b/resources/lang/fr/models.php index 8b609f41..4f89d1f9 100644 --- a/resources/lang/fr/models.php +++ b/resources/lang/fr/models.php @@ -25,5 +25,6 @@ return [ 'recipe' => 'recette', 'product_type' => 'type de produit|types de produit', 'claim' => 'allégations', + 'offer' => 'offre', ] ]; diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php index ac23480a..9e94f6b9 100644 --- a/resources/lang/fr/validation.php +++ b/resources/lang/fr/validation.php @@ -247,11 +247,13 @@ return [ 'claim' => 'allégation', 'color_block' => 'couleur du block', 'color' => 'couleur', + 'contract' => 'contrat', 'cooking_time' => 'temps de cuisson (en minutes)', 'demarche' => 'démarche', 'details' => 'détails', 'difficulty' => 'difficulté', 'event' => 'évènement', + 'function' => 'fonction', 'general_setting_type' => 'type de configuration', 'highlight' => 'mise en avant', 'ingredient' => 'ingrédient', @@ -260,6 +262,7 @@ return [ 'job' => 'offre', 'jobs_list' => 'liste d\'offres', 'label' => 'label', + 'location' => 'lieu', 'menu' => 'menu', 'model' => 'élément ciblé', 'nb_guests' => 'nombre de personnes de référence pour la liste des ingrédients', @@ -273,8 +276,8 @@ return [ 'product_id' => 'ID du produit', 'publication' => 'publication', 'publish_at' => 'date de publication', - 'published' => 'publié', 'published_at' => 'publié le', + 'published' => 'publié', 'route_name' => 'nom de la route', 'seo_description' => 'description SEO', 'seo_title' => 'titre SEO', diff --git a/resources/ts/bo/utilities/fontawesome.ts b/resources/ts/bo/utilities/fontawesome.ts index f9a1ca6a..f530fb43 100644 --- a/resources/ts/bo/utilities/fontawesome.ts +++ b/resources/ts/bo/utilities/fontawesome.ts @@ -75,7 +75,8 @@ import { faList, faHandPointUp, faFolderTree, - faClockRotateLeft + faClockRotateLeft, + faHandshake } from "@fortawesome/free-solid-svg-icons"; config.autoReplaceSvg = false; @@ -155,5 +156,6 @@ library.add(faList); library.add(faFolderTree); library.add(faHandPointUp); library.add(faClockRotateLeft); +library.add(faHandshake); dom.watch(); diff --git a/resources/views/bo/layouts/sidemenu.blade.php b/resources/views/bo/layouts/sidemenu.blade.php index 49921fda..7566e9bb 100644 --- a/resources/views/bo/layouts/sidemenu.blade.php +++ b/resources/views/bo/layouts/sidemenu.blade.php @@ -237,14 +237,16 @@ 'fw-bold active' => url()->current() === route('bo.static_pages.edit', $recruitmentStaticPageModel->type->value()) || request()->routeIs('bo.professions.*') - || request()->routeIs('bo.brand_advantages.*'), + || request()->routeIs('bo.brand_advantages.*') + || request()->routeIs('bo.offers.*'), ])> url()->current() === route('bo.static_pages.edit', $recruitmentStaticPageModel->type->value()) || request()->routeIs('bo.professions.*') - || request()->routeIs('bo.brand_advantages.*'), + || request()->routeIs('bo.brand_advantages.*') + || request()->routeIs('bo.offers.*'), ])>  {{ __('Recrutement') }} @@ -281,6 +283,21 @@ {{ str(__('models.classes.profession'))->ucFirst()->plural() }} + {{-- OFFERS --}} +
  • + request()->routeIs('bo.offers.*'), + ]) data-bs-toggle="tooltip" + href="{{ route('bo.offers.index') }}" + title="{{ __('crud.helpers.update_model', ['model' => __('models.classes.offer')]) }}"> + request()->routeIs('bo.offers.*'), + ])> + {{ str(__('models.classes.offer'))->ucFirst()->plural() }} + +
  • {{-- BRAND ADVANTAGES --}}
  • $offerModel]) + +@section('title', __('Création d\'une :model', ['model' => __('models.classes.offer')])) +@section('description', __('Création d\'une tout nouveau :model', ['model' => __('models.classes.offer')])) +@section('breadcrumb', request()->route()->getName()) + +@section('content') +
    +
    + + + + @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $offerModel]) +
    +
    + +
    +
    +
    + @csrf + @include('bo.pages.offers.form') + @include('bo.partials.script-button-clone') +
    +
    + +
    +
    +
    +@endsection diff --git a/resources/views/bo/pages/offers/edit.blade.php b/resources/views/bo/pages/offers/edit.blade.php new file mode 100644 index 00000000..ca6b92a3 --- /dev/null +++ b/resources/views/bo/pages/offers/edit.blade.php @@ -0,0 +1,60 @@ +@extends('bo.layout', ['brParam' => $offerModel]) + +@section('title', __('Édition d\'une :model', ['model' => __('models.classes.offer')])) +@section('description', __('Édition d\'une :model déjà existant', ['model' => __('models.classes.offer')])) +@section('breadcrumb', request()->route()->getName()) + +@section('content') +
    +
    + + + + @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $offerModel]) +
    +
    +
    + @can('view', $offerModel) + + + + @endcan + @can('duplicate', $offerModel) + + + + @endcan + + @can('delete', $offerModel) + @csrf + @method('DELETE') + + @endcan +
    +
    +
    +
    + @csrf + @method('PUT') + @include('bo.pages.offers.form') + @include('bo.partials.script-button-clone') +
    +
    + +
    +
    +
    +@endsection diff --git a/resources/views/bo/pages/offers/form.blade.php b/resources/views/bo/pages/offers/form.blade.php new file mode 100644 index 00000000..d20f9007 --- /dev/null +++ b/resources/views/bo/pages/offers/form.blade.php @@ -0,0 +1,259 @@ +
    + {{-- TITLE --}} +
    +
    + {{ str(__('validation.attributes.title'))->ucFirst() }} +
    +
    + @include('bo.partials.inputs.varchar', [ + 'id' => 'offerTitle', + 'label' => __(':field :inter:model', [ + 'field' => str(__('validation.attributes.title'))->ucFirst(), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]), + 'hasWordCounter' => true, + 'name' => 'title', + 'required' => true, + 'model' => $offerModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.attributes.title'), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]), + 'min' => 3, + 'max' => 255, + ]), + 'placeholder' => __(':field :inter:model', [ + 'field' => str(__('validation.attributes.title'))->ucFirst(), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]), + ]) +
    +
    +
    +
    + {{-- LOCATION --}} +
    +
    + {{ str(__('validation.custom.location'))->ucFirst() }} +
    +
    + @include('bo.partials.inputs.varchar', [ + 'id' => 'offerLocation', + 'label' => __(':field :inter:model', [ + 'field' => str(__('validation.custom.location'))->ucFirst(), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]), + 'hasWordCounter' => true, + 'name' => 'location', + 'required' => true, + 'model' => $offerModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.custom.location'), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]), + 'min' => 3, + 'max' => 255, + ]), + 'placeholder' => __(':field :inter:model', [ + 'field' => str(__('validation.custom.location'))->ucFirst(), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]), + ]) +
    +
    +
    +
    + {{-- CONTRACT --}} +
    +
    + {{ str(__('validation.custom.contract'))->ucFirst() }} +
    +
    + @include('bo.partials.inputs.varchar', [ + 'id' => 'offerContract', + 'label' => __(':field :inter:model', [ + 'field' => str(__('validation.custom.contract'))->ucFirst(), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]), + 'hasWordCounter' => true, + 'name' => 'contract', + 'required' => true, + 'model' => $offerModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.custom.contract'), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]), + 'min' => 3, + 'max' => 255, + ]), + 'placeholder' => __(':field :inter:model', [ + 'field' => str(__('validation.custom.contract'))->ucFirst(), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]), + ]) +
    +
    +
    +
    + {{-- FUNCTION --}} +
    +
    + {{ str(__('validation.custom.function'))->ucFirst() }} +
    +
    + @include('bo.partials.inputs.varchar', [ + 'id' => 'offerFunction', + 'label' => __(':field :inter:model', [ + 'field' => str(__('validation.custom.function'))->ucFirst(), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]), + 'hasWordCounter' => true, + 'name' => 'function', + 'required' => true, + 'model' => $offerModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.custom.function'), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]), + 'min' => 3, + 'max' => 255, + ]), + 'placeholder' => __(':field :inter:model', [ + 'field' => str(__('validation.custom.function'))->ucFirst(), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]), + ]) +
    +
    +
    +
    + {{-- PICTURE --}} +
    +
    + {{ str(__('validation.attributes.image'))->ucFirst() }} +
    + {{-- PICTURE_ALT --}} +
    + @include('bo.partials.inputs.varchar', [ + 'id' => 'offerPictureAlt', + 'label' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => str(__('validation.custom.attribute_alt'))->ucFirst(), + 'inter1' => __('validation.custom.of_the.vowel'), + 'field2' => __('validation.attributes.image'), + 'inter2' => __('validation.custom.of_the.male'), + 'model' => __('models.classes.offer'), + ]), + 'hasWordCounter' => true, + 'name' => 'picture_alt', + 'required' => false, + 'model' => $offerModel ?? null, + 'helper' => __('crud.minicrud.picture_alt_helper'), + 'placeholder' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => str(__('validation.custom.attribute_alt'))->ucFirst(), + 'inter1' => __('validation.custom.of_the.vowel'), + 'field2' => __('validation.attributes.image'), + 'inter2' => __('validation.custom.of_the.male'), + 'model' => __('models.classes.offer'), + ]), + ]) +
    + {{-- PICTURE_TITLE --}} +
    + @include('bo.partials.inputs.varchar', [ + 'id' => 'offerPictureTitle', + 'label' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => str(__('validation.custom.attribute_title'))->ucFirst(), + 'inter1' => __('validation.custom.of_the.vowel'), + 'field2' => __('validation.attributes.image'), + 'inter2' => __('validation.custom.of_the.male'), + 'model' => __('models.classes.offer'), + ]), + 'hasWordCounter' => true, + 'name' => 'picture_title', + 'required' => false, + 'model' => $offerModel ?? null, + 'helper' => __('crud.minicrud.picture_title_helper'), + 'placeholder' => __(':field1 :inter1:field2 :inter2:model', [ + 'field1' => str(__('validation.custom.attribute_title'))->ucFirst(), + 'inter1' => __('validation.custom.of_the.vowel'), + 'field2' => __('validation.attributes.image'), + 'inter2' => __('validation.custom.of_the.male'), + 'model' => __('models.classes.offer'), + ]), + ]) +
    + {{-- PICTURE --}} +
    + @include('bo.partials.inputs.varchar-picture', [ + 'id' => 'offerPicture', + 'name' => 'picture', + 'width' => 300, + 'height' => 200, + 'model' => $offerModel ?? null, + 'required' => true, + 'preview' => true, + ]) +
    +
    +
    +
    + {{-- DESCRIPTION --}} +
    +
    + {{ str(__('validation.attributes.description'))->ucfirst() }} +
    +
    + @include('bo.partials.inputs.text-html', [ + 'id' => 'offerDescription', + 'label' => str( + __(':field :inter:model', [ + 'field' => __('validation.attributes.description'), + 'inter' => __('validation.custom.of_the.vowel'), + 'model' => __('models.classes.offer'), + ]))->ucfirst(), + 'name' => 'description', + 'model' => $offerModel ?? null, + 'required' => true, + 'helper' => __('crud.minicrud.text_warning'), + ]) +
    +
    +
    +
    + {{-- PUBLISHED --}} +
    +
    + {{ __('crud.minicrud.visibility_label') }} +
    +
    + @include('bo.partials.inputs.tinyint-boolean', [ + 'id' => 'offerPublished', + 'name' => 'published', + 'model' => $offerModel ?? null, + 'label' => __('crud.minicrud.visibility_label'), + 'helper' => __('crud.minicrud.visibility_helper'), + ]) +
    +
    +
    +
    +
    +

    {{ __('* Champs obligatoires') }}

    +
    +
    diff --git a/resources/views/bo/pages/offers/index-list.blade.php b/resources/views/bo/pages/offers/index-list.blade.php new file mode 100644 index 00000000..893138a9 --- /dev/null +++ b/resources/views/bo/pages/offers/index-list.blade.php @@ -0,0 +1,84 @@ + + @if (count($offerModels) > 0) + + @include('bo.modules.table-col-sorter', [ + 'cols' => [ + 'title' => str(__('validation.attributes.title'))->ucfirst(), + 'location' => str(__('validation.custom.location'))->ucfirst(), + 'contract' => str(__('validation.custom.contract'))->ucfirst(), + 'function' => str(__('validation.custom.function'))->ucfirst(), + 'picture' => str(__('validation.attributes.image'))->ucfirst(), + 'published' => str(__('validation.custom.publication'))->ucfirst(), + 'updated_at' => str(__('validation.attributes.updated_at'))->ucfirst(), + ], + ]) + + + @foreach ($offerModels as $offerModel) + + + + + + + @include('bo.modules.change-published-status', [ + 'routeName' => 'offers', + 'model' => $offerModel, + ]) + + + + @endforeach + + @else + + + + @endif +
    {{ $offerModel->title }}{{ $offerModel->location }}{{ $offerModel->contract }}{{ $offerModel->function }} + {{ $offerModel->picture_alt }} + + {{ $offerModel->updated_at->isoFormat('LLLL') }} + + @canAny(['show', 'duplicate', 'update', 'delete'], $offerModel) +
    + @can('view', $offerModel) + + + + @endcan + @can('duplicate', $offerModel) + + + + @endcan + @can('update', $offerModel) + + + + @endcan + @can('delete', $offerModel) + @csrf + @method('DELETE') + + @endcan +
    + @else + + + + @endcan +
    + {{ trans_choice('crud.other.no_model_found', 1, ['model' => __('models.classes.offer')]) }} +
    diff --git a/resources/views/bo/pages/offers/index.blade.php b/resources/views/bo/pages/offers/index.blade.php new file mode 100644 index 00000000..409ca93d --- /dev/null +++ b/resources/views/bo/pages/offers/index.blade.php @@ -0,0 +1,21 @@ +@extends('bo.layout') + +@section('title', str(__('models.classes.offer'))->ucfirst()->plural()) +@section('description', __('Liste de tous les :model', ['model' => str(__('models.classes.offer'))->plural()])) + +@section('content') +
    + @include('modules.breadcrumbs.breadcrumb-body') + @can('create', \App\Models\Offer::class) + + + + @endcan +
    + @include('bo.modules.search-bar') +
    + @include('bo.pages.offers.index-list') +
    + {{ $offerModels->links() }} +@endsection diff --git a/resources/views/bo/pages/offers/show.blade.php b/resources/views/bo/pages/offers/show.blade.php new file mode 100644 index 00000000..332e2fff --- /dev/null +++ b/resources/views/bo/pages/offers/show.blade.php @@ -0,0 +1,112 @@ +@extends('bo.layout', ['brParam' => $offerModel]) + +@section('title', __('Aperçu d\'une :model', ['model' => __('models.classes.offer')])) +@section('description', __('Aperçu d\'une :model déjà existante', ['model' => __('models.classes.offer')])) +@section('breadcrumb', request()->route()->getName()) + +@section('content') +
    +
    + + + + @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $offerModel]) +
    + @canAny(['duplicate', 'update', 'delete'], $offerModel) +
    + @can('duplicate', $offerModel) + + + + @endcan + @can('update', $offerModel) + + + + @endcan + @can('delete', $offerModel) + @csrf + @method('DELETE') + + @endcan +
    + @endcan +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{ str(__('validation.attributes.title'))->ucFirst() }} + {{ $offerModel->title }} +
    {{ str(__('validation.custom.location'))->ucFirst() }} + {{ $offerModel->location }} +
    {{ str(__('validation.custom.contract'))->ucFirst() }} + {{ $offerModel->contract }} +
    {{ str(__('validation.custom.function'))->ucFirst() }} + {{ $offerModel->function }} +
    {{ str(__('validation.attributes.description'))->ucFirst() }} + {{ $offerModel->description }} +
    {{ str(__('validation.attributes.image'))->ucFirst() }} + {{ $offerModel->picture_alt }} +
    + {{ str(sprintf("%s du bouton", __('validation.custom.publication')))->ucFirst() }} + + {{ $offerModel->published ? __('Oui') : __('Non') }} +
    {{ str(__('validation.attributes.created_at'))->ucFirst() }} + {{ str($offerModel->created_at->isoFormat('LLLL'))->ucFirst() }} +
    {{ str(__('validation.attributes.updated_at'))->ucFirst() }} + {{ str($offerModel->updated_at->isoFormat('LLLL'))->ucFirst() }} +
    +
    +
    +
    +@endsection diff --git a/routes/breadcrumbs-bo.php b/routes/breadcrumbs-bo.php index bb923300..53c0360a 100644 --- a/routes/breadcrumbs-bo.php +++ b/routes/breadcrumbs-bo.php @@ -12,6 +12,7 @@ use App\Models\ConsumptionMoment; use App\Models\MainSlide; use App\Models\GeneralSetting; use App\Models\Meat; +use App\Models\Offer; use App\Models\Product; use App\Models\ProductType; use App\Models\Range; @@ -494,3 +495,27 @@ Breadcrumbs::for('bo.recipes.duplicate', function (Generator $trail) { Breadcrumbs::for('bo.seo.index', function (Generator $trail) { $trail->push(Str::of(trans('champs SEO'))->ucfirst(), route('bo.recipes.index')); }); + +// * OFFERS +Breadcrumbs::for('bo.offers.index', function (Generator $trail) { + $trail->push( + Str::of(trans('models.classes.offer'))->ucfirst()->plural(), + route('bo.offers.index', ['sort_col' => 'updated_at', 'sort_way' => 'desc']) + ); +}); +Breadcrumbs::for('bo.offers.show', function (Generator $trail, Offer $offer) { + $trail->parent('bo.offers.index'); + $trail->push(Str::ucfirst(trans('crud.actions.view')), route('bo.offers.show', $offer)); +}); +Breadcrumbs::for('bo.offers.create', function (Generator $trail) { + $trail->parent('bo.offers.index'); + $trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.offers.create')); +}); +Breadcrumbs::for('bo.offers.edit', function (Generator $trail, Offer $offer) { + $trail->parent('bo.offers.index'); + $trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.offers.edit', $offer)); +}); +Breadcrumbs::for('bo.offers.duplicate', function (Generator $trail) { + $trail->parent('bo.offers.index'); + $trail->push(Str::ucfirst(trans('crud.actions.duplicate'))); +}); diff --git a/routes/web-bo.php b/routes/web-bo.php index 909a957f..9b0abd78 100644 --- a/routes/web-bo.php +++ b/routes/web-bo.php @@ -12,6 +12,7 @@ use App\Http\Controllers\Bo\KeyNumberController; use App\Http\Controllers\Bo\MainSlideController; use App\Http\Controllers\Bo\MenuNodeDataController; use App\Http\Controllers\Bo\MeatController; +use App\Http\Controllers\Bo\OfferController; use App\Http\Controllers\Bo\ProductController; use App\Http\Controllers\Bo\ProductTypeController; use App\Http\Controllers\Bo\ProfessionController; @@ -320,6 +321,20 @@ Route::middleware(['throttle:web'])->prefix('bo')->name('bo.')->group(function ( Route::get('/products/json/paginate', [ProductController::class, 'jsonPaginateModels']) ->name('products.json-paginate'); + // * OFFERS. + Route::resource('offers', OfferController::class) + ->withoutMiddleware('htmlsanitize') + ->middleware('htmlsanitize:description'); + Route::patch('/offers/{offer}/change-published', [ + OfferController::class, 'changePublished' + ])->name('offers.change-published'); + Route::patch('/offers/{offer}/change-order/{direction}', [ + OfferController::class, 'changeOrder' + ])->where('direction', 'up|down')->name('offers.change-order'); + Route::get('/offers/{offer}/duplicate', [ + OfferController::class, 'duplicate' + ])->name('offers.duplicate'); + Route::middleware('can:isConceptor')->group(function () { // * ACTIVITY LOGS. Route::resource('activity_logs', ActivityLogController::class)->only(['index', 'show']);