till download files, everything works

This commit is contained in:
dimitar committed 2024-11-01 22:13:14 +01:00
1 parent ce893a4185
commit ed1a580b09
6050 files changed
+768543 -177

No files matched your search

@@ -1,45 +1,153 @@
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { getUserInfo } from '../../services/api';
import * as jwtDecode from 'jwt-decode';
import { motion } from 'framer-motion';
import { format } from 'date-fns';
import { FiFolder, FiDownload, FiChevronRight, FiChevronDown } from 'react-icons/fi';
import { useAuth } from '../../hooks/useAuth';
import { getSharedDocuments } from '../../services/api';
function Dashboard() {
const [userInfo, setUserInfo] = useState(null);
const [documents, setDocuments] = useState([]);
const [expandedFolders, setExpandedFolders] = useState({});
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const { user } = useAuth();
useEffect(() => {
fetchUserInfo();
}, []);
const fetchDocuments = async () => {
try {
const response = await getSharedDocuments(user.id);
const groupedDocs = groupDocumentsByCompanyAndDate(response.data);
setDocuments(groupedDocs);
} catch (err) {
setError('Failed to fetch documents');
console.error('Error fetching documents:', err);
} finally {
setLoading(false);
}
};
const fetchUserInfo = async () => {
if (user) {
fetchDocuments();
}
}, [user]);
const groupDocumentsByCompanyAndDate = (docs) => {
return docs.reduce((acc, doc) => {
const folderName = `${doc.sharedWith?.name || 'Unknown'}-${format(new Date(doc.createdAt), 'yyyy-MM-dd')}`;
if (!acc[folderName]) {
acc[folderName] = [];
}
acc[folderName].push(doc);
return acc;
}, {});
};
const handleDownload = async (s3Key, fileName) => {
try {
const token = localStorage.getItem('token');
const decodedToken = jwtDecode.jwtDecode(token);
const response = await getUserInfo(decodedToken.sub);
setUserInfo(response.data);
} catch (error) {
console.error('Failed to fetch user info:', error);
window.open(`http://localhost:3000/documents/shared/download/${s3Key}`, '_blank');
} catch (err) {
console.error('Error downloading document:', err);
alert('Failed to download document. Please try again.');
}
};
if (!userInfo) {
return <div>Loading...</div>;
const toggleFolder = (folderName) => {
setExpandedFolders(prev => ({
...prev,
[folderName]: !prev[folderName]
}));
};
if (loading) {
return (
<div className="flex justify-center items-center min-h-screen">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
</div>
);
}
if (error) {
return (
<div className="text-center text-red-600 p-4">
{error}
</div>
);
}
return (
<div className="dashboard">
<h1>Welcome, {userInfo.name}!</h1>
<p>Email: {userInfo.email}</p>
<div className="container mx-auto px-4 py-8">
<h1 className="text-2xl font-bold mb-6">Your Laboratory Reports</h1>
<h2>Quick Links</h2>
<ul>
<li><Link to="/documents">My Documents</Link></li>
<li><Link to="/upload">Upload Document</Link></li>
{userInfo.isAdmin && <li><Link to="/admin">Admin Panel</Link></li>}
</ul>
{Object.entries(documents).length === 0 ? (
<p className="text-gray-600">No documents have been shared with you yet.</p>
) : (
<div className="grid gap-4">
{Object.entries(documents).map(([folderName, folderDocs]) => (
<motion.div
key={folderName}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="bg-white p-4 rounded-lg shadow-md"
>
<button
onClick={() => toggleFolder(folderName)}
className="w-full flex items-center text-left p-2 hover:bg-gray-50 rounded-md"
>
<FiFolder className="text-blue-600 mr-2" />
{expandedFolders[folderName] ? (
<FiChevronDown className="mr-2" />
) : (
<FiChevronRight className="mr-2" />
)}
<span className="font-medium">{folderName}</span>
<span className="ml-2 text-sm text-gray-500">
({folderDocs.length} {folderDocs.length === 1 ? 'report' : 'reports'})
</span>
</button>
<h2>Recent Activity</h2>
{/* You can add a list of recent activities here */}
<p>No recent activity to display.</p>
{expandedFolders[folderName] && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
className="mt-2 space-y-2 pl-8"
>
{folderDocs.map((doc) => (
<div
key={doc.id}
className="bg-white p-6 rounded-lg shadow-md hover:shadow-lg transition-shadow"
>
<div className="flex justify-between items-start mb-4">
<h2 className="text-xl font-semibold">{doc.title}</h2>
<span className={`px-2 py-1 text-xs rounded-full ${
doc.status === 'completed'
? 'bg-green-100 text-green-800'
: 'bg-yellow-100 text-yellow-800'
}`}>
{doc.status}
</span>
</div>
<div className="text-sm text-gray-600">
<p>Created: {new Date(doc.createdAt).toLocaleDateString()}</p>
</div>
<div className="mt-4 flex justify-end">
<button
onClick={() => handleDownload(doc.s3Key, doc.title)}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition-colors"
>
<FiDownload className="inline-block mr-2" />
Download
</button>
</div>
</div>
))}
</motion.div>
)}
</motion.div>
))}
</div>
)}
</div>
);
}
+144 -27
View File
@@ -1,42 +1,159 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { login } from '../../services/api';
import { motion } from 'framer-motion';
import axios from 'axios';
function Login() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const Login = () => {
const navigate = useNavigate();
const [formData, setFormData] = useState({
username: '',
password: ''
});
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
setError('');
};
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
setError('');
try {
const response = await login(username, password);
localStorage.setItem('token', response.data.access_token);
navigate('/dashboard');
} catch (error) {
console.error('Login failed:', error);
const response = await axios.post('http://localhost:3000/auth/login', formData, {
headers: {
'Content-Type': 'application/json',
},
});
if (response.data.access_token) {
// Store the token
localStorage.setItem('token', response.data.access_token);
// Redirect based on user role (assuming you have this info)
const isAdmin = response.data.isAdmin; // Adjust based on your API response
navigate(isAdmin ? '/admin' : '/dashboard');
} else {
setError('Invalid response from server');
}
} catch (err) {
setError(
err.response?.data?.message ||
'Неуспешна најава. Проверете ги вашите податоци.'
);
} finally {
setIsLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Username"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
<button type="submit">Login</button>
</form>
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white flex items-center justify-center px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="max-w-md w-full space-y-8 bg-white p-8 rounded-xl shadow-lg"
>
{/* Header */}
<div className="text-center">
<h2 className="text-3xl font-bold text-gray-900">
Најавете се
</h2>
<p className="mt-2 text-sm text-gray-600">
Внесете ги вашите податоци за најава
</p>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
<div className="space-y-4">
{/* Username Field */}
<div>
<label
htmlFor="username"
className="block text-sm font-medium text-gray-700"
>
Корисничко име
</label>
<input
id="username"
name="username"
type="text"
required
value={formData.username}
onChange={handleInputChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm
focus:border-blue-500 focus:ring-blue-500 sm:text-sm
px-3 py-2 border"
aria-label="Username"
/>
</div>
{/* Password Field */}
<div>
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700"
>
Лозинка
</label>
<input
id="password"
name="password"
type="password"
required
value={formData.password}
onChange={handleInputChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm
focus:border-blue-500 focus:ring-blue-500 sm:text-sm
px-3 py-2 border"
aria-label="Password"
/>
</div>
</div>
{/* Error Message */}
{error && (
<div
className="text-red-500 text-sm text-center bg-red-50 p-2 rounded"
role="alert"
>
{error}
</div>
)}
{/* Submit Button */}
<button
type="submit"
disabled={isLoading}
className="w-full flex justify-center py-3 px-4 border border-transparent
rounded-full text-sm font-semibold text-white bg-blue-600
hover:bg-blue-700 focus:outline-none focus:ring-2
focus:ring-offset-2 focus:ring-blue-500 transition-colors
duration-300 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? (
<span className="flex items-center">
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Најава во тек...
</span>
) : (
'Најави се'
)}
</button>
</form>
</motion.div>
</div>
);
}
};
export default Login;