From 35fc53e2307d17537a50c25758d2b276dfdfa3ac Mon Sep 17 00:00:00 2001 From: Alexis Gousseau Date: Mon, 10 Jun 2024 15:46:50 +0200 Subject: [PATCH] feat: :sparkles: add CRUD product types --- .../Controllers/Bo/ProductTypeController.php | 187 ++++++++++++++++++ app/Http/Controllers/Bo/VideoController.php | 2 +- .../ProductTypes/StoreProductTypeRequest.php | 74 +++++++ .../ProductTypes/UpdateProductTypeRequest.php | 18 ++ app/Models/ProductType.php | 103 ++++++++++ app/Policies/ProductTypePolicy.php | 86 ++++++++ .../Controllers/UpdateModelPublished.php | 2 +- database/factories/ProductTypeFactory.php | 24 +++ .../product_types/default-picture-150x150.png | Bin 0 -> 1934 bytes ...6_06_161707_create_product_types_table.php | 44 +++++ .../seeders/Development/ProductTypeSeeder.php | 30 +++ resources/lang/en/models.php | 7 +- resources/lang/fr/models.php | 1 + resources/views/bo/layouts/sidemenu.blade.php | 17 +- .../bo/pages/product_types/create.blade.php | 36 ++++ .../bo/pages/product_types/edit.blade.php | 60 ++++++ .../bo/pages/product_types/form.blade.php | 157 +++++++++++++++ .../pages/product_types/index-list.blade.php | 89 +++++++++ .../bo/pages/product_types/index.blade.php | 21 ++ .../bo/pages/product_types/show.blade.php | 96 +++++++++ routes/breadcrumbs-bo.php | 25 +++ routes/web-bo.php | 12 ++ 22 files changed, 1087 insertions(+), 4 deletions(-) create mode 100644 app/Http/Controllers/Bo/ProductTypeController.php create mode 100644 app/Http/Requests/Bo/ProductTypes/StoreProductTypeRequest.php create mode 100644 app/Http/Requests/Bo/ProductTypes/UpdateProductTypeRequest.php create mode 100644 app/Models/ProductType.php create mode 100644 app/Policies/ProductTypePolicy.php create mode 100644 database/factories/ProductTypeFactory.php create mode 100644 database/factories/assets/product_types/default-picture-150x150.png create mode 100644 database/migrations/2024_06_06_161707_create_product_types_table.php create mode 100644 database/seeders/Development/ProductTypeSeeder.php create mode 100644 resources/views/bo/pages/product_types/create.blade.php create mode 100644 resources/views/bo/pages/product_types/edit.blade.php create mode 100644 resources/views/bo/pages/product_types/form.blade.php create mode 100644 resources/views/bo/pages/product_types/index-list.blade.php create mode 100644 resources/views/bo/pages/product_types/index.blade.php create mode 100644 resources/views/bo/pages/product_types/show.blade.php diff --git a/app/Http/Controllers/Bo/ProductTypeController.php b/app/Http/Controllers/Bo/ProductTypeController.php new file mode 100644 index 00000000..fe3a5112 --- /dev/null +++ b/app/Http/Controllers/Bo/ProductTypeController.php @@ -0,0 +1,187 @@ +authorizeResource(ProductType::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 $productTypeModels */ + $productTypeModels = ProductType::query(); + + /** @var string $search Search field */ + $search = $request->search; + if ($search) { + $this->searchQuery( + $productTypeModels, + $search, + null, + 'label', + 'description', + ); + } + $searchFields = \implode(', ', [ + trans('validation.custom.label'), + trans('validation.attributes.description'), + ]); + + /** Sort columns with a query */ + $this->sortQuery($productTypeModels); + + /** Custom pagination */ + $productTypeModels = $this->paginate($productTypeModels); + + return view('bo.pages.product_types.index', compact('productTypeModels', 'search', 'searchFields')); + } + + /** + * Show the specified resource. + * + * @param \App\Models\ProductType $productType + * @return \Illuminate\Contracts\View\View + */ + public function show(ProductType $productType): \Illuminate\Contracts\View\View + { + return view('bo.pages.product_types.show', ['productTypeModel' => $productType]); + } + + /** + * Show the form for creating a new resource. + * + * @param \App\Models\ProductType $productType + * @return \Illuminate\Contracts\View\View + */ + public function create(ProductType $productType): \Illuminate\Contracts\View\View + { + return view('bo.pages.product_types.create', ['productTypeModel' => $productType]); + } + + /** + * Store a newly created resource in storage. + * + * @param \App\Http\Requests\Bo\ProductTypes\StoreProductTypeRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(StoreProductTypeRequest $request): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($request) { + $productTypeModel = new ProductType(); + $productTypeModel->fill($request->validated()); + if ($productTypeModel->saveOrFail()) { + return redirect()->route('bo.product_types.edit', $productTypeModel) + ->with('success', trans('crud.messages.has_been_created', [ + 'model' => Str::ucfirst(trans('models.classes.product_type')) + ])); + } + return redirect()->back() + ->with('error', trans('crud.messages.cannot_be_created', [ + 'model' => Str::ucfirst(trans('models.classes.product_type')) + ])); + }); + } + + /** + * Show the form for editing the specified resource. + * + * @param \App\Models\ProductType $productType + * @return \Illuminate\Contracts\View\View + */ + public function edit(ProductType $productType): \Illuminate\Contracts\View\View + { + return view('bo.pages.product_types.edit', ['productTypeModel' => $productType]); + } + + /** + * Update the specified resource in storage. + * + * @param \App\Http\Requests\Bo\ProductTypes\UpdateProductTypeRequest $request + * @param \App\Models\ProductType $productType + * @return \Illuminate\Http\RedirectResponse + */ + public function update( + UpdateProductTypeRequest $request, + ProductType $productType + ): \Illuminate\Http\RedirectResponse { + return DB::transaction(function () use ($request, $productType) { + $productType->fill($request->validated()); + if ($productType->saveOrFail()) { + return redirect()->route('bo.product_types.edit', $productType) + ->with('success', trans('crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans('models.classes.product_type')) + ])); + } + return redirect()->route('bo.product_types.edit', $productType) + ->with('error', trans('crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans('models.classes.product_type')) + ])); + }); + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\ProductType $productType + * @return \Illuminate\Http\RedirectResponse + */ + public function destroy(ProductType $productType): \Illuminate\Http\RedirectResponse + { + /** @var array */ + $previousQueries = []; + \parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries); + if ($productType->deleteOrFail()) { + return redirect()->route('bo.product_types.index', $previousQueries) + ->with('success', trans('crud.messages.has_been_deleted', [ + 'model' => Str::ucfirst(trans('models.classes.product_type')) + ])); + } + return redirect()->back() + ->with('error', trans('crud.messages.cannot_be_deleted', [ + 'model' => Str::ucfirst(trans('models.classes.product_type')) + ])); + } + + /** + * Duplicate the specified resource. + * + * @param \App\Models\ProductType $productType + * @return \Illuminate\Contracts\View\View + */ + public function duplicate(ProductType $productType): \Illuminate\Contracts\View\View + { + return $this->create($productType->replicate()); + } + + /** + * Change model button status. + * + * @param \App\Models\ProductType $productType + * @return \Illuminate\Http\RedirectResponse + */ + public function changeButtonStatus(ProductType $productType) + { + $productType->update(['button_status' => !$productType->getOriginal('button_status')]); + return redirect()->back()->with('success', __('crud.messages.publish_status_saved')); + } +} diff --git a/app/Http/Controllers/Bo/VideoController.php b/app/Http/Controllers/Bo/VideoController.php index 3522ca03..bd465cb4 100644 --- a/app/Http/Controllers/Bo/VideoController.php +++ b/app/Http/Controllers/Bo/VideoController.php @@ -192,6 +192,6 @@ class VideoController extends Controller public function changeHomepage(Video $video): \Illuminate\Http\RedirectResponse { $video->update(['is_on_homepage' => !$video->getOriginal('is_on_homepage')]); - return redirect()->back()->with('success', trans(__('crud.messages.publish_status_saved'))); + return redirect()->back()->with('success', __('crud.messages.publish_status_saved')); } } diff --git a/app/Http/Requests/Bo/ProductTypes/StoreProductTypeRequest.php b/app/Http/Requests/Bo/ProductTypes/StoreProductTypeRequest.php new file mode 100644 index 00000000..cd0fbac7 --- /dev/null +++ b/app/Http/Requests/Bo/ProductTypes/StoreProductTypeRequest.php @@ -0,0 +1,74 @@ +merge([ + 'button_status' => $this->boolean('button_status') + ]); + $this->mergePicture('picture'); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + $rules = [ + 'label' => 'required|string|between:3,255', + 'description' => ['required', 'string', new WysiwygLength()], + 'picture_alt' => 'required_with:picture|nullable|between:3,255', + 'picture_title' => 'required_with:picture|nullable|between:3,255', + 'button_status' => 'required|boolean', + ]; + return \array_merge( + $rules, + $this->pictureRules('picture', 150, 150, 150, 150), + ); + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'label' => trans('validation.custom.label'), + 'description' => trans('validation.attributes.description'), + 'picture' => trans('validation.attributes.image'), + 'picture_alt' => trans('validation.custom.attribute_alt'), + 'picture_title' => trans('validation.custom.attribute_title'), + 'button_status' => trans('validation.attributes.published') + ]; + } +} diff --git a/app/Http/Requests/Bo/ProductTypes/UpdateProductTypeRequest.php b/app/Http/Requests/Bo/ProductTypes/UpdateProductTypeRequest.php new file mode 100644 index 00000000..8a5c8f7e --- /dev/null +++ b/app/Http/Requests/Bo/ProductTypes/UpdateProductTypeRequest.php @@ -0,0 +1,18 @@ +route('product_type')); + } +} diff --git a/app/Models/ProductType.php b/app/Models/ProductType.php new file mode 100644 index 00000000..5eaf6ca1 --- /dev/null +++ b/app/Models/ProductType.php @@ -0,0 +1,103 @@ + + */ + protected $fillable = [ + 'label', + 'slug', + 'description', + 'picture', + 'picture_alt', + 'picture_title', + 'button_status', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'button_status' => 'boolean', + ]; + + /** + * Perform any actions required after the model boots. + * + * @return void + */ + protected static function booted(): void + { + static::creating(function (self $model) { + self::assertLabelIsUnique($model->label); + $model->picture = FileStorageHelper::storeFile($model, $model->picture, true); + }); + static::updating(function (self $model) { + self::assertLabelIsUnique($model->label, $model->id); + $model->picture = FileStorageHelper::storeFile($model, $model->picture, true); + }); + static::updated(function (self $model) { + FileStorageHelper::removeOldFile($model, 'picture'); + }); + static::deleted(function (self $model) { + FileStorageHelper::removeFile($model, 'picture'); + }); + } + + // * METHODS + + /** + * Asserts using validation that the label is unique + * + * @param string|null $label + * @param integer|null $id + * @return void + * @throws \Illuminate\Validation\ValidationException If a tange label already exists. + */ + private static function assertLabelIsUnique(string|null $label, ?int $id = null): void + { + $table = (new self())->getTable(); + ToolboxHelper::assertFieldIsUnique($table, 'label', $label, $id); + } + + // * RELATIONSHIPS + + /** + * Get all of the products for the product type. + * + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ + public function products(): \Illuminate\Database\Eloquent\Relations\MorphToMany + { + return $this->morphToMany(Product::class, 'productable'); + } +} diff --git a/app/Policies/ProductTypePolicy.php b/app/Policies/ProductTypePolicy.php new file mode 100644 index 00000000..9f6f15e7 --- /dev/null +++ b/app/Policies/ProductTypePolicy.php @@ -0,0 +1,86 @@ +update(['published' => !$model->getOriginal('published')]); - return redirect()->back()->with('success', trans(__('crud.messages.publish_status_saved'))); + return redirect()->back()->with('success', __('crud.messages.publish_status_saved')); } } diff --git a/database/factories/ProductTypeFactory.php b/database/factories/ProductTypeFactory.php new file mode 100644 index 00000000..7ace3a1c --- /dev/null +++ b/database/factories/ProductTypeFactory.php @@ -0,0 +1,24 @@ + + */ + public function definition() + { + return [ + 'label' => fake()->words(rand(1, 2), true), + 'description' => fake()->sentence(), + 'picture_alt' => fake()->sentence(), + 'picture_title' => fake()->sentence(), + 'button_status' => fake()->boolean(75), + ]; + } +} diff --git a/database/factories/assets/product_types/default-picture-150x150.png b/database/factories/assets/product_types/default-picture-150x150.png new file mode 100644 index 0000000000000000000000000000000000000000..1c9171a03bdaa0cee8bbbad94be5acac2373d4e2 GIT binary patch literal 1934 zcmeHI`#Tc~7#{VcC=^jc9NIyWb#}_bG$9nt)LiDw?btbPOA4W|xAqyqp3Cf}d~xK+6g0MI*lBVe~s}0bhhV5ql?5liNAx7AD%YI^AwaurVgsZTP|b3u@AV(-TnFbVjfcn5Qr=nuMbrD7J%tAgSm zsN|pfy{S@uTdLqDB%xji=1R0JYDe%h!;SD1(yDTwsMzOXOq|MZv4vqPE9zys3RNP3($?pB3p((rasqq}CN8bDadi%CHjA+eKj(lYS(BG^J(|th zabjKu&^Zef3iq_Iuz*lyuL>;$Z*IHVi_0r260Y{Tw_Kc%cEZ$_$!G*Xm>YpI7F2IcPnl1gXQl06xOtN8WT}%$DJLa>QRdo z6YnSpSNeTX=Jcz{3=~Tawc}dSc6elg#bC1+`*tZnNzigcgb$*IuIVeWhd{f^nqwt? zyH5D*-eW#I+}_&SS~o#j`ZBptnamHaVpAr01Mh9(-fSlSFvzG+yz^w{3DdOsAO~-} zGJ0l6v(-RT$hhCyX;#Dr?Z@`#(OJkzl!LzxChAc*^4CJceXE?~zUXu@9d%>=BAVQM zQ{UVsj4uleh8m}RDP#CyPWQC!DKO&>A=C_>@$;o#mi~*K8iz3Dr>) z>KbkV+iJ*ses?r7h#fe6zDOAC`8Cz{D2bU@)fWybEkLqxJ64ysyTo)=T0Z92id(); + $table->string('label')->comment('Product type description.'); + $table->text('description')->comment('Product type description.'); + $table->string('picture')->comment('Product type picture.'); + $table->string('picture_alt')->comment('Picture alt attribute.'); + $table->string('picture_title')->comment('Picture title attribute.'); + $table->boolean('button_status')->comment('Product type button publish status.'); + $table->timestamps(); + }); + + // Call seeder. + Artisan::call('db:seed', [ + '--class' => ProductTypeSeeder::class, + ]); + } + + /** + * Run the migrations. + * + * @return void + */ + public function down(): void + { + Schema::dropIfExists('product_types'); + } +}; diff --git a/database/seeders/Development/ProductTypeSeeder.php b/database/seeders/Development/ProductTypeSeeder.php new file mode 100644 index 00000000..4507785c --- /dev/null +++ b/database/seeders/Development/ProductTypeSeeder.php @@ -0,0 +1,30 @@ +make()->each(function (ProductType $productTypeModel) { + $productTypeModel->picture = FileStorageHelper::storeFile( + $productTypeModel, + new \SplFileInfo(\resource_path( + '../database/factories/assets/product_types/default-picture-150x150.png' + )) + ); + $productTypeModel->saveQuietly(); + }); + } +} diff --git a/resources/lang/en/models.php b/resources/lang/en/models.php index f045b02d..c60c80e4 100644 --- a/resources/lang/en/models.php +++ b/resources/lang/en/models.php @@ -11,13 +11,18 @@ return [ 'key_number' => 'key number', 'main_slide' => 'main slide', 'demarche_slide' => 'demarche slide', - 'approach' => 'approach|approaches', 'hot_news' => 'hot news', + 'range' => 'range', + 'subrange' => 'subrange', + 'approach' => 'approach|approaches', + 'meat' => 'meat', + 'submeat' => 'submeat', 'label' => 'label', 'demarche' => 'demarche', 'consumption_moment' => 'consumption moment', 'video' => 'video', 'product' => 'product', 'recipe' => 'recipe', + 'product_type' => 'product type|product types', ] ]; diff --git a/resources/lang/fr/models.php b/resources/lang/fr/models.php index 2a0347b8..008bb0e4 100644 --- a/resources/lang/fr/models.php +++ b/resources/lang/fr/models.php @@ -23,5 +23,6 @@ return [ 'video' => 'vidéo', 'product' => 'produit', 'recipe' => 'recette', + 'product_type' => 'type de produit|types de produit', ] ]; diff --git a/resources/views/bo/layouts/sidemenu.blade.php b/resources/views/bo/layouts/sidemenu.blade.php index f5ab5737..537c5592 100644 --- a/resources/views/bo/layouts/sidemenu.blade.php +++ b/resources/views/bo/layouts/sidemenu.blade.php @@ -374,8 +374,23 @@ ])> {{ str(__('models.classes.product'))->ucFirst()->plural() }} + --}} + {{-- PRODUCT TYPES --}} +
  • + request()->routeIs('bo.product_types.*') + ]) data-bs-toggle="tooltip" + href="{{ route('bo.product_types.index') }}" + title="{{ __('crud.helpers.update_model', ['model' => trans_choice('models.classes.product_type', 2)]) }}"> + request()->routeIs('bo.product_types.*') + ])> + {{ str(trans_choice('models.classes.product_type', 2))->ucFirst() }} +
  • -
  • --}} +
  • {{-- MEATS --}}
  • $productTypeModel]) + +@section('title', __('Création d\'une :model', ['model' => trans_choice('models.classes.product_type', 1)])) +@section('description', __('Création d\'une tout nouveau :model', ['model' => trans_choice('models.classes.product_type', 1)])) +@section('breadcrumb', request()->route()->getName()) + +@section('content') +
    +
    + + + + @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $productTypeModel]) +
    +
    + +
    +
    +
    + @csrf + @include('bo.pages.product_types.form') + @include('bo.partials.script-button-clone') +
    +
    + +
    +
    +
    +@endsection diff --git a/resources/views/bo/pages/product_types/edit.blade.php b/resources/views/bo/pages/product_types/edit.blade.php new file mode 100644 index 00000000..cbc42a71 --- /dev/null +++ b/resources/views/bo/pages/product_types/edit.blade.php @@ -0,0 +1,60 @@ +@extends('bo.layout', ['brParam' => $productTypeModel]) + +@section('title', __('Édition d\'une :model', ['model' => trans_choice('models.classes.product_type', 1)])) +@section('description', __('Édition d\'une :model déjà existant', ['model' => trans_choice('models.classes.product_type', 1)])) +@section('breadcrumb', request()->route()->getName()) + +@section('content') +
    +
    + + + + @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $productTypeModel]) +
    +
    +
    + @can('view', $productTypeModel) + + + + @endcan + @can('duplicate', $productTypeModel) + + + + @endcan + + @can('delete', $productTypeModel) + @csrf + @method('DELETE') + + @endcan +
    +
    +
    +
    + @csrf + @method('PUT') + @include('bo.pages.product_types.form') + @include('bo.partials.script-button-clone') +
    +
    + +
    +
    +
    +@endsection diff --git a/resources/views/bo/pages/product_types/form.blade.php b/resources/views/bo/pages/product_types/form.blade.php new file mode 100644 index 00000000..59610e25 --- /dev/null +++ b/resources/views/bo/pages/product_types/form.blade.php @@ -0,0 +1,157 @@ +
    + {{-- LABEL --}} +
    +
    + {{ str(__('validation.custom.label'))->ucFirst() }} +
    +
    + @include('bo.partials.inputs.varchar', [ + 'id' => 'productTypeLabel', + 'label' => __(':field :inter:model', [ + 'field' => str(__('validation.custom.label'))->ucFirst(), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.product_type', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'label', + 'required' => true, + 'model' => $productTypeModel ?? null, + 'helper' => __('validation.between.string', [ + 'attribute' => __(':field :inter:model', [ + 'field' => __('validation.custom.label'), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.product_type', 1), + ]), + 'min' => 3, + 'max' => 255, + ]), + 'placeholder' => __(':field :inter:model', [ + 'field' => str(__('validation.custom.label'))->ucFirst(), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.product_type', 1), + ]), + ]) +
    +
    +
    +
    + {{-- DESCRIPTION --}} +
    +
    + {{ str(__('validation.attributes.description'))->ucfirst() }} +
    +
    + @include('bo.partials.inputs.text-html', [ + 'id' => 'productTypeDescription', + 'label' => str( + __(':field :inter:model', [ + 'field' => __('validation.attributes.description'), + 'inter' => __('validation.custom.of_the.male'), + 'model' => trans_choice('models.classes.product_type', 1), + ]))->ucfirst(), + 'name' => 'description', + 'model' => $productTypeModel ?? null, + 'required' => true, + 'helper' => __('crud.minicrud.text_warning'), + ]) +
    +
    +
    +
    + {{-- PICTURE --}} +
    +
    + {{ str(__('validation.attributes.image'))->ucFirst() }} +
    + {{-- PICTURE_ALT --}} +
    + @include('bo.partials.inputs.varchar', [ + 'id' => 'productTypePictureAlt', + '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' => trans_choice('models.classes.product_type', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'picture_alt', + 'required' => true, + 'model' => $productTypeModel ?? 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' => trans_choice('models.classes.product_type', 1), + ]), + ]) +
    + {{-- PICTURE_TITLE --}} +
    + @include('bo.partials.inputs.varchar', [ + 'id' => 'productTypePictureTitle', + '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' => trans_choice('models.classes.product_type', 1), + ]), + 'hasWordCounter' => true, + 'name' => 'picture_title', + 'required' => true, + 'model' => $productTypeModel ?? 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' => trans_choice('models.classes.product_type', 1), + ]), + ]) +
    + {{-- PICTURE --}} +
    + @include('bo.partials.inputs.varchar-picture', [ + 'id' => 'productTypePicture', + 'name' => 'picture', + 'width' => 150, + 'height' => 150, + 'model' => $productTypeModel ?? null, + 'required' => true, + 'preview' => true, + ]) +
    +
    +
    +
    + {{-- BUTTON STATUS --}} +
    + @can('changeButtonStatus', $productTypeModel) +
    + {{ __('crud.minicrud.visibility_label') }} +
    +
    + @include('bo.partials.inputs.tinyint-boolean', [ + 'id' => 'productTypeButtonStatus', + 'name' => 'button_status', + 'model' => $productTypeModel ?? null, + 'label' => __('Rendre visible le bouton'), + 'helper' => __('crud.minicrud.visibility_helper'), + ]) +
    +
    +
    + @else + + + + @endcan +
    +
    +

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

    +
    +
    diff --git a/resources/views/bo/pages/product_types/index-list.blade.php b/resources/views/bo/pages/product_types/index-list.blade.php new file mode 100644 index 00000000..dc8e019f --- /dev/null +++ b/resources/views/bo/pages/product_types/index-list.blade.php @@ -0,0 +1,89 @@ + + @if (count($productTypeModels) > 0) + + @include('bo.modules.table-col-sorter', [ + 'cols' => [ + 'label' => str(__('validation.custom.label'))->ucfirst(), + 'description' => str(__('validation.attributes.description'))->ucfirst(), + 'button_status' => str(__('validation.custom.button_status'))->ucfirst(), + 'updated_at' => str(__('validation.attributes.updated_at'))->ucfirst(), + ], + ]) + + + @foreach ($productTypeModels as $productTypeModel) + + + + + + + + @endforeach + + @else + + + + @endif +
    {{ $productTypeModel->label }}{!! clean($productTypeModel->description) !!} +
    + @csrf + @method('PATCH') + +
    +
    + {{ $productTypeModel->updated_at->isoFormat('LLLL') }} + + @canAny(['show', 'duplicate', 'update', 'delete'], $productTypeModel) +
    + @can('view', $productTypeModel) + + + + @endcan + @can('duplicate', $productTypeModel) + + + + @endcan + @can('update', $productTypeModel) + + + + @endcan + @can('delete', $productTypeModel) + @csrf + @method('DELETE') + + @endcan +
    + @else + + + + @endcan +
    + {{ trans_choice('crud.other.no_model_found', 1, ['model' => trans_choice('models.classes.product_type', 1)]) }} +
    diff --git a/resources/views/bo/pages/product_types/index.blade.php b/resources/views/bo/pages/product_types/index.blade.php new file mode 100644 index 00000000..bd4ebb08 --- /dev/null +++ b/resources/views/bo/pages/product_types/index.blade.php @@ -0,0 +1,21 @@ +@extends('bo.layout') + +@section('title', str(trans_choice('models.classes.product_type', 2))->ucfirst()) +@section('description', __('Liste de tous les :model', ['model' => trans_choice('models.classes.product_type', 2)])) + +@section('content') +
    + @include('modules.breadcrumbs.breadcrumb-body') + @can('create', \App\Models\ProductType::class) + + + + @endcan +
    + @include('bo.modules.search-bar') +
    + @include('bo.pages.product_types.index-list') +
    + {{ $productTypeModels->links() }} +@endsection diff --git a/resources/views/bo/pages/product_types/show.blade.php b/resources/views/bo/pages/product_types/show.blade.php new file mode 100644 index 00000000..b444192f --- /dev/null +++ b/resources/views/bo/pages/product_types/show.blade.php @@ -0,0 +1,96 @@ +@extends('bo.layout', ['brParam' => $productTypeModel]) + +@section('title', __('Aperçu d\'une :model', ['model' => trans_choice('models.classes.product_type', 1)])) +@section('description', __('Aperçu d\'une :model déjà existante', ['model' => trans_choice('models.classes.product_type', 1)])) +@section('breadcrumb', request()->route()->getName()) + +@section('content') +
    +
    + + + + @include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $productTypeModel]) +
    + @canAny(['duplicate', 'update', 'delete'], $productTypeModel) +
    + @can('duplicate', $productTypeModel) + + + + @endcan + @can('update', $productTypeModel) + + + + @endcan + @can('delete', $productTypeModel) + @csrf + @method('DELETE') + + @endcan +
    + @endcan +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{ str(__('validation.custom.label'))->ucFirst() }} + {{ $productTypeModel->label }} +
    {{ str(__('validation.attributes.description'))->ucFirst() }} + {!! clean($productTypeModel->description) !!} +
    {{ str(__('validation.attributes.image'))->ucFirst() }} + {{ $productTypeModel->picture_alt }} +
    + {{ str(__('validation.custom.publication'))->ucFirst() }} + + {{ $productTypeModel->button_status + ? str($productTypeModel->created_at->isoFormat('LLLL'))->ucFirst() + : __('crud.other.model_not_published') }} +
    {{ str(__('validation.attributes.created_at'))->ucFirst() }} + {{ str($productTypeModel->created_at->isoFormat('LLLL'))->ucFirst() }} +
    {{ str(__('validation.attributes.updated_at'))->ucFirst() }} + {{ str($productTypeModel->updated_at->isoFormat('LLLL'))->ucFirst() }} +
    +
    +
    +
    +@endsection diff --git a/routes/breadcrumbs-bo.php b/routes/breadcrumbs-bo.php index c99179ac..84a9db9e 100644 --- a/routes/breadcrumbs-bo.php +++ b/routes/breadcrumbs-bo.php @@ -11,6 +11,7 @@ use App\Models\ConsumptionMoment; use App\Models\MainSlide; use App\Models\GeneralSetting; use App\Models\Meat; +use App\Models\ProductType; use App\Models\Range; use App\Models\Recipe; use App\Models\StaticPage; @@ -302,6 +303,30 @@ Breadcrumbs::for('bo.submeats.duplicate', function (Generator $trail) { $trail->push(Str::ucfirst(trans('crud.actions.duplicate'))); }); +// * PRODUCT TYPES +Breadcrumbs::for('bo.product_types.index', function (Generator $trail) { + $trail->push( + Str::of(trans_choice('models.classes.product_type', 2))->ucfirst(), + route('bo.product_types.index') + ); +}); +Breadcrumbs::for('bo.product_types.show', function (Generator $trail, ProductType $productType) { + $trail->parent('bo.product_types.index'); + $trail->push(Str::ucfirst(trans('crud.actions.view')), route('bo.product_types.show', $productType)); +}); +Breadcrumbs::for('bo.product_types.create', function (Generator $trail) { + $trail->parent('bo.product_types.index'); + $trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.product_types.create')); +}); +Breadcrumbs::for('bo.product_types.edit', function (Generator $trail, ProductType $productType) { + $trail->parent('bo.product_types.index'); + $trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.product_types.edit', $productType)); +}); +Breadcrumbs::for('bo.product_types.duplicate', function (Generator $trail) { + $trail->parent('bo.product_types.index'); + $trail->push(Str::ucfirst(trans('crud.actions.duplicate'))); +}); + // * APPROACHES Breadcrumbs::for('bo.approaches.index', function (Generator $trail, int $approach_type) { $trail->push( diff --git a/routes/web-bo.php b/routes/web-bo.php index 4872d25b..fcdb779f 100644 --- a/routes/web-bo.php +++ b/routes/web-bo.php @@ -12,6 +12,7 @@ use App\Http\Controllers\Bo\MainSlideController; use App\Http\Controllers\Bo\MenuNodeDataController; use App\Http\Controllers\Bo\MeatController; use App\Http\Controllers\Bo\ProductController; +use App\Http\Controllers\Bo\ProductTypeController; use App\Http\Controllers\Bo\ProfessionController; use App\Http\Controllers\Bo\RangeController; use App\Http\Controllers\Bo\RecipeController; @@ -205,6 +206,17 @@ Route::prefix('bo') SubmeatController::class, 'duplicate' ])->name('submeats.duplicate'); + // * PRODUCT TYPES. + Route::resource('product_types', ProductTypeController::class) + ->withoutMiddleware('htmlsanitize') + ->middleware('htmlsanitize:description'); + Route::patch('/product_types/{product_type}/change-button-status', [ + ProductTypeController::class, 'changeButtonStatus' + ])->name('product_types.change-button-status'); + Route::get('/product_types/{product_type}/duplicate', [ + ProductTypeController::class, 'duplicate' + ])->name('product_types.duplicate'); + // * APPROACHES. Route::get('/approaches/{approach_type}', [ ApproachController::class, 'index'