fonc(produit) implémente le multi-variations

This commit is contained in:
gcch 2025-12-15 15:34:50 +01:00
commit 05baad8fdd
26 changed files with 1320 additions and 184 deletions

View file

@ -1,11 +1,9 @@
<?php
<?php declare(strict_types=1);
/**
* Le modèle de la Page d'Accueil.
*/
declare(strict_types=1);
use Timber\Timber;
// Contexte et modèles

View file

@ -1,46 +1,43 @@
<?php
<?php declare(strict_types=1);
/**
* Le modèle de la Page d'un Produit.
*/
declare(strict_types=1);
use function Crell\fp\pipe;
use HaikuAtelier\Data\Product;
use Timber\Timber;
use function Crell\fp\pipe;
require_once __DIR__ . '/src/inc/HTML.php';
require_once __DIR__ . '/src/inc/TraitementInformations.php';
// Contexte et modèles
$contexte = Timber::context();
$modeles = ['produit.twig'];
$context = Timber::context();
$templates = ['produit.twig'];
/** @var WC_Product $produit */
$produit = wc_get_product();
$product = wc_get_product();
/** @var mixed $donnees_produit */
$donnees_produit = recupere_informations_produit_page_produit(wc_get_product());
if ($product === null || is_bool($product)) {
throw new Exception("Le Produit n'existe pas.");
}
/** @var bool $est_variation Le Produit est-il Variable (possède-t-il des variations ?) */
$est_produit_variable = 'variable' === $produit->get_type();
// $donnees_produit = recupere_informations_produit_page_produit($product);
$donnees_produit = Product::new($product);
/** @var array $variations_produit Un tableau des informations d'affichage de chaque Variation du Produit */
// Un tableau des informations d'affichage de chaque Variation du Produit
$variations_produit = pipe(
// Récupère les IDs des Enfants (Variations)
wc_get_product()->get_children(),
$product->get_children(),
// Récupère les Variations
fn($enfants) => array_map(
static fn(/** @var list<int> */ $enfants): array => array_map(
callback: wc_get_product(...),
array: $enfants,
),
// Ne conserve que les Informations souhaitées
fn($variations) => array_map(
callback: fn($variation) => [
// Ne conserve que les Informations souhaitées.
static fn(/** @var list<WC_Product> */ $variations): array => array_map(
callback: static fn(WC_Product $variation): array => [
'id' => $variation->get_id(),
// Ne récupère que le titre de l'Attribut unique de la Variation
// Ne récupère que le titre de l'Attribut unique de la Variation.
'titre' => match (true) {
'' !== $variation->get_attribute('pa_side') => $variation->get_attribute('pa_side'),
'' !== $variation->get_attribute('pa_stone') => $variation->get_attribute('pa_stone'),
@ -60,14 +57,14 @@ $variations_produit = pipe(
$prix_maximal = collect($variations_produit)->max('prix');
$produits_meme_collection = array_map(
callback: 'recupere_informations_produit_page_produit',
array: recupere_produits_meme_collection($donnees_produit['collection'])($donnees_produit['id']),
callback: recupere_informations_produit_page_produit(...),
array: recupere_produits_meme_collection($donnees_produit->collection)($donnees_produit->id),
);
$contexte['produit'] = $donnees_produit;
$contexte['prix_maximal'] = $prix_maximal;
$contexte['variations_produit'] = $variations_produit;
$contexte['produits_meme_collection'] = $produits_meme_collection;
$context['produit'] = $donnees_produit;
$context['prix_maximal'] = $prix_maximal;
$context['variations_produit'] = $variations_produit;
$context['produits_meme_collection'] = $produits_meme_collection;
/**
* Charge les Scripts nécessaires pour la page Produit.
@ -89,11 +86,15 @@ function charge_scripts_page_produit(): void {
add_action('wp_enqueue_scripts', 'charge_scripts_page_produit');
// $lal = wp_json_encode($contexte);
// echo "<script>console.debug({$lal});</script>";
$lal = wp_json_encode($context);
echo "<script>console.debug({$lal});</script>";
$lol = wc_get_product()->get_children();
$lol = wp_json_encode($lol);
echo "<script>console.debug({$lol});</script>";
// Rendu
Timber::render(
filenames: $modeles,
data: $contexte,
filenames: $templates,
data: $context,
);

View file

@ -0,0 +1,33 @@
<?php declare(strict_types=1);
namespace HaikuAtelier\Data;
use Illuminate\Support\Arr;
use WC_Product_Attribute;
use WP_Term;
final readonly class Attribute {
/**
* @param list<AttributeOption> $options
*/
public function __construct(
public string $name,
public string $slug,
public array $options,
) {}
public static function new(WC_Product_Attribute $attribute): self {
$name = wc_attribute_label($attribute->get_name());
$slug = $attribute->get_name();
/** @var list<WP_Term> */
$terms = $attribute->get_terms() ?? [];
/** @var list<AttributeOption> */
$options = Arr::map($terms, static fn(WP_Term $term): AttributeOption => AttributeOption::new($term));
return new self(
name: $name,
slug: $slug,
options: $options,
);
}
}

View file

@ -0,0 +1,25 @@
<?php declare(strict_types=1);
namespace HaikuAtelier\Data;
use WP_Term;
final readonly class AttributeOption {
public function __construct(
public int $id,
public string $name,
public string $slug,
) {}
public static function new(WP_Term $term): self {
$id = $term->term_taxonomy_id;
$name = $term->name;
$slug = $term->slug;
return new self(
id: $id,
name: $name,
slug: $slug,
);
}
}

View file

@ -0,0 +1,103 @@
<?php declare(strict_types=1);
namespace HaikuAtelier\Data;
use HaikuAtelier\WP\HaikuProduct;
use HaikuAtelier\WP\Term;
use Illuminate\Support\Arr;
use Psl\Option;
use function Psl\Option\from_nullable;
use WC_Product;
use WC_Product_Attribute;
use WP_Term;
final readonly class Product {
/**
* @param list<Attribute> $attributes
* @param list<string> $left_column_photos
* @param list<string> $right_column_photos
* @param list<int> $variation_ids
*/
private function __construct(
public array $attributes,
public string $category,
public string $collection,
public string $details,
public int $id,
public string $name,
public string $price,
public array $left_column_photos,
public array $right_column_photos,
public string $default_photo,
public string $hover_photo,
public string $slug,
public int $stock,
public array $variation_ids,
public string $url,
) {}
/**
* @return list<Attribute>
*/
public static function get_attributes_for_product(WC_Product $product): array {
/** @var list<Attribute> */
return $product->get_attributes()
|> (static fn($attributes) => Arr::map($attributes, static fn(WC_Product_Attribute $attribute): Attribute => Attribute::new(
$attribute,
)));
}
public static function new(WC_Product $product): self {
/** @var list<Attribute> */
$attributes = self::get_attributes_for_product($product);
/** @var lowercase-string */
$category = $product->get_id() |> wc_get_product_category_list(...) |> strtolower(...);
/** @var Option\Option<list<WP_Term>> */
$collection = Term::get_terms($product->get_id(), 'collection');
/** @var Option\Option<WP_Term> */
$collection = $collection->andThen(
static fn(array $terms): Option\Option => head($terms) |> from_nullable(...),
);
/** @var Option\Option<string> */
$collection = $collection->map(static fn(WP_Term $term) => $term->slug);
/** @var string */
$collection = $collection->unwrapOr('');
/** @var string */
$details = $product->get_description() |> wpautop(...);
$id = $product->get_id();
$name = $product->get_name();
$price = $product->get_price();
/** @var list<string> */
$left_column_photos = HaikuProduct::get_left_column_photos($id);
/** @var list<string> */
$right_column_photos = HaikuProduct::get_right_column_photos($id);
$default_photo = $left_column_photos[0] ?? genere_balise_img_multiformats('-1');
$hover_photo = $right_column_photos[0] ?? genere_balise_img_multiformats('-1', true);
$slug = $product->get_slug();
$stock = $product->get_stock_quantity() ?? 1;
/** @var list<int> */
$variation_ids = $product->get_children();
$url = $product->get_permalink();
return new self(
attributes: $attributes,
category: $category,
collection: $collection,
details: $details,
id: $id,
name: $name,
price: $price,
left_column_photos: $left_column_photos,
right_column_photos: $right_column_photos,
default_photo: $default_photo,
hover_photo: $hover_photo,
slug: $slug,
stock: $stock,
variation_ids: $variation_ids,
url: $url,
);
}
}

View file

@ -6,25 +6,30 @@
declare(strict_types=1);
use function Crell\fp\pipe;
use HaikuAtelier\Data\Attribute;
use HaikuAtelier\Data\Product;
// Page Shop
/**
* TODO.
*
* @param int $id TODO
* @param bool $lazy TODO
* @param string $id TODO
* @param bool $lazy TODO
*
* @return string TODO
*/
function genere_balise_img_multiformats($id, $lazy = false) {
function genere_balise_img_multiformats(string $id, bool $lazy = false): string {
$int_id = (int) $id;
if (-1 === $id) {
return '';
}
$url = wp_get_attachment_image_url($id, 'full');
$chemin = realpath(get_attached_file($id)) ?: realpath(get_attached_file($id));
$alt = get_post_meta($id, '_wp_attachment_image_alt', true);
$url = wp_get_attachment_image_url($int_id, 'full');
$chemin = realpath(get_attached_file($int_id)) ?: realpath(get_attached_file($int_id));
$alt = get_post_meta($int_id, '_wp_attachment_image_alt', true);
$dimensions = $chemin ? getimagesize($chemin) : ['', ''];
$avif = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.avif') : false;
@ -34,27 +39,26 @@ function genere_balise_img_multiformats($id, $lazy = false) {
// Génère un tableau avec les différents formats valides
$formats = pipe(
[$avif, $jxl, $webp],
fn($tableau) => array_filter(
static fn($tableau): array => array_filter(
array: $tableau,
callback: fn($chemin_format) => false !== $chemin_format,
callback: static fn($chemin_format): bool => false !== $chemin_format,
),
fn($tableau) => array_map(
static fn($tableau): array => array_map(
array: $tableau,
callback: fn($chemin_format) => [
'format' => pathinfo($chemin_format)['extension'],
callback: static fn($chemin_format): array => [
'format' => pathinfo((string) $chemin_format)['extension'],
'taille' => filesize($chemin_format),
'url' =>
pathinfo($url)['dirname']
'url' => pathinfo($url)['dirname']
. '/'
. pathinfo($url)['filename']
. '.'
. pathinfo($chemin_format)['extension'],
. pathinfo((string) $chemin_format)['extension'],
],
),
);
usort(
array: $formats,
callback: fn($a, $b) => $a['taille'] <=> $b['taille'],
callback: static fn($a, $b): int => $a['taille'] <=> $b['taille'],
);
// Construis les balises <source> avec les formats valides
@ -84,47 +88,36 @@ function genere_balise_img_multiformats($id, $lazy = false) {
/**
* TODO.
*
* @param WC_Product $a
* @param WC_Product $b
*
* @return int
*/
function tri_variations_par_prix_descendant($a, $b) {
if ($a->get_price() === $b->get_price()) {
return 0;
}
return $a->get_price() < $b->get_price() ? 1 : -1;
function tri_variations_par_prix_descendant(WC_Product $a, WC_Product $b): int {
return $b->get_price() <=> $a->get_price();
}
/**
* Récupère les informations utilisées pour la grille des Produits et les retourne sous forme
* de tableau associatif.
*
* @param WC_Product $produit
*
* @return mixed un tableau avec uniquement les informations pour la Grille de Produits
*/
function recupere_informations_produit_shop($produit) {
function recupere_informations_produit_shop(WC_Product $produit): mixed {
/** @var int $prix_maximal Le prix maximal du Produit. */
$prix_maximal = pipe(
// Récupère les Variations
$produit->get_children(),
// Récupère les informations de chaque Variation
fn($enfants) => array_map(
static fn($enfants): array => array_map(
callback: wc_get_product(...),
array: $enfants,
),
// Trie les Variations par prix descendant
fn($variations) => array_map(
callback: fn($variation) => $variation->get_price(),
static fn($variations): array => array_map(
callback: static fn($variation) => $variation->get_price(),
array: $variations,
),
// Récupère le Prix de la Variation la plus chère
fn($prix) => collect($prix)->max(),
static fn($prix) => collect($prix)->max(),
// Récupère le Prix pour la Variation la plus chère OU le prix du Produit simple
fn($prix_variation_maximale) => $prix_variation_maximale ?? $produit->get_price(),
static fn($prix_variation_maximale) => $prix_variation_maximale ?? $produit->get_price(),
);
// TEMP: Cas de la Carte Cadeau où aucun prix ne doit être affiché. Idéalement utiliser un système d'étiquettes pour ces cas là.
@ -158,49 +151,50 @@ function recupere_informations_produit_shop($produit) {
/**
* Retourne un tableau associatif des informations affichées sur la page Produit depuis les données brutes d'un Produit.
*
* @param WC_Product $donnees_produit
*/
function recupere_informations_produit_page_produit($donnees_produit): mixed {
function recupere_informations_produit_page_produit(WC_Product $product): mixed {
/** @var list<Attribute> */
$attributs = Product::get_attributes_for_product($product);
return [
// Attributs du Produit
'attributs' => wc_get_product()->get_attributes(),
'attributs' => $attributs,
// Catégorie du Produit
'categorie' => pipe($donnees_produit->get_id(), wc_get_product_category_list(...), strtolower(...)),
'categorie' => pipe($product->get_id(), wc_get_product_category_list(...), strtolower(...)),
// Slug de la Collection - Peut ne pas avoir été défini
'collection' => get_the_terms($donnees_produit->get_id(), 'collection')[0]->slug ?? '',
'collection' => get_the_terms($product->get_id(), 'collection')[0]->slug ?? '',
// Détails (Description) du Produit
'details' => wpautop($donnees_produit->get_description()),
'details' => wpautop($product->get_description()),
// Identifiant du Produit
'id' => $donnees_produit->get_id(),
'id' => $product->get_id(),
// Nom affiché du Produit
'nom' => $donnees_produit->get_name(),
'nom' => $product->get_name(),
// Prix affiché du Produit
'prix' => $donnees_produit->get_price(),
'prix' => $product->get_price(),
'photos_colonne_gauche' => array_map(
callback: 'genere_balise_img_multiformats',
array: get_post_meta($post_id = $donnees_produit->get_id(), $key = '_photos_colonne_gauche|||0|value'),
callback: genere_balise_img_multiformats(...),
array: get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_gauche|||0|value'),
),
'photos_colonne_droite' => array_map(
callback: 'genere_balise_img_multiformats',
callback: genere_balise_img_multiformats(...),
array: carbon_get_the_post_meta('photos_colonne_droite'),
),
'photo_repos' => genere_balise_img_multiformats(
get_post_meta($post_id = $donnees_produit->get_id(), $key = '_photos_colonne_gauche|||0|value')[0] ?? -1,
get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_gauche|||0|value')[0] ?? -1,
false,
),
'photo_survol' => genere_balise_img_multiformats(
get_post_meta($post_id = $donnees_produit->get_id(), $key = '_photos_colonne_droite|||0|value')[0] ?? -1,
get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_droite|||0|value')[0] ?? -1,
true,
),
// Slug du Produit
'slug' => $donnees_produit->get_slug(),
'slug' => $product->get_slug(),
// Quantité de Produit en stock
'stock' => $donnees_produit->get_stock_quantity() ?? 1,
'stock' => $product->get_stock_quantity() ?? 1,
// Variations du Produit
'variations_ids' => $donnees_produit->get_children(),
'variations_ids' => $product->get_children(),
// URL du Produit
'url' => $donnees_produit->get_permalink(),
'url' => $product->get_permalink(),
];
}
@ -209,14 +203,10 @@ function recupere_informations_produit_page_produit($donnees_produit): mixed {
* collection) et les retourne sous forme de tableau associatif.
*
* Pour faciliter l'usage avec `array_map`, utilise une fonction avec curryfication.
*
* @param string $slug_collection
*
* @return mixed
*/
function recupere_produits_meme_collection($slug_collection) {
function recupere_produits_meme_collection(string $slug_collection): mixed {
// @param int $id_produit
return fn($id_produit) => wc_get_products([
return static fn($id_produit) => wc_get_products([
'exclude' => [$id_produit],
'limit' => 4,
'order' => 'DESC',
@ -228,12 +218,7 @@ function recupere_produits_meme_collection($slug_collection) {
// Page Panier
/**
* @param mixed $attributs_produit
*
* @return mixed
*/
function recupere_et_formate_attributs_produit($attributs_produit) {
function recupere_et_formate_attributs_produit(mixed $attributs_produit): mixed {
return [
'taille' => ['nom' => 'Size', 'valeur' => $attributs_produit['pa_size'] ?? false],
'pierre' => ['nom' => 'Stone', 'valeur' => $attributs_produit['pa_stone'] ?? false],

View file

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace HaikuAtelier\WP;
use Illuminate\Support\Arr;
use function is_array;
use function is_string;
final readonly class HaikuProduct {
/**
* @return list<string>
*/
public static function get_left_column_photos(int $post_id): array {
/** @var list<string> */
return Post::get_post_meta_array($post_id, '_photos_colonne_gauche|||0|value')->unwrapOr([])
|> (static fn(array $meta) => Arr::where($meta, static fn($meta): bool => is_string($meta)))
|> (static fn(array $array) => Arr::map($array, genere_balise_img_multiformats(...)));
}
/**
* @return list<string>
*/
public static function get_right_column_photos(int $post_id): array {
$meta = carbon_get_post_meta($post_id, 'photos_colonne_droite');
if (is_array($meta)) {
/** @var list<string> */
return Arr::where($meta, static fn($meta): bool => is_string($meta))
|> (static fn(array $array) => Arr::map($array, genere_balise_img_multiformats(...)));
}
return [];
}
}

View file

@ -0,0 +1,55 @@
<?php declare(strict_types=1);
namespace HaikuAtelier\WP;
use Psl\Option;
use function Psl\Option\none;
use function Psl\Option\some;
use WP_Error;
use WP_Term;
use function is_array;
final readonly class Post {
/**
* @return Option\Option<mixed>
*/
public static function get_post_meta(int $post_id, string $key): Option\Option {
/** @var false|mixed|string */
$value = get_post_meta($post_id, $key, true);
if ($value === false) {
return none();
}
return some($value);
}
/**
* @return Option\Option<array<mixed>>
*/
public static function get_post_meta_array(int $post_id, string $key): Option\Option {
/** @var array<mixed>|false */
$value = get_post_meta($post_id, $key, false);
if (is_array($value)) {
return some($value);
}
return none();
}
/**
* @return Option\Option<array<mixed>>
*/
public static function get_terms(int $post_id, string $taxonomy_name): Option\Option {
/** @var false|list<WP_Term>|WP_Error */
$terms = get_the_terms($post_id, $taxonomy_name);
if (is_array($terms)) {
return some($terms);
}
return none();
}
}

View file

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
namespace HaikuAtelier\WP;
use Psl\Option;
use function Psl\Option\none;
use function Psl\Option\some;
use WP_Term;
use function is_array;
final readonly class Term {
/**
* @return Option\Option<list<WP_Term>>
*/
public static function get_terms(int $post_id, string $taxonomy_name): Option\Option {
$terms = get_the_terms($post_id, $taxonomy_name);
if (is_array($terms)) {
/** @var Option\Option<list<WP_Term>> */
return some($terms);
}
return none();
}
}

View file

@ -59,7 +59,10 @@
font-size: var(--resume-police-nom-taille);
}
&__selection-variation {
&__attribut-variation {
display: flex;
flex-flow: row wrap;
gap: var(--espace-m) var(--espace-l);
font-size: var(--resume-police-selecteur-taille);
font-weight: var(--resume-police-selecteur-graisse);
text-transform: lowercase;
@ -98,7 +101,7 @@
pointer-events: none;
content: " ";
position: absolute;
top: 10px;
top: 7px;
right: 0.4rem;
display: inline-block;
width: 0.9rem;
@ -148,7 +151,7 @@
}
@media (width <= 500px) {
.selecteur-produit__selection-variation {
.selecteur-produit__selection-variation-attribut {
flex-flow: column nowrap;
row-gap: var(--espace-inter-colonne);

View file

@ -4,12 +4,13 @@ import { pipe } from "@mobily/ts-belt";
import { forEach as arrayForEach } from "@mobily/ts-belt/Array";
import { get as dictGet } from "@mobily/ts-belt/Dict";
import { tap as optionTap } from "@mobily/ts-belt/Option";
import { pipe as epipe } from "effect";
import { EitherAsync, Maybe } from "purify-ts";
import { match, P } from "ts-pattern";
import { type AnySchema, ValiError } from "valibot";
import type { WCStoreCart } from "./lib/types/api/cart";
import type { WCStoreCartAddItemArgs } from "./lib/types/api/cart-add-item.ts";
import type { WCStoreCartAddItemArgs, WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.ts";
import type { FetchErrors } from "./lib/types/reseau.ts";
import { ROUTE_API_AJOUTE_ARTICLE_PANIER } from "./constantes/api.ts";
@ -23,8 +24,8 @@ import {
DOM_BOUTON_AJOUT_PANIER,
DOM_BOUTONS_ACCORDEON,
DOM_CONTENUS_ACCORDEON,
DOM_PRIX_PRODUIT,
DOM_DOM_QUANTITE,
DOM_PRIX_PRODUIT,
} from "./constantes/dom.ts";
import { lanceAnimationCycleLoading } from "./lib/animations.ts";
import { mustGetEleInDocument, mustGetElesInDocument, recupereElementDocumentEither } from "./lib/dom.ts";
@ -64,8 +65,9 @@ const E = {
BOUTON_AJOUT_PANIER: mustGetEleInDocument<HTMLButtonElement>(DOM_BOUTON_AJOUT_PANIER),
BOUTONS_ACCORDEON: mustGetElesInDocument<HTMLAnchorElement>(DOM_BOUTONS_ACCORDEON),
CONTENUS_ACCORDEON: mustGetElesInDocument<HTMLDivElement>(DOM_CONTENUS_ACCORDEON),
PRIX_PRODUIT: mustGetEleInDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT),
DOM_VARIATION: recupereElementDocumentEither<HTMLSelectElement>(DOM_DOM_QUANTITE),
PRIX_PRODUIT: mustGetEleInDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT),
VARIATION_CHOICE_FORM: mustGetEleInDocument<HTMLFormElement>("#variation-choice"),
};
const gereAccordeonDetailsProduit = (): void => {
@ -119,17 +121,39 @@ const gereAccordeonDetailsProduit = (): void => {
});
})
);
E.BOUTON_AJOUT_PANIER.addEventListener("click", (): void => ajouteProduitAuPanier());
E.BOUTON_AJOUT_PANIER.addEventListener("click", (event: MouseEvent): void => ajouteProduitAuPanier(event));
};
const ajouteProduitAuPanier = (): void => {
const getAttributeValuesFromDom = () => {
const selectElements = epipe(
document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"),
Array.from<HTMLSelectElement>,
);
if (selectElements.length === 0) return [];
const attributeValues = selectElements.map(select => {
return {
attribute: select.id.replace("selecteur-attribut-", ""),
value: select.value,
} satisfies WCStoreCartAddItemArgsItems;
});
return attributeValues;
};
const ajouteProduitAuPanier = (event: MouseEvent): void => {
event.preventDefault();
console.debug("getAttributeValuesFromDom", getAttributeValuesFromDom());
// Construis les arguments de la requête au backend
const argsRequete: WCStoreCartAddItemArgs = {
id: E.DOM_VARIATION
.map((selecteur: HTMLSelectElement): number => Number(selecteur.value))
// Récupère l'ID du Produit de la Page pour les Produits simples
.orDefault(ETATS_PAGE.idProduit),
// id: E.DOM_VARIATION
// .map((selecteur: HTMLSelectElement): number => Number(selecteur.value))
// // Récupère l'ID du Produit de la Page pour les Produits simples
// .orDefault(ETATS_PAGE.idProduit),
id: ETATS_PAGE.idProduit,
quantity: 1,
variation: getAttributeValuesFromDom(),
};
// Réalise la Requête et traite sa Réponse
@ -209,6 +233,28 @@ const ajouteProduitAuPanier = (): void => {
.run();
};
const initAddToCartButtonActivationOnUserChoice = (): void => {
const selectElements: ReadonlyArray<HTMLSelectElement> = epipe(
document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"),
Array.from<HTMLSelectElement>,
);
// S'il n'y a pas de sélecteur de variation, activer le bouton.
if (selectElements.length === 0) {
E.BOUTON_AJOUT_PANIER.removeAttribute(ATTRIBUT_DESACTIVE);
}
E.VARIATION_CHOICE_FORM.addEventListener("change", (): void => {
const formValidity = E.VARIATION_CHOICE_FORM.checkValidity();
if (formValidity) {
E.BOUTON_AJOUT_PANIER.removeAttribute(ATTRIBUT_DESACTIVE);
} else {
E.BOUTON_AJOUT_PANIER.setAttribute(ATTRIBUT_DESACTIVE, "");
}
});
};
document.addEventListener("DOMContentLoaded", (): void => {
gereAccordeonDetailsProduit();
initAddToCartButtonActivationOnUserChoice();
});

View file

@ -25,7 +25,7 @@ $informations_produits = wc_get_products([
/** @var InformationsProduitShop $produits Les informations strictement nécessaires pour la grille des Produits. */
$produits = array_map(
callback: 'recupere_informations_produit_shop',
callback: recupere_informations_produit_shop(...),
array: $informations_produits,
);
$contexte['produits'] = $produits;

View file

@ -24,7 +24,7 @@
loading="eager"
src="{{ rel_url }}.jpg"
width="{{ width }}"
onload="this.style.opacity=1"
onload="this.style.opacity = 1"
>
</picture>
{% endmacro %}

View file

@ -1,49 +1,63 @@
{# Barre flottante avec le nom du Produit, le sélectgeur de variation et de quantité pour le Panier. #}
{# Barre flottante avec le nom du Produit, le sélecteur de variation et de quantité pour le Panier. #}
<aside
aria-label="Product's name, price and variation selection"
class="resume-produit"
>
<section class="selecteur-produit">
<h3 class="selecteur-produit__nom">{{ produit.nom }}</h3>
<form
class="selecteur-produit"
id="variation-choice"
name="variation-choice"
>
<h3 class="selecteur-produit__nom">{{ produit.name }}</h3>
<div class="selecteur-produit__selection-variation">
{% if variations_produit|length > 1 %}
<label
for="selecteur-variation"
id="label-selecteur-variation"
>
Option:
</label>
<div class="selecteur-produit__selection-variation__selecteurs">
<select
aria-labelledby="label-selecteur-variation"
id="selecteur-variation"
name="variations"
>
<option
disabled
selected
value=""
>
--
</option>
{% for variation in variations_produit %}
<option
data-prix="{{ variation.prix }}"
value="{{ variation.id }}"
>
{{ variation.titre }}
</option>
{% endfor %}
</select>
</div>
<div class="selecteur-produit__attribut-variation">
{% if produit.attributes %}
{% for attribut in produit.attributes %}
<div class="test">
{{ include('parts/pages/produit/selecteur-attributs-produit.twig') }}
</div>
{% endfor %}
{% endif %}
<!--
{% if variations_produit|length > 1 %}
<label
for="selecteur-variation"
id="label-selecteur-variation"
>
Option:
</label>
<div class="selecteur-produit__attribut-variation__selecteurs">
<select
aria-labelledby="label-selecteur-variation"
id="selecteur-variation"
name="variations"
>
<option
disabled
selected
value=""
>
--
</option>
{% for variation in variations_produit %}
<option
data-prix="{{ variation.prix }}"
value="{{ variation.id }}"
>
{{ variation.titre }}
</option>
{% endfor %}
</select>
</div>
{% endif %}
-->
</div>
<p class="selecteur-produit__prix">{{ prix_maximal ?? produit.prix }}€</p>
</section>
<p class="selecteur-produit__prix">{{ prix_maximal ?? produit.price }}€</p>
</form>
</aside>
<aside
@ -107,12 +121,14 @@
</div>
<div class="details-produit__actions">
{# Désactive le bouton d'ajout au panier en cas d'absence de stock. #}
{% if produit.stock > 0 %}
<button
class="bouton-case-pleine"
{{ variations_produit|length > 1 ? 'disabled' : '' }}
disabled
for="variation-choice"
id="bouton-ajout-panier"
type="button"
type="submit"
>
Add to cart
</button>
@ -120,6 +136,7 @@
<button
class="bouton-case-pleine"
disabled
for="variation-choice"
id="bouton-ajout-panier"
type="button"
>

View file

@ -3,7 +3,7 @@
aria-label="Photo of the Product alone"
class="colonne colonne-gauche"
>
{% for photo in produit.photos_colonne_gauche %}
{% for photo in produit.left_column_photos %}
<figure
data-index="0"
role="figure"
@ -19,7 +19,7 @@
aria-label="Photos of the Product worn"
class="colonne colonne-droite"
>
{% for photo in produit.photos_colonne_droite %}
{% for photo in produit.right_column_photos %}
<figure
data-index="{{ loop.index }}"
role="figure"

View file

@ -0,0 +1,28 @@
<div class="selecteur-produit__attribut-variation__selecteurs">
<label
for="selecteur-attribut-{{ attribut.slug }}"
id="label-selecteur-attribut-{{ attribut.slug }}"
>
{{ attribut.name }}:
</label>
<select
aria-labelledby="label-selecteur-attribut-{{ atribut.slug }}"
id="selecteur-attribut-{{ attribut.slug }}"
name="attribut-{{ attribut.slug }}"
required
>
<option
disabled
selected
value=""
>
--
</option>
{% for term in attribut.options %}
<option value="{{ term.slug }}">
{{ term.name }}
</option>
{% endfor %}
</select>
</div>