translation, form fix

This commit is contained in:
dimitar 2025-04-03 01:27:07 +02:00
parent c396a00bbc
commit ab17c2bb92
5 changed files with 161 additions and 122 deletions

View File

@ -8,6 +8,7 @@ import {
} from "../../services/api"; } from "../../services/api";
import DocumentUpload from "../documentUpload/DocumentUpload"; import DocumentUpload from "../documentUpload/DocumentUpload";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
import { import {
FiUsers, FiUsers,
FiFile, FiFile,
@ -16,8 +17,10 @@ import {
FiLoader, FiLoader,
FiKey, FiKey,
} from "react-icons/fi"; } from "react-icons/fi";
import { useRef } from "react";
function AdminPanel() { function AdminPanel() {
const createUserFormRef = useRef(null);
const navigate = useNavigate(); const navigate = useNavigate();
const [activeTab, setActiveTab] = useState("documents"); const [activeTab, setActiveTab] = useState("documents");
const [users, setUsers] = useState([]); const [users, setUsers] = useState([]);
@ -87,15 +90,17 @@ function AdminPanel() {
e.preventDefault(); e.preventDefault();
try { try {
await createUser(newUser); await createUser(newUser);
fetchData();
} catch (err) {
setError("Failed to create user");
} finally {
// setNewUser(initialUserFormState);
setNewUser({ setNewUser({
name: "", name: "",
email: "", email: "",
password: "", password: "",
isAdmin: false, isAdmin: false,
}); });
fetchData();
} catch (err) {
setError("Failed to create user");
} }
}; };
@ -123,9 +128,9 @@ function AdminPanel() {
if (!isAdmin) return null; if (!isAdmin) return null;
const tabs = [ const tabs = [
{ id: "documents", name: "Documents", icon: FiFile }, { id: "documents", name: "Документи", icon: FiFile },
{ id: "users", name: "Users", icon: FiUsers }, { id: "users", name: "Клиенти", icon: FiUsers },
{ id: "upload", name: "Upload Document", icon: FiUpload }, { id: "upload", name: "Прикачи документ", icon: FiUpload },
]; ];
if (loading) { if (loading) {
@ -146,9 +151,13 @@ function AdminPanel() {
<h1 className="text-3xl font-bold text-white mb-2"> <h1 className="text-3xl font-bold text-white mb-2">
Admin Dashboard Admin Dashboard
</h1> </h1>
<p className="text-neutral-400">Manage users and documents</p> <p className="text-neutral-400">Клиенти и доументи</p>
</header> </header>
{/* <motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
> */}
{/* Tabs */} {/* Tabs */}
<div className="flex space-x-4 mb-6"> <div className="flex space-x-4 mb-6">
{tabs.map(({ id, name, icon: Icon }) => ( {tabs.map(({ id, name, icon: Icon }) => (
@ -180,12 +189,15 @@ function AdminPanel() {
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50">
<div className="bg-primary-800 rounded-xl p-6 w-full max-w-md"> <div className="bg-primary-800 rounded-xl p-6 w-full max-w-md">
<h3 className="text-xl font-bold text-white mb-4"> <h3 className="text-xl font-bold text-white mb-4">
Reset Password for {resetPasswordModal.userName} Ресетирај лозинка за корисникот
</h3>
<h3 className="text-xl font-bold text-white mb-4">
{resetPasswordModal.userName}
</h3> </h3>
<form onSubmit={handleResetPassword} className="space-y-4"> <form onSubmit={handleResetPassword} className="space-y-4">
<input <input
type="password" type="password"
placeholder="New Password" placeholder="Нова лозинка"
value={resetPasswordModal.newPassword} value={resetPasswordModal.newPassword}
onChange={(e) => onChange={(e) =>
setResetPasswordModal({ setResetPasswordModal({
@ -212,7 +224,7 @@ function AdminPanel() {
} }
className="px-4 py-2 text-neutral-400 hover:text-white transition-colors" className="px-4 py-2 text-neutral-400 hover:text-white transition-colors"
> >
Cancel Откажи
</button> </button>
<button <button
type="submit" type="submit"
@ -221,7 +233,7 @@ function AdminPanel() {
transition-colors shadow-lg" transition-colors shadow-lg"
> >
<FiKey className="w-4 h-4" /> <FiKey className="w-4 h-4" />
<span>Reset Password</span> <span>Потврди</span>
</button> </button>
</div> </div>
</form> </form>
@ -232,24 +244,29 @@ function AdminPanel() {
<div className="grid gap-6"> <div className="grid gap-6">
{activeTab === "documents" && ( {activeTab === "documents" && (
<div className="bg-primary-800/50 backdrop-blur-lg rounded-xl overflow-hidden shadow-xl"> <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"> <div className="overflow-x-auto">
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="border-b border-primary-700"> <tr className="border-b border-primary-700">
<th className="px-6 py-4 text-left text-sm text-neutral-400"> <th className="px-6 py-4 text-left text-md text-neutral-400">
Title Име
</th> </th>
<th className="px-6 py-4 text-left text-sm text-neutral-400"> {/* <th className="px-6 py-4 text-left text-md text-neutral-400">
Status Статус
</th> */}
<th className="px-6 py-4 text-left text-md text-neutral-400">
Прикачено од
</th> </th>
<th className="px-6 py-4 text-left text-sm text-neutral-400"> <th className="px-6 py-4 text-left text-md text-neutral-400">
Uploaded By Споделено со
</th> </th>
<th className="px-6 py-4 text-left text-sm text-neutral-400"> <th className="px-6 py-4 text-left text-md text-neutral-400">
Shared With Креирано на
</th>
<th className="px-6 py-4 text-left text-sm text-neutral-400">
Created At
</th> </th>
</tr> </tr>
</thead> </thead>
@ -260,7 +277,7 @@ function AdminPanel() {
className="border-b border-primary-700/50 hover:bg-primary-700/30" 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 text-white">{doc.title}</td>
<td className="px-6 py-4"> {/* <td className="px-6 py-4">
<span <span
className={`px-3 py-1 rounded-full text-xs font-medium ${ className={`px-3 py-1 rounded-full text-xs font-medium ${
doc.status === "completed" doc.status === "completed"
@ -274,7 +291,7 @@ function AdminPanel() {
> >
{doc.status} {doc.status}
</span> </span>
</td> </td> */}
<td className="px-6 py-4 text-neutral-300"> <td className="px-6 py-4 text-neutral-300">
{doc.uploadedBy?.name} ({doc.uploadedBy?.email}) {doc.uploadedBy?.name} ({doc.uploadedBy?.email})
</td> </td>
@ -298,6 +315,7 @@ function AdminPanel() {
</tbody> </tbody>
</table> </table>
</div> </div>
</motion.div>
</div> </div>
)} )}
@ -305,27 +323,32 @@ function AdminPanel() {
<> <>
<div className="bg-primary-800/50 backdrop-blur-lg rounded-xl p-6 mb-6 shadow-xl"> <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 className="text-xl font-bold text-white mb-4">
Create New User Креирај корисник
</h2> </h2>
<form <form
ref={createUserFormRef}
onSubmit={handleCreateUser} onSubmit={handleCreateUser}
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4" className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"
> >
<input <input
type="text" type="text"
placeholder="Name" placeholder="Име"
value={newUser.name} value={newUser.name}
id="name"
name="name"
required
onChange={(e) => onChange={(e) =>
setNewUser({ ...newUser, name: e.target.value }) setNewUser({ ...newUser, name: e.target.value })
} }
className="bg-primary-700/30 border border-primary-600 rounded-lg px-4 py-2 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 text-white placeholder-neutral-400 focus:outline-none focus:border-primary-500
focus:ring-1 focus:ring-primary-500" focus:ring-1 focus:ring-primary-500"
required
/> />
<input <input
type="email" type="email"
placeholder="Email" id="email"
name="email"
placeholder="Мејл"
value={newUser.email} value={newUser.email}
onChange={(e) => onChange={(e) =>
setNewUser({ ...newUser, email: e.target.value }) setNewUser({ ...newUser, email: e.target.value })
@ -336,8 +359,10 @@ function AdminPanel() {
required required
/> />
<input <input
type="password" // type="password"
placeholder="Password" id="password"
name="password"
placeholder="Лозинка"
value={newUser.password} value={newUser.password}
onChange={(e) => onChange={(e) =>
setNewUser({ ...newUser, password: e.target.value }) setNewUser({ ...newUser, password: e.target.value })
@ -359,7 +384,7 @@ function AdminPanel() {
focus:ring-primary-500" focus:ring-primary-500"
/> />
<label htmlFor="isAdmin" className="text-white"> <label htmlFor="isAdmin" className="text-white">
Is Admin Дали е Администратор
</label> </label>
</div> </div>
<button <button
@ -369,7 +394,7 @@ function AdminPanel() {
transition-colors shadow-lg" transition-colors shadow-lg"
> >
<FiUserPlus className="w-4 h-4" /> <FiUserPlus className="w-4 h-4" />
<span>Create User</span> <span>Креирај корисник</span>
</button> </button>
</form> </form>
</div> </div>
@ -379,16 +404,16 @@ function AdminPanel() {
<thead> <thead>
<tr className="border-b border-primary-700"> <tr className="border-b border-primary-700">
<th className="px-6 py-4 text-left text-sm text-neutral-400"> <th className="px-6 py-4 text-left text-sm text-neutral-400">
Name Име
</th> </th>
<th className="px-6 py-4 text-left text-sm text-neutral-400"> <th className="px-6 py-4 text-left text-sm text-neutral-400">
Email Мејл
</th> </th>
<th className="px-6 py-4 text-left text-sm text-neutral-400"> <th className="px-6 py-4 text-left text-sm text-neutral-400">
Role Улога
</th> </th>
<th className="px-6 py-4 text-left text-sm text-neutral-400"> <th className="px-6 py-4 text-left text-sm text-neutral-400">
Actions Ресетирај
</th> </th>
</tr> </tr>
</thead> </thead>
@ -421,7 +446,7 @@ function AdminPanel() {
className="flex items-center space-x-1 text-neutral-400 hover:text-white transition-colors" className="flex items-center space-x-1 text-neutral-400 hover:text-white transition-colors"
> >
<FiKey className="w-4 h-4" /> <FiKey className="w-4 h-4" />
<span>Reset Password</span> <span>Ресетирај лозинка</span>
</button> </button>
</td> </td>
</tr> </tr>

View File

@ -1,15 +1,21 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from "react";
import { useAuth } from '../../hooks/useAuth'; import { useAuth } from "../../hooks/useAuth";
import { getSharedDocuments } from '../../services/api'; import { getSharedDocuments } from "../../services/api";
import { format } from 'date-fns'; import { format } from "date-fns";
import { FiFolder, FiFile, FiDownload, FiChevronRight, FiLoader } from 'react-icons/fi'; import {
import { downloadDocument } from '../../services/api'; FiFolder,
FiFile,
FiDownload,
FiChevronRight,
FiLoader,
} from "react-icons/fi";
import { downloadDocument } from "../../services/api";
function Dashboard() { function Dashboard() {
const [documents, setDocuments] = useState({}); const [documents, setDocuments] = useState({});
const [expandedFolders, setExpandedFolders] = useState({}); const [expandedFolders, setExpandedFolders] = useState({});
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(''); const [error, setError] = useState("");
const { user } = useAuth(); const { user } = useAuth();
useEffect(() => { useEffect(() => {
@ -22,8 +28,8 @@ function Dashboard() {
const groupedDocs = groupDocumentsByCompanyAndDate(response.data); const groupedDocs = groupDocumentsByCompanyAndDate(response.data);
setDocuments(groupedDocs); setDocuments(groupedDocs);
} catch (err) { } catch (err) {
console.error('Error fetching documents:', err); console.error("Error fetching documents:", err);
setError('Failed to fetch documents'); setError("Failed to fetch documents");
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -34,12 +40,12 @@ function Dashboard() {
const groupDocumentsByCompanyAndDate = (docs) => { const groupDocumentsByCompanyAndDate = (docs) => {
if (!Array.isArray(docs)) { if (!Array.isArray(docs)) {
console.error('Expected array of documents, received:', docs); console.error("Expected array of documents, received:", docs);
return {}; return {};
} }
return docs.reduce((acc, doc) => { return docs.reduce((acc, doc) => {
const folderName = `${doc.sharedWith?.name || 'Unknown'}-${format(new Date(doc.createdAt), 'yyyy-MM-dd')}`; const folderName = `${doc.sharedWith?.name || user.name}-${format(new Date(doc.createdAt), "dd-MM-yyyy")}`;
if (!acc[folderName]) { if (!acc[folderName]) {
acc[folderName] = []; acc[folderName] = [];
} }
@ -52,15 +58,15 @@ function Dashboard() {
try { try {
await downloadDocument(s3Key); await downloadDocument(s3Key);
} catch (err) { } catch (err) {
console.error('Error downloading document:', err); console.error("Error downloading document:", err);
alert('Failed to download document. Please try again.'); alert("Failed to download document. Please try again.");
} }
}; };
const toggleFolder = (folderName) => { const toggleFolder = (folderName) => {
setExpandedFolders(prev => ({ setExpandedFolders((prev) => ({
...prev, ...prev,
[folderName]: !prev[folderName] [folderName]: !prev[folderName],
})); }));
}; };
@ -69,7 +75,7 @@ function Dashboard() {
<div className="min-h-screen flex items-center justify-center bg-primary-900"> <div className="min-h-screen flex items-center justify-center bg-primary-900">
<div className="flex items-center space-x-3 text-primary-400"> <div className="flex items-center space-x-3 text-primary-400">
<FiLoader className="w-6 h-6 animate-spin" /> <FiLoader className="w-6 h-6 animate-spin" />
<span className="text-lg font-medium">Loading documents...</span> <span className="text-lg font-medium">Се вчитува...</span>
</div> </div>
</div> </div>
); );
@ -89,8 +95,12 @@ function Dashboard() {
<div className="min-h-screen bg-gradient-to-br from-primary-900 to-primary-800 p-6"> <div className="min-h-screen bg-gradient-to-br from-primary-900 to-primary-800 p-6">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
<header className="mb-8 mt-20"> <header className="mb-8 mt-20">
<h1 className="text-3xl font-bold text-white mb-2">Your Documents</h1> <h1 className="text-3xl font-bold text-white mb-2">
<p className="text-neutral-400">Access and manage your shared documents</p> Вашите Документи
</h1>
<p className="text-neutral-400">
{/* Access and manage your shared documents */}
</p>
</header> </header>
<div className="grid gap-6"> <div className="grid gap-6">
@ -109,11 +119,13 @@ function Dashboard() {
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<FiFolder className="w-5 h-5 text-primary-400" /> <FiFolder className="w-5 h-5 text-primary-400" />
<span className="font-medium">{folderName}</span> <span className="font-medium">{folderName}</span>
<span className="text-sm text-neutral-400">({docs.length} files)</span> <span className="text-sm text-neutral-400">
({docs.length} files)
</span>
</div> </div>
<FiChevronRight <FiChevronRight
className={`w-5 h-5 transition-transform duration-200 ${ className={`w-5 h-5 transition-transform duration-200 ${
expandedFolders[folderName] ? 'rotate-90' : '' expandedFolders[folderName] ? "rotate-90" : ""
}`} }`}
/> />
</button> </button>
@ -145,8 +157,10 @@ function Dashboard() {
</div> </div>
)) ))
) : ( ) : (
<div className="text-center py-12 bg-neutral-900/80 backdrop-blur-lg rounded-xl <div
border border-neutral-800"> className="text-center py-12 bg-neutral-900/80 backdrop-blur-lg rounded-xl
border border-neutral-800"
>
<p className="text-neutral-400">No documents available</p> <p className="text-neutral-400">No documents available</p>
</div> </div>
)} )}

View File

@ -64,12 +64,12 @@ function DocumentUpload() {
formData.append("uploadedById", uploadedById); formData.append("uploadedById", uploadedById);
// Debug log // Debug log
console.log("Form Data:", { // console.log("Form Data:", {
title, // title,
sharedWithId, // sharedWithId,
uploadedById, // uploadedById,
fileName: file.name, // fileName: file.name,
}); // });
try { try {
await uploadDocument(formData); await uploadDocument(formData);
@ -90,9 +90,9 @@ function DocumentUpload() {
<div className="max-w-4xl mx-auto"> <div className="max-w-4xl mx-auto">
<header className="mb-8 mt-20"> <header className="mb-8 mt-20">
<h1 className="text-3xl font-bold text-white mb-2"> <h1 className="text-3xl font-bold text-white mb-2">
Upload Document Сподели документ
</h1> </h1>
<p className="text-neutral-400">Share documents with your clients</p> {/* <p className="text-neutral-400">Share documents with your clients</p> */}
</header> </header>
<motion.div <motion.div
@ -104,8 +104,8 @@ function DocumentUpload() {
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
{/* Title Input */} {/* Title Input */}
<div> <div>
<label className="block text-sm font-medium text-white mb-2"> <label className="block text-sm font-medium text-white mb-4">
Document Title Наслов на документот
</label> </label>
<input <input
type="text" type="text"
@ -120,7 +120,7 @@ function DocumentUpload() {
{/* File Input */} {/* File Input */}
<div> <div>
<label className="block text-sm font-medium text-white mb-2"> <label className="block text-sm font-medium text-white mb-2">
File Документ
</label> </label>
<div className="flex items-center justify-center w-full"> <div className="flex items-center justify-center w-full">
<label <label
@ -130,7 +130,7 @@ function DocumentUpload() {
> >
<FiUpload className="w-8 h-8 text-neutral-400" /> <FiUpload className="w-8 h-8 text-neutral-400" />
<span className="mt-2 text-sm text-neutral-400"> <span className="mt-2 text-sm text-neutral-400">
{file ? file.name : "Select a file"} {file ? file.name : "Избери документ"}
</span> </span>
<input <input
type="file" type="file"
@ -145,7 +145,7 @@ function DocumentUpload() {
{/* User Selection */} {/* User Selection */}
<div> <div>
<label className="block text-sm font-medium text-white mb-2"> <label className="block text-sm font-medium text-white mb-2">
Share with User Сподели со
</label> </label>
<div className="relative"> <div className="relative">
<FiUsers className="absolute top-3 left-3 text-neutral-400" /> <FiUsers className="absolute top-3 left-3 text-neutral-400" />
@ -156,7 +156,7 @@ function DocumentUpload() {
text-white focus:outline-none focus:border-primary-500" text-white focus:outline-none focus:border-primary-500"
required required
> >
<option value="">Select a user</option> <option value="">Избери корисник</option>
{availableUsers.map((user) => ( {availableUsers.map((user) => (
<option key={user.id} value={user.id}> <option key={user.id} value={user.id}>
{user.name} ({user.email}) {user.name} ({user.email})
@ -176,7 +176,7 @@ function DocumentUpload() {
{/* Success Message */} {/* Success Message */}
{status === "completed" && ( {status === "completed" && (
<div className="p-4 bg-green-500/10 border border-green-500/20 rounded-lg text-green-200"> <div className="p-4 bg-green-500/10 border border-green-500/20 rounded-lg text-green-200">
Document uploaded successfully! Документот успешно е споделен!
</div> </div>
)} )}
@ -199,7 +199,7 @@ function DocumentUpload() {
Upload Complete Upload Complete
</> </>
) : ( ) : (
"Upload Document" "Прикачи"
)} )}
</button> </button>
</form> </form>