📦 Feature: Structure données JSON projets + Navigation rapide
- Ajout data/projects.json avec 3 projets de test - Fonctions PHP: loadJsonData, getProjects, getProjectsByCategory, getProjectBySlug, getAllTechnologies - Gestion erreurs fichier manquant/JSON invalide - Section navigation rapide sur page d'accueil (Projets, Compétences, Me Découvrir) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
55
data/projects.json
Normal file
55
data/projects.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Site E-commerce XYZ",
|
||||||
|
"slug": "ecommerce-xyz",
|
||||||
|
"category": "vedette",
|
||||||
|
"thumbnail": "ecommerce-xyz-thumb.webp",
|
||||||
|
"url": "https://example.com",
|
||||||
|
"github": "https://github.com/user/project",
|
||||||
|
"technologies": ["PHP", "JavaScript", "Tailwind CSS", "MySQL"],
|
||||||
|
"context": "Client souhaitant moderniser sa boutique en ligne pour améliorer l'expérience utilisateur et augmenter les conversions.",
|
||||||
|
"solution": "Développement d'une solution e-commerce sur mesure avec panier persistant, paiement sécurisé Stripe, et interface d'administration.",
|
||||||
|
"teamwork": "Projet réalisé en collaboration avec un designer UI/UX. J'ai pris en charge l'intégration et le développement backend.",
|
||||||
|
"duration": "3 mois",
|
||||||
|
"screenshots": [
|
||||||
|
"ecommerce-xyz-screen-1.webp",
|
||||||
|
"ecommerce-xyz-screen-2.webp",
|
||||||
|
"ecommerce-xyz-screen-3.webp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Application de Gestion",
|
||||||
|
"slug": "app-gestion",
|
||||||
|
"category": "vedette",
|
||||||
|
"thumbnail": "app-gestion-thumb.webp",
|
||||||
|
"url": null,
|
||||||
|
"github": "https://github.com/user/app-gestion",
|
||||||
|
"technologies": ["React", "Node.js", "PostgreSQL", "Docker"],
|
||||||
|
"context": "Startup ayant besoin d'un outil interne pour gérer ses ressources et planifier ses projets.",
|
||||||
|
"solution": "Application web full-stack avec authentification, gestion des rôles, tableaux de bord et exports PDF.",
|
||||||
|
"teamwork": null,
|
||||||
|
"duration": "4 mois",
|
||||||
|
"screenshots": [
|
||||||
|
"app-gestion-screen-1.webp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Site Vitrine Restaurant",
|
||||||
|
"slug": "restaurant-vitrine",
|
||||||
|
"category": "secondaire",
|
||||||
|
"thumbnail": "restaurant-thumb.webp",
|
||||||
|
"url": "https://restaurant-example.com",
|
||||||
|
"github": null,
|
||||||
|
"technologies": ["HTML", "CSS", "JavaScript"],
|
||||||
|
"context": "Restaurant local souhaitant une présence en ligne simple.",
|
||||||
|
"solution": null,
|
||||||
|
"teamwork": null,
|
||||||
|
"duration": "2 semaines",
|
||||||
|
"screenshots": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -13,3 +13,82 @@ function include_template(string $name, array $data = []): void
|
|||||||
extract($data);
|
extract($data);
|
||||||
include __DIR__ . "/../templates/{$name}.php";
|
include __DIR__ . "/../templates/{$name}.php";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Charge et parse un fichier JSON
|
||||||
|
* @param string $filename Nom du fichier dans le dossier data/
|
||||||
|
* @return array Données décodées ou tableau vide en cas d'erreur
|
||||||
|
*/
|
||||||
|
function loadJsonData(string $filename): array
|
||||||
|
{
|
||||||
|
$path = __DIR__ . "/../data/{$filename}";
|
||||||
|
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
error_log("JSON file not found: {$filename}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = file_get_contents($path);
|
||||||
|
$data = json_decode($content, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
error_log("JSON parse error in {$filename}: " . json_last_error_msg());
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère tous les projets
|
||||||
|
* @return array Liste des projets
|
||||||
|
*/
|
||||||
|
function getProjects(): array
|
||||||
|
{
|
||||||
|
$data = loadJsonData('projects.json');
|
||||||
|
return $data['projects'] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les projets par catégorie
|
||||||
|
* @param string $category Catégorie (vedette|secondaire)
|
||||||
|
* @return array Projets filtrés
|
||||||
|
*/
|
||||||
|
function getProjectsByCategory(string $category): array
|
||||||
|
{
|
||||||
|
return array_filter(getProjects(), fn($p) => $p['category'] === $category);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère un projet par son slug
|
||||||
|
* @param string $slug Slug du projet
|
||||||
|
* @return array|null Projet ou null si non trouvé
|
||||||
|
*/
|
||||||
|
function getProjectBySlug(string $slug): ?array
|
||||||
|
{
|
||||||
|
$projects = getProjects();
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
if ($project['slug'] === $slug) {
|
||||||
|
return $project;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les technologies uniques de tous les projets
|
||||||
|
* @return array Liste triée des technologies
|
||||||
|
*/
|
||||||
|
function getAllTechnologies(): array
|
||||||
|
{
|
||||||
|
$technologies = [];
|
||||||
|
foreach (getProjects() as $project) {
|
||||||
|
foreach ($project['technologies'] ?? [] as $tech) {
|
||||||
|
if (!in_array($tech, $technologies)) {
|
||||||
|
$technologies[] = $tech;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort($technologies);
|
||||||
|
return $technologies;
|
||||||
|
}
|
||||||
|
|||||||
59
index.php
59
index.php
@@ -49,6 +49,65 @@ include_template('navbar', compact('currentPage'));
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Section Navigation Rapide -->
|
||||||
|
<section class="section bg-surface">
|
||||||
|
<div class="container-content">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2 class="section-title">Explorez mon portfolio</h2>
|
||||||
|
<p class="section-subtitle">
|
||||||
|
Découvrez mes réalisations, compétences et parcours
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8">
|
||||||
|
<!-- Carte Projets -->
|
||||||
|
<a href="/projets" class="card-interactive group">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||||
|
<svg class="w-8 h-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-subheading mb-2 group-hover:text-primary transition-colors">Projets</h3>
|
||||||
|
<p class="text-text-secondary">
|
||||||
|
Découvrez mes réalisations web avec démonstrations et explications techniques.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Carte Compétences -->
|
||||||
|
<a href="/competences" class="card-interactive group">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||||
|
<svg class="w-8 h-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-subheading mb-2 group-hover:text-primary transition-colors">Compétences</h3>
|
||||||
|
<p class="text-text-secondary">
|
||||||
|
Technologies maîtrisées et outils utilisés, avec preuves à l'appui.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Carte Me Découvrir -->
|
||||||
|
<a href="/a-propos" class="card-interactive group">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||||
|
<svg class="w-8 h-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-subheading mb-2 group-hover:text-primary transition-colors">Me Découvrir</h3>
|
||||||
|
<p class="text-text-secondary">
|
||||||
|
Mon parcours, mes motivations et ce qui me passionne au-delà du code.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<?php include_template('footer'); ?>
|
<?php include_template('footer'); ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user