add search belongsto dropdown to video crud

This commit is contained in:
Nicolas Demangeat 2024-05-07 10:51:33 +02:00
parent 1fb5c73a23
commit bba1aaecd9
13 changed files with 168 additions and 39 deletions

View file

@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\KeyNumbers\StoreKeyNumberRequest;
use App\Http\Requests\Bo\KeyNumbers\UpdateKeyNumberRequest;
use App\Models\KeyNumber;
use App\Traits\Controllers\UpdateModelPublished;
use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class ProductController extends Controller
{
use UpdateModelPublished;
use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(KeyNumber::class);
}
/**
* Get a paginate resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function jsonPaginate(Request $request): \Illuminate\Http\JsonResponse
{
/** @var \App\Models\Product $productModels */
$productModels = $request->targetModel::query()
->orderBy('label', 'ASC')
->paginate($request->input('paginate') ?? 10);
return \response()->json($productModels);
}
}

View file

@ -79,7 +79,9 @@ class VideoController extends Controller
*/ */
public function create(Video $video): \Illuminate\Contracts\View\View public function create(Video $video): \Illuminate\Contracts\View\View
{ {
return view('bo.pages.videos.create', ['videoModel' => $video]); $productModels = Product::query()->orderBy('label', 'ASC')->paginate(10);
return view('bo.pages.videos.create', ['videoModel' => $video, 'productModels' => $productModels]);
} }
/** /**
@ -115,7 +117,7 @@ class VideoController extends Controller
*/ */
public function edit(Video $video): \Illuminate\Contracts\View\View public function edit(Video $video): \Illuminate\Contracts\View\View
{ {
$productModels = Product::query()->get(); $productModels = Product::query()->orderBy('label', 'ASC')->paginate(10);
return view('bo.pages.videos.edit', ['videoModel' => $video, 'productModels' => $productModels]); return view('bo.pages.videos.edit', ['videoModel' => $video, 'productModels' => $productModels]);
} }
@ -180,4 +182,16 @@ class VideoController extends Controller
{ {
return $this->create($video->replicate()); return $this->create($video->replicate());
} }
/**
* Duplicate the specified resource.
*
* @param \App\Models\Video $video
* @return \Illuminate\Http\RedirectResponse
*/
public function changeHomepage(Video $video): \Illuminate\Http\RedirectResponse
{
$video->update(['is_on_homepage' => !$video->getOriginal('is_on_homepage')]);
return redirect()->back()->with('success', trans(__('crud.messages.publish_status_saved')));
}
} }

View file

@ -29,7 +29,8 @@ class StoreVideoRequest extends FormRequest
protected function prepareForValidation(): void protected function prepareForValidation(): void
{ {
$this->merge([ $this->merge([
'published' => $this->boolean('published') 'published' => $this->boolean('published'),
'is_on_homepage' => $this->boolean('is_on_homepage'),
]); ]);
} }
@ -43,6 +44,7 @@ class StoreVideoRequest extends FormRequest
return [ return [
'title' => 'required|string|between:3,255', 'title' => 'required|string|between:3,255',
'video_id' => 'required|string|between:3,255', 'video_id' => 'required|string|between:3,255',
'product_id' => 'sometimes|nullable|integer|exists:products,id',
'is_on_homepage' => 'required|boolean', 'is_on_homepage' => 'required|boolean',
'published' => 'required|boolean', 'published' => 'required|boolean',
]; ];
@ -57,9 +59,9 @@ class StoreVideoRequest extends FormRequest
{ {
return [ return [
'title' => trans('validation.attributes.title'), 'title' => trans('validation.attributes.title'),
'video_id' => trans('validation.attributes.source'), 'video_id' => trans('validation.custom.video_id'),
'is_on_homepage' => trans('validation.attributes.homepage'), 'is_on_homepage' => trans('validation.custom.on_homepage'),
'published' => trans('validation.attributes.published') 'published' => trans('validation.custom.published')
]; ];
} }
} }

View file

@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Model;
/** /**
* @property string $title Video title. * @property string $title Video title.
* @property string $video_id Youtube/Vimeo video ID. * @property string $video_id Youtube/Vimeo video ID.
* @property \App\Models\Product|null $product_id Associated product.
* @property integer $order Change order request. * @property integer $order Change order request.
* @property boolean $published Video publish status. * @property boolean $published Video publish status.
* @property boolean $is_on_homepage Is the video on the home page? * @property boolean $is_on_homepage Is the video on the home page?
@ -30,11 +31,22 @@ class Video extends Model
protected $fillable = [ protected $fillable = [
'title', 'title',
'video_id', 'video_id',
'product_id',
'is_on_homepage', 'is_on_homepage',
'order', 'order',
'published', 'published',
]; ];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'published' => 'boolean',
'is_on_homepage' => 'boolean',
];
/** /**
* Perform any actions required after the model boots. * Perform any actions required after the model boots.
* *

View file

@ -14,6 +14,7 @@ use App\Models\Range;
use App\Models\Submeat; use App\Models\Submeat;
use App\Models\Subrange; use App\Models\Subrange;
use App\Models\User; use App\Models\User;
use App\Models\Video;
use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -64,6 +65,7 @@ class RouteServiceProvider extends ServiceProvider
Route::model('submeat', Submeat::class); Route::model('submeat', Submeat::class);
Route::model('approach', Approach::class); Route::model('approach', Approach::class);
Route::model('consumption_moment', ConsumptionMoment::class); Route::model('consumption_moment', ConsumptionMoment::class);
Route::model('video', Video::class);
} }
/** /**

View file

@ -18,7 +18,7 @@ return new class extends Migration
$table->string('title')->comment('Video title.'); $table->string('title')->comment('Video title.');
$table->string('video_id')->comment('The video ID.'); $table->string('video_id')->comment('The video ID.');
$table->boolean('is_on_homepage')->comment('Is display on homepage ?'); $table->boolean('is_on_homepage')->comment('Is display on homepage ?');
$table->foreignId('product_id')->comment('Associated product')->constrained() $table->foreignId('product_id')->nullable()->comment('Associated product')->constrained()
->onUpdate('no action')->onDelete('cascade'); ->onUpdate('no action')->onDelete('cascade');
$table->integer('order')->comment('Video order.'); $table->integer('order')->comment('Video order.');
$table->boolean('published')->comment('Video publish status.'); $table->boolean('published')->comment('Video publish status.');

View file

@ -32,6 +32,7 @@ import {
faEye, faEye,
faEyeSlash, faEyeSlash,
faFile, faFile,
faFilm,
faFolderOpen, faFolderOpen,
faHouse, faHouse,
faKey, faKey,
@ -109,5 +110,6 @@ library.add(faArrowRightArrowLeft);
library.add(faBuildingWheat); library.add(faBuildingWheat);
library.add(faClock); library.add(faClock);
library.add(faDrumstickBite); library.add(faDrumstickBite);
library.add(faFilm);
dom.watch(); dom.watch();

View file

@ -186,7 +186,7 @@
'btn btn-primary w-100 text-start mt-1', 'btn btn-primary w-100 text-start mt-1',
'fw-bold active' => request()->routeIs('bo.videos.*'), 'fw-bold active' => request()->routeIs('bo.videos.*'),
])> ])>
<i class="fa-solid fa-user-tie me-1"></i>&nbsp;{{ str(__('models.classes.video'))->ucFirst() }} <i class="fa-solid fa-film"></i>&nbsp;{{ str(__('models.classes.video'))->ucFirst() }}
</a> </a>
</div> </div>
</nav> </nav>

View file

@ -41,20 +41,16 @@
@include('bo.partials.inputs.varchar', [ @include('bo.partials.inputs.varchar', [
'id' => 'videoId', 'id' => 'videoId',
'label' => str( 'label' => str(
__(':field :inter:model', [ __(':field', [
'field' => __('validation.custom.video_id'), 'field' => __('validation.custom.video_id')
'inter' => __('validation.custom.of_the.female'),
'model' => __('models.classes.video'),
]))->ucfirst(), ]))->ucfirst(),
'hasWordCounter' => true, 'hasWordCounter' => true,
'name' => 'video_id', 'name' => 'video_id',
'required' => true, 'required' => true,
'model' => $videoModel ?? null, 'model' => $videoModel ?? null,
'helper' => __('validation.between.string', [ 'helper' => __('validation.between.string', [
'attribute' => __(':field :inter:model', [ 'attribute' => __(':field', [
'field' => __('validation.custom.video_id'), 'field' => __('validation.custom.video_id')
'inter' => __('validation.custom.of_the.female'),
'model' => __('models.classes.video'),
]), ]),
'min' => 3, 'min' => 3,
'max' => 255, 'max' => 255,
@ -71,21 +67,26 @@
<legend>{{ str(__('models.classes.product'))->ucfirst() }}</legend> <legend>{{ str(__('models.classes.product'))->ucfirst() }}</legend>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
@include('bo.partials.inputs.bigint-foreign-id', [ @include('bo.partials.inputs.search-belongs-to-dropdown', [
'id' => 'videoProductId', 'id' => 'videoProduct',
'label' => __(':field :inter:model', [ 'label' => __(':model associée', [
'field' => str(__('models.classes.product'))->ucfirst(), 'model' => str(__('models.classes.product'))->ucFirst(),
'inter' => __('validation.custom.of_the.female'),
'model' => __('models.classes.video'),
]), ]),
'name' => 'product_id', 'name' => 'product_id',
'required' => true, 'targetClass' => 'App\Models\Product',
'model' => $videoModel ?? null, 'modelListPaginate' => $productModels ?? [],
'associatedModels' => $productModels ?? null, 'modelSelected' => $videoModel->product,
'foreignLabel' => 'label', 'routeName' => 'bo.products.json-paginate',
'helper' => __('crud.minicrud.associated_entity', [ 'multiple' => false,
'entity' => __('models.classes.product'), 'fieldName' => 'label',
'required' => false,
'disabled' => false,
'placeholder' => __('Rechercher un :model', [
'model' => __('models.classes.product'),
]), ]),
'helper' => __(
'Sélectionner un :model qui sera associée parmis la liste (un seul et unique :model doit être choisi).',
['model' => __('models.classes.product')]),
]) ])
</div> </div>
</div> </div>

View file

@ -5,28 +5,43 @@
'cols' => [ 'cols' => [
'title' => str(__('validation.attributes.title'))->ucfirst(), 'title' => str(__('validation.attributes.title'))->ucfirst(),
'is_on_homepage' => str(__('validation.custom.on_homepage'))->ucfirst(), 'is_on_homepage' => str(__('validation.custom.on_homepage'))->ucfirst(),
'order' => str(__('validation.custom.order'))->ucfirst(),
'published' => str(__('validation.custom.activated'))->ucfirst(), 'published' => str(__('validation.custom.activated'))->ucfirst(),
'updated_at' => str(__('validation.attributes.updated_at'))->ucfirst(), 'updated_at' => str(__('validation.attributes.updated_at'))->ucfirst(),
'order' => str(__('validation.custom.order'))->ucfirst(),
], ],
]) ])
</thead> </thead>
<tbody> <tbody>
@foreach ($videoModels as $videoModel) @foreach ($videoModels as $videoModel)
<tr class="border-bottom"> <tr class="border-bottom">
<td class="text-center align-middle">{{ $videoModel->title }}</td>
{{-- FIXME: modifier l'affichage de bool is on homepage, peut etre voir pour faire comme publish --}}
<td class="text-center align-middle">{{ $videoModel->is_on_homepage ? 'oui' : 'non' }}</td>
@php @php
$routeName = request()->route()->getName(); $routeName = request()->route()->getName();
@endphp @endphp
@if (empty(request()->search) && session()->get("$routeName.sort_col") === 'order') <td class="text-center align-middle">{{ $videoModel->title }}</td>
@include('bo.modules.change-model-order', [ <td class="text-center align-middle">
'routeName' => 'videos', @can('changePublished', $videoModel)
'model' => $videoModel, <form action="{{ route('bo.videos.change-homepage', ['video' => $videoModel]) }}" method="POST">
'models' => $videoModels, @csrf
]) @method('PATCH')
@endif <button data-bs-toggle="tooltip" type="submit"
title="{{ __($videoModel->is_on_homepage ? __('crud.other.publish') : __('crud.other.unpublish')) }}" @class([
'btn btn-sm',
'btn-primary' => $videoModel->is_on_homepage,
'btn-danger' => !$videoModel->is_on_homepage,
])>
<i @class([
'fa-solid',
'fa-circle-check' => $videoModel->is_on_homepage,
'fa-circle-xmark' => !$videoModel->is_on_homepage,
])></i>
</button>
</form>
@else
<span class="text-danger" data-bs-toggle="tooltip" title="{{ __('crud.other.right') }}">
<i class="fa-solid fa-ban"></i>
</span>
@endcan
</td>
@include('bo.modules.change-published-status', [ @include('bo.modules.change-published-status', [
'routeName' => 'videos', 'routeName' => 'videos',
'model' => $videoModel, 'model' => $videoModel,
@ -34,6 +49,13 @@
<td class="text-center align-middle"> <td class="text-center align-middle">
<span class="badge text-bg-secondary">{{ $videoModel->updated_at->isoFormat('LLLL') }}</span> <span class="badge text-bg-secondary">{{ $videoModel->updated_at->isoFormat('LLLL') }}</span>
</td> </td>
@if (empty(request()->search) && session()->get("$routeName.sort_col") === 'order')
@include('bo.modules.change-model-order', [
'routeName' => 'videos',
'model' => $videoModel,
'models' => $videoModels,
])
@endif
<td class="text-end align-middle"> <td class="text-end align-middle">
@canAny(['view', 'duplicate', 'update', 'delete'], $videoModel) @canAny(['view', 'duplicate', 'update', 'delete'], $videoModel)
<form class="btn-group confirmDeleteTS" action="{{ route('bo.videos.destroy', $videoModel) }}" method="POST" <form class="btn-group confirmDeleteTS" action="{{ route('bo.videos.destroy', $videoModel) }}" method="POST"

View file

@ -13,4 +13,5 @@
window.__SYSTEM._routes['bo.menu_node_data.index'] = '{{ route('bo.menu_node_data.index') }}'; window.__SYSTEM._routes['bo.menu_node_data.index'] = '{{ route('bo.menu_node_data.index') }}';
window.__SYSTEM._routes['bo.menu_node_data.update'] = '{{ route('bo.menu_node_data.update', ['menu_node_data' => 'MENU_NODE_DATA']) }}'; window.__SYSTEM._routes['bo.menu_node_data.update'] = '{{ route('bo.menu_node_data.update', ['menu_node_data' => 'MENU_NODE_DATA']) }}';
window.__SYSTEM._routes['bo.approaches.json-paginate'] = '{{ route('bo.approaches.json-paginate') }}'; window.__SYSTEM._routes['bo.approaches.json-paginate'] = '{{ route('bo.approaches.json-paginate') }}';
window.__SYSTEM._routes['bo.products.json-paginate'] = '{{ route('bo.products.json-paginate') }}';
</script> </script>

View file

@ -16,6 +16,7 @@ use App\Models\StaticPage;
use App\Models\Submeat; use App\Models\Submeat;
use App\Models\Subrange; use App\Models\Subrange;
use App\Models\User; use App\Models\User;
use App\Models\Video;
use Diglactic\Breadcrumbs\Breadcrumbs; use Diglactic\Breadcrumbs\Breadcrumbs;
use Diglactic\Breadcrumbs\Generator; use Diglactic\Breadcrumbs\Generator;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -364,3 +365,27 @@ Breadcrumbs::for('bo.consumption_moments.duplicate', function (Generator $trail)
$trail->parent('bo.consumption_moments.index'); $trail->parent('bo.consumption_moments.index');
$trail->push(Str::ucfirst(trans('crud.actions.duplicate'))); $trail->push(Str::ucfirst(trans('crud.actions.duplicate')));
}); });
// * VIDEOS
Breadcrumbs::for('bo.videos.index', function (Generator $trail) {
$trail->push(
Str::of(trans('models.classes.video'))->plural()->ucfirst(),
route('bo.videos.index')
);
});
Breadcrumbs::for('bo.videos.show', function (Generator $trail, Video $video) {
$trail->parent('bo.videos.index');
$trail->push(Str::ucfirst(trans('crud.actions.view')), route('bo.videos.show', $video));
});
Breadcrumbs::for('bo.videos.create', function (Generator $trail) {
$trail->parent('bo.videos.index');
$trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.videos.create'));
});
Breadcrumbs::for('bo.videos.edit', function (Generator $trail, Video $video) {
$trail->parent('bo.videos.index');
$trail->push(Str::ucfirst(trans('crud.actions.edit')), route('bo.videos.edit', $video));
});
Breadcrumbs::for('bo.videos.duplicate', function (Generator $trail) {
$trail->parent('bo.videos.index');
$trail->push(Str::ucfirst(trans('crud.actions.duplicate')));
});

View file

@ -11,6 +11,7 @@ use App\Http\Controllers\Bo\KeyNumberController;
use App\Http\Controllers\Bo\MainSlideController; use App\Http\Controllers\Bo\MainSlideController;
use App\Http\Controllers\Bo\MenuNodeDataController; use App\Http\Controllers\Bo\MenuNodeDataController;
use App\Http\Controllers\Bo\MeatController; use App\Http\Controllers\Bo\MeatController;
use App\Http\Controllers\Bo\ProductController;
use App\Http\Controllers\Bo\ProfessionController; use App\Http\Controllers\Bo\ProfessionController;
use App\Http\Controllers\Bo\RangeController; use App\Http\Controllers\Bo\RangeController;
use App\Http\Controllers\Bo\StaticPageController; use App\Http\Controllers\Bo\StaticPageController;
@ -238,12 +239,17 @@ Route::prefix('bo')
Route::patch('/videos/{video}/change-published', [ Route::patch('/videos/{video}/change-published', [
VideoController::class, 'changePublished' VideoController::class, 'changePublished'
])->name('videos.change-published'); ])->name('videos.change-published');
Route::patch('/videos/{video}/change-homepage', [
VideoController::class, 'changeHomepage'
])->name('videos.change-homepage');
Route::patch('/videos/{video}/change-order/{direction}', [ Route::patch('/videos/{video}/change-order/{direction}', [
VideoController::class, 'changeOrder' VideoController::class, 'changeOrder'
])->where('direction', 'up|down')->name('videos.change-order'); ])->where('direction', 'up|down')->name('videos.change-order');
Route::get('/videos/{video}/duplicate', [ Route::get('/videos/{video}/duplicate', [
VideoController::class, 'duplicate' VideoController::class, 'duplicate'
])->name('videos.duplicate'); ])->name('videos.duplicate');
Route::get('/products/json/paginate', [ProductController::class, 'jsonPaginate'])
->name('products.json-paginate');
Route::middleware('can:isConceptor')->group(function () { Route::middleware('can:isConceptor')->group(function () {
// * ACTIVITY LOGS. // * ACTIVITY LOGS.