admin panel redisign
This commit is contained in:
parent
8cbdde39c1
commit
1804bdbb99
@ -46,4 +46,6 @@ model Notification {
|
||||
userId Int
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { S3Service } from '../s3/s3.service';
|
||||
import { CreateDocumentDto } from '../dto/create-document.dto';
|
||||
@ -85,7 +85,6 @@ export class AdminService {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async getAllDocuments() {
|
||||
return this.prisma.document.findMany();
|
||||
}
|
||||
@ -172,22 +171,91 @@ export class AdminService {
|
||||
});
|
||||
}
|
||||
|
||||
// async uploadDocument(
|
||||
// file: Express.Multer.File,
|
||||
// title: string,
|
||||
// userId: number,
|
||||
// ) {
|
||||
// try {
|
||||
// // First create document with pending status
|
||||
// const document = await this.prisma.document.create({
|
||||
// data: {
|
||||
// title,
|
||||
// authorId: userId,
|
||||
// status: 'pending',
|
||||
// s3Key: '', // Temporary empty key
|
||||
// },
|
||||
// });
|
||||
|
||||
// // Update status to uploading
|
||||
// await this.prisma.document.update({
|
||||
// where: { id: document.id },
|
||||
// data: { status: 'uploading' },
|
||||
// });
|
||||
|
||||
// // Upload to S3
|
||||
// const s3Key = await this.s3Service.uploadFile(file, 'documents');
|
||||
|
||||
// // Update document with s3Key and completed status
|
||||
// return this.prisma.document.update({
|
||||
// where: { id: document.id },
|
||||
// data: {
|
||||
// s3Key,
|
||||
// status: 'completed',
|
||||
// },
|
||||
// });
|
||||
// } catch (error) {
|
||||
// // If anything fails, update status to failed
|
||||
// const document = await this.prisma.document.findFirst({
|
||||
// where: { title, authorId: userId },
|
||||
// });
|
||||
// if (document) {
|
||||
// await this.prisma.document.update({
|
||||
// where: { id: document.id },
|
||||
// data: { status: 'failed' },
|
||||
// });
|
||||
// }
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
// problem whith upload status writing to db, i will fix it later
|
||||
|
||||
async uploadDocument(
|
||||
file: Express.Multer.File,
|
||||
title: string,
|
||||
authorId: number,
|
||||
userId: number,
|
||||
) {
|
||||
const key = `documents/${Date.now()}-${file.originalname}`;
|
||||
const s3Key = await this.s3Service.uploadFile(file, key);
|
||||
let s3Key;
|
||||
try {
|
||||
// First upload to S3
|
||||
s3Key = await this.s3Service.uploadFile(file, 'documents');
|
||||
|
||||
return this.prisma.document.create({
|
||||
data: {
|
||||
title,
|
||||
s3Key,
|
||||
authorId,
|
||||
status: 'completed',
|
||||
},
|
||||
});
|
||||
// Then create document with completed status and s3Key
|
||||
const document = await this.prisma.document.create({
|
||||
data: {
|
||||
title,
|
||||
authorId: userId,
|
||||
s3Key,
|
||||
status: 'completed', // Set status to completed immediately after successful upload
|
||||
},
|
||||
});
|
||||
|
||||
return document;
|
||||
} catch (error) {
|
||||
// Create document with failed status if upload fails
|
||||
if (title && userId) {
|
||||
await this.prisma.document.create({
|
||||
data: {
|
||||
title,
|
||||
authorId: userId,
|
||||
s3Key: s3Key || '',
|
||||
status: 'failed',
|
||||
},
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getDocumentUrl(documentId: number) {
|
||||
|
||||
92
frontend/imk/package-lock.json
generated
92
frontend/imk/package-lock.json
generated
@ -20,13 +20,13 @@
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"postcss": "^8.4.28",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"postcss": "^8.4.47",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"vite": "^4.4.5"
|
||||
}
|
||||
},
|
||||
@ -1166,9 +1166,9 @@
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.15",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz",
|
||||
"integrity": "sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==",
|
||||
"version": "10.4.20",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
|
||||
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1185,11 +1185,11 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"browserslist": "^4.21.10",
|
||||
"caniuse-lite": "^1.0.30001520",
|
||||
"fraction.js": "^4.2.0",
|
||||
"browserslist": "^4.23.3",
|
||||
"caniuse-lite": "^1.0.30001646",
|
||||
"fraction.js": "^4.3.7",
|
||||
"normalize-range": "^0.1.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"picocolors": "^1.0.1",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"bin": {
|
||||
@ -1262,9 +1262,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.21.10",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz",
|
||||
"integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==",
|
||||
"version": "4.24.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
|
||||
"integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1281,10 +1281,10 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001517",
|
||||
"electron-to-chromium": "^1.4.477",
|
||||
"node-releases": "^2.0.13",
|
||||
"update-browserslist-db": "^1.0.11"
|
||||
"caniuse-lite": "^1.0.30001669",
|
||||
"electron-to-chromium": "^1.5.41",
|
||||
"node-releases": "^2.0.18",
|
||||
"update-browserslist-db": "^1.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
@ -1325,9 +1325,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001523",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001523.tgz",
|
||||
"integrity": "sha512-I5q5cisATTPZ1mc588Z//pj/Ox80ERYDfR71YnvY7raS/NOk8xXlZcB0sF7JdqaV//kOaa6aus7lRfpdnt1eBA==",
|
||||
"version": "1.0.30001673",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001673.tgz",
|
||||
"integrity": "sha512-WTrjUCSMp3LYX0nE12ECkV0a+e6LC85E0Auz75555/qr78Oc8YWhEPNfDd6SHdtlCMSzqtuXY0uyEMNRcsKpKw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1524,9 +1524,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.503",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz",
|
||||
"integrity": "sha512-LF2IQit4B0VrUHFeQkWhZm97KuJSGF2WJqq1InpY+ECpFRkXd8yTIaTtJxsO0OKDmiBYwWqcrNaXOurn2T2wiA==",
|
||||
"version": "1.5.48",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.48.tgz",
|
||||
"integrity": "sha512-FXULnNK7ACNI9MTMOVAzUGiz/YrK9Kcb0s/JT4aJgsam7Eh6XYe7Y6q95lPq+VdBe1DpT2eTnfXFtnuPGCks4w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es-abstract": {
|
||||
@ -1682,9 +1682,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@ -2148,16 +2148,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.1.tgz",
|
||||
"integrity": "sha512-/KxoyCnPM0GwYI4NN0Iag38Tqt+od3/mLuguepLgCAKPn0ZhC544nssAW0tG2/00zXEYl9W+7hwAIpLHo6Oc7Q==",
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/infusion"
|
||||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
@ -2815,9 +2815,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "1.19.3",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.3.tgz",
|
||||
"integrity": "sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w==",
|
||||
"version": "1.21.6",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
|
||||
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
@ -3078,9 +3078,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
||||
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
@ -4007,9 +4007,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
|
||||
"integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==",
|
||||
"version": "3.4.14",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz",
|
||||
"integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
@ -4017,10 +4017,10 @@
|
||||
"chokidar": "^3.5.3",
|
||||
"didyoumean": "^1.2.2",
|
||||
"dlv": "^1.1.3",
|
||||
"fast-glob": "^3.2.12",
|
||||
"fast-glob": "^3.3.0",
|
||||
"glob-parent": "^6.0.2",
|
||||
"is-glob": "^4.0.3",
|
||||
"jiti": "^1.18.2",
|
||||
"jiti": "^1.21.0",
|
||||
"lilconfig": "^2.1.0",
|
||||
"micromatch": "^4.0.5",
|
||||
"normalize-path": "^3.0.0",
|
||||
@ -4210,9 +4210,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
|
||||
"integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
|
||||
"integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -4229,8 +4229,8 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"escalade": "^3.1.1",
|
||||
"picocolors": "^1.0.0"
|
||||
"escalade": "^3.2.0",
|
||||
"picocolors": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"update-browserslist-db": "cli.js"
|
||||
|
||||
@ -22,13 +22,13 @@
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"postcss": "^8.4.28",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"postcss": "^8.4.47",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"vite": "^4.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,164 +1,238 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getAllUsers, uploadDocument } from '../../services/api';
|
||||
|
||||
function AdminPanel() {
|
||||
const [users, setUsers] = useState([]);
|
||||
const [file, setFile] = useState(null);
|
||||
const [title, setTitle] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
fetchUsers();
|
||||
}, []);
|
||||
|
||||
const fetchUsers = async () => {
|
||||
try {
|
||||
const response = await getAllUsers();
|
||||
setUsers(response.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch users:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileUpload = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('title', title);
|
||||
|
||||
try {
|
||||
await uploadDocument(formData);
|
||||
alert('Document uploaded successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to upload document:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Users</h2>
|
||||
<ul>
|
||||
{users.map(user => (
|
||||
<li key={user.id}>{user.name} ({user.email})</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<h2>Upload Document</h2>
|
||||
<form onSubmit={handleFileUpload}>
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="Document Title"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="file"
|
||||
onChange={(e) => setFile(e.target.files[0])}
|
||||
required
|
||||
/>
|
||||
<button type="submit">Upload</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AdminPanel;
|
||||
|
||||
// import { useState } from 'react';
|
||||
// import { uploadDocument } from '../../services/api';
|
||||
|
||||
// const AdminPanel = () => {
|
||||
// import { useState, useEffect } from 'react';
|
||||
// import { getAllUsers, uploadDocument } from '../../services/api';
|
||||
// import DocumentUpload from '../documentUpload/DocumentUpload';
|
||||
// function AdminPanel() {
|
||||
// const [users, setUsers] = useState([]);
|
||||
// const [file, setFile] = useState(null);
|
||||
// const [progress, setProgress] = useState(0);
|
||||
// const [title, setTitle] = useState('');
|
||||
// const [content, setContent] = useState('');
|
||||
// const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
// const handleFileChange = (event) => {
|
||||
// if (event.target.files) {
|
||||
// setFile(event.target.files[0]);
|
||||
// useEffect(() => {
|
||||
// fetchUsers();
|
||||
// }, []);
|
||||
|
||||
// const fetchUsers = async () => {
|
||||
// try {
|
||||
// const response = await getAllUsers();
|
||||
// setUsers(response.data);
|
||||
// } catch (error) {
|
||||
// console.error('Failed to fetch users:', error);
|
||||
// }
|
||||
// };
|
||||
|
||||
// const handleSubmit = async (event) => {
|
||||
// event.preventDefault();
|
||||
// if (!file || !title) {
|
||||
// alert('Please select a file and enter a title');
|
||||
// return;
|
||||
// }
|
||||
// const handleFileUpload = async (e) => {
|
||||
// e.preventDefault();
|
||||
// if (!file) return;
|
||||
|
||||
// const formData = new FormData();
|
||||
// formData.append('file', file);
|
||||
// formData.append('title', title);
|
||||
// formData.append('content', content);
|
||||
|
||||
// setIsUploading(true);
|
||||
// try {
|
||||
// await uploadDocument(formData, (progressEvent) => {
|
||||
// const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
||||
// setProgress(percentCompleted);
|
||||
// });
|
||||
|
||||
// alert('Document uploaded successfully!');
|
||||
// setProgress(0);
|
||||
// setFile(null);
|
||||
// setTitle('');
|
||||
// setContent('');
|
||||
// await uploadDocument(formData);
|
||||
// alert('Document uploaded successfully');
|
||||
// } catch (error) {
|
||||
// console.error('Upload failed:', error);
|
||||
// alert('Upload failed. Please try again.');
|
||||
// } finally {
|
||||
// setIsUploading(false);
|
||||
// setProgress(0);
|
||||
// console.error('Failed to upload document:', error);
|
||||
// }
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <div className="admin-panel">
|
||||
// <h2>Admin Panel - Upload Document</h2>
|
||||
// <form onSubmit={handleSubmit}>
|
||||
// <div>
|
||||
// <label htmlFor="title">Document Title:</label>
|
||||
// <input
|
||||
// type="text"
|
||||
// id="title"
|
||||
// value={title}
|
||||
// onChange={(e) => setTitle(e.target.value)}
|
||||
// required
|
||||
// />
|
||||
// </div>
|
||||
// <div>
|
||||
// <label htmlFor="content">Document Content:</label>
|
||||
// <textarea
|
||||
// id="content"
|
||||
// value={content}
|
||||
// onChange={(e) => setContent(e.target.value)}
|
||||
// rows="4"
|
||||
// />
|
||||
// </div>
|
||||
// <div>
|
||||
// <label htmlFor="file">Select File:</label>
|
||||
// <input
|
||||
// type="file"
|
||||
// id="file"
|
||||
// onChange={handleFileChange}
|
||||
// required
|
||||
// />
|
||||
// </div>
|
||||
// <button type="submit" disabled={isUploading}>
|
||||
// {isUploading ? 'Uploading...' : 'Upload Document'}
|
||||
// </button>
|
||||
// {progress > 0 && (
|
||||
// <div>
|
||||
// <progress value={progress} max="100" />
|
||||
// <p>{progress}% uploaded</p>
|
||||
// </div>
|
||||
// )}
|
||||
// <div>
|
||||
// <h2>Users</h2>
|
||||
// <ul>
|
||||
// {users.map(user => (
|
||||
// <li key={user.id}>{user.name} ({user.email})</li>
|
||||
// ))}
|
||||
// </ul>
|
||||
|
||||
// <h2>Upload Document</h2>
|
||||
// <form onSubmit={handleFileUpload}>
|
||||
// <input
|
||||
// type="text"
|
||||
// value={title}
|
||||
// onChange={(e) => setTitle(e.target.value)}
|
||||
// placeholder="Document Title"
|
||||
// required
|
||||
// />
|
||||
// <input
|
||||
// type="file"
|
||||
// onChange={(e) => setFile(e.target.files[0])}
|
||||
// required
|
||||
// />
|
||||
// <button type="submit">Upload</button>
|
||||
// </form>
|
||||
// <DocumentUpload />
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
// }
|
||||
|
||||
// export default AdminPanel;
|
||||
// export default AdminPanel;
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getAllUsers, getAllDocuments } from '../../services/api';
|
||||
import DocumentUpload from '../documentUpload/DocumentUpload';
|
||||
|
||||
function AdminPanel() {
|
||||
const [activeTab, setActiveTab] = useState('documents');
|
||||
const [users, setUsers] = useState([]);
|
||||
const [documents, setDocuments] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [activeTab]);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
if (activeTab === 'users') {
|
||||
const response = await getAllUsers();
|
||||
setUsers(response.data);
|
||||
} else if (activeTab === 'documents') {
|
||||
const response = await getAllDocuments();
|
||||
setDocuments(response.data);
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to fetch data. Please try again.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
pending: 'bg-yellow-100 text-yellow-800',
|
||||
uploading: 'bg-blue-100 text-blue-800',
|
||||
completed: 'bg-green-100 text-green-800',
|
||||
failed: 'bg-red-100 text-red-800'
|
||||
};
|
||||
return colors[status] || 'bg-gray-100 text-gray-800';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
{/* Admin Header */}
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<h1 className="text-2xl font-bold text-gray-900">Admin Dashboard</h1>
|
||||
</div>
|
||||
|
||||
{/* Tab Navigation */}
|
||||
<div className="border-b border-gray-200">
|
||||
<nav className="flex -mb-px">
|
||||
<button
|
||||
onClick={() => setActiveTab('documents')}
|
||||
className={`px-6 py-3 border-b-2 font-medium text-sm ${
|
||||
activeTab === 'documents'
|
||||
? 'border-indigo-500 text-indigo-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
Documents
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('users')}
|
||||
className={`px-6 py-3 border-b-2 font-medium text-sm ${
|
||||
activeTab === 'users'
|
||||
? 'border-indigo-500 text-indigo-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
Users
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('upload')}
|
||||
className={`px-6 py-3 border-b-2 font-medium text-sm ${
|
||||
activeTab === 'upload'
|
||||
? 'border-indigo-500 text-indigo-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
Upload Document
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Content Area */}
|
||||
<div className="p-6">
|
||||
{error && (
|
||||
<div className="mb-4 p-4 bg-red-100 text-red-700 rounded-md">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600"></div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{activeTab === 'documents' && (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Title</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Author</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Created At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{documents.map((doc) => (
|
||||
<tr key={doc.id}>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{doc.title}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${getStatusColor(doc.status)}`}>
|
||||
{doc.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{doc.authorId}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{new Date(doc.createdAt).toLocaleDateString()}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'users' && (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{users.map((user) => (
|
||||
<tr key={user.id}>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{user.name}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{user.email}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${user.isAdmin ? 'bg-purple-100 text-purple-800' : 'bg-gray-100 text-gray-800'}`}>
|
||||
{user.isAdmin ? 'Admin' : 'User'}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'upload' && <DocumentUpload />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AdminPanel;
|
||||
108
frontend/imk/src/components/documentUpload/DocumentUpload.jsx
Normal file
108
frontend/imk/src/components/documentUpload/DocumentUpload.jsx
Normal file
@ -0,0 +1,108 @@
|
||||
import { useState } from 'react';
|
||||
import { uploadDocument } from '../../services/api';
|
||||
|
||||
function DocumentUpload() {
|
||||
const [file, setFile] = useState(null);
|
||||
const [title, setTitle] = useState('');
|
||||
const [status, setStatus] = useState('idle'); // idle, uploading, completed, failed
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
|
||||
const handleFileChange = (event) => {
|
||||
const selectedFile = event.target.files[0];
|
||||
setFile(selectedFile);
|
||||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
if (!file || !title) {
|
||||
setErrorMessage('Please provide both a title and a file');
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus('uploading');
|
||||
setErrorMessage('');
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('title', title);
|
||||
|
||||
try {
|
||||
await uploadDocument(formData);
|
||||
setStatus('completed');
|
||||
setTitle('');
|
||||
setFile(null);
|
||||
// Reset form
|
||||
event.target.reset();
|
||||
} catch (error) {
|
||||
setStatus('failed');
|
||||
setErrorMessage(error.response?.data?.message || 'Upload failed. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 className="text-2xl font-bold mb-6">Upload Document</h2>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Document Title
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
File
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
onChange={handleFileChange}
|
||||
className="mt-1 block w-full"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{errorMessage && (
|
||||
<div className="text-red-500 text-sm mt-2">
|
||||
{errorMessage}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={status === 'uploading'}
|
||||
className={`w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white
|
||||
${status === 'uploading'
|
||||
? 'bg-gray-400 cursor-not-allowed'
|
||||
: 'bg-indigo-600 hover:bg-indigo-700'
|
||||
}`}
|
||||
>
|
||||
{status === 'uploading' ? (
|
||||
<>
|
||||
<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>
|
||||
Uploading...
|
||||
</>
|
||||
) : 'Upload Document'}
|
||||
</button>
|
||||
|
||||
{status === 'completed' && (
|
||||
<div className="text-green-500 text-sm mt-2">
|
||||
Document uploaded successfully!
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DocumentUpload;
|
||||
@ -16,12 +16,26 @@ api.interceptors.request.use((config) => {
|
||||
});
|
||||
export const createUser = (userData) => api.post('/admin/users', userData);
|
||||
export const login = (username, password) => api.post('/auth/login', { username, password });
|
||||
export const getAllUsers = () => api.get('/admin/users');
|
||||
// export const getAllUsers = () => api.get('/admin/users');
|
||||
export const shareDocument = (documentId, userIds) => api.post(`/admin/documents/${documentId}/share`, { userIds });
|
||||
export const updateDocumentStatus = (documentId, status) => api.put(`/admin/documents/${documentId}/status`, { status });
|
||||
export const uploadDocument = (formData) => api.post('/admin/documents', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
// export const uploadDocument = (formData) => api.post('/admin/documents', formData, {
|
||||
// headers: { 'Content-Type': 'multipart/form-data' },
|
||||
// });
|
||||
|
||||
|
||||
export const uploadDocument = async (formData) => {
|
||||
const response = await api.post('/admin/documents', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
export const getUserInfo = () => api.get('/auth/user-info');
|
||||
// ... existing code ...
|
||||
|
||||
export const getAllDocuments = () => api.get('/admin/documents');
|
||||
export const getAllUsers = () => api.get('/admin/users');
|
||||
|
||||
export default api;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user