From 1c272881b689e11b85e29373ca385ea2d64d447e Mon Sep 17 00:00:00 2001 From: Noah Lancien Date: Fri, 20 Jun 2025 09:51:29 +0200 Subject: [PATCH 1/3] docs: added Meilisearch commands to the README --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 9bc9f3b3..7eb4a8c2 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,21 @@ Models list : ✅ Testimonial ❌ User ✅ Video + +# Meilisearch +When deploying, if there any modification to a concerned Model, you may refresh Meilisearch cache. Concerned models are : +- News +- NewsBreeding +- Product +- Recipe + +```sh +php artisan scout:flush "\App\Models\News" +php artisan scout:import "\App\Models\News" +php artisan scout:flush "\App\Models\NewsBreeding" +php artisan scout:import "\App\Models\NewsBreeding" +php artisan scout:flush "\App\Models\Product" +php artisan scout:import "\App\Models\Product" +php artisan scout:flush "\App\Models\Recipe" +php artisan scout:import "\App\Models\Recipe" +``` From ba2a837fc5ba79623d5db2423c1c3967348c10e4 Mon Sep 17 00:00:00 2001 From: Alexis Gousseau Date: Thu, 19 Jun 2025 17:37:29 +0200 Subject: [PATCH 2/3] feat: :sparkles: add ratings functionnalities #261 --- app/Enums/RatingEnum.php | 17 + app/Http/Controllers/Bo/ProductController.php | 2 +- app/Http/Controllers/Bo/RecipeController.php | 2 +- app/Http/Controllers/Controller.php | 223 +---------- app/Http/Controllers/Fo/ProductController.php | 18 +- app/Http/Controllers/Fo/RatingController.php | 58 +++ app/Http/Controllers/Fo/RecipeController.php | 2 + app/Http/Requests/Fo/SingleOptinRequest.php | 4 +- app/Http/Requests/Fo/StoreRatingRequest.php | 47 +++ app/Models/Customer.php | 10 + app/Models/Product.php | 24 +- app/Models/Rating.php | 113 ++++++ app/Models/Recipe.php | 24 +- composer.lock | 78 ++-- .../2025_06_19_144056_add_ratings_table.php | 37 ++ package-lock.json | 12 + package.json | 1 + resources/assets/images/ratings/btn-icon.svg | 4 + .../assets/images/ratings/customer-icon.png | Bin 0 -> 4904 bytes resources/lang/en/models.php | 1 + resources/lang/fr/models.php | 1 + resources/sass/fo/pages/product.scss | 22 +- .../fo/components/RatingSectionComponent.vue | 359 ++++++++++++++++++ .../ts/fo/components/RatingStarsComponent.vue | 136 +++++++ .../bo/pages/products/index-list.blade.php | 10 + .../bo/pages/recipes/index-list.blade.php | 10 + .../views/fo/pages/products/show.blade.php | 16 +- .../views/fo/pages/recipes/show.blade.php | 8 + .../views/modules/script-routes.blade.php | 10 +- routes/web-fo.php | 8 + 30 files changed, 964 insertions(+), 293 deletions(-) create mode 100644 app/Enums/RatingEnum.php create mode 100644 app/Http/Controllers/Fo/RatingController.php create mode 100644 app/Http/Requests/Fo/StoreRatingRequest.php create mode 100644 app/Models/Rating.php create mode 100644 database/migrations/2025_06_19_144056_add_ratings_table.php create mode 100644 resources/assets/images/ratings/btn-icon.svg create mode 100644 resources/assets/images/ratings/customer-icon.png create mode 100644 resources/ts/fo/components/RatingSectionComponent.vue create mode 100644 resources/ts/fo/components/RatingStarsComponent.vue diff --git a/app/Enums/RatingEnum.php b/app/Enums/RatingEnum.php new file mode 100644 index 00000000..241bb4b5 --- /dev/null +++ b/app/Enums/RatingEnum.php @@ -0,0 +1,17 @@ +with('ratings')->withAvg('ratings', 'rate'); /** @var string $search Search field */ $search = $request->search; diff --git a/app/Http/Controllers/Bo/RecipeController.php b/app/Http/Controllers/Bo/RecipeController.php index c9164f5e..29c96f8d 100644 --- a/app/Http/Controllers/Bo/RecipeController.php +++ b/app/Http/Controllers/Bo/RecipeController.php @@ -36,7 +36,7 @@ class RecipeController extends Controller public function index(Request $request): \Illuminate\Contracts\View\View { /** @var \Illuminate\Database\Eloquent\Builder $recipeModels */ - $recipeModels = Recipe::query(); + $recipeModels = Recipe::query()->with('ratings')->withAvg('ratings', 'rate'); /** @var string $search Search field */ $search = $request->search; diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index fc5963b7..c9b70a42 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -2,9 +2,6 @@ namespace App\Http\Controllers; -use App\Enums\Pagination\ItemsPerPaginationEnum; -use App\Enums\Theme\BootstrapThemeEnum; -use App\Lib\Helpers\ToolboxHelper; use App\Models\Discount; use App\Models\Message; use App\Models\Notification; @@ -13,13 +10,11 @@ use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator as LengthAwarePaginatorCustom; -use Illuminate\Routing\Controller as BaseController; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Schema; -use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\View; use Illuminate\Support\Str; +use Uwil\LaravelCommon\Controllers\Controller as BaseController; class Controller extends BaseController { @@ -73,203 +68,6 @@ class Controller extends BaseController return route('club.home.show'); } - /** - * Build Search query on specified fields - * splitting search into words or using whole sentence. - * - * @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder. - * @param string $search The query string. - * @param callable|null $searchQuery The query string. - * @param string ...$fields Either a simple array or a string - * in case of a string the two fields - * would be concatenate to make the search. - * @return \Illuminate\Database\Eloquent\Builder - */ - protected function searchQuery( - Builder $query, - string $search, - callable|null $searchQuery = null, - string ...$fields - ): \Illuminate\Database\Eloquent\Builder { - if (count($fields)) { - // Search using words. - $words = explode(' ', $search); - $query->where(function ($query) use ($searchQuery, $words, $search, $fields) { - foreach ($words as $word) { - if (!strlen($word)) { - continue; - } - $this->searchOnFields($query, $word, $fields); - } //end foreach - // Search whole using whole sentence. - $this->searchOnFields($query, $search, $fields); - if ($searchQuery) { - $searchQuery($query); - } - }); - } //end if - return $query; - } - - /** - * Build query search on all fields. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param string $search - * @param array $fields - * @return \Illuminate\Database\Eloquent\Builder - */ - private function searchOnFields( - Builder $query, - string $search, - array $fields - ): \Illuminate\Database\Eloquent\Builder { - $table = $query->getModel()->getTable(); - foreach ($fields as $field) { - $field = str_replace('?', '\?', $field); - if (is_array($field)) { - $query->orWhereRaw( - sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($field)->map(function ($q) use ($table) { - return "`$table`.`$q`"; - })->implode(', \' \', ')), - ['%' . htmlspecialchars($search) . '%'] - ); - return $query; - } - // Get models woth enum's label. - if (is_array($this->getSearchValue($query, $search, $field))) { - foreach ($this->getSearchValue($query, $search, $field) as $array) { - $query->orWhereRaw( - "UPPER(`$table`.`$field`) LIKE UPPER(?)", - ['%' . htmlspecialchars($array->value) . '%'] - ); - } - return $query; - } - // Get models with relation's name/label. - if (Str::endsWith($field, '_id')) { - $query->orWhereHas(Str::remove('_id', $field), function ($queryRelation) use ($field, $search) { - if (Schema::hasColumn(Str::remove('_id', $field) . 's', 'name')) { - $queryRelation->where('name', 'like', '%' . $search . '%'); - } elseif (Schema::hasColumn(Str::remove('_id', $field) . 's', 'label')) { - $queryRelation->where('label', 'like', '%' . $search . '%'); - } - }); - return $query; - } - $query->orWhereRaw( - "UPPER(`$table`.`$field`) LIKE UPPER(?)", - ['%' . htmlspecialchars($this->getSearchValue($query, $search, $field)) . '%'] - ); - } //end foreach - return $query; - } - - /** - * Get the search value (on string or Enum). - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param string $search - * @param string $field - * @return string|array - */ - protected function getSearchValue(Builder $query, string $search, string $field): string|array - { - $cast = $query->getModel()->getCasts()[$field] ?? null; - if ( - count($query->getModel()->getCasts()) and // Check if the model has casts. - $cast and // Check if there is cast about specific field. - enum_exists($cast) // Check if the cast is an enum. - ) { - /** @var \App\Traits\Enums $cast */ - // @phpstan-ignore-next-line - $cases = collect($cast::toArray()); - $enum = $cases->filter(function ($item) use ($search) { - return preg_match('/' . Str::lower($search) . '/', Str::lower($item->label)); - }); - return $enum->toArray(); - } else { - return $search; - } - } - - /** - * Sort columns with a query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param array $ignore - * @return \Illuminate\Database\Eloquent\Builder - */ - protected function sortQuery(Builder $query, array $ignore = []): \Illuminate\Database\Eloquent\Builder - { - $rName = request()->route()->getName(); - $table = $query->getModel()->getTable(); - if (request()->input('sort_col')) { - Session::put("$rName.sort_col", request()->input('sort_col')); - Session::put("$rName.sorted", true); - } - if (request()->input('sort_way')) { - Session::put("$rName.sort_way", request()->input('sort_way')); - Session::put("$rName.sorted", true); - } - if (request()->rst) { - Session::remove("$rName.sorted"); - Session::remove("$rName.sort_col"); - Session::remove("$rName.sort_way"); - } - if ( - request()->input('sort_col') and - request()->input('sort_way') and - Schema::hasColumn($table, request()->input('sort_col')) - ) { - if (!in_array(request()->input('sort_col'), $ignore)) { - $query = $query->orderBy( - \sprintf("$table.%s", request()->input('sort_col')), - request()->input('sort_way') - ); - } - } elseif (Schema::hasColumn($table, 'order')) { - $query = $query->orderBy('order', 'ASC'); - Session::put("$rName.sort_col", 'order'); - Session::put("$rName.sort_way", 'asc'); - } elseif (Schema::hasColumn($table, 'updated_at')) { - $query = $query->orderBy('updated_at', 'DESC'); - Session::put("$rName.sort_col", 'updated_at'); - Session::put("$rName.sort_way", 'desc'); - } elseif (Schema::hasColumn($table, 'created_at')) { - $query = $query->orderBy('created_at', 'DESC'); - Session::put("$rName.sort_col", 'created_at'); - Session::put("$rName.sort_way", 'desc'); - } - return $query; - } - - /** - * Customize pagination with cache or config (default). - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \App\Enums\Pagination\ItemsPerPaginationEnum|null $pagination - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - protected function paginate( - Builder $query, - ItemsPerPaginationEnum|null $pagination = null - ): \Illuminate\Contracts\Pagination\LengthAwarePaginator { - $currentRoutePath = Str::slug(request()->route()->getName()); - $cacheKey = "pagination.{$currentRoutePath}"; - $pagination = $pagination ?? ItemsPerPaginationEnum::twelve; - $pagination = ToolboxHelper::getValidatedEnum( - Cache::get($cacheKey, $pagination->value()), - 'pagination', - '\App\Enums\Pagination\ItemsPerPaginationEnum', - ); - if (Cache::get($cacheKey) !== $pagination) { - Cache::put($cacheKey, $pagination); - } - $query = $query->paginate($pagination); - return $query; - } - /** * Get a paginate resource. * @@ -286,25 +84,6 @@ class Controller extends BaseController return \response()->json($models); } - /** - * Set the bootstrap theme or light (default). - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse - */ - protected function setTheme(Request $request): \Illuminate\Http\RedirectResponse - { - $theme = ToolboxHelper::getValidatedEnum( - $request->theme ?: Cache::get('theme', BootstrapThemeEnum::light->value()), - 'theme', - '\App\Http\Controllers\BootstrapThemeEnum', - ); - if (Cache::get("theme") !== $theme) { - Cache::put("theme", $theme); - } - return redirect()->back()->with('success', trans('crud.messages.theme_updated')); - } - /** * Get models titles for search input propositions. * diff --git a/app/Http/Controllers/Fo/ProductController.php b/app/Http/Controllers/Fo/ProductController.php index 8cbed485..8610bfb6 100644 --- a/app/Http/Controllers/Fo/ProductController.php +++ b/app/Http/Controllers/Fo/ProductController.php @@ -8,6 +8,7 @@ use App\Lib\Helpers\CollectionHelper; use App\Models\Meat; use App\Models\Product; use App\Models\Range; +use App\Models\Rating; use App\Models\Recipe; use App\Models\StaticPage; use App\Models\Submeat; @@ -55,18 +56,18 @@ class ProductController extends Controller $meatModels = Meat::getPublishedOrderedWithSubmeats(); $submeatModels = $submeatModelsFiltered ?? Submeat::getPublishedOrdered(); return view('fo.pages.products.index', [ - 'viewsPaginate' => CollectionHelper::getViewsFromPaginateModels( + 'viewsPaginate' => CollectionHelper::getViewsFromPaginateModels( $productModels, 'fo.partials.product-card', 'model' ), - 'productDiscoverModels' => $productDiscoverModels, - 'staticPageModel' => $staticPageModel, - 'rangeModels' => $rangeModels, - 'subrangeModels' => $subrangeModels, - 'meatModels' => $meatModels, - 'submeatModels' => $submeatModels, - 'searchPropositions' => $this->getProductModelsTitles(), + 'productDiscoverModels' => $productDiscoverModels, + 'staticPageModel' => $staticPageModel, + 'rangeModels' => $rangeModels, + 'subrangeModels' => $subrangeModels, + 'meatModels' => $meatModels, + 'submeatModels' => $submeatModels, + 'searchPropositions' => $this->getProductModelsTitles(), ]); } @@ -116,6 +117,7 @@ class ProductController extends Controller 'recipeModel' => $recipeModel, 'videoModel' => $videoModel, 'staticPageModel' => $staticPageModel, + 'ratingsData' => Rating::getRatings($productModel), ]); } diff --git a/app/Http/Controllers/Fo/RatingController.php b/app/Http/Controllers/Fo/RatingController.php new file mode 100644 index 00000000..fe4ba177 --- /dev/null +++ b/app/Http/Controllers/Fo/RatingController.php @@ -0,0 +1,58 @@ +validated(); + + /** @var \App\Models\Product|\App\Models\Recipe $relatedModel */ + $relatedModel = app($validated['rating_type'])::findOrFail($validated['rating_id']); + + // Delete rating. + if ($validated['rate'] == 0) { + Rating::query()->where([ + ['customer_id', $validated['customer_id']], + ['rating_id', $validated['rating_id']], + ['rating_type', $validated['rating_type']], + ])->first()->delete(); + + return response()->json([ + 'message' => trans('Votre note a bien été supprimée !'), + 'data' => Rating::resetCache($relatedModel), + ]); + } + + // Save or update rating. + Rating::updateOrCreate( + [ + 'customer_id' => $validated['customer_id'], + 'rating_id' => $validated['rating_id'], + 'rating_type' => $validated['rating_type'], + ], + [ + 'rate' => $validated['rate'], + ] + ); + + return response()->json([ + 'message' => trans('Votre note a bien été enregistrée !'), + 'data' => Rating::resetCache($relatedModel), + ]); + }); + } +} diff --git a/app/Http/Controllers/Fo/RecipeController.php b/app/Http/Controllers/Fo/RecipeController.php index 13b39285..34ca39d0 100644 --- a/app/Http/Controllers/Fo/RecipeController.php +++ b/app/Http/Controllers/Fo/RecipeController.php @@ -8,6 +8,7 @@ use App\Http\Resources\Fo\Recipes\RecipeCommericalSheetResource; use App\Lib\Helpers\CollectionHelper; use App\Models\ConsumptionMoment; use App\Models\Meat; +use App\Models\Rating; use Illuminate\Database\Eloquent\Builder; use App\Models\Recipe; use App\Models\StaticPage; @@ -91,6 +92,7 @@ class RecipeController extends Controller 'recipeModels' => $this->getRecipesFiltered($recipeModel), 'associatedProductModels' => $recipeModel->products->where('published', true), 'staticPageModel' => $staticPageModel, + 'ratingsData' => Rating::getRatings($recipeModel), ]); } diff --git a/app/Http/Requests/Fo/SingleOptinRequest.php b/app/Http/Requests/Fo/SingleOptinRequest.php index 42fda12b..074be97b 100644 --- a/app/Http/Requests/Fo/SingleOptinRequest.php +++ b/app/Http/Requests/Fo/SingleOptinRequest.php @@ -11,7 +11,7 @@ class SingleOptinRequest extends FormRequest * * @return boolean */ - public function authorize() + public function authorize(): bool { return true; } @@ -21,7 +21,7 @@ class SingleOptinRequest extends FormRequest * * @return array */ - public function rules() + public function rules(): array { return [ 'email' => 'required|email:rfc,dns,spoof', diff --git a/app/Http/Requests/Fo/StoreRatingRequest.php b/app/Http/Requests/Fo/StoreRatingRequest.php new file mode 100644 index 00000000..64c482ad --- /dev/null +++ b/app/Http/Requests/Fo/StoreRatingRequest.php @@ -0,0 +1,47 @@ + 'required|integer|min:0|max:5', + 'rating_id' => 'required|integer|exists:products,id', + 'rating_type' => 'required|in:App\\Models\\Product,App\\Models\\Recipe', + 'customer_id' => 'required|integer|exists:customers,id', + ]; + } + + /** + * Get the error messages for the defined validation rules. + * + * @return array + */ + public function messages(): array + { + return [ + 'rate' => trans('notation'), + 'rating_id' => trans('élément ciblé'), + 'customer_id' => trans('utilisateur connecté'), + ]; + } +} diff --git a/app/Models/Customer.php b/app/Models/Customer.php index 38e68956..cbafc5e4 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -483,6 +483,16 @@ class Customer extends Authenticatable implements MustVerifyEmail return $this->hasMany(Message::class, 'from'); } + /** + * Get all of the sentMessages for the Customer. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function ratings(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(Rating::class); + } + /** * Get all of the recipes that are favorite by this customer. * diff --git a/app/Models/Product.php b/app/Models/Product.php index 0868109f..6cb51ad6 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -115,6 +115,15 @@ class Product extends Model implements SeoInterface, Sitemapable, SchemaOrgInter 'sortableAttributes' => ['label', 'description', 'preparation', 'ingredients'] ]; + /** + * Get sortable virtual column. + * + * @var array + */ + public static $sortableVirtualColumn = [ + 'ratings_avg_rate', + ]; + /** * Perform any actions required after the model boots. * @@ -126,8 +135,9 @@ class Product extends Model implements SeoInterface, Sitemapable, SchemaOrgInter self::assertLabelIsUnique($product->label); self::manageFields($product); }); - static::created(function () { + static::created(function (self $product) { self::resetCache(); + Rating::resetCache($product); }); static::updating(function (self $product) { self::assertLabelIsUnique($product->label, $product->getKey()); @@ -138,12 +148,14 @@ class Product extends Model implements SeoInterface, Sitemapable, SchemaOrgInter FileStorageHelper::removeOldFile($product, 'banner'); FileStorageHelper::removeOldFile($product, 'packaging'); self::resetCache(); + Rating::resetCache($product); }); static::deleted(function (self $product) { FileStorageHelper::removeFile($product, 'picture'); FileStorageHelper::removeFile($product, 'banner'); FileStorageHelper::removeFile($product, 'packaging'); self::resetCache(); + Rating::resetCache($product); }); } @@ -473,4 +485,14 @@ class Product extends Model implements SeoInterface, Sitemapable, SchemaOrgInter { return $this->hasMany(IncoNutritionalValue::class)->first(); } + + /** + * Get the nutritionnal values for the product. + * + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + public function ratings(): \Illuminate\Database\Eloquent\Relations\MorphMany + { + return $this->morphMany(Rating::class, 'rating'); + } } diff --git a/app/Models/Rating.php b/app/Models/Rating.php new file mode 100644 index 00000000..50532854 --- /dev/null +++ b/app/Models/Rating.php @@ -0,0 +1,113 @@ + + */ + protected $fillable = [ + 'customer_id', + 'rating_id', + 'rate', + 'rating_type', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'rate' => RatingEnum::class, + ]; + + // * METHODS + + /** + * Get all ratings data. + * + * @param \App\Models\Product|\App\Models\Recipe $model + * @return array + */ + public static function resetCache(Product|Recipe $model): array + { + Cache::forget(sprintf('%s-ratings', $model->slug)); + return self::getRatings($model); + } + + /** + * Get all ratings data. + * + * @param \App\Models\Product|\App\Models\Recipe $model + * @return array + */ + public static function getRatings(Product|Recipe $model): array + { + return Cache::remember(sprintf('%s-ratings', $model->slug), 86400, function () use ($model) { + /** @var \App\Models\Customer|null customerModel */ + $customerModel = auth('club')->user(); + + return [ + 'total' => $model->ratings()->count(), + 'totalRate' => round($model->ratings()->get()->avg(function (Rating $model) { + return $model->rate->value(); + }), 1), + 'ratings' => $model->ratings()->inRandomOrder() + ->take(6)->with('customer')->get()->each(function (Rating $rating) { + $rating->customer_username = $rating->customer->username; + return $rating; + }), + 'ratingType' => get_class($model), + 'customerRating' => $customerModel + ? $model->ratings()->where('customer_id', $customerModel->getkey())->first() + : null, + ]; + }); + } + + // * RELATIONSHIPS + + /** + * Get all of the products that are favorite by this customer. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function customer(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(Customer::class); + } + + /** + * Get the parent rateable model (Product or Recipe). + * + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + public function model(): \Illuminate\Database\Eloquent\Relations\MorphTo + { + return $this->morphTo('rating'); + } +} diff --git a/app/Models/Recipe.php b/app/Models/Recipe.php index dee41a4d..74a45ecc 100644 --- a/app/Models/Recipe.php +++ b/app/Models/Recipe.php @@ -103,6 +103,15 @@ class Recipe extends Model implements SeoInterface, Sitemapable, SchemaOrgInterf 'sortableAttributes' => ['title', 'preparation', 'advice'] ]; + /** + * Get sortable virtual column. + * + * @var array + */ + public static $sortableVirtualColumn = [ + 'ratings_avg_rate', + ]; + /** * Perform any actions required after the model boots. * @@ -115,8 +124,9 @@ class Recipe extends Model implements SeoInterface, Sitemapable, SchemaOrgInterf self::setHighlightValue($model); self::manageFields($model); }); - static::created(function () { + static::created(function (self $model) { self::resetCache(); + Rating::resetCache($model); }); static::updating(function (self $model) { self::assertTitleIsUnique($model->title, $model->getKey()); @@ -126,10 +136,12 @@ class Recipe extends Model implements SeoInterface, Sitemapable, SchemaOrgInterf static::updated(function (self $model) { FileStorageHelper::removeOldFile($model, 'picture'); self::resetCache(); + Rating::resetCache($model); }); static::deleted(function (self $model) { FileStorageHelper::removeFile($model, 'picture'); self::resetCache(); + Rating::resetCache($model); }); } @@ -446,4 +458,14 @@ class Recipe extends Model implements SeoInterface, Sitemapable, SchemaOrgInterf { return $this->morphToMany(Product::class, 'productable')->with(['meat', 'submeat']); } + + /** + * Get the nutritionnal values for the product. + * + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + public function ratings(): \Illuminate\Database\Eloquent\Relations\MorphMany + { + return $this->morphMany(Rating::class, 'rating'); + } } diff --git a/composer.lock b/composer.lock index 48c1e098..5c279c6d 100644 --- a/composer.lock +++ b/composer.lock @@ -4060,16 +4060,16 @@ }, { "name": "nesbot/carbon", - "version": "3.10.0", + "version": "3.10.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9" + "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/c1397390dd0a7e0f11660f0ae20f753d88c1f3d9", - "reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/1fd1935b2d90aef2f093c5e35f7ae1257c448d00", + "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00", "shasum": "" }, "require": { @@ -4161,7 +4161,7 @@ "type": "tidelift" } ], - "time": "2025-06-12T10:24:28+00:00" + "time": "2025-06-21T15:19:35+00:00" }, { "name": "nette/schema", @@ -5562,16 +5562,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.8", + "version": "v0.12.9", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625" + "reference": "1b801844becfe648985372cb4b12ad6840245ace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/85057ceedee50c49d4f6ecaff73ee96adb3b3625", - "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/1b801844becfe648985372cb4b12ad6840245ace", + "reference": "1b801844becfe648985372cb4b12ad6840245ace", "shasum": "" }, "require": { @@ -5635,9 +5635,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.8" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.9" }, - "time": "2025-03-16T03:05:19+00:00" + "time": "2025-06-23T02:35:06+00:00" }, { "name": "ralouphie/getallheaders", @@ -6004,16 +6004,16 @@ }, { "name": "sentry/sentry", - "version": "4.14.0", + "version": "4.14.1", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "f8c50304bd2f1640704345274bdd5af150b6945d" + "reference": "a28c4a6f5fda2bf730789a638501d7a737a64eda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/f8c50304bd2f1640704345274bdd5af150b6945d", - "reference": "f8c50304bd2f1640704345274bdd5af150b6945d", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/a28c4a6f5fda2bf730789a638501d7a737a64eda", + "reference": "a28c4a6f5fda2bf730789a638501d7a737a64eda", "shasum": "" }, "require": { @@ -6077,7 +6077,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/4.14.0" + "source": "https://github.com/getsentry/sentry-php/tree/4.14.1" }, "funding": [ { @@ -6089,7 +6089,7 @@ "type": "custom" } ], - "time": "2025-06-13T17:22:57+00:00" + "time": "2025-06-23T15:25:52+00:00" }, { "name": "sentry/sentry-laravel", @@ -9408,16 +9408,16 @@ }, { "name": "uwil/laravel-common", - "version": "3.2.1", + "version": "3.3.1", "source": { "type": "git", "url": "git@lab.s-web.fr:ldc-web/projets/laravel-common.git", - "reference": "f928001a45fad96874192ec9dd8400834259f0d9" + "reference": "fe5ee26f21a3aba48bc5fbd778bf51fdd63bacab" }, "dist": { "type": "zip", - "url": "https://lab.s-web.fr/api/v4/projects/215/packages/composer/archives/uwil/laravel-common.zip?sha=f928001a45fad96874192ec9dd8400834259f0d9", - "reference": "f928001a45fad96874192ec9dd8400834259f0d9", + "url": "https://lab.s-web.fr/api/v4/projects/215/packages/composer/archives/uwil/laravel-common.zip?sha=fe5ee26f21a3aba48bc5fbd778bf51fdd63bacab", + "reference": "fe5ee26f21a3aba48bc5fbd778bf51fdd63bacab", "shasum": "" }, "require": { @@ -10826,16 +10826,16 @@ }, { "name": "laravel-lang/attributes", - "version": "2.13.4", + "version": "2.13.5", "source": { "type": "git", "url": "https://github.com/Laravel-Lang/attributes.git", - "reference": "c3671787b92fb83da1f5a4f9b6dcc9f4e3edfafe" + "reference": "0f7a0c195cbd4eb000b14dc3fd233dacd23a10f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Laravel-Lang/attributes/zipball/c3671787b92fb83da1f5a4f9b6dcc9f4e3edfafe", - "reference": "c3671787b92fb83da1f5a4f9b6dcc9f4e3edfafe", + "url": "https://api.github.com/repos/Laravel-Lang/attributes/zipball/0f7a0c195cbd4eb000b14dc3fd233dacd23a10f7", + "reference": "0f7a0c195cbd4eb000b14dc3fd233dacd23a10f7", "shasum": "" }, "require": { @@ -10889,9 +10889,9 @@ ], "support": { "issues": "https://github.com/Laravel-Lang/attributes/issues", - "source": "https://github.com/Laravel-Lang/attributes/tree/2.13.4" + "source": "https://github.com/Laravel-Lang/attributes/tree/2.13.5" }, - "time": "2025-03-14T10:43:34+00:00" + "time": "2025-06-23T09:39:20+00:00" }, { "name": "laravel-lang/common", @@ -10976,16 +10976,16 @@ }, { "name": "laravel-lang/http-statuses", - "version": "3.10.3", + "version": "3.10.4", "source": { "type": "git", "url": "https://github.com/Laravel-Lang/http-statuses.git", - "reference": "32f9c3e9a93179ea6769844eaddfe8ae4cf3df63" + "reference": "97fdb638f95bf9ca8131cc887750d70980aa7be3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Laravel-Lang/http-statuses/zipball/32f9c3e9a93179ea6769844eaddfe8ae4cf3df63", - "reference": "32f9c3e9a93179ea6769844eaddfe8ae4cf3df63", + "url": "https://api.github.com/repos/Laravel-Lang/http-statuses/zipball/97fdb638f95bf9ca8131cc887750d70980aa7be3", + "reference": "97fdb638f95bf9ca8131cc887750d70980aa7be3", "shasum": "" }, "require": { @@ -11037,9 +11037,9 @@ ], "support": { "issues": "https://github.com/Laravel-Lang/http-statuses/issues", - "source": "https://github.com/Laravel-Lang/http-statuses/tree/3.10.3" + "source": "https://github.com/Laravel-Lang/http-statuses/tree/3.10.4" }, - "time": "2025-03-11T20:03:32+00:00" + "time": "2025-06-23T09:39:16+00:00" }, { "name": "laravel-lang/lang", @@ -12132,16 +12132,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.46", + "version": "10.5.47", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d" + "reference": "3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8080be387a5be380dda48c6f41cee4a13aadab3d", - "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3", + "reference": "3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3", "shasum": "" }, "require": { @@ -12213,7 +12213,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.46" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.47" }, "funding": [ { @@ -12237,7 +12237,7 @@ "type": "tidelift" } ], - "time": "2025-05-02T06:46:24+00:00" + "time": "2025-06-20T11:29:11+00:00" }, { "name": "sebastian/cli-parser", diff --git a/database/migrations/2025_06_19_144056_add_ratings_table.php b/database/migrations/2025_06_19_144056_add_ratings_table.php new file mode 100644 index 00000000..961fbc3e --- /dev/null +++ b/database/migrations/2025_06_19_144056_add_ratings_table.php @@ -0,0 +1,37 @@ +id(); + $table->foreignId('customer_id')->comment('Rating associated customer.')->constrained() + ->onUpdate('cascade')->onDelete('cascade'); + $table->unsignedBigInteger('rating_id')->comment('Rating key of the associated model.'); + $table->unsignedTinyInteger('rate')->comment('Rating note (Enum RatingEnum).'); + $table->string('rating_type')->comment('Rating associated model.'); + $table->timestamps(); + $table->unique(['customer_id', 'rating_id', 'rating_type']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::dropIfExists('ratings'); + } +}; diff --git a/package-lock.json b/package-lock.json index dfe96869..260f1146 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "bootstrap": "^5.3.2", "chart.js": "^4.4.0", "cropperjs": "^1.6.1", + "date-fns": "^4.1.0", "eslint": "latest", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-vue": "latest", @@ -3141,6 +3142,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", diff --git a/package.json b/package.json index eb45e2e1..dc813276 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "bootstrap": "^5.3.2", "chart.js": "^4.4.0", "cropperjs": "^1.6.1", + "date-fns": "^4.1.0", "eslint": "latest", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-vue": "latest", diff --git a/resources/assets/images/ratings/btn-icon.svg b/resources/assets/images/ratings/btn-icon.svg new file mode 100644 index 00000000..31c33a41 --- /dev/null +++ b/resources/assets/images/ratings/btn-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/assets/images/ratings/customer-icon.png b/resources/assets/images/ratings/customer-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..50e120720c9031c53a1ee94b8ad83849c4ddbc6a GIT binary patch literal 4904 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
    '\'Breeders map\' component', 'discount' => 'discount', 'sell_item' => 'item on sale|items on sale', + 'rating' => 'rating', ] ]; diff --git a/resources/lang/fr/models.php b/resources/lang/fr/models.php index 572afecf..0f5dec02 100644 --- a/resources/lang/fr/models.php +++ b/resources/lang/fr/models.php @@ -39,5 +39,6 @@ return [ 'breeder_map_component' => 'composant \'Carte des éleveurs\'', 'discount' => 'réduction', 'sell_item' => 'objet en vente|objets en vente', + 'rating' => 'note', ] ]; diff --git a/resources/sass/fo/pages/product.scss b/resources/sass/fo/pages/product.scss index 8bb97ae4..7971af5d 100644 --- a/resources/sass/fo/pages/product.scss +++ b/resources/sass/fo/pages/product.scss @@ -136,17 +136,6 @@ } } - .modal { - z-index: 1035 !important; - - button { - top: -0.75rem; - right: -0.75rem; - width: 2rem; - height: 2rem; - } - } - .tag-new { top: -1.5rem; left: 3rem; @@ -222,6 +211,17 @@ } } } + + .modal { + z-index: 1035 !important; + + .btn-modal-close { + top: -0.75rem; + right: -0.75rem; + width: 2rem; + height: 2rem; + } + } } // Set modal filter behind cookies. diff --git a/resources/ts/fo/components/RatingSectionComponent.vue b/resources/ts/fo/components/RatingSectionComponent.vue new file mode 100644 index 00000000..8995eef3 --- /dev/null +++ b/resources/ts/fo/components/RatingSectionComponent.vue @@ -0,0 +1,359 @@ + + + + + diff --git a/resources/ts/fo/components/RatingStarsComponent.vue b/resources/ts/fo/components/RatingStarsComponent.vue new file mode 100644 index 00000000..d88e9167 --- /dev/null +++ b/resources/ts/fo/components/RatingStarsComponent.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/resources/views/bo/pages/products/index-list.blade.php b/resources/views/bo/pages/products/index-list.blade.php index 376ca222..ed1b4886 100644 --- a/resources/views/bo/pages/products/index-list.blade.php +++ b/resources/views/bo/pages/products/index-list.blade.php @@ -3,6 +3,7 @@ @include('bo.modules.table-col-sorter', [ 'cols' => [ + 'ratings_avg_rate' => str(__('models.classes.rating'))->ucfirst(), 'label' => str(__('validation.custom.label'))->ucfirst(), 'packaging' => str(__('validation.attributes.image'))->ucfirst(), 'published' => str(__('validation.custom.publication'))->ucfirst(), @@ -13,6 +14,15 @@ @foreach ($productModels as $productModel) + + @if ($productModel->ratings->count()) +

    + {{ round($productModel->ratings_avg_rate, 1) }} +

    + @else + {{ __('Aucune note') }} + @endif + {{ $productModel->label }} @include('bo.modules.table-col-sorter', [ 'cols' => [ + 'ratings_avg_rate' => str(__('models.classes.rating'))->ucfirst(), 'title' => str(__('validation.attributes.title'))->ucfirst(), 'picture' => str(__('validation.attributes.image'))->ucfirst(), 'highlight' => str(__('validation.custom.highlight'))->ucfirst(), @@ -14,6 +15,15 @@ @foreach ($recipeModels as $recipeModel) + + @if ($recipeModel->ratings->count()) +

    + {{ round($recipeModel->ratings_avg_rate, 1) }} +

    + @else + {{ __('Aucune note') }} + @endif + {{ $recipeModel->title }} @@ -210,7 +209,7 @@ {{-- PRODUCTTYPE/DEMARCHE DESKTOP --}} @if (count($productModel->productTypes)) {{-- CHANGEME: quand la relation product<->productType aura été changé, adapter cette partie --}} -
    +
    {{ $productModel->productTypes[0]->picture_alt }} @@ -260,7 +259,7 @@