feat: add associated models (claims/productTypes/meats/submeat) fields in product form
This commit is contained in:
parent
6b847a7931
commit
34bb5c4120
16 changed files with 509 additions and 45 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
database/factories/assets/products/default-picture-300x200.png
Normal file
BIN
database/factories/assets/products/default-picture-300x200.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
BIN
database/factories/assets/products/default-picture-315x450.png
Normal file
BIN
database/factories/assets/products/default-picture-315x450.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
|
|
@ -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(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
'modelListPaginate' => $modelListPaginate,
|
||||
'modelSelected' => $modelSelected,
|
||||
'fieldName' => $fieldName,
|
||||
'limits' => $limits ?? null,
|
||||
'required' => $required,
|
||||
'disabled' => $disabled,
|
||||
'placeholder' => $placeholder,
|
||||
|
|
|
|||
|
|
@ -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') }}';
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue