Czego się nauczysz?
- Walidacji URL-i (linków do GitHub/GitLab)
- Pracy z tablicami (lista technologii)
- Implementacji filtrowania i wyszukiwania
- Tworzenia katalogów i portfoliów
- Wyświetlania kart projektów
This content is not available in your language yet.
Stworzysz Rejestr projektów uczniowskich - katalog prezentujący projekty uczniów z opisami, użytymi technologiami i linkami do repozytoriów. System pozwala dodawać nowe projekty, przeglądać istniejące i filtrować po technologiach.
Czego się nauczysz?
W prawdziwej pracy...
Katalogi i portfolia są podstawą prezentacji prac - od repozytoriów open source, przez portfolio freelancerów, po wewnętrzne katalogi projektów firmowych. Umiejętność projektowania systemów katalogowych z tagami, filtrowaniem i walidacją URL jest fundamentem dla każdego programisty aplikacji webowych.
Formularz dodawania projektu Użytkownik podaje tytuł, opis, listę technologii i link do repozytorium.
Walidacja danych System sprawdza poprawność wprowadzonych danych - czy tytuł nie jest pusty, czy URL jest prawidłowy.
Katalog projektów Wszystkie projekty są wyświetlane jako karty z miniaturami informacji.
Szczegóły projektu Użytkownik może zobaczyć pełny opis projektu i przejść do repozytorium.
Przykładowa struktura pliku JSON:
{ "projects": [ { "id": 1, "title": "System rezerwacji sal", "description": "Aplikacja webowa do rezerwowania sal konferencyjnych z kalendarzem i powiadomieniami email.", "technologies": ["PHP", "MySQL", "JavaScript", "Bootstrap"], "url": "https://github.com/jankowalski/room-booking", "author": "Jan Kowalski", "class": "4TI", "status": "completed", "created_at": "2026-02-10 14:30:00" }, { "id": 2, "title": "Aplikacja pogodowa", "description": "Mobilna aplikacja do sprawdzania pogody z wykorzystaniem API OpenWeatherMap.", "technologies": ["React Native", "JavaScript", "REST API"], "url": "https://github.com/annanowak/weather-app", "author": "Anna Nowak", "class": "3TI", "status": "in_progress", "created_at": "2026-02-08 10:15:00" }, { "id": 3, "title": "Sklep internetowy", "description": "E-commerce z koszykiem, płatnościami i panelem administracyjnym.", "technologies": ["PHP", "MySQL", "CSS", "PayU API"], "url": "https://gitlab.com/piotrw/shop", "author": "Piotr Wiśniewski", "class": "4TI", "status": "completed", "created_at": "2026-01-20 16:45:00" } ]}Wymagane funkcje:
Przykładowy scenariusz:
Ocena: 3.0Użytkownik wpisuje tytuł “Moja aplikacja”, opis, zaznacza technologie PHP i JavaScript, podaje link do GitHub. Po zapisie widzi swój projekt w katalogu jako kartę z listą technologii.
Wszystko z wariantu A, plus:
FILTER_VALIDATE_URL)htmlspecialchars() przy wyświetlaniuPrzykładowy scenariusz:
Ocena: 4.0-5.0Użytkownik klika “PHP” w filtrach i widzi tylko projekty używające PHP. Może sortować po dacie, żeby zobaczyć najnowsze. Każdy projekt ma ikonki technologii i link do repozytorium.
Wszystko z wariantu B, plus:
Przykładowy scenariusz:
Ocena: 5.0-6.0Admin widzi wszystkie projekty i może edytować błędne dane. Użytkownik wyszukuje “rezerwacja” i widzi pasujące projekty. Projekty “w trakcie” mają specjalną etykietę i są wyróżnione.
Walidacja projektu:
$title = trim($_POST['title'] ?? '');$description = trim($_POST['description'] ?? '');$technologies = $_POST['technologies'] ?? [];$url = trim($_POST['url'] ?? '');
if (empty($title)) { $errors[] = "Tytuł projektu jest wymagany";}
if (strlen($title) > 100) { $errors[] = "Tytuł może mieć maksymalnie 100 znaków";}
if (strlen($description) < 20) { $errors[] = "Opis powinien mieć co najmniej 20 znaków";}
if (strlen($description) > 1000) { $errors[] = "Opis może mieć maksymalnie 1000 znaków";}
if (empty($technologies)) { $errors[] = "Wybierz co najmniej jedną technologię";}
// Walidacja technologii z dozwolonej listy$validTech = getAvailableTechnologies();foreach ($technologies as $tech) { if (!in_array($tech, $validTech)) { $errors[] = "Nieprawidłowa technologia: " . htmlspecialchars($tech); }}
if (!empty($url) && !filter_var($url, FILTER_VALIDATE_URL)) { $errors[] = "Podaj prawidłowy adres URL";}
// Sprawdź czy URL to GitHub/GitLab (opcjonalnie)if (!empty($url) && !preg_match('/^https?:\/\/(github\.com|gitlab\.com|bitbucket\.org)/', $url)) { $warnings[] = "Zalecamy linki do GitHub, GitLab lub Bitbucket";}Dostępne technologie:
function getAvailableTechnologies(): array { return [ 'PHP', 'JavaScript', 'TypeScript', 'Python', 'Java', 'C#', 'HTML', 'CSS', 'SCSS', 'Bootstrap', 'Tailwind', 'React', 'Vue', 'Angular', 'Node.js', 'Express', 'MySQL', 'PostgreSQL', 'MongoDB', 'SQLite', 'REST API', 'GraphQL', 'Docker', 'Git', ];}
function getTechnologyInfo(string $tech): array { $techInfo = [ 'PHP' => ['color' => '#777BB4', 'icon' => '🐘'], 'JavaScript' => ['color' => '#F7DF1E', 'icon' => '⚡'], 'TypeScript' => ['color' => '#3178C6', 'icon' => '📘'], 'Python' => ['color' => '#3776AB', 'icon' => '🐍'], 'React' => ['color' => '#61DAFB', 'icon' => '⚛️'], 'Vue' => ['color' => '#4FC08D', 'icon' => '💚'], 'MySQL' => ['color' => '#4479A1', 'icon' => '🗄️'], 'Node.js' => ['color' => '#339933', 'icon' => '🟢'], ];
return $techInfo[$tech] ?? ['color' => '#6c757d', 'icon' => '🔧'];}Dodawanie projektu:
function addProject(array &$projects, array $data): int { $newId = empty($projects) ? 1 : max(array_column($projects, 'id')) + 1;
$newProject = [ 'id' => $newId, 'title' => $data['title'], 'description' => $data['description'], 'technologies' => $data['technologies'], 'url' => $data['url'], 'author' => $data['author'] ?? 'Anonim', 'class' => $data['class'] ?? '', 'status' => $data['status'] ?? 'in_progress', 'created_at' => date('Y-m-d H:i:s'), ];
$projects[] = $newProject; return $newId;}Filtrowanie po technologii:
function filterByTechnology(array $projects, string $technology): array { if (empty($technology)) { return $projects; }
return array_filter($projects, function($project) use ($technology) { return in_array($technology, $project['technologies']); });}
function filterByMultipleTechnologies(array $projects, array $technologies): array { if (empty($technologies)) { return $projects; }
return array_filter($projects, function($project) use ($technologies) { // Projekt musi zawierać wszystkie wybrane technologie return count(array_intersect($technologies, $project['technologies'])) === count($technologies); });}
function filterByAnyTechnology(array $projects, array $technologies): array { if (empty($technologies)) { return $projects; }
return array_filter($projects, function($project) use ($technologies) { // Projekt musi zawierać co najmniej jedną z wybranych technologii return !empty(array_intersect($technologies, $project['technologies'])); });}Sortowanie projektów:
function sortProjects(array $projects, string $sortBy = 'created_at', string $order = 'desc'): array { usort($projects, function($a, $b) use ($sortBy, $order) { if ($sortBy === 'title') { $result = strcasecmp($a['title'], $b['title']); } else { $result = strcmp($a[$sortBy] ?? '', $b[$sortBy] ?? ''); }
return $order === 'desc' ? -$result : $result; });
return $projects;}Wyszukiwanie pełnotekstowe:
function searchProjects(array $projects, string $query): array { if (empty($query)) { return $projects; }
$query = mb_strtolower($query);
return array_filter($projects, function($project) use ($query) { $title = mb_strtolower($project['title']); $description = mb_strtolower($project['description']); $technologies = mb_strtolower(implode(' ', $project['technologies'])); $author = mb_strtolower($project['author'] ?? '');
return strpos($title, $query) !== false || strpos($description, $query) !== false || strpos($technologies, $query) !== false || strpos($author, $query) !== false; });}Wyświetlanie karty projektu:
function renderProjectCard(array $project): string { $title = htmlspecialchars($project['title']); $description = htmlspecialchars(mb_substr($project['description'], 0, 150) . '...'); $author = htmlspecialchars($project['author'] ?? 'Anonim'); $url = htmlspecialchars($project['url'] ?? '#'); $status = $project['status'] === 'completed' ? 'Ukończony' : 'W trakcie'; $statusClass = $project['status'] === 'completed' ? 'completed' : 'in-progress';
$techBadges = ''; foreach ($project['technologies'] as $tech) { $info = getTechnologyInfo($tech); $techBadges .= sprintf( '<span class="tech-badge" style="background: %s">%s %s</span>', $info['color'], $info['icon'], htmlspecialchars($tech) ); }
return <<<HTML <div class="project-card"> <div class="card-header"> <h3 class="project-title">{$title}</h3> <span class="status-badge {$statusClass}">{$status}</span> </div> <p class="project-description">{$description}</p> <div class="technologies">{$techBadges}</div> <div class="card-footer"> <span class="author">👤 {$author}</span> <a href="{$url}" target="_blank" class="repo-link">📦 Repozytorium</a> </div> </div> HTML;}JavaScript - filtrowanie na żywo:
function setupTechFilter() { const checkboxes = document.querySelectorAll('.tech-filter'); const projects = document.querySelectorAll('.project-card');
checkboxes.forEach(checkbox => { checkbox.addEventListener('change', filterProjects); });
function filterProjects() { const selected = Array.from(checkboxes) .filter(cb => cb.checked) .map(cb => cb.value);
projects.forEach(project => { const projectTech = project.dataset.technologies.split(',');
if (selected.length === 0) { project.style.display = 'block'; } else { const hasMatch = selected.some(tech => projectTech.includes(tech)); project.style.display = hasMatch ? 'block' : 'none'; } }); }}
// Wyszukiwarkafunction setupSearch() { const searchInput = document.getElementById('search'); const projects = document.querySelectorAll('.project-card');
searchInput.addEventListener('input', function() { const query = this.value.toLowerCase();
projects.forEach(project => { const title = project.querySelector('.project-title').textContent.toLowerCase(); const desc = project.querySelector('.project-description').textContent.toLowerCase();
project.style.display = (title.includes(query) || desc.includes(query)) ? 'block' : 'none'; }); });}Wykorzystaj lekcje!
Cotygodniowe spotkania podczas lekcji to idealny moment, by:
Pracuj iteracyjnie - lepiej mieć działający wariant A niż niedokończony C!