admin panel redisign
This commit is contained in:
parent
8cbdde39c1
commit
1804bdbb99
@ -47,3 +47,5 @@ model Notification {
|
|||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
createdAt DateTime @default(now())
|
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 { PrismaService } from '../prisma/prisma.service';
|
||||||
import { S3Service } from '../s3/s3.service';
|
import { S3Service } from '../s3/s3.service';
|
||||||
import { CreateDocumentDto } from '../dto/create-document.dto';
|
import { CreateDocumentDto } from '../dto/create-document.dto';
|
||||||
@ -85,7 +85,6 @@ export class AdminService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async getAllDocuments() {
|
async getAllDocuments() {
|
||||||
return this.prisma.document.findMany();
|
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(
|
async uploadDocument(
|
||||||
file: Express.Multer.File,
|
file: Express.Multer.File,
|
||||||
title: string,
|
title: string,
|
||||||
authorId: number,
|
userId: number,
|
||||||
) {
|
) {
|
||||||
const key = `documents/${Date.now()}-${file.originalname}`;
|
let s3Key;
|
||||||
const s3Key = await this.s3Service.uploadFile(file, key);
|
try {
|
||||||
|
// First upload to S3
|
||||||
|
s3Key = await this.s3Service.uploadFile(file, 'documents');
|
||||||
|
|
||||||
return this.prisma.document.create({
|
// Then create document with completed status and s3Key
|
||||||
data: {
|
const document = await this.prisma.document.create({
|
||||||
title,
|
data: {
|
||||||
s3Key,
|
title,
|
||||||
authorId,
|
authorId: userId,
|
||||||
status: 'completed',
|
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) {
|
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": "^18.2.15",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@vitejs/plugin-react": "^4.0.3",
|
"@vitejs/plugin-react": "^4.0.3",
|
||||||
"autoprefixer": "^10.4.15",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^8.45.0",
|
"eslint": "^8.45.0",
|
||||||
"eslint-plugin-react": "^7.32.2",
|
"eslint-plugin-react": "^7.32.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.3",
|
"eslint-plugin-react-refresh": "^0.4.3",
|
||||||
"postcss": "^8.4.28",
|
"postcss": "^8.4.47",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.4.14",
|
||||||
"vite": "^4.4.5"
|
"vite": "^4.4.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1166,9 +1166,9 @@
|
|||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
},
|
},
|
||||||
"node_modules/autoprefixer": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.4.15",
|
"version": "10.4.20",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
|
||||||
"integrity": "sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==",
|
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -1185,11 +1185,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"browserslist": "^4.21.10",
|
"browserslist": "^4.23.3",
|
||||||
"caniuse-lite": "^1.0.30001520",
|
"caniuse-lite": "^1.0.30001646",
|
||||||
"fraction.js": "^4.2.0",
|
"fraction.js": "^4.3.7",
|
||||||
"normalize-range": "^0.1.2",
|
"normalize-range": "^0.1.2",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.1",
|
||||||
"postcss-value-parser": "^4.2.0"
|
"postcss-value-parser": "^4.2.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -1262,9 +1262,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.21.10",
|
"version": "4.24.2",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
|
||||||
"integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==",
|
"integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -1281,10 +1281,10 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001517",
|
"caniuse-lite": "^1.0.30001669",
|
||||||
"electron-to-chromium": "^1.4.477",
|
"electron-to-chromium": "^1.5.41",
|
||||||
"node-releases": "^2.0.13",
|
"node-releases": "^2.0.18",
|
||||||
"update-browserslist-db": "^1.0.11"
|
"update-browserslist-db": "^1.1.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"browserslist": "cli.js"
|
"browserslist": "cli.js"
|
||||||
@ -1325,9 +1325,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001523",
|
"version": "1.0.30001673",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001523.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001673.tgz",
|
||||||
"integrity": "sha512-I5q5cisATTPZ1mc588Z//pj/Ox80ERYDfR71YnvY7raS/NOk8xXlZcB0sF7JdqaV//kOaa6aus7lRfpdnt1eBA==",
|
"integrity": "sha512-WTrjUCSMp3LYX0nE12ECkV0a+e6LC85E0Auz75555/qr78Oc8YWhEPNfDd6SHdtlCMSzqtuXY0uyEMNRcsKpKw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -1524,9 +1524,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.503",
|
"version": "1.5.48",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.48.tgz",
|
||||||
"integrity": "sha512-LF2IQit4B0VrUHFeQkWhZm97KuJSGF2WJqq1InpY+ECpFRkXd8yTIaTtJxsO0OKDmiBYwWqcrNaXOurn2T2wiA==",
|
"integrity": "sha512-FXULnNK7ACNI9MTMOVAzUGiz/YrK9Kcb0s/JT4aJgsam7Eh6XYe7Y6q95lPq+VdBe1DpT2eTnfXFtnuPGCks4w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/es-abstract": {
|
"node_modules/es-abstract": {
|
||||||
@ -1682,9 +1682,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
"version": "3.1.1",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@ -2148,16 +2148,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fraction.js": {
|
"node_modules/fraction.js": {
|
||||||
"version": "4.2.1",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||||
"integrity": "sha512-/KxoyCnPM0GwYI4NN0Iag38Tqt+od3/mLuguepLgCAKPn0ZhC544nssAW0tG2/00zXEYl9W+7hwAIpLHo6Oc7Q==",
|
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "*"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "patreon",
|
"type": "patreon",
|
||||||
"url": "https://www.patreon.com/infusion"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fs.realpath": {
|
"node_modules/fs.realpath": {
|
||||||
@ -2815,9 +2815,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jiti": {
|
"node_modules/jiti": {
|
||||||
"version": "1.19.3",
|
"version": "1.21.6",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.3.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
|
||||||
"integrity": "sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w==",
|
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
@ -3078,9 +3078,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.13",
|
"version": "2.0.18",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||||
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
|
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
@ -4007,9 +4007,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.3.3",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz",
|
||||||
"integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==",
|
"integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
@ -4017,10 +4017,10 @@
|
|||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"didyoumean": "^1.2.2",
|
"didyoumean": "^1.2.2",
|
||||||
"dlv": "^1.1.3",
|
"dlv": "^1.1.3",
|
||||||
"fast-glob": "^3.2.12",
|
"fast-glob": "^3.3.0",
|
||||||
"glob-parent": "^6.0.2",
|
"glob-parent": "^6.0.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
"jiti": "^1.18.2",
|
"jiti": "^1.21.0",
|
||||||
"lilconfig": "^2.1.0",
|
"lilconfig": "^2.1.0",
|
||||||
"micromatch": "^4.0.5",
|
"micromatch": "^4.0.5",
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
@ -4210,9 +4210,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.0.11",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
|
||||||
"integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==",
|
"integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -4229,8 +4229,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"escalade": "^3.1.1",
|
"escalade": "^3.2.0",
|
||||||
"picocolors": "^1.0.0"
|
"picocolors": "^1.1.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"update-browserslist-db": "cli.js"
|
"update-browserslist-db": "cli.js"
|
||||||
|
|||||||
@ -22,13 +22,13 @@
|
|||||||
"@types/react": "^18.2.15",
|
"@types/react": "^18.2.15",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@vitejs/plugin-react": "^4.0.3",
|
"@vitejs/plugin-react": "^4.0.3",
|
||||||
"autoprefixer": "^10.4.15",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^8.45.0",
|
"eslint": "^8.45.0",
|
||||||
"eslint-plugin-react": "^7.32.2",
|
"eslint-plugin-react": "^7.32.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.3",
|
"eslint-plugin-react-refresh": "^0.4.3",
|
||||||
"postcss": "^8.4.28",
|
"postcss": "^8.4.47",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.4.14",
|
||||||
"vite": "^4.4.5"
|
"vite": "^4.4.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,164 +1,238 @@
|
|||||||
|
// 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 [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>
|
||||||
|
// <DocumentUpload />
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export default AdminPanel;
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { getAllUsers, uploadDocument } from '../../services/api';
|
import { getAllUsers, getAllDocuments } from '../../services/api';
|
||||||
|
import DocumentUpload from '../documentUpload/DocumentUpload';
|
||||||
|
|
||||||
function AdminPanel() {
|
function AdminPanel() {
|
||||||
|
const [activeTab, setActiveTab] = useState('documents');
|
||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
const [file, setFile] = useState(null);
|
const [documents, setDocuments] = useState([]);
|
||||||
const [title, setTitle] = useState('');
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUsers();
|
fetchData();
|
||||||
}, []);
|
}, [activeTab]);
|
||||||
|
|
||||||
const fetchUsers = async () => {
|
const fetchData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError('');
|
||||||
try {
|
try {
|
||||||
const response = await getAllUsers();
|
if (activeTab === 'users') {
|
||||||
setUsers(response.data);
|
const response = await getAllUsers();
|
||||||
} catch (error) {
|
setUsers(response.data);
|
||||||
console.error('Failed to fetch users:', error);
|
} 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 handleFileUpload = async (e) => {
|
const getStatusColor = (status) => {
|
||||||
e.preventDefault();
|
const colors = {
|
||||||
if (!file) return;
|
pending: 'bg-yellow-100 text-yellow-800',
|
||||||
|
uploading: 'bg-blue-100 text-blue-800',
|
||||||
const formData = new FormData();
|
completed: 'bg-green-100 text-green-800',
|
||||||
formData.append('file', file);
|
failed: 'bg-red-100 text-red-800'
|
||||||
formData.append('title', title);
|
};
|
||||||
|
return colors[status] || 'bg-gray-100 text-gray-800';
|
||||||
try {
|
|
||||||
await uploadDocument(formData);
|
|
||||||
alert('Document uploaded successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to upload document:', error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<h2>Users</h2>
|
<div className="bg-white rounded-lg shadow">
|
||||||
<ul>
|
{/* Admin Header */}
|
||||||
{users.map(user => (
|
<div className="px-6 py-4 border-b border-gray-200">
|
||||||
<li key={user.id}>{user.name} ({user.email})</li>
|
<h1 className="text-2xl font-bold text-gray-900">Admin Dashboard</h1>
|
||||||
))}
|
</div>
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Upload Document</h2>
|
{/* Tab Navigation */}
|
||||||
<form onSubmit={handleFileUpload}>
|
<div className="border-b border-gray-200">
|
||||||
<input
|
<nav className="flex -mb-px">
|
||||||
type="text"
|
<button
|
||||||
value={title}
|
onClick={() => setActiveTab('documents')}
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
className={`px-6 py-3 border-b-2 font-medium text-sm ${
|
||||||
placeholder="Document Title"
|
activeTab === 'documents'
|
||||||
required
|
? 'border-indigo-500 text-indigo-600'
|
||||||
/>
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||||
<input
|
}`}
|
||||||
type="file"
|
>
|
||||||
onChange={(e) => setFile(e.target.files[0])}
|
Documents
|
||||||
required
|
</button>
|
||||||
/>
|
<button
|
||||||
<button type="submit">Upload</button>
|
onClick={() => setActiveTab('users')}
|
||||||
</form>
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdminPanel;
|
export default AdminPanel;
|
||||||
|
|
||||||
// import { useState } from 'react';
|
|
||||||
// import { uploadDocument } from '../../services/api';
|
|
||||||
|
|
||||||
// const AdminPanel = () => {
|
|
||||||
// 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]);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleSubmit = async (event) => {
|
|
||||||
// event.preventDefault();
|
|
||||||
// if (!file || !title) {
|
|
||||||
// alert('Please select a file and enter a title');
|
|
||||||
// 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('');
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error('Upload failed:', error);
|
|
||||||
// alert('Upload failed. Please try again.');
|
|
||||||
// } finally {
|
|
||||||
// setIsUploading(false);
|
|
||||||
// setProgress(0);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// 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>
|
|
||||||
// )}
|
|
||||||
// </form>
|
|
||||||
// </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 createUser = (userData) => api.post('/admin/users', userData);
|
||||||
export const login = (username, password) => api.post('/auth/login', { username, password });
|
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 shareDocument = (documentId, userIds) => api.post(`/admin/documents/${documentId}/share`, { userIds });
|
||||||
export const updateDocumentStatus = (documentId, status) => api.put(`/admin/documents/${documentId}/status`, { status });
|
export const updateDocumentStatus = (documentId, status) => api.put(`/admin/documents/${documentId}/status`, { status });
|
||||||
export const uploadDocument = (formData) => api.post('/admin/documents', formData, {
|
// export const uploadDocument = (formData) => api.post('/admin/documents', formData, {
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
// 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');
|
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;
|
export default api;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user