Реалізація опитування CSAT на веб-сайті
CSAT (Customer Satisfaction Score) вимірює задоволення конкретною взаємодією: «Наскільки ви задоволені цим замовленням/підтримкою/функцією?» Зазвичай шкала 1-5 зі смайликами або зірками. CSAT = (задоволені / усього) × 100%.
Різниця від NPS
CSAT — точкова оцінка транзакції. NPS — довгострокова лояльність. CSAT збирається відразу після події: закриття квитка підтримки, доставка замовлення, завершення онбордингу.
API та модель
Schema::create('csat_responses', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
$table->morphs('subject'); // Поліморфний зв'язок: Order, SupportTicket, Feature
$table->tinyInteger('score')->unsigned(); // 1-5
$table->text('comment')->nullable();
$table->timestamps();
});
// CsatController
public function store(Request $request): JsonResponse
{
$request->validate([
'score' => 'required|integer|min:1|max:5',
'subject_type' => 'required|string|in:order,ticket',
'subject_id' => 'required|integer',
'comment' => 'nullable|string|max:500',
]);
CsatResponse::updateOrCreate(
['user_id' => auth()->id(), 'subject_type' => $request->subject_type, 'subject_id' => $request->subject_id],
['score' => $request->score, 'comment' => $request->comment]
);
return response()->json(['success' => true]);
}
Frontend: Віджет зіркового рейтингу
const STARS = [1, 2, 3, 4, 5];
const LABELS = ['Жахливо', 'Погано', 'Нормально', 'Добре', 'Чудово'];
export function CsatWidget({ subjectType, subjectId }: CsatProps) {
const [hover, setHover] = useState(0);
const [score, setScore] = useState(0);
const [submitted, setSubmitted] = useState(false);
const submit = async (s: number) => {
setScore(s);
await fetch('/api/csat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ score: s, subject_type: subjectType, subject_id: subjectId }),
});
setSubmitted(true);
};
if (submitted) return <p className="text-sm text-gray-500">Дякуємо за оцінку!</p>;
return (
<div className="flex items-center gap-1">
<span className="text-sm text-gray-600 mr-2">Оцініть якість:</span>
{STARS.map(s => (
<button key={s} title={LABELS[s - 1]}
onMouseEnter={() => setHover(s)} onMouseLeave={() => setHover(0)}
onClick={() => submit(s)}
className={`text-2xl ${(hover || score) >= s ? 'text-yellow-400' : 'text-gray-300'}`}>
★
</button>
))}
</div>
);
}
Поштовий тригер після закриття квитка
// У Observer або Listener на подію SupportTicket closed
public function handle(TicketClosed $event): void
{
Mail::to($event->ticket->user->email)
->later(now()->addMinutes(10), new CsatRequestMail($event->ticket));
}
В листі – прямі посилання з оцінкою: https://site.com/csat?ticket=123&score=5&token=HMAC.
Графік
CSAT-віджет з тригером на основі подій та API: 1-2 робочих дні.







