All guides
IntermediateSelf-pacedFree GuideSection 20/95

Mini-projet — Application web complète

Practise development foundations: programming, web, APIs, data and AI tools. Work through sections and exercises to build your first projects.

The full lessons for this course are currently available in French. Use the section menu to navigate the French course.

💡

Free content, available without an account. Tools and models change: use the current model guide to check their versions. Models and sources

Section 11.1.20 : Mini-projet — Application web complète

🎯 Objectif pédagogique

Synthétiser toutes les compétences de la Semaine 1 en créant une application web fonctionnelle de bout en bout : HTML structuré, CSS stylisé, JavaScript interactif, appel API, et déploiement. C'est le moment de preuve que Marc est prêt pour la Semaine 2.


Le projet : "Job Tracker" — Tableau de bord de candidatures

Marc est en reconversion. Il a besoin de suivre ses candidatures. Plutôt que d'utiliser un fichier Excel (son réflexe d'ex-analyste), il va construire son propre outil — en appliquant tout ce qu'il a appris cette semaine.

Architecture du projet

job-tracker/
├── index.html          ← Structure (HTML)
├── style.css           ← Design (CSS)
├── app.js              ← Logique (JavaScript)
├── README.md           ← Documentation
├── .gitignore          ← Fichiers à ignorer
└── .env                ← Clé API (si IA intégrée)

Étape 1 : HTML — Structure de la page

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Job Tracker — Marc Dupont</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <header>
    <h1>🎯 Job Tracker</h1>
    <p>Suivi de candidatures — Bootcamp Semaine 1</p>
  </header>

  <main>
    <section id="add-job">
      <h2>Ajouter une candidature</h2>
      <form id="job-form">
        <input type="text" id="company" placeholder="Entreprise" required>
        <input type="text" id="position" placeholder="Poste" required>
        <select id="status">
          <option value="applied">📨 Candidature envoyée</option>
          <option value="interview">🎤 Entretien planifié</option>
          <option value="offer">🎉 Offre reçue</option>
          <option value="rejected">❌ Refusée</option>
        </select>
        <input type="date" id="date">
        <button type="submit">Ajouter</button>
      </form>
    </section>

    <section id="stats">
      <div class="stat-card" id="total-count">
        <span class="stat-number">0</span>
        <span class="stat-label">Total</span>
      </div>
      <div class="stat-card" id="interview-count">
        <span class="stat-number">0</span>
        <span class="stat-label">Entretiens</span>
      </div>
      <div class="stat-card" id="offer-count">
        <span class="stat-number">0</span>
        <span class="stat-label">Offres</span>
      </div>
    </section>

    <section id="jobs-list">
      <h2>Candidatures</h2>
      <div id="jobs-container"></div>
    </section>
  </main>

  <footer>
    <p>Construit par Marc Dupont — Semaine 1 du Bootcamp Tech & IA</p>
  </footer>

  <script src="app.js"></script>
</body>
</html>

Étape 2 : CSS — Design professionnel

* \{ margin: 0; padding: 0; box-sizing: border-box; \}

body \{
  font-family: 'Inter', -apple-system, sans-serif;
  background: #F5F3EF;
  color: #333;
  line-height: 1.6;
\}

header \{
  background: linear-gradient(135deg, #0891B2, #06b6d4);
  color: white;
  padding: 2rem;
  text-align: center;
\}

main \{ max-width: 900px; margin: 2rem auto; padding: 0 1rem; \}

/* Formulaire */
#job-form \{
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  background: white;
  padding: 1.5rem;
  border-radius: 12px;
  border: 1px solid #E2DFD8;
  margin-bottom: 2rem;
\}

#job-form input, #job-form select \{
  flex: 1 1 200px;
  padding: 0.75rem;
  border: 1px solid #E2DFD8;
  border-radius: 8px;
  font-size: 0.95rem;
\}

#job-form button \{
  padding: 0.75rem 2rem;
  background: #0891B2;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 600;
\}

#job-form button:hover \{ background: #0e7490; \}

/* Stats */
#stats \{
  display: flex;
  gap: 1rem;
  margin-bottom: 2rem;
\}

.stat-card \{
  flex: 1;
  background: white;
  padding: 1.5rem;
  border-radius: 12px;
  text-align: center;
  border: 1px solid #E2DFD8;
\}

.stat-number \{
  display: block;
  font-size: 2rem;
  font-weight: 700;
  color: #0891B2;
\}

.stat-label \{ color: #666; font-size: 0.85rem; \}

/* Job cards */
.job-card \{
  background: white;
  padding: 1.25rem;
  border-radius: 8px;
  border: 1px solid #E2DFD8;
  margin-bottom: 0.75rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
\}

.job-card .company \{ font-weight: 600; \}
.job-card .position \{ color: #666; \}

.badge \{
  padding: 0.25rem 0.75rem;
  border-radius: 20px;
  font-size: 0.8rem;
  font-weight: 500;
\}

.badge-applied \{ background: #dbeafe; color: #1d4ed8; \}
.badge-interview \{ background: #fef3c7; color: #92400e; \}
.badge-offer \{ background: #d1fae5; color: #065f46; \}
.badge-rejected \{ background: #fee2e2; color: #991b1b; \}

.delete-btn \{
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1.2rem;
  opacity: 0.5;
\}

.delete-btn:hover \{ opacity: 1; \}

@media (max-width: 640px) \{
  #stats \{ flex-direction: column; \}
  #job-form \{ flex-direction: column; \}
\}

footer \{
  text-align: center;
  padding: 2rem;
  color: #999;
  margin-top: 3rem;
\}

Étape 3 : JavaScript — Logique et interactivité

// État de l'application
let jobs = JSON.parse(localStorage.getItem("jobs")) || [];

// Éléments DOM
const form = document.querySelector("#job-form");
const container = document.querySelector("#jobs-container");

// Soumission du formulaire
form.addEventListener("submit", (e) => \{
  e.preventDefault();
  
  const job = \{
    id: Date.now(),
    company: document.querySelector("#company").value.trim(),
    position: document.querySelector("#position").value.trim(),
    status: document.querySelector("#status").value,
    date: document.querySelector("#date").value || new Date().toISOString().split("T")[0]
  \};
  
  jobs.push(job);
  save();
  render();
  form.reset();
\});

// Sauvegarder dans localStorage
function save() \{
  localStorage.setItem("jobs", JSON.stringify(jobs));
\}

// Supprimer une candidature
function deleteJob(id) \{
  jobs = jobs.filter(j => j.id !== id);
  save();
  render();
\}

// Afficher les stats
function updateStats() \{
  document.querySelector("#total-count .stat-number").textContent = jobs.length;
  document.querySelector("#interview-count .stat-number").textContent = 
    jobs.filter(j => j.status === "interview").length;
  document.querySelector("#offer-count .stat-number").textContent = 
    jobs.filter(j => j.status === "offer").length;
\}

// Rendu des cartes
function render() \{
  const statusLabels = \{
    applied: "📨 Envoyée",
    interview: "🎤 Entretien",
    offer: "🎉 Offre",
    rejected: "❌ Refusée"
  \};
  
  container.innerHTML = jobs.map(job => \`
    <div class="job-card">
      <div>
        <span class="company">\$\{job.company\}</span>
        <span class="position"> — \$\{job.position\}</span>
        <small style="color:#999; margin-left:0.5rem">\$\{job.date\}</small>
      </div>
      <div style="display:flex; align-items:center; gap:0.5rem">
        <span class="badge badge-\$\{job.status\}">\$\{statusLabels[job.status]\}</span>
        <button class="delete-btn" onclick="deleteJob(\$\{job.id\})">🗑️</button>
      </div>
    </div>
  \`).join("");
  
  updateStats();
\}

// Rendu initial
render();

Étape 4 : Git + Déploiement

# Initialiser et committer
cd job-tracker
git init
echo "node_modules/\n.env" > .gitignore
git add .
git commit -m "feat: Job Tracker — projet Semaine 1"

# Pousser sur GitHub
git remote add origin https://github.com/votre-username/job-tracker.git
git push -u origin main

# Déployer sur Vercel
# → vercel.com → Import projet → Deploy
Loading diagram…

Fonctionnalités bonus (optionnel)

  1. →Filtrage par statut (ajouter des boutons radio)
  2. →Export CSV des candidatures
  3. →Mode sombre avec toggle
  4. →IA : "Analysez mes candidatures" via l'API OpenAI/Groq

🏋️ Récapitulatif Semaine 1

CompétenceSectionStatut
Architecture ordinateur11.1.1✅
Internet & HTTP11.1.2✅
Terminal11.1.3✅
Environnement dev (VS Code)11.1.4✅
Git11.1.5✅
GitHub11.1.6✅
Figma11.1.7✅
HTML11.1.8-11.1.9✅
CSS11.1.10-11.1.11✅
JavaScript11.1.12-11.1.14✅
Python11.1.15-11.1.16✅
APIs11.1.17-11.1.18✅
Déploiement11.1.19✅
Mini-projet11.1.20✅

Marc referme son laptop avec un sourire. Il a un portfolio en ligne, un Job Tracker fonctionnel, et les bases pour la suite. En une semaine, il est passé de "je ne sais pas ce qu'est un terminal" à "j'ai déployé une application web". La Semaine 2 s'attaque aux données — pandas, SQL, et visualisation. L'analyste financier en lui a hâte.

Quick Check

Quel est l'avantage d'utiliser localStorage dans le Job Tracker ?

Quick Check

Pourquoi utiliser JSON.stringify() et JSON.parse() avec localStorage ?

Quick Check

L'application Job Tracker utilise toutes les technologies SAUF :

Your progress

Progress is self-reported and stored in your browser. The final completion record is not a qualification or an independent assessment.

View progress and completion record

Learn AI, from prompts to agents

15 free guidesQuizzes, diagrams and workshopsNo account needed

One practical AI workflow in your inbox

Methods, resources and new exercises. Free, unsubscribe at any time.