Czego się nauczysz?
- Projektowania systemu ticketowego (helpdesk)
- Pracy z priorytetami i statusami workflow
- Implementacji przepływu pracy (NEW → IN_PROGRESS → DONE)
- Filtrowania i sortowania zgłoszeń
- Tworzenia panelu operatora IT
This content is not available in your language yet.
Stworzysz System zgłoszeń serwisowych IT - aplikację typu helpdesk, gdzie użytkownicy zgłaszają usterki sprzętowe i programowe, a operatorzy IT zarządzają zgłoszeniami, zmieniają ich statusy i śledzą postęp realizacji.
Czego się nauczysz?
W prawdziwej pracy...
Systemy helpdesk są fundamentem wsparcia technicznego w każdej organizacji. Jira Service Management, Zendesk, ServiceNow, Freshdesk - wszystkie opierają się na tych samych zasadach. Umiejętność projektowania systemów z workflow, priorytetami i eskalacją jest niezbędna dla każdego programisty aplikacji enterprise.
Formularz zgłoszenia Użytkownik opisuje usterkę: temat, szczegółowy opis, lokalizację (np. sala 203) i wybiera priorytet.
Walidacja danych System sprawdza poprawność wprowadzonych danych - czy temat nie jest pusty, czy priorytet jest z dozwolonej listy.
Lista zgłoszeń Wszystkie zgłoszenia są wyświetlane z priorytetami, statusami i datami, posortowane według pilności.
Zarządzanie statusami Operator IT może zmieniać status zgłoszenia w miarę jego realizacji.
Przykładowa struktura pliku JSON:
{ "tickets": [ { "id": 1, "ticket_number": "IT-2026-0001", "title": "Nie działa drukarka w pokoju 203", "description": "Drukarka HP LaserJet nie drukuje. Świeci się czerwona kontrolka błędu. Problem występuje od wczoraj.", "category": "hardware", "location": "Sala 203", "priority": "high", "status": "in_progress", "reporter_name": "Jan Kowalski", "reporter_email": "jan.kowalski@szkola.pl", "assigned_to": "admin", "created_at": "2026-02-10 09:30:00", "updated_at": "2026-02-10 11:00:00", "resolved_at": null }, { "id": 2, "ticket_number": "IT-2026-0002", "title": "Prośba o instalację programu Excel", "description": "Potrzebuję Microsoft Excel na stanowisku w sekretariacie do raportów.", "category": "software", "location": "Sekretariat", "priority": "low", "status": "new", "reporter_name": "Anna Nowak", "reporter_email": "anna.nowak@szkola.pl", "assigned_to": null, "created_at": "2026-02-11 14:20:00", "updated_at": "2026-02-11 14:20:00", "resolved_at": null } ]}Wymagane funkcje:
Przykładowy scenariusz:
Ocena: 3.0Użytkownik wypełnia formularz: “Nie działa drukarka w pokoju 203”, opisuje problem, wybiera priorytet “wysoki”. Po zapisie widzi potwierdzenie i numer zgłoszenia IT-2026-0001. Zgłoszenie pojawia się na liście.
Wszystko z wariantu A, plus:
htmlspecialchars() przy wyświetlaniuPrzykładowy scenariusz:
Ocena: 4.0-5.0Operator IT loguje się i filtruje: “pokaż tylko wysokie priorytety”. Widzi 3 pilne zgłoszenia z czerwonym znacznikiem. Może posortować po dacie, żeby zobaczyć najstarsze najpierw.
Wszystko z wariantu B, plus:
Przykładowy scenariusz:
Ocena: 5.0-6.0Admin zmienia status zgłoszenia z “Nowe” na “W realizacji” - system zapisuje datę. Po naprawie oznacza jako “Zamknięte”. Raport pokazuje: “Otwarte: 5, W realizacji: 3, Zamknięte: 42. Średni czas realizacji: 4h”.
Generowanie numeru zgłoszenia:
function generateTicketNumber(array $tickets): string { $year = date('Y'); $prefix = "IT-$year-";
$maxSeq = 0; foreach ($tickets as $ticket) { if (strpos($ticket['ticket_number'], $prefix) === 0) { $seq = (int) substr($ticket['ticket_number'], strlen($prefix)); $maxSeq = max($maxSeq, $seq); } }
return $prefix . str_pad($maxSeq + 1, 4, '0', STR_PAD_LEFT);}Walidacja danych wejściowych:
$title = trim($_POST['title'] ?? '');$description = trim($_POST['description'] ?? '');$location = trim($_POST['location'] ?? '');$priority = $_POST['priority'] ?? '';$category = $_POST['category'] ?? '';
if (empty($title)) { $errors[] = "Temat zgłoszenia jest wymagany";}
if (strlen($title) < 5) { $errors[] = "Temat musi mieć minimum 5 znaków";}
$validPriorities = ['low', 'medium', 'high', 'critical'];if (!in_array($priority, $validPriorities)) { $errors[] = "Wybierz poprawny priorytet";}
$validCategories = ['hardware', 'software', 'network', 'other'];if (!in_array($category, $validCategories)) { $errors[] = "Wybierz poprawną kategorię";}Priorytety i statusy:
function getPriorities(): array { return [ 'low' => ['label' => 'Niski', 'color' => '#27ae60', 'icon' => '🟢'], 'medium' => ['label' => 'Średni', 'color' => '#f39c12', 'icon' => '🟡'], 'high' => ['label' => 'Wysoki', 'color' => '#e67e22', 'icon' => '🟠'], 'critical' => ['label' => 'Krytyczny', 'color' => '#e74c3c', 'icon' => '🔴'], ];}
function getStatuses(): array { return [ 'new' => ['label' => 'Nowe', 'color' => '#3498db', 'icon' => '📬'], 'in_progress' => ['label' => 'W realizacji', 'color' => '#f39c12', 'icon' => '⚙️'], 'waiting' => ['label' => 'Oczekuje', 'color' => '#9b59b6', 'icon' => '⏳'], 'done' => ['label' => 'Zamknięte', 'color' => '#27ae60', 'icon' => '✅'], ];}
function getCategories(): array { return [ 'hardware' => ['label' => 'Sprzęt', 'icon' => '🖥️'], 'software' => ['label' => 'Oprogramowanie', 'icon' => '💿'], 'network' => ['label' => 'Sieć', 'icon' => '🌐'], 'other' => ['label' => 'Inne', 'icon' => '📋'], ];}Zmiana statusu:
function changeTicketStatus(array &$tickets, int $ticketId, string $newStatus): bool { $validStatuses = array_keys(getStatuses());
if (!in_array($newStatus, $validStatuses)) { return false; }
foreach ($tickets as &$ticket) { if ($ticket['id'] === $ticketId) { $ticket['status'] = $newStatus; $ticket['updated_at'] = date('Y-m-d H:i:s');
if ($newStatus === 'done') { $ticket['resolved_at'] = date('Y-m-d H:i:s'); }
return true; } }
return false;}
// Dozwolone przejścia statusówfunction getAllowedTransitions(string $currentStatus): array { $transitions = [ 'new' => ['in_progress', 'done'], 'in_progress' => ['waiting', 'done', 'new'], 'waiting' => ['in_progress', 'done'], 'done' => ['new'], // Reopen ];
return $transitions[$currentStatus] ?? [];}Filtrowanie i sortowanie:
function filterTickets(array $tickets, ?string $status = null, ?string $priority = null, ?string $category = null): array { return array_filter($tickets, function($ticket) use ($status, $priority, $category) { if ($status && $ticket['status'] !== $status) return false; if ($priority && $ticket['priority'] !== $priority) return false; if ($category && $ticket['category'] !== $category) return false; return true; });}
function sortTickets(array $tickets, string $sortBy = 'created_at', string $order = 'desc'): array { // Priorytet sortowania: critical > high > medium > low $priorityOrder = ['critical' => 0, 'high' => 1, 'medium' => 2, 'low' => 3];
usort($tickets, function($a, $b) use ($sortBy, $order, $priorityOrder) { if ($sortBy === 'priority') { $result = ($priorityOrder[$a['priority']] ?? 99) - ($priorityOrder[$b['priority']] ?? 99); } else { $result = strcmp($a[$sortBy] ?? '', $b[$sortBy] ?? ''); }
return $order === 'desc' ? -$result : $result; });
return $tickets;}Statystyki zgłoszeń (wariant C):
function getTicketStatistics(array $tickets): array { $stats = [ 'total' => count($tickets), 'by_status' => [], 'by_priority' => [], 'by_category' => [], 'avg_resolution_time' => 0, ];
$resolutionTimes = [];
foreach ($tickets as $ticket) { // Zliczanie po statusie $status = $ticket['status']; $stats['by_status'][$status] = ($stats['by_status'][$status] ?? 0) + 1;
// Zliczanie po priorytecie $priority = $ticket['priority']; $stats['by_priority'][$priority] = ($stats['by_priority'][$priority] ?? 0) + 1;
// Zliczanie po kategorii $category = $ticket['category']; $stats['by_category'][$category] = ($stats['by_category'][$category] ?? 0) + 1;
// Czas realizacji dla zamkniętych if ($ticket['status'] === 'done' && !empty($ticket['resolved_at'])) { $created = strtotime($ticket['created_at']); $resolved = strtotime($ticket['resolved_at']); $resolutionTimes[] = ($resolved - $created) / 3600; // w godzinach } }
if (!empty($resolutionTimes)) { $stats['avg_resolution_time'] = round(array_sum($resolutionTimes) / count($resolutionTimes), 1); }
return $stats;}Wykorzystaj lekcje!
Cotygodniowe spotkania podczas lekcji to idealny moment, by:
Pracuj iteracyjnie - lepiej mieć działający wariant A niż niedokończony C!