fitaiProto/apps/admin/src/app/settings/page.tsx
2026-04-02 22:47:27 +02:00

973 lines
35 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import axios from "axios";
import {
Database,
Download,
RefreshCw,
AlertTriangle,
Check,
Loader2,
Trash2,
Users,
CalendarCheck,
TrendingUp,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { useUser } from "@clerk/nextjs";
import log from "@/lib/logger";
import { MEMBERSHIP_FEATURES } from "@/lib/membership/features";
interface Backup {
name: string;
size: number;
createdAt: string;
}
interface Gym {
id: string;
name: string;
location?: string | null;
latitude?: number | null;
longitude?: number | null;
geofenceRadiusMeters?: number | null;
geofenceEnabled?: boolean;
status: "active" | "inactive";
adminUserId: string;
createdAt?: number;
}
interface GymStats {
totalUsers: number;
admins: number;
trainers: number;
clients: number;
membershipStats: {
basic: number;
premium: number;
vip: number;
};
activeClients: number;
attendanceLast30Days: number;
}
export default function SettingsPage() {
const { user } = useUser();
const [backups, setBackups] = useState<Backup[]>([]);
const [loading, setLoading] = useState(true);
const [creatingBackup, setCreatingBackup] = useState(false);
const [restoring, setRestoring] = useState<string | null>(null);
const [message, setMessage] = useState<{
type: "success" | "error";
text: string;
} | null>(null);
// Gym picker state
const [gyms, setGyms] = useState<Gym[]>([]);
const [gymsLoading, setGymsLoading] = useState<boolean>(true);
const [gymMessage, setGymMessage] = useState<{
type: "success" | "error";
text: string;
} | null>(null);
// Selected gym for details
const [selectedGym, setSelectedGym] = useState<Gym | null>(null);
const [gymStats, setGymStats] = useState<GymStats | null>(null);
const [statsLoading, setStatsLoading] = useState(false);
const [deletingGym, setDeletingGym] = useState(false);
const [savingGeofence, setSavingGeofence] = useState(false);
const [geofenceLatitude, setGeofenceLatitude] = useState("");
const [geofenceLongitude, setGeofenceLongitude] = useState("");
const [geofenceRadiusMeters, setGeofenceRadiusMeters] = useState("30");
const [geofenceEnabled, setGeofenceEnabled] = useState(true);
// Create Gym modal state
const [showCreateGym, setShowCreateGym] = useState(false);
const [gymName, setGymName] = useState("");
const [gymLocation, setGymLocation] = useState("");
const [creatingGym, setCreatingGym] = useState(false);
const fetchBackups = async () => {
try {
const response = await axios.get("/api/admin/backups");
setBackups(response.data);
} catch (error) {
log.error("Failed to fetch backups", error);
} finally {
setLoading(false);
}
};
const fetchGyms = async () => {
setGymsLoading(true);
setGymMessage(null);
try {
const res = await axios.get("/api/gyms");
setGyms(Array.isArray(res.data) ? res.data : []);
} catch (error) {
log.error("Failed to fetch gyms", error);
setGymMessage({ type: "error", text: "Failed to load gyms" });
} finally {
setGymsLoading(false);
}
};
const fetchGymStats = async (gymId: string) => {
setStatsLoading(true);
try {
const res = await axios.get(`/api/gyms/${gymId}/stats`);
if (res.data?.stats) {
setGymStats(res.data.stats);
}
} catch (error) {
log.error("Failed to fetch gym stats", error);
} finally {
setStatsLoading(false);
}
};
useEffect(() => {
fetchBackups();
fetchGyms();
}, []);
useEffect(() => {
if (selectedGym) {
fetchGymStats(selectedGym.id);
} else {
setGymStats(null);
}
}, [selectedGym]);
const handleCreateBackup = async () => {
setCreatingBackup(true);
setMessage(null);
try {
await axios.post("/api/admin/backups");
await fetchBackups();
setMessage({ type: "success", text: "Backup created successfully" });
} catch (error) {
log.error("Failed to create backup", error);
setMessage({ type: "error", text: "Failed to create backup" });
} finally {
setCreatingBackup(false);
}
};
const handleRestore = async (filename: string) => {
if (
!window.confirm(
`Are you sure you want to restore from ${filename}? This will overwrite the current database.`,
)
) {
return;
}
setRestoring(filename);
setMessage(null);
try {
await axios.post("/api/admin/backups/restore", { filename });
setMessage({ type: "success", text: "Database restored successfully" });
} catch (error) {
log.error("Failed to restore backup", error);
setMessage({ type: "error", text: "Failed to restore backup" });
} finally {
setRestoring(null);
}
};
const formatSize = (bytes: number) => {
const units = ["B", "KB", "MB", "GB"];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString();
};
const handleSelectGym = async (gym: Gym | null) => {
setSelectedGym(gym);
setGymStats(null);
if (gym) {
setGeofenceLatitude(
gym.latitude !== null && gym.latitude !== undefined
? String(gym.latitude)
: "",
);
setGeofenceLongitude(
gym.longitude !== null && gym.longitude !== undefined
? String(gym.longitude)
: "",
);
setGeofenceRadiusMeters(String(gym.geofenceRadiusMeters ?? 30));
setGeofenceEnabled(gym.geofenceEnabled ?? true);
}
};
const handleSaveGeofence = async () => {
if (!selectedGym) return;
const latitude =
geofenceLatitude.trim() === "" ? null : Number(geofenceLatitude);
const longitude =
geofenceLongitude.trim() === "" ? null : Number(geofenceLongitude);
const radius = Number(geofenceRadiusMeters);
if (
latitude !== null &&
(!Number.isFinite(latitude) || latitude < -90 || latitude > 90)
) {
setGymMessage({
type: "error",
text: "Latitude must be between -90 and 90",
});
return;
}
if (
longitude !== null &&
(!Number.isFinite(longitude) || longitude < -180 || longitude > 180)
) {
setGymMessage({
type: "error",
text: "Longitude must be between -180 and 180",
});
return;
}
if (!Number.isFinite(radius) || radius <= 0) {
setGymMessage({
type: "error",
text: "Radius must be a positive number",
});
return;
}
setSavingGeofence(true);
setGymMessage(null);
try {
const response = await axios.patch(`/api/gyms/${selectedGym.id}`, {
latitude,
longitude,
geofenceRadiusMeters: radius,
geofenceEnabled,
});
setGymMessage({ type: "success", text: "Geofence settings updated" });
const updatedGym = response.data as Gym;
setSelectedGym(updatedGym);
setGyms((prev) =>
prev.map((gym) => (gym.id === updatedGym.id ? updatedGym : gym)),
);
} catch (error) {
log.error("Failed to update geofence settings", error);
setGymMessage({
type: "error",
text: "Failed to update geofence settings",
});
} finally {
setSavingGeofence(false);
}
};
const handleDeleteGym = async (gymId: string) => {
if (
!window.confirm(
"Are you sure you want to delete this gym? This action cannot be undone.",
)
) {
return;
}
setDeletingGym(true);
setGymMessage(null);
try {
const response = await axios.delete(`/api/gyms/${gymId}`);
log.info("Delete gym response:", response.data);
setGymMessage({ type: "success", text: "Gym deleted successfully" });
setSelectedGym(null);
setGymStats(null);
await fetchGyms();
} catch (error: any) {
log.error("Failed to delete gym", error);
const errorMessage =
error.response?.data?.error ||
error.response?.data ||
error.message ||
"Failed to delete gym";
setGymMessage({ type: "error", text: errorMessage });
} finally {
setDeletingGym(false);
}
};
const userRole = (user?.publicMetadata?.role as string) ?? "client";
const isSuperAdmin = userRole === "superAdmin";
return (
<div className="space-y-8 p-8">
<div>
<h2 className="text-3xl font-bold text-slate-900">Settings</h2>
<p className="text-slate-500 mt-2">
Manage your application settings and database.
</p>
</div>
{/* Gym Management */}
<div className="bg-white rounded-xl shadow-sm border border-slate-100 p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-50 rounded-lg">
<Database className="w-6 h-6 text-blue-600" />
</div>
<div>
<h3 className="text-xl font-bold text-slate-900">
Gym Management
</h3>
<p className="text-sm text-slate-500">
{isSuperAdmin
? "Select a gym to view details, create or delete gyms"
: "Select your gym or proceed without a gym"}
</p>
<p className="text-xs text-blue-600 mt-1">
{user ? (
<>
Current role:{" "}
<span className="font-medium">
{String(user.publicMetadata?.role ?? "unknown")}
</span>
</>
) : (
"Loading user metadata..."
)}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button
onClick={fetchGyms}
disabled={gymsLoading}
variant="outline"
size="sm"
className="flex items-center gap-2"
>
{gymsLoading ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<RefreshCw className="w-4 h-4" />
)}
Refresh
</Button>
{isSuperAdmin && (
<Button
onClick={() => setShowCreateGym(true)}
size="sm"
className="flex items-center gap-2"
>
Create Gym
</Button>
)}
</div>
</div>
{gymMessage && (
<div
className={`p-4 rounded-lg mb-6 flex items-center gap-2 ${gymMessage.type === "success" ? "bg-green-50 text-green-700" : "bg-red-50 text-red-700"}`}
>
{gymMessage.type === "success" ? (
<Check className="w-5 h-5" />
) : (
<AlertTriangle className="w-5 h-5" />
)}
{gymMessage.text}
</div>
)}
{showCreateGym && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white p-6 rounded-lg shadow-lg max-w-md w-full">
<h3 className="text-lg font-semibold mb-4">Create Gym</h3>
<form
onSubmit={async (e) => {
e.preventDefault();
try {
setCreatingGym(true);
await axios.post("/api/gyms", {
name: gymName.trim(),
location: gymLocation.trim() || undefined,
});
setGymMessage({
type: "success",
text: "Gym created successfully",
});
setShowCreateGym(false);
setGymName("");
setGymLocation("");
fetchGyms();
} catch (error) {
log.error("Failed to create gym", error);
setGymMessage({
type: "error",
text: "Failed to create gym",
});
} finally {
setCreatingGym(false);
}
}}
>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">
Gym Name
</label>
<input
type="text"
value={gymName}
onChange={(e) => setGymName(e.target.value)}
className="w-full border border-gray-300 rounded px-3 py-2"
required
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">
Location (optional)
</label>
<input
type="text"
value={gymLocation}
onChange={(e) => setGymLocation(e.target.value)}
className="w-full border border-gray-300 rounded px-3 py-2"
placeholder="Enter location"
/>
</div>
<div className="flex justify-end gap-2">
<button
type="button"
onClick={() => {
setShowCreateGym(false);
setGymName("");
setGymLocation("");
}}
className="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400"
>
Cancel
</button>
<button
type="submit"
disabled={creatingGym}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex items-center gap-2"
>
{creatingGym ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : null}
Create
</button>
</div>
</form>
</div>
</div>
)}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Gym List */}
<div className="lg:col-span-1 space-y-4">
<h4 className="font-semibold text-slate-900">Gyms</h4>
{gymsLoading ? (
<div className="flex items-center justify-center p-8 text-slate-500">
<Loader2 className="w-5 h-5 animate-spin mr-2" />
Loading...
</div>
) : gyms.length === 0 ? (
<div className="p-4 text-center text-slate-500">
No active gyms found.
</div>
) : (
<div className="space-y-2 max-h-96 overflow-y-auto">
{gyms.map((gym) => (
<div
key={gym.id}
className={`border rounded-lg p-3 cursor-pointer transition-colors ${
selectedGym?.id === gym.id
? "border-blue-500 bg-blue-50"
: "hover:bg-slate-50"
}`}
onClick={() => handleSelectGym(gym)}
>
<div className="flex items-center justify-between">
<div>
<h5 className="font-medium text-slate-900">
{gym.name}
</h5>
<p className="text-xs text-slate-500">
{gym.location || "No location"}
</p>
</div>
<span
className={`text-xs px-2 py-1 rounded ${
gym.status === "active"
? "bg-green-100 text-green-700"
: "bg-red-100 text-red-700"
}`}
>
{gym.status}
</span>
</div>
</div>
))}
</div>
)}
</div>
{/* Gym Details / Stats */}
<div className="lg:col-span-2">
{selectedGym ? (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h4 className="font-semibold text-slate-900">
{selectedGym.name} - Details
</h4>
{isSuperAdmin && (
<Button
variant="ghost"
size="sm"
className="text-red-600 hover:text-red-700 hover:bg-red-50"
onClick={() => handleDeleteGym(selectedGym.id)}
disabled={deletingGym}
>
{deletingGym ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Trash2 className="w-4 h-4 mr-1" />
)}
Delete Gym
</Button>
)}
</div>
{/* Gym Info */}
<div className="grid grid-cols-2 gap-4 p-4 bg-slate-50 rounded-lg">
<div>
<p className="text-xs text-slate-500">Location</p>
<p className="font-medium">
{selectedGym.location || "Not specified"}
</p>
</div>
<div>
<p className="text-xs text-slate-500">Status</p>
<p className="font-medium capitalize">
{selectedGym.status}
</p>
</div>
<div>
<p className="text-xs text-slate-500">Geofence</p>
<p className="font-medium">
{selectedGym.geofenceEnabled === false
? "Disabled"
: `${selectedGym.geofenceRadiusMeters ?? 30}m`}
</p>
</div>
</div>
{/* Geofence Settings */}
<div className="p-4 border rounded-lg space-y-3">
<div className="flex items-center justify-between">
<h5 className="text-sm font-medium text-slate-700">
Attendance Geofence
</h5>
<label className="inline-flex items-center gap-2 text-sm text-slate-700">
<input
type="checkbox"
checked={geofenceEnabled}
onChange={(e) => setGeofenceEnabled(e.target.checked)}
/>
Enabled
</label>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div>
<label className="block text-xs text-slate-500 mb-1">
Latitude
</label>
<input
type="number"
step="any"
value={geofenceLatitude}
onChange={(e) => setGeofenceLatitude(e.target.value)}
className="w-full border border-gray-300 rounded px-3 py-2"
placeholder="e.g. 37.7749"
/>
</div>
<div>
<label className="block text-xs text-slate-500 mb-1">
Longitude
</label>
<input
type="number"
step="any"
value={geofenceLongitude}
onChange={(e) => setGeofenceLongitude(e.target.value)}
className="w-full border border-gray-300 rounded px-3 py-2"
placeholder="e.g. -122.4194"
/>
</div>
<div>
<label className="block text-xs text-slate-500 mb-1">
Radius (meters)
</label>
<input
type="number"
min="1"
value={geofenceRadiusMeters}
onChange={(e) =>
setGeofenceRadiusMeters(e.target.value)
}
className="w-full border border-gray-300 rounded px-3 py-2"
/>
</div>
</div>
<div className="flex items-center justify-between">
<p className="text-xs text-slate-500">
Default radius is 30m and geofence is enabled by default.
</p>
<Button
size="sm"
onClick={handleSaveGeofence}
disabled={savingGeofence}
>
{savingGeofence ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
"Save Geofence"
)}
</Button>
</div>
</div>
{/* Stats */}
{statsLoading ? (
<div className="flex items-center justify-center p-8">
<Loader2 className="w-6 h-6 animate-spin text-slate-400" />
</div>
) : gymStats ? (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="p-4 bg-blue-50 rounded-lg">
<div className="flex items-center gap-2 mb-1">
<Users className="w-4 h-4 text-blue-600" />
<span className="text-xs text-blue-600">
Total Users
</span>
</div>
<p className="text-2xl font-bold text-blue-700">
{gymStats.totalUsers}
</p>
</div>
<div className="p-4 bg-green-50 rounded-lg">
<div className="flex items-center gap-2 mb-1">
<CalendarCheck className="w-4 h-4 text-green-600" />
<span className="text-xs text-green-600">
Active Clients
</span>
</div>
<p className="text-2xl font-bold text-green-700">
{gymStats.activeClients}
</p>
</div>
<div className="p-4 bg-purple-50 rounded-lg">
<div className="flex items-center gap-2 mb-1">
<TrendingUp className="w-4 h-4 text-purple-600" />
<span className="text-xs text-purple-600">
Check-ins (30d)
</span>
</div>
<p className="text-2xl font-bold text-purple-700">
{gymStats.attendanceLast30Days}
</p>
</div>
<div className="p-4 bg-orange-50 rounded-lg">
<div className="flex items-center gap-2 mb-1">
<Users className="w-4 h-4 text-orange-600" />
<span className="text-xs text-orange-600">
Trainers
</span>
</div>
<p className="text-2xl font-bold text-orange-700">
{gymStats.trainers}
</p>
</div>
</div>
) : null}
{/* Membership Distribution */}
{gymStats && (
<div className="mt-4">
<h5 className="text-sm font-medium text-slate-700 mb-2">
Membership Distribution
</h5>
<div className="flex gap-4">
<div className="flex-1 p-3 bg-slate-100 rounded-lg text-center">
<p className="text-2xl font-bold text-slate-700">
{gymStats.membershipStats.basic}
</p>
<p className="text-xs text-slate-500">Basic</p>
</div>
<div className="flex-1 p-3 bg-blue-50 rounded-lg text-center">
<p className="text-2xl font-bold text-blue-700">
{gymStats.membershipStats.premium}
</p>
<p className="text-xs text-blue-500">Premium</p>
</div>
<div className="flex-1 p-3 bg-yellow-50 rounded-lg text-center">
<p className="text-2xl font-bold text-yellow-700">
{gymStats.membershipStats.vip}
</p>
<p className="text-xs text-yellow-600">VIP</p>
</div>
</div>
</div>
)}
{/* Membership Feature Access */}
<div className="mt-6">
<h5 className="text-sm font-medium text-slate-700 mb-2">
Membership Feature Access
</h5>
<div className="overflow-x-auto border rounded-lg">
<table className="w-full text-left text-sm">
<thead className="bg-slate-50 border-b">
<tr>
<th className="px-4 py-3 font-semibold text-slate-900">
Feature
</th>
<th className="px-4 py-3 font-semibold text-slate-900">
Basic
</th>
<th className="px-4 py-3 font-semibold text-slate-900">
Premium
</th>
<th className="px-4 py-3 font-semibold text-slate-900">
VIP
</th>
</tr>
</thead>
<tbody className="divide-y">
<tr>
<td className="px-4 py-3 text-slate-700">
Recommendations per month
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.basic.recommendationsPerMonth}
</td>
<td className="px-4 py-3 text-slate-700">
Unlimited
</td>
<td className="px-4 py-3 text-slate-700">
Unlimited
</td>
</tr>
<tr>
<td className="px-4 py-3 text-slate-700">
Nutrition tracking
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.basic.nutritionTracking
? "Yes"
: "No"}
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.premium.nutritionTracking
? "Yes"
: "No"}
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.vip.nutritionTracking
? "Yes"
: "No"}
</td>
</tr>
<tr>
<td className="px-4 py-3 text-slate-700">
Hydration tracking
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.basic.hydrationTracking
? "Yes"
: "No"}
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.premium.hydrationTracking
? "Yes"
: "No"}
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.vip.hydrationTracking
? "Yes"
: "No"}
</td>
</tr>
<tr>
<td className="px-4 py-3 text-slate-700">
Advanced statistics
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.basic.advancedStatistics
? "Yes"
: "No"}
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.premium.advancedStatistics
? "Yes"
: "No"}
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.vip.advancedStatistics
? "Yes"
: "No"}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
) : (
<div className="flex items-center justify-center h-64 bg-slate-50 rounded-lg">
<p className="text-slate-500">Select a gym to view details</p>
</div>
)}
</div>
</div>
</div>
{/* Database Management */}
<div className="bg-white rounded-xl shadow-sm border border-slate-100 p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-50 rounded-lg">
<Database className="w-6 h-6 text-blue-600" />
</div>
<div>
<h3 className="text-xl font-bold text-slate-900">
Database Management
</h3>
<p className="text-sm text-slate-500">
Create backups and restore your database
</p>
</div>
</div>
<Button
onClick={handleCreateBackup}
disabled={creatingBackup}
className="flex items-center gap-2"
>
{creatingBackup ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Download className="w-4 h-4" />
)}
Create Backup
</Button>
</div>
{message && (
<div
className={`p-4 rounded-lg mb-6 flex items-center gap-2 ${
message.type === "success"
? "bg-green-50 text-green-700"
: "bg-red-50 text-red-700"
}`}
>
{message.type === "success" ? (
<Check className="w-5 h-5" />
) : (
<AlertTriangle className="w-5 h-5" />
)}
{message.text}
</div>
)}
<div className="border rounded-lg overflow-hidden">
<table className="w-full text-left text-sm">
<thead className="bg-slate-50 border-b">
<tr>
<th className="px-6 py-4 font-semibold text-slate-900">
Filename
</th>
<th className="px-6 py-4 font-semibold text-slate-900">Size</th>
<th className="px-6 py-4 font-semibold text-slate-900">
Created At
</th>
<th className="px-6 py-4 font-semibold text-slate-900 text-right">
Actions
</th>
</tr>
</thead>
<tbody className="divide-y">
{loading ? (
<tr>
<td
colSpan={4}
className="px-6 py-8 text-center text-slate-500"
>
Loading backups...
</td>
</tr>
) : backups.length === 0 ? (
<tr>
<td
colSpan={4}
className="px-6 py-8 text-center text-slate-500"
>
No backups found
</td>
</tr>
) : (
backups.map((backup) => (
<tr
key={backup.name}
className="hover:bg-slate-50 transition-colors"
>
<td className="px-6 py-4 font-medium text-slate-900">
{backup.name}
</td>
<td className="px-6 py-4 text-slate-600">
{formatSize(backup.size)}
</td>
<td className="px-6 py-4 text-slate-600">
{formatDate(backup.createdAt)}
</td>
<td className="px-6 py-4 text-right">
<Button
variant="ghost"
size="sm"
onClick={() => handleRestore(backup.name)}
disabled={!!restoring}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
{restoring === backup.name ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<RefreshCw className="w-4 h-4 mr-2" />
)}
Restore
</Button>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</div>
);
}