Merge branch 'ocb-search' into 'develop'

feat: ✨ add ocb breeders search

See merge request ldc-web/projets/le-gaulois!185
This commit is contained in:
noah.lancien 2024-12-05 10:57:12 +00:00
commit 629f397745
11 changed files with 406 additions and 3 deletions

View file

@ -6,6 +6,7 @@ use App\Enums\Settings\SettingsType;
use App\Http\Controllers\Controller;
use App\Models\Breeder;
use App\Models\GeneralSetting;
use App\Models\OcbBreeder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
@ -59,7 +60,7 @@ class BreederController extends Controller
/**
* Get all the breeders views in a given department.
*
* @param Request $request
* @param \Illuminate\Http\Request $request
* @return array<string>
*/
public function getDepartmentBreedersViews(Request $request): array
@ -86,4 +87,15 @@ class BreederController extends Controller
{
return GeneralSetting::query()->where('type', SettingsType::breederMap)->first()->value;
}
/**
* Get all the breeders views in a given department.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function searchOcbBreedersCards(Request $request): array
{
return OcbBreeder::getOcbBreederCards($request->page, $request->search);
}
}

View file

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\NewsBreeding;
use App\Models\OcbBreeder;
use App\Models\StaticPage;
class BreedingController extends Controller
@ -23,6 +24,19 @@ class BreedingController extends Controller
]);
}
/**
* Display the given breeding page.
*
* @return \Illuminate\Contracts\View\View
*/
public function OcbBreederPage(): \Illuminate\Contracts\View\View
{
$ocbBreederModels = OcbBreeder::getOcbBreederCards();
return view('fo.pages.breeding.oui-c-est-bon', [
'ocbBreederModels' => $ocbBreederModels,
]);
}
/**
* Display the given breeding article.
*

62
app/Models/OcbBreeder.php Normal file
View file

@ -0,0 +1,62 @@
<?php
namespace App\Models;
use App\Enums\Breeders\OCBType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
/**
* @property string $first_name OCB breeder first name.
* @property string $last_name OCB breeder last name.
* @property string $department OCB breeder department.
* @property string $postal_code OCB breeder postal code.
* @property \App\Enums\Breeders\OCBType $type OCB breeder type.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
*/
class OcbBreeder extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array<integer, string>
*/
protected $fillable = [
'first_name',
'last_name',
'department',
'postal_code',
'type',
];
/**
* The attributes that should be cast.
*
* @var array<string,string>
*/
protected $casts = [
'type' => OCBType::class
];
// * METHODS.
/**
* Created at nice date.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public static function getOcbBreederCards(int $page = 1, string|null $search = null): array
{
return OcbBreeder::query()
->when($search, function (Builder $query) use ($search) {
$query->where('first_name', 'like', '%' . $search . '%')
->orWhere('last_name', 'like', '%' . $search . '%');
})
->paginate(perPage: 9, page: $page)
->through(function (OcbBreeder $ocbBreederModel) {
return view('components.fo.breeding.card-ocb-breeder', ['ocbBreederModel' => $ocbBreederModel])->render();
})->toArray();
}
}

View file

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::create('ocb_breeders', function (Blueprint $table) {
$table->id()->comment('OCB breeder id.');
$table->string('first_name')->comment('OCB breeder first name.');
$table->string('last_name')->comment('OCB breeder last name.');
$table->string('department')->comment('OCB breeder department.');
$table->string('postal_code')->comment('OCB breeder postal code.');
$table->tinyInteger('type')->comment('OCB breeder type.');
$table->timestamp('created_at')->nullable()->comment('OCB breeder created at date.');
$table->timestamp('updated_at')->nullable()->comment('OCB breeder updated at date.');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::dropIfExists('ocb_breeders');
}
};

View file

@ -0,0 +1,241 @@
<template>
<div class="search-breeder-ocb-component">
<div class="row justify-content-center">
<!-- SEARCH INPUT -->
<div class="col-12 col-md-6 my-5">
<div class="input-group">
<input
ref="inputSearch"
type="text"
class="form-control"
name="search-breeder-ocb"
:placeholder="placeholder"
>
<button
v-if="inputSearch?.value.length"
class="btn btn-tertiary"
:title="trans.methods.__('Effacer la recherche')"
@click="resetSearch()"
type="button"
>
<FontAwesomeIcon
icon="fa-solid fa-xmark"
/>
</button>
<button
class="btn btn-secondary"
:title="trans.methods.__('Lancer la recherche')"
@click="searchBreederOCB()"
type="button"
>
{{ trans.methods.__('rechercher') }}
</button>
</div>
</div>
<!-- SEARCH RESULTS -->
<div class="col-12">
<!-- RESULT CONTENT -->
<div
v-if="cards.length"
class="row justify-content-center"
>
<template v-if="!loading">
<template
v-for="(card, cardIndex) in cards"
:key="cardIndex"
>
<div
class="col-12 col-sm-6 col-lg-4 mb-3"
v-html="card"
/>
</template>
</template>
<template v-if="loading || paginationParameters.loading">
<div class="col-12 col-sm-6 col-lg-4 mb-3">
<div class="placeholder-glow bg-light position-relative line-border-small bg-white d-flex flex-column justify-content-start p-3">
<span class="placeholder col-8" />
<span class="placeholder col-5 mt-2" />
</div>
</div>
</template>
</div>
<!-- MESSAGES & PAGINATION -->
<template v-if="!loading">
<div
v-if="paginationParameters.page < paginationParameters.lastPage"
class="col-12 text-center mb-5"
>
<button
id="btn-arrow"
class="btn btn-secondary"
type="button"
@click="searchBreederOCB(true)"
>
{{ trans.methods.__("charger les résultats suivants") }}
</button>
</div>
<div
v-else-if="!cards.length"
class="col-12 text-center mb-5"
>
<FontAwesomeIcon
icon="fa-solid fa-triangle-exclamation"
class="no-result-icon"
/>
<p class="text-body-primary text-center fst-italic my-2">
{{ trans.methods.__('Aucun élément n\'est disponible') }}
</p>
<button
class="btn btn-sm btn-secondary"
type="button"
:title="trans.methods.__('Effacer la recherche')"
@click="resetSearch()"
>
{{ trans.methods.__('Effacer la recherche') }}
</button>
</div>
<div
v-else
class="col-12 mb-5"
>
<p class="text-body-primary text-center fst-italic m-0">
{{ trans.methods.__('Tous les éléments sont chargés') }}
</p>
</div>
</template>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineOptions, onMounted, useAttrs, ref, computed, reactive } from "vue";
import parseLaravelErrorMessages from "./../../modules/parseLaravelErrorMessages";
import trans from "../../modules/trans";
defineOptions({
name: "SearchBreederOcbComponent"
});
// * ATTRIBUTES
const attrs = useAttrs();
// * REFS
const inputSearch = ref<HTMLInputElement|null>(null);
// * DATA
const routeUrl = ref<string>("");
const cards = ref<Array<string>>([]);
const placeholder = ref<string>("");
const loading = ref<boolean>(false);
/** Pagination parameters. */
const paginationParameters = reactive<{
page: number,
itemPerPage: number,
lastPage: number,
loading: boolean,
}>({
page: 1,
itemPerPage: 1,
lastPage: 1,
loading: false,
});
// * COMPUTED
const isUsedWithProps = computed<boolean>(() => attrs.json === undefined);
// * LIFECYCLE
onMounted(() => {
const json = String(attrs.json ?? "{}"),
data = JSON.parse(json);
if (!isUsedWithProps.value) {
routeUrl.value = data.routeUrl;
cards.value = data.cards.data;
placeholder.value = data.placeholder;
/** Pagination parameters. */
paginationParameters.page = data.cards.current_page;
paginationParameters.itemPerPage = data.cards.per_page;
paginationParameters.lastPage = data.cards.last_page;
}
});
// * METHODS
/**
* Reset all filters activated.
* @return void
*/
function resetSearch(): void {
inputSearch.value!.value = "";
searchBreederOCB();
}
/**
* Get models by text search.
* @return void
*/
function searchBreederOCB(concat: boolean = false): void {
if (!inputSearch.value) {
throw new Error("Input doesn't exist");
}
if (!routeUrl.value) {
throw new Error(`Route ${routeUrl.value} doesn't exist`);
}
paginationParameters.page = (concat) ? paginationParameters.page + 1 : 1;
(concat) ? paginationParameters.loading = true : loading.value = true;
window.axios
.post(routeUrl.value, {"search": inputSearch.value.value, "page": paginationParameters.page})
.then((response) => {
paginationParameters.page = response.data.current_page;
paginationParameters.lastPage = response.data.last_page;
cards.value = (concat) ? cards.value.concat(response.data.data) : response.data.data;
}).then(() => {
(concat) ? paginationParameters.loading = false : loading.value = false;
})
.catch(ajaxErrorHandler);
}
/**
* Return error message after the ajax call.
* @return void
*/
function ajaxErrorHandler(e: any): void {
let message = trans.methods.__("Une erreur est survenue");
if (e.response.status === 422) {
message = parseLaravelErrorMessages.methods.parseValidationErrors(
e.response?.data?.errors ?? {}
) || message;
if (window.vueDebug) {
console.warn(e.response.data.errors);
}
} else if (e.response.status === 419) {
console.log(trans.methods.__("Votre session a expiré, la page va être rechargée"));
setTimeout(() => window.location.reload(), 2000);
} else if (e.response.status === 403) {
console.log(trans.methods.__("Vous n'êtes pas autorisé a effectuer cette action"));
setTimeout(() => window.location.reload(), 2000);
} else {
if (window.vueDebug) {
console.error(e);
}
}
console.log(message);
}
</script>
<style lang="scss" scopped>
.placeholder-glow {
.placeholder:first-of-type {
height: 2rem;
}
.placeholder:last-of-type {
height: 1.5rem;
}
}
.no-result-icon {
width: 4rem;
height: 4rem;
}
</style>

View file

@ -1,6 +1,8 @@
import { config, dom, library } from "@fortawesome/fontawesome-svg-core";
import { faUser } from "@fortawesome/free-regular-svg-icons";
import {
faArrowLeft,
faArrowRight,
faCircleCheck,
faCircleExclamation,
faCircleInfo,
@ -34,7 +36,9 @@ library.add(
faCircleQuestion,
faCircleXmark,
faHeart,
farHeart
farHeart,
faArrowLeft,
faArrowRight,
);
dom.watch();

View file

@ -0,0 +1,4 @@
<div class="bg-light position-relative line-border-small bg-white d-flex flex-column justify-content-start p-3">
<p class="h3">{{ sprintf('%s %s', $ocbBreederModel->first_name, $ocbBreederModel->last_name) }}</p>
<small class="f-bakerie-rough text-success fw-bold h4">{{ sprintf('Éleveur du/de %s - %s', $ocbBreederModel->department, $ocbBreederModel->postal_code) }}</small>
</div>

View file

@ -0,0 +1,23 @@
<div class="container position-relative pb-5">
<div class="row justify-content-center">
<div class="col-12">
<h2 class="h1 f-intro-head-h-base section-title pb-2 text-center">
{{ __('Retrouvez tous nos éleveurs Oui c\'est Bon !') }}
</h2>
</div>
<div class="col-12 col-md-6 text-center">
<p class="mb-0">{{ __('Parce que nous sommes fiers de notre savoir-faire français et de l\'origine France de nos produits,
nous vous dévoilons les éleveurs partenaires et engagés au quotidien dans notre démarche Oui c\'est Bon Le Gaulois') }}</p>
</div>
<div class="col-12">
@php
$data = [
'cards' => $ocbBreederModels,
'routeUrl' => route('fo.breeders.search-ocb'),
'placeholder' => __('Nom de l\'éleveur'),
];
@endphp
<div class="search-breeder-ocb" data-json='@json($data)'></div>
</div>
</div>
</div>

View file

@ -12,6 +12,7 @@
<div class="bg-green-wood h-100 w-100" id="demarche-bg"></div>
</div>
<x-fo.breeding.section.oui-c-est-bon.cahier-des-charges-section />
<x-fo.breeding.section.oui-c-est-bon.search-breeders-section :ocbBreederModels="$ocbBreederModels" />
<x-fo.breeding.section.oui-c-est-bon.our-breeder-section />
</section>
{{-- <section class="pb-3 pt-5" id="breeder-map-section">

View file

@ -56,6 +56,7 @@
'{{ route('fo.breeders.get-department-breeders-views', ['department' => 'DEPARTMENT']) }}';
window.__SYSTEM._routes['fo.breeders.get-manageable-text-component'] =
'{{ route('fo.breeders.get-manageable-text-component') }}';
window.__SYSTEM._routes['fo.breeders.search-ocb'] = '{{ route('fo.breeders.search-ocb') }}';
window.__SYSTEM._routes['fo.news.filtered'] = '{{ route('fo.news.filtered', ['legend_slug' => 'LEGENDSLUG']) }}';
// CLUB
window.__SYSTEM._routes['club.departaimants.save-state'] = '{{ route('club.departaimants.save-state', ['collectiontype' => 'TYPE']) }}';

View file

@ -126,7 +126,7 @@ Route::middleware(['throttle:web'])->name('fo.')->group(function () {
->name('breeding.')
->group(function () {
Route::view('/', 'fo.pages.breeding.index')->name('index');
Route::view('/oui-c-est-bon', 'fo.pages.breeding.oui-c-est-bon')->name('oui-c-est-bon');
Route::get('/oui-c-est-bon', [BreedingController::class, 'OCBbreederPage'])->name('oui-c-est-bon');
Route::view('/sortant-a-l-exterieur', 'fo.pages.breeding.sortant-a-l-exterieur')
->name('sortant-a-l-exterieur');
Route::get('/{breeding_static_page:slug}', [BreedingController::class, 'breedingPage'])
@ -164,5 +164,9 @@ Route::middleware(['throttle:web'])->name('fo.')->group(function () {
->name('get-department-breeders-views');
Route::get('/get-manageable-text-component', [BreederController::class, 'getManageableTextComponent'])
->name('get-manageable-text-component');
// * OCB BREEDERS.
Route::post('/recherche-ocb-eleveurs', [BreederController::class, 'searchOcbBreedersCards'])
->name('search-ocb');
});
});