Przejdź do głównej zawartości

System zgłoszeń serwisowych IT

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?

  • 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

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.

  1. Formularz zgłoszenia Użytkownik opisuje usterkę: temat, szczegółowy opis, lokalizację (np. sala 203) i wybiera priorytet.

  2. Walidacja danych System sprawdza poprawność wprowadzonych danych - czy temat nie jest pusty, czy priorytet jest z dozwolonej listy.

  3. Lista zgłoszeń Wszystkie zgłoszenia są wyświetlane z priorytetami, statusami i datami, posortowane według pilności.

  4. 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
}
]
}
  • Folderhelpdesk-it/
    • index.php (panel główny - lista zgłoszeń)
    • zgłoś.php (formularz nowego zgłoszenia)
    • zgłoszenie.php (podgląd szczegółów)
    • status.php (zmiana statusu)
    • panel.php (panel operatora - wariant B/C)
    • raport.php (raport statystyk - wariant C)
    • Folderincludes/
      • config.php
      • functions.php
      • validation.php
      • auth.php (wariant C)
    • Folderdata/
      • tickets.json
      • users.json (wariant C)
    • Foldercss/
      • style.css
    • Folderjs/
      • validation.js

Wymagane funkcje:

  • Formularz zgłoszenia: temat, opis, lokalizacja, priorytet (select)
  • Walidacja PHP (temat wymagany, priorytet z listy)
  • Min. 1 walidacja JavaScript
  • Zapis zgłoszeń do pliku JSON
  • Lista wszystkich zgłoszeń
  • Komunikaty błędów i sukcesu

Przykładowy scenariusz:

Uż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.

Ocena: 3.0

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ów
function 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:

  • 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!