145 lines
5.1 KiB
TypeScript
145 lines
5.1 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
|
||
interface Code {
|
||
id: string;
|
||
code: string;
|
||
usedByUserId: string | null;
|
||
usedAt: string | null;
|
||
createdAt: string;
|
||
createdBy: { username: string };
|
||
}
|
||
|
||
export default function AdminCodesPage() {
|
||
const [codes, setCodes] = useState<Code[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [generating, setGenerating] = useState(false);
|
||
const [newCode, setNewCode] = useState("");
|
||
const [error, setError] = useState("");
|
||
|
||
const fetchCodes = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const res = await fetch("/api/admin/codes", { credentials: "include" });
|
||
if (res.ok) {
|
||
setCodes(await res.json());
|
||
}
|
||
} catch {
|
||
// ignore
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
fetchCodes();
|
||
}, []);
|
||
|
||
const handleGenerate = async () => {
|
||
setGenerating(true);
|
||
setError("");
|
||
setNewCode("");
|
||
try {
|
||
const res = await fetch("/api/admin/codes", { method: "POST", credentials: "include" });
|
||
const data = await res.json();
|
||
if (!res.ok) {
|
||
throw new Error(data.error || "Грешка при генерирање");
|
||
}
|
||
setNewCode(data.code);
|
||
fetchCodes();
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : "Грешка при генерирање");
|
||
} finally {
|
||
setGenerating(false);
|
||
}
|
||
};
|
||
|
||
const handleDelete = async (id: string) => {
|
||
if (!confirm("Дали сте сигурни?")) return;
|
||
try {
|
||
await fetch(`/api/admin/codes/${id}`, { method: "DELETE", credentials: "include" });
|
||
fetchCodes();
|
||
} catch {
|
||
// ignore
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div>
|
||
<div className="mb-8 flex items-center justify-between">
|
||
<h1 className="text-2xl font-semibold text-stone-900">Кодови</h1>
|
||
<button
|
||
onClick={handleGenerate}
|
||
disabled={generating}
|
||
className="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-light disabled:cursor-not-allowed disabled:opacity-50"
|
||
>
|
||
{generating ? "Генерирање..." : "Генерирај код"}
|
||
</button>
|
||
</div>
|
||
|
||
{newCode && (
|
||
<div className="mb-8 rounded-lg border border-green-200 bg-green-50 p-4">
|
||
<p className="text-sm font-medium text-green-800">Нов код:</p>
|
||
<p className="mt-1 text-2xl font-bold tracking-widest text-green-900">{newCode}</p>
|
||
<p className="mt-1 text-xs text-green-600">Копирајте го кодот. Ќе биде прикажан само еднаш.</p>
|
||
</div>
|
||
)}
|
||
|
||
{error && (
|
||
<div className="mb-8 rounded-lg bg-red-50 p-4 text-sm text-red-700">{error}</div>
|
||
)}
|
||
|
||
<div className="rounded-lg bg-white shadow-sm">
|
||
{loading ? (
|
||
<p className="p-6 text-sm text-stone-500">Вчитување...</p>
|
||
) : codes.length === 0 ? (
|
||
<p className="p-6 text-sm text-stone-500">Нема генерирани кодови</p>
|
||
) : (
|
||
<table className="w-full text-left text-sm">
|
||
<thead className="border-b border-stone-200">
|
||
<tr>
|
||
<th className="px-6 py-3 font-medium text-stone-500">Код</th>
|
||
<th className="px-6 py-3 font-medium text-stone-500">Креиран од</th>
|
||
<th className="px-6 py-3 font-medium text-stone-500">Креиран</th>
|
||
<th className="px-6 py-3 font-medium text-stone-500">Статус</th>
|
||
<th className="px-6 py-3 font-medium text-stone-500">Акции</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-stone-100">
|
||
{codes.map((item) => (
|
||
<tr key={item.id} className="hover:bg-stone-50">
|
||
<td className="px-6 py-4 font-mono text-stone-900">{item.code}</td>
|
||
<td className="px-6 py-4 text-stone-600">{item.createdBy.username}</td>
|
||
<td className="px-6 py-4 text-stone-600">{new Date(item.createdAt).toLocaleDateString("mk-MK")}</td>
|
||
<td className="px-6 py-4">
|
||
{item.usedByUserId ? (
|
||
<span className="inline-flex rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800">
|
||
Искористен
|
||
</span>
|
||
) : (
|
||
<span className="inline-flex rounded-full bg-stone-100 px-2.5 py-0.5 text-xs font-medium text-stone-600">
|
||
Неискористен
|
||
</span>
|
||
)}
|
||
</td>
|
||
<td className="px-6 py-4">
|
||
{!item.usedByUserId && (
|
||
<button
|
||
onClick={() => handleDelete(item.id)}
|
||
className="text-sm text-red-600 hover:underline"
|
||
>
|
||
Избриши
|
||
</button>
|
||
)}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|