tags an search

commit d72af19ac7c028c514ee9055386c65bf4fcc0ffc
Author: Olivier Legat <olivier.legat@ldc.fr>
Date:   Fri Apr 9 15:08:30 2021 +0200

    recherche - filtrage basique

commit 926efbe97826d6ab8168ccb93f329283c11fd4ec
Author: Olivier Legat <olivier.legat@ldc.fr>
Date:   Fri Apr 9 14:15:17 2021 +0200

    placeholder add tag

commit a93d542a6a294bf214a952236bc3cd708b019241
Author: Olivier Legat <olivier.legat@ldc.fr>
Date:   Fri Apr 9 14:11:39 2021 +0200

    résistance de l'ajour de tags au accents, majuscules, etc..

commit c5eb6296e9ae2fff2fb7e15a93349bfc965cac53
Author: Olivier Legat <olivier.legat@ldc.fr>
Date:   Fri Apr 9 13:34:59 2021 +0200

    BO tags sur produits
This commit is contained in:
Olivier Legat 2021-04-09 15:09:36 +02:00
parent d7dc16fd07
commit 3bd65ee2f0
15 changed files with 1840 additions and 17 deletions

View file

@ -7,6 +7,7 @@ use App\Models\OldSlug;
use App\Models\FoodProduct;
use App\Models\Product;
use App\Models\Range;
use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
@ -141,7 +142,9 @@ class ProductsController extends Controller
$ranges = Range::all();
return view('bo.products.edit', compact('product', 'ranges'));
$existingTags = Tag::all();
$selectedtags = $product->tags;
return view('bo.products.edit', compact('product', 'ranges','existingTags','selectedtags'));
}
public function update(Request $request, $id)
@ -161,6 +164,7 @@ class ProductsController extends Controller
$star = $request->star === null ? 0 : 1;
$slug = Str::slug($name);
$rangeId = $request->range_id;
$tags = $request->tags;
$request->validate([
'name' => 'required|string|max:255',
@ -246,9 +250,37 @@ class ProductsController extends Controller
$this->$methodName($request, $product->id);
}
$arTagIds = array();
foreach($tags as $tag) {
$oTag = json_decode($tag);
if(empty($oTag->id)) {
//we need a new tag
$arTagIds[] = $this->storeTag($oTag);
} else {
$arTagIds[] = $oTag->id;
}
}
//prise en charge de la liaison avec les produits
$product->tags()->sync($arTagIds);
return redirect()->route('bo.products.edit', $product->id)->with('success', 'Modifications enregistrés !');
}
public function storeTag($oTag)
{
$slug = Str::slug($oTag->label);
$tag = Tag::where('slug', $slug)->first();
if($tag === null) {
//si on pas encore ce tag on le crée
$tag = new Tag();
$tag->label = $oTag->label;
$tag->slug = Str::slug($oTag->label);
$tag->save();
}
return $tag->id;
}
public function destroy($id)
{
$product = Product::findOrFail($id);

View file

@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Models\Product;
use App\Models\Range;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class ProductsController extends Controller
{
@ -40,7 +41,24 @@ class ProductsController extends Controller
public function allProducts(Request $request) {
//do better
$products = Product::where('status', 1)->paginate(12);
return view ('search', compact('products'));
$filter = $request->query('filter');
$filtertag = $request->query('tag');
if (!empty($filter)) {
$products = Product::where('status', 1)
->where('name', 'like', '%'.$filter.'%')
->paginate(12);
} elseif (!empty($filtertag)) {
$products = Product::where('status', 1)
->whereHas('tags', function($q) use($filtertag)
{
$q->where('slug', '=', Str::slug($filtertag));
})
->paginate(12);
} else {
$products = Product::where('status', 1)->paginate(12);
}
return view ('search', compact('products', 'filter', 'filtertag'));
}
}

View file

@ -264,4 +264,10 @@ class Product extends Model implements Buyable
{
return $this->belongsTo(TaxRate::class, 'tax_rate_id');
}
//tags
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}

18
app/Models/Tag.php Normal file
View file

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
use HasFactory;
public $timestamps = false;
public function products()
{
return $this->belongsToMany(Product::class);
}
}

View file

@ -0,0 +1,37 @@
<?php
use Database\Seeders\TagsSeeder;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTagsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('label');
$table->string('slug');
$table->collation = 'utf8mb4_unicode_ci';
});
$seeder = new TagsSeeder();
$seeder->run();
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('tags');
}
}

View file

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProductTagTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('product_tag', function (Blueprint $table) {
$table->foreignId('product_id')->constrained('products'); //produit considéré
$table->foreignId('tag_id')->constrained('tags'); //tag associée
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('product_tag');
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Database\Seeders;
use App\Models\Tag;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;
class TagsSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$canard = new Tag();
$canard->label = "Canard";
$canard->slug = Str::slug($canard->label);
$canard->save();
$poulet = new Tag();
$poulet->label = "Poulet";
$poulet->slug = Str::slug($poulet->label);
$poulet->save();
$dinde = new Tag();
$dinde->label = "Dinde";
$dinde->slug = Str::slug($dinde->label);
$dinde->save();
$charcuterie = new Tag();
$charcuterie->label = "Charcuteries";
$charcuterie->slug = Str::slug($charcuterie->label);
$charcuterie->save();
$panes = new Tag();
$panes->label = "Panés";
$panes->slug = Str::slug($panes->label);
$panes->save();
$dinde = new Tag();
$dinde->label = "Dinde";
$dinde->slug = Str::slug($dinde->label);
$dinde->save();
$produits_surgeles = new Tag();
$produits_surgeles->label = "Produits surgelés";
$produits_surgeles->slug = Str::slug($produits_surgeles->label);
$produits_surgeles->save();
$paniers = new Tag();
$paniers->label = "Paniers garnis";
$paniers->slug = Str::slug($paniers->label);
$paniers->save();
}
}

6
package-lock.json generated
View file

@ -1124,6 +1124,12 @@
"integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==",
"dev": true
},
"@voerro/vue-tagsinput": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@voerro/vue-tagsinput/-/vue-tagsinput-2.7.1.tgz",
"integrity": "sha512-G7ClILHgtc/D1vEw9qp6dlsAstlhwLDkPvEp6w1iY/dmSzu4hhxaMk8jyahjxQRDgiLlrAikESOsfkVhy18YwQ==",
"dev": true
},
"@vue/component-compiler-utils": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.2.0.tgz",

View file

@ -10,6 +10,7 @@
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"@voerro/vue-tagsinput": "^2.7.1",
"axios": "^0.21.1",
"bootstrap": "^4.6.0",
"cross-env": "^7.0",

1534
public/b-o/js/app.js vendored

File diff suppressed because one or more lines are too long

3
resources/js/app.js vendored
View file

@ -5,9 +5,12 @@ window.Vue = require('vue');
// import Vuex from 'vuex';
// import { Container, Draggable } from 'vue-smooth-dnd';
// import { applyDrag, generateItems } from './helpers';
import VoerroTagsInput from '@voerro/vue-tagsinput';
Vue.component('faq', require('./components/Faq.vue').default);
Vue.component('products-cards-dnd', require('./components/ProductsCardsDnd.vue').default);
Vue.component('tags-input', VoerroTagsInput);
Vue.component('auto-complete-tags', require('./components/AutoCompleteTags.vue').default);
const app = new Vue({
el: '#app',

View file

@ -0,0 +1,43 @@
<template>
<tags-input element-id="tags"
v-model="selectedTags"
:existing-tags="existingtags"
id-field="id"
text-field="label"
placeholder="Ajouter un tag"
:typeahead="true"
typeahead-style="dropdown">
</tags-input>
</template>
<script>
export default ({
components: { VoerroTagsInput },
props: {
dbexistingtags: {
type: String,
},
dbselectedtags: {
type: String,
},
},
data() {
return {
selectedTags: [],
existingtags: [],
}
},
mounted() {
console.log('Autocomplete Tags Component mounted.');
this.existingtags = JSON.parse(this.dbexistingtags);
this.selectedTags = JSON.parse(this.dbselectedtags);
},
methods: {
}
})
</script>
<style scoped>
@import url("https://cdn.jsdelivr.net/npm/@voerro/vue-tagsinput@2.7.1/dist/style.css");
</style>

View file

@ -26,6 +26,10 @@
<label for="name">Nom du produit *</label>
<input type="text" id="name" name="name" class="form-control" placeholder="Nom du produit" value="{{$product->name}}" required>
</div>
<div class="form-group">
<label for="name">Tags</label>
<auto-complete-tags dbexistingtags='@json($existingTags)' dbselectedtags='@json($selectedtags)'></auto-complete-tags>
</div>
<div class="form-group">
<label>Description *</label>
<textarea class="form-control" id="description" name="description" placeholder="Description *">{{$product->description}}</textarea>

View file

@ -3,7 +3,21 @@
@section('title', __('Tous les produits'))
@section('content')
<div class="auth-form auth-form_register">
<form class="auth-form__form form" method="GET">
<div class="form__row">
<div class="form__field">
<input type="text" class="form__input" id="filter" name="filter" placeholder="Nom du produit" value="{{$filter}}">
</div>
<div class="form__field">
<input type="text" class="form__input" id="tag" name="tag" placeholder="Tag" value="{{$filtertag}}">
</div>
</div>
<div class="form__field form__submit">
<button type="submit" class="form__btn btn btn_blue">{{ __('Filtrer') }}</button>
</div>
</form>
</div>
@include('partials.products-list')
@include('partials.ranges-slider')

View file

@ -39,7 +39,7 @@
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
{{--<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>PAGES</span>
</h6>
<ul class="nav flex-column mb-2">
@ -59,9 +59,31 @@
<a class="nav-link" href="{{route('bo.products.index')}}">Produits</a>
<a class="nav-link" href="{{route('bo.ranges.index')}}">Gammes de produits</a>
</li>
</ul>
<div class="accordion" id="accordionExample">
{{-- <div class="card">
</ul>--}}
<div class="accordion" id="accordionMenu">
<div class="card">
<div class="card-header" id="headingPages">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapsePages" aria-expanded="true" aria-controls="collapseOne">
Pages
</button>
</h2>
</div>
<div id="collapsePages" class="collapse {{ ( request()->routeIs('bo.content') || request()->routeIs('bo.faq') ) ? 'show' : '' }}" aria-labelledby="headingPages" tata-parent="#accordionMenu">
<div class="card-body">
<ul class="nav flex-column mb-2">
<li class="nav-item"><a class="nav-link" href="{{route('bo.content', 'accueil')}}">Accueil</a></li>
<li class="nav-item"><a class="nav-link" href="{{route('bo.content', 'contact')}}">Contact</a></li>
<li class="nav-item"><a class="nav-link" href="{{route('bo.content', 'legals')}}">Mentions Légales</a></li>
<li class="nav-item"><a class="nav-link" href="{{route('bo.content', 'cgv')}}">CGV</a></li>
<li class="nav-item"><a class="nav-link" href="{{route('bo.content', 'confidentialite')}}">Politique de confidentialité</a></li>
<li class="nav-item"><a class="nav-link" href="{{route('bo.faq')}}">FAQ</a></li>
</ul>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingProduits">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseProduits" aria-expanded="true" aria-controls="collapseOne">
@ -70,17 +92,17 @@
</h2>
</div>
<div id="collapseProduits" class="collapse" aria-labelledby="headingProduits" tata-parent="#accordionExample">
<div id="collapseProduits" class="collapse {{ ( request()->routeIs('bo.products*') || request()->routeIs('bo.ranges*') ) ? 'show' : '' }}" aria-labelledby="headingProduits" tata-parent="#accordionMenu">
<div class="card-body">
<ul class="nav flex-column mb-2">
<li class="nav-item">
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link" href="{{route('bo.products.index')}}">Produits</a>
<a class="nav-link" href="{{route('bo.ranges.index')}}">Gammes de produits</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</div> --}}
</div>
<div class="card">
<div class="card-header" id="headingMerchandising">
<h2 class="mb-0">
@ -90,7 +112,7 @@
</h2>
</div>
<div id="collapseMerchandising" class="collapse {{ ( request()->routeIs('bo.promotions*') || request()->routeIs('bo.products.starred') ) ? 'show' : '' }}" aria-labelledby="headingMerchandising" tata-parent="#accordionExample">
<div id="collapseMerchandising" class="collapse {{ ( request()->routeIs('bo.promotions*') || request()->routeIs('bo.products.starred') ) ? 'show' : '' }}" aria-labelledby="headingMerchandising" tata-parent="#accordionMenu">
<div class="card-body">
<ul class="nav flex-column mb-2">
<li class="nav-item">