Przejdź do głównej zawartości

Generator CV w HTML

Stworzysz Generator CV w HTML - aplikację, która na podstawie wypełnionego formularza generuje profesjonalne CV w formacie HTML. System pozwala wprowadzić dane osobowe, umiejętności, doświadczenie i edukację, a następnie wygenerować gotowy dokument.

Czego się nauczysz?

  • Generowania dokumentów HTML dynamicznie
  • Walidacji formularzy z wieloma polami
  • Pracy z szablonami i stylami CSS
  • Zapisywania i pobierania plików
  • Tworzenia systemu historii wersji

W prawdziwej pracy...

Generatory dokumentów są podstawą wielu aplikacji biznesowych - od faktur i raportów, przez certyfikaty, po CV i listy motywacyjne. Umiejętność projektowania systemów generujących dokumenty HTML z dynamicznymi danymi jest fundamentem dla każdego programisty aplikacji webowych i narzędzi biurowych.

  1. Formularz danych CV Użytkownik wprowadza dane osobowe, kontakt, umiejętności, doświadczenie zawodowe i edukację.

  2. Walidacja danych System sprawdza poprawność wprowadzonych danych - wymagane pola, format email, poprawność dat.

  3. Podgląd CV Użytkownik widzi wygenerowane CV w formacie HTML przed zapisem.

  4. Zapis/Pobranie Użytkownik może zapisać CV jako plik HTML lub przechować w historii.

Przykładowa struktura pliku JSON (historia CV):

{
"cvHistory": [
{
"id": 1,
"user_id": "user123",
"template": "classic",
"data": {
"personal": {
"name": "Jan Kowalski",
"title": "Frontend Developer",
"email": "jan.kowalski@email.pl",
"phone": "+48 123 456 789",
"address": "Warszawa, Polska",
"linkedin": "linkedin.com/in/jankowalski",
"github": "github.com/jankowalski"
},
"summary": "Programista z 3-letnim doświadczeniem w tworzeniu aplikacji webowych...",
"skills": ["JavaScript", "React", "CSS", "HTML", "Git"],
"experience": [
{
"company": "Tech Corp",
"position": "Junior Frontend Developer",
"from": "2024-01",
"to": "2026-01",
"description": "Tworzenie interfejsów użytkownika w React."
}
],
"education": [
{
"school": "Politechnika Warszawska",
"degree": "Inżynier Informatyki",
"from": "2020",
"to": "2024"
}
],
"languages": [
{"language": "Polski", "level": "ojczysty"},
{"language": "Angielski", "level": "B2"}
]
},
"created_at": "2026-02-10 14:30:00"
}
]
}
  • Foldergenerator-cv/
    • index.php (formularz CV)
    • preview.php (podgląd CV)
    • download.php (pobieranie HTML)
    • historia.php (historia CV - wariant B/C)
    • Folderincludes/
      • config.php
      • functions.php
      • validation.php
      • templates.php
      • auth.php (wariant C)
    • Foldertemplates/
      • classic.php
      • modern.php (wariant C)
      • minimal.php (wariant C)
    • Folderdata/
      • cv_history.json
      • users.json (wariant C)
    • Foldercss/
      • style.css
      • cv-classic.css
      • cv-modern.css (wariant C)
    • Folderjs/
      • validation.js
      • form.js

Wymagane funkcje:

  • Formularz: dane osobowe, umiejętności, doświadczenie, edukacja
  • Walidacja PHP (wymagane pola, format email)
  • Min. 1 walidacja JavaScript
  • Generacja CV w HTML (podgląd)
  • Komunikaty błędów i sukcesu

Przykładowy scenariusz:

Użytkownik wypełnia formularz: imię “Jan Kowalski”, email “jan@email.pl”, dodaje umiejętności i doświadczenie. Klika “Generuj” i widzi profesjonalne CV w HTML gotowe do wydruku.

Ocena: 3.0

Walidacja danych CV:

$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$phone = trim($_POST['phone'] ?? '');
$skills = $_POST['skills'] ?? [];
if (empty($name)) {
$errors[] = "Imię i nazwisko jest wymagane";
}
if (strlen($name) > 100) {
$errors[] = "Imię i nazwisko może mieć maksymalnie 100 znaków";
}
if (empty($email)) {
$errors[] = "Email jest wymagany";
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Podaj poprawny adres email";
}
if (!empty($phone) && !preg_match('/^[\d\s\+\-\(\)]{9,20}$/', $phone)) {
$errors[] = "Nieprawidłowy format numeru telefonu";
}
if (empty($skills)) {
$errors[] = "Dodaj przynajmniej jedną umiejętność";
}

Generowanie CV w HTML:

function generateCV(array $data, string $template = 'classic'): string {
ob_start();
// Escapowanie danych
$name = htmlspecialchars($data['personal']['name'] ?? '', ENT_QUOTES, 'UTF-8');
$title = htmlspecialchars($data['personal']['title'] ?? '', ENT_QUOTES, 'UTF-8');
$email = htmlspecialchars($data['personal']['email'] ?? '', ENT_QUOTES, 'UTF-8');
$phone = htmlspecialchars($data['personal']['phone'] ?? '', ENT_QUOTES, 'UTF-8');
$summary = nl2br(htmlspecialchars($data['summary'] ?? '', ENT_QUOTES, 'UTF-8'));
// Załaduj szablon
include "templates/{$template}.php";
return ob_get_clean();
}

Szablon CV (classic.php):

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>CV - <?= $name ?></title>
<style>
body { font-family: 'Georgia', serif; max-width: 800px; margin: 0 auto; padding: 40px; }
.header { text-align: center; border-bottom: 2px solid #333; padding-bottom: 20px; margin-bottom: 30px; }
.name { font-size: 2em; margin: 0; }
.title { color: #666; font-style: italic; }
.contact { margin: 15px 0; color: #444; }
.section { margin-bottom: 25px; }
.section-title { font-size: 1.3em; border-bottom: 1px solid #ccc; padding-bottom: 5px; margin-bottom: 15px; }
.skills { display: flex; flex-wrap: wrap; gap: 10px; }
.skill { background: #f0f0f0; padding: 5px 15px; border-radius: 20px; }
.experience-item, .education-item { margin-bottom: 15px; }
.position { font-weight: bold; }
.company, .school { color: #666; }
.dates { color: #888; font-size: 0.9em; }
</style>
</head>
<body>
<div class="header">
<h1 class="name"><?= $name ?></h1>
<?php if ($title): ?><p class="title"><?= $title ?></p><?php endif; ?>
<div class="contact">
<?= $email ?> | <?= $phone ?>
</div>
</div>
<?php if ($summary): ?>
<div class="section">
<h2 class="section-title">O mnie</h2>
<p><?= $summary ?></p>
</div>
<?php endif; ?>
<div class="section">
<h2 class="section-title">Umiejętności</h2>
<div class="skills">
<?php foreach ($data['skills'] ?? [] as $skill): ?>
<span class="skill"><?= htmlspecialchars($skill) ?></span>
<?php endforeach; ?>
</div>
</div>
<div class="section">
<h2 class="section-title">Doświadczenie</h2>
<?php foreach ($data['experience'] ?? [] as $exp): ?>
<div class="experience-item">
<div class="position"><?= htmlspecialchars($exp['position']) ?></div>
<div class="company"><?= htmlspecialchars($exp['company']) ?></div>
<div class="dates"><?= htmlspecialchars($exp['from']) ?> - <?= htmlspecialchars($exp['to'] ?: 'obecnie') ?></div>
<p><?= nl2br(htmlspecialchars($exp['description'] ?? '')) ?></p>
</div>
<?php endforeach; ?>
</div>
<div class="section">
<h2 class="section-title">Edukacja</h2>
<?php foreach ($data['education'] ?? [] as $edu): ?>
<div class="education-item">
<div class="position"><?= htmlspecialchars($edu['degree']) ?></div>
<div class="school"><?= htmlspecialchars($edu['school']) ?></div>
<div class="dates"><?= htmlspecialchars($edu['from']) ?> - <?= htmlspecialchars($edu['to']) ?></div>
</div>
<?php endforeach; ?>
</div>
</body>
</html>

Pobieranie pliku HTML:

function downloadCV(string $html, string $filename): void {
header('Content-Type: text/html; charset=utf-8');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . strlen($html));
echo $html;
exit;
}
// Użycie:
$html = generateCV($cvData, 'classic');
$filename = 'cv-' . sanitizeFilename($cvData['personal']['name']) . '.html';
downloadCV($html, $filename);
function sanitizeFilename(string $name): string {
$name = strtolower($name);
$name = preg_replace('/[^a-z0-9]+/', '-', $name);
return trim($name, '-');
}

Zapisywanie do historii:

function saveCVToHistory(array &$history, array $cvData, string $template): int {
$newId = empty($history) ? 1 : max(array_column($history, 'id')) + 1;
$entry = [
'id' => $newId,
'user_id' => $_SESSION['user_id'] ?? 'guest',
'template' => $template,
'data' => $cvData,
'created_at' => date('Y-m-d H:i:s'),
];
$history[] = $entry;
return $newId;
}
function getCVHistory(array $history, string $userId): array {
return array_filter($history, fn($cv) => $cv['user_id'] === $userId);
}

Dostępne szablony:

function getTemplates(): array {
return [
'classic' => [
'name' => 'Klasyczny',
'description' => 'Elegancki, tradycyjny styl',
'css' => 'cv-classic.css',
],
'modern' => [
'name' => 'Nowoczesny',
'description' => 'Minimalistyczny, świeży design',
'css' => 'cv-modern.css',
],
'minimal' => [
'name' => 'Minimalny',
'description' => 'Prosty, czytelny układ',
'css' => 'cv-minimal.css',
],
];
}

JavaScript - dynamiczne dodawanie sekcji:

function addExperienceEntry() {
const container = document.getElementById('experience-container');
const index = container.querySelectorAll('.experience-entry').length;
const html = `
<div class="experience-entry" data-index="${index}">
<input type="text" name="experience[${index}][company]" placeholder="Firma" required>
<input type="text" name="experience[${index}][position]" placeholder="Stanowisko" required>
<input type="month" name="experience[${index}][from]" required>
<input type="month" name="experience[${index}][to]">
<textarea name="experience[${index}][description]" placeholder="Opis obowiązków"></textarea>
<button type="button" onclick="removeEntry(this)">Usuń</button>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
}
function removeEntry(button) {
button.closest('.experience-entry, .education-entry').remove();
}

Wykorzystaj lekcje!

Cotygodniowe spotkania podczas lekcji to idealny moment, by:

  • Pokazać postępy - nawet małe kroki się liczą
  • Wyjaśnić wątpliwości - pytaj, nie zgaduj
  • Skonsultować rozwiązania - feedback pomoże Ci się rozwijać

Pracuj iteracyjnie - lepiej mieć działający wariant A niż niedokończony C!