feat : add to install

This commit is contained in:
System User 2026-02-19 16:43:37 +01:00
parent 35d96fa64f
commit 0c78f5a0fb
10 changed files with 361 additions and 212 deletions

View file

@ -24,4 +24,5 @@ enum InputType: int
case videoVimeo = 14;
case videoYoutube = 15;
case video = 16;
}

View file

@ -0,0 +1,187 @@
<?php
namespace App\Lib\Helpers;
use Illuminate\Support\Str;
class StaticPageSectionHelper
{
/** Get value string from string|array MiniCrud|object MiniCrud
* @param mixed $x key
* @return string
*/
private static function value(mixed $x): string
{
if (is_string($x)) return trim($x);
if (is_array($x)) {
$v = $x['value'] ?? '';
return is_string($v) ? trim($v) : '';
}
if (is_object($x)) {
$v = $x->value ?? '';
return is_string($v) ? trim($v) : '';
}
return '';
}
/** Get alt/title from array MiniCrud image
* @param array $x picto array
* @return string
*/
private static function imgAlt(array $x): string
{
if (is_array($x)) {
$v = $x['alt'] ?? '';
return is_string($v) ? trim($v) : '';
}
return '';
}
/** Get alt/title from array MiniCrud image
* @param array $x picto array
* @return string
*/
private static function imgTitle(array $x): string
{
if (is_array($x)) {
$v = $x['title'] ?? '';
return is_string($v) ? trim($v) : '';
}
return '';
}
/** Extrait des points depuis HTML: priorise <li>, sinon split par <br>/newlines
* @param string $html content
* @return array
*/
private static function extractContent(string $html): array
{
$html = trim($html);
$title = null;
$points = [];
$asterisc = null;
// ASTERISC
if (preg_match('/<p>\s*\*(.*?)<\/p>/is', $html, $match)) {
$asterisc = strip_tags($match[1]);
$html = str_replace($match[0], '', $html);
}
// titre
if (preg_match('/<(h[1-3])[^>]*>(.*?)<\/\1>/is', $html, $match)) {
$title = trim(strip_tags($match[2], '<strong><br>'));
$html = str_replace($match[0], '', $html);
}
// List
if (stripos($html, '<li') !== false) {
preg_match_all('/<li[^>]*>(.*?)<\/li>/is', $html, $matches);
$points = collect($matches[1] ?? [])
->map(fn ($x) => trim(strip_tags($x, '<strong><br>')))
->filter()
->values()
->all();
} else {
$text = preg_replace('/<br\s*\/?>/i', "\n", $html);
$text = strip_tags($text, '<strong>');
$lines = preg_split("/\r\n|\n|\r/", $text);
$points = collect($lines)
->map(fn ($x) => trim($x))
->filter()
->values()
->all();
}
return [
'title' => $title,
'points' => $points,
'asterisc' => $asterisc,
];
}
/**
* Build Items "cahier de charge" cards
*@param array $data infos from data field
*/
public static function buildItems(array $data): array
{
// collect des sections par index (1..N)
$indexes = collect(array_keys($data))
->filter(fn ($k) => is_string($k) && str_starts_with($k, 'category_section_'))
->map(fn ($k) => (int) str_replace('category_section_', '', $k))
->filter(fn ($i) => $i > 0)
->unique()
->sort()
->values();
// transforme to "rows"
$rows = $indexes->map(function (int $i) use ($data) {
$categoryKey = self::value($data["category_section_$i"] ?? '');
$titleArr = $data["title_section_$i"] ?? null;
$descArr = $data["description_section_$i"] ?? null;
$pictoArr = $data["picto_section_$i"] ?? null;
$title = self::value($titleArr);
$desc = self::value($descArr);
$picto = self::value($pictoArr);
return [
'index' => $i,
'categoryKey' => $categoryKey,
'categoryLabel' => self::value($data[$categoryKey] ?? $categoryKey),
'sectionTitle' => $title,
'descriptionHtml' => $desc,
'iconPath' => $picto,
'iconAlt' => self::imgAlt($pictoArr) ?: $title,
'iconDesc' => self::imgTitle($pictoArr) ?: $title,
];
})
->filter(fn ($r) => $r['categoryKey'] !== '' && $r['sectionTitle'] !== '')
->values();
$groupedByTab = $rows->groupBy('categoryKey');
// build final $items
$items = [];
foreach ($groupedByTab as $tabKey => $sections) {
$tabLabel = self::value($data[$tabKey] ?? $tabKey);
$slugKey = Str::slug($tabLabel, '_');
if ($slugKey === '') $slugKey = $tabKey;
$items[$slugKey] = $sections
->sortBy('index')
->map(function ($s) {
$content = self::extractContent($s['descriptionHtml']);
return [
'category' => $s['sectionTitle'],
'icon' => [
'path' => $s['iconPath'],
'alt' => $s['iconAlt'],
'description' => $s['iconDesc'],
],
'content' => [
array_filter([
'title' => $content['title'],
'points' => $content['points'],
'asterisc' => $content['asterisc']
])
],
'class' => 'card-template-sm',
];
})
->values()
->all();
}
return $items;
}
}

View file

@ -171,9 +171,6 @@ class ToolboxHelper
*/
public static function assertNoInternalBoUrls(string $field, string $text): string
{
// dump($field);
if($text === null || $text === " ") {
}
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(

View file

@ -295,4 +295,32 @@ class StaticPage extends Model implements SchemaOrgInterface, RedirectableInterf
{
return $this->hasMany(NewsBreeding::class);
}
/**
* Get categories
*/
public function getCategorySectionAttribute(): array
{
$data = $this->data ?? [];
// Convertit objet => array
if (is_object($data)) {
$data = json_decode(json_encode($data), true);
}
if (!is_array($data)) {
return [];
}
return collect($data)
->filter(fn ($v, $k) => is_string($k) && str_starts_with($k, 'tab_title_'))
->map(function ($v) {
// $v peut être array ou objet
$value = is_array($v) ? ($v['value'] ?? null) : ($v->value ?? null);
return is_string($value) ? trim($value) : null;
})
->filter(fn ($v) => is_string($v) && $v !== '')
->values()
->all();
}
}

View file

@ -42,56 +42,56 @@ return new class extends Migration
"required"=> true
],
"tab_title_1" => [
"label" => "Titre onglet",
"label" => "Titre onglet 1",
"inputType" => 11,
"value" => "",
"validate" => "required|string|between:3,50",
"required" => true
],
"tab_title_2" => [
"label" => "Titre onglet",
"label" => "Titre onglet 2",
"inputType" => 11,
"value" => "",
"validate" => "nullable|string|between:3,50",
"required" => false
],
"tab_title_3" => [
"label" => "Titre onglet",
"label" => "Titre onglet 3",
"inputType" => 11,
"value" => "",
"validate" => "nullable|string|between:3,50",
"required" => false
],
"tab_title_4" => [
"label" => "Titre onglet",
"label" => "Titre onglet 4",
"inputType" => 11,
"value" => "",
"validate" => "nullable|string|between:3,50",
"required" => false
],
"tab_title_5" => [
"label" => "Titre onglet",
"label" => "Titre onglet 5",
"inputType" => 11,
"value" => "",
"validate" => "nullable|string|between:3,50",
"required" => false
],
"title_section_1" => [
"label" => "Titre rubrique",
"label" => "Titre rubrique 1",
"inputType" => 11,
"value" => "",
"validate" => "required|string|between:3,255",
"required" => true
],
"description_section_1"=> [
"label"=> "Description rubrique",
"label"=> "Description rubrique 1",
"inputType"=> 12,
"value"=> "",
"validate"=> "string|min:15",
"validate"=> "required|string|min:15",
"required"=> true
],
"picto_section_1"=> [
"label"=> "Image rubrique",
"label"=> "Image rubrique 1",
"inputType"=> 4,
"dimensions"=> [
"width"=> 90,
@ -104,21 +104,21 @@ return new class extends Migration
"required"=> true
],
"title_section_2" => [
"label" => "Titre rubrique",
"label" => "Titre rubrique 2",
"inputType" => 11,
"value" => "",
"validate" => "required|string|between:3,255",
"required" => true
],
"description_section_2"=> [
"label"=> "Description rubrique",
"label"=> "Description rubrique 2",
"inputType"=> 12,
"value"=> "",
"validate"=> "string|min:15",
"validate"=> "required|string|min:15",
"required"=> true
],
"picto_section_2"=> [
"label"=> "Image rubrique",
"label"=> "Image rubrique 2",
"inputType"=> 4,
"dimensions"=> [
"width"=> 80,
@ -131,21 +131,21 @@ return new class extends Migration
"required"=> true
],
"title_section_3" => [
"label" => "Titre rubrique",
"label" => "Titre rubrique 3",
"inputType" => 11,
"value" => "",
"validate" => "nullable|string|between:3,255",
"required" => false
],
"description_section_3"=> [
"label"=> "Description rubrique",
"label"=> "Description rubrique 3",
"inputType"=> 12,
"value"=> "",
"validate"=> "nullable|string|min:15",
"required"=> false
],
"picto_section_3"=> [
"label"=> "Image rubrique",
"label"=> "Image rubrique 3",
"inputType"=> 4,
"dimensions"=> [
"width"=> 80,
@ -158,21 +158,21 @@ return new class extends Migration
"required"=> false
],
"title_section_4" => [
"label" => "Titre rubrique",
"label" => "Titre rubrique 4",
"inputType" => 11,
"value" => "",
"validate" => "nullable|string|between:3,255",
"required" => false
],
"description_section_4"=> [
"label"=> "Description rubrique",
"label"=> "Description rubrique 4",
"inputType"=> 12,
"value"=> "",
"validate"=> "nullable|string|min:15",
"required"=> false
],
"picto_section_4"=> [
"label"=> "Image rubrique",
"label"=> "Image rubrique 4",
"inputType"=> 4,
"dimensions"=> [
"width"=> 80,
@ -185,21 +185,21 @@ return new class extends Migration
"required"=> false
],
"title_section_5" => [
"label" => "Titre rubrique",
"label" => "Titre rubrique 5",
"inputType" => 11,
"value" => "",
"validate" => "nullable|string|between:3,255",
"required" => false
],
"description_section_5"=> [
"label"=> "Description rubrique",
"label"=> "Description rubrique 5",
"inputType"=> 12,
"value"=> "",
"validate"=> "nullable|string|min:15",
"required"=> false
],
"picto_section_5"=> [
"label"=> "Image rubrique",
"label"=> "Image rubrique 5",
"inputType"=> 4,
"dimensions"=> [
"width"=> 80,
@ -212,21 +212,21 @@ return new class extends Migration
"required"=> false
],
"title_section_6" => [
"label" => "Titre rubrique",
"label" => "Titre rubrique 6",
"inputType" => 11,
"value" => "",
"validate" => "nullable|string|between:3,255",
"required" => false
],
"description_section_6"=> [
"label"=> "Description rubrique",
"label"=> "Description rubrique 6",
"inputType"=> 12,
"value"=> "",
"validate"=> "nullable|min:15",
"required"=> false
],
"picto_section_6"=> [
"label"=> "Image rubrique",
"label"=> "Image rubrique 6",
"inputType"=> 4,
"dimensions"=> [
"width"=> 80,

View file

@ -131,9 +131,7 @@
padding: 0.5rem 0.75rem;
}
#chicken-button-outside,
#chicken-button,
#turkey-button {
.tab-btn {
--bs-btn-padding-y: .25rem;
--bs-btn-padding-x: .5rem;
--bs-btn-font-size: .75rem;

View file

@ -1,48 +1,84 @@
document.addEventListener("DOMContentLoaded", function () {
if (!document.querySelector("#oui-c-est-bon")) {
return;
// document.addEventListener("DOMContentLoaded", function () {
// if (!document.querySelector("#oui-c-est-bon")) {
// return;
// }
// const chickenButton = document.getElementById("chicken-button");
// const turkeyButton = document.getElementById("turkey-button");
// function toggleCards(selectedType: "chicken" | "turkey") {
// const chickenCards = document.querySelectorAll(".chicken-card");
// const turkeyCards = document.querySelectorAll(".turkey-card");
// // Here I toggle visibility of cards
// chickenCards.forEach(card => card.classList.toggle("d-none", selectedType !== "chicken"));
// turkeyCards.forEach(card => card.classList.toggle("d-none", selectedType !== "turkey"));
// }
// function handleButtonClick(event: MouseEvent) {
// const target = event.target as HTMLElement;
// const selectedType = target.id === "chicken-button" ? "chicken" : "turkey";
// const isChicken = selectedType === "chicken";
// if (chickenButton) {
// chickenButton.classList.toggle("btn-secondary", isChicken);
// chickenButton.classList.toggle("btn-light", !isChicken);
// }
// // Here we toggle buttons classes
// if (turkeyButton) {
// turkeyButton.classList.toggle("btn-secondary", !isChicken);
// turkeyButton.classList.toggle("btn-light", isChicken);
// }
// // Show/Hide cards
// toggleCards(selectedType);
// }
// if (chickenButton) {
// chickenButton.addEventListener("click", handleButtonClick);
// }
// if (turkeyButton) {
// turkeyButton.addEventListener("click", handleButtonClick);
// }
// // Set default card to chicken
// // things will change if data come from db
// toggleCards("chicken");
// });
document.addEventListener("DOMContentLoaded", () => {
const tabsRoot = document.getElementById("cards-tabs");
const container = document.getElementById("cards-container");
if (!tabsRoot || !container) return;
const buttons = tabsRoot.querySelectorAll<HTMLButtonElement>(".tab-btn[data-tab]");
const cards = container.querySelectorAll<HTMLElement>(".tab-card[data-tab]");
if (!buttons.length) return;
function setActive(tab: string) {
buttons.forEach((btn) => {
const active = btn.dataset.tab === tab;
btn.classList.toggle("btn-secondary", active);
btn.classList.toggle("btn-light", !active);
btn.classList.toggle("is-active", active);
});
cards.forEach((card) => {
card.classList.toggle("d-none", card.dataset.tab !== tab);
});
}
const chickenButton = document.getElementById("chicken-button");
const turkeyButton = document.getElementById("turkey-button");
buttons.forEach((btn) => {
btn.addEventListener("click", () => {
const tab = btn.dataset.tab;
if (!tab) return;
setActive(tab);
});
});
function toggleCards(selectedType: "chicken" | "turkey") {
const chickenCards = document.querySelectorAll(".chicken-card");
const turkeyCards = document.querySelectorAll(".turkey-card");
// Here I toggle visibility of cards
chickenCards.forEach(card => card.classList.toggle("d-none", selectedType !== "chicken"));
turkeyCards.forEach(card => card.classList.toggle("d-none", selectedType !== "turkey"));
}
function handleButtonClick(event: MouseEvent) {
const target = event.target as HTMLElement;
const selectedType = target.id === "chicken-button" ? "chicken" : "turkey";
const isChicken = selectedType === "chicken";
if (chickenButton) {
chickenButton.classList.toggle("btn-secondary", isChicken);
chickenButton.classList.toggle("btn-light", !isChicken);
}
// Here we toggle buttons classes
if (turkeyButton) {
turkeyButton.classList.toggle("btn-secondary", !isChicken);
turkeyButton.classList.toggle("btn-light", isChicken);
}
// Show/Hide cards
toggleCards(selectedType);
}
if (chickenButton) {
chickenButton.addEventListener("click", handleButtonClick);
}
if (turkeyButton) {
turkeyButton.addEventListener("click", handleButtonClick);
}
// Set default card to chicken
// things will change if data come from db
toggleCards("chicken");
});
const defaultTab = buttons[0].dataset.tab;
if (defaultTab) setActive(defaultTab);
});

View file

@ -1,6 +1,6 @@
<div @class(['mt-md-0 mt-5', $class ?? ''])>
<div @class(['mt-md-0 mt-5', $class ?? '']) data-tab="{{ $attributes['data-tab'] }}">
<div class="light-card">
<img class="img-fluid light-card-icon" src="{{ Vite::asset($icon['path']) }}" title="{{ $icon['description'] }}"
<img class="img-fluid light-card-icon" src="{{ asset($icon['path']) }}" title="{{ $icon['description'] }}"
alt="{{ $icon['alt'] }}">
<span class="text-uppercase bg-secondary text-light fw-semibold right-rotate-sm">{{ $category }}</span>

View file

@ -6,12 +6,12 @@
<div class="mt-5">
{{-- <x-fo.breeding.portrait-slider /> --}}
<x-fo.breeding.sortant-a-l-exterieur-card href="{{ route('fo.breeding.sortant-a-l-exterieur') }}"
{{-- <x-fo.breeding.sortant-a-l-exterieur-card href="{{ route('fo.breeding.sortant-a-l-exterieur') }}"
title="{{ __('La démarche Sortant à l\'extérieur') }}" containerTitle="{{ __('Découvrez aussi') }}"
btnText="{{ __('En savoir plus') }}" :icon="[
'path' => 'resources/assets/images/breeding/sortant-a-l-exterieur/logo-sortant-a-l-exterieur.png',
'alt' => 'pictogramme sortant à l\'extérieur',
'description' => 'pictogramme sortant à l\'extérieur',
]" />
]" /> --}}
</div>
</div>

View file

@ -1,123 +1,12 @@
@props(['staticPageModel' => $staticPageModel])
@php
$items = [
'chicken' => [
[
'category' => 'Accès à l\'extérieur',
'icon' => [
'path' => 'resources/assets/images/breeding/sortant-a-l-exterieur/chicken-outside-sun.svg',
'alt' => "Pictogramme d'un accès à l'eau et l'alimenttaion en continu",
'description' => "Pictogramme d'une goutte d'eau et d'un épi de blé entouré de d'un clycle de deux flèches",
],
'content' => [
[
'title' => 'Des volailles qui sortent à l’Extérieur',
'points' => [
nl2br(__('Nos poulets ont accès quotidiennement à un :strongStartparcours extérieur pendant au minimum 8 heures*:strongEnd', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
nl2br(__('Les trappes sont ouvertes toute la journée : les volailles peuvent donc :strongStartsortir dans les prés:strongEnd ou :strongStartrester dans le poulailler:strongEnd comme elles le souhaitent ! Cela permet de leur assurer des conditions d’élevage les plus proches possible de leur milieu naturel, à la lumière naturelle le jour, et à l’obscurité toute la nuit.', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
],
'asterisc' => '*Durant au moins la moitié de leur élevage',
],
],
'class' => 'card-template-sm',
],
[
'category' => 'Amélioration du bien-être animal',
'icon' => [
'path' => 'resources/assets/images/breeding/sortant-a-l-exterieur/chicken-heart.svg',
'alt' => "Pictogramme d'une volaille en bonne santé",
'description' => "Pictogramme d'une volaille avec un coeur pour valoriser le bien-être animal dans les élevages",
],
'content' => [
[
'title' => 'Le bien-être animal au cœur des préoccupations',
'text' => 'Nos éleveurs partenaires ont des convictions fortes :',
],
[
'points' => [
nl2br(__('Elever des :strongStartvolailles 100% françaises:strongEnd', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
nl2br(__('Privilégiez des poulets à croissance lente élevées pendant :strongStart56 jours minimum.:strongEnd', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
nl2br(__('Utiliser une :strongStartmédecine douce:strongEnd pour soigner leurs volailles. L’utilisation d’antibiotiques n’est faite qu’en dernier recours et après autorisation d’un vétérinaire indépendant*.', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
],
'asterisc' => '*Conformément aux exigences réglementaires',
],
],
'class' => 'card-template-md',
],
[
'category' => 'Environnement',
'icon' => [
'path' => 'resources/assets/images/breeding/sortant-a-l-exterieur/chicken-arrow-left-right.svg',
'alt' => "Pictogramme de volailles avec un accès à l'extérieur",
'description' => 'Pictogramme de volailles dans un champs ensoleillé',
],
'content' => [
[
'title' => 'Plus d\'espace à l\'extérieur mais aussi à l\'intérieur !',
'points' => [
nl2br(__('En moyenne :strongStart30% d’espace supplémentaire dans le poulailler:strongEnd par rapport aux élevages conventionnels pour garantir plus d’espaces aux poulets pour se déplacer et exprimer leur comportement naturel.', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
nl2br(__('Les volailles ont à disposition des jouets comme des :strongStartballots de paille et des pierres à piquer.:strongEnd', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
],
],
],
'class' => 'card-template-md',
],
[
'category' => 'Alimentation',
'icon' => [
'path' => 'resources/assets/images/breeding/sortant-a-l-exterieur/chicken-outside-food.svg',
'alt' => "Pictogramme d'une volaille avec plus d'espace",
'description' => "Pictogramme d'une volaille avec deux flèches opposées pour indiquer plus d'espace à l'intérieur et à l'extérieur du poulailler",
],
'content' => [
[
'title' => 'Un accès constant à l\'eau et à la nourriture',
'points' => [
nl2br(__('Nos volailles sont nourries avec des :strongStartcéréales entières 100% françaises:strongEnd, sélectionnées par des nutritionnistes et :strongStartgaranties SANS OGM:strongEnd (<0,9%).', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
nl2br(__('Nos volailles ont :strongStartaccès toute la journée à des abreuvoirs d’eau et à leur nourriture dans les mangeoires.:strongEnd', [
'strongStart' => '<strong>',
'strongEnd' => '</strong>',
'br' => '<br>',
])),
],
],
],
'class' => 'card-template-sm',
],
],
];
use App\Lib\Helpers\StaticPageSectionHelper;
$data = $staticPageModel->data;
$sections = [];
$tabs = [];
$items = StaticPageSectionHelper::buildItems($data);
@endphp
<section class="position-relative line-border-large z-1 overflow-hidden">
<div class="position-absolute h-100 w-100 top-0 z-0">
@ -131,30 +20,43 @@
{{ __('Notre cahier des charges') }}
</h2>
{{-- Oui c'est bon Logo --}}
{{-- <div class="d-flex justify-content-center mb-3">
<div class="d-flex justify-content-center mb-3">
<div>
<img class="img-fluid center-block"
src="{{ asset($staticPageModel->data['spec_image']['value']) }}"
alt="{{ __($staticPageModel->data['spec_image']['alt']) }}">
src="{{ asset($staticPageModel->data['picto']['value']) }}"
alt="{{ __($staticPageModel->data['picto']['alt']) }}">
</div>
</div>
</div>
<p class="w-lg-50 mx-auto text-center">
{{-- {{ __($staticPageModel->data['spec_description']['value']) }}
</p> --}}
{{ __(strip_tags($staticPageModel->data['description']['value'])) }}
</p>
</div>
</div>
{{-- Buttons --}}
<div class="d-flex justify-content-center gap-md-4 gap-5">
<button class="btn btn-secondary fs-6 d-block" id="chicken-button-outside" type="button">
{{ __('Poulet') }}
@php
$tabs = array_keys($items);
$activeTab = $tabs[0] ?? null;
@endphp
<div class="d-flex justify-content-center gap-md-4 gap-5" id="cards-tabs">
@foreach ($tabs as $tab)
<button class="btn fs-6 d-block tab-btn {{ $tab === $activeTab ? 'btn-secondary is-active' : 'btn-light' }}" data-tab="{{ $tab }}" type="button">
{{ __($tab) }}
</button>
@endforeach
</div>
{{-- Cards --}}
<div class="row mb-5 mt-5" id="cards-container-outside">
<div class="row mb-5 mt-5" id="cards-container">
<div class="card-grid">
@foreach ($items['chicken'] as $item)
<x-fo.breeding.light-card :category="$item['category']" :icon="$item['icon']" :content="$item['content']" :class="$item['class'] . ' chicken-card'" />
@foreach ($items as $tabKey => $sections)
@foreach ($sections as $section)
<x-fo.breeding.light-card
:category="$section['category']"
:icon="$section['icon']"
:content="$section['content']"
:class="$section['class'] . ' tab-card '. ($tabKey !== $activeTab ? 'd-none' : '')" data-tab="{{ $tabKey }}" />
@endforeach
@endforeach
</div>
</div>