feat: add associated models (claims/productTypes/meats/submeat) fields in product form

This commit is contained in:
Alexis Gousseau 2024-06-12 17:11:08 +02:00
parent 6b847a7931
commit 34bb5c4120
16 changed files with 509 additions and 45 deletions

View file

@ -7,7 +7,11 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Products\StoreProductRequest;
use App\Http\Requests\Bo\Products\UpdateProductRequest;
use App\Models\Approach;
use App\Models\Claim;
use App\Models\Meat;
use App\Models\Product;
use App\Models\ProductType;
use App\Models\Submeat;
use Illuminate\Http\Request;
use App\Traits\Controllers\UpdateModelPublished;
use App\Traits\Controllers\ChangesModelOrder;
@ -97,10 +101,29 @@ class ProductController extends Controller
->where('published', true)
->orderBy('name', 'ASC')
->paginate(10);
$productTypeModels = ProductType::query()
->orderBy('label', 'ASC')
->paginate(10);
$claimModels = Claim::query()
->where('published', true)
->orderBy('label', 'ASC')
->paginate(10);
$meatModels = Meat::query()
->where('published', true)
->orderBy('label', 'ASC')
->paginate(10);
$submeatModels = Submeat::query()
->where('published', true)
->orderBy('label', 'ASC')
->paginate(10);
return view('bo.pages.products.create', [
'productModel' => $product,
'meatModels' => $meatModels,
'submeatModels' => $submeatModels,
'approachDemarchModels' => $approachDemarchModels,
'approachLabelModels' => $approachLabelModels,
'productTypeModels' => $productTypeModels,
'claimModels' => $claimModels,
]);
}
@ -114,9 +137,16 @@ class ProductController extends Controller
{
return DB::transaction(function () use ($request) {
$product = new Product();
$product->fill(Arr::except($request->validated(), ['labels']));
$product->fill(Arr::except(
$request->validated(),
['labels', 'productTypes', 'claims', 'meats', 'submeats']
));
if ($product->saveOrFail()) {
$product->labels()->sync(collect($request->validated()['labels'])->pluck('id'));
$product->labels()->sync(collect($request->validated()['labels'] ?? [])->pluck('id'));
$product->productTypes()->sync(collect($request->validated()['productTypes'] ?? [])->pluck('id'));
$product->claims()->sync(collect($request->validated()['claims'] ?? [])->pluck('id'));
$product->meats()->sync(collect($request->validated()['meats'] ?? [])->pluck('id'));
$product->submeats()->sync(collect($request->validated()['submeats'] ?? [])->pluck('id'));
return redirect()->route('bo.products.edit', $product)
->with('success', trans('crud.messages.has_been_created', [
'model' => Str::ucfirst(trans('models.classes.product'))
@ -147,10 +177,29 @@ class ProductController extends Controller
->where('published', true)
->orderBy('name', 'ASC')
->paginate(10);
$productTypeModels = ProductType::query()
->orderBy('label', 'ASC')
->paginate(10);
$claimModels = Claim::query()
->where('published', true)
->orderBy('label', 'ASC')
->paginate(10);
$meatModels = Meat::query()
->where('published', true)
->orderBy('label', 'ASC')
->paginate(10);
$submeatModels = Submeat::query()
->where('published', true)
->orderBy('label', 'ASC')
->paginate(10);
return view('bo.pages.products.edit', [
'productModel' => $product,
'meatModels' => $meatModels,
'submeatModels' => $submeatModels,
'approachDemarchModels' => $approachDemarchModels,
'approachLabelModels' => $approachLabelModels,
'productTypeModels' => $productTypeModels,
'claimModels' => $claimModels,
]);
}
@ -164,8 +213,15 @@ class ProductController extends Controller
public function update(UpdateProductRequest $request, Product $product): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $product) {
$product->fill(Arr::except($request->validated(), ['labels']));
$product->labels()->sync(collect($request->validated()['labels'])->pluck('id'));
$product->fill(Arr::except(
$request->validated(),
['labels', 'productTypes', 'claims', 'meats', 'submeats']
));
$product->labels()->sync(collect($request->validated()['labels'] ?? [])->pluck('id'));
$product->productTypes()->sync(collect($request->validated()['productTypes'] ?? [])->pluck('id'));
$product->claims()->sync(collect($request->validated()['claims'] ?? [])->pluck('id'));
$product->meats()->sync(collect($request->validated()['meats'] ?? [])->pluck('id'));
$product->submeats()->sync(collect($request->validated()['submeats'] ?? [])->pluck('id'));
if ($product->saveOrFail()) {
return redirect()->route('bo.products.edit', $product)

View file

@ -5,6 +5,7 @@ namespace App\Http\Requests\Bo\Products;
use App\Models\Product;
use App\Rules\WysiwygLength;
use App\Traits\Requests\HasPicture;
use Closure;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
@ -58,9 +59,35 @@ class StoreProductRequest extends FormRequest
'details' => ['required', 'string', new WysiwygLength()],
'preparation' => ['required', 'string', new WysiwygLength()],
'ingredients' => ['required', 'string', new WysiwygLength()],
'approach_id' => 'required|exists:approaches,id',
'labels' => 'required|array|min:1',
'labels.*.id' => 'required|integer|exists:approaches,id',
'meats' => 'required_without:submeats|array|between:1,2',
'meats.*.id' => 'required_without:submeats|integer|exists:meats,id',
'submeats' => 'required_without:meats|array|between:1,2',
'submeats.*.id' => 'required_without:meats|integer|exists:submeats,id',
'approach_id' => [
'sometimes', 'nullable', 'exists:approaches,id',
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function (string $attribute, mixed $value, Closure $fail) {
// phpcs:enable
if (!empty($this->input('productTypes'))) {
$fail(trans('Le champs démarche doit être vide quand types de produit est renseigné.'));
}
}
],
'labels' => 'sometimes|nullable|array|min:1',
'labels.*.id' => 'sometimes|nullable|integer|exists:approaches,id',
'productTypes' => [
'sometimes', 'nullable', 'array', 'min:1',
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function (string $attribute, mixed $value, Closure $fail) {
// phpcs:enable
if (!empty($this->input('approach_id'))) {
$fail(trans('Le champs types de produit doit être vide quand démarche est renseigné.'));
}
}
],
'productTypes.*.id' => 'sometimes|nullable|integer|exists:product_types,id',
'claims' => 'sometimes|nullable|array|min:1',
'claims.*.id' => 'sometimes|nullable|integer|exists:claims,id',
'new' => 'required|boolean',
'discover' => 'required|boolean',
'published' => 'required|boolean',
@ -68,8 +95,8 @@ class StoreProductRequest extends FormRequest
return \array_merge(
$rules,
$this->pictureRules('picture', 300, 200, 300, 200),
$this->pictureRules('banner', 300, 200, 300, 200),
$this->pictureRules('packaging', 300, 200, 300, 200),
$this->pictureRules('banner', 1440, 420, 1440, 420),
$this->pictureRules('packaging', 315, 450, 315, 450),
);
}

View file

@ -8,7 +8,6 @@ use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\ActivityLog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Str;
/**
@ -143,7 +142,7 @@ class Meat extends Model
*
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function products(): MorphToMany
public function products(): \Illuminate\Database\Eloquent\Relations\MorphToMany
{
return $this->morphToMany(Product::class, 'productable');
}

View file

@ -3,13 +3,16 @@
namespace App\Models;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\ActivityLog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\Str;
/**
* @property string $label Product label.
* @property string $slug Product slug.
* @property string $description Product description.
* @property string $details Product details.
* @property string $preparation Product preparation.
@ -42,6 +45,7 @@ class Product extends Model
*/
protected $fillable = [
'label',
'slug',
'picture',
'picture_alt',
'picture_title',
@ -80,14 +84,12 @@ class Product extends Model
protected static function booted(): void
{
static::creating(function (self $product) {
$product->picture = FileStorageHelper::storeFile($product, $product->picture, true);
$product->banner = FileStorageHelper::storeFile($product, $product->banner, true);
$product->packaging = FileStorageHelper::storeFile($product, $product->packaging, true);
self::assertLabelIsUnique($product->label);
self::manageFields($product);
});
static::updating(function (self $product) {
$product->picture = FileStorageHelper::storeFile($product, $product->picture, true);
$product->banner = FileStorageHelper::storeFile($product, $product->banner, true);
$product->packaging = FileStorageHelper::storeFile($product, $product->packaging, true);
self::assertLabelIsUnique($product->label, $product->id);
self::manageFields($product);
});
static::updated(function (self $product) {
FileStorageHelper::removeOldFile($product, 'picture');
@ -103,6 +105,35 @@ class Product extends Model
// * METHODS
/**
* Manage model fields.
*
* @param self $product
* @return void
*/
private static function manageFields(self $product): void
{
$product->slug = Str::slug($product->label);
$product->picture = FileStorageHelper::storeFile($product, $product->picture, true);
$product->banner = FileStorageHelper::storeFile($product, $product->banner, true);
$product->packaging = FileStorageHelper::storeFile($product, $product->packaging, true);
}
/**
* Asserts using validation that the label is unique
*
* @param string|null $label
* @param integer|null $id
* @return void
* @throws \Illuminate\Validation\ValidationException If a range label already exists.
*/
private static function assertLabelIsUnique(string|null $label, ?int $id = null)
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, 'label', $label, $id);
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($label), $id);
}
/**
* Display a listing of the resource.
*
@ -208,4 +239,24 @@ class Product extends Model
{
return $this->morphedByMany(Approach::class, 'productable');
}
/**
* Get all product types that are assigned to this product.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function productTypes(): \Illuminate\Database\Eloquent\Relations\MorphToMany
{
return $this->morphedByMany(ProductType::class, 'productable');
}
/**
* Get all product types that are assigned to this product.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function claims(): \Illuminate\Database\Eloquent\Relations\MorphToMany
{
return $this->morphedByMany(Claim::class, 'productable');
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -95,7 +95,9 @@ return new class extends Migration
);
$productModel->packaging_alt = "attribut alt de l'image";
$productModel->packaging_title = "attribut titre de l'image";
$productModel->approach_id = rand(0, 1) ? collect($approachDemarches->random(1))->first()->getKey() : null;
$productModel->approach_id = rand(0, 1)
? collect($approachDemarches->random(1))->first()->getKey()
: null;
$productModel->new = rand(0, 1);
$productModel->discover = rand(0, 1);
$productModel->published = rand(0, 1);
@ -111,7 +113,7 @@ return new class extends Migration
]
);
}
// Morph relationships (approach->label).
// Morph relationships (productType).
for ($i = 0; $i <= rand(0, count($productTypeModels)); $i++) {
DB::table('productables')->insert(
[
@ -121,7 +123,7 @@ return new class extends Migration
]
);
}
// Morph relationships (approach->label).
// Morph relationships (claim).
for ($i = 0; $i <= rand(0, count($claimModels)); $i++) {
DB::table('productables')->insert(
[

View file

@ -0,0 +1,74 @@
<?php
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Product;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Product::query()->get()->map(function (Product $productModel) {
FileStorageHelper::removeFile($productModel, 'picture');
FileStorageHelper::removeOldFile($productModel, 'banner');
FileStorageHelper::removeOldFile($productModel, 'packaging');
$productModel->picture = FileStorageHelper::storeFile(
$productModel,
new \SplFileInfo(\resource_path(
'../database/factories/assets/products/default-picture-300x200.png'
))
);
$productModel->banner = FileStorageHelper::storeFile(
$productModel,
new \SplFileInfo(\resource_path(
'../database/factories/assets/products/default-picture-1440x420.png'
))
);
$productModel->packaging = FileStorageHelper::storeFile(
$productModel,
new \SplFileInfo(\resource_path(
'../database/factories/assets/products/default-picture-315x450.png'
))
);
$productModel->saveQuietly();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Product::query()->get()->map(function (Product $productModel) {
FileStorageHelper::removeFile($productModel, 'picture');
FileStorageHelper::removeOldFile($productModel, 'banner');
FileStorageHelper::removeOldFile($productModel, 'packaging');
$productModel->picture = FileStorageHelper::storeFile(
$productModel,
new \SplFileInfo(\resource_path(
'../database/factories/assets/products/default-picture-1440x420.png'
))
);
$productModel->banner = FileStorageHelper::storeFile(
$productModel,
new \SplFileInfo(\resource_path(
'../database/factories/assets/products/default-picture-1440x420.png'
))
);
$productModel->packaging = FileStorageHelper::storeFile(
$productModel,
new \SplFileInfo(\resource_path(
'../database/factories/assets/products/default-picture-1440x420.png'
))
);
$productModel->saveQuietly();
});
}
};

View file

@ -0,0 +1,38 @@
<?php
use App\Models\Product;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('products', function (Blueprint $table) {
$table->string('slug')->comment('Product slug.')->after('label');
});
Product::query()->get()->map(function (Product $productModel) {
$productModel->slug = Str::slug($productModel->label);
$productModel->saveQuietly();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('slug');
});
}
};

View file

@ -22,8 +22,8 @@ class ProductSeeder extends Seeder
$rangeModel = Range::inRandomOrder()->first();
DB::table('productables')->insert(
[
'product_id' => $productModel->id,
'productable_id' => $rangeModel->id,
'product_id' => $productModel->getKey(),
'productable_id' => $rangeModel->getKey(),
'productable_type' => 'App\Models\Range'
]
);
@ -32,8 +32,8 @@ class ProductSeeder extends Seeder
$subrangeModels = $rangeModel->subranges;
DB::table('productables')->insert(
[
'product_id' => $productModel->id,
'productable_id' => $subrangeModels->random()->id,
'product_id' => $productModel->getKey(),
'productable_id' => $subrangeModels->random()->getKey(),
'productable_type' => 'App\Models\Subrange'
]
);
@ -44,8 +44,8 @@ class ProductSeeder extends Seeder
$meatModel = Meat::inRandomOrder()->first();
DB::table('productables')->insert(
[
'product_id' => $productModel->id,
'productable_id' => $meatModel->id,
'product_id' => $productModel->getKey(),
'productable_id' => $meatModel->getKey(),
'productable_type' => 'App\Models\Meat'
]
);
@ -55,13 +55,45 @@ class ProductSeeder extends Seeder
$submeatModels = $meatModel->submeats;
DB::table('productables')->insert(
[
'product_id' => $productModel->id,
'productable_id' => $submeatModels->random()->id,
'product_id' => $productModel->getKey(),
'productable_id' => $submeatModels->random()->getKey(),
'productable_type' => 'App\Models\Submeat'
]
);
}
} //end if
// Morph relationships (ProductTypes).
if (\fake()->boolean(50)) {
/** @var \App\Models\ProductType $productTypeModel */
$productTypeModel = Range::inRandomOrder()->first();
DB::table('productables')->insert(
[
'product_id' => $productModel->getKey(),
'productable_id' => $productTypeModel->getKey(),
'productable_type' => 'App\Models\ProductType'
]
);
} else {
$productModel->approach_id = Range::inRandomOrder()->first()->getKey();
$productModel->saveQuietly();
}
// Morph relationships (Claims).
if (\fake()->boolean(50)) {
/** @var \App\Models\Claim $claimModel */
$claimModel = Range::inRandomOrder()->first();
DB::table('productables')->insert(
[
'product_id' => $productModel->getKey(),
'productable_id' => $claimModel->getKey(),
'productable_type' => 'App\Models\Claim'
]
);
} else {
$productModel->approach_id = Range::inRandomOrder()->first()->getKey();
$productModel->saveQuietly();
}
});
}
}

View file

@ -16,7 +16,9 @@
:selectable="(option: VselectOption) =>
(modelSelected !== null)
? (Array.isArray(modelSelected))
? !modelSelected.includes(option)
? (selectParameters.limits !== null && modelSelected.length >= selectParameters.limits[1])
? false
: !modelSelected.includes(option)
: modelSelected.id !== option.id
: true"
:filterable="true"
@ -109,7 +111,7 @@
<Transition name="fade">
<p
v-show="message"
class="message"
class="message text-danger mb-1"
>
{{ message }}
</p>
@ -172,6 +174,10 @@ const props = defineProps({
type: Object as PropType<VselectOption|VselectOption[]|null>,
default: () => { return []; }
},
limits: {
type: Object as PropType<Array<number>|null>,
default: null
},
required: {
type: Boolean,
default: false
@ -221,12 +227,14 @@ const modelSelected = ref<VselectOption|VselectOption[]|null>(props.modelselecte
/** Select parameters. */
const selectParameters = reactive<{
limits: Array<number>|null,
multiple: boolean,
required: boolean,
disabled: boolean,
placeholder: string,
fieldName: string,
}>({
limits: props.limits,
multiple: props.multiple,
required: props.required,
disabled: props.disabled,
@ -265,6 +273,7 @@ onMounted(() => {
modelSelected.value = data.modelSelected;
});
/** Select parameters. */
selectParameters.limits = data.limits;
selectParameters.multiple = Boolean(data.multiple);
selectParameters.required = Boolean(data.required);
selectParameters.disabled = Boolean(data.disabled);
@ -307,6 +316,18 @@ watch(props, () => {
watch(modelSelected, () => {
emits("selected-value-changed", modelSelected.value);
if (
modelSelected.value !== null &&
selectParameters.limits !== null &&
Array.isArray(modelSelected.value)
) {
message.value = (modelSelected.value.length < selectParameters.limits[0]) ?
trans.methods.__(`Minimum ${selectParameters.limits[0]} requis`) :
"";
message.value = (modelSelected.value.length > selectParameters.limits[1]) ?
trans.methods.__(`Maxmimum ${selectParameters.limits[1]} requis`) :
"";
}
});
// * METHODS

View file

@ -52,7 +52,8 @@
'name' => 'description',
'model' => $productModel ?? null,
'required' => true,
'helper' => __('crud.minicrud.text_warning') .
'helper' =>
__('crud.minicrud.text_warning') .
' ' .
__('validation.min.string', [
'attribute' => __('validation.attributes.description'),
@ -80,7 +81,8 @@
'name' => 'details',
'model' => $productModel ?? null,
'required' => true,
'helper' => __('crud.minicrud.text_warning') .
'helper' =>
__('crud.minicrud.text_warning') .
' ' .
__('validation.min.string', [
'attribute' => __('validation.custom.details'),
@ -108,7 +110,8 @@
'name' => 'preparation',
'model' => $productModel ?? null,
'required' => true,
'helper' => __('crud.minicrud.text_warning') .
'helper' =>
__('crud.minicrud.text_warning') .
' ' .
__('validation.min.string', [
'attribute' => __('validation.custom.preparation'),
@ -136,7 +139,8 @@
'name' => 'ingredients',
'model' => $productModel ?? null,
'required' => true,
'helper' => __('crud.minicrud.text_warning') .
'helper' =>
__('crud.minicrud.text_warning') .
' ' .
__('validation.min.string', [
'attribute' => str(__('validation.custom.ingredient'))->plural(),
@ -277,8 +281,8 @@
@include('bo.partials.inputs.varchar-picture', [
'id' => 'productBanner',
'name' => 'banner',
'width' => 300,
'height' => 200,
'width' => 1440,
'height' => 420,
'model' => $productModel ?? null,
'required' => true,
'preview' => true,
@ -347,8 +351,8 @@
@include('bo.partials.inputs.varchar-picture', [
'id' => 'productPackaging',
'name' => 'packaging',
'width' => 300,
'height' => 200,
'width' => 315,
'height' => 450,
'model' => $productModel ?? null,
'required' => true,
'preview' => true,
@ -357,10 +361,108 @@
</div>
</fieldset>
</div>
{{-- MEATS/SUBMEATS --}}
<div id="meatsTypes" class="col-12 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ str(__('models.classes.meat'))->plural()->ucFirst() }}</legend>
<div class="row">
{{-- SELECT MEAT OR SUBMEAT --}}
<div class="col-12 col-lg-6">
<label class="col-form-label" for="selectMeatType">
<b>{{ __('Sélectionner le type de :model à associer', ['model' => __('models.classes.meat')]) }}
*</b>
</label>
<select id="selectMeatType" class="form-select" onchange="updateMeatTypeInput(this.value)"
role="button">
<option value="meats" @selected(old('meats', count($productModel->meats)))>
{{ str(__('models.classes.meat'))->ucFirst() }}</option>
<option value="submeats" @selected(old('submeats', count($productModel->submeats)))>
{{ str(__('models.classes.submeat'))->ucFirst() }}</option>
</select>
<small class="form-text text-body-secondary" id="selectMeatTypeHelp">
{{ __('Sélectionner un type de :model à associer parmis la liste.', [
'model' => __('models.classes.meat'),
]) }}
</small>
</div>
{{-- MEATS --}}
<div id="meats-input" class="col-12 col-lg-6 meat-fields d-none">
@include('bo.partials.inputs.search-belongs-to-dropdown', [
'id' => 'productMeat',
'label' => __(':model associée(s)', [
'model' => str(__('models.classes.meat'))->ucFirst(),
]),
'name' => 'meats',
'model' => $productModel,
'targetClass' => 'App\Models\Meat',
'modelListPaginate' => $meatModels ?? [],
'routeName' => 'bo.product-types.json-paginate',
'fieldName' => 'label',
'multiple' => true,
'limits' => [1, 2],
'required' => true,
'disabled' => false,
'placeholder' => __('Rechercher une :model', [
'model' => __('models.classes.meat'),
]),
'helper' => __(
'Sélectionner une ou plusieurs :model qui sera associée(s) parmis la liste (minimum 1 et maximum 2).',
[
'model' => str(__('models.classes.meat'))->plural(),
]),
])
</div>
{{-- SUBMEATS --}}
<div id="submeats-input" class="col-12 col-lg-6 meat-fields d-none">
@include('bo.partials.inputs.search-belongs-to-dropdown', [
'id' => 'productSubmeats',
'label' => __(':model associée(s)', [
'model' => str(__('models.classes.submeat'))->ucFirst(),
]),
'name' => 'submeats',
'model' => $productModel,
'targetClass' => 'App\Models\Submeat',
'modelListPaginate' => $submeatModels ?? [],
'routeName' => 'bo.submeats.json-paginate',
'fieldName' => 'label',
'multiple' => true,
'limits' => [1, 2],
'required' => true,
'disabled' => false,
'placeholder' => __('Rechercher une :model', [
'model' => __('models.classes.submeat'),
]),
'helper' => __(
'Sélectionner une ou plusieurs :model qui sera associée(s) parmis la liste (minimum 1 et maximum 2).',
[
'model' => str(__('models.classes.submeat'))->plural(),
]),
])
</div>
<script>
setTimeout(() => {
updateMeatTypeInput(({{ old('submeats', count($productModel->submeats)) }}) ? "submeats" : "meats");
}, 1000);
function updateMeatTypeInput(val) {
document.querySelectorAll("#meatsTypes .meat-fields").forEach(select => {
if (select.id === `${val}-input`) {
select.classList.remove("d-none");
select.querySelector(`input[type="hidden"]`)?.removeAttribute("disabled");
} else {
select.classList.add("d-none");
select.querySelector(`input[type="hidden"]`)?.setAttribute("disabled", true);
}
});
};
</script>
</div>
</fieldset>
</div>
{{-- DEMARCH/LABELS --}}
<div class="col-12 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ str(trans_choice('models.classes.approach', \INF))->ucFirst() }}</legend>
<legend>{{ str(__('associations'))->ucFirst() }}</legend>
<div class="row">
{{-- DEMARCH --}}
<div class="col-12 col-lg-6">
@ -391,7 +493,7 @@
<div class="col-12 col-lg-6">
@include('bo.partials.inputs.search-belongs-to-dropdown', [
'id' => 'productLabels',
'label' => __(':model associé', [
'label' => __(':model associé(s)', [
'model' => str(__('models.classes.label'))->ucFirst(),
]),
'name' => 'labels',
@ -401,16 +503,67 @@
'routeName' => 'bo.approaches.json-paginate',
'fieldName' => 'name',
'multiple' => true,
'limits' => [0, 4],
'required' => true,
'disabled' => false,
'placeholder' => __('Rechercher un :model', [
'model' => __('models.classes.label'),
]),
'helper' => __(
'Sélectionner un ou plusieurs :model qui sera associé parmis la liste.',
'Sélectionner un ou plusieurs :model qui sera associé parmis la liste (minimum 0 et maximum 4).',
['model' => str(__('models.classes.label'))->plural()]),
])
</div>
{{-- PRODUCT TYPES --}}
<div class="col-12 col-lg-6">
@include('bo.partials.inputs.search-belongs-to-dropdown', [
'id' => 'productProductTypes',
'label' => __(':model associé(s)', [
'model' => str(trans_choice('models.classes.product_type', \INF))->ucFirst(),
]),
'name' => 'productTypes',
'model' => $productModel,
'targetClass' => 'App\Models\ProductType',
'modelListPaginate' => $productTypeModels ?? [],
'routeName' => 'bo.product-types.json-paginate',
'fieldName' => 'label',
'multiple' => true,
'limits' => [0, 4],
'required' => true,
'disabled' => false,
'placeholder' => __('Rechercher un :model', [
'model' => trans_choice('models.classes.product_type', 1),
]),
'helper' => __(
'Sélectionner un ou plusieurs :model qui sera associé parmis la liste (minimum 0 et maximum 4).',
['model' => trans_choice('models.classes.product_type', \INF)]),
])
</div>
{{-- CLAIMS --}}
<div class="col-12 col-lg-6">
@include('bo.partials.inputs.search-belongs-to-dropdown', [
'id' => 'productClaims',
'label' => __(':model associée(s)', [
'model' => str(__('models.classes.claim'))->ucFirst(),
]),
'name' => 'claims',
'model' => $productModel,
'targetClass' => 'App\Models\Claim',
'modelListPaginate' => $claimModels ?? [],
'routeName' => 'bo.claims.json-paginate',
'fieldName' => 'label',
'multiple' => true,
'limits' => [0, 4],
'required' => true,
'disabled' => false,
'placeholder' => __('Rechercher un :model', [
'model' => __('models.classes.claim'),
]),
'helper' => __(
'Sélectionner une ou plusieurs :model qui sera associée parmis la liste (minimum 0 et maximum 4).',
['model' => __('models.classes.claim')]),
])
</div>
</div>
</fieldset>
</div>

View file

@ -4,7 +4,7 @@
@include('bo.modules.table-col-sorter', [
'cols' => [
'label' => str(__('validation.custom.label'))->ucfirst(),
'picture' => str(__('validation.attributes.image'))->ucfirst(),
'packaging' => str(__('validation.attributes.image'))->ucfirst(),
'published' => str(__('validation.custom.publication'))->ucfirst(),
'updated_at' => str(__('validation.attributes.updated_at'))->ucfirst(),
],
@ -15,8 +15,8 @@
<tr class="border-bottom">
<td class="text-center align-middle">{{ $productModel->label }}</td>
<td class="text-center align-middle">
<img class="image-preview" src="{{ asset($productModel->picture) }}" title="{{ $productModel->picture_title }}"
alt="{{ $productModel->picture_alt }}">
<img class="image-preview" src="{{ asset($productModel->packaging) }}" title="{{ $productModel->packaging_title }}"
alt="{{ $productModel->packaging_alt }}">
</td>
@include('bo.modules.change-published-status', [
'routeName' => 'products',

View file

@ -14,6 +14,7 @@
'modelListPaginate' => $modelListPaginate,
'modelSelected' => $modelSelected,
'fieldName' => $fieldName,
'limits' => $limits ?? null,
'required' => $required,
'disabled' => $disabled,
'placeholder' => $placeholder,

View file

@ -13,6 +13,8 @@
window.__SYSTEM._routes['bo.products.json-paginate'] = '{{ route('bo.products.json-paginate') }}';
window.__SYSTEM._routes['bo.recipes.json-paginate'] = '{{ route('bo.recipes.json-paginate') }}';
window.__SYSTEM._routes['bo.videos.json-paginate'] = '{{ route('bo.videos.json-paginate') }}';
window.__SYSTEM._routes['bo.meats.json-paginate'] = '{{ route('bo.meats.json-paginate') }}';
window.__SYSTEM._routes['bo.submeats.json-paginate'] = '{{ route('bo.submeats.json-paginate') }}';
// FO
window.__SYSTEM._routes['fo.home.products'] = '{{ route('fo.home.products', ['range_slug' => 'RANGESLUG']) }}';
window.__SYSTEM._routes['fo.contact.show'] = '{{ route('fo.contact.show') }}';

View file

@ -195,6 +195,8 @@ Route::prefix('bo')
Route::patch('/meats/{meat}/update-submeats-order', [
MeatController::class, 'updateSubmeatsOrder'
])->name('meats.update-submeats-order');
Route::get('/meats/json/paginate', [MeatController::class, 'jsonPaginateModels'])
->name('meats.json-paginate');
// * SUBMEATS.
Route::resource('submeats', SubmeatController::class)
@ -206,6 +208,8 @@ Route::prefix('bo')
Route::get('/submeats/{submeat}/duplicate', [
SubmeatController::class, 'duplicate'
])->name('submeats.duplicate');
Route::get('/submeats/json/paginate', [SubmeatController::class, 'jsonPaginateModels'])
->name('submeats.json-paginate');
// * PRODUCT TYPES.
Route::resource('product_types', ProductTypeController::class)
@ -217,6 +221,8 @@ Route::prefix('bo')
Route::get('/product_types/{product_type}/duplicate', [
ProductTypeController::class, 'duplicate'
])->name('product_types.duplicate');
Route::get('/product-types/json/paginate', [ProductTypeController::class, 'jsonPaginateModels'])
->name('product_types.json-paginate');
// * CLAIMS.
Route::resource('claims', ClaimController::class);
@ -226,6 +232,8 @@ Route::prefix('bo')
Route::get('/claims/{claim}/duplicate', [
ClaimController::class, 'duplicate'
])->name('claims.duplicate');
Route::get('/claims/json/paginate', [ClaimController::class, 'jsonPaginateModels'])
->name('claims.json-paginate');
// * APPROACHES.
Route::get('/approaches/{approach_type}', [
@ -297,7 +305,7 @@ Route::prefix('bo')
// * PRODUCTS.
Route::resource('products', ProductController::class)
->withoutMiddleware('htmlsanitize')
->middleware('htmlsanitize:description,details,preparation');
->middleware('htmlsanitize:description,details,preparation,ingredients');
Route::get('/products/{product}/duplicate', [
ProductController::class, 'duplicate'
])->name('products.duplicate');