Merge pull request 'BE, FE Document' (#113) from vi.document into dev
Reviewed-on: #113
This commit is contained in:
		
						commit
						d8cbb7e01b
					
				| 
						 | 
				
			
			@ -0,0 +1,167 @@
 | 
			
		|||
<?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',
 | 
			
		||||
            'existing_files' => 'nullable|array',
 | 
			
		||||
            '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);
 | 
			
		||||
            $selectedExistingFiles = $request->existing_files ?? [];
 | 
			
		||||
 | 
			
		||||
            $filesToDelete = array_diff($existingFiles, $selectedExistingFiles);
 | 
			
		||||
            foreach ($filesToDelete as $file) {
 | 
			
		||||
                $filePath = str_replace('storage/', 'public/', $file);
 | 
			
		||||
                if (Storage::exists($filePath)) {
 | 
			
		||||
                    Storage::delete($filePath);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $uploadedFiles = $selectedExistingFiles;
 | 
			
		||||
            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'];
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
use App\Http\Middleware\CheckPermission;
 | 
			
		||||
use Illuminate\Support\Facades\Route;
 | 
			
		||||
use Illuminate\Support\Facades\Storage;
 | 
			
		||||
use Modules\Admin\app\Http\Controllers\AdminController;
 | 
			
		||||
use Modules\Admin\app\Http\Controllers\BannerController;
 | 
			
		||||
use Modules\Admin\app\Http\Controllers\CategoryController;
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +10,7 @@ use Modules\Admin\app\Http\Controllers\ClientController;
 | 
			
		|||
use Modules\Admin\app\Http\Controllers\CountryController;
 | 
			
		||||
use Modules\Admin\app\Http\Controllers\CustomThemeController;
 | 
			
		||||
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\LeaveManagementController;
 | 
			
		||||
use Modules\Admin\app\Http\Controllers\SettingController;
 | 
			
		||||
| 
						 | 
				
			
			@ -214,6 +216,32 @@ Route::middleware('api')
 | 
			
		|||
                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::group([
 | 
			
		||||
                'prefix' => 'document',
 | 
			
		||||
            ], function () {
 | 
			
		||||
                Route::get('/all', [DocumentController::class, 'all'])->middleware('check.permission:admin.hr.staff.accountant');
 | 
			
		||||
                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');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            Route::get('/download-file/{filename}', function ($filename) {
 | 
			
		||||
                $path = "uploads/{$filename}";
 | 
			
		||||
 | 
			
		||||
                if (!Storage::disk('public')->exists($path)) {
 | 
			
		||||
                    return response()->json(['error' => 'File not found'], 404);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $headers = [
 | 
			
		||||
                    'Access-Control-Allow-Origin' => '*',
 | 
			
		||||
                    'Access-Control-Allow-Methods' => 'GET',
 | 
			
		||||
                    'Access-Control-Allow-Headers' => 'Content-Type',
 | 
			
		||||
                    'Content-Disposition' => 'inline; filename="' . $filename . '"',
 | 
			
		||||
                ];
 | 
			
		||||
 | 
			
		||||
                return response()->file(storage_path("app/public/{$path}"), $headers);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -230,6 +230,7 @@ class JiraService
 | 
			
		|||
        $groupedIssues = [];
 | 
			
		||||
        $users_data = [];
 | 
			
		||||
        $user_warning = [];
 | 
			
		||||
        $ignore_projects = ['PJ_tracking'];
 | 
			
		||||
        foreach ($users as $user) {
 | 
			
		||||
            $user = (array) $user[0];
 | 
			
		||||
            $users_data[$user['displayName']]['user'] = $user;
 | 
			
		||||
| 
						 | 
				
			
			@ -252,12 +253,20 @@ class JiraService
 | 
			
		|||
 | 
			
		||||
            $issues = json_decode($response->getBody()->getContents(), true);
 | 
			
		||||
 | 
			
		||||
             // Lọc các issue không thuộc các project bị ignore
 | 
			
		||||
            $filtered_issues = array_filter($issues['issues'], function ($issue) use ($ignore_projects) {
 | 
			
		||||
                return !in_array($issue['fields']['project']['name'], $ignore_projects);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $issues['issues'] = $filtered_issues;
 | 
			
		||||
 | 
			
		||||
            if (count($issues['issues']) == 0) {
 | 
			
		||||
                $user_warning[] = $user;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            foreach ($issues['issues'] as $issue) {
 | 
			
		||||
                $projectName = $issue['fields']['project']['name'];
 | 
			
		||||
                if(!in_array($projectName, $ignore_projects)) {
 | 
			
		||||
                    $username = $issue['fields']['assignee']['displayName'];
 | 
			
		||||
                    $issue['fields']['assignee']['emailAddress'] = $user['emailAddress'];
 | 
			
		||||
                    if (!isset($groupedIssues[$projectName])) {
 | 
			
		||||
| 
						 | 
				
			
			@ -279,6 +288,8 @@ class JiraService
 | 
			
		|||
                    $users_data[$user['displayName']]['total_spent'] = $users_data[$user['displayName']]['total_spent'] + $issue['fields']['timespent'];
 | 
			
		||||
                    $users_data[$user['displayName']]['total_est'] = $users_data[$user['displayName']]['total_est'] + ($issue['fields']['timeoriginalestimate'] ?? 0);
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
											
										
									
								
							| 
						 | 
				
			
			@ -34,6 +34,7 @@
 | 
			
		|||
    "react": "^18.2.0",
 | 
			
		||||
    "react-doc-viewer": "^0.1.14",
 | 
			
		||||
    "react-dom": "^18.2.0",
 | 
			
		||||
    "react-file-viewer": "^1.2.1",
 | 
			
		||||
    "react-redux": "^8.1.3",
 | 
			
		||||
    "react-router-dom": "^6.19.0",
 | 
			
		||||
    "reactstrap": "^9.2.2",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -110,3 +110,12 @@ export const deleteFile = API_URL + 'v1/admin/profile/delete-profile-file'
 | 
			
		|||
export const listTechnical = API_URL + 'v1/admin/technical/get-all'
 | 
			
		||||
export const createTechnical = API_URL + 'v1/admin/technical/create'
 | 
			
		||||
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'
 | 
			
		||||
 | 
			
		||||
// Download File
 | 
			
		||||
export const downloadFile = API_URL + 'v1/admin/download-file'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,7 @@ import {
 | 
			
		|||
  IconCalendarClock,
 | 
			
		||||
  IconChartDots2,
 | 
			
		||||
  IconDevices,
 | 
			
		||||
  IconFileInvoice,
 | 
			
		||||
  IconFolders,
 | 
			
		||||
  IconLayoutSidebarLeftExpand,
 | 
			
		||||
  IconLayoutSidebarRightExpand,
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +42,7 @@ import {
 | 
			
		|||
  IconSun,
 | 
			
		||||
  IconTicket,
 | 
			
		||||
  IconUsersGroup,
 | 
			
		||||
  IconZoomExclamation
 | 
			
		||||
  IconZoomExclamation,
 | 
			
		||||
} from '@tabler/icons-react'
 | 
			
		||||
import { useCallback, useEffect, useState } from 'react'
 | 
			
		||||
import { useLocation, useNavigate } from 'react-router-dom'
 | 
			
		||||
| 
						 | 
				
			
			@ -71,6 +72,13 @@ const data = [
 | 
			
		|||
    permissions: 'admin,hr,staff,tester',
 | 
			
		||||
    group: 'staff',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    link: '/documents',
 | 
			
		||||
    label: 'Documents',
 | 
			
		||||
    icon: IconFileInvoice,
 | 
			
		||||
    permissions: 'admin,hr,staff,tester,accountant',
 | 
			
		||||
    group: 'staff',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    link: '/leave-management',
 | 
			
		||||
    label: 'Leave Management',
 | 
			
		||||
| 
						 | 
				
			
			@ -239,7 +247,11 @@ const Navbar = ({
 | 
			
		|||
  // })
 | 
			
		||||
 | 
			
		||||
  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: 'other', label: 'Other', permissions: 'admin,hr' },
 | 
			
		||||
    { 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,311 @@
 | 
			
		|||
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,
 | 
			
		||||
  Badge,
 | 
			
		||||
  Box,
 | 
			
		||||
  Button,
 | 
			
		||||
  Dialog,
 | 
			
		||||
  Group,
 | 
			
		||||
  Loader,
 | 
			
		||||
  Text,
 | 
			
		||||
} from '@mantine/core'
 | 
			
		||||
import { useDisclosure } from '@mantine/hooks'
 | 
			
		||||
import { notifications } from '@mantine/notifications'
 | 
			
		||||
import { IconEdit, 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 columns = [
 | 
			
		||||
    {
 | 
			
		||||
      name: 'id',
 | 
			
		||||
      size: '5%',
 | 
			
		||||
      header: 'ID',
 | 
			
		||||
      render: (row: TDocument) => {
 | 
			
		||||
        return <Box>{row?.id ? row.id : ''}</Box>
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'title',
 | 
			
		||||
      size: '30%',
 | 
			
		||||
      header: 'Title',
 | 
			
		||||
      render: (row: TDocument) => {
 | 
			
		||||
        return <Text ta="start">{row?.title}</Text>
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'uri',
 | 
			
		||||
      size: '60%',
 | 
			
		||||
      header: 'URI',
 | 
			
		||||
      render: (row: TDocument) => {
 | 
			
		||||
        return (
 | 
			
		||||
          <Box>
 | 
			
		||||
            {row.type === 'file' ? (
 | 
			
		||||
              <Group gap={5}>
 | 
			
		||||
                {row?.uri &&
 | 
			
		||||
                  row?.uri.split(',')?.map((uriItem) => (
 | 
			
		||||
                    <Badge
 | 
			
		||||
                      style={{ cursor: 'pointer' }}
 | 
			
		||||
                      tt="initial"
 | 
			
		||||
                      onClick={() => {
 | 
			
		||||
                        setSelectDataRow({ ...row, uri: uriItem })
 | 
			
		||||
                        openModalFile()
 | 
			
		||||
                      }}
 | 
			
		||||
                      color="orange"
 | 
			
		||||
                    >
 | 
			
		||||
                      {uriItem.replace('storage/uploads/', '')}
 | 
			
		||||
                    </Badge>
 | 
			
		||||
                  ))}
 | 
			
		||||
              </Group>
 | 
			
		||||
            ) : (
 | 
			
		||||
              <Group gap={5}>
 | 
			
		||||
                {row?.uri &&
 | 
			
		||||
                  row?.uri.split(',')?.map((uriItem) => (
 | 
			
		||||
                    <Anchor
 | 
			
		||||
                      ta="start"
 | 
			
		||||
                      href={uriItem}
 | 
			
		||||
                      target="_blank"
 | 
			
		||||
                      title={uriItem}
 | 
			
		||||
                    >
 | 
			
		||||
                      <Badge style={{ cursor: 'pointer' }} tt="initial">
 | 
			
		||||
                        {uriItem}
 | 
			
		||||
                      </Badge>
 | 
			
		||||
                    </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'}>
 | 
			
		||||
        <Group wrap="wrap" mb={'md'} px={16}>
 | 
			
		||||
          <Text fw={500}>Note: </Text>
 | 
			
		||||
 | 
			
		||||
          <Badge style={{ cursor: 'pointer' }} tt="initial">
 | 
			
		||||
            Links
 | 
			
		||||
          </Badge>
 | 
			
		||||
 | 
			
		||||
          <Badge style={{ cursor: 'pointer' }} tt="initial" color="orange">
 | 
			
		||||
            Files
 | 
			
		||||
          </Badge>
 | 
			
		||||
        </Group>
 | 
			
		||||
 | 
			
		||||
        {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,299 @@
 | 
			
		|||
import { useEffect, useState } from 'react'
 | 
			
		||||
import { useForm } from '@mantine/form'
 | 
			
		||||
import {
 | 
			
		||||
  Modal,
 | 
			
		||||
  Button,
 | 
			
		||||
  TextInput,
 | 
			
		||||
  Text,
 | 
			
		||||
  Box,
 | 
			
		||||
  Switch,
 | 
			
		||||
  Checkbox,
 | 
			
		||||
  FileInput,
 | 
			
		||||
  TagsInput,
 | 
			
		||||
  Group,
 | 
			
		||||
} 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 && data.uri.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])
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        data.uri.forEach((fileUri: string) => {
 | 
			
		||||
          tmpFormData.append('existing_files[]', fileUri)
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
              />
 | 
			
		||||
 | 
			
		||||
              {selectDataRow?.uri && form.values.uri.length > 0 && (
 | 
			
		||||
                <Box>
 | 
			
		||||
                  <Text fw={500}>Existing Files:</Text>
 | 
			
		||||
                  {form.values.uri.map((fileUri: string, index) => (
 | 
			
		||||
                    <Group key={index} justify="space-between" mb="sm">
 | 
			
		||||
                      <Text size="sm">
 | 
			
		||||
                        {fileUri.replace('storage/uploads/', '')}
 | 
			
		||||
                      </Text>
 | 
			
		||||
                      <Button
 | 
			
		||||
                        color="red"
 | 
			
		||||
                        size="xs"
 | 
			
		||||
                        ml="md"
 | 
			
		||||
                        onClick={() =>
 | 
			
		||||
                          form.setFieldValue(
 | 
			
		||||
                            'uri',
 | 
			
		||||
                            form.values.uri.filter((_, i) => i !== index),
 | 
			
		||||
                          )
 | 
			
		||||
                        }
 | 
			
		||||
                      >
 | 
			
		||||
                        Remove
 | 
			
		||||
                      </Button>
 | 
			
		||||
                    </Group>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </Box>
 | 
			
		||||
              )}
 | 
			
		||||
            </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,111 @@
 | 
			
		|||
import { useEffect, useState } from 'react'
 | 
			
		||||
import { getDownloadFile } from '@/rtk/helpers/apiService'
 | 
			
		||||
import { Modal, Text, Box, Loader, Paper, Button, Group } from '@mantine/core'
 | 
			
		||||
import FileViewer from 'react-file-viewer'
 | 
			
		||||
import { IconDownload } from '@tabler/icons-react'
 | 
			
		||||
import { downloadFile } from '@/api/Admin'
 | 
			
		||||
 | 
			
		||||
type MProps = {
 | 
			
		||||
  opened: boolean
 | 
			
		||||
  close: () => void
 | 
			
		||||
  selectDataRow: any
 | 
			
		||||
  setSelectDataRow: any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface TDocumentFile {
 | 
			
		||||
  uri: string
 | 
			
		||||
  fileType: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ModalFileDocument = ({
 | 
			
		||||
  opened,
 | 
			
		||||
  close,
 | 
			
		||||
  selectDataRow,
 | 
			
		||||
  setSelectDataRow,
 | 
			
		||||
}: MProps) => {
 | 
			
		||||
  const [fileDoc, setFileDoc] = useState<TDocumentFile>()
 | 
			
		||||
  const [loader, setLoader] = useState<boolean>(false)
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    getFile()
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  const getFile = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      setLoader(true)
 | 
			
		||||
      const params = {}
 | 
			
		||||
      const fileUri = selectDataRow?.uri.replace('storage/uploads/', '')
 | 
			
		||||
      const res = await getDownloadFile(`${downloadFile}/${fileUri}`, params)
 | 
			
		||||
 | 
			
		||||
      setFileDoc({
 | 
			
		||||
        uri: URL.createObjectURL(res.data),
 | 
			
		||||
        fileType: getFileType(selectDataRow?.uri) || 'default',
 | 
			
		||||
      })
 | 
			
		||||
    } catch (error: any) {
 | 
			
		||||
      console.log(error)
 | 
			
		||||
    } finally {
 | 
			
		||||
      setLoader(false)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const supportedFileTypes = ['pdf', 'xlsx', 'xls', '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="65%"
 | 
			
		||||
      title={
 | 
			
		||||
        <Text fw={700} fz={'lg'}>
 | 
			
		||||
          File Detail: {selectDataRow?.title}
 | 
			
		||||
        </Text>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <Group justify="flex-end" mb={'md'}>
 | 
			
		||||
        <a
 | 
			
		||||
          href={`${import.meta.env.VITE_BACKEND_URL}${selectDataRow.uri}`}
 | 
			
		||||
          download="Document Download"
 | 
			
		||||
          target={
 | 
			
		||||
            getFileType(selectDataRow?.uri) === 'pdf' ? '_blank' : '_self'
 | 
			
		||||
          }
 | 
			
		||||
        >
 | 
			
		||||
          <Button
 | 
			
		||||
            leftSection={<IconDownload size={18} />}
 | 
			
		||||
            color={
 | 
			
		||||
              getFileType(selectDataRow?.uri) === 'pdf'
 | 
			
		||||
                ? 'red'
 | 
			
		||||
                : getFileType(selectDataRow?.uri) === 'xlsx' ||
 | 
			
		||||
                  getFileType(selectDataRow?.uri) === 'xls'
 | 
			
		||||
                ? 'green'
 | 
			
		||||
                : ''
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            Download .{getFileType(selectDataRow?.uri)}
 | 
			
		||||
          </Button>
 | 
			
		||||
        </a>
 | 
			
		||||
      </Group>
 | 
			
		||||
 | 
			
		||||
      <Paper withBorder>
 | 
			
		||||
        {loader ? (
 | 
			
		||||
          <Box ta={'center'} my={20}>
 | 
			
		||||
            <Loader size={40} />
 | 
			
		||||
          </Box>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <Box w="100%">
 | 
			
		||||
            <FileViewer fileType={fileDoc?.fileType} filePath={fileDoc?.uri} />
 | 
			
		||||
          </Box>
 | 
			
		||||
        )}
 | 
			
		||||
      </Paper>
 | 
			
		||||
    </Modal>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ModalFileDocument
 | 
			
		||||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import ProtectedRoute from '@/components/ProtectedRoute/ProtectedRoute'
 | 
			
		|||
import AllProfiles from '@/pages/AllProfiles/AllProfiles'
 | 
			
		||||
import Allocation from '@/pages/Allocation/Allocation'
 | 
			
		||||
import PageLogin from '@/pages/Auth/Login/Login'
 | 
			
		||||
import Document from '@/pages/Document/Document'
 | 
			
		||||
import LeaveManagement from '@/pages/LeaveManagement/LeaveManagement'
 | 
			
		||||
import PageNotFound from '@/pages/NotFound/NotFound'
 | 
			
		||||
import OrganizationSettings from '@/pages/OrganizationSettings/OrganizationSettings'
 | 
			
		||||
| 
						 | 
				
			
			@ -81,6 +82,20 @@ const mainRoutes = [
 | 
			
		|||
      </ProtectedRoute>
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/documents',
 | 
			
		||||
    element: (
 | 
			
		||||
      <ProtectedRoute mode="home" permission="staff,accountant">
 | 
			
		||||
        <BasePage
 | 
			
		||||
          main={
 | 
			
		||||
            <>
 | 
			
		||||
              <Document />
 | 
			
		||||
            </>
 | 
			
		||||
          }
 | 
			
		||||
        ></BasePage>
 | 
			
		||||
      </ProtectedRoute>
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/timekeeping',
 | 
			
		||||
    element: (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue