add BE, FE Document
This commit is contained in:
parent
2561d39b4a
commit
0c5f56d30c
|
|
@ -0,0 +1,163 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Admin\app\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Traits\HasFilterRequest;
|
||||||
|
use App\Traits\HasOrderByRequest;
|
||||||
|
use App\Traits\HasSearchRequest;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Modules\Admin\app\Models\Document;
|
||||||
|
|
||||||
|
class DocumentController extends Controller
|
||||||
|
{
|
||||||
|
use HasOrderByRequest;
|
||||||
|
use HasFilterRequest;
|
||||||
|
use HasSearchRequest;
|
||||||
|
|
||||||
|
public function all(Request $request)
|
||||||
|
{
|
||||||
|
$documents = new Document;
|
||||||
|
|
||||||
|
// Order by
|
||||||
|
$this->orderByRequest($documents, $request);
|
||||||
|
$documents->orderBy('title', 'asc');
|
||||||
|
// Filter
|
||||||
|
$this->filterRequest(
|
||||||
|
builder: $documents,
|
||||||
|
request: $request,
|
||||||
|
filterKeys: [
|
||||||
|
'title' => self::F_TEXT,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$this->searchRequest(
|
||||||
|
builder: $documents,
|
||||||
|
value: $request->get('search'),
|
||||||
|
fields: [
|
||||||
|
'title',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$responseData = $documents->get();
|
||||||
|
return AbstractController::ResultSuccess($responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'title' => 'required|string|max:255',
|
||||||
|
'type' => 'required|in:file,link',
|
||||||
|
'uri' => 'nullable|array',
|
||||||
|
'uri.*' => 'nullable|url',
|
||||||
|
'files' => 'nullable|array',
|
||||||
|
'files.*' => 'file|mimes:doc,docx,xls,xlsx,pdf|max:20480',
|
||||||
|
'is_active' => 'required|boolean',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if($request->type == "file") {
|
||||||
|
$uploadedFiles = [];
|
||||||
|
if ($request->hasFile('files')) {
|
||||||
|
foreach ($request->file('files') as $file) {
|
||||||
|
$path = $file->store('uploads', options: 'public');
|
||||||
|
$uploadedFiles[] = "storage/{$path}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$document = Document::create([
|
||||||
|
'title' => $request->title,
|
||||||
|
'type' => $request->type,
|
||||||
|
'uri' => implode(',', $uploadedFiles),
|
||||||
|
'is_active' => $request->is_active,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return AbstractController::ResultSuccess($document, "Document created successfully!");
|
||||||
|
}
|
||||||
|
|
||||||
|
$document = Document::create([
|
||||||
|
'title' => $request->title,
|
||||||
|
'type' => $request->type,
|
||||||
|
'uri' => implode(',', $request->uri),
|
||||||
|
'is_active' => $request->is_active,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return AbstractController::ResultSuccess($document, "Document created successfully!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'id' => 'required|exists:documents,id',
|
||||||
|
'title' => 'required|string|max:255',
|
||||||
|
'type' => 'required|in:file,link',
|
||||||
|
'uri' => 'nullable|array',
|
||||||
|
'uri.*' => 'nullable|url',
|
||||||
|
'files' => 'nullable|array',
|
||||||
|
'files.*' => 'file|mimes:doc,docx,xls,xlsx,pdf|max:20480',
|
||||||
|
'is_active' => 'required|boolean',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$document = Document::find($request->input('id'));
|
||||||
|
if (!$document) {
|
||||||
|
return AbstractController::ResultError("Document not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->type === "file") {
|
||||||
|
$existingFiles = explode(',', $document->uri);
|
||||||
|
foreach ($existingFiles as $file) {
|
||||||
|
$filePath = str_replace('storage/', 'public/', $file);
|
||||||
|
if (Storage::exists($filePath)) {
|
||||||
|
Storage::delete($filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadedFiles = [];
|
||||||
|
if ($request->hasFile('files')) {
|
||||||
|
foreach ($request->file('files') as $file) {
|
||||||
|
$path = $file->store('uploads', 'public');
|
||||||
|
$uploadedFiles[] = "storage/{$path}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$document->update([
|
||||||
|
'title' => $request->title,
|
||||||
|
'type' => $request->type,
|
||||||
|
'uri' => implode(',', $uploadedFiles),
|
||||||
|
'is_active' => $request->is_active,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return AbstractController::ResultSuccess($document, "Document updated successfully!");
|
||||||
|
}
|
||||||
|
|
||||||
|
$document->update([
|
||||||
|
'title' => $request->title,
|
||||||
|
'type' => $request->type,
|
||||||
|
'uri' => implode(',', $request->uri),
|
||||||
|
'is_active' => $request->is_active,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return AbstractController::ResultSuccess($document, "Document updated successfully!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Request $request)
|
||||||
|
{
|
||||||
|
$id = $request->input('id');
|
||||||
|
$document = Document::find($id);
|
||||||
|
if (!$document) {
|
||||||
|
return AbstractController::ResultError("Document not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($document->type === "file") {
|
||||||
|
$existingFiles = explode(',', $document->uri);
|
||||||
|
foreach ($existingFiles as $file) {
|
||||||
|
$filePath = str_replace('storage/', 'public/', $file);
|
||||||
|
if (Storage::exists($filePath)) {
|
||||||
|
Storage::delete($filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$document->delete();
|
||||||
|
return AbstractController::ResultSuccess("Document deleted successfully!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Admin\app\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Document extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['title', 'uri', 'type', 'is_active'];
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ use Modules\Admin\app\Http\Controllers\ClientController;
|
||||||
use Modules\Admin\app\Http\Controllers\CountryController;
|
use Modules\Admin\app\Http\Controllers\CountryController;
|
||||||
use Modules\Admin\app\Http\Controllers\CustomThemeController;
|
use Modules\Admin\app\Http\Controllers\CustomThemeController;
|
||||||
use Modules\Admin\app\Http\Controllers\DashboardController;
|
use Modules\Admin\app\Http\Controllers\DashboardController;
|
||||||
|
use Modules\Admin\app\Http\Controllers\DocumentController;
|
||||||
use Modules\Admin\app\Http\Controllers\JiraController;
|
use Modules\Admin\app\Http\Controllers\JiraController;
|
||||||
use Modules\Admin\app\Http\Controllers\LeaveManagementController;
|
use Modules\Admin\app\Http\Controllers\LeaveManagementController;
|
||||||
use Modules\Admin\app\Http\Controllers\SettingController;
|
use Modules\Admin\app\Http\Controllers\SettingController;
|
||||||
|
|
@ -214,6 +215,15 @@ Route::middleware('api')
|
||||||
Route::get('/get-list-user-by-tech-id/{technicalId}', [TechnicalController::class, 'getListUserByTechnicalId'])->middleware('check.permission:admin');
|
Route::get('/get-list-user-by-tech-id/{technicalId}', [TechnicalController::class, 'getListUserByTechnicalId'])->middleware('check.permission:admin');
|
||||||
Route::post('/technicals-user/update', [TechnicalController::class, 'updateTechnicalsUser']);
|
Route::post('/technicals-user/update', [TechnicalController::class, 'updateTechnicalsUser']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::group([
|
||||||
|
'prefix' => 'document',
|
||||||
|
], function () {
|
||||||
|
Route::get('/all', [DocumentController::class, 'all'])->middleware('check.permission:admin');
|
||||||
|
Route::post('/create', [DocumentController::class, 'create'])->middleware('check.permission:admin');
|
||||||
|
Route::post('/update', [DocumentController::class, 'update'])->middleware('check.permission:admin');
|
||||||
|
Route::get('/delete', [DocumentController::class, 'delete'])->middleware('check.permission:admin');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('documents', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('title');
|
||||||
|
$table->text('uri');
|
||||||
|
$table->enum('type', ['file', 'link'])->default("file");
|
||||||
|
$table->boolean('is_active')->default(true);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('documents');
|
||||||
|
}
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -110,3 +110,9 @@ export const deleteFile = API_URL + 'v1/admin/profile/delete-profile-file'
|
||||||
export const listTechnical = API_URL + 'v1/admin/technical/get-all'
|
export const listTechnical = API_URL + 'v1/admin/technical/get-all'
|
||||||
export const createTechnical = API_URL + 'v1/admin/technical/create'
|
export const createTechnical = API_URL + 'v1/admin/technical/create'
|
||||||
export const deleteTechnical = API_URL + 'v1/admin/technical/delete'
|
export const deleteTechnical = API_URL + 'v1/admin/technical/delete'
|
||||||
|
|
||||||
|
// Document
|
||||||
|
export const listDocument = API_URL + 'v1/admin/document/all'
|
||||||
|
export const createDocument = API_URL + 'v1/admin/document/create'
|
||||||
|
export const updateDocument = API_URL + 'v1/admin/document/update'
|
||||||
|
export const deleteDocument = API_URL + 'v1/admin/document/delete'
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import {
|
||||||
IconCalendarClock,
|
IconCalendarClock,
|
||||||
IconChartDots2,
|
IconChartDots2,
|
||||||
IconDevices,
|
IconDevices,
|
||||||
|
IconFileInvoice,
|
||||||
IconFolders,
|
IconFolders,
|
||||||
IconLayoutSidebarLeftExpand,
|
IconLayoutSidebarLeftExpand,
|
||||||
IconLayoutSidebarRightExpand,
|
IconLayoutSidebarRightExpand,
|
||||||
|
|
@ -41,7 +42,7 @@ import {
|
||||||
IconSun,
|
IconSun,
|
||||||
IconTicket,
|
IconTicket,
|
||||||
IconUsersGroup,
|
IconUsersGroup,
|
||||||
IconZoomExclamation
|
IconZoomExclamation,
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
|
@ -71,6 +72,13 @@ const data = [
|
||||||
permissions: 'admin,hr,staff,tester',
|
permissions: 'admin,hr,staff,tester',
|
||||||
group: 'staff',
|
group: 'staff',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
link: '/documents',
|
||||||
|
label: 'Documents',
|
||||||
|
icon: IconFileInvoice,
|
||||||
|
permissions: 'admin',
|
||||||
|
group: 'staff',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
link: '/leave-management',
|
link: '/leave-management',
|
||||||
label: 'Leave Management',
|
label: 'Leave Management',
|
||||||
|
|
@ -239,7 +247,11 @@ const Navbar = ({
|
||||||
// })
|
// })
|
||||||
|
|
||||||
const group = [
|
const group = [
|
||||||
{ name: 'staff', label: 'General', permissions: 'admin,hr,staff,tester,accountant' },
|
{
|
||||||
|
name: 'staff',
|
||||||
|
label: 'General',
|
||||||
|
permissions: 'admin,hr,staff,tester,accountant',
|
||||||
|
},
|
||||||
{ name: 'admin', label: 'Admin', permissions: 'admin' },
|
{ name: 'admin', label: 'Admin', permissions: 'admin' },
|
||||||
{ name: 'other', label: 'Other', permissions: 'admin,hr' },
|
{ name: 'other', label: 'Other', permissions: 'admin,hr' },
|
||||||
{ name: 'test', label: 'Test', permissions: 'admin,tester' },
|
{ name: 'test', label: 'Test', permissions: 'admin,tester' },
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
.title {
|
||||||
|
background-color: light-dark(var(white), var(--mantine-color-dark-7));
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 var(--mantine-spacing-sm) var(--mantine-spacing-lg)
|
||||||
|
var(--mantine-spacing-sm);
|
||||||
|
border-bottom: solid rgba(201, 201, 201, 0.377) 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionIcon {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteIcon {
|
||||||
|
color: red;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editIcon {
|
||||||
|
color: rgb(9, 132, 132);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editIcon:hover {
|
||||||
|
background-color: rgba(203, 203, 203, 0.809);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteIcon:hover {
|
||||||
|
background-color: rgba(203, 203, 203, 0.809);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,319 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { get } from '@/rtk/helpers/apiService'
|
||||||
|
import { deleteDocument, listDocument } from '@/api/Admin'
|
||||||
|
import { Xdelete } from '@/rtk/helpers/CRUD'
|
||||||
|
|
||||||
|
import { Anchor, Box, Button, Dialog, Group, Loader, Text } from '@mantine/core'
|
||||||
|
import { useDisclosure } from '@mantine/hooks'
|
||||||
|
import { notifications } from '@mantine/notifications'
|
||||||
|
import {
|
||||||
|
IconEdit,
|
||||||
|
IconFileInfo,
|
||||||
|
IconFileTypeDoc,
|
||||||
|
IconFileTypePdf,
|
||||||
|
IconFileTypeXls,
|
||||||
|
IconFileUnknown,
|
||||||
|
IconX,
|
||||||
|
} from '@tabler/icons-react'
|
||||||
|
import DataTableAll from '@/components/DataTable/DataTable'
|
||||||
|
import ModalAddEditDocument from './ModalAddEditDocument'
|
||||||
|
import ModalFileDocument from './ModalFileDocument'
|
||||||
|
|
||||||
|
import classes from './Document.module.css'
|
||||||
|
|
||||||
|
interface TDocument {
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
uri: string
|
||||||
|
type: string
|
||||||
|
is_active: boolean
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestPagination = {
|
||||||
|
data: TDocument[]
|
||||||
|
status: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Document = () => {
|
||||||
|
const [loader, setLoader] = useState<boolean>(false)
|
||||||
|
const [action, setAction] = useState<string>('')
|
||||||
|
const [rows, setRows] = useState<RequestPagination>({
|
||||||
|
data: [],
|
||||||
|
status: true,
|
||||||
|
})
|
||||||
|
const [selectDataRow, setSelectDataRow] = useState<any>({})
|
||||||
|
|
||||||
|
const [
|
||||||
|
openedModalAddEdit,
|
||||||
|
{ open: openModalAddEdit, close: closeModalAddEdit },
|
||||||
|
] = useDisclosure(false)
|
||||||
|
const [openedModalFile, { open: openModalFile, close: closeModalFile }] =
|
||||||
|
useDisclosure(false)
|
||||||
|
|
||||||
|
const [openedDialogDelete, setOpenedDialogDelete] = useState(false)
|
||||||
|
const [deleteLoader, setDeleteLoader] = useState<boolean>(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getAllData()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getAllData = async () => {
|
||||||
|
try {
|
||||||
|
setLoader(true)
|
||||||
|
const params = {}
|
||||||
|
const res = await get(listDocument, params)
|
||||||
|
if (res.status) {
|
||||||
|
setRows(res)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: error.message ?? error,
|
||||||
|
color: 'red',
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setLoader(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (id: number | undefined) => {
|
||||||
|
try {
|
||||||
|
setDeleteLoader(true)
|
||||||
|
await Xdelete(deleteDocument, { id: id }, getAllData)
|
||||||
|
setSelectDataRow({})
|
||||||
|
setOpenedDialogDelete(false)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
} finally {
|
||||||
|
setDeleteLoader(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFileTypeIcon = (url: string) => {
|
||||||
|
if (!url) return null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsedUrl = new URL(url)
|
||||||
|
const hostname = parsedUrl.hostname
|
||||||
|
const pathname = parsedUrl.pathname.toLowerCase()
|
||||||
|
|
||||||
|
if (
|
||||||
|
hostname.includes('docs.google.com') ||
|
||||||
|
hostname.includes('drive.google.com')
|
||||||
|
) {
|
||||||
|
if (pathname.includes('/document/')) {
|
||||||
|
return <IconFileTypeDoc style={{ color: '#1e62c1' }} />
|
||||||
|
}
|
||||||
|
if (pathname.includes('/spreadsheets/')) {
|
||||||
|
return <IconFileTypeXls style={{ color: '#0e864b' }} />
|
||||||
|
}
|
||||||
|
return <IconFileTypePdf style={{ color: '#ff1b0e' }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <IconFileUnknown style={{ color: '#000' }} />
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Invalid URL:', url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
size: '5%',
|
||||||
|
header: 'ID',
|
||||||
|
render: (row: TDocument) => {
|
||||||
|
return <Box>{row?.id ? row.id : ''}</Box>
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
size: '40%',
|
||||||
|
header: 'Title',
|
||||||
|
render: (row: TDocument) => {
|
||||||
|
return <Text ta="start">{row?.title}</Text>
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'uri',
|
||||||
|
size: '50%',
|
||||||
|
header: 'URI',
|
||||||
|
render: (row: TDocument) => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{row.type === 'file' ? (
|
||||||
|
<Text
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
ta="start"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectDataRow(row)
|
||||||
|
openModalFile()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconFileInfo style={{ color: '#ffa500' }} />
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Group>
|
||||||
|
{row?.uri &&
|
||||||
|
row?.uri.split(',')?.map((uriItem) => (
|
||||||
|
<Anchor
|
||||||
|
ta="start"
|
||||||
|
href={uriItem}
|
||||||
|
target="_blank"
|
||||||
|
title={uriItem}
|
||||||
|
>
|
||||||
|
{getFileTypeIcon(uriItem)}
|
||||||
|
</Anchor>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '#',
|
||||||
|
size: '5%',
|
||||||
|
header: 'Action',
|
||||||
|
render: (row: TDocument) => {
|
||||||
|
return (
|
||||||
|
<Box className={classes.optionIcon}>
|
||||||
|
<IconEdit
|
||||||
|
className={classes.editIcon}
|
||||||
|
onClick={() => {
|
||||||
|
setAction('edit')
|
||||||
|
setSelectDataRow(row)
|
||||||
|
openModalAddEdit()
|
||||||
|
}}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
<IconX
|
||||||
|
className={classes.deleteIcon}
|
||||||
|
onClick={() => {
|
||||||
|
setOpenedDialogDelete(true)
|
||||||
|
setSelectDataRow(row)
|
||||||
|
}}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<div className={classes.title}>
|
||||||
|
<h3>
|
||||||
|
<Text>Admin/</Text>
|
||||||
|
Documents
|
||||||
|
</h3>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setAction('add')
|
||||||
|
openModalAddEdit()
|
||||||
|
}}
|
||||||
|
disabled={loader}
|
||||||
|
>
|
||||||
|
+ Add
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Box mt={'md'}>
|
||||||
|
{loader ? (
|
||||||
|
<Box ta={'center'}>
|
||||||
|
<Loader size={40} mt={'15%'} />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<DataTableAll
|
||||||
|
data={rows.data}
|
||||||
|
columns={columns}
|
||||||
|
size=""
|
||||||
|
searchInput
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{openedModalFile && (
|
||||||
|
<ModalFileDocument
|
||||||
|
opened={openedModalFile}
|
||||||
|
close={closeModalFile}
|
||||||
|
selectDataRow={selectDataRow}
|
||||||
|
setSelectDataRow={setSelectDataRow}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{openedModalAddEdit && (
|
||||||
|
<ModalAddEditDocument
|
||||||
|
opened={openedModalAddEdit}
|
||||||
|
close={closeModalAddEdit}
|
||||||
|
action={action}
|
||||||
|
setAction={setAction}
|
||||||
|
selectDataRow={selectDataRow}
|
||||||
|
setSelectDataRow={setSelectDataRow}
|
||||||
|
getAllData={getAllData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{openedDialogDelete && (
|
||||||
|
<Dialog
|
||||||
|
opened={openedDialogDelete}
|
||||||
|
className={classes.dialog}
|
||||||
|
withCloseButton
|
||||||
|
onClose={() => {
|
||||||
|
setSelectDataRow({})
|
||||||
|
setOpenedDialogDelete(false)
|
||||||
|
}}
|
||||||
|
size="lg"
|
||||||
|
radius="md"
|
||||||
|
position={{ top: 30, right: 10 }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
className={classes.dialogText}
|
||||||
|
size="sm"
|
||||||
|
mb="xs"
|
||||||
|
fw={500}
|
||||||
|
pr={20}
|
||||||
|
>
|
||||||
|
<Text ta="center">
|
||||||
|
Do you want to delete the document <b>{selectDataRow?.title}</b> ?
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Group justify="center" m={10}>
|
||||||
|
<Button
|
||||||
|
fw={700}
|
||||||
|
size="xs"
|
||||||
|
variant="filled"
|
||||||
|
color="red"
|
||||||
|
onClick={async () => handleDelete(selectDataRow?.id)}
|
||||||
|
disabled={deleteLoader}
|
||||||
|
>
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
fw={700}
|
||||||
|
size="xs"
|
||||||
|
variant="filled"
|
||||||
|
color="gray"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectDataRow({})
|
||||||
|
setOpenedDialogDelete(false)
|
||||||
|
}}
|
||||||
|
disabled={deleteLoader}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Document
|
||||||
|
|
@ -0,0 +1,268 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useForm } from '@mantine/form'
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Button,
|
||||||
|
TextInput,
|
||||||
|
Text,
|
||||||
|
Box,
|
||||||
|
Switch,
|
||||||
|
Checkbox,
|
||||||
|
FileInput,
|
||||||
|
TagsInput,
|
||||||
|
} from '@mantine/core'
|
||||||
|
|
||||||
|
import { create, update } from '@/rtk/helpers/CRUD'
|
||||||
|
import { createDocument, updateDocument } from '@/api/Admin'
|
||||||
|
import { getHeaderInfo } from '@/rtk/helpers/tokenCreator'
|
||||||
|
import { notifications } from '@mantine/notifications'
|
||||||
|
|
||||||
|
type MProps = {
|
||||||
|
opened: boolean
|
||||||
|
close: () => void
|
||||||
|
setAction: (arg0: any) => void
|
||||||
|
selectDataRow: any
|
||||||
|
setSelectDataRow: any
|
||||||
|
action: string
|
||||||
|
getAllData: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalAddEditDocument = ({
|
||||||
|
opened,
|
||||||
|
close,
|
||||||
|
setAction,
|
||||||
|
selectDataRow,
|
||||||
|
setSelectDataRow,
|
||||||
|
action,
|
||||||
|
getAllData,
|
||||||
|
}: MProps) => {
|
||||||
|
const [loadingSubmit, setLoadingSubmit] = useState(false)
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: {
|
||||||
|
title: '',
|
||||||
|
type: true,
|
||||||
|
files: [] as File[],
|
||||||
|
uri: [],
|
||||||
|
is_active: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.setValues({
|
||||||
|
title: selectDataRow?.title ?? '',
|
||||||
|
type: selectDataRow?.type ? selectDataRow?.type === 'file' : true,
|
||||||
|
files: [],
|
||||||
|
uri: selectDataRow?.uri?.split(',') ?? [],
|
||||||
|
is_active: selectDataRow?.is_active ?? true,
|
||||||
|
})
|
||||||
|
}, [selectDataRow])
|
||||||
|
|
||||||
|
const handleCreate = async (data: any) => {
|
||||||
|
try {
|
||||||
|
setLoadingSubmit(true)
|
||||||
|
|
||||||
|
let formdata = {}
|
||||||
|
const header = await getHeaderInfo()
|
||||||
|
|
||||||
|
if (data.type) {
|
||||||
|
if (data.files.length < 1) {
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Upload at least 1 file',
|
||||||
|
color: 'red',
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
header.headers['Content-Type'] = 'multipart/form-data'
|
||||||
|
const tmpFormData = new FormData()
|
||||||
|
|
||||||
|
tmpFormData.append('title', data.title)
|
||||||
|
tmpFormData.append('type', data.type ? 'file' : 'link')
|
||||||
|
tmpFormData.append('is_active', data.is_active ? '1' : '0')
|
||||||
|
for (let i = 0; i < data.files.length; i++) {
|
||||||
|
tmpFormData.append('files[]', data.files[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
formdata = tmpFormData
|
||||||
|
} else {
|
||||||
|
const { files, ...rest } = data
|
||||||
|
formdata = {
|
||||||
|
...rest,
|
||||||
|
type: rest.type ? 'file' : 'link',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await create(createDocument, formdata, getAllData, header)
|
||||||
|
if (res === true) {
|
||||||
|
setAction('')
|
||||||
|
close()
|
||||||
|
form.reset()
|
||||||
|
setSelectDataRow({})
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log(error)
|
||||||
|
} finally {
|
||||||
|
setLoadingSubmit(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpdate = async (data: any) => {
|
||||||
|
try {
|
||||||
|
setLoadingSubmit(true)
|
||||||
|
|
||||||
|
let formdata = {}
|
||||||
|
const header = await getHeaderInfo()
|
||||||
|
|
||||||
|
if (data.type) {
|
||||||
|
if (data.files.length < 1) {
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Upload at least 1 file',
|
||||||
|
color: 'red',
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
header.headers['Content-Type'] = 'multipart/form-data'
|
||||||
|
const tmpFormData = new FormData()
|
||||||
|
|
||||||
|
tmpFormData.append('title', data.title)
|
||||||
|
tmpFormData.append('type', data.type ? 'file' : 'link')
|
||||||
|
tmpFormData.append('is_active', data.is_active)
|
||||||
|
for (let i = 0; i < data.files.length; i++) {
|
||||||
|
tmpFormData.append('files[]', data.files[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
formdata = tmpFormData
|
||||||
|
} else {
|
||||||
|
const { files, ...rest } = data
|
||||||
|
formdata = {
|
||||||
|
...rest,
|
||||||
|
type: rest.type ? 'file' : 'link',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await update(
|
||||||
|
updateDocument + `?id=${selectDataRow?.id}`,
|
||||||
|
formdata,
|
||||||
|
getAllData,
|
||||||
|
header,
|
||||||
|
)
|
||||||
|
if (res === true) {
|
||||||
|
setAction('')
|
||||||
|
close()
|
||||||
|
form.reset()
|
||||||
|
setSelectDataRow({})
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error)
|
||||||
|
} finally {
|
||||||
|
setLoadingSubmit(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={() => {
|
||||||
|
setAction('')
|
||||||
|
close()
|
||||||
|
form.reset()
|
||||||
|
setSelectDataRow({})
|
||||||
|
}}
|
||||||
|
size="lg"
|
||||||
|
title={
|
||||||
|
<Text fw={700} fz={'lg'}>
|
||||||
|
{action === 'add' ? 'Add Document' : 'Update Document'}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
onSubmit={form.onSubmit((values) => {
|
||||||
|
if (action === 'add') {
|
||||||
|
handleCreate(values)
|
||||||
|
} else {
|
||||||
|
handleUpdate(values)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
label="Title"
|
||||||
|
maxLength={255}
|
||||||
|
key={'title'}
|
||||||
|
mb={'md'}
|
||||||
|
{...form.getInputProps('title')}
|
||||||
|
disabled={loadingSubmit}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
{selectDataRow?.id ? (
|
||||||
|
''
|
||||||
|
) : (
|
||||||
|
<Switch
|
||||||
|
style={{ width: 'fit-content' }}
|
||||||
|
label={form.values.type ? 'Upload files' : 'Enter links'}
|
||||||
|
checked={form.values.type}
|
||||||
|
onChange={(event) =>
|
||||||
|
form.setFieldValue('type', event.currentTarget.checked)
|
||||||
|
}
|
||||||
|
mb={'md'}
|
||||||
|
disabled={loadingSubmit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box mb={'md'}>
|
||||||
|
{form.values.type ? (
|
||||||
|
<Box>
|
||||||
|
<FileInput
|
||||||
|
accept=".doc,.docx,.xls,.xlsx,.pdf"
|
||||||
|
label="Upload Doc, Excel, PDF files"
|
||||||
|
multiple
|
||||||
|
mb="md"
|
||||||
|
value={form.values.files}
|
||||||
|
onChange={(files) => {
|
||||||
|
form.setFieldValue('files', files || [])
|
||||||
|
}}
|
||||||
|
disabled={loadingSubmit}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box>
|
||||||
|
<TagsInput
|
||||||
|
label="Enter uri"
|
||||||
|
key={'uri'}
|
||||||
|
mb={'md'}
|
||||||
|
{...form.getInputProps('uri')}
|
||||||
|
disabled={loadingSubmit}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
label="Is Active"
|
||||||
|
mb={'md'}
|
||||||
|
checked={form.values.is_active}
|
||||||
|
onChange={(event) =>
|
||||||
|
form.setFieldValue('is_active', event.currentTarget.checked)
|
||||||
|
}
|
||||||
|
disabled={loadingSubmit}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box ta="center" mt="lg">
|
||||||
|
<Button color="green" type="submit" loading={loadingSubmit}>
|
||||||
|
{action === 'add' ? 'Create' : 'Update'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalAddEditDocument
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { Modal, Text, Box } from '@mantine/core'
|
||||||
|
|
||||||
|
type MProps = {
|
||||||
|
opened: boolean
|
||||||
|
close: () => void
|
||||||
|
selectDataRow: any
|
||||||
|
setSelectDataRow: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalFileDocument = ({
|
||||||
|
opened,
|
||||||
|
close,
|
||||||
|
selectDataRow,
|
||||||
|
setSelectDataRow,
|
||||||
|
}: MProps) => {
|
||||||
|
// const supportedFileTypes = ['pdf', 'xls', 'xlsx', 'docx', 'doc']
|
||||||
|
// const getFileType = (fileName: string) => {
|
||||||
|
// const extension = fileName.split('.').pop()?.toLowerCase()
|
||||||
|
// return supportedFileTypes.includes(extension!) ? extension : 'default'
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={() => {
|
||||||
|
close()
|
||||||
|
setSelectDataRow({})
|
||||||
|
}}
|
||||||
|
size="xl"
|
||||||
|
title={
|
||||||
|
<Text fw={700} fz={'lg'}>
|
||||||
|
{selectDataRow?.title}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
{selectDataRow?.uri?.split(',').map((uriItem: string) => {
|
||||||
|
let url = import.meta.env.VITE_BACKEND_URL + uriItem
|
||||||
|
const extension = url.split('.').pop()?.toLowerCase()
|
||||||
|
|
||||||
|
if (extension === 'doc' || extension === 'docx') {
|
||||||
|
const docUrl = encodeURIComponent(uriItem)
|
||||||
|
url = `https://view.officeapps.live.com/op/embed.aspx?src=${docUrl}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<iframe
|
||||||
|
key={uriItem}
|
||||||
|
src={url}
|
||||||
|
style={{ width: '100%', height: '800px', border: 'none' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalFileDocument
|
||||||
|
|
@ -5,6 +5,7 @@ import ProtectedRoute from '@/components/ProtectedRoute/ProtectedRoute'
|
||||||
import AllProfiles from '@/pages/AllProfiles/AllProfiles'
|
import AllProfiles from '@/pages/AllProfiles/AllProfiles'
|
||||||
import Allocation from '@/pages/Allocation/Allocation'
|
import Allocation from '@/pages/Allocation/Allocation'
|
||||||
import PageLogin from '@/pages/Auth/Login/Login'
|
import PageLogin from '@/pages/Auth/Login/Login'
|
||||||
|
import Document from '@/pages/Document/Document'
|
||||||
import LeaveManagement from '@/pages/LeaveManagement/LeaveManagement'
|
import LeaveManagement from '@/pages/LeaveManagement/LeaveManagement'
|
||||||
import PageNotFound from '@/pages/NotFound/NotFound'
|
import PageNotFound from '@/pages/NotFound/NotFound'
|
||||||
import OrganizationSettings from '@/pages/OrganizationSettings/OrganizationSettings'
|
import OrganizationSettings from '@/pages/OrganizationSettings/OrganizationSettings'
|
||||||
|
|
@ -81,6 +82,20 @@ const mainRoutes = [
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/documents',
|
||||||
|
element: (
|
||||||
|
<ProtectedRoute mode="home" permission="admin">
|
||||||
|
<BasePage
|
||||||
|
main={
|
||||||
|
<>
|
||||||
|
<Document />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
></BasePage>
|
||||||
|
</ProtectedRoute>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/timekeeping',
|
path: '/timekeeping',
|
||||||
element: (
|
element: (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue