deconstructing admin panel :)
This commit is contained in:
parent
ab17c2bb92
commit
275a7873d2
@ -7,6 +7,7 @@ import {
|
||||
resetUserPassword,
|
||||
} from "../../services/api";
|
||||
import DocumentUpload from "../documentUpload/DocumentUpload";
|
||||
import Documents from "../documentUpload/Documents";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
@ -241,84 +242,14 @@ function AdminPanel() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-6">
|
||||
{/* <div className="grid gap-6"> */}
|
||||
<>
|
||||
{activeTab === "documents" && (
|
||||
<div className="bg-primary-800/50 backdrop-blur-lg rounded-xl overflow-hidden shadow-xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-primary-700">
|
||||
<th className="px-6 py-4 text-left text-md text-neutral-400">
|
||||
Име
|
||||
</th>
|
||||
{/* <th className="px-6 py-4 text-left text-md text-neutral-400">
|
||||
Статус
|
||||
</th> */}
|
||||
<th className="px-6 py-4 text-left text-md text-neutral-400">
|
||||
Прикачено од
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left text-md text-neutral-400">
|
||||
Споделено со
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left text-md text-neutral-400">
|
||||
Креирано на
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{documents.map((doc) => (
|
||||
<tr
|
||||
key={doc.id}
|
||||
className="border-b border-primary-700/50 hover:bg-primary-700/30"
|
||||
>
|
||||
<td className="px-6 py-4 text-white">{doc.title}</td>
|
||||
{/* <td className="px-6 py-4">
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-xs font-medium ${
|
||||
doc.status === "completed"
|
||||
? "bg-green-500/20 text-green-300"
|
||||
: doc.status === "pending"
|
||||
? "bg-yellow-500/20 text-yellow-300"
|
||||
: doc.status === "uploading"
|
||||
? "bg-primary-500/20 text-primary-300"
|
||||
: "bg-neutral-500/20 text-neutral-300"
|
||||
}`}
|
||||
>
|
||||
{doc.status}
|
||||
</span>
|
||||
</td> */}
|
||||
<td className="px-6 py-4 text-neutral-300">
|
||||
{doc.uploadedBy?.name} ({doc.uploadedBy?.email})
|
||||
</td>
|
||||
<td className="px-6 py-4 text-neutral-300">
|
||||
{doc.sharedWith && doc.sharedWith.length > 0
|
||||
? doc.sharedWith.map((user) => (
|
||||
<div
|
||||
key={user.id}
|
||||
className="whitespace-nowrap"
|
||||
>
|
||||
{user.name} ({user.email})
|
||||
</div>
|
||||
))
|
||||
: "None"}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-neutral-300">
|
||||
{new Date(doc.createdAt).toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</motion.div>
|
||||
<div className="bg-primary-800/50 backdrop-blur-lg rounded-xl p-6 shadow-xl">
|
||||
<Documents />
|
||||
</div>
|
||||
)}
|
||||
|
||||
</>
|
||||
{activeTab === "users" && (
|
||||
<>
|
||||
<div className="bg-primary-800/50 backdrop-blur-lg rounded-xl p-6 mb-6 shadow-xl">
|
||||
@ -464,7 +395,6 @@ function AdminPanel() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
135
frontend/src/components/documentUpload/Documents.jsx
Normal file
135
frontend/src/components/documentUpload/Documents.jsx
Normal file
@ -0,0 +1,135 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { getAllDocuments } from "../../services/api";
|
||||
import { getAllUsers } from "../../services/api";
|
||||
import {
|
||||
FiUsers,
|
||||
FiFile,
|
||||
FiUpload,
|
||||
FiKey,
|
||||
FiLoader,
|
||||
FiUserPlus,
|
||||
} from "react-icons/fi";
|
||||
|
||||
function Documents() {
|
||||
const [documents, setDocuments] = useState([]);
|
||||
const [users, setUsers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [documentsResponse, usersResponse] = await Promise.all([
|
||||
getAllDocuments(),
|
||||
getAllUsers(),
|
||||
]);
|
||||
|
||||
// Make sure we're getting the data property from the response
|
||||
setDocuments(
|
||||
Array.isArray(documentsResponse.data) ? documentsResponse.data : [],
|
||||
);
|
||||
setUsers(Array.isArray(usersResponse.data) ? usersResponse.data : []);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
setError("Failed to load data");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<FiLoader className="w-8 h-8 animate-spin text-primary-500" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-lg text-red-200">
|
||||
{error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-primary-800/50 backdrop-blur-lg rounded-xl overflow-hidden shadow-xl">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-primary-700">
|
||||
<th className="px-6 py-4 text-left text-md text-neutral-400">
|
||||
Име
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left text-md text-neutral-400">
|
||||
Прикачено од
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left text-md text-neutral-400">
|
||||
Споделено со
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left text-md text-neutral-400">
|
||||
Креирано на
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{documents.length > 0 ? (
|
||||
documents.map((doc) => (
|
||||
<tr
|
||||
key={doc.id}
|
||||
className="border-b border-primary-700/50 hover:bg-primary-700/30"
|
||||
>
|
||||
<td className="px-6 py-4 text-white">{doc.title || "N/A"}</td>
|
||||
<td className="px-6 py-4 text-neutral-300">
|
||||
{doc.uploadedBy ? (
|
||||
<>
|
||||
{doc.uploadedBy.name} ({doc.uploadedBy.email})
|
||||
</>
|
||||
) : (
|
||||
"N/A"
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-neutral-300">
|
||||
{doc.sharedWith && doc.sharedWith.length > 0 ? (
|
||||
<div className="whitespace-nowrap">
|
||||
{doc.sharedWith.map((user) => (
|
||||
<div key={user.id}>
|
||||
{user.name} ({user.email})
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
"None"
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-neutral-300">
|
||||
{doc.createdAt
|
||||
? new Date(doc.createdAt).toLocaleString()
|
||||
: "N/A"}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td
|
||||
colSpan="4"
|
||||
className="px-6 py-4 text-center text-neutral-400"
|
||||
>
|
||||
No documents available
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Documents;
|
||||
@ -0,0 +1,136 @@
|
||||
import { useState } from "react";
|
||||
|
||||
function Users() {
|
||||
const [newUser, setNewUser] = useState({});
|
||||
return (
|
||||
<>
|
||||
<div className="bg-primary-800/50 backdrop-blur-lg rounded-xl p-6 mb-6 shadow-xl">
|
||||
<h2 className="text-xl font-bold text-white mb-4">Креирај корисник</h2>
|
||||
<form
|
||||
ref={createUserFormRef}
|
||||
onSubmit={handleCreateUser}
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Име"
|
||||
value={newUser.name}
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
onChange={(e) => setNewUser({ ...newUser, name: e.target.value })}
|
||||
className="bg-primary-700/30 border border-primary-600 rounded-lg px-4 py-2
|
||||
text-white placeholder-neutral-400 focus:outline-none focus:border-primary-500
|
||||
focus:ring-1 focus:ring-primary-500"
|
||||
/>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="Мејл"
|
||||
value={newUser.email}
|
||||
onChange={(e) => setNewUser({ ...newUser, email: e.target.value })}
|
||||
className="bg-primary-700/30 border border-primary-600 rounded-lg px-4 py-2
|
||||
text-white placeholder-neutral-400 focus:outline-none focus:border-primary-500
|
||||
focus:ring-1 focus:ring-primary-500"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
// type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Лозинка"
|
||||
value={newUser.password}
|
||||
onChange={(e) =>
|
||||
setNewUser({ ...newUser, password: e.target.value })
|
||||
}
|
||||
className="bg-primary-700/30 border border-primary-600 rounded-lg px-4 py-2
|
||||
text-white placeholder-neutral-400 focus:outline-none focus:border-primary-500
|
||||
focus:ring-1 focus:ring-primary-500"
|
||||
required
|
||||
/>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="isAdmin"
|
||||
checked={newUser.isAdmin}
|
||||
onChange={(e) =>
|
||||
setNewUser({ ...newUser, isAdmin: e.target.checked })
|
||||
}
|
||||
className="rounded border-primary-600 bg-primary-700/30 text-primary-500
|
||||
focus:ring-primary-500"
|
||||
/>
|
||||
<label htmlFor="isAdmin" className="text-white">
|
||||
Дали е Администратор
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex items-center justify-center space-x-2 px-4 py-2
|
||||
bg-primary-600 hover:bg-primary-700 text-white rounded-lg
|
||||
transition-colors shadow-lg"
|
||||
>
|
||||
<FiUserPlus className="w-4 h-4" />
|
||||
<span>Креирај корисник</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="bg-primary-800/50 backdrop-blur-lg rounded-xl overflow-hidden shadow-xl">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-primary-700">
|
||||
<th className="px-6 py-4 text-left text-sm text-neutral-400">
|
||||
Име
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-neutral-400">
|
||||
Мејл
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-neutral-400">
|
||||
Улога
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-neutral-400">
|
||||
Ресетирај
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
<tr
|
||||
key={user.id}
|
||||
className="border-b border-primary-700/50 hover:bg-primary-700/30"
|
||||
>
|
||||
<td className="px-6 py-4 text-white">{user.name}</td>
|
||||
<td className="px-6 py-4 text-white">{user.email}</td>
|
||||
<td className="px-6 py-4">
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-xs font-medium
|
||||
${user.isAdmin ? "bg-primary-500/20 text-primary-300" : "bg-neutral-500/20 text-neutral-300"}`}
|
||||
>
|
||||
{user.isAdmin ? "Admin" : "User"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<button
|
||||
onClick={() =>
|
||||
setResetPasswordModal({
|
||||
isOpen: true,
|
||||
userId: user.id,
|
||||
userName: user.name,
|
||||
newPassword: "",
|
||||
})
|
||||
}
|
||||
className="flex items-center space-x-1 text-neutral-400 hover:text-white transition-colors"
|
||||
>
|
||||
<FiKey className="w-4 h-4" />
|
||||
<span>Ресетирај лозинка</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user