Ajout de la FAQ (thème et question) dans le BO

This commit is contained in:
Romain Bonhomme 2020-10-26 13:47:52 +01:00
parent bdae3a73e8
commit ea8b431b5b
14 changed files with 1682 additions and 0 deletions

View file

@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers\bo;
use App\Http\Controllers\Controller;
use App\Models\FaqQuestions;
use App\Models\FaqThemes;
use Illuminate\Http\Request;
class FaqQuestionsController extends Controller
{
public function index() {
$questions = FaqQuestions::all();
for ($i = 0; $i < count($questions); $i++) {
$questions[$i]['toggle'] = false;
}
$themes = FaqThemes::all();
return view('bo.faq-questions.index', compact('questions', 'themes'));
}
public function destroy(Request $request)
{
if ($request->id !== null) {
$question = FaqQuestions::findOrFail($request->id);
$question->delete();
}
}
public function saveDatas(Request $request) {
$questions = $request->questions;
for ($i = 0; $i < count($questions); $i++) {
if (isset($questions[$i]["id"])) {
$q = FaqQuestions::findOrFail($questions[$i]["id"]);
$q->question = $questions[$i]["question"];
$q->answer = $questions[$i]["answer"];
$q->positive_vote = $questions[$i]["positive_vote"];
$q->negative_vote = $questions[$i]["negative_vote"];
$q->theme_id = $questions[$i]["theme_id"];
$q->save();
} else {
$t = FaqQuestions::updateOrCreate(
[
'question' => $questions[$i]["question"],
'answer' => $questions[$i]["answer"],
'positive_vote' => $questions[$i]["positive_vote"],
'negative_vote' => $questions[$i]["negative_vote"],
'theme_id' => $questions[$i]["theme_id"]
]
);
$questions[$i]["id"] = $t->id;
}
}
return $questions;
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\bo;
use App\Http\Controllers\Controller;
use App\Models\FaqQuestions;
use App\Models\FaqThemes;
use Illuminate\Http\Request;
class FaqThemesController extends Controller
{
public function index() {
$themes = FaqThemes::all();
return view('bo.faq-themes.index', compact('themes'));
}
public function destroy(Request $request)
{
$deleteOk = 0;
if (FaqQuestions::where('theme_id', $request->id)->count() > 0) {
$deleteOk = 1;
} else if ($request->id !== null) {
$theme = FaqThemes::findOrFail($request->id);
$theme->delete();
} else {
$deleteOk = 2;
}
return $deleteOk;
}
public function saveDatas(Request $request) {
$themes = $request->themes;
for ($i = 0; $i < count($themes); $i++) {
if (isset($themes[$i]["id"])) {
$t = FaqThemes::findOrFail($themes[$i]["id"]);
$t->name = $themes[$i]["name"];
$t->save();
} else {
$t = FaqThemes::updateOrCreate(['name' => $themes[$i]["name"]]);
$themes[$i]["id"] = $t->id;
}
}
return $themes;
}
}

View file

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class FaqQuestions extends Model
{
protected $table = "faq_questions";
protected $fillable = ["question", "answer", "positive_vote", "negative_vote", "theme_id"];
public function theme() {
return $this->belongsTo('App\Models\FaqThemes', 'theme_id');
}
}

16
app/Models/FaqThemes.php Normal file
View file

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class FaqThemes extends Model
{
protected $table = "faq_themes";
protected $fillable = ["name"];
protected $with = ['questions'];
public function questions() {
return $this->hasMany('App\Models\FaqQuestions', 'theme_id');
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFaqThemes extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('faq_themes', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('faq_themes');
}
}

View file

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFaqQuestions extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('faq_questions', function (Blueprint $table) {
$table->id();
$table->string('question');
$table->string('answer');
$table->unsignedInteger('positive_vote');
$table->unsignedInteger('negative_vote');
$table->foreignId('theme_id')->constrained('faq_themes');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('faq_questions');
}
}

1147
public/js/app.js vendored

File diff suppressed because it is too large Load diff

2
resources/js/app.js vendored
View file

@ -29,6 +29,8 @@ Vue.component('prizes', require('./components/Prizes.vue').default);
Vue.component('instant-win', require('./components/InstantWin.vue').default);
Vue.component('codes-ean', require('./components/CodesEAN.vue').default);
Vue.component('banned-ip', require('./components/BannedIP.vue').default);
Vue.component('faq-themes', require('./components/FaqThemes.vue').default);
Vue.component('faq-questions', require('./components/FaqQuestions.vue').default);
/**
* Next, we will create a fresh Vue application instance and attach it to

View file

@ -0,0 +1,157 @@
<template>
<div class="card">
<div class="card-header">
<div class="row">
<div class="col-md-3">
FAQ - Gestion des questions
</div>
<div class="col-md-6 text-center">
<transition name="fade">
<span v-show="processing">
{{ response }}
</span>
</transition>
</div>
<div class="col-md-3 text-right">
<button class="btn btn-primary btn-sm text-white float-right" @click="saveData()" :disabled="processing">
Sauvegarder
<div class="spinner-border spinner-border-sm text-light" role="status" v-show="processing">
<span class="sr-only">Loading...</span>
</div>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="col-md-10 offset-md-1">
<div v-for="(item, index) in datas" :key="item.id">
<div class="form-inline">
<label class="col-form-label col-md-3">Question :</label>
<input type="text" v-model="item.question" class="form-control form-control-sm col-md-7" placeholder="Entrer votre question ici" required>
<a class="text-decoration-none col-md-2 col-form-label w-100 text-right" href="#_" @click="item.toggle = !item.toggle">
Détails
<i class="fas" :class="{ 'fa-chevron-down': !item.toggle, 'fa-chevron-up': item.toggle}"></i>
</a>
</div>
<transition name="slide-fade">
<div class="form-inline bg-light rounded" v-show="item.toggle">
<label class="col-form-label col-md-3">Réponse :</label>
<input type="text" v-model="item.answer" class="form-control form-control-sm col-md-7" placeholder="Entrer votre réponse ici" required><br/>
<div class="col-md-2 text-right">
<button class="btn btn-danger btn-sm" @click="removeItem(index)">Supprimer</button>
</div>
<div class="form-group col-md-4">
<label class="col-form-label">Votes positifs</label>
<input type="text" v-model="item.positive_vote" class="form-control form-control-sm" required>
</div>
<div class="form-group col-md-4">
<label class="col-form-label">Votes négatifs</label>
<input type="text" v-model="item.negative_vote" class="form-control form-control-sm" required>
</div>
<div class="form-group col-md-4">
<label class="col-form-label">Thème</label>
<select v-model="item.theme_id" class="custom-select custom-select-sm">
<option v-for="theme in themes" :key="theme.id" :value="theme.id" :selected="theme.id == item.theme_id">{{theme.name}}</option>
</select>
</div>
</div>
</transition>
<hr/>
</div>
<div class="form-group text-right">
<button class="btn btn-primary btn-sm text-white" @click="newItem">Ajouter une question</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('FaqQuestions Component mounted.');
this.datas = JSON.parse(this.dbdatas);
this.themes = JSON.parse(this.dbthemes);
},
props: [
'dbdatas',
'dbthemes',
],
data() {
return {
datas: [],
themes: [],
response: null,
processing: false
}
},
watch: {
},
methods: {
newItem() {
this.datas.push({id: null, question: '', answer: '', positive_vote: 0, negative_vote: 0, theme_id: null, toggle: false});
},
removeItem(index) {
if (this.datas[index]['id'] !== null) {
this.processing = true;
this.response = "Suppression en cours...";
axios.post('/bo/ajax/delete-faq-question', {
id: this.datas[index]['id'],
}).then(
(response) => {
this.response = "Suppression terminée !";
this.processing = false;
}
)
.catch(function (error) {
console.log(error);
this.response = "Erreur lors de la suppression.";
this.processing = false;
});
this.datas.splice(index, 1);
} else {
this.datas.splice(index, 1);
}
},
saveData() {
this.processing = true;
this.response = "En cours de sauvegarde..."
axios.post('/bo/ajax/save-faq-question', {
questions: this.datas,
}).then(
(response) => {
this.datas = response.data;
this.response = "Sauvegarde terminée !";
this.processing = false;
}
)
.catch(function (error) {
console.log(error);
this.response = "Erreur lors de la sauvegarde.";
this.processing = false;
});
}
}
}
</script>
<style scoped>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .1s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}
</style>

View file

@ -0,0 +1,146 @@
<template>
<div class="card">
<div class="card-header">
<div class="row">
<div class="col-md-3">
FAQ - Gestion des thèmes
</div>
<div class="col-md-6 text-center">
<transition name="fade">
<span v-show="processing">
{{ response }}
</span>
</transition>
</div>
<div class="col-md-3 text-right">
<button class="btn btn-primary btn-sm text-white float-right" @click="saveData()" :disabled="processing">
Sauvegarder
<div class="spinner-border spinner-border-sm text-light" role="status" v-show="processing">
<span class="sr-only">Loading...</span>
</div>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="col-md-10 offset-md-1">
<table class="table table-bordered text-center">
<thead>
<tr>
<th class="w-75" scope="col">Nom du thème <span class="required-asterisk">*</span></th>
<th class="w-50" scope="col"></th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in datas" v-bind:key="item.id">
<td><input class="form-control form-control-sm" v-model="item.name" type="text" required></td>
<td>
<button @click="removeItem(index)" :disabled="processing" class="btn btn-secondary btn-sm">
Supprimer
<div class="spinner-border spinner-border-sm text-light" role="status" v-show="processing">
<span class="sr-only">Loading...</span>
</div>
</button>
</td>
</tr>
</tbody>
</table>
<div class="form-group text-right">
<button class="btn btn-primary btn-sm text-white" @click="newItem">Ajouter une ligne</button>
<input type="hidden" :name="name" :value="JSON.stringify(datas)" required>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('FaqThemes Component mounted.');
this.datas = JSON.parse(this.dbdatas);
},
props: [
'name',
'dbdatas',
],
data() {
return {
datas: [],
response: null,
processing: false
}
},
watch: {
},
methods: {
newItem() {
this.datas.push({id: null, name: ''});
},
removeItem(index) {
if (this.datas[index]['id'] !== null) {
this.processing = true;
this.response = "Suppression en cours...";
axios.post('/bo/ajax/delete-faq-theme', {
id: this.datas[index]['id'],
}).then(
(response) => {
let result = response.data;
switch (result) {
case 0: case 2:
// 0 = Supprimé, 2 = Pas encore en base, supprimer juste dans la vue
this.response = "Suppression terminée !";
this.datas.splice(index, 1);
this.processing = false;
break;
case 1:
// 1 = Non supprimer car des questions lui sont lié
this.response = "Erreur : des questions sont encore liées à ce thème.";
setTimeout(function() {
this.processing = false;
}.bind(this), 1500);
break;
}
}
)
.catch(function (error) {
console.log(error);
this.response = "Erreur lors de la suppression.";
this.processing = false;
});
} else {
this.datas.splice(index, 1);
}
},
saveData() {
this.processing = true;
this.response = "En cours de sauvegarde..."
axios.post('/bo/ajax/save-faq-theme', {
themes: this.datas,
}).then(
(response) => {
this.datas = response.data;
this.response = "Sauvegarde terminée !";
this.processing = false;
}
)
.catch(function (error) {
console.log(error);
this.response = "Erreur lors de la sauvegarde.";
this.processing = false;
});
}
}
}
</script>
<style scoped>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>

View file

@ -0,0 +1,5 @@
@extends('layouts.bo')
@section('content')
<faq-questions dbdatas='@json($questions)' dbthemes='@json($themes)'/>
@endsection

View file

@ -0,0 +1,5 @@
@extends('layouts.bo')
@section('content')
<faq-themes dbdatas='@json($themes)'/>
@endsection

View file

@ -100,6 +100,13 @@
<a class="dropdown-item" href="{{route('bo.content', 3)}}">Règlement</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button">FAQ</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{{route('bo.faq-themes')}}">Thèmes</a>
<a class="dropdown-item" href="{{route('bo.faq-questions')}}">Questions</a>
</div>
</li>
</nav>
</div>
<div class="col-md-10">

View file

@ -46,4 +46,12 @@ Route::namespace('Bo')
Route::get('/banned-ip', 'BannedIPController@index')->name('banned-ip');
Route::post('/ajax/delete-banned-ip', 'BannedIPController@destroy');
Route::post('/ajax/save-banned-ip', 'BannedIPController@saveDatas');
// FAQ - Thèmes
Route::get('/faq-themes', 'FaqThemesController@index')->name('faq-themes');
Route::post('/ajax/delete-faq-theme', 'FaqThemesController@destroy');
Route::post('/ajax/save-faq-theme', 'FaqThemesController@saveDatas');
// FAQ - Questions
Route::get('/faq-questions', 'FaqQuestionsController@index')->name('faq-questions');
Route::post('/ajax/delete-faq-question', 'FaqQuestionsController@destroy');
Route::post('/ajax/save-faq-question', 'FaqQuestionsController@saveDatas');
});