✨ Story 3.3: liste projets vedettes
This commit is contained in:
7
assets/img/projects/default-project.svg
Normal file
7
assets/img/projects/default-project.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 225" width="400" height="225">
|
||||||
|
<rect width="400" height="225" fill="#1E1E28" />
|
||||||
|
<rect x="24" y="24" width="352" height="177" rx="12" fill="#2A2A36" stroke="#3A3A48" stroke-width="2" />
|
||||||
|
<circle cx="200" cy="100" r="28" fill="#FA784F" opacity="0.25" />
|
||||||
|
<rect x="140" y="142" width="120" height="12" rx="6" fill="#FA784F" opacity="0.6" />
|
||||||
|
<rect x="160" y="160" width="80" height="8" rx="4" fill="#71717A" />
|
||||||
|
</svg>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Ready for Dev
|
review
|
||||||
|
|
||||||
## Story
|
## Story
|
||||||
|
|
||||||
@@ -21,31 +21,31 @@ Ready for Dev
|
|||||||
|
|
||||||
## Tasks / Subtasks
|
## Tasks / Subtasks
|
||||||
|
|
||||||
- [] **Task 1 : Créer la page projects.php** (AC: 1)
|
- [x] **Task 1 : Créer la page projects.php** (AC: 1)
|
||||||
- [] Créer `pages/projects.php`
|
- [x] Créer `pages/projects.php`
|
||||||
- [] Récupérer les projets vedettes avec `getProjectsByCategory('vedette')`
|
- [x] Récupérer les projets vedettes avec `getProjectsByCategory('vedette')`
|
||||||
- [] Inclure header, navbar, footer
|
- [x] Inclure header, navbar, footer
|
||||||
|
|
||||||
- [] **Task 2 : Créer le template project-card.php** (AC: 2, 6)
|
- [x] **Task 2 : Créer le template project-card.php** (AC: 2, 6)
|
||||||
- [] Créer `templates/project-card.php`
|
- [x] Créer `templates/project-card.php`
|
||||||
- [] Afficher le thumbnail avec lazy loading
|
- [x] Afficher le thumbnail avec lazy loading
|
||||||
- [] Afficher le titre
|
- [x] Afficher le titre
|
||||||
- [] Afficher les badges technologies (max 4)
|
- [x] Afficher les badges technologies (max 4)
|
||||||
- [] Rendre le composant réutilisable
|
- [x] Rendre le composant réutilisable
|
||||||
|
|
||||||
- [] **Task 3 : Implémenter la grille responsive** (AC: 4)
|
- [x] **Task 3 : Implémenter la grille responsive** (AC: 4)
|
||||||
- [] 1 colonne sur mobile
|
- [x] 1 colonne sur mobile
|
||||||
- [] 2 colonnes sur tablette (sm:)
|
- [x] 2 colonnes sur tablette (sm:)
|
||||||
- [] 3 colonnes sur desktop (lg:)
|
- [x] 3 colonnes sur desktop (lg:)
|
||||||
|
|
||||||
- [] **Task 4 : Ajouter les interactions** (AC: 3, 5)
|
- [x] **Task 4 : Ajouter les interactions** (AC: 3, 5)
|
||||||
- [] Carte entière cliquable (lien vers /projet/{slug})
|
- [x] Carte entière cliquable (lien vers /projet/{slug})
|
||||||
- [] Effet hover avec card-interactive
|
- [x] Effet hover avec card-interactive
|
||||||
- [] Transition smooth
|
- [x] Transition smooth
|
||||||
|
|
||||||
- [] **Task 5 : Gérer les cas limites**
|
- [x] **Task 5 : Gérer les cas limites**
|
||||||
- [] Aucun projet → message "Projets à venir"
|
- [x] Aucun projet → message "Projets à venir"
|
||||||
- [] Image manquante → placeholder (onerror fallback)
|
- [x] Image manquante → placeholder (onerror fallback)
|
||||||
|
|
||||||
## Dev Notes
|
## Dev Notes
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ $maxTechs = 4;
|
|||||||
## Dev Agent Record
|
## Dev Agent Record
|
||||||
|
|
||||||
### Agent Model Used
|
### Agent Model Used
|
||||||
Claude Opus 4.5 (claude-opus-4-5-20251101)
|
GPT-5 Codex
|
||||||
|
|
||||||
### File List
|
### File List
|
||||||
| File | Action | Description |
|
| File | Action | Description |
|
||||||
@@ -192,6 +192,8 @@ Claude Opus 4.5 (claude-opus-4-5-20251101)
|
|||||||
| `pages/projects.php` | Modified | Page liste projets vedettes |
|
| `pages/projects.php` | Modified | Page liste projets vedettes |
|
||||||
| `templates/project-card.php` | Created | Template carte projet réutilisable |
|
| `templates/project-card.php` | Created | Template carte projet réutilisable |
|
||||||
| `assets/img/projects/default-project.svg` | Created | Placeholder image par défaut |
|
| `assets/img/projects/default-project.svg` | Created | Placeholder image par défaut |
|
||||||
|
| `tests/projects-list.test.php` | Created | Tests page projets |
|
||||||
|
| `tests/run.ps1` | Modified | Ajout tests projets |
|
||||||
|
|
||||||
### Completion Notes
|
### Completion Notes
|
||||||
- Grille responsive: 1 col (mobile) → 2 cols (sm) → 3 cols (lg)
|
- Grille responsive: 1 col (mobile) → 2 cols (sm) → 3 cols (lg)
|
||||||
@@ -199,7 +201,7 @@ Claude Opus 4.5 (claude-opus-4-5-20251101)
|
|||||||
- Lazy loading natif sur les images
|
- Lazy loading natif sur les images
|
||||||
- Fallback onerror pour images manquantes
|
- Fallback onerror pour images manquantes
|
||||||
- Message "Projets à venir" si aucun projet
|
- Message "Projets à venir" si aucun projet
|
||||||
- 2 projets vedettes affichés correctement
|
- Tests: `powershell -ExecutionPolicy Bypass -File tests/run.ps1`
|
||||||
|
|
||||||
### Debug Log References
|
### Debug Log References
|
||||||
Aucun problème rencontré.
|
Aucun problème rencontré.
|
||||||
@@ -209,4 +211,4 @@ Aucun problème rencontré.
|
|||||||
| Date | Version | Description | Author |
|
| Date | Version | Description | Author |
|
||||||
|------|---------|-------------|--------|
|
|------|---------|-------------|--------|
|
||||||
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
|
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
|
||||||
| 2026-01-23 | 1.0 | Implémentation complète | James (Dev) |
|
| 2026-02-04 | 1.0 | Implémentation complète | Amelia |
|
||||||
|
|||||||
@@ -1,16 +1,37 @@
|
|||||||
<?php
|
<?php
|
||||||
$pageTitle = 'Projets';
|
$pageTitle = 'Mes Projets';
|
||||||
|
$pageDescription = 'Découvrez mes réalisations web : sites vitrines, e-commerce, applications et plus encore.';
|
||||||
$currentPage = 'projects';
|
$currentPage = 'projects';
|
||||||
|
|
||||||
include_template('header', compact('pageTitle'));
|
$featuredProjects = getProjectsByCategory('vedette');
|
||||||
|
|
||||||
|
include_template('header', compact('pageTitle', 'pageDescription'));
|
||||||
include_template('navbar', compact('currentPage'));
|
include_template('navbar', compact('currentPage'));
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<main class="min-h-screen">
|
<main>
|
||||||
<div class="container-content py-20">
|
<section class="section">
|
||||||
<h1 class="text-heading mb-4">Projets</h1>
|
<div class="container-content">
|
||||||
<p class="text-text-secondary">La liste des projets arrive bientôt.</p>
|
<div class="section-header">
|
||||||
</div>
|
<h1 class="section-title">Mes Projets</h1>
|
||||||
|
<p class="section-subtitle">
|
||||||
|
Découvrez les réalisations qui illustrent mon travail et mes compétences.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($featuredProjects)): ?>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
|
||||||
|
<?php foreach ($featuredProjects as $project): ?>
|
||||||
|
<?php include_template('project-card', ['project' => $project]); ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="text-center text-text-muted py-12">
|
||||||
|
Projets à venir...
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php include_template('footer'); ?>
|
<?php include_template('footer'); ?>
|
||||||
50
templates/project-card.php
Normal file
50
templates/project-card.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Carte projet réutilisable
|
||||||
|
* @param array $project Données du projet
|
||||||
|
*/
|
||||||
|
|
||||||
|
$title = $project['title'] ?? 'Sans titre';
|
||||||
|
$slug = $project['slug'] ?? '#';
|
||||||
|
$thumbnail = $project['thumbnail'] ?? 'default-project.svg';
|
||||||
|
$technologies = $project['technologies'] ?? [];
|
||||||
|
$maxTechs = 4;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<article class="card-interactive group">
|
||||||
|
<a href="/projet/<?= htmlspecialchars($slug, ENT_QUOTES, 'UTF-8') ?>" class="block">
|
||||||
|
<div class="aspect-thumbnail overflow-hidden">
|
||||||
|
<picture>
|
||||||
|
<source
|
||||||
|
srcset="/assets/img/projects/<?= htmlspecialchars($thumbnail, ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
type="image/webp"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/assets/img/projects/<?= htmlspecialchars(str_replace('.webp', '.jpg', $thumbnail), ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
alt="Aperçu du projet <?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
width="400"
|
||||||
|
height="225"
|
||||||
|
loading="lazy"
|
||||||
|
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
|
||||||
|
onerror="this.onerror=null;this.src='/assets/img/projects/default-project.svg';"
|
||||||
|
>
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="text-lg font-semibold text-text-primary mb-3 group-hover:text-primary transition-colors">
|
||||||
|
<?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<?php foreach (array_slice($technologies, 0, $maxTechs) as $tech): ?>
|
||||||
|
<span class="badge"><?= htmlspecialchars($tech, ENT_QUOTES, 'UTF-8') ?></span>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php if (count($technologies) > $maxTechs): ?>
|
||||||
|
<span class="badge badge-muted">+<?= count($technologies) - $maxTechs ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
23
tests/projects-list.test.php
Normal file
23
tests/projects-list.test.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../includes/functions.php';
|
||||||
|
|
||||||
|
function assertTrue($cond, $msg) {
|
||||||
|
if (!$cond) {
|
||||||
|
fwrite(STDERR, $msg . PHP_EOL);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$projects = getProjectsByCategory('vedette');
|
||||||
|
assertTrue(is_array($projects), 'featured projects not array');
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
include __DIR__ . '/../templates/project-card.php';
|
||||||
|
}
|
||||||
|
$html = ob_get_clean();
|
||||||
|
|
||||||
|
assertTrue(strpos($html, 'badge') !== false, 'missing badges');
|
||||||
|
assertTrue(strpos($html, 'loading="lazy"') !== false, 'missing lazy loading');
|
||||||
|
|
||||||
|
fwrite(STDOUT, "OK\n");
|
||||||
@@ -11,4 +11,5 @@ $here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|||||||
& (Join-Path $here 'router.test.ps1')
|
& (Join-Path $here 'router.test.ps1')
|
||||||
php (Join-Path $here 'projects.test.php')
|
php (Join-Path $here 'projects.test.php')
|
||||||
php (Join-Path $here 'router.test.php')
|
php (Join-Path $here 'router.test.php')
|
||||||
|
php (Join-Path $here 'projects-list.test.php')
|
||||||
'OK'
|
'OK'
|
||||||
Reference in New Issue
Block a user