Мини-система обработки обращений со статусной моделью
Цель
Разработать систему тикетов с возможностью:
- создавать обращения
- менять статус
- удалять записи
- фильтровать по статусу
- расширять статусную модель через отдельный подключаемый модуль
Структура проекта
tickets/
index.html
app.js
api.php
config.php
status_plugin.php
data/
tickets.sqlite
1. Базовая версия системы
Файл config.php
<?php
function get_statuses() {
return [
'Новое',
'В работе',
'Завершено'
];
}
Файл api.php
<?php
header('Content-Type: application/json');
require_once 'config.php';
$db = new PDO('sqlite:' . __DIR__ . '/data/tickets.sqlite');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec("
CREATE TABLE IF NOT EXISTS tickets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
email TEXT,
message TEXT,
status TEXT,
created_at TEXT
)
");
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
$stmt = $db->prepare("
INSERT INTO tickets (name, email, message, status, created_at)
VALUES (:name, :email, :message, :status, :created_at)
");
$stmt->execute([
':name' => $data['name'],
':email' => $data['email'],
':message' => $data['message'],
':status' => 'Новое',
':created_at' => date('Y-m-d H:i:s')
]);
echo json_encode(['status' => 'created']);
exit;
}
if ($method === 'PUT') {
$data = json_decode(file_get_contents('php://input'), true);
$statuses = get_statuses();
if (!in_array($data['status'], $statuses)) {
echo json_encode(['error' => 'Invalid status']);
exit;
}
$stmt = $db->prepare("
UPDATE tickets
SET status = :status
WHERE id = :id
");
$stmt->execute([
':status' => $data['status'],
':id' => $data['id']
]);
echo json_encode(['status' => 'updated']);
exit;
}
if ($method === 'DELETE') {
parse_str($_SERVER['QUERY_STRING'], $query);
$stmt = $db->prepare("DELETE FROM tickets WHERE id = :id");
$stmt->execute([':id' => $query['id']]);
echo json_encode(['status' => 'deleted']);
exit;
}
$status_filter = $_GET['status'] ?? null;
if ($status_filter) {
$stmt = $db->prepare("
SELECT * FROM tickets
WHERE status = :status
ORDER BY id DESC
");
$stmt->execute([':status' => $status_filter]);
} else {
$stmt = $db->query("
SELECT * FROM tickets
ORDER BY id DESC
");
}
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
Файл index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Ticket System</title>
</head>
<body>
<h2>Создать обращение</h2>
<input id="name" placeholder="Имя">
<input id="email" placeholder="Email">
<textarea id="message" placeholder="Сообщение"></textarea>
<button onclick="createTicket()">Создать</button>
<h2>Фильтр</h2>
<select id="filter" onchange="load()">
<option value="">Все</option>
<option>Новое</option>
<option>В работе</option>
<option>Завершено</option>
</select>
<h2>Список</h2>
<div id="list"></div>
<script src="app.js"></script>
</body>
</html>
Файл app.js
async function createTicket() {
const data = {
name: name.value,
email: email.value,
message: message.value
};
await fetch('api.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
load();
}
async function changeStatus(id, status) {
await fetch('api.php', {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({id, status})
});
load();
}
async function deleteTicket(id) {
await fetch('api.php?id=' + id, {
method: 'DELETE'
});
load();
}
async function load() {
const status = filter.value;
const url = status ? `api.php?status=${status}` : 'api.php';
const res = await fetch(url);
const data = await res.json();
list.innerHTML = '';
data.forEach(item => {
const div = document.createElement('div');
div.innerHTML = `
<b>${item.name}</b> (${item.status})
<br>${item.message}
<br>
<button onclick="changeStatus(${item.id}, 'В работе')">В работу</button>
<button onclick="changeStatus(${item.id}, 'Завершено')">Завершить</button>
<button onclick="deleteTicket(${item.id})">Удалить</button>
<hr>
`;
list.appendChild(div);
});
}
load();
2. Задание на изменение архитектуры
Добавить подключаемый модуль статусной модели
Создать файл status_plugin.php, который расширяет список статусов.
Новый файл status_plugin.php
<?php
function extend_statuses($statuses) {
$statuses[] = 'Отклонено';
$statuses[] = 'Требует уточнения';
return $statuses;
}
Изменить config.php
<?php
function get_statuses() {
$statuses = [
'Новое',
'В работе',
'Завершено'
];
if (file_exists('status_plugin.php')) {
require_once 'status_plugin.php';
if (function_exists('extend_statuses')) {
$statuses = extend_statuses($statuses);
}
}
return $statuses;
}
Задача студентам
- Подключить модуль
- Добавить новые кнопки для новых статусов
- Обновить выпадающий список фильтра
- Проверить валидацию на сервере
- Добавить запрет перехода из «Завершено» в «Новое»