Sprint2
This commit is contained in:
parent
67e6892c21
commit
aadb5c0acc
75 changed files with 4043 additions and 177 deletions
|
|
@ -1,24 +1,40 @@
|
|||
image: jakzal/phpqa:php7.3
|
||||
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
|
||||
|
||||
stages:
|
||||
- Security
|
||||
- CodingStandards
|
||||
|
||||
security:
|
||||
composer-security:
|
||||
stage: Security
|
||||
script:
|
||||
- local-php-security-checker --format=json --path=./composer.lock > security-checker-report.json
|
||||
- local-php-security-checker --format=json --path=./composer.lock > composer-security-checker-report.json
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expose_as: 'security-checker'
|
||||
expose_as: 'composer-security-checker'
|
||||
paths:
|
||||
- security-checker-report.json
|
||||
- composer-security-checker-report.json
|
||||
allow_failure: true
|
||||
|
||||
npm-security:
|
||||
stage: Security
|
||||
script:
|
||||
- audit-ci -p npm -o json > npm-security-checker-report.json
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expose_as: 'npm-security-checker'
|
||||
paths:
|
||||
- npm-security-checker-report.json
|
||||
allow_failure: true
|
||||
|
||||
phpcs:
|
||||
stage: CodingStandards
|
||||
before_script:
|
||||
- composer install
|
||||
cache:
|
||||
paths:
|
||||
- vendor/
|
||||
script:
|
||||
- phpcs --report=junit --report-file=phpcs-report.xml
|
||||
- vendor/bin/phpcs --report=junit --report-file=phpcs-report.xml
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expose_as: 'phpcs'
|
||||
|
|
|
|||
23
.vscode/settings.json
vendored
Normal file
23
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.activeBackground": "#595926",
|
||||
"activityBar.activeBorder": "#439c9c",
|
||||
"activityBar.background": "#595926",
|
||||
"activityBar.foreground": "#e7e7e7",
|
||||
"activityBar.inactiveForeground": "#e7e7e799",
|
||||
"activityBarBadge.background": "#439c9c",
|
||||
"activityBarBadge.foreground": "#15202b",
|
||||
"sash.hoverBorder": "#595926",
|
||||
"statusBar.background": "#353517",
|
||||
"statusBar.foreground": "#e7e7e7",
|
||||
"statusBarItem.hoverBackground": "#595926",
|
||||
"statusBarItem.remoteBackground": "#353517",
|
||||
"statusBarItem.remoteForeground": "#e7e7e7",
|
||||
"titleBar.activeBackground": "#353517",
|
||||
"titleBar.activeForeground": "#e7e7e7",
|
||||
"titleBar.inactiveBackground": "#35351799",
|
||||
"titleBar.inactiveForeground": "#e7e7e799"
|
||||
},
|
||||
"peacock.remoteColor": "#353517",
|
||||
"vue3snippets.enable-compile-vue-file-on-did-save-code": true
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\RangesStatusUpdate;
|
||||
use App\Jobs\ProductStatusUpdate;
|
||||
use App\Jobs\SlidersStatusUpdate;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
|
@ -17,6 +19,8 @@ class Kernel extends ConsoleKernel
|
|||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
$schedule->job(SlidersStatusUpdate::class)->everyMinute();
|
||||
$schedule->job(RangesStatusUpdate::class)->everyMinute();
|
||||
$schedule->job(ProductStatusUpdate::class)->everyMinute();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
47
app/Enums/Products/ProductLabelEnum.php
Normal file
47
app/Enums/Products/ProductLabelEnum.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Products;
|
||||
|
||||
use Kwaadpepper\Enum\BaseEnum;
|
||||
|
||||
/**
|
||||
* @method static self bio()
|
||||
* @method static self lr()
|
||||
* @method static self bea()
|
||||
* @method static self nutriscore()
|
||||
* @method static self planetscore()
|
||||
*/
|
||||
final class ProductLabelEnum extends BaseEnum
|
||||
{
|
||||
/**
|
||||
* Enum values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
'bio' => 0,
|
||||
'lr' => 1,
|
||||
'bea' => 2,
|
||||
'nutriscore' => 3,
|
||||
'planetscore' => 4
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum labels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'bio' => trans('Bio'),
|
||||
'lr' => trans('Label Rouge'),
|
||||
'bea' => trans('Bea'),
|
||||
'nutriscore' => trans('Nutriscore'),
|
||||
'planetscore' => trans('PlanetScore'),
|
||||
];
|
||||
}
|
||||
}
|
||||
41
app/Enums/Ranges/RangeCategoryEnum.php
Normal file
41
app/Enums/Ranges/RangeCategoryEnum.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Ranges;
|
||||
|
||||
use Kwaadpepper\Enum\BaseEnum;
|
||||
|
||||
/**
|
||||
* @method static self poultry()
|
||||
* @method static self egg()
|
||||
* @method static self partners()
|
||||
*/
|
||||
final class RangeCategoryEnum extends BaseEnum
|
||||
{
|
||||
/**
|
||||
* Enum values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
'poultry' => 0,
|
||||
'egg' => 1,
|
||||
'partners' => 2
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum labels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'poultry' => trans('🐔 Vollaille'),
|
||||
'egg' => trans('🥚 Oeuf'),
|
||||
'partners' => trans('🤝 Produits partenaires'),
|
||||
];
|
||||
}
|
||||
}
|
||||
153
app/Http/Controllers/Bo/ProductController.php
Normal file
153
app/Http/Controllers/Bo/ProductController.php
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Products\StoreProductRequest;
|
||||
use App\Http\Requests\Bo\Products\UpdateProductRequest;
|
||||
use App\Models\Product;
|
||||
use App\Models\Range;
|
||||
use App\Traits\Controllers\UpdateModelStatus;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ProductController extends Controller
|
||||
{
|
||||
use UpdateModelStatus;
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $products */
|
||||
$products = Product::query();
|
||||
|
||||
if ($request->search) {
|
||||
$this->searchQuery(
|
||||
$products,
|
||||
$request->search,
|
||||
// * Search in tags
|
||||
function ($q) use ($request) {
|
||||
$q->orWhereHas('tags', function ($q) use ($request) {
|
||||
$q->whereRaw(
|
||||
'UPPER(CONCAT(`tags`.`name`)) LIKE UPPER(?)',
|
||||
['%' . filter_var($request->search, FILTER_SANITIZE_STRING) . '%']
|
||||
);
|
||||
});
|
||||
},
|
||||
'title',
|
||||
'description'
|
||||
);
|
||||
}
|
||||
|
||||
$this->sortQuery($products);
|
||||
$searchFields = trans('Titre, Description');
|
||||
|
||||
$products->orderBy('updated_at', 'DESC');
|
||||
$products = $products->paginate();
|
||||
|
||||
return view('back.products.index', compact('products', 'searchFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$ranges = Range::all();
|
||||
return view('back.products.create', compact('ranges'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Products\StoreProductRequest $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(StoreProductRequest $request)
|
||||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$product = new Product();
|
||||
$product->fill($request->validated());
|
||||
|
||||
if ($product->saveOrFail()) {
|
||||
return redirect()
|
||||
->route('bo.products.edit', $product)
|
||||
->with('success', trans(
|
||||
':model a été créé',
|
||||
['model' => Str::singular(trans('models.classes.products'))]
|
||||
));
|
||||
}
|
||||
return back();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\Product $product
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function edit(Product $product)
|
||||
{
|
||||
$ranges = Range::all();
|
||||
return view('back.products.edit', [
|
||||
'productModel' => $product,
|
||||
'ranges' => $ranges,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Products\UpdateProductRequest $request
|
||||
* @param \App\Models\Product $product
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(UpdateProductRequest $request, Product $product)
|
||||
{
|
||||
return DB::transaction(function () use ($request, $product) {
|
||||
$product->fill($request->validated());
|
||||
/** @var \App\Models\IncoNutritionalValue */
|
||||
$nut = $product->incoNutritionalValues()->firstOrNew();
|
||||
$nut->fill($request->validated());
|
||||
$nut->saveOrFail();
|
||||
|
||||
if ($product->saveOrFail()) {
|
||||
return redirect()
|
||||
->route('bo.products.edit', $product)
|
||||
->with('success', trans(
|
||||
':model a été mis à jour',
|
||||
['model' => Str::singular(trans('models.classes.products'))]
|
||||
));
|
||||
}
|
||||
return back();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Product $product
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Product $product)
|
||||
{
|
||||
if ($product->deleteOrFail()) {
|
||||
return redirect()
|
||||
->route('bo.products.index')
|
||||
->with('warning', trans(
|
||||
':model a bien été supprimé',
|
||||
['model' => Str::singular(trans('models.classes.products'))]
|
||||
));
|
||||
}
|
||||
return back();
|
||||
}
|
||||
}
|
||||
197
app/Http/Controllers/Bo/RangeController.php
Normal file
197
app/Http/Controllers/Bo/RangeController.php
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Ranges\RangeModelTitleRequest;
|
||||
use App\Http\Requests\Bo\Ranges\RangeSearchUriRequest;
|
||||
use App\Http\Requests\Bo\Ranges\StoreRangeRequest;
|
||||
use App\Http\Requests\Bo\Ranges\UpdateRangeRequest;
|
||||
use App\Http\Resources\UriResource;
|
||||
use App\Models\Breeder;
|
||||
use App\Models\Range;
|
||||
use App\Models\Recipe;
|
||||
use App\Traits\Controllers\UpdateModelStatus;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class RangeController extends Controller
|
||||
{
|
||||
use UpdateModelStatus;
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $ranges */
|
||||
$ranges = Range::query();
|
||||
|
||||
if ($request->search) {
|
||||
$this->searchQuery(
|
||||
$ranges,
|
||||
$request->search,
|
||||
// * Search in tags
|
||||
function ($q) use ($request) {
|
||||
$q->orWhereHas('tags', function ($q) use ($request) {
|
||||
$q->whereRaw(
|
||||
'UPPER(CONCAT(`tags`.`name`)) LIKE UPPER(?)',
|
||||
['%' . filter_var($request->search, FILTER_SANITIZE_STRING) . '%']
|
||||
);
|
||||
});
|
||||
},
|
||||
'title',
|
||||
'description'
|
||||
);
|
||||
}
|
||||
|
||||
$this->sortQuery($ranges);
|
||||
$searchFields = trans('Titre, Description');
|
||||
|
||||
$ranges->orderBy('updated_at', 'DESC');
|
||||
$ranges = $ranges->paginate();
|
||||
|
||||
return view('back.ranges.index', compact('ranges', 'searchFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('back.ranges.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Ranges\StoreRangeRequest $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(StoreRangeRequest $request)
|
||||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$range = new Range();
|
||||
$range->fill($request->validated());
|
||||
|
||||
if ($range->saveOrFail()) {
|
||||
return redirect()
|
||||
->route('bo.ranges.edit', $range)
|
||||
->with('success', trans(
|
||||
':model a été créé',
|
||||
['model' => Str::singular(trans('models.classes.ranges'))]
|
||||
));
|
||||
}
|
||||
return back();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\Range $range
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function edit(Range $range)
|
||||
{
|
||||
return view('back.ranges.edit', [
|
||||
'rangeModel' => $range
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Ranges\UpdateRangeRequest $request
|
||||
* @param \App\Models\Range $range
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(UpdateRangeRequest $request, Range $range)
|
||||
{
|
||||
return DB::transaction(function () use ($request, $range) {
|
||||
$range->fill($request->validated());
|
||||
|
||||
if ($range->saveOrFail()) {
|
||||
return redirect()
|
||||
->route('bo.ranges.edit', $range)
|
||||
->with('success', trans(
|
||||
':model a été mis à jour',
|
||||
['model' => Str::singular(trans('models.classes.ranges'))]
|
||||
));
|
||||
}
|
||||
return back();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Range $range
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Range $range)
|
||||
{
|
||||
if ($range->deleteOrFail()) {
|
||||
return redirect()
|
||||
->route('bo.ranges.index')
|
||||
->with('warning', trans(
|
||||
':model a bien été supprimé',
|
||||
['model' => Str::singular(trans('models.classes.ranges'))]
|
||||
));
|
||||
}
|
||||
return back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search uris for ranges input links
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Ranges\RangeSearchUriRequest $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function searchUri(RangeSearchUriRequest $request)
|
||||
{
|
||||
|
||||
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Breeder> */
|
||||
$breeders = Breeder::where('firstname', 'LIKE', "%$request->search%")
|
||||
->orWhere('lastname', 'LIKE', "%$request->search%")
|
||||
->orderBy('updated_at')->paginate();
|
||||
|
||||
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Recipe> */
|
||||
$recipes = Recipe::where('title', 'LIKE', "%$request->search%")
|
||||
->orWhere('slug', 'LIKE', "%$request->search%")
|
||||
->orderBy('updated_at')->paginate();
|
||||
|
||||
return new UriResource(
|
||||
collect($breeders->all())
|
||||
->merge($recipes->all())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Model title input text uri (from DB value)
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Ranges\RangeModelTitleRequest $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function modelTitle(RangeModelTitleRequest $request)
|
||||
{
|
||||
$title = '';
|
||||
switch ($request->model) {
|
||||
case Breeder::class:
|
||||
/** @var \App\Models\Breeder */
|
||||
$breeder = Breeder::whereId($request->id)->first();
|
||||
$title = \trim(optional($breeder)->lastname . ' ' . optional($breeder)->firstname);
|
||||
break;
|
||||
case Recipe::class:
|
||||
$title = Recipe::whereId($request->id)->first()->title ?? '';
|
||||
break;
|
||||
}
|
||||
return \response()->json($title);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,20 +4,19 @@ namespace App\Http\Controllers\Bo;
|
|||
|
||||
use App\Enums\Sliders\SliderType;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\ChangeOrderRequest;
|
||||
use App\Http\Requests\Bo\Sliders\StoreSliderRequest;
|
||||
use App\Http\Requests\Bo\Sliders\UpdateSliderRequest;
|
||||
use App\Models\Slider;
|
||||
use App\Traits\Controllers\UpdateModelStatus;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use App\Traits\Models\ChangesModelOrder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Str;
|
||||
use Kwaadpepper\Enum\Exceptions\EnumNotRoutableException;
|
||||
|
||||
class SliderController extends Controller
|
||||
{
|
||||
use ChangesModelOrder;
|
||||
use UpdateModelStatus;
|
||||
|
||||
/**
|
||||
|
|
@ -145,43 +144,4 @@ class SliderController extends Controller
|
|||
}
|
||||
return back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Item Order
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\ChangeOrderRequest $request
|
||||
* @param \App\Models\Slider $slider
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function changeOrder(ChangeOrderRequest $request, Slider $slider)
|
||||
{
|
||||
try {
|
||||
DB::transaction(function () use ($request, $slider) {
|
||||
$tmp = Slider::query();
|
||||
if ($request->action === 'up') {
|
||||
// * Push Item upward
|
||||
$tmp->where('order', '<', $slider->order)
|
||||
->orderBy('order', 'DESC');
|
||||
} else {
|
||||
// * Push Item downward
|
||||
$tmp->where('order', '>', $slider->order)
|
||||
->orderBy('order', 'ASC');
|
||||
}
|
||||
$tmp->whereType('type', $slider->type);
|
||||
$tmp = $tmp->firstOrFail();
|
||||
$newOrder = $tmp->order;
|
||||
$tmp->order = $slider->order;
|
||||
$slider->order = $newOrder;
|
||||
if (!$slider->saveOrFail() or !$tmp->saveOrFail()) {
|
||||
throw new \ErrorException("Error Processing Request", 1);
|
||||
}
|
||||
});
|
||||
} catch (\ErrorException | ModelNotFoundException $e) {
|
||||
Session::flash('error', trans('Erreur lors de la modification de l\'ordre :message', [
|
||||
'message' => $e->getMessage()
|
||||
]));
|
||||
} //end try
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
141
app/Http/Requests/Bo/Products/StoreProductRequest.php
Normal file
141
app/Http/Requests/Bo/Products/StoreProductRequest.php
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Products;
|
||||
|
||||
use App\Enums\Products\ProductLabelEnum;
|
||||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
|
||||
|
||||
/**
|
||||
* Unicity for fields is handled by model events
|
||||
*/
|
||||
class StoreProductRequest extends FormRequest
|
||||
{
|
||||
use HasPicture;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth('backend')->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'slug' => Str::slug($this->title),
|
||||
'status' => $this->status ? true : false,
|
||||
'show_ingredients' => $this->show_ingredients ? true : false,
|
||||
'show_preparation_advices' => $this->show_preparation_advices ? true : false,
|
||||
'show_sales_pitch' => $this->show_sales_pitch ? true : false,
|
||||
'show_allergen_list' => $this->show_allergen_list ? true : false,
|
||||
'show_nutrionnal_values' => $this->show_nutrionnal_values ? true : false,
|
||||
'show_portions' => $this->show_portions ? true : false,
|
||||
'labels' => $this->labels ?? []
|
||||
]);
|
||||
$this->mergePicture();
|
||||
$this->mergePicture('picture_thumb');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return \array_merge(
|
||||
[
|
||||
'title' => 'required|min:6|max:255',
|
||||
'slug' => 'required|max:255',
|
||||
'seo_title' => 'required|min:45|max:70',
|
||||
'seo_description' => 'required|min:50|max:160',
|
||||
'description' => 'required|string|min:15',
|
||||
'picture_alt' => 'required_with:picture|nullable|string|min:6|max:255',
|
||||
'picture_title' => 'required_with:picture|nullable|string|min:6|max:255',
|
||||
'picture_thumb_alt' => 'required_with:picture_thumb|nullable|string|min:6|max:255',
|
||||
'picture_thumb_title' => 'required_with:picture_thumb|nullable|string|min:6|max:255',
|
||||
'publish_at' => 'nullable|date_format:Y-m-d\TH:i',
|
||||
'unpublish_at' => 'nullable|date_format:Y-m-d\TH:i',
|
||||
'status' => 'required|boolean',
|
||||
'range_id' => 'required|exists:ranges,id',
|
||||
|
||||
'ingredients' => 'required|string|min:15',
|
||||
'preparation_advices' => 'required|string|min:15',
|
||||
'sales_pitch' => 'required|string|min:15',
|
||||
'allergen_list' => 'required|string|min:15',
|
||||
'number_portion' => 'required|numeric|min:0',
|
||||
|
||||
'show_ingredients' => 'required|boolean',
|
||||
'show_preparation_advices' => 'required|boolean',
|
||||
'show_sales_pitch' => 'required|boolean',
|
||||
'show_allergen_list' => 'required|boolean',
|
||||
'show_nutrionnal_values' => 'required|boolean',
|
||||
'show_portions' => 'required|boolean',
|
||||
|
||||
'labels' => 'sometimes|array',
|
||||
'labels.*' => 'sometimes|array',
|
||||
'labels.*.value' => ['required', new EnumIsValidRule(ProductLabelEnum::class)],
|
||||
|
||||
'weight' => 'required|numeric',
|
||||
'energy' => 'required|numeric',
|
||||
'energy_kj' => 'required|numeric',
|
||||
'protein' => 'required|numeric',
|
||||
'carbohydrates' => 'required|numeric',
|
||||
'sugar' => 'required|numeric',
|
||||
'fat' => 'required|numeric',
|
||||
'saturated_fatty_acids' => 'required|numeric',
|
||||
'dietary_fiber' => 'required|numeric',
|
||||
'salt' => 'required|numeric',
|
||||
],
|
||||
$this->pictureRules('picture', 700, 700, 700, 700),
|
||||
$this->pictureRules('picture_thumb', 300, 300, 300, 300)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'title' => trans('title'),
|
||||
'slug' => trans('title'),
|
||||
'seo_title' => trans('title') . ' seo',
|
||||
'seo_description' => trans('description') . ' seo',
|
||||
'description' => trans('description'),
|
||||
'picture' => trans('illustration'),
|
||||
'picture_alt' => trans('Texte alternatif de l\'illustration'),
|
||||
'picture_title' => trans('Titre de l\'illustration'),
|
||||
'picture_thumb' => trans('vignette'),
|
||||
'picture_thumb_alt' => trans('Texte alternatif de la vignette'),
|
||||
'picture_thumb_title' => trans('Titre de la vignette'),
|
||||
'status' => trans('publié ?'),
|
||||
'publish_at' => trans('date de publication'),
|
||||
'unpublish_at' => trans('date de dépublication'),
|
||||
|
||||
'nutritional' => trans('valeurs nutritionelles'),
|
||||
'nutritional.energy' => trans('énergie'),
|
||||
'nutritional.energy_kj' => trans('énergie kj'),
|
||||
'nutritional.protein' => trans('protéines'),
|
||||
'nutritional.carbohydrates' => trans('glucides'),
|
||||
'nutritional.sugar' => trans('sucres'),
|
||||
'nutritional.fat' => trans('matière grasse'),
|
||||
'nutritional.saturated_fatty_acids' => trans('acides gras saturés'),
|
||||
'nutritional.dietary_fiber' => trans('fibres alimentaires'),
|
||||
'nutritional.salt' => trans('sel'),
|
||||
];
|
||||
}
|
||||
}
|
||||
10
app/Http/Requests/Bo/Products/UpdateProductRequest.php
Normal file
10
app/Http/Requests/Bo/Products/UpdateProductRequest.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Products;
|
||||
|
||||
/**
|
||||
* Unicity for fields is handled by model events
|
||||
*/
|
||||
class UpdateProductRequest extends StoreProductRequest
|
||||
{
|
||||
}
|
||||
43
app/Http/Requests/Bo/Ranges/RangeModelTitleRequest.php
Normal file
43
app/Http/Requests/Bo/Ranges/RangeModelTitleRequest.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Ranges;
|
||||
|
||||
use App\Models\Breeder;
|
||||
use App\Models\Recipe;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class RangeModelTitleRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->check() and $this->json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'id' => 'required|numeric',
|
||||
'model' => [
|
||||
\sprintf('in:%s,%s', Breeder::class, Recipe::class),
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed,Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
|
||||
function ($attribute, $value, $fail) {
|
||||
$table = (new $value())->getTable();
|
||||
Validator::validate(['model' => $this->id], [
|
||||
'model' => "required|exists:{$table},id",
|
||||
]);
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
31
app/Http/Requests/Bo/Ranges/RangeSearchUriRequest.php
Normal file
31
app/Http/Requests/Bo/Ranges/RangeSearchUriRequest.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Ranges;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class RangeSearchUriRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->check() and $this->json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'search' => 'required|string',
|
||||
'page' => 'sometimes|numeric',
|
||||
];
|
||||
}
|
||||
}
|
||||
128
app/Http/Requests/Bo/Ranges/StoreRangeRequest.php
Normal file
128
app/Http/Requests/Bo/Ranges/StoreRangeRequest.php
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Ranges;
|
||||
|
||||
use App\Enums\Ranges\RangeCategoryEnum;
|
||||
use App\Models\Breeder;
|
||||
use App\Models\Recipe;
|
||||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
|
||||
|
||||
/**
|
||||
* Unicity for fields is handled by model events
|
||||
*/
|
||||
class StoreRangeRequest extends FormRequest
|
||||
{
|
||||
use HasPicture;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth('backend')->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'slug' => Str::slug($this->title),
|
||||
'status' => $this->status ? true : false
|
||||
]);
|
||||
$this->mergePicture();
|
||||
$this->mergePicture('picture_thumb');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return \array_merge(
|
||||
[
|
||||
'title' => 'required|min:6|max:255',
|
||||
'slug' => 'required|max:255',
|
||||
'category' => ['required', new EnumIsValidRule(RangeCategoryEnum::class)],
|
||||
'seo_title' => 'required|min:45|max:70',
|
||||
'seo_description' => 'required|min:50|max:160',
|
||||
'description' => 'required|string|min:15',
|
||||
'picture_alt' => 'required_with:picture|nullable|string|min:6|max:255',
|
||||
'picture_title' => 'required_with:picture|nullable|string|min:6|max:255',
|
||||
'picture_thumb_alt' => 'required_with:picture_thumb|nullable|string|min:6|max:255',
|
||||
'picture_thumb_title' => 'required_with:picture_thumb|nullable|string|min:6|max:255',
|
||||
'publish_at' => 'nullable|date_format:Y-m-d\TH:i',
|
||||
'unpublish_at' => 'nullable|date_format:Y-m-d\TH:i',
|
||||
'status' => 'required|boolean',
|
||||
'link1' => self::getUrlValidationRules(),
|
||||
'link2' => self::getUrlValidationRules(),
|
||||
],
|
||||
$this->pictureRules('picture', 700, 700, 700, 700),
|
||||
$this->pictureRules('picture_thumb', 300, 300, 300, 300)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'title' => trans('title'),
|
||||
'slug' => trans('title'),
|
||||
'seo_title' => trans('title') . ' seo',
|
||||
'seo_description' => trans('description') . ' seo',
|
||||
'description' => trans('description'),
|
||||
'picture' => trans('illustration'),
|
||||
'picture_alt' => trans('Texte alternatif de l\'illustration'),
|
||||
'picture_title' => trans('Titre de l\'illustration'),
|
||||
'picture_thumb' => trans('vignette'),
|
||||
'picture_thumb_alt' => trans('Texte alternatif de la vignette'),
|
||||
'picture_thumb_title' => trans('Titre de la vignette'),
|
||||
'status' => trans('publié ?'),
|
||||
'publish_at' => trans('date de publication'),
|
||||
'unpublish_at' => trans('date de dépublication'),
|
||||
'link1' => trans('lien 1'),
|
||||
'link2' => trans('lien 2'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Url validation rules
|
||||
* Validates an internal url format
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getUrlValidationRules(): array
|
||||
{
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
return ['nullable', function ($attribute, $value, $fail) {
|
||||
$params = collect(\explode('#', \ltrim(\rtrim($value, '#'), '#')));
|
||||
if (
|
||||
!\in_array(
|
||||
$params->get(0),
|
||||
[Breeder::class, Recipe::class]
|
||||
)
|
||||
) {
|
||||
$fail(trans('Invalid internal url'));
|
||||
return;
|
||||
}
|
||||
// Check if model id exists. Param 0 is class, 1 is id.
|
||||
if (!\forward_static_call([$params->get(0), 'find'], $params->get(1))) {
|
||||
$fail(trans("$attribute n\'est plus un lien valide."));
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
10
app/Http/Requests/Bo/Ranges/UpdateRangeRequest.php
Normal file
10
app/Http/Requests/Bo/Ranges/UpdateRangeRequest.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Ranges;
|
||||
|
||||
/**
|
||||
* Unicity for fields is handled by model events
|
||||
*/
|
||||
class UpdateRangeRequest extends StoreRangeRequest
|
||||
{
|
||||
}
|
||||
27
app/Http/Resources/BreederUriResource.php
Normal file
27
app/Http/Resources/BreederUriResource.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\Breeder;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class BreederUriResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param mixed $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
/** @var \App\Models\Breeder */
|
||||
$model = $this->resource;
|
||||
return [
|
||||
'class' => Breeder::class,
|
||||
'label' => \trim("{$model->lastname} {$model->firstname}"),
|
||||
'value' => $model->id
|
||||
];
|
||||
}
|
||||
}
|
||||
27
app/Http/Resources/RecipeUriResource.php
Normal file
27
app/Http/Resources/RecipeUriResource.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\Recipe;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class RecipeUriResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param mixed $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
/** @var \App\Models\Recipe */
|
||||
$model = $this->resource;
|
||||
return [
|
||||
'class' => Recipe::class,
|
||||
'label' => \trim("{$model->title}"),
|
||||
'value' => $model->id
|
||||
];
|
||||
}
|
||||
}
|
||||
37
app/Http/Resources/UriResource.php
Normal file
37
app/Http/Resources/UriResource.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\Breeder;
|
||||
use App\Models\Recipe;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class UriResource extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @param mixed $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
/** @var \Illuminate\Support\Collection $collection */
|
||||
$collection = $this->resource;
|
||||
return $collection->map(function (Model $model) {
|
||||
switch (\get_class($model)) {
|
||||
case Breeder::class:
|
||||
return new BreederUriResource($model);
|
||||
case Recipe::class:
|
||||
return new RecipeUriResource($model);
|
||||
default:
|
||||
throw new \RuntimeException(sprintf(
|
||||
'Unhandled model %s',
|
||||
\get_class($model)
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
41
app/Jobs/ProductStatusUpdate.php
Normal file
41
app/Jobs/ProductStatusUpdate.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Product;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Update published and visibility attributes reading their
|
||||
* publish_at and unpublish_at attributes.
|
||||
*/
|
||||
class ProductStatusUpdate
|
||||
{
|
||||
/**
|
||||
* Called when the job will be executed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$productsToPublish = Product::where('publish_at', '<=', Carbon::now()->toDateTime())
|
||||
->whereNotNull('publish_at')
|
||||
->whereStatus(false)->get();
|
||||
Log::debug(__CLASS__ . " products to publish " . $productsToPublish->count());
|
||||
foreach ($productsToPublish as $product) {
|
||||
$product->publish_at = null;
|
||||
$product->status = true;
|
||||
$product->saveOrFail();
|
||||
}
|
||||
$productsToUnPublish = Product::where('unpublish_at', '<=', Carbon::now()->toDateTime())
|
||||
->whereNotNull('unpublish_at')
|
||||
->whereStatus(true)->get();
|
||||
Log::debug(__CLASS__ . " products to unpublish " . $productsToPublish->count());
|
||||
foreach ($productsToUnPublish as $product) {
|
||||
$product->unpublish_at = null;
|
||||
$product->status = false;
|
||||
$product->saveOrFail();
|
||||
}
|
||||
}
|
||||
}
|
||||
41
app/Jobs/RangesStatusUpdate.php
Normal file
41
app/Jobs/RangesStatusUpdate.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Range;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Update published and visibility attributes reading their
|
||||
* publish_at and unpublish_at attributes.
|
||||
*/
|
||||
class RangesStatusUpdate
|
||||
{
|
||||
/**
|
||||
* Called when the job will be executed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$rangesToPublish = Range::where('publish_at', '<=', Carbon::now()->toDateTime())
|
||||
->whereNotNull('publish_at')
|
||||
->whereStatus(false)->get();
|
||||
Log::debug(__CLASS__ . " ranges to publish " . $rangesToPublish->count());
|
||||
foreach ($rangesToPublish as $range) {
|
||||
$range->publish_at = null;
|
||||
$range->status = true;
|
||||
$range->saveOrFail();
|
||||
}
|
||||
$rangesToUnPublish = Range::where('unpublish_at', '<=', Carbon::now()->toDateTime())
|
||||
->whereNotNull('unpublish_at')
|
||||
->whereStatus(true)->get();
|
||||
Log::debug(__CLASS__ . " ranges to unpublish " . $rangesToPublish->count());
|
||||
foreach ($rangesToUnPublish as $range) {
|
||||
$range->unpublish_at = null;
|
||||
$range->status = false;
|
||||
$range->saveOrFail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -192,6 +192,50 @@ class ToolboxHelper
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove pictures in ckeditor
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $attribute
|
||||
* @return void
|
||||
*/
|
||||
public static function removeImagesOnCkEditor(Model $model, string $attribute)
|
||||
{
|
||||
// * Remove inline uploaded images
|
||||
collect(ToolboxHelper::getCkEditorLocalUploadedFiles($model->getOriginal($attribute)))
|
||||
->map(function (string $originalPicturePath) {
|
||||
// * If a picture is not used anymore
|
||||
if (File::exists(\public_path($originalPicturePath))) {
|
||||
File::delete(\public_path($originalPicturePath));
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Remove unused pictures in ckeditor
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $attribute
|
||||
* @return void
|
||||
*/
|
||||
public static function removeUnusedImagesOnCkEditor(Model $model, string $attribute)
|
||||
{
|
||||
// * Remove unused inline uploaded images
|
||||
if ($model->{$attribute} != $model->getOriginal($attribute)) {
|
||||
$updatedPicturesPath = collect(
|
||||
ToolboxHelper::getCkEditorLocalUploadedFiles($model->{$attribute})
|
||||
);
|
||||
collect(ToolboxHelper::getCkEditorLocalUploadedFiles($model->getOriginal($attribute)))
|
||||
->map(function (string $originalPicturePath) use ($updatedPicturesPath) {
|
||||
// * If a picture is not used anymore
|
||||
if (!$updatedPicturesPath->contains($originalPicturePath)) {
|
||||
if (File::exists(\public_path($originalPicturePath))) {
|
||||
File::delete(\public_path($originalPicturePath));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an image of different types
|
||||
*
|
||||
|
|
|
|||
11
app/Models/Breeder.php
Normal file
11
app/Models/Breeder.php
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Breeder extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
}
|
||||
72
app/Models/IncoNutritionalValue.php
Normal file
72
app/Models/IncoNutritionalValue.php
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class IncoNutritionalValue extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'weight',
|
||||
'energy',
|
||||
'energy_kj',
|
||||
'protein',
|
||||
'carbohydrates',
|
||||
'sugar',
|
||||
'fat',
|
||||
'saturated_fatty_acids',
|
||||
'dietary_fiber',
|
||||
'salt',
|
||||
];
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Inherited from LG, will use for arcadia stream inbound
|
||||
*
|
||||
* @param array $xmlInco
|
||||
* @return array
|
||||
*/
|
||||
public static function convertTranslateXmlInco(array $xmlInco): array
|
||||
{
|
||||
$cvt = [
|
||||
'' => 'weight',
|
||||
'val_energ_kcal_r' => 'energy',
|
||||
'val_energ_kj_r' => 'energy_kj',
|
||||
'proteine_r' => 'protein',
|
||||
'sucre_r' => 'sugar',
|
||||
'mat_grass_r' => 'fat',
|
||||
'fib_alim_r' => 'dietary_fiber',
|
||||
'glucides_r' => 'carbohydrates',
|
||||
'ags_r' => 'saturated_fatty_acids',
|
||||
'sel_r' => 'salt'
|
||||
];
|
||||
$o = [];
|
||||
|
||||
foreach ($cvt as $k => $t) {
|
||||
$o[$t] = $xmlInco[$k] ?? null;
|
||||
}
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* BelongsTo product relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ use App\Enums\PublishedStatus;
|
|||
use App\Lib\ToolboxHelper;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
use Kwaadpepper\Enum\Traits\CastsEnums;
|
||||
|
||||
|
|
@ -85,27 +84,14 @@ class News extends Model
|
|||
}
|
||||
});
|
||||
static::updated(function (self $model) {
|
||||
// * Remove unused inline uploaded images
|
||||
if ($model->content != $model->getOriginal('content')) {
|
||||
$updatedPicturesPath = collect(
|
||||
ToolboxHelper::getCkEditorLocalUploadedFiles($model->content)
|
||||
);
|
||||
collect(ToolboxHelper::getCkEditorLocalUploadedFiles($model->getOriginal('content')))
|
||||
->map(function (string $originalPicturePath) use ($updatedPicturesPath) {
|
||||
// * If a picture is not used anymore
|
||||
if (!$updatedPicturesPath->contains($originalPicturePath)) {
|
||||
if (File::exists(\public_path($originalPicturePath))) {
|
||||
File::delete(\public_path($originalPicturePath));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
ToolboxHelper::removeUnusedImagesOnCkEditor($model, 'content');
|
||||
if ($model->picture != $model->getOriginal('picture')) {
|
||||
$model->picture = ToolboxHelper::removeOldImage($model, 'picture');
|
||||
}
|
||||
});
|
||||
static::deleting(function (self $model) {
|
||||
ToolboxHelper::removeImage($model, 'picture');
|
||||
ToolboxHelper::removeImagesOnCkEditor($model, 'content');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
204
app/Models/Product.php
Normal file
204
app/Models/Product.php
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\Products\ProductLabelEnum;
|
||||
use App\Lib\ToolboxHelper;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Product extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'slug',
|
||||
'seo_title',
|
||||
'seo_description',
|
||||
'description',
|
||||
'picture',
|
||||
'picture_title',
|
||||
'picture_alt',
|
||||
'picture_thumb',
|
||||
'picture_thumb_title',
|
||||
'picture_thumb_alt',
|
||||
'labels',
|
||||
'ingredients',
|
||||
'preparation_advices',
|
||||
'sales_pitch',
|
||||
'allergen_list',
|
||||
'number_portion',
|
||||
'status',
|
||||
'publish_at',
|
||||
'unpublish_at',
|
||||
'range_id',
|
||||
'show_ingredients',
|
||||
'show_preparation_advices',
|
||||
'show_sales_pitch',
|
||||
'show_allergen_list',
|
||||
'show_nutrionnal_values',
|
||||
'show_portions'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'labels' => 'array',
|
||||
'status' => 'bool',
|
||||
'show_ingredients' => 'bool',
|
||||
'show_preparation_advices' => 'bool',
|
||||
'show_sales_pitch' => 'bool',
|
||||
'show_allergen_list' => 'bool',
|
||||
'show_nutrionnal_values' => 'bool',
|
||||
'publish_at' => 'datetime',
|
||||
'unpublish_at' => 'datetime'
|
||||
];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
static::assertTitleIsUnique($model->title);
|
||||
self::validateLabels($model->labels);
|
||||
$model->slug = Str::slug($model->title);
|
||||
ToolboxHelper::assertNoInternalBoUrls('description', $model->description);
|
||||
ToolboxHelper::assertNoInternalBoUrls('ingredients', $model->ingredients);
|
||||
ToolboxHelper::assertNoInternalBoUrls('preparation_advices', $model->preparation_advices);
|
||||
ToolboxHelper::assertNoInternalBoUrls('sales_pitch', $model->sales_pitch);
|
||||
ToolboxHelper::assertNoInternalBoUrls('allergen_list', $model->allergen_list);
|
||||
$model->slug = Str::slug($model->title);
|
||||
$model->description = clean($model->description);
|
||||
$model->ingredients = clean($model->ingredients);
|
||||
$model->preparation_advices = clean($model->preparation_advices);
|
||||
$model->sales_pitch = clean($model->sales_pitch);
|
||||
$model->allergen_list = clean($model->allergen_list);
|
||||
$model->picture = ToolboxHelper::storeImage($model, $model->picture);
|
||||
$model->picture_thumb = ToolboxHelper::storeImage($model, $model->picture_thumb);
|
||||
if (!$model->status) {
|
||||
$model->publish_at = null;
|
||||
} else {
|
||||
$model->unpublish_at = null;
|
||||
}
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
static::assertTitleIsUnique($model->title, $model->id);
|
||||
self::validateLabels($model->labels);
|
||||
$model->slug = Str::slug($model->title);
|
||||
ToolboxHelper::assertNoInternalBoUrls('description', $model->description);
|
||||
ToolboxHelper::assertNoInternalBoUrls('ingredients', $model->ingredients);
|
||||
ToolboxHelper::assertNoInternalBoUrls('preparation_advices', $model->preparation_advices);
|
||||
ToolboxHelper::assertNoInternalBoUrls('sales_pitch', $model->sales_pitch);
|
||||
ToolboxHelper::assertNoInternalBoUrls('allergen_list', $model->allergen_list);
|
||||
$model->slug = Str::slug($model->title);
|
||||
$model->description = clean($model->description);
|
||||
$model->ingredients = clean($model->ingredients);
|
||||
$model->preparation_advices = clean($model->preparation_advices);
|
||||
$model->sales_pitch = clean($model->sales_pitch);
|
||||
$model->allergen_list = clean($model->allergen_list);
|
||||
// * Store new picture if changed
|
||||
if ($model->picture != $model->getOriginal('picture')) {
|
||||
$model->picture = ToolboxHelper::storeImage($model, $model->picture);
|
||||
}
|
||||
// * Store new picture_thumb if changed
|
||||
if ($model->picture_thumb != $model->getOriginal('picture_thumb')) {
|
||||
$model->picture_thumb = ToolboxHelper::storeImage($model, $model->picture_thumb);
|
||||
}
|
||||
});
|
||||
static::updated(function (self $model) {
|
||||
ToolboxHelper::removeUnusedImagesOnCkEditor($model, 'description');
|
||||
ToolboxHelper::removeUnusedImagesOnCkEditor($model, 'ingredients');
|
||||
ToolboxHelper::removeUnusedImagesOnCkEditor($model, 'preparation_advices');
|
||||
ToolboxHelper::removeUnusedImagesOnCkEditor($model, 'sales_pitch');
|
||||
ToolboxHelper::removeUnusedImagesOnCkEditor($model, 'allergen_list');
|
||||
if ($model->picture != $model->getOriginal('picture')) {
|
||||
$model->picture = ToolboxHelper::removeOldImage($model, 'picture');
|
||||
}
|
||||
});
|
||||
static::deleting(function (self $model) {
|
||||
ToolboxHelper::removeImage($model, 'picture');
|
||||
ToolboxHelper::removeImage($model, 'picture_thumb');
|
||||
ToolboxHelper::removeImagesOnCkEditor($model, 'description');
|
||||
ToolboxHelper::removeImagesOnCkEditor($model, 'ingredients');
|
||||
ToolboxHelper::removeImagesOnCkEditor($model, 'preparation_advices');
|
||||
ToolboxHelper::removeImagesOnCkEditor($model, 'sales_pitch');
|
||||
ToolboxHelper::removeImagesOnCkEditor($model, 'allergen_list');
|
||||
});
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* HasOne incoNutritionalValue relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function incoNutritionalValues()
|
||||
{
|
||||
return $this->hasOne(IncoNutritionalValue::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsTo range relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function range()
|
||||
{
|
||||
return $this->belongsTo(Range::class);
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Asserts using validation that the title is unique
|
||||
*
|
||||
* @param mixed $title
|
||||
* @param integer|null $id
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If an actu title already exists.
|
||||
*/
|
||||
private static function assertTitleIsUnique($title, ?int $id = null)
|
||||
{
|
||||
$table = (new self())->getTable();
|
||||
ToolboxHelper::assertFieldIsUnique($table, 'title', $title, $id);
|
||||
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($title), $id);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $labels
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If a label is invalid.
|
||||
*/
|
||||
private static function validateLabels(array $labels)
|
||||
{
|
||||
collect($labels)->map(function ($label) {
|
||||
if (!\is_object($label) or !($label instanceof ProductLabelEnum)) {
|
||||
if (
|
||||
!\is_array($label) or
|
||||
!\array_key_exists('value', $label) or
|
||||
!\in_array($label['value'], ProductLabelEnum::toValues())
|
||||
) {
|
||||
throw ValidationException::withMessages([
|
||||
'labels' => 'Invalid label given'
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
180
app/Models/Range.php
Normal file
180
app/Models/Range.php
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\Ranges\RangeCategoryEnum;
|
||||
use App\Lib\ToolboxHelper;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Kwaadpepper\Enum\Traits\CastsEnums;
|
||||
|
||||
class Range extends Model
|
||||
{
|
||||
use CastsEnums;
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'slug',
|
||||
'seo_title',
|
||||
'seo_description',
|
||||
'description',
|
||||
'category',
|
||||
'picture',
|
||||
'picture_title',
|
||||
'picture_alt',
|
||||
'picture_thumb',
|
||||
'picture_thumb_title',
|
||||
'picture_thumb_alt',
|
||||
'status',
|
||||
'publish_at',
|
||||
'unpublish_at',
|
||||
'link1',
|
||||
'link2',
|
||||
];
|
||||
|
||||
/**
|
||||
* Map attribute names to enum classes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $enumCasts = [
|
||||
'category' => RangeCategoryEnum::class
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'status' => 'bool',
|
||||
'publish_at' => 'datetime',
|
||||
'unpublish_at' => 'datetime'
|
||||
];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
// phpcs:enable
|
||||
static::creating(function (self $model) {
|
||||
static::assertTitleIsUnique($model->title);
|
||||
ToolboxHelper::assertNoInternalBoUrls('description', $model->description);
|
||||
$model->slug = Str::slug($model->title);
|
||||
$model->description = clean($model->description);
|
||||
$model->picture = ToolboxHelper::storeImage($model, $model->picture);
|
||||
$model->picture_thumb = ToolboxHelper::storeImage($model, $model->picture_thumb);
|
||||
if (!$model->status) {
|
||||
$model->publish_at = null;
|
||||
} else {
|
||||
$model->unpublish_at = null;
|
||||
}
|
||||
if ($model->link1) {
|
||||
self::validateLinkAttribute('link1', $model->link1);
|
||||
}
|
||||
if ($model->link2) {
|
||||
self::validateLinkAttribute('link2', $model->link2);
|
||||
}
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
static::assertTitleIsUnique($model->title, $model->id);
|
||||
ToolboxHelper::assertNoInternalBoUrls('description', $model->description);
|
||||
if ($model->picture != $model->getOriginal('picture')) {
|
||||
$model->picture = ToolboxHelper::storeImage($model, $model->picture);
|
||||
}
|
||||
if ($model->picture_thumb != $model->getOriginal('picture_thumb')) {
|
||||
$model->picture_thumb = ToolboxHelper::storeImage($model, $model->picture_thumb);
|
||||
}
|
||||
// * If status changed
|
||||
if (\boolval($model->getOriginal('status')) !== $model->status) {
|
||||
if ($model->status) {
|
||||
$model->publish_at = null;
|
||||
Session::flash('info', trans('La gamme est à présent publié'));
|
||||
} else {
|
||||
$model->unpublish_at = null;
|
||||
Session::flash('warning', trans('La gamme est à présent dépublié'));
|
||||
}
|
||||
}
|
||||
if ($model->link1) {
|
||||
self::validateLinkAttribute('link1', $model->link1);
|
||||
}
|
||||
if ($model->link2) {
|
||||
self::validateLinkAttribute('link2', $model->link2);
|
||||
}
|
||||
});
|
||||
static::updated(function (self $model) {
|
||||
if ($model->picture != $model->getOriginal('picture')) {
|
||||
$model->picture = ToolboxHelper::removeOldImage($model, 'picture');
|
||||
}
|
||||
if ($model->picture_v != $model->getOriginal('picture_v')) {
|
||||
$model->picture_v = ToolboxHelper::removeOldImage($model, 'picture_v');
|
||||
}
|
||||
});
|
||||
static::deleting(function (self $model) {
|
||||
ToolboxHelper::removeImage($model, 'picture');
|
||||
ToolboxHelper::removeImage($model, 'picture_v');
|
||||
});
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Asserts using validation that the title is unique
|
||||
*
|
||||
* @param mixed $title
|
||||
* @param integer|null $id
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If an actu title already exists.
|
||||
*/
|
||||
private static function assertTitleIsUnique($title, ?int $id = null)
|
||||
{
|
||||
$table = (new self())->getTable();
|
||||
ToolboxHelper::assertFieldIsUnique($table, 'title', $title, $id);
|
||||
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($title), $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an internal link
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param string $value
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If the $attribute link is invalid.
|
||||
*/
|
||||
public static function validateLinkAttribute(string $attribute, string $value): void
|
||||
{
|
||||
// Validate case internal url.
|
||||
$params = collect(\explode('#', \ltrim(\rtrim($value, '#'), '#')));
|
||||
if (
|
||||
!\in_array(
|
||||
$params->get(0),
|
||||
[Breeder::class, Recipe::class]
|
||||
)
|
||||
) {
|
||||
throw ValidationException::withMessages([
|
||||
$attribute => trans('Le lien contient un modèle invalide \':model\'', [
|
||||
'model' => $params->get(0)
|
||||
])
|
||||
]);
|
||||
}
|
||||
// Check if model id exists. Param 0 is class, 1 is id.
|
||||
if (!\forward_static_call([$params->get(0), 'find'], $params->get(1))) {
|
||||
throw ValidationException::withMessages([
|
||||
$attribute => trans('Le lien n\'est plus valide.')
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
app/Models/Recipe.php
Normal file
45
app/Models/Recipe.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Lib\ToolboxHelper;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Recipe extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
static::assertTitleIsUnique($model->title);
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
static::assertTitleIsUnique($model->title, $model->id);
|
||||
});
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Asserts using validation that the title is unique
|
||||
*
|
||||
* @param mixed $title
|
||||
* @param integer|null $id
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If an actu title already exists.
|
||||
*/
|
||||
private static function assertTitleIsUnique($title, ?int $id = null)
|
||||
{
|
||||
$table = (new self())->getTable();
|
||||
ToolboxHelper::assertFieldIsUnique($table, 'title', $title, $id);
|
||||
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($title), $id);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ namespace App\Providers;
|
|||
|
||||
use App\Lib\ToolboxHelper;
|
||||
use App\Models\News;
|
||||
use App\Models\Product;
|
||||
use App\Models\Range;
|
||||
use App\Models\Slider;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
|
|
@ -43,6 +45,8 @@ class RouteServiceProvider extends ServiceProvider
|
|||
->group(base_path('routes/web-fo.php'));
|
||||
});
|
||||
|
||||
Route::model('product', Product::class);
|
||||
Route::model('range', Range::class);
|
||||
Route::model('slider', Slider::class);
|
||||
Route::model('news', News::class, function ($value) {
|
||||
return News::where(function ($q) use ($value) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ trait UpdateModelStatus
|
|||
/**
|
||||
* Change model published
|
||||
*
|
||||
* @param Model $model
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function changePublished(Model $model)
|
||||
|
|
|
|||
39
app/Traits/Models/ChangesModelOrder.php
Normal file
39
app/Traits/Models/ChangesModelOrder.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits\Models;
|
||||
|
||||
use App\Http\Requests\Bo\ChangeOrderRequest;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
trait ChangesModelOrder
|
||||
{
|
||||
/**
|
||||
* Change order
|
||||
*
|
||||
* @param \App\Traits\Models\ChangeOrderRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au model utilisé.
|
||||
*/
|
||||
public function changeOrder(ChangeOrderRequest $request, Model $model): RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request, $model) {
|
||||
/** @var \Illuminate\Database\Eloquent\Model|null */
|
||||
$tmp = (\get_class($model))::where('order', $request->direction ? '<' : '>', $model->order)
|
||||
->orderBy('order', $request->direction ? 'DESC' : 'ASC')->first();
|
||||
if (!$tmp) {
|
||||
return back()->with('error', trans('L\'ordre n\'a pas pu être modifié'));
|
||||
}
|
||||
$newOrder = $tmp->order;
|
||||
$tmp->order = $model->order;
|
||||
$model->order = $newOrder;
|
||||
$tmp->saveOrFail();
|
||||
$model->saveOrFail();
|
||||
Session::flash('success', trans('L\'ordre à été modifié'));
|
||||
return back();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ trait HasPicture
|
|||
/**
|
||||
* Image rules
|
||||
*
|
||||
* @param string $name
|
||||
* @param integer|null $minWith
|
||||
* @param integer|null $minHeight
|
||||
* @param integer|null $maxWith
|
||||
|
|
@ -44,20 +45,23 @@ trait HasPicture
|
|||
* @return array
|
||||
*/
|
||||
public function pictureRules(
|
||||
string $name = 'picture',
|
||||
?int $minWith = null,
|
||||
?int $minHeight = null,
|
||||
?int $maxWith = null,
|
||||
?int $maxHeight = null
|
||||
): array {
|
||||
$this->minWith = $minWith ?? $this->minWith;
|
||||
$this->minHeight = $minHeight ?? $this->minHeight;
|
||||
$this->maxWith = $maxWith ?? $this->maxWith;
|
||||
$this->maxHeight = $maxHeight ?? $this->maxHeight;
|
||||
$sizes = (object)[
|
||||
'minWith' => $minWith ?? $this->minWith,
|
||||
'minHeight' => $minHeight ?? $this->minHeight,
|
||||
'maxWith' => $maxWith ?? $this->maxWith,
|
||||
'maxHeight' => $maxHeight ?? $this->maxHeight
|
||||
];
|
||||
return [
|
||||
'picture' => [
|
||||
$name => [
|
||||
'required',
|
||||
function (string $attribute, $value, callable $fail) {
|
||||
return $this->validatePicture($attribute, $value, $fail);
|
||||
function (string $attribute, $value, callable $fail) use ($sizes) {
|
||||
return $this->validatePicture($attribute, $value, $fail, $sizes);
|
||||
}
|
||||
]
|
||||
];
|
||||
|
|
@ -66,6 +70,7 @@ trait HasPicture
|
|||
/**
|
||||
* Images rules
|
||||
*
|
||||
* @param string $name
|
||||
* @param integer|null $minWith
|
||||
* @param integer|null $minHeight
|
||||
* @param integer|null $maxWith
|
||||
|
|
@ -73,22 +78,25 @@ trait HasPicture
|
|||
* @return array
|
||||
*/
|
||||
public function picturesRules(
|
||||
string $name = 'pictures',
|
||||
?int $minWith = null,
|
||||
?int $minHeight = null,
|
||||
?int $maxWith = null,
|
||||
?int $maxHeight = null
|
||||
): array {
|
||||
$this->minWith = $minWith ?? $this->minWith;
|
||||
$this->minHeight = $minHeight ?? $this->minHeight;
|
||||
$this->maxWith = $maxWith ?? $this->maxWith;
|
||||
$this->maxHeight = $maxHeight ?? $this->maxHeight;
|
||||
$sizes = (object)[
|
||||
'minWith' => $minWith ?? $this->minWith,
|
||||
'minHeight' => $minHeight ?? $this->minHeight,
|
||||
'maxWith' => $maxWith ?? $this->maxWith,
|
||||
'maxHeight' => $maxHeight ?? $this->maxHeight
|
||||
];
|
||||
return [
|
||||
'pictures' => 'sometimes|array',
|
||||
'pictureValidate' => 'required_with:pictures|array',
|
||||
'pictureValidate.*' => [
|
||||
$name => 'sometimes|array',
|
||||
"{$name}Validate" => "required_with:{$name}|array",
|
||||
"{$name}Validate.*" => [
|
||||
'required',
|
||||
function (string $attribute, $value, callable $fail) {
|
||||
return $this->validatePicture($attribute, $value, $fail);
|
||||
function (string $attribute, $value, callable $fail) use ($sizes) {
|
||||
return $this->validatePicture($attribute, $value, $fail, $sizes);
|
||||
}
|
||||
]
|
||||
];
|
||||
|
|
@ -100,10 +108,11 @@ trait HasPicture
|
|||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @param callback $fail
|
||||
* @param object $sizes
|
||||
* @return void
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
*/
|
||||
private function validatePicture(string $attribute, $value, callable $fail)
|
||||
private function validatePicture(string $attribute, $value, callable $fail, object $sizes)
|
||||
{
|
||||
if (!File::exists($value)) {
|
||||
$fail(trans(":attribute n'existe pas dans le système de fichier"));
|
||||
|
|
@ -119,22 +128,22 @@ trait HasPicture
|
|||
$height = intval($dimensions[1]);
|
||||
|
||||
$xMessage = trans(
|
||||
":attribute la largeur doit être comprise entre {$this->minWith}px et {$this->maxWith}px"
|
||||
":attribute la largeur doit être comprise entre {$sizes->minWith}px et {$sizes->maxWith}px"
|
||||
);
|
||||
if ($this->minWith === $this->maxWith) {
|
||||
$xMessage = trans(":attribute la largeur doit être de {$this->minWith}px");
|
||||
if ($sizes->minWith === $sizes->maxWith) {
|
||||
$xMessage = trans(":attribute la largeur doit être de {$sizes->minWith}px");
|
||||
}
|
||||
$yMessage = trans(
|
||||
":attribute la hauteur doit être comprise entre {$this->minWith}px et {$this->maxWith}px"
|
||||
":attribute la hauteur doit être comprise entre {$sizes->minWith}px et {$sizes->maxWith}px"
|
||||
);
|
||||
if ($this->minHeight === $this->maxHeight) {
|
||||
$yMessage = trans(":attribute la hauteur doit être de {$this->minWith}px");
|
||||
if ($sizes->minHeight === $sizes->maxHeight) {
|
||||
$yMessage = trans(":attribute la hauteur doit être de {$sizes->minWith}px");
|
||||
}
|
||||
|
||||
if (($width < $this->minWith) or ($width > $this->maxWith)) {
|
||||
if (($width < $sizes->minWith) or ($width > $sizes->maxWith)) {
|
||||
$fail($xMessage);
|
||||
}
|
||||
if (($height < $this->minHeight) or ($height > $this->maxHeight)) {
|
||||
if (($height < $sizes->minHeight) or ($height > $sizes->maxHeight)) {
|
||||
$fail($yMessage);
|
||||
}
|
||||
} //end if
|
||||
|
|
@ -143,29 +152,30 @@ trait HasPicture
|
|||
/**
|
||||
* Merges products pictures fields with request data
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
protected function mergePicture(): void
|
||||
protected function mergePicture(string $name = 'picture'): void
|
||||
{
|
||||
$picturePath = $this->picture;
|
||||
$picturePath = $this->{$name};
|
||||
if (is_string($picturePath)) {
|
||||
$this->merge(['pictureValidate' => []]);
|
||||
$this->merge(["{$name}Validate" => []]);
|
||||
// ! Empty picture.
|
||||
if ($picturePath === 'false') {
|
||||
// * Empty picture and exit
|
||||
$this->merge(['pictures' => []]);
|
||||
$this->merge([$name => []]);
|
||||
return;
|
||||
}
|
||||
$decodedPath = \urldecode($picturePath);
|
||||
$this->merge([
|
||||
'pictureValidate' => array_merge(
|
||||
$this->pictureValidate,
|
||||
"{$name}Validate" => array_merge(
|
||||
$this->{"{$name}Validate"},
|
||||
[\public_path(ltrim($decodedPath, '/'))]
|
||||
)
|
||||
]);
|
||||
$changedPicture = ltrim($decodedPath, '/');
|
||||
$this->merge([
|
||||
'picture' => $changedPicture
|
||||
$name => $changedPicture
|
||||
]);
|
||||
} //end if
|
||||
}
|
||||
|
|
@ -173,31 +183,32 @@ trait HasPicture
|
|||
/**
|
||||
* Merges products pictures fields with request data
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
protected function mergePictures(): void
|
||||
protected function mergePictures(string $name = 'pictures'): void
|
||||
{
|
||||
$pictures = $this->pictures;
|
||||
$pictures = $this->{$name};
|
||||
if (is_array($pictures)) {
|
||||
$this->merge(['pictureValidate' => []]);
|
||||
$this->merge(["{$name}Validate" => []]);
|
||||
foreach ($pictures as $key => $picturePath) {
|
||||
// ! Empty picture.
|
||||
if ($picturePath === 'false') {
|
||||
// * Empty picture and exit
|
||||
$this->merge(['pictures' => []]);
|
||||
$this->merge([$name => []]);
|
||||
return;
|
||||
}
|
||||
$decodedPath = \urldecode($picturePath);
|
||||
$this->merge([
|
||||
'pictureValidate' => array_merge(
|
||||
$this->pictureValidate,
|
||||
"{$name}Validate" => array_merge(
|
||||
$this->{"{$name}Validate"},
|
||||
[\public_path(ltrim($decodedPath, '/'))]
|
||||
)
|
||||
]);
|
||||
$changedPicture = $this->pictures;
|
||||
$changedPicture = $this->{$name};
|
||||
$changedPicture[$key] = ltrim($decodedPath, '/');
|
||||
$this->merge([
|
||||
'pictures' => $changedPicture
|
||||
$name => $changedPicture
|
||||
]);
|
||||
}
|
||||
} //end if
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@
|
|||
"@php artisan package:discover --ansi"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
|
||||
"local-php-security-checker"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
|
|
|
|||
49
composer.lock
generated
49
composer.lock
generated
|
|
@ -2569,16 +2569,16 @@
|
|||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "2.58.0",
|
||||
"version": "2.59.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/briannesbitt/Carbon.git",
|
||||
"reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055"
|
||||
"reference": "a9000603ea337c8df16cc41f8b6be95a65f4d0f5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/97a34af22bde8d0ac20ab34b29d7bfe360902055",
|
||||
"reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055",
|
||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/a9000603ea337c8df16cc41f8b6be95a65f4d0f5",
|
||||
"reference": "a9000603ea337c8df16cc41f8b6be95a65f4d0f5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2593,11 +2593,12 @@
|
|||
"doctrine/orm": "^2.7",
|
||||
"friendsofphp/php-cs-fixer": "^3.0",
|
||||
"kylekatarnls/multi-tester": "^2.0",
|
||||
"ondrejmirtes/better-reflection": "*",
|
||||
"phpmd/phpmd": "^2.9",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^0.12.54 || ^1.0",
|
||||
"phpunit/php-file-iterator": "^2.0.5",
|
||||
"phpunit/phpunit": "^7.5.20 || ^8.5.23",
|
||||
"phpstan/phpstan": "^0.12.99 || ^1.7.14",
|
||||
"phpunit/php-file-iterator": "^2.0.5 || ^3.0.6",
|
||||
"phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.4"
|
||||
},
|
||||
"bin": [
|
||||
|
|
@ -2654,15 +2655,19 @@
|
|||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://opencollective.com/Carbon",
|
||||
"type": "open_collective"
|
||||
"url": "https://github.com/sponsors/kylekatarnls",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/nesbot/carbon",
|
||||
"url": "https://opencollective.com/Carbon#sponsor",
|
||||
"type": "opencollective"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-04-25T19:31:17+00:00"
|
||||
"time": "2022-06-29T21:43:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
|
|
@ -3266,16 +3271,16 @@
|
|||
},
|
||||
{
|
||||
"name": "psy/psysh",
|
||||
"version": "v0.11.5",
|
||||
"version": "v0.11.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bobthecow/psysh.git",
|
||||
"reference": "c23686f9c48ca202710dbb967df8385a952a2daf"
|
||||
"reference": "77fc7270031fbc28f9a7bea31385da5c4855cb7a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/c23686f9c48ca202710dbb967df8385a952a2daf",
|
||||
"reference": "c23686f9c48ca202710dbb967df8385a952a2daf",
|
||||
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/77fc7270031fbc28f9a7bea31385da5c4855cb7a",
|
||||
"reference": "77fc7270031fbc28f9a7bea31385da5c4855cb7a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -3336,9 +3341,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/bobthecow/psysh/issues",
|
||||
"source": "https://github.com/bobthecow/psysh/tree/v0.11.5"
|
||||
"source": "https://github.com/bobthecow/psysh/tree/v0.11.7"
|
||||
},
|
||||
"time": "2022-05-27T18:03:49+00:00"
|
||||
"time": "2022-07-07T13:49:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
|
|
@ -6382,16 +6387,16 @@
|
|||
},
|
||||
{
|
||||
"name": "facade/ignition",
|
||||
"version": "2.17.5",
|
||||
"version": "2.17.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facade/ignition.git",
|
||||
"reference": "1d71996f83c9a5a7807331b8986ac890352b7a0c"
|
||||
"reference": "6acd82e986a2ecee89e2e68adfc30a1936d1ab7c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/facade/ignition/zipball/1d71996f83c9a5a7807331b8986ac890352b7a0c",
|
||||
"reference": "1d71996f83c9a5a7807331b8986ac890352b7a0c",
|
||||
"url": "https://api.github.com/repos/facade/ignition/zipball/6acd82e986a2ecee89e2e68adfc30a1936d1ab7c",
|
||||
"reference": "6acd82e986a2ecee89e2e68adfc30a1936d1ab7c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -6456,7 +6461,7 @@
|
|||
"issues": "https://github.com/facade/ignition/issues",
|
||||
"source": "https://github.com/facade/ignition"
|
||||
},
|
||||
"time": "2022-02-23T18:31:24+00:00"
|
||||
"time": "2022-06-30T18:26:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fakerphp/faker",
|
||||
|
|
|
|||
29
database/factories/BreederFactory.php
Normal file
29
database/factories/BreederFactory.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Breeder;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class BreederFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = Breeder::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'firstname' => $this->faker->firstName(),
|
||||
'lastname' => $this->faker->lastName(),
|
||||
];
|
||||
}
|
||||
}
|
||||
50
database/factories/IncoNutritionalValueFactory.php
Normal file
50
database/factories/IncoNutritionalValueFactory.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\IncoNutritionalValue;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
*/
|
||||
class IncoNutritionalValueFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = IncoNutritionalValue::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
"weight" => $this->faker->boolean(10) ?
|
||||
null : $this->faker->numberBetween(231 - 150, 231 + 150),
|
||||
"energy" => $this->faker->boolean(10) ?
|
||||
null : $this->faker->numberBetween(231 - 150, 231 + 150),
|
||||
"energy_kj" => $this->faker->boolean(10) ?
|
||||
null : $this->faker->numberBetween(964 - 150, 964 + 150),
|
||||
"protein" => $this->faker->boolean(10) ?
|
||||
null : $this->faker->numberBetween(9.2 - 3, 9.2 + 15),
|
||||
"carbohydrates" => $this->faker->boolean(10) ?
|
||||
null : $this->faker->numberBetween(20 - 10, 20 + 25),
|
||||
"sugar" => $this->faker->boolean(10) ?
|
||||
null : $this->faker->numberBetween(1.5 - 1, 1.5 + 6),
|
||||
"fat" => $this->faker->boolean(10) ?
|
||||
null : $this->faker->numberBetween(12 - 6, 12 + 6),
|
||||
"saturated_fatty_acids" => $this->faker->boolean(10) ?
|
||||
null : $this->faker->numberBetween(3.4 - 2, 3.4 + 6),
|
||||
"dietary_fiber" => $this->faker->boolean(10) ?
|
||||
null : $this->faker->numberBetween(2.9 - 2, 2.9 + 6),
|
||||
"salt" => $this->faker->boolean(10) ?
|
||||
null : $this->faker->numberBetween(1.1 - 1, 1.1 + 1.1)
|
||||
];
|
||||
}
|
||||
}
|
||||
53
database/factories/ProductFactory.php
Normal file
53
database/factories/ProductFactory.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\Products\ProductLabelEnum;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ProductFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = Product::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
$title = $this->faker->unique()->words(\mt_rand(3, 4), true);
|
||||
return [
|
||||
'title' => $title,
|
||||
'slug' => \Illuminate\Support\Str::slug($title),
|
||||
'seo_title' => $this->faker->words(8, true),
|
||||
'seo_description' => $this->faker->words(25, true),
|
||||
'description' => $this->faker->words(25, true),
|
||||
'picture' => '/assets/images/products/product_fake_700_700.jpg',
|
||||
'picture_title' => $this->faker->words(8, true),
|
||||
'picture_alt' => $this->faker->words(8, true),
|
||||
'picture_thumb' => '/assets/images/products/product_fake_thumb_300_300.jpg',
|
||||
'picture_thumb_title' => $this->faker->words(8, true),
|
||||
'picture_thumb_alt' => $this->faker->words(8, true),
|
||||
'labels' => collect(ProductLabelEnum::toArray())->random(rand(0, 4))->all(),
|
||||
'ingredients' => $this->faker->words(8, true),
|
||||
'preparation_advices' => $this->faker->text(\mt_rand(15, 150)),
|
||||
'sales_pitch' => $this->faker->text(\mt_rand(15, 150)),
|
||||
'allergen_list' => $this->faker->words(8, true),
|
||||
'number_portion' => $this->faker->numberBetween(0, 8),
|
||||
'status' => $this->faker->boolean(75),
|
||||
'show_ingredients' => $this->faker->boolean(75),
|
||||
'show_preparation_advices' => $this->faker->boolean(75),
|
||||
'show_sales_pitch' => $this->faker->boolean(75),
|
||||
'show_allergen_list' => $this->faker->boolean(75),
|
||||
'show_nutrionnal_values' => $this->faker->boolean(75),
|
||||
'show_portions' => $this->faker->boolean(75),
|
||||
];
|
||||
}
|
||||
}
|
||||
43
database/factories/RangeFactory.php
Normal file
43
database/factories/RangeFactory.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\Ranges\RangeCategoryEnum;
|
||||
use App\Models\Range;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class RangeFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = Range::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
$title = $this->faker->unique()->words(rand(6, 12), true);
|
||||
return [
|
||||
'title' => $title,
|
||||
'slug' => Str::slug($title),
|
||||
'seo_title' => $this->faker->words(8, true),
|
||||
'seo_description' => $this->faker->words(25, true),
|
||||
'description' => $this->faker->words(25, true),
|
||||
'category' => collect(RangeCategoryEnum::toArray())->random(1)->get(0),
|
||||
'picture' => '/assets/images/ranges/range_fake_700_700.jpg',
|
||||
'picture_title' => $this->faker->words(8, true),
|
||||
'picture_alt' => $this->faker->words(8, true),
|
||||
'picture_thumb' => '/assets/images/ranges/range_fake_thumb_300_300.jpg',
|
||||
'picture_thumb_title' => $this->faker->words(8, true),
|
||||
'picture_thumb_alt' => $this->faker->words(8, true),
|
||||
'status' => $this->faker->boolean(75),
|
||||
];
|
||||
}
|
||||
}
|
||||
31
database/factories/RecipeFactory.php
Normal file
31
database/factories/RecipeFactory.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Recipe;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class RecipeFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = Recipe::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
$title = $this->faker->unique()->words(rand(2, 4), true);
|
||||
return [
|
||||
'title' => $title,
|
||||
'slug' => Str::slug($title)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateBreedersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('breeders', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string('firstname')->comment('The breeder firstname');
|
||||
$table->string('lastname')->comment('The breeder lastname');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('breeders');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateRecipesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('recipes', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string('title')->comment('Nom de la recette');
|
||||
$table->string('slug')->comment('slug de la recette');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('recipes');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateRangesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('ranges', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string('title')->comment('Nom de la gamme');
|
||||
$table->string('slug')->comment('slug de la gamme');
|
||||
|
||||
$table->string('seo_title')
|
||||
->comment('Utilisé pour la balise title de la gamme. De 53 à 60 chars recommandés.');
|
||||
$table->string('seo_description')
|
||||
->comment('Utilisé pour la balise méta description de la gamme. De 50 à 160 chars recommandés.');
|
||||
|
||||
$table->text('description')->comment('Description de la gamme');
|
||||
|
||||
$table->unsignedTinyInteger('category')->comment('Enum Ranges\\RangeCategoryEnum');
|
||||
|
||||
$table->string('picture')->nullable()->comment('L\'illustration de la gamme 700x700 px.');
|
||||
$table->string('picture_title')->nullable()->comment('Attribut title de l\'image.');
|
||||
$table->string('picture_alt')->nullable()->comment('Attribut alt de l\'image.');
|
||||
|
||||
$table->string('picture_thumb')->nullable()->comment('La vignette de la gamme 300x300 px.');
|
||||
$table->string('picture_thumb_title')->nullable()->comment('Attribut title de l\'image.');
|
||||
$table->string('picture_thumb_alt')->nullable()->comment('Attribut alt de l\'image.');
|
||||
|
||||
$table->boolean('status')->comment('La gamme est visible ou non');
|
||||
|
||||
$table->timestamp('publish_at')->nullable()
|
||||
->comment('The date where the category status state should be set on.');
|
||||
$table->timestamp('unpublish_at')->nullable()
|
||||
->comment('The date where the category status state should be set off.');
|
||||
|
||||
$table->string('link1')->nullable()
|
||||
->comment('Category Link 1 #App\Models\Model#ID#, Sould be validated on store and update');
|
||||
$table->string('link2')->nullable()
|
||||
->comment('Category Link 2 #App\Models\Model#ID#, Sould be validated on store and update');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('ranges');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Range;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateProductsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('products', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string('title')->comment('Nom du produit');
|
||||
$table->string('slug')->comment('slug du produit');
|
||||
|
||||
$table->string('seo_title')
|
||||
->comment('Utilisé pour la balise title de la page. De 53 à 60 chars recommandés.');
|
||||
$table->string('seo_description')
|
||||
->comment('Utilisé pour la balise méta description de la page. De 50 à 160 chars recommandés.');
|
||||
|
||||
$table->text('description')->comment('Description du produit');
|
||||
|
||||
$table->string('picture')->nullable()->comment('L\'illustration du produit 700x700 px.');
|
||||
$table->string('picture_title')->nullable()->comment('Attribut title de l\'image.');
|
||||
$table->string('picture_alt')->nullable()->comment('Attribut alt de l\'image.');
|
||||
|
||||
$table->string('picture_thumb')->nullable()->comment('La vignette du produit 300x300 px.');
|
||||
$table->string('picture_thumb_title')->nullable()->comment('Attribut title de l\'image.');
|
||||
$table->string('picture_thumb_alt')->nullable()->comment('Attribut alt de l\'image.');
|
||||
|
||||
$table->json('labels')->comment('List of Products\\ProductLabelEnum');
|
||||
|
||||
$table->text('ingredients')->comment('Ingredients');
|
||||
$table->text('preparation_advices')->comment('Preparation advices');
|
||||
$table->text('sales_pitch')->comment('Arguments de vente');
|
||||
$table->text('allergen_list')->comment('Liste allergene issues de inco');
|
||||
$table->unsignedTinyInteger('number_portion')->comment('Portion number');
|
||||
|
||||
$table->boolean('show_ingredients')->comment('Afficher en front ou non.');
|
||||
$table->boolean('show_preparation_advices')->comment('Afficher en front ou non.');
|
||||
$table->boolean('show_sales_pitch')->comment('Afficher en front ou non.');
|
||||
$table->boolean('show_allergen_list')->comment('Afficher en front ou non.');
|
||||
$table->boolean('show_nutrionnal_values')->comment('Afficher en front ou non.');
|
||||
$table->boolean('show_portions')->comment('Afficher en front ou non.');
|
||||
|
||||
$table->boolean('status')->comment('Le produit est visible ou non');
|
||||
|
||||
$table->timestamp('publish_at')->nullable()
|
||||
->comment('The date where the product status state should be set on.');
|
||||
$table->timestamp('unpublish_at')->nullable()
|
||||
->comment('The date where the product status state should be set off.');
|
||||
|
||||
$table->foreignIdFor(Range::class)->constrained();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('products');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
// @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
|
||||
class CreateIncoNutritionalValuesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('inco_nutritional_values', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->double('weight')->nullable();
|
||||
$table->double('energy')->nullable();
|
||||
$table->double('energy_kj')->nullable();
|
||||
$table->double('protein')->nullable();
|
||||
$table->double('carbohydrates')->nullable();
|
||||
$table->double('sugar')->nullable();
|
||||
$table->double('fat')->nullable();
|
||||
$table->double('saturated_fatty_acids')->nullable();
|
||||
$table->double('dietary_fiber')->nullable();
|
||||
$table->double('salt')->nullable();
|
||||
|
||||
$table->foreignId('product_id');
|
||||
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('inco_nutritional_values');
|
||||
}
|
||||
}
|
||||
21
database/seeders/BreederSeeder.php
Normal file
21
database/seeders/BreederSeeder.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Breeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BreederSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
DB::table('breeders')->truncate();
|
||||
Breeder::factory(150)->create();
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,11 @@ class DatabaseSeeder extends Seeder
|
|||
{
|
||||
$this->call([
|
||||
NewsSeeder::class,
|
||||
SliderSeeder::class
|
||||
SliderSeeder::class,
|
||||
BreederSeeder::class,
|
||||
RangeSeeder::class,
|
||||
RecipeSeeder::class,
|
||||
ProductSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
database/seeders/IncoNutritionalValueSeeder.php
Normal file
21
database/seeders/IncoNutritionalValueSeeder.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\IncoNutritionalValue;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class IncoNutritionalValueSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
DB::table('inco_nutritional_values')->truncate();
|
||||
IncoNutritionalValue::factory(150)->create();
|
||||
}
|
||||
}
|
||||
38
database/seeders/ProductSeeder.php
Normal file
38
database/seeders/ProductSeeder.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\IncoNutritionalValue;
|
||||
use App\Models\Product;
|
||||
use App\Models\Range;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ProductSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
DB::table('inco_nutritional_values')->truncate();
|
||||
Product::all()->map(function (Product $product) {
|
||||
$product->deleteOrFail();
|
||||
});
|
||||
$ranges = Range::all();
|
||||
Product::factory(150)->make()->each(function (Product $product) use ($ranges) {
|
||||
\mt_srand();
|
||||
if (\mt_rand(0, 2)) {
|
||||
$product->range()->associate($ranges->random(1)->first());
|
||||
$product->saveOrFail();
|
||||
$product->fresh();
|
||||
/** @var \App\Models\IncoNutritionalValue $incValue */
|
||||
$incValue = IncoNutritionalValue::factory(1)->make()->get(0);
|
||||
$incValue->product()->associate($product);
|
||||
$incValue->saveOrFail();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
22
database/seeders/RangeSeeder.php
Normal file
22
database/seeders/RangeSeeder.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Range;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class RangeSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
Range::all()->map(function (Range $range) {
|
||||
$range->deleteOrFail();
|
||||
});
|
||||
Range::factory(8)->create();
|
||||
}
|
||||
}
|
||||
20
database/seeders/RecipeSeeder.php
Normal file
20
database/seeders/RecipeSeeder.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Recipe;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class RecipeSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
Recipe::truncate();
|
||||
Recipe::factory(75)->create();
|
||||
}
|
||||
}
|
||||
96
package-lock.json
generated
96
package-lock.json
generated
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "srcs",
|
||||
"name": "loue",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
"@types/bootstrap": "^5.1.12",
|
||||
"@types/cropperjs": "^1.3.0",
|
||||
"@types/toastify-js": "^1.11.1",
|
||||
"@types/vue-select": "^3.16.0",
|
||||
"@types/webpack-env": "^1.17.0",
|
||||
"axios": "^0.21",
|
||||
"bootstrap": "^5.1.3",
|
||||
|
|
@ -30,7 +31,8 @@
|
|||
"ts-loader": "^9.3.0",
|
||||
"typescript": "^4.7.3",
|
||||
"vue": "^3.2.36",
|
||||
"vue-loader": "^16.8.3"
|
||||
"vue-loader": "^16.8.3",
|
||||
"vue-select": "^4.0.0-beta.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
|
|
@ -2253,6 +2255,42 @@
|
|||
"integrity": "sha512-Ef03kGFWseAQYIQwN83WbhRxD+DOd+X6p22j9olA/TnvE0crDMc3fyoctKSpXgEDVWq5l3p98otIdpNX1pOYMA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/vue-select": {
|
||||
"version": "3.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vue-select/-/vue-select-3.16.0.tgz",
|
||||
"integrity": "sha512-eUNzHK8tY7fT6fWR3mcN3kq9BznRrFPFvmpBZ66DAz4k9aD27AwT54gvR58O9P+ql7/iC0yVvl5lUTjKZ7K1tA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"vue": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/vue-select/node_modules/@vue/compiler-sfc": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.4.tgz",
|
||||
"integrity": "sha512-WCaF33mlKLSvHDKvOD6FzTa5CI2FlMTeJf3MxJsNP0KDgRoI6RdXhHo9dtvCqV4Sywf9Owm17wTLT1Ymu/WsOQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.18.4",
|
||||
"postcss": "^8.4.14",
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/vue-select/node_modules/csstype": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
|
||||
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/vue-select/node_modules/vue": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.4.tgz",
|
||||
"integrity": "sha512-8KGyyzFSj/FrKj1y7jyEpv8J4osgZx6Lk1lVzh1aP4BqsXZhATH1r0gdJNz00MMyBhK0/m2cNoPuOZ1NzeiUEw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-sfc": "2.7.4",
|
||||
"csstype": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/webpack-env": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.17.0.tgz",
|
||||
|
|
@ -9059,6 +9097,15 @@
|
|||
"webpack": "^4.1.0 || ^5.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-select": {
|
||||
"version": "4.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-select/-/vue-select-4.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-sXufbTDluz26a39UUZE9jeh9Hojnn8zr9qDDUlVEBc7E0F+h3+Nzy8YzyFHuVNrrYLmK0zrPkb7hWFulAEW41Q==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"vue": "3.x"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-style-loader": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
||||
|
|
@ -11316,6 +11363,44 @@
|
|||
"integrity": "sha512-Ef03kGFWseAQYIQwN83WbhRxD+DOd+X6p22j9olA/TnvE0crDMc3fyoctKSpXgEDVWq5l3p98otIdpNX1pOYMA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/vue-select": {
|
||||
"version": "3.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vue-select/-/vue-select-3.16.0.tgz",
|
||||
"integrity": "sha512-eUNzHK8tY7fT6fWR3mcN3kq9BznRrFPFvmpBZ66DAz4k9aD27AwT54gvR58O9P+ql7/iC0yVvl5lUTjKZ7K1tA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"vue": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/compiler-sfc": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.4.tgz",
|
||||
"integrity": "sha512-WCaF33mlKLSvHDKvOD6FzTa5CI2FlMTeJf3MxJsNP0KDgRoI6RdXhHo9dtvCqV4Sywf9Owm17wTLT1Ymu/WsOQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.18.4",
|
||||
"postcss": "^8.4.14",
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
|
||||
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==",
|
||||
"dev": true
|
||||
},
|
||||
"vue": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.4.tgz",
|
||||
"integrity": "sha512-8KGyyzFSj/FrKj1y7jyEpv8J4osgZx6Lk1lVzh1aP4BqsXZhATH1r0gdJNz00MMyBhK0/m2cNoPuOZ1NzeiUEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@vue/compiler-sfc": "2.7.4",
|
||||
"csstype": "^3.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/webpack-env": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.17.0.tgz",
|
||||
|
|
@ -16483,6 +16568,13 @@
|
|||
"loader-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"vue-select": {
|
||||
"version": "4.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-select/-/vue-select-4.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-sXufbTDluz26a39UUZE9jeh9Hojnn8zr9qDDUlVEBc7E0F+h3+Nzy8YzyFHuVNrrYLmK0zrPkb7hWFulAEW41Q==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"vue-style-loader": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"@types/bootstrap": "^5.1.12",
|
||||
"@types/cropperjs": "^1.3.0",
|
||||
"@types/toastify-js": "^1.11.1",
|
||||
"@types/vue-select": "^3.16.0",
|
||||
"@types/webpack-env": "^1.17.0",
|
||||
"axios": "^0.21",
|
||||
"bootstrap": "^5.1.3",
|
||||
|
|
@ -35,6 +36,7 @@
|
|||
"ts-loader": "^9.3.0",
|
||||
"typescript": "^4.7.3",
|
||||
"vue": "^3.2.36",
|
||||
"vue-loader": "^16.8.3"
|
||||
"vue-loader": "^16.8.3",
|
||||
"vue-select": "^4.0.0-beta.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
resources/assets/images/products/product_fake_700_700.jpg
Normal file
BIN
resources/assets/images/products/product_fake_700_700.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
BIN
resources/assets/images/products/product_fake_thumb_300_300.jpg
Normal file
BIN
resources/assets/images/products/product_fake_thumb_300_300.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
resources/assets/images/ranges/range_fake_700_700.jpg
Normal file
BIN
resources/assets/images/ranges/range_fake_700_700.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
BIN
resources/assets/images/ranges/range_fake_thumb_300_300.jpg
Normal file
BIN
resources/assets/images/ranges/range_fake_thumb_300_300.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
|
|
@ -5,5 +5,9 @@ return [
|
|||
'news' => 'News',
|
||||
'tags' => 'Tags',
|
||||
'sliders' => 'Sliders',
|
||||
'ranges' => 'Ranges',
|
||||
'products' => 'Products',
|
||||
'recipes' => 'Recipes',
|
||||
'breeders' => 'Breeders',
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -5,5 +5,9 @@ return [
|
|||
'news' => 'Actualités',
|
||||
'tags' => 'Tags',
|
||||
'sliders' => 'Sliders',
|
||||
'ranges' => 'Gammes',
|
||||
'products' => 'Produits',
|
||||
'recipes' => 'Recettes',
|
||||
'breeders' => 'Éleveurs',
|
||||
]
|
||||
];
|
||||
|
|
|
|||
165
resources/ts/components/bo/EnumTagDropdownComponent.vue
Normal file
165
resources/ts/components/bo/EnumTagDropdownComponent.vue
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
<template>
|
||||
<div class="taggableDropdown">
|
||||
<div v-if="errorMessages.length">
|
||||
<span class="d-block text-danger" v-for="errorMessage of errorMessages">
|
||||
{{ errorMessage }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-inline-block tagInput">
|
||||
<VueTagsInputCls
|
||||
v-model="tag"
|
||||
:tags="tags"
|
||||
:autocomplete-items="filteredItems"
|
||||
:autocomplete-min-length="0"
|
||||
@before-adding-tag="beforeTagSave"
|
||||
@tags-changed="changedTags"
|
||||
:max-tags="6"
|
||||
:placeholder="__('Ajoutez ici vos étiquettes')"
|
||||
/>
|
||||
<div class="text-center py-2" v-if="waitingListObjects.length">
|
||||
<button class="btn btn-sm btn-dark ms-2" v-on:click.prevent="cancelTags">
|
||||
{{ __("Annuler") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="(sTag, index) in tags" :key="index">
|
||||
<input
|
||||
:name="`${name}[${String(index)}][value]`"
|
||||
type="hidden"
|
||||
:value="sTag.value"
|
||||
/>
|
||||
<input
|
||||
:name="`${name}[${String(index)}][label]`"
|
||||
type="hidden"
|
||||
:value="sTag.text"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import type VueTagsInput from "@sipec/vue3-tags-input";
|
||||
import { VueTagsInput as VueTagsInputCls } from "@sipec/vue3-tags-input";
|
||||
import slugify from "slugify";
|
||||
import route from "../../modules/route";
|
||||
import toast from "../../modules/toast";
|
||||
import trans from "../../modules/trans";
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [toast, route, trans],
|
||||
components: {
|
||||
VueTagsInputCls,
|
||||
},
|
||||
beforeMount() {
|
||||
if (!this.$attrs.json) {
|
||||
throw new Error("this component requires json");
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const json = String(this.$attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
this.name = data.name;
|
||||
this.tags = this.parseTags(data.value);
|
||||
this.autocompleteItems = this.parseTags(data.tags);
|
||||
},
|
||||
data(): {
|
||||
name: string;
|
||||
tag: string;
|
||||
tags: Array<CustomTag>;
|
||||
autocompleteItems: Array<CustomTag>;
|
||||
waitingListObjects: Array<VueTagsInput.IAddArgs>;
|
||||
errorMessages: Array<string>;
|
||||
} {
|
||||
return {
|
||||
name: "",
|
||||
tag: "",
|
||||
tags: [],
|
||||
autocompleteItems: [],
|
||||
waitingListObjects: [],
|
||||
errorMessages: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
parseTags(tags: Array<CustomTag>): Array<CustomTag> {
|
||||
return tags.map((tag: CustomTag) => {
|
||||
tag.text = String(tag.label);
|
||||
return tag;
|
||||
});
|
||||
},
|
||||
cancelTags(): void {
|
||||
this.$nextTick(() => {
|
||||
this.waitingListObjects = [];
|
||||
this.tag = "";
|
||||
this.showErrorMessages([]);
|
||||
});
|
||||
},
|
||||
beforeTagSave(obj: VueTagsInput.IAddArgs): void {
|
||||
this.waitingListObjects = [];
|
||||
this.showErrorMessages([]);
|
||||
// * Tag exists in selected tags
|
||||
if (this.tagExists(this.tags, obj.tag)) {
|
||||
this.showErrorMessages([this.__("Cette étiquette est déjà dans la liste.")]);
|
||||
return;
|
||||
}
|
||||
// * Tag exists in local list
|
||||
let customTag = this.tagExists(this.autocompleteItems, obj.tag);
|
||||
if (customTag) {
|
||||
obj.tag = customTag;
|
||||
this.tags.push(customTag);
|
||||
this.tag = "";
|
||||
return;
|
||||
}
|
||||
// * Add tag in waiting list
|
||||
this.waitingListObjects.push(obj);
|
||||
},
|
||||
changedTags(newTags: Array<CustomTag>): void {
|
||||
this.tags = newTags as Array<CustomTag>;
|
||||
},
|
||||
tagExists(items: Array<CustomTag>, tagToCheck: VueTagsInput.ITag): CustomTag | null {
|
||||
let customTag: CustomTag | null = null;
|
||||
items.filter((tag: CustomTag) => {
|
||||
if (
|
||||
tag.value === (tagToCheck as CustomTag).value ||
|
||||
tag.label === slugify(tagToCheck.text)
|
||||
) {
|
||||
customTag = tag;
|
||||
}
|
||||
});
|
||||
return customTag;
|
||||
},
|
||||
showErrorMessages(messages: Array<string>): void {
|
||||
this.errorMessages = messages;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
filteredItems() {
|
||||
return this.autocompleteItems.filter((i) => {
|
||||
return i.text.toLowerCase().indexOf(this.tag.toLowerCase()) !== -1;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../../node_modules/bootstrap/scss/bootstrap-utilities.scss";
|
||||
|
||||
.taggableDropdown {
|
||||
.tagInput {
|
||||
width: 100%;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.vue-tags-input {
|
||||
max-width: inherit !important;
|
||||
}
|
||||
.ti-autocomplete {
|
||||
overflow-y: scroll !important;
|
||||
max-height: 10rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
273
resources/ts/components/bo/ModelLinkComponent.vue
Normal file
273
resources/ts/components/bo/ModelLinkComponent.vue
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
<template>
|
||||
<div class="ModelLink form-control">
|
||||
<VueSelect
|
||||
:placeholder="_placeholder"
|
||||
:_loading="_loading"
|
||||
v-model="_value"
|
||||
:clearable="true"
|
||||
label="label"
|
||||
:options="_data"
|
||||
v-on:search="search"
|
||||
:disabled="_readonly || _disabled"
|
||||
:filterable="true"
|
||||
:searchable="true"
|
||||
>
|
||||
<template v-slot:option="{ option }: { option: VSelectProps }">
|
||||
{{
|
||||
option.label.length >= _maxLength
|
||||
? option.label.substring(0, _maxLength) + ".."
|
||||
: option.label
|
||||
}}
|
||||
</template>
|
||||
<template
|
||||
v-slot:no-options="{ search, searching }: { search: string, searching: boolean }"
|
||||
>
|
||||
<template v-if="searching">
|
||||
{{ __("Aucun résultat pour") }}
|
||||
<em>{{ search }}</em> .
|
||||
</template>
|
||||
<em v-else style="opacity: 0.5">{{ __("Chercher un lien du site") }}</em>
|
||||
</template>
|
||||
<template v-slot:list-footer>
|
||||
<li class="pagination" v-if="_data.length">
|
||||
<button
|
||||
v-on:click.prevent="_page -= 1"
|
||||
:disabled="!_hasPrevPage"
|
||||
class="btn btn-sm btn-primary mt-2 mb-2 ms-2 me-2"
|
||||
>
|
||||
{{ __("précédent") }}
|
||||
</button>
|
||||
<button
|
||||
v-on:click.prevent="_page += 1"
|
||||
:disabled="!_hasNextPage"
|
||||
class="btn btn-sm btn-primary mt-2 mb-2"
|
||||
>
|
||||
{{ __("suivant") }}
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
</VueSelect>
|
||||
<input v-if="_isJson" type="hidden" :name="String(_name)" :value="_value?.value" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import VueSelect from "vue-select";
|
||||
import trans from "../../modules/trans";
|
||||
import route from "../../modules/route";
|
||||
import axios from "axios";
|
||||
|
||||
type SelectModel = {
|
||||
class: string;
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [trans, route],
|
||||
components: {
|
||||
VueSelect,
|
||||
},
|
||||
beforeMount() {
|
||||
if (!this.$attrs.json) {
|
||||
throw new Error("this component requires json");
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const json = String(this.$attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
if (Object.keys(data).length) {
|
||||
this._isJson = true;
|
||||
this._name = data.name;
|
||||
}
|
||||
this._disabled = this.disabled ? this.disabled : data.disabled ? true : false;
|
||||
this._readonly = this.readonly ? this.readonly : data.readonly ? true : false;
|
||||
this._placeholder = this.placeholder ? this.placeholder : data.placeholder;
|
||||
this._maxLength = this.maxLength ? this.maxLength : data.maxLength;
|
||||
this.parseValue(this.value ? this.value : data.value);
|
||||
this.search();
|
||||
},
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: 35,
|
||||
},
|
||||
},
|
||||
data(): {
|
||||
_isJson: boolean;
|
||||
_name: string | null;
|
||||
_disabled: boolean;
|
||||
_readonly: boolean;
|
||||
_placeholder: string;
|
||||
_maxLength: number;
|
||||
_value: SelectModel | null;
|
||||
_loading: boolean;
|
||||
_searchText: string;
|
||||
_data: SelectModel[];
|
||||
_page: number;
|
||||
_perPage: number;
|
||||
_hasNextPage: boolean;
|
||||
_hasPrevPage: boolean;
|
||||
} {
|
||||
return {
|
||||
_isJson: false,
|
||||
_name: null,
|
||||
_disabled: false,
|
||||
_readonly: false,
|
||||
_placeholder: "",
|
||||
_value: null,
|
||||
_loading: false,
|
||||
_searchText: "",
|
||||
_data: [],
|
||||
_page: 1,
|
||||
_perPage: 15,
|
||||
_hasNextPage: true,
|
||||
_hasPrevPage: false,
|
||||
_maxLength: 65,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
_page() {
|
||||
if (this._page < 1) {
|
||||
this._page = 1;
|
||||
}
|
||||
if (this._page > 1) {
|
||||
this._hasPrevPage = true;
|
||||
} else {
|
||||
this._hasPrevPage = false;
|
||||
}
|
||||
this.search();
|
||||
},
|
||||
_data() {
|
||||
if (this._data.length == this._perPage) {
|
||||
this._hasNextPage = true;
|
||||
} else {
|
||||
this._hasNextPage = false;
|
||||
}
|
||||
},
|
||||
value() {
|
||||
this.parseValue(this.value);
|
||||
},
|
||||
_value() {
|
||||
this.$emit("input", this._value ? this._value.value : "");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
search(search = null) {
|
||||
if (search !== false && search !== null) {
|
||||
this._searchText = search;
|
||||
}
|
||||
if (!this._searchText) {
|
||||
return;
|
||||
}
|
||||
let routeUrl = route.methods.route("bo.ranges.search-uri");
|
||||
if (!routeUrl) {
|
||||
throw new Error(`undefined route bo.ranges.search-uri`);
|
||||
}
|
||||
const url = new URL(routeUrl);
|
||||
routeUrl = `${url.origin}${url.pathname}?`;
|
||||
routeUrl += "page=" + encodeURIComponent(this._page);
|
||||
routeUrl += "&search=" + encodeURIComponent(this._searchText);
|
||||
this._loading = true;
|
||||
axios
|
||||
.get(routeUrl)
|
||||
.then((response) => {
|
||||
this.parseResponse(response.data.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
})
|
||||
.then(() => {
|
||||
this._loading = false;
|
||||
});
|
||||
},
|
||||
parseValue(value: string | null) {
|
||||
if (!value) {
|
||||
this._value = null;
|
||||
return;
|
||||
}
|
||||
let routeUrl = route.methods.route("bo.ranges.model-title");
|
||||
if (!routeUrl) {
|
||||
throw new Error(`undefined route bo.ranges.model-title`);
|
||||
}
|
||||
const url = new URL(routeUrl),
|
||||
model = value.replace(/^#/, "").replace(/#$/, "").split("#");
|
||||
|
||||
routeUrl = `${url.origin}${url.pathname}?`;
|
||||
|
||||
routeUrl += "model=" + encodeURIComponent(model[0]);
|
||||
routeUrl += "&id=" + encodeURIComponent(model[1]);
|
||||
axios.get(routeUrl).then((response) => {
|
||||
let label = String(response.data as string);
|
||||
label =
|
||||
label.length >= this._maxLength
|
||||
? label.substring(0, this._maxLength) + ".."
|
||||
: label;
|
||||
if (label) {
|
||||
this._value = this.parseResource({
|
||||
class: model[0],
|
||||
label: label,
|
||||
value: model[1],
|
||||
});
|
||||
} else {
|
||||
console.error("empty data response");
|
||||
}
|
||||
});
|
||||
},
|
||||
parseResource(model: SelectModel): SelectModel {
|
||||
let label = model.label;
|
||||
switch (model.class) {
|
||||
case "App\\Models\\Recipe":
|
||||
label = `${this.__("models.classes.recipes")} - ${model.label}`;
|
||||
break;
|
||||
case "App\\Models\\Breeder":
|
||||
label = `${this.__("models.classes.breeders")} - ${model.label}`;
|
||||
break;
|
||||
}
|
||||
return {
|
||||
label: label,
|
||||
class: model.class,
|
||||
value: `#${model.class}#${model.value}#`,
|
||||
};
|
||||
},
|
||||
parseResponse(data: SelectModel[]) {
|
||||
const out: SelectModel[] = [];
|
||||
for (const model of data) {
|
||||
out.push(this.parseResource(model));
|
||||
}
|
||||
// pass _searchText if is valid url and nothing else is found
|
||||
if (out.length) {
|
||||
this._data = out;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../../node_modules/vue-select/dist/vue-select.css";
|
||||
|
||||
.ModelLink {
|
||||
padding: 0 !important;
|
||||
.vs__dropdown-toggle {
|
||||
border: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -53,7 +53,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
const el = e.target;
|
||||
if (!el || !(el instanceof HTMLFormElement)) {
|
||||
throw new Error(
|
||||
"confirmDoneJS can only be executed on an exising form"
|
||||
"confirmSubmitJS can only be executed on an exising form"
|
||||
);
|
||||
}
|
||||
confirm.methods.confirm(
|
||||
|
|
@ -70,24 +70,42 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
}
|
||||
elements = document.getElementsByClassName("confirmJS");
|
||||
for (let element of elements) {
|
||||
element.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
const el = e.target;
|
||||
if (!el || !(el instanceof HTMLFormElement)) {
|
||||
throw new Error(
|
||||
"confirmDoneJS can only be executed on an exising form"
|
||||
);
|
||||
}
|
||||
confirm.methods.confirm(
|
||||
"Confirmez",
|
||||
"Êtes vous sure ?",
|
||||
el,
|
||||
function (response: SweetAlertResult<any>) {
|
||||
(el as HTMLFormElement).submit();
|
||||
},
|
||||
{ icon: "warning" }
|
||||
);
|
||||
return false;
|
||||
});
|
||||
element.addEventListener(
|
||||
"click",
|
||||
function (e) {
|
||||
if (!element.getAttribute("confirmJS")) {
|
||||
e.preventDefault();
|
||||
const el = e.target;
|
||||
if (!el || !(el instanceof HTMLElement)) {
|
||||
throw new Error(
|
||||
"confirmJS can only be executed on a html element"
|
||||
);
|
||||
}
|
||||
(async () => {
|
||||
const promise = new Promise(
|
||||
(resolve: (value: boolean) => void, reject) => {
|
||||
console.log(el);
|
||||
confirm.methods.confirm(
|
||||
"Confirmez",
|
||||
"Êtes vous sure ?",
|
||||
el,
|
||||
function (response: SweetAlertResult<any>) {
|
||||
resolve(response.isConfirmed);
|
||||
},
|
||||
{ icon: "warning" }
|
||||
);
|
||||
return false;
|
||||
}
|
||||
);
|
||||
if (await promise) {
|
||||
element.setAttribute("confirmJS", "true");
|
||||
el.click();
|
||||
element.removeAttribute("confirmJS");
|
||||
}
|
||||
})();
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
5
resources/ts/index.d.ts
vendored
5
resources/ts/index.d.ts
vendored
|
|
@ -1,7 +1,8 @@
|
|||
export {};
|
||||
|
||||
import { AxiosStatic } from "axios";
|
||||
import type VueTagsInput from "vue3-tags-input";
|
||||
import type VueTagsInput from "@sipec/vue3-tags-input";
|
||||
import type { VueSelectProps } from "vue-select";
|
||||
|
||||
// * Recursive List<string>
|
||||
type StringList = Record<PropertyKey, string>;
|
||||
|
|
@ -34,6 +35,8 @@ declare global {
|
|||
type LaravelModel = Model;
|
||||
type LaravelModelList = ModelList;
|
||||
|
||||
type VSelectProps = VueSelectProps;
|
||||
|
||||
interface Window {
|
||||
$displayMenu: (show: boolean) => void;
|
||||
$toggleBasketMenu: () => void;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default {
|
|||
confirm(
|
||||
title: string,
|
||||
text: string = "",
|
||||
self: HTMLElement,
|
||||
self: Object,
|
||||
after: (response: SweetAlertResult<any>) => void = (
|
||||
response: SweetAlertResult<any>
|
||||
) => {},
|
||||
|
|
|
|||
|
|
@ -15,5 +15,15 @@
|
|||
'model' => Str::singular(__('models.classes.sliders'))
|
||||
]) }}" data-bs-toggle="tooltip">{{ __('models.classes.sliders') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(request()->routeIs('bo.ranges.*')) active @endif" href="{{ route('bo.ranges.index') }}" title="{{ __('Administrer les :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges'))
|
||||
]) }}" data-bs-toggle="tooltip">{{ __('models.classes.ranges') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(request()->routeIs('bo.products.*')) active @endif" href="{{ route('bo.products.index') }}" title="{{ __('Administrer les :model', [
|
||||
'model' => Str::singular(__('models.classes.products'))
|
||||
]) }}" data-bs-toggle="tooltip">{{ __('models.classes.products') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
@endauth
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@
|
|||
tags: {
|
||||
store: '{{ route('bo.tags.store') }}',
|
||||
jsonStore: '{{ route('bo.tags.jsonStore') }}'
|
||||
}
|
||||
},
|
||||
ranges: {
|
||||
'model-title': '{{ route('bo.ranges.model-title') }}',
|
||||
'search-uri': '{{ route('bo.ranges.search-uri') }}',
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
30
resources/views/back/products/create.blade.php
Normal file
30
resources/views/back/products/create.blade.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
@extends('layouts.back')
|
||||
|
||||
@section('title', sprintf('%s - %s', __('models.classes.products'), Str::ucfirst(__('création'))))
|
||||
|
||||
@section('breadcrumb', request()->route()->getName())
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@include('back.breadcrumb.breadcrumb-body')
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h1 class="col h4">
|
||||
<a href="{{ route('bo.products.index') }}" class="text-decoration-none btn btn-sm btn-primary" role="button" aria-roledescription="{{ _('Revenir en arrière') }}"><i class="fa-solid fa-arrow-left-long"></i></a>
|
||||
<span> {{ __('models.classes.products') }} {{ __('création') }}</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('bo.products.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@include('back.products.form')
|
||||
<button type="submit" class="btn btn-primary">{{ __('Enregister') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
54
resources/views/back/products/edit.blade.php
Normal file
54
resources/views/back/products/edit.blade.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
@extends('layouts.back')
|
||||
|
||||
@section('title', sprintf('%s - %s %s', __('models.classes.products'), Str::ucfirst(__('édition')), $productModel->title))
|
||||
|
||||
@section('breadcrumb', request()->route()->getName())
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('back.breadcrumb.breadcrumb-body', [
|
||||
'brParam' => $productModel
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="col h4">
|
||||
<a href="{{ route('bo.products.index') }}" class="text-decoration-none btn btn-sm btn-primary" role="button" aria-roledescription="{{ _('Revenir en arrière') }}"><i class="fa-solid fa-arrow-left-long"></i></a>
|
||||
<span> {{ __('models.classes.products') }} {{ __('édition') }}</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col text-end">
|
||||
<form action="{{ route('bo.products.destroy', $productModel->id) }}" method="POST" class="d-inline confirmDeleteJS">
|
||||
@method('DELETE')
|
||||
@csrf
|
||||
<div class="btn-group" role="group">
|
||||
{{-- Public View Page --}}
|
||||
@if($productModel->status)
|
||||
<a class="btn btn-warning" href="" target="_blank" title="{{ __('Visualiser :model', [
|
||||
'model' => Str::singular(__('models.classes.products'))
|
||||
]) }}" data-bs-toggle="tooltip"><i class="fa-solid fa-eye"></i></a>
|
||||
@endif
|
||||
<button class="btn btn-danger" title="{{ __('Supprimer :model', [
|
||||
'model' => Str::singular(__('models.classes.products'))
|
||||
]) }}" data-bs-toggle="tooltip"><i class="fa-solid fa-trash"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('bo.products.update', $productModel->id) }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
@include('back.products.form', compact('productModel'))
|
||||
<button type="submit" class="btn btn-primary" title="{{ __('Enregister :model en base', [
|
||||
'model' => Str::singular(__('models.classes.products'))
|
||||
]) }}" data-bs-toggle="tooltip">{{ __('Enregister') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
415
resources/views/back/products/form.blade.php
Normal file
415
resources/views/back/products/form.blade.php
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Informations générales') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="productModelTitle" class="col-form-label"><b>{{ __('Titre') }}</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'productModelTitle'])'></div>
|
||||
<input name="title" type="text" value="{{ old('title', $productModel->title ?? '') }}" class="form-control"
|
||||
id="productModelTitle" aria-describedby="productModelTitleHelp" placeholder="{{ __('Un titre beau et explicite') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'title', 'helperId' => 'productModelTitleHelp' , 'helper' => __('Doit décrire précisément le produit')
|
||||
])
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'slug'
|
||||
])
|
||||
</div>
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="productModelType" class="col-form-label"><b>{{ __('Gamme') }}</b></label>
|
||||
<select id="productModelType" class="form-select" name="range_id" aria-describedby="productModelTypeHelp" required>
|
||||
<option value="" @if(is_null(old('range_id', $productModel->range_id ?? null))) selected @endif disabled>{{ __('Séléctionnez une gamme') }}</option>
|
||||
@foreach($ranges as $range)
|
||||
<option value="{{ $range->id }}" @if(old('range_id', optional($productModel ?? null)->range_id) == $range->id) selected @endif>{{ $range->title }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'range_id', 'helperId' => 'productModelTypeHelp' , 'helper' => __('La gamme du produit')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Seo') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="productModelSeoTitle" class="col-form-label"><b>{{ __('Titre seo') }}</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'productModelSeoTitle'])'></div>
|
||||
<input name="seo_title" type="text" value="{{ old('seo_title', $productModel->seo_title ?? '') }}" class="form-control"
|
||||
id="productModelSeoTitle" aria-describedby="productModelSeoTitleHelp" placeholder="{{ __('Un titre en 70 caractères maximum') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'seo_title', 'helperId' => 'productModelSeoTitleHelp' , 'helper' => __('Doit décrire précisément le produit, recommandation pour un positionnement optimal dans les moteurs de recherche : entre 53 et 60 caractères, balise <title> ')
|
||||
])
|
||||
</div>
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="productModelSeoDescription" class="col-form-label"><b>{{ __('Description seo') }}</b></label>
|
||||
@php
|
||||
$data = [
|
||||
'id' => 'productModelSeoDescription',
|
||||
'class' => 'form-control',
|
||||
'name' => 'seo_description',
|
||||
'placeholder' => __('Une description du produit pour les moteurs de recherche'),
|
||||
'ariaDescribedby' => 'productModelSeoDescriptionHelp',
|
||||
'required' => true,
|
||||
'value' => old('seo_description', $productModel->seo_description ?? '')
|
||||
];
|
||||
@endphp
|
||||
<div id="input-text-auto" data-json='@json($data)'></div>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'seo_description', 'helperId' => 'productModelSeoDescriptionHelp' , 'helper' => __(' Doit décrire précisément le produit, requiert entre 50 et 160 caractères, balise <meta> description')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Labels') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 form-group">
|
||||
@php
|
||||
$data = [
|
||||
'name' => 'labels',
|
||||
'value' => old('labels', $productModel->labels ?? []),
|
||||
'tags' => App\Enums\Products\ProductLabelEnum::toArray(),
|
||||
];
|
||||
@endphp
|
||||
<div id="enum-tag-dropdown" data-json='@json($data)'></div>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'labels', 'helperId' => 'newsModelTagsHelp' , 'helper' => __('Les labels associés au produit')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Publication') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label for="productPublishedAt"><b>{{ __('Date de publication (Europe/Paris)') }}</b></label>
|
||||
<input id="productPublishedAt" name="publish_at" class="form-control publishTime" type="datetime-local" data-form-type="date" aria-describedby="productPublishAtHelp" value="{{ old('publish_at', ((isset($productModel) and $productModel->publish_at) ? $productModel->publish_at->format('Y-m-d\TH:i') : '')) }}">
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'publish_at', 'helperId' => 'productPublishAtHelp' , 'helper' => (isset($productModel) and $productModel->publish_at) ? __('Laisser vide pour un affichage immédiat du produit.') : __('Vider pour un affichage immédiat du produit')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label for="productUnPublishedAt"><b>{{ __('Date de fin de publication (Europe/Paris)') }}</b></label>
|
||||
<input id="productUnPublishedAt" name="unpublish_at" class="form-control publishTime" type="datetime-local" data-form-type="date" aria-describedby="productUnPublishAtHelp" value="{{ old('unpublish_at', ((isset($productModel) and $productModel->unpublish_at) ? $productModel->unpublish_at->format('Y-m-d\TH:i') : '')) }}">
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'unpublish_at', 'helperId' => 'productUnPublishAtHelp' , 'helper' => (isset($productModel) and $productModel->unpublish_at) ? __('Laisser vide pour un affichage permanent du produit.') : __('Vider pour un affichage permanent du produit')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Illustration') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="productPictureAlt" class="col-form-label"><b>{{ __('Texte alternatif de l\'image') }}</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'productPictureAlt'])'></div>
|
||||
<input name="picture_alt" type="text" value="{{ old('picture_alt', $productModel->picture_alt ?? '') }}" class="form-control"
|
||||
id="productPictureAlt" aria-describedby="productPictureAltHelp" placeholder="{{ __('Une image de petits chatons') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'picture_alt', 'helperId' => 'productPictureAltHelp' , 'helper' => __('Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots)')
|
||||
])
|
||||
</div>
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="productPictureTitle" class="col-form-label"><b>{{ __('Titre de l\'image') }}</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'productPictureTitle'])'></div>
|
||||
<input name="picture_title" type="text" value="{{ old('picture_title', $productModel->picture_title ?? '') }}" class="form-control"
|
||||
id="productPictureTitle" aria-describedby="productPictureTitleHelp" placeholder="{{ __('Une description du produit pour les moteurs de recherche') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'picture_title', 'helperId' => 'productPictureTitleHelp' , 'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur l\'image.')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@php
|
||||
$data = [
|
||||
'id' => 'productModelPicture',
|
||||
'name' => 'picture',
|
||||
'helper' => __('L\'image qui va servir de vignette à le produit, doit être de 700px (hauteur) par 700px (largeur)'),
|
||||
'width' => 700,
|
||||
'height' => 700,
|
||||
'value' => $productModel->picture ?? '',
|
||||
];
|
||||
@endphp
|
||||
@include('back.modules.input-error', [ 'inputName' => 'picture'])
|
||||
<div class="image-input" data-json='@json($data)'></div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Vignette') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="productPictureThumbAlt" class="col-form-label"><b>{{ __('Texte alternatif de l\'image') }}</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'productPictureThumbAlt'])'></div>
|
||||
<input name="picture_thumb_alt" type="text" value="{{ old('picture_thumb_alt', $productModel->picture_thumb_alt ?? '') }}" class="form-control"
|
||||
id="productPictureThumbAlt" aria-describedby="productPictureThumbAltHelp" placeholder="{{ __('Une image de petits chatons') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'picture_thumb_alt', 'helperId' => 'productPictureThumbAltHelp' , 'helper' => __('Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots)')
|
||||
])
|
||||
</div>
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="productPictureThumbTitle" class="col-form-label"><b>{{ __('Titre de l\'image') }}</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'productPictureThumbTitle'])'></div>
|
||||
<input name="picture_thumb_title" type="text" value="{{ old('picture_thumb_title', $productModel->picture_thumb_title ?? '') }}" class="form-control"
|
||||
id="productPictureThumbTitle" aria-describedby="productPictureThumbTitleHelp" placeholder="{{ __('Une description du produit pour les moteurs de recherche') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'picture_thumb_title', 'helperId' => 'productPictureThumbTitleHelp' , 'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur l\'image.')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@php
|
||||
$data = [
|
||||
'id' => 'productModelThumbPicture',
|
||||
'name' => 'picture_thumb',
|
||||
'helper' => __('L\'image qui va servir de vignette à le produit, doit être de 300px (hauteur) par 300px (largeur)'),
|
||||
'width' => 300,
|
||||
'height' => 300,
|
||||
'value' => $productModel->picture_thumb ?? '',
|
||||
];
|
||||
@endphp
|
||||
@include('back.modules.input-error', [ 'inputName' => 'picture_thumb'])
|
||||
<div class="image-input" data-json='@json($data)'></div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Description du produit') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 form-group">
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'description', 'helperId' => 'productModelDescriptionHelp' , 'helper' => __('Relisez bien votre description avant de la publier.')
|
||||
])
|
||||
@php
|
||||
$data = [
|
||||
'name' => 'description',
|
||||
'text' => old('description', $productModel->description ?? ''),
|
||||
];
|
||||
@endphp
|
||||
<div class="ck-editor-bo" data-json='@json($data)'></div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Informations Inco') }}</legend>
|
||||
@php $attributes = [
|
||||
'ingredients' => __('Ingrédients'),
|
||||
'preparation_advices' => __('Conseils de préparation'),
|
||||
'sales_pitch' => __('Arguments de vente'),
|
||||
'allergen_list' => __('Liste d\'allergènes')
|
||||
]; @endphp
|
||||
@foreach($attributes as $attribute => $helperText)
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 form-group">
|
||||
<span class="h6 fw-bold d-inline-block mb-2">{{ $helperText }}</span>
|
||||
<div class="form-check form-switch d-inline-block ms-5">
|
||||
<input class="form-check-input" name="{{ "show_{$attribute}" }}" type="checkbox" role="switch" id="{{ "{$attribute}Switch" }}" value="1" @if(old("show_{$attribute}", $productModel->{"show_{$attribute}"} ?? null)) checked @endif>
|
||||
<label class="form-check-label" for="{{ "{$attribute}Switch" }}"><span class="fw-bold fst-italic">{{ __('Afficher ? ') }}</span></label>
|
||||
</div>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => $attribute
|
||||
])
|
||||
@php
|
||||
$data = [
|
||||
'name' => $attribute,
|
||||
'text' => old($attribute, $productModel->{$attribute} ?? ''),
|
||||
];
|
||||
@endphp
|
||||
<div class="ck-editor-bo" data-json='@json($data)'></div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="productModelPortions" class="col-form-label d-inline-block"><b>{{ __('Nombre de portions') }}</b></label>
|
||||
<div class="form-check form-switch d-inline-block ms-5">
|
||||
<input class="form-check-input" name="show_portions" type="checkbox" role="switch" id="portionsSwitch" value="1" @if(old('show_portions', $productModel->show_portions ?? null)) checked @endif>
|
||||
<label class="form-check-label" for="portionsSwitch"><span class="fw-bold fst-italic">{{ __('Afficher ? ') }}</span></label>
|
||||
</div>
|
||||
<input name="number_portion" type="number" value="{{ old('number_portion', $productModel->number_portion ?? 0) }}" class="form-control"
|
||||
id="productModelPortions" aria-describedby="productModelPortionsHelp" min="0" step="1" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'number_portion', 'helperId' => 'productModelPortionsHelp' , 'helper' => __('Nombre de portions du produit')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<label class="h6 fw-bold d-inline-block">{{ __('Valeurs nutritionelles') }}</label>
|
||||
<div class="form-check form-switch d-inline-block ms-5">
|
||||
<input class="form-check-input" name="show_nutrionnal_values" type="checkbox" role="switch" id="nutrionnaSwitch" value="1" @if(old('show_nutrionnal_values', $productModel->show_nutrionnal_values ?? null)) checked @endif>
|
||||
<label class="form-check-label" for="nutrionnaSwitch"><span class="fw-bold fst-italic">{{ __('Afficher ? ') }}</span></label>
|
||||
</div>
|
||||
@php
|
||||
$incoNut = (isset($productModel) and $productModel->xmlInco) ? json_decode($productModel->xmlInco->nutritional_values) : null;
|
||||
@endphp
|
||||
<div class="table-responsive">
|
||||
<table class="table table-light text-center table-sm table-bordered table-striped">
|
||||
<thead class="table-group-divider">
|
||||
<tr>
|
||||
<th scope="col">Nutriment</th>
|
||||
<th scope="col">Valeur</th>
|
||||
@if(isset($incoNut))
|
||||
<th scope="col">Val. Source Inco</th>
|
||||
@endif
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-group-divider">
|
||||
@php $incoValues = [
|
||||
'Poids de référence (en g)' => ['product' => 'weight', 'inco' => ''],
|
||||
'Énergie (en kCal)' => ['product' => 'energy', 'inco' => 'val_energ_kcal_r'],
|
||||
'Énergie (en kJ)' => ['product' => 'energy_kj', 'inco' => 'val_energ_kj_r'],
|
||||
'Protéines (en g)' => ['product' => 'protein', 'inco' => 'proteine_r'],
|
||||
'Glucides (en g)' => ['product' => 'carbohydrates', 'inco' => 'glucides_r'],
|
||||
'Sucres (en g)' => ['product' => 'sugar', 'inco' => 'sucre_r'],
|
||||
'Matière grasse (en g)' => ['product' => 'fat', 'inco' => 'mat_grass_r'],
|
||||
'Acides Gras Saturés (en g)' => ['product' => 'saturated_fatty_acids', 'inco' => 'ags_r'],
|
||||
'Fibres alimentaires (en g)' => ['product' => 'dietary_fiber', 'inco' => 'fib_alim_r'],
|
||||
'Sel (en g)' => ['product' => 'salt', 'inco' => 'sel_r']
|
||||
]; @endphp
|
||||
@foreach($incoValues as $title => $incoValue)
|
||||
<tr>
|
||||
<td class="w-50">{{ $title }}</td>
|
||||
<td class="w-25"><input class="form-control form-control-sm w-100 h-100" type="number" step="1" name="{{ $incoValue['product'] }}" value="{{ old($incoValue['product'], isset($productModel->incoNutritionalValues->{$incoValue['product']}) ? str_replace(',', '.', (string)$productModel->incoNutritionalValues->{$incoValue['product']}) : 0) }}"></td>
|
||||
@if(isset($incoNut))
|
||||
<td class="w-25"><span class="text-danger">{{ isset($incoNut->{$incoValue['inco']}) ? str_replace(',', '.', $incoNut->{$incoValue['inco']}) : 0 }}</span></td>
|
||||
@endif
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@if(isset($productModel) and isset($productModel->xmlInco))
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-5 text-center">
|
||||
<a href="#" class="btn btn-danger confirmJS">{{ __('Copier les Valeurs nutritionelles') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check">
|
||||
<input name="status" class="form-check-input mt-2" type="checkbox" id="productStatus" aria-describedby="productStatusHelp" value="1" @if(old('status', $productModel->status ?? ''))checked @endif>
|
||||
<label class="form-check-label user-select-none pt-1" for="productStatus"><b>{{ __('Publié') }}</b></label><br>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'status', 'helperId' => 'productStatusHelp' , 'helper' => __('Le produit est visible ou non')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@push('scripts')
|
||||
<script nonce="{{ $nonce }}">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
// Publish times
|
||||
let publishTimes = document.getElementsByClassName('publishTime');
|
||||
for (let input of publishTimes) {
|
||||
let parent = input.parentNode,
|
||||
dates = {
|
||||
start: 0,
|
||||
end: 0
|
||||
};
|
||||
input.originalValue = (new Date(input.value)).valueOf();
|
||||
if(input.originalValue === NaN) {
|
||||
input.originalValue = 0
|
||||
}
|
||||
if(input.id === 'productPublishedAt') {
|
||||
dates.start = input.originalValue;
|
||||
} else {
|
||||
dates.end = input.originalValue;
|
||||
}
|
||||
let getEuropeParisTime = () => {
|
||||
return new Date((new Date()).getTime() + (60000*((new Date()).getTimezoneOffset()+60)));
|
||||
};
|
||||
let setDateHelpers = (input, parent) => {
|
||||
let small = parent.children.productPublishAtHelp ? parent.children.productPublishAtHelp : parent.children.productUnPublishAtHelp;
|
||||
let isPlanned = false;
|
||||
if(input.id === 'productPublishedAt') {
|
||||
isPlanned = dates.start > dates.end && dates.end != 0;
|
||||
} else {
|
||||
isPlanned = dates.start < dates.end && dates.start != 0;
|
||||
}
|
||||
if (isPlanned || getEuropeParisTime().valueOf() < (new Date(input.value)).valueOf()) {
|
||||
let date = new Date(input.value);
|
||||
// small.textContent = "{{ __('Planifié pour le ') }}" + date.toLocaleString();
|
||||
small.textContent = input.id === 'productPublishedAt' ?
|
||||
"{{ __('Vider pour un affichage immédiat du produit.') }}" :
|
||||
"{{ __('Vider pour un affichage permanent du produit.') }}";
|
||||
} else {
|
||||
// small.textContent = input.id === 'productPublishedAt' ?
|
||||
// "{{ __('Uniquement pour planifier la publication') }}" :
|
||||
// "{{ __('Uniquement pour planifier la fin de la publication') }}";
|
||||
small.textContent = input.id === 'productPublishedAt' ?
|
||||
"{{ __('Laisser vide pour un affichage immédiat du produit.') }}" :
|
||||
"{{ __('Laisser vide pour un affichage permanent du produit.') }}";
|
||||
}
|
||||
};
|
||||
setDateHelpers(input, parent);
|
||||
input.addEventListener('change', (event) => {
|
||||
let parent = event.target.parentNode,
|
||||
input = event.target, value;
|
||||
value = (new Date(input.value)).valueOf();
|
||||
if(input.originalValue === NaN) {
|
||||
value = 0
|
||||
}
|
||||
if(input.id === 'productPublishedAt') {
|
||||
dates.start = value;
|
||||
} else {
|
||||
dates.end = value;
|
||||
}
|
||||
setDateHelpers(input, parent);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
103
resources/views/back/products/index.blade.php
Normal file
103
resources/views/back/products/index.blade.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
@extends('layouts.back')
|
||||
|
||||
@section('title', sprintf('%s - %s', __('models.classes.products'), Str::ucfirst(__('liste'))))
|
||||
|
||||
@section('breadcrumb', request()->route()->getName())
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('back.breadcrumb.breadcrumb-body')
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h2">{{ __('models.classes.products') }} {{ Str::ucfirst(__('liste')) }}</h1>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="input-group justify-content-end">
|
||||
<a href="{{ route('bo.products.create') }}" class="btn btn-primary" title="{{ __('Ajouter un :model', [
|
||||
'model' => Str::singular(__('models.classes.products'))
|
||||
]) }}" data-bs-toggle="tooltip"><i class="fa-solid fa-plus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@include('back.modules.searchBar')
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-6 p-0 p-md-3">
|
||||
{{ $products->withQueryString()->onEachSide(1)->links() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="table-responsive">
|
||||
<table class="table fixAction">
|
||||
<thead>
|
||||
@php
|
||||
$cols = ['title' => __('Titre'), 'description' => __('Description'), 'status' => __('Publication'), 'updated_at' => __('Mis à jour le')];
|
||||
@endphp
|
||||
@include('back.modules.tableColSorter', [ 'cols' => $cols, 'mobileHide' => ['status']])
|
||||
</thead>
|
||||
<tbody>
|
||||
@if($products->count())
|
||||
@foreach ($products as $productModel)
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ $productModel->title }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ Str::limit(strip_tags($productModel->description)) }}</span>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<form action="{{ route('bo.products.change-published', $productModel->id) }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-sm" title="{{ __(':action :model', [
|
||||
'action' => $productModel->status ? __('Dépublier') : __('Publier'),
|
||||
'model' => Str::singular(__('models.classes.products'))
|
||||
]) }}" data-bs-toggle="tooltip">
|
||||
<i class="fas @if($productModel->status)fa-check check-green @else fa-times cross-red @endif"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ $productModel->updated_at->formatLocalized('%d/%m/%Y à %I:%M') }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
{{-- Public View Page --}}
|
||||
@if($productModel->status)
|
||||
<a class="btn btn-sm btn-warning" href="" target="_blank" title="{{ __('Visualiser :model', [
|
||||
'model' => Str::singular(__('models.classes.products'))
|
||||
]) }}" data-bs-toggle="tooltip"><i class="fa-solid fa-eye"></i></a>
|
||||
@endif
|
||||
<a class="btn btn-sm btn-primary" href="{{ route('bo.products.edit', $productModel->id) }}" title="{{ __('Modifier :model', [
|
||||
'model' => Str::singular(__('models.classes.products'))
|
||||
]) }}" data-bs-toggle="tooltip"><i class="fa-solid fa-pencil"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@else
|
||||
<tr>
|
||||
<td colspan="5" class="text-center"><span class="h3">{{
|
||||
__('Aucun résultat pour les :models', ['models' => __('models.classes.products')])
|
||||
}}</span></td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-6 p-0 p-md-3">
|
||||
{{ $products->withQueryString()->onEachSide(1)->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
30
resources/views/back/ranges/create.blade.php
Normal file
30
resources/views/back/ranges/create.blade.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
@extends('layouts.back')
|
||||
|
||||
@section('title', sprintf('%s - %s', __('models.classes.ranges'), Str::ucfirst(__('création'))))
|
||||
|
||||
@section('breadcrumb', request()->route()->getName())
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@include('back.breadcrumb.breadcrumb-body')
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h1 class="col h4">
|
||||
<a href="{{ route('bo.ranges.index') }}" class="text-decoration-none btn btn-sm btn-primary" role="button" aria-roledescription="{{ _('Revenir en arrière') }}"><i class="fa-solid fa-arrow-left-long"></i></a>
|
||||
<span> {{ __('models.classes.ranges') }} {{ __('création') }}</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('bo.ranges.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@include('back.ranges.form')
|
||||
<button type="submit" class="btn btn-primary">{{ __('Enregister') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
54
resources/views/back/ranges/edit.blade.php
Normal file
54
resources/views/back/ranges/edit.blade.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
@extends('layouts.back')
|
||||
|
||||
@section('title', sprintf('%s - %s %s', __('models.classes.ranges'), Str::ucfirst(__('édition')), $rangeModel->title))
|
||||
|
||||
@section('breadcrumb', request()->route()->getName())
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('back.breadcrumb.breadcrumb-body', [
|
||||
'brParam' => $rangeModel
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="col h4">
|
||||
<a href="{{ route('bo.ranges.index') }}" class="text-decoration-none btn btn-sm btn-primary" role="button" aria-roledescription="{{ _('Revenir en arrière') }}"><i class="fa-solid fa-arrow-left-long"></i></a>
|
||||
<span> {{ __('models.classes.ranges') }} {{ __('édition') }}</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col text-end">
|
||||
<form action="{{ route('bo.ranges.destroy', $rangeModel->id) }}" method="POST" class="d-inline confirmDeleteJS">
|
||||
@method('DELETE')
|
||||
@csrf
|
||||
<div class="btn-group" role="group">
|
||||
{{-- Public View Page --}}
|
||||
@if($rangeModel->status)
|
||||
<a class="btn btn-warning" href="" target="_blank" title="{{ __('Visualiser :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges'))
|
||||
]) }}" data-bs-toggle="tooltip"><i class="fa-solid fa-eye"></i></a>
|
||||
@endif
|
||||
<button class="btn btn-danger" title="{{ __('Supprimer :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges'))
|
||||
]) }}" data-bs-toggle="tooltip"><i class="fa-solid fa-trash"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('bo.ranges.update', $rangeModel->id) }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
@include('back.ranges.form', compact('rangeModel'))
|
||||
<button type="submit" class="btn btn-primary" title="{{ __('Enregister :model en base', [
|
||||
'model' => Str::singular(__('models.classes.ranges'))
|
||||
]) }}" data-bs-toggle="tooltip">{{ __('Enregister') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
327
resources/views/back/ranges/form.blade.php
Normal file
327
resources/views/back/ranges/form.blade.php
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Informations générales') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="rangeModelTitle" class="col-form-label"><b>{{ __('Titre') }}</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'rangeModelTitle'])'></div>
|
||||
<input name="title" type="text" value="{{ old('title', $rangeModel->title ?? '') }}" class="form-control"
|
||||
id="rangeModelTitle" aria-describedby="rangeModelTitleHelp" placeholder="{{ __('Un titre beau et explicite') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'title', 'helperId' => 'rangeModelTitleHelp' , 'helper' => __('Doit décrire précisément la gamme')
|
||||
])
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'slug'
|
||||
])
|
||||
</div>
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="rangeCategory" class="col-form-label"><b>{{ __('Catégorie de la gamme') }}</b></label>
|
||||
<select id="rangeCategory" class="form-select" name="category" aria-describedby="rangeCategoryHelp" required>
|
||||
<option value="" @if(is_null(old('category', $rangeModel->category ?? null))) selected @endif disabled>{{ __('Séléctionnez une catégorie') }}</option>
|
||||
@foreach(App\Enums\Ranges\RangeCategoryEnum::toArray() as $category)
|
||||
<option value="{{ $category->value }}" @if(old('type', optional($rangeModel->category ?? null)->value) == $category->value) selected @endif>{{ $category->label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'category', 'helperId' => 'rangeCategoryHelp' , 'helper' => __('Le type de gamme')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Seo') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="rangeModelSeoTitle" class="col-form-label"><b>{{ __('Titre seo') }}</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'rangeModelSeoTitle'])'></div>
|
||||
<input name="seo_title" type="text" value="{{ old('seo_title', $rangeModel->seo_title ?? '') }}" class="form-control"
|
||||
id="rangeModelSeoTitle" aria-describedby="rangeModelSeoTitleHelp" placeholder="{{ __('Un titre en 70 caractères maximum') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'seo_title', 'helperId' => 'rangeModelSeoTitleHelp' , 'helper' => __('Doit décrire précisément la gamme, recommandation pour un positionnement optimal dans les moteurs de recherche : entre 53 et 60 caractères, balise <title> ')
|
||||
])
|
||||
</div>
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="rangeModelSeoDescription" class="col-form-label"><b>{{ __('Description seo') }}</b></label>
|
||||
@php
|
||||
$data = [
|
||||
'id' => 'rangeModelSeoDescription',
|
||||
'class' => 'form-control',
|
||||
'name' => 'seo_description',
|
||||
'placeholder' => __('Une description de la gamme pour les moteurs de recherche'),
|
||||
'ariaDescribedby' => 'rangeModelSeoDescriptionHelp',
|
||||
'required' => true,
|
||||
'value' => old('seo_description', $rangeModel->seo_description ?? '')
|
||||
];
|
||||
@endphp
|
||||
<div id="input-text-auto" data-json='@json($data)'></div>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'seo_description', 'helperId' => 'rangeModelSeoDescriptionHelp' , 'helper' => __(' Doit décrire précisément la gamme, requiert entre 50 et 160 caractères, balise <meta> description')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Publication') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label for="rangePublishedAt"><b>{{ __('Date de publication (Europe/Paris)') }}</b></label>
|
||||
<input id="rangePublishedAt" name="publish_at" class="form-control publishTime" type="datetime-local" data-form-type="date" aria-describedby="rangePublishAtHelp" value="{{ old('publish_at', ((isset($rangeModel) and $rangeModel->publish_at) ? $rangeModel->publish_at->format('Y-m-d\TH:i') : '')) }}">
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'publish_at', 'helperId' => 'rangePublishAtHelp' , 'helper' => (isset($rangeModel) and $rangeModel->publish_at) ? __('Laisser vide pour un affichage immédiat de la gamme.') : __('Vider pour un affichage immédiat de la gamme')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label for="rangeUnPublishedAt"><b>{{ __('Date de fin de publication (Europe/Paris)') }}</b></label>
|
||||
<input id="rangeUnPublishedAt" name="unpublish_at" class="form-control publishTime" type="datetime-local" data-form-type="date" aria-describedby="rangeUnPublishAtHelp" value="{{ old('unpublish_at', ((isset($rangeModel) and $rangeModel->unpublish_at) ? $rangeModel->unpublish_at->format('Y-m-d\TH:i') : '')) }}">
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'unpublish_at', 'helperId' => 'rangeUnPublishAtHelp' , 'helper' => (isset($rangeModel) and $rangeModel->unpublish_at) ? __('Laisser vide pour un affichage permanent de la gamme.') : __('Vider pour un affichage permanent de la gamme')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Illustration') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="rangePictureAlt" class="col-form-label"><b>{{ __('Texte alternatif de l\'image') }}</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'rangePictureAlt'])'></div>
|
||||
<input name="picture_alt" type="text" value="{{ old('picture_alt', $rangeModel->picture_alt ?? '') }}" class="form-control"
|
||||
id="rangePictureAlt" aria-describedby="rangePictureAltHelp" placeholder="{{ __('Une image de petits chatons') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'picture_alt', 'helperId' => 'rangePictureAltHelp' , 'helper' => __('Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots)')
|
||||
])
|
||||
</div>
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="rangePictureTitle" class="col-form-label"><b>{{ __('Titre de l\'image') }}</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'rangePictureTitle'])'></div>
|
||||
<input name="picture_title" type="text" value="{{ old('picture_title', $rangeModel->picture_title ?? '') }}" class="form-control"
|
||||
id="rangePictureTitle" aria-describedby="rangePictureTitleHelp" placeholder="{{ __('Une description de la gamme pour les moteurs de recherche') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'picture_title', 'helperId' => 'rangePictureTitleHelp' , 'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur l\'image.')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@php
|
||||
$data = [
|
||||
'id' => 'rangeModelPicture',
|
||||
'name' => 'picture',
|
||||
'helper' => __('L\'image qui va servir de vignette à la gamme, doit être de 700px (hauteur) par 700px (largeur)'),
|
||||
'width' => 700,
|
||||
'height' => 700,
|
||||
'value' => $rangeModel->picture ?? '',
|
||||
];
|
||||
@endphp
|
||||
@include('back.modules.input-error', [ 'inputName' => 'picture'])
|
||||
<div class="image-input" data-json='@json($data)'></div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Vignette') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="rangePictureThumbAlt" class="col-form-label"><b>{{ __('Texte alternatif de l\'image') }}</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'rangePictureThumbAlt'])'></div>
|
||||
<input name="picture_thumb_alt" type="text" value="{{ old('picture_thumb_alt', $rangeModel->picture_thumb_alt ?? '') }}" class="form-control"
|
||||
id="rangePictureThumbAlt" aria-describedby="rangePictureThumbAltHelp" placeholder="{{ __('Une image de petits chatons') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'picture_thumb_alt', 'helperId' => 'rangePictureThumbAltHelp' , 'helper' => __('Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots)')
|
||||
])
|
||||
</div>
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="rangePictureThumbTitle" class="col-form-label"><b>{{ __('Titre de l\'image') }}</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'rangePictureThumbTitle'])'></div>
|
||||
<input name="picture_thumb_title" type="text" value="{{ old('picture_thumb_title', $rangeModel->picture_thumb_title ?? '') }}" class="form-control"
|
||||
id="rangePictureThumbTitle" aria-describedby="rangePictureThumbTitleHelp" placeholder="{{ __('Une description de la gamme pour les moteurs de recherche') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'picture_thumb_title', 'helperId' => 'rangePictureThumbTitleHelp' , 'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur l\'image.')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@php
|
||||
$data = [
|
||||
'id' => 'rangeModelThumbPicture',
|
||||
'name' => 'picture_thumb',
|
||||
'helper' => __('L\'image qui va servir de vignette à la gamme, doit être de 300px (hauteur) par 300px (largeur)'),
|
||||
'width' => 300,
|
||||
'height' => 300,
|
||||
'value' => $rangeModel->picture_thumb ?? '',
|
||||
];
|
||||
@endphp
|
||||
@include('back.modules.input-error', [ 'inputName' => 'picture_thumb'])
|
||||
<div class="image-input" data-json='@json($data)'></div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Description de la gamme') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 form-group">
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'description', 'helperId' => 'rangeModelDescriptionHelp' , 'helper' => __('Relisez bien votre description avant de la publier.')
|
||||
])
|
||||
@php
|
||||
$data = [
|
||||
'name' => 'description',
|
||||
'csrf' => csrf_token(),
|
||||
'text' => old('description', $rangeModel->description ?? ''),
|
||||
];
|
||||
@endphp
|
||||
<div id="ck-editor-bo" data-json='@json($data)'></div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend>{{ __('Liens') }}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 form-group">
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'link1', 'helperId' => 'rangeModelLink1Help' , 'helper' => __('Lien à afficher sur la gamme')
|
||||
])
|
||||
@php
|
||||
$data = [
|
||||
'name' => 'link1',
|
||||
'placeholder' => __('lien numéro 1'),
|
||||
'value' => old('link1', $rangeModel->link1 ?? null),
|
||||
];
|
||||
@endphp
|
||||
<div class="model-link" data-json='@json($data)'></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 form-group">
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'link2', 'helperId' => 'rangeModelLink1Help' , 'helper' => __('Lien à afficher sur la gamme')
|
||||
])
|
||||
@php
|
||||
$data = [
|
||||
'name' => 'link2',
|
||||
'placeholder' => __('lien numéro 2'),
|
||||
'value' => old('link2', $rangeModel->link2 ?? null),
|
||||
];
|
||||
@endphp
|
||||
<div class="model-link" data-json='@json($data)'></div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check">
|
||||
<input name="status" class="form-check-input mt-2" type="checkbox" id="rangeStatus" aria-describedby="rangeStatusHelp" value="1" @if(old('status', $rangeModel->status ?? ''))checked @endif>
|
||||
<label class="form-check-label user-select-none pt-1" for="rangeStatus"><b>{{ __('Publié') }}</b></label><br>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'status', 'helperId' => 'rangeStatusHelp' , 'helper' => __('La gamme est visible ou non')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@push('scripts')
|
||||
<script nonce="{{ $nonce }}">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
// Publish times
|
||||
let publishTimes = document.getElementsByClassName('publishTime');
|
||||
for (let input of publishTimes) {
|
||||
let parent = input.parentNode,
|
||||
dates = {
|
||||
start: 0,
|
||||
end: 0
|
||||
};
|
||||
input.originalValue = (new Date(input.value)).valueOf();
|
||||
if(input.originalValue === NaN) {
|
||||
input.originalValue = 0
|
||||
}
|
||||
if(input.id === 'rangePublishedAt') {
|
||||
dates.start = input.originalValue;
|
||||
} else {
|
||||
dates.end = input.originalValue;
|
||||
}
|
||||
let getEuropeParisTime = () => {
|
||||
return new Date((new Date()).getTime() + (60000*((new Date()).getTimezoneOffset()+60)));
|
||||
};
|
||||
let setDateHelpers = (input, parent) => {
|
||||
let small = parent.children.rangePublishAtHelp ? parent.children.rangePublishAtHelp : parent.children.rangeUnPublishAtHelp;
|
||||
let isPlanned = false;
|
||||
if(input.id === 'rangePublishedAt') {
|
||||
isPlanned = dates.start > dates.end && dates.end != 0;
|
||||
} else {
|
||||
isPlanned = dates.start < dates.end && dates.start != 0;
|
||||
}
|
||||
if (isPlanned || getEuropeParisTime().valueOf() < (new Date(input.value)).valueOf()) {
|
||||
let date = new Date(input.value);
|
||||
// small.textContent = "{{ __('Planifié pour le ') }}" + date.toLocaleString();
|
||||
small.textContent = input.id === 'rangePublishedAt' ?
|
||||
"{{ __('Vider pour un affichage immédiat de la gamme.') }}" :
|
||||
"{{ __('Vider pour un affichage permanent de la gamme.') }}";
|
||||
} else {
|
||||
// small.textContent = input.id === 'rangePublishedAt' ?
|
||||
// "{{ __('Uniquement pour planifier la publication') }}" :
|
||||
// "{{ __('Uniquement pour planifier la fin de la publication') }}";
|
||||
small.textContent = input.id === 'rangePublishedAt' ?
|
||||
"{{ __('Laisser vide pour un affichage immédiat de la gamme.') }}" :
|
||||
"{{ __('Laisser vide pour un affichage permanent de la gamme.') }}";
|
||||
}
|
||||
};
|
||||
setDateHelpers(input, parent);
|
||||
input.addEventListener('change', (event) => {
|
||||
let parent = event.target.parentNode,
|
||||
input = event.target, value;
|
||||
value = (new Date(input.value)).valueOf();
|
||||
if(input.originalValue === NaN) {
|
||||
value = 0
|
||||
}
|
||||
if(input.id === 'rangePublishedAt') {
|
||||
dates.start = value;
|
||||
} else {
|
||||
dates.end = value;
|
||||
}
|
||||
setDateHelpers(input, parent);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
106
resources/views/back/ranges/index.blade.php
Normal file
106
resources/views/back/ranges/index.blade.php
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
@extends('layouts.back')
|
||||
|
||||
@section('title', sprintf('%s - %s', __('models.classes.ranges'), Str::ucfirst(__('liste'))))
|
||||
|
||||
@section('breadcrumb', request()->route()->getName())
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('back.breadcrumb.breadcrumb-body')
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h2">{{ __('models.classes.ranges') }} {{ Str::ucfirst(__('liste')) }}</h1>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="input-group justify-content-end">
|
||||
<a href="{{ route('bo.ranges.create') }}" class="btn btn-primary" title="{{ __('Ajouter une :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges'))
|
||||
]) }}" data-bs-toggle="tooltip"><i class="fa-solid fa-plus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@include('back.modules.searchBar')
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-6 p-0 p-md-3">
|
||||
{{ $ranges->withQueryString()->onEachSide(1)->links() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="table-responsive">
|
||||
<table class="table fixAction">
|
||||
<thead>
|
||||
@php
|
||||
$cols = ['title' => __('Titre'), 'category' => __('Categorie'), 'description' => __('Description'), 'status' => __('Publication'), 'updated_at' => __('Mis à jour le')];
|
||||
@endphp
|
||||
@include('back.modules.tableColSorter', [ 'cols' => $cols, 'mobileHide' => ['status']])
|
||||
</thead>
|
||||
<tbody>
|
||||
@if($ranges->count())
|
||||
@foreach ($ranges as $rangeModel)
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ $rangeModel->title }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ $rangeModel->category->label }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ Str::limit(strip_tags($rangeModel->description)) }}</span>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<form action="{{ route('bo.ranges.change-published', $rangeModel->id) }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-sm" title="{{ __(':action :model', [
|
||||
'action' => $rangeModel->status ? __('Dépublier') : __('Publier'),
|
||||
'model' => Str::singular(__('models.classes.ranges'))
|
||||
]) }}" data-bs-toggle="tooltip">
|
||||
<i class="fas @if($rangeModel->status)fa-check check-green @else fa-times cross-red @endif"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ $rangeModel->updated_at->formatLocalized('%d/%m/%Y à %I:%M') }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
{{-- Public View Page --}}
|
||||
@if($rangeModel->status)
|
||||
<a class="btn btn-sm btn-warning" href="" target="_blank" title="{{ __('Visualiser :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges'))
|
||||
]) }}" data-bs-toggle="tooltip"><i class="fa-solid fa-eye"></i></a>
|
||||
@endif
|
||||
<a class="btn btn-sm btn-primary" href="{{ route('bo.ranges.edit', $rangeModel->id) }}" title="{{ __('Modifier :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges'))
|
||||
]) }}" data-bs-toggle="tooltip"><i class="fa-solid fa-pencil"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@else
|
||||
<tr>
|
||||
<td colspan="5" class="text-center"><span class="h3">{{
|
||||
__('Aucun résultat pour les :models', ['models' => __('models.classes.ranges')])
|
||||
}}</span></td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-6 p-0 p-md-3">
|
||||
{{ $ranges->withQueryString()->onEachSide(1)->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
<form action="{{ route('bo.sliders.change-published', $slider->id) }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-sm" title="{{ __(':action :model', [
|
||||
'action' => $slider->status ? __('Déplublier') : __('Publier'),
|
||||
'action' => $slider->status ? __('Dépublier') : __('Publier'),
|
||||
'model' => Str::singular(__('models.classes.sliders'))
|
||||
]) }}" data-bs-toggle="tooltip">
|
||||
<i class="fas @if($slider->status)fa-check check-green @else fa-times cross-red @endif"></i>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
use App\Http\Controllers\Bo\HomeController;
|
||||
use App\Http\Controllers\Bo\NewsController;
|
||||
use App\Http\Controllers\Bo\ProductController;
|
||||
use App\Http\Controllers\Bo\RangeController;
|
||||
use App\Http\Controllers\Bo\SliderController;
|
||||
use App\Http\Controllers\Bo\TagController;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
|
@ -19,14 +21,37 @@ Route::prefix('bo')->name('bo.')->group(function () {
|
|||
Route::middleware('auth:backend')->group(function () {
|
||||
Route::get('/', [HomeController::class, 'dashboard'])->name('dashboard');
|
||||
|
||||
// * SLIDERS
|
||||
Route::resource('sliders', SliderController::class)->except(['show']);
|
||||
Route::post('/sliders/{slider}/change-order', [SliderController::class, 'changeOrder'])
|
||||
->name('sliders.change-order');
|
||||
Route::post('/sliders/{slider}/change-published', [SliderController::class, 'changePublished'])
|
||||
->name('sliders.change-published');
|
||||
|
||||
Route::resource('sliders', SliderController::class)->except(['show']);
|
||||
// * RANGES
|
||||
Route::resource('ranges', RangeController::class)->except(['show'])
|
||||
->withoutMiddleware('htmlsanitize')
|
||||
->middleware('htmlsanitize:description');
|
||||
Route::post('/ranges/{range}/change-published', [RangeController::class, 'changePublished'])
|
||||
->name('ranges.change-published');
|
||||
// * Ranges PAGE SEARCH
|
||||
Route::get('/ranges/search-uri', [RangeController::class, 'searchUri'])
|
||||
->name('ranges.search-uri');
|
||||
Route::get('/ranges/model-title', [RangeController::class, 'modelTitle'])
|
||||
->name('ranges.model-title');
|
||||
|
||||
// * PRODUCTS
|
||||
Route::resource('products', ProductController::class)->except(['show'])
|
||||
->withoutMiddleware('htmlsanitize')
|
||||
->middleware('htmlsanitize:description,ingredients,preparation_advices,sales_pitch,allergen_list');
|
||||
Route::post('/products/{product}/change-published', [ProductController::class, 'changePublished'])
|
||||
->name('products.change-published');
|
||||
|
||||
// * TAGS
|
||||
Route::resource('tags', TagController::class)->except(['show']);
|
||||
Route::post('/tags/store', [TagController::class, 'jsonStore'])->name('tags.jsonStore');
|
||||
|
||||
// * NEWS
|
||||
Route::resource('news', NewsController::class)
|
||||
->except(['show'])
|
||||
->withoutMiddleware('htmlsanitize')
|
||||
|
|
|
|||
|
|
@ -6,15 +6,4 @@ use Tests\TestCase;
|
|||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* A basic test example.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_example()
|
||||
{
|
||||
$response = $this->get('/');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,4 @@ use PHPUnit\Framework\TestCase;
|
|||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* A basic test example.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_example()
|
||||
{
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue