Compare commits
	
		
			27 Commits
		
	
	
		
			04ee5e4081
			...
			9a9a94e5d4
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								 | 
						9a9a94e5d4 | |
| 
							
							
								 | 
						d803d69b72 | |
| 
							
							
								 | 
						d1f889c996 | |
| 
							
							
								 | 
						e174146678 | |
| 
							
							
								
								 | 
						cd2570bc07 | |
| 
							
							
								
								 | 
						230beb4ed7 | |
| 
							
							
								 | 
						60925a83af | |
| 
							
							
								
								 | 
						b6476feae5 | |
| 
							
							
								
								 | 
						8b9e18bdcb | |
| 
							
							
								 | 
						4896461838 | |
| 
							
							
								 | 
						7ef2897469 | |
| 
							
							
								 | 
						88870e075c | |
| 
							
							
								
								 | 
						008c335b79 | |
| 
							
							
								 | 
						20745381db | |
| 
							
							
								
								 | 
						a5ece4d65d | |
| 
							
							
								
								 | 
						d8cbb7e01b | |
| 
							
							
								 | 
						78bdca4e23 | |
| 
							
							
								 | 
						e026b70e66 | |
| 
							
							
								 | 
						0c5f56d30c | |
| 
							
							
								 | 
						2561d39b4a | |
| 
							
							
								
								 | 
						12a1447931 | |
| 
							
							
								
								 | 
						35ecaa802d | |
| 
							
							
								 | 
						1018172b58 | |
| 
							
							
								 | 
						0ebe4c772e | |
| 
							
							
								 | 
						281c8ad98a | |
| 
							
							
								
								 | 
						a2e36501bb | |
| 
							
							
								
								 | 
						4d94e26bb9 | 
| 
						 | 
					@ -0,0 +1,161 @@
 | 
				
			||||||
 | 
					<?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([
 | 
				
			||||||
 | 
					            'type' => 'required|in:file,link',
 | 
				
			||||||
 | 
					            'files' => 'nullable|array',
 | 
				
			||||||
 | 
					            'files.*.title' => 'required|string|max:255',
 | 
				
			||||||
 | 
					            'files.*.file' => 'required|file|mimes:doc,docx,xls,xlsx,pdf|max:20480',
 | 
				
			||||||
 | 
					            'links' => 'nullable|array',
 | 
				
			||||||
 | 
					            'links.*.title' => 'required|string|max:255',
 | 
				
			||||||
 | 
					            'links.*.uri' => 'required|string|url',
 | 
				
			||||||
 | 
					            'is_active' => 'required|boolean',
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $documents = [];
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if ($request->type === "file") {
 | 
				
			||||||
 | 
					            foreach ($request->file('files') as $index => $file) {
 | 
				
			||||||
 | 
					                $path = $file['file']->store('uploads', 'public');
 | 
				
			||||||
 | 
					                $documents[] = [
 | 
				
			||||||
 | 
					                    'title' => $request->input("files.$index.title"),
 | 
				
			||||||
 | 
					                    'type' => 'file',
 | 
				
			||||||
 | 
					                    'uri' => "storage/{$path}",
 | 
				
			||||||
 | 
					                    'is_active' => $request->is_active,
 | 
				
			||||||
 | 
					                    'created_at' => now(),
 | 
				
			||||||
 | 
					                    'updated_at' => now(),
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            foreach ($request->links as $link) {
 | 
				
			||||||
 | 
					                $documents[] = [
 | 
				
			||||||
 | 
					                    'title' => $link['title'],
 | 
				
			||||||
 | 
					                    'type' => 'link',
 | 
				
			||||||
 | 
					                    'uri' => $link['uri'],
 | 
				
			||||||
 | 
					                    'is_active' => $request->is_active,
 | 
				
			||||||
 | 
					                    'created_at' => now(),
 | 
				
			||||||
 | 
					                    'updated_at' => now(),
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!empty($documents)) {
 | 
				
			||||||
 | 
					            Document::insert($documents);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return AbstractController::ResultSuccess($documents, "Documents 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|url',
 | 
				
			||||||
 | 
					            'file' => 'nullable|file|mimes:doc,docx,xls,xlsx,pdf|max:20480',
 | 
				
			||||||
 | 
					            'existing_file' => 'nullable|string',
 | 
				
			||||||
 | 
					            'is_active' => 'required|boolean',
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $document = Document::find($request->input('id'));
 | 
				
			||||||
 | 
					        if (!$document) {
 | 
				
			||||||
 | 
					            return AbstractController::ResultError("Document not found.");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($request->type === "file") {
 | 
				
			||||||
 | 
					            $uri = $request->existing_file;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ($request->hasFile('file')) {
 | 
				
			||||||
 | 
					                $filePath = str_replace('storage/', 'public/', $request->existing_file);
 | 
				
			||||||
 | 
					                if (Storage::exists($filePath)) {
 | 
				
			||||||
 | 
					                    Storage::delete($filePath);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                $path = $request->file('file')->store('uploads', 'public');
 | 
				
			||||||
 | 
					                $uri = "storage/{$path}";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $document->update([
 | 
				
			||||||
 | 
					                'title' => $request->title,
 | 
				
			||||||
 | 
					                'type' => $request->type,
 | 
				
			||||||
 | 
					                'uri' => $uri,
 | 
				
			||||||
 | 
					                'is_active' => $request->is_active,
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return AbstractController::ResultSuccess($document, "Document updated successfully!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $document->update([
 | 
				
			||||||
 | 
					            'title' => $request->title,
 | 
				
			||||||
 | 
					            'type' => $request->type,
 | 
				
			||||||
 | 
					            'uri' =>  $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") {
 | 
				
			||||||
 | 
					            $filePath = str_replace('storage/', 'public/', $document->uri);
 | 
				
			||||||
 | 
					            if (Storage::exists($filePath)) {
 | 
				
			||||||
 | 
					                Storage::delete($filePath);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $document->delete();
 | 
				
			||||||
 | 
					        return AbstractController::ResultSuccess("Document deleted successfully!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -178,6 +178,27 @@ class JiraController extends Controller
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getWeeklyReport()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            $startOfWeek = Carbon::now()->startOfWeek()->format('Y-m-d'); // Mặc định là Thứ Hai
 | 
				
			||||||
 | 
					            $endOfWeek = Carbon::now()->endOfWeek()->format('Y-m-d'); // Mặc định là Chủ Nhật
 | 
				
			||||||
 | 
					            // dd($startOfWeek);
 | 
				
			||||||
 | 
					            $results = [];
 | 
				
			||||||
 | 
					            $workLogs = $this->jiraService->getAllUserWorkLogs($startOfWeek, $endOfWeek);
 | 
				
			||||||
 | 
					            foreach($workLogs as $data){
 | 
				
			||||||
 | 
					                $results[$data['username']] = $data['information']['issues'];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return response()->json([
 | 
				
			||||||
 | 
					                $results
 | 
				
			||||||
 | 
					            ], 200);
 | 
				
			||||||
 | 
					        } catch (\Exception $e) {
 | 
				
			||||||
 | 
					            return response()->json(['error' => $e->getMessage()], 500);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function sendReport()
 | 
					    public function sendReport()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $dateFormatted = Carbon::yesterday()->setTimezone(env('TIME_ZONE'))->format('Y-m-d');
 | 
					        $dateFormatted = Carbon::yesterday()->setTimezone(env('TIME_ZONE'))->format('Y-m-d');
 | 
				
			||||||
| 
						 | 
					@ -186,7 +207,7 @@ class JiraController extends Controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $tasksByUser = $this->formatWorkLogsByUser($workLogs);
 | 
					        $tasksByUser = $this->formatWorkLogsByUser($workLogs);
 | 
				
			||||||
        // Mail::to(['luanlt632000@gmail.com'])->send(new WorklogReport($tasksByUser));
 | 
					        // Mail::to(['luanlt632000@gmail.com'])->send(new WorklogReport($tasksByUser));
 | 
				
			||||||
        Mail::to(['luanlt632000@gmail.com', 'admin@apactech.io'])->send(new WorklogReport($tasksByUser));
 | 
					        Mail::to(['joseph@apactech.io', 'admin@apactech.io'])->send(new WorklogReport($tasksByUser));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // return "Email sent successfully!";
 | 
					        // return "Email sent successfully!";
 | 
				
			||||||
        return response()->json([
 | 
					        return response()->json([
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 App\Http\Middleware\CheckPermission;
 | 
				
			||||||
use Illuminate\Support\Facades\Route;
 | 
					use Illuminate\Support\Facades\Route;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Storage;
 | 
				
			||||||
use Modules\Admin\app\Http\Controllers\AdminController;
 | 
					use Modules\Admin\app\Http\Controllers\AdminController;
 | 
				
			||||||
use Modules\Admin\app\Http\Controllers\BannerController;
 | 
					use Modules\Admin\app\Http\Controllers\BannerController;
 | 
				
			||||||
use Modules\Admin\app\Http\Controllers\CategoryController;
 | 
					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\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;
 | 
				
			||||||
| 
						 | 
					@ -112,6 +114,8 @@ Route::middleware('api')
 | 
				
			||||||
                Route::get('/get-all-sprint-by-id-board', [JiraController::class, 'getAllSprintByIdBoard'])->middleware('check.permission:admin.tester');
 | 
					                Route::get('/get-all-sprint-by-id-board', [JiraController::class, 'getAllSprintByIdBoard'])->middleware('check.permission:admin.tester');
 | 
				
			||||||
                Route::get('/get-all-issue-by-id-sprint', [JiraController::class, 'getAllIssueByIdSprint']);
 | 
					                Route::get('/get-all-issue-by-id-sprint', [JiraController::class, 'getAllIssueByIdSprint']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Route::get('/export-weekly-report', [JiraController::class, 'getWeeklyReport']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Route::get('/all-issue-by-project', [JiraController::class, 'fetchIssuesByProject']);
 | 
					                Route::get('/all-issue-by-project', [JiraController::class, 'fetchIssuesByProject']);
 | 
				
			||||||
                Route::get('/worklogs', [JiraController::class, 'getAllUserWorkLogs'])->middleware('check.permission:admin.staff');
 | 
					                Route::get('/worklogs', [JiraController::class, 'getAllUserWorkLogs'])->middleware('check.permission:admin.staff');
 | 
				
			||||||
                Route::get('/allocation', [JiraController::class, 'getAllUserDoing'])->middleware('check.permission:admin.staff');
 | 
					                Route::get('/allocation', [JiraController::class, 'getAllUserDoing'])->middleware('check.permission:admin.staff');
 | 
				
			||||||
| 
						 | 
					@ -214,6 +218,32 @@ 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.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);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,9 @@ class JiraService
 | 
				
			||||||
                'Authorization' => $this->authHeader,
 | 
					                'Authorization' => $this->authHeader,
 | 
				
			||||||
                'Accept' => 'application/json',
 | 
					                'Accept' => 'application/json',
 | 
				
			||||||
                'Content-Type' => 'application/json'
 | 
					                'Content-Type' => 'application/json'
 | 
				
			||||||
            ]
 | 
					            ],
 | 
				
			||||||
 | 
					            'timeout'  => 60, // Tăng thời gian timeout lên 60 giây
 | 
				
			||||||
 | 
					            'connect_timeout' => 30 // Tăng thời gian chờ kết nối lên 30 giây
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -230,6 +232,7 @@ class JiraService
 | 
				
			||||||
        $groupedIssues = [];
 | 
					        $groupedIssues = [];
 | 
				
			||||||
        $users_data = [];
 | 
					        $users_data = [];
 | 
				
			||||||
        $user_warning = [];
 | 
					        $user_warning = [];
 | 
				
			||||||
 | 
					        $ignore_projects = ['PJ_tracking'];
 | 
				
			||||||
        foreach ($users as $user) {
 | 
					        foreach ($users as $user) {
 | 
				
			||||||
            $user = (array) $user[0];
 | 
					            $user = (array) $user[0];
 | 
				
			||||||
            $users_data[$user['displayName']]['user'] = $user;
 | 
					            $users_data[$user['displayName']]['user'] = $user;
 | 
				
			||||||
| 
						 | 
					@ -252,32 +255,41 @@ class JiraService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $issues = json_decode($response->getBody()->getContents(), true);
 | 
					            $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) {
 | 
					            if (count($issues['issues']) == 0) {
 | 
				
			||||||
                $user_warning[] = $user;
 | 
					                $user_warning[] = $user;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            foreach ($issues['issues'] as $issue) {
 | 
					            foreach ($issues['issues'] as $issue) {
 | 
				
			||||||
                $projectName = $issue['fields']['project']['name'];
 | 
					                $projectName = $issue['fields']['project']['name'];
 | 
				
			||||||
                $username = $issue['fields']['assignee']['displayName'];
 | 
					                if (!in_array($projectName, $ignore_projects)) {
 | 
				
			||||||
                $issue['fields']['assignee']['emailAddress'] = $user['emailAddress'];
 | 
					                    $username = $issue['fields']['assignee']['displayName'];
 | 
				
			||||||
                if (!isset($groupedIssues[$projectName])) {
 | 
					                    $issue['fields']['assignee']['emailAddress'] = $user['emailAddress'];
 | 
				
			||||||
                    $groupedIssues[$projectName] = [];
 | 
					                    if (!isset($groupedIssues[$projectName])) {
 | 
				
			||||||
                    $groupedIssues[$projectName]['project'] = $issue['fields']['project'];
 | 
					                        $groupedIssues[$projectName] = [];
 | 
				
			||||||
 | 
					                        $groupedIssues[$projectName]['project'] = $issue['fields']['project'];
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (!isset($groupedIssues[$projectName]['users'][$username])) {
 | 
				
			||||||
 | 
					                        $groupedIssues[$projectName]['users'][$username] = [];
 | 
				
			||||||
 | 
					                        $groupedIssues[$projectName]['users'][$username]['user'] = $issue['fields']['assignee'];
 | 
				
			||||||
 | 
					                        $groupedIssues[$projectName]['users'][$username]['p_total_spent'] = 0;
 | 
				
			||||||
 | 
					                        $groupedIssues[$projectName]['users'][$username]['p_total_est'] = 0;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    $groupedIssues[$projectName]['users'][$username]['issues'][] = $issue;
 | 
				
			||||||
 | 
					                    $groupedIssues[$projectName]['users'][$username]['p_total_spent'] = $groupedIssues[$projectName]['users'][$username]['p_total_spent'] + $issue['fields']['timespent'];
 | 
				
			||||||
 | 
					                    $groupedIssues[$projectName]['users'][$username]['p_total_est'] = $groupedIssues[$projectName]['users'][$username]['p_total_est'] + ($issue['fields']['timeoriginalestimate'] ?? 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    $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);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (!isset($groupedIssues[$projectName]['users'][$username])) {
 | 
					 | 
				
			||||||
                    $groupedIssues[$projectName]['users'][$username] = [];
 | 
					 | 
				
			||||||
                    $groupedIssues[$projectName]['users'][$username]['user'] = $issue['fields']['assignee'];
 | 
					 | 
				
			||||||
                    $groupedIssues[$projectName]['users'][$username]['p_total_spent'] = 0;
 | 
					 | 
				
			||||||
                    $groupedIssues[$projectName]['users'][$username]['p_total_est'] = 0;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                $groupedIssues[$projectName]['users'][$username]['issues'][] = $issue;
 | 
					 | 
				
			||||||
                $groupedIssues[$projectName]['users'][$username]['p_total_spent'] = $groupedIssues[$projectName]['users'][$username]['p_total_spent'] + $issue['fields']['timespent'];
 | 
					 | 
				
			||||||
                $groupedIssues[$projectName]['users'][$username]['p_total_est'] = $groupedIssues[$projectName]['users'][$username]['p_total_est'] + ($issue['fields']['timeoriginalestimate'] ?? 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                $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);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -314,4 +326,18 @@ class JiraService
 | 
				
			||||||
        $response = $this->client->get('/rest/agile/1.0/sprint/' . $id . '/issue');
 | 
					        $response = $this->client->get('/rest/agile/1.0/sprint/' . $id . '/issue');
 | 
				
			||||||
        return json_decode($response->getBody()->getContents(), true);
 | 
					        return json_decode($response->getBody()->getContents(), true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getWeeklyReport()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $body = [
 | 
				
			||||||
 | 
					            'fields' => ['summary', 'status', 'timeoriginalestimate', 'timespent', 'assignee', 'project', 'worklog'],
 | 
				
			||||||
 | 
					            'jql' => 'worklogDate >= startOfWeek() AND worklogDate < startOfWeek(1) order by created DESC',
 | 
				
			||||||
 | 
					            'maxResults' => 1000
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $response = $this->client->post('/rest/api/3/search', [
 | 
				
			||||||
 | 
					            'body' => json_encode($body)
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					        return json_decode($response->getBody()->getContents(), true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -1 +1,3 @@
 | 
				
			||||||
VITE_BACKEND_URL=http://localhost:8000/
 | 
					VITE_BACKEND_URL=http://localhost:8000/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					VITE_URL_DRAWIO="https://viewer.diagrams.net/?tags=%7B%7D&lightbox=1&highlight=0000ff&edit=_blank&layers=1&nav=1&title=Test%20Draw.drawio&dark=auto#Uhttps%3A%2F%2Fdrive.google.com%2Fuc%3Fid%3D1LmB9wCac9DonQPFU-53g1nhI9SfvWuWK%26export%3Ddownload"
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -34,6 +34,7 @@
 | 
				
			||||||
    "react": "^18.2.0",
 | 
					    "react": "^18.2.0",
 | 
				
			||||||
    "react-doc-viewer": "^0.1.14",
 | 
					    "react-doc-viewer": "^0.1.14",
 | 
				
			||||||
    "react-dom": "^18.2.0",
 | 
					    "react-dom": "^18.2.0",
 | 
				
			||||||
 | 
					    "react-file-viewer": "^1.2.1",
 | 
				
			||||||
    "react-redux": "^8.1.3",
 | 
					    "react-redux": "^8.1.3",
 | 
				
			||||||
    "react-router-dom": "^6.19.0",
 | 
					    "react-router-dom": "^6.19.0",
 | 
				
			||||||
    "reactstrap": "^9.2.2",
 | 
					    "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 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'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Download File
 | 
				
			||||||
 | 
					export const downloadFile = API_URL + 'v1/admin/download-file'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -250,12 +250,7 @@ export const DataTableAll = ({
 | 
				
			||||||
    if (query !== '') {
 | 
					    if (query !== '') {
 | 
				
			||||||
      setTData(
 | 
					      setTData(
 | 
				
			||||||
        data.filter((obj) =>
 | 
					        data.filter((obj) =>
 | 
				
			||||||
          Object.values(obj).some(
 | 
					          Object.values(obj)?.find((c: any) => c.toString().normalize('NFC').toLowerCase().includes(query.normalize('NFC').toLowerCase())))
 | 
				
			||||||
            (value: any) =>
 | 
					 | 
				
			||||||
              value !== null &&
 | 
					 | 
				
			||||||
              value.toString().toLowerCase().includes(query.toLowerCase()),
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      if (pagination) {
 | 
					      if (pagination) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,hr,staff,tester,accountant',
 | 
				
			||||||
 | 
					    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' },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,3 +3,8 @@ declare module '*.css'
 | 
				
			||||||
declare module '@codemirror/lang-javascript'
 | 
					declare module '@codemirror/lang-javascript'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare const __VITE_BACKEND_URL__: string
 | 
					declare const __VITE_BACKEND_URL__: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare module 'react-file-viewer' {
 | 
				
			||||||
 | 
					  const FileViewer: any
 | 
				
			||||||
 | 
					  export default FileViewer
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ import {
 | 
				
			||||||
  Popover,
 | 
					  Popover,
 | 
				
			||||||
  Text,
 | 
					  Text,
 | 
				
			||||||
  Tooltip,
 | 
					  Tooltip,
 | 
				
			||||||
 | 
					  Switch,
 | 
				
			||||||
} from '@mantine/core'
 | 
					} from '@mantine/core'
 | 
				
			||||||
import { IconInnerShadowTopRightFilled } from '@tabler/icons-react'
 | 
					import { IconInnerShadowTopRightFilled } from '@tabler/icons-react'
 | 
				
			||||||
import { useEffect, useState } from 'react'
 | 
					import { useEffect, useState } from 'react'
 | 
				
			||||||
| 
						 | 
					@ -80,6 +81,31 @@ const Allocation = () => {
 | 
				
			||||||
  const [opened, setOpened] = useState(false)
 | 
					  const [opened, setOpened] = useState(false)
 | 
				
			||||||
  const [issDetail, setIssDetail] = useState('')
 | 
					  const [issDetail, setIssDetail] = useState('')
 | 
				
			||||||
  const [data, setData] = useState<any>({})
 | 
					  const [data, setData] = useState<any>({})
 | 
				
			||||||
 | 
					  const [showDrawio, setShowDrawio] = useState(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    // Check if iframe already exists
 | 
				
			||||||
 | 
					    const existingIframe = document.querySelector('#drawio iframe')
 | 
				
			||||||
 | 
					    if (existingIframe) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Add iframe only if it doesn't exist
 | 
				
			||||||
 | 
					    const iframe = document.createElement('iframe')
 | 
				
			||||||
 | 
					    iframe.src = import.meta.env.VITE_URL_DRAWIO ?? ''
 | 
				
			||||||
 | 
					    iframe.style.width = '100%'
 | 
				
			||||||
 | 
					    iframe.style.height = '500px'
 | 
				
			||||||
 | 
					    const drawioDiv = document.getElementById('drawio')
 | 
				
			||||||
 | 
					    if (drawioDiv) {
 | 
				
			||||||
 | 
					      drawioDiv.appendChild(iframe)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      const iframe = document.querySelector('#drawio iframe')
 | 
				
			||||||
 | 
					      if (iframe) {
 | 
				
			||||||
 | 
					        iframe.remove()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getAll = async () => {
 | 
					  const getAll = async () => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const res = await get(getAllUserDoing)
 | 
					      const res = await get(getAllUserDoing)
 | 
				
			||||||
| 
						 | 
					@ -117,6 +143,14 @@ const Allocation = () => {
 | 
				
			||||||
  }, [])
 | 
					  }, [])
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
 | 
					      <Box style={{ display: 'flex', alignItems: 'center', margin: '10px' }}>
 | 
				
			||||||
 | 
					        <Switch
 | 
				
			||||||
 | 
					          label="Show Diagram"
 | 
				
			||||||
 | 
					          checked={showDrawio}
 | 
				
			||||||
 | 
					          onChange={(event) => setShowDrawio(event.currentTarget.checked)}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </Box>
 | 
				
			||||||
 | 
					      <div id="drawio" style={{ display: showDrawio ? 'block' : 'none' }}></div>
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
        <Box
 | 
					        <Box
 | 
				
			||||||
          style={{
 | 
					          style={{
 | 
				
			||||||
| 
						 | 
					@ -138,12 +172,12 @@ const Allocation = () => {
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <Box>
 | 
					          <Box>
 | 
				
			||||||
            <Text fw={600} fz={'md'}>
 | 
					            {/* <Text fw={600} fz={'md'}>
 | 
				
			||||||
              Admin/
 | 
					              Admin/
 | 
				
			||||||
            </Text>
 | 
					            </Text>
 | 
				
			||||||
            <Text fw={700} fz={'lg'}>
 | 
					            <Text fw={700} fz={'lg'}>
 | 
				
			||||||
              Personnel Allocation
 | 
					              Personnel Allocation
 | 
				
			||||||
            </Text>
 | 
					            </Text> */}
 | 
				
			||||||
            <Text fw={600} fz={'sm'} c={'gray'} fs={'italic'} ml={'md'}>
 | 
					            <Text fw={600} fz={'sm'} c={'gray'} fs={'italic'} ml={'md'}>
 | 
				
			||||||
              "P:" is the timspent/timeestimate number within the project itself
 | 
					              "P:" is the timspent/timeestimate number within the project itself
 | 
				
			||||||
            </Text>
 | 
					            </Text>
 | 
				
			||||||
| 
						 | 
					@ -224,27 +258,38 @@ const Allocation = () => {
 | 
				
			||||||
                                        alignItems: 'center',
 | 
					                                        alignItems: 'center',
 | 
				
			||||||
                                      }}
 | 
					                                      }}
 | 
				
			||||||
                                    >
 | 
					                                    >
 | 
				
			||||||
                                      <Box w='10%'>
 | 
					                                      <Box w="10%">
 | 
				
			||||||
                                      <IconInnerShadowTopRightFilled
 | 
					                                        <IconInnerShadowTopRightFilled
 | 
				
			||||||
                                        style={{ color: 'orange' }}
 | 
					                                          style={{ color: 'orange' }}
 | 
				
			||||||
                                        height={20}
 | 
					                                          height={20}
 | 
				
			||||||
                                        width={20}
 | 
					                                          width={20}
 | 
				
			||||||
                                        display={userData.issues?.filter(
 | 
					                                          display={
 | 
				
			||||||
                                          (iss: Issue) =>
 | 
					                                            userData.issues?.filter(
 | 
				
			||||||
                                            iss.fields.status.name === 'In Progress' && 
 | 
					                                              (iss: Issue) =>
 | 
				
			||||||
                                          ((Date.now() - (new Date(iss.changelog?.histories[0]?.created)).getTime()) > 172800000)
 | 
					                                                iss.fields.status.name ===
 | 
				
			||||||
                                        ).length > 0 ? 'block' :'none'}
 | 
					                                                  'In Progress' &&
 | 
				
			||||||
                                      />
 | 
					                                                Date.now() -
 | 
				
			||||||
 | 
					                                                  new Date(
 | 
				
			||||||
 | 
					                                                    iss.changelog?.histories[0]?.created,
 | 
				
			||||||
 | 
					                                                  ).getTime() >
 | 
				
			||||||
 | 
					                                                  172800000,
 | 
				
			||||||
 | 
					                                            ).length > 0
 | 
				
			||||||
 | 
					                                              ? 'block'
 | 
				
			||||||
 | 
					                                              : 'none'
 | 
				
			||||||
 | 
					                                          }
 | 
				
			||||||
 | 
					                                        />
 | 
				
			||||||
                                      </Box>
 | 
					                                      </Box>
 | 
				
			||||||
                                      <Box display={'flex'}>
 | 
					                                      <Box display={'flex'}>
 | 
				
			||||||
                                      <Avatar
 | 
					                                        <Avatar
 | 
				
			||||||
                                        size={'sm'}
 | 
					                                          size={'sm'}
 | 
				
			||||||
                                        ml={'5px'}
 | 
					                                          ml={'5px'}
 | 
				
			||||||
                                        src={userData.user.avatarUrls['48x48']}
 | 
					                                          src={
 | 
				
			||||||
                                      />
 | 
					                                            userData.user.avatarUrls['48x48']
 | 
				
			||||||
                                      <Text ml={'md'} fw={600}>
 | 
					                                          }
 | 
				
			||||||
                                        {user}
 | 
					                                        />
 | 
				
			||||||
                                      </Text>
 | 
					                                        <Text ml={'md'} fw={600}>
 | 
				
			||||||
 | 
					                                          {user}
 | 
				
			||||||
 | 
					                                        </Text>
 | 
				
			||||||
                                      </Box>
 | 
					                                      </Box>
 | 
				
			||||||
                                    </Box>
 | 
					                                    </Box>
 | 
				
			||||||
                                    <Box
 | 
					                                    <Box
 | 
				
			||||||
| 
						 | 
					@ -260,9 +305,12 @@ const Allocation = () => {
 | 
				
			||||||
                                      }}
 | 
					                                      }}
 | 
				
			||||||
                                      ml={'md'}
 | 
					                                      ml={'md'}
 | 
				
			||||||
                                      p="0 20px"
 | 
					                                      p="0 20px"
 | 
				
			||||||
                                      
 | 
					 | 
				
			||||||
                                    >
 | 
					                                    >
 | 
				
			||||||
                                      <Text ml={'md'} fw={700} fz={'sm'}>{`P: `}</Text>
 | 
					                                      <Text
 | 
				
			||||||
 | 
					                                        ml={'md'}
 | 
				
			||||||
 | 
					                                        fw={700}
 | 
				
			||||||
 | 
					                                        fz={'sm'}
 | 
				
			||||||
 | 
					                                      >{`P: `}</Text>
 | 
				
			||||||
                                      <Text fw={700} c="green" fz={'sm'}>{`${
 | 
					                                      <Text fw={700} c="green" fz={'sm'}>{`${
 | 
				
			||||||
                                        userData.p_total_spent / 60 / 60
 | 
					                                        userData.p_total_spent / 60 / 60
 | 
				
			||||||
                                      }h/`}</Text>
 | 
					                                      }h/`}</Text>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,338 @@
 | 
				
			||||||
 | 
					import { useEffect, useState } from 'react'
 | 
				
			||||||
 | 
					import { get } from '@/rtk/helpers/apiService'
 | 
				
			||||||
 | 
					import { deleteDocument, listDocument } from '@/api/Admin'
 | 
				
			||||||
 | 
					import { Xdelete } from '@/rtk/helpers/CRUD'
 | 
				
			||||||
 | 
					import { useSelector } from 'react-redux'
 | 
				
			||||||
 | 
					import { RootState } from '@/rtk/store'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Anchor, Box, Button, Dialog, Group, Loader, Text } from '@mantine/core'
 | 
				
			||||||
 | 
					import { useDisclosure } from '@mantine/hooks'
 | 
				
			||||||
 | 
					import { notifications } from '@mantine/notifications'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  IconEdit,
 | 
				
			||||||
 | 
					  IconFileTypeDoc,
 | 
				
			||||||
 | 
					  IconFileTypePdf,
 | 
				
			||||||
 | 
					  IconFileTypeXls,
 | 
				
			||||||
 | 
					  IconLink,
 | 
				
			||||||
 | 
					  IconX,
 | 
				
			||||||
 | 
					} from '@tabler/icons-react'
 | 
				
			||||||
 | 
					import DataTableAll from '@/components/DataTable/DataTable'
 | 
				
			||||||
 | 
					import ModalFileDocument from './ModalFileDocument'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import classes from './Document.module.css'
 | 
				
			||||||
 | 
					import ModalAddDocument from './ModalAddDocument'
 | 
				
			||||||
 | 
					import ModalEditDocument from './ModalEditDocument'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 user = useSelector((state: RootState) => state.authentication)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [loader, setLoader] = useState<boolean>(false)
 | 
				
			||||||
 | 
					  const [rows, setRows] = useState<RequestPagination>({
 | 
				
			||||||
 | 
					    data: [],
 | 
				
			||||||
 | 
					    status: true,
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  const [selectDataRow, setSelectDataRow] = useState<any>({})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [openedModalAdd, { open: openModalAdd, close: closeModalAdd }] =
 | 
				
			||||||
 | 
					    useDisclosure(false)
 | 
				
			||||||
 | 
					  const [openedModalEdit, { open: openModalEdit, close: closeModalEdit }] =
 | 
				
			||||||
 | 
					    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 = (row: TDocument) => {
 | 
				
			||||||
 | 
					    const uri = row?.uri
 | 
				
			||||||
 | 
					    if (!uri) return null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const extension = uri.split('.').pop()?.toLowerCase()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (['doc'].includes(extension!)) {
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        <a
 | 
				
			||||||
 | 
					          href={`${import.meta.env.VITE_BACKEND_URL}${uri}`}
 | 
				
			||||||
 | 
					          download="Document Download"
 | 
				
			||||||
 | 
					          target="_self"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <IconFileTypeDoc style={{ color: '#1e62c1' }} />
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (['xls', 'xlsx'].includes(extension!)) {
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        <a
 | 
				
			||||||
 | 
					          href={`${import.meta.env.VITE_BACKEND_URL}${uri}`}
 | 
				
			||||||
 | 
					          download="Document Download"
 | 
				
			||||||
 | 
					          target="_self"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <IconFileTypeXls style={{ color: '#0e864b' }} />
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (['docx'].includes(extension!)) {
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        <IconFileTypeDoc
 | 
				
			||||||
 | 
					          onClick={() => {
 | 
				
			||||||
 | 
					            openModalFile()
 | 
				
			||||||
 | 
					            setSelectDataRow(row)
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          style={{ color: '#1e62c1' }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <IconFileTypePdf
 | 
				
			||||||
 | 
					        onClick={() => {
 | 
				
			||||||
 | 
					          openModalFile()
 | 
				
			||||||
 | 
					          setSelectDataRow(row)
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					        style={{ color: '#ff1b0e' }}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const columns = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      name: 'title',
 | 
				
			||||||
 | 
					      size: '50%',
 | 
				
			||||||
 | 
					      header: 'Title',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      name: 'uri',
 | 
				
			||||||
 | 
					      size: '45%',
 | 
				
			||||||
 | 
					      header: 'URI',
 | 
				
			||||||
 | 
					      render: (row: TDocument) => {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					          <Box>
 | 
				
			||||||
 | 
					            {row.type === 'file' ? (
 | 
				
			||||||
 | 
					              <Box
 | 
				
			||||||
 | 
					                w="fit-content"
 | 
				
			||||||
 | 
					                style={{ cursor: 'pointer' }}
 | 
				
			||||||
 | 
					                title={`File .${row?.uri
 | 
				
			||||||
 | 
					                  .split('.')
 | 
				
			||||||
 | 
					                  .pop()
 | 
				
			||||||
 | 
					                  ?.toLowerCase()} detail`}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                {getFileTypeIcon(row)}
 | 
				
			||||||
 | 
					              </Box>
 | 
				
			||||||
 | 
					            ) : (
 | 
				
			||||||
 | 
					              <Anchor
 | 
				
			||||||
 | 
					                ta="start"
 | 
				
			||||||
 | 
					                href={row?.uri}
 | 
				
			||||||
 | 
					                target="_blank"
 | 
				
			||||||
 | 
					                title={row?.uri}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <IconLink />
 | 
				
			||||||
 | 
					              </Anchor>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </Box>
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      name: '#',
 | 
				
			||||||
 | 
					      size: '5%',
 | 
				
			||||||
 | 
					      header: 'Action',
 | 
				
			||||||
 | 
					      render: (row: TDocument) => {
 | 
				
			||||||
 | 
					        if (!user.user.user.permission.includes('admin')) {
 | 
				
			||||||
 | 
					          return ''
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					          <Box className={classes.optionIcon}>
 | 
				
			||||||
 | 
					            <IconEdit
 | 
				
			||||||
 | 
					              className={classes.editIcon}
 | 
				
			||||||
 | 
					              onClick={() => {
 | 
				
			||||||
 | 
					                setSelectDataRow(row)
 | 
				
			||||||
 | 
					                openModalEdit()
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					              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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {user.user.user.permission.includes('admin') ? (
 | 
				
			||||||
 | 
					          <Button onClick={() => openModalAdd()} 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}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {openedModalAdd && (
 | 
				
			||||||
 | 
					        <ModalAddDocument
 | 
				
			||||||
 | 
					          opened={openedModalAdd}
 | 
				
			||||||
 | 
					          close={closeModalAdd}
 | 
				
			||||||
 | 
					          getAllData={getAllData}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {openedModalEdit && (
 | 
				
			||||||
 | 
					        <ModalEditDocument
 | 
				
			||||||
 | 
					          opened={openedModalEdit}
 | 
				
			||||||
 | 
					          close={closeModalEdit}
 | 
				
			||||||
 | 
					          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,254 @@
 | 
				
			||||||
 | 
					import { useEffect, useState } from 'react'
 | 
				
			||||||
 | 
					import { useForm } from '@mantine/form'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Modal,
 | 
				
			||||||
 | 
					  Button,
 | 
				
			||||||
 | 
					  Text,
 | 
				
			||||||
 | 
					  Box,
 | 
				
			||||||
 | 
					  Switch,
 | 
				
			||||||
 | 
					  Checkbox,
 | 
				
			||||||
 | 
					  FileInput,
 | 
				
			||||||
 | 
					  TextInput,
 | 
				
			||||||
 | 
					  Group,
 | 
				
			||||||
 | 
					  ActionIcon,
 | 
				
			||||||
 | 
					} from '@mantine/core'
 | 
				
			||||||
 | 
					import { IconPlus, IconTrash } from '@tabler/icons-react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { create } from '@/rtk/helpers/CRUD'
 | 
				
			||||||
 | 
					import { createDocument } from '@/api/Admin'
 | 
				
			||||||
 | 
					import { getHeaderInfo } from '@/rtk/helpers/tokenCreator'
 | 
				
			||||||
 | 
					import { notifications } from '@mantine/notifications'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MProps = {
 | 
				
			||||||
 | 
					  opened: boolean
 | 
				
			||||||
 | 
					  close: () => void
 | 
				
			||||||
 | 
					  getAllData: () => void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ModalAddDocument = ({ opened, close, getAllData }: MProps) => {
 | 
				
			||||||
 | 
					  const [loadingSubmit, setLoadingSubmit] = useState(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const form = useForm({
 | 
				
			||||||
 | 
					    initialValues: {
 | 
				
			||||||
 | 
					      type: 'file',
 | 
				
			||||||
 | 
					      files: [] as { title: string; file: File }[],
 | 
				
			||||||
 | 
					      links: [] as { title: string; uri: string }[],
 | 
				
			||||||
 | 
					      is_active: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    form.reset()
 | 
				
			||||||
 | 
					  }, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleCreate = async (values: any) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      if (values.type === 'file' && values.files.length === 0) {
 | 
				
			||||||
 | 
					        notifications.show({
 | 
				
			||||||
 | 
					          title: 'Error',
 | 
				
			||||||
 | 
					          message: 'No files uploaded!!!',
 | 
				
			||||||
 | 
					          color: 'red',
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (values.type === 'link' && values.links.length === 0) {
 | 
				
			||||||
 | 
					        notifications.show({
 | 
				
			||||||
 | 
					          title: 'Error',
 | 
				
			||||||
 | 
					          message: 'No links provided!!!',
 | 
				
			||||||
 | 
					          color: 'red',
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setLoadingSubmit(true)
 | 
				
			||||||
 | 
					      const header = await getHeaderInfo()
 | 
				
			||||||
 | 
					      const formData = new FormData()
 | 
				
			||||||
 | 
					      header.headers['Content-Type'] = 'multipart/form-data'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      formData.append('type', values.type)
 | 
				
			||||||
 | 
					      formData.append('is_active', values.is_active ? '1' : '0')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (values.type === 'file') {
 | 
				
			||||||
 | 
					        values.files.forEach((item: any, index: number) => {
 | 
				
			||||||
 | 
					          formData.append(`files[${index}][title]`, item.title)
 | 
				
			||||||
 | 
					          formData.append(`files[${index}][file]`, item.file)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        values.links.forEach((item: any, index: number) => {
 | 
				
			||||||
 | 
					          formData.append(`links[${index}][title]`, item.title)
 | 
				
			||||||
 | 
					          formData.append(`links[${index}][uri]`, item.uri)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const res = await create(createDocument, formData, getAllData, header)
 | 
				
			||||||
 | 
					      if (res === true) {
 | 
				
			||||||
 | 
					        resetForm()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.error(error)
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      setLoadingSubmit(false)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const resetForm = () => {
 | 
				
			||||||
 | 
					    close()
 | 
				
			||||||
 | 
					    form.reset()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Modal
 | 
				
			||||||
 | 
					      opened={opened}
 | 
				
			||||||
 | 
					      onClose={resetForm}
 | 
				
			||||||
 | 
					      size="lg"
 | 
				
			||||||
 | 
					      title={
 | 
				
			||||||
 | 
					        <Text fw={700} fz={'lg'}>
 | 
				
			||||||
 | 
					          Add Document
 | 
				
			||||||
 | 
					        </Text>
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <form onSubmit={form.onSubmit(handleCreate)}>
 | 
				
			||||||
 | 
					        <Switch
 | 
				
			||||||
 | 
					          style={{ width: 'fit-content' }}
 | 
				
			||||||
 | 
					          label={form.values.type === 'file' ? 'Upload files' : 'Enter links'}
 | 
				
			||||||
 | 
					          checked={form.values.type === 'file'}
 | 
				
			||||||
 | 
					          onChange={(event) =>
 | 
				
			||||||
 | 
					            form.setFieldValue(
 | 
				
			||||||
 | 
					              'type',
 | 
				
			||||||
 | 
					              event.currentTarget.checked ? 'file' : 'link',
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          mb={'md'}
 | 
				
			||||||
 | 
					          disabled={loadingSubmit}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {form.values.type === 'file' ? (
 | 
				
			||||||
 | 
					          <Box>
 | 
				
			||||||
 | 
					            <FileInput
 | 
				
			||||||
 | 
					              accept=".doc,.docx,.xls,.xlsx,.pdf"
 | 
				
			||||||
 | 
					              label="Upload Doc, Excel, PDF files"
 | 
				
			||||||
 | 
					              multiple
 | 
				
			||||||
 | 
					              mb="md"
 | 
				
			||||||
 | 
					              value={form.values.files.map((file) => file.file)}
 | 
				
			||||||
 | 
					              onChange={(files) => {
 | 
				
			||||||
 | 
					                if (files) {
 | 
				
			||||||
 | 
					                  const newFiles = files.map((file) => ({
 | 
				
			||||||
 | 
					                    title: file.name.split('.')[0],
 | 
				
			||||||
 | 
					                    file,
 | 
				
			||||||
 | 
					                  }))
 | 
				
			||||||
 | 
					                  form.setFieldValue('files', newFiles)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					              disabled={loadingSubmit}
 | 
				
			||||||
 | 
					              required
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {form.values.files.map((item, index) => (
 | 
				
			||||||
 | 
					              <Group key={index} mb={'md'}>
 | 
				
			||||||
 | 
					                <TextInput
 | 
				
			||||||
 | 
					                  w="50%"
 | 
				
			||||||
 | 
					                  label="Title"
 | 
				
			||||||
 | 
					                  value={form.values.files[index].title}
 | 
				
			||||||
 | 
					                  onChange={(event) =>
 | 
				
			||||||
 | 
					                    form.setFieldValue(
 | 
				
			||||||
 | 
					                      `files.${index}.title`,
 | 
				
			||||||
 | 
					                      event.currentTarget.value,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  maxLength={255}
 | 
				
			||||||
 | 
					                  disabled={loadingSubmit}
 | 
				
			||||||
 | 
					                  required
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <Text mt={24} w="30%">
 | 
				
			||||||
 | 
					                  {item.file?.name}
 | 
				
			||||||
 | 
					                </Text>
 | 
				
			||||||
 | 
					              </Group>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					          </Box>
 | 
				
			||||||
 | 
					        ) : (
 | 
				
			||||||
 | 
					          <Box>
 | 
				
			||||||
 | 
					            {form.values.links.map((_, index) => (
 | 
				
			||||||
 | 
					              <Group key={index} mb={'md'}>
 | 
				
			||||||
 | 
					                <TextInput
 | 
				
			||||||
 | 
					                  w="35%"
 | 
				
			||||||
 | 
					                  label="Title"
 | 
				
			||||||
 | 
					                  value={form.values.links[index].title}
 | 
				
			||||||
 | 
					                  onChange={(event) =>
 | 
				
			||||||
 | 
					                    form.setFieldValue(
 | 
				
			||||||
 | 
					                      `links.${index}.title`,
 | 
				
			||||||
 | 
					                      event.currentTarget.value,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  maxLength={255}
 | 
				
			||||||
 | 
					                  disabled={loadingSubmit}
 | 
				
			||||||
 | 
					                  required
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <TextInput
 | 
				
			||||||
 | 
					                  w="50%"
 | 
				
			||||||
 | 
					                  label="URL"
 | 
				
			||||||
 | 
					                  value={form.values.links[index].uri}
 | 
				
			||||||
 | 
					                  onChange={(event) =>
 | 
				
			||||||
 | 
					                    form.setFieldValue(
 | 
				
			||||||
 | 
					                      `links.${index}.uri`,
 | 
				
			||||||
 | 
					                      event.currentTarget.value,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  disabled={loadingSubmit}
 | 
				
			||||||
 | 
					                  required
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <ActionIcon
 | 
				
			||||||
 | 
					                  color="red"
 | 
				
			||||||
 | 
					                  variant="light"
 | 
				
			||||||
 | 
					                  onClick={() =>
 | 
				
			||||||
 | 
					                    form.setFieldValue(
 | 
				
			||||||
 | 
					                      'links',
 | 
				
			||||||
 | 
					                      form.values.links.filter((_, i) => i !== index),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  disabled={loadingSubmit}
 | 
				
			||||||
 | 
					                  mt={24}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  <IconTrash size={16} />
 | 
				
			||||||
 | 
					                </ActionIcon>
 | 
				
			||||||
 | 
					              </Group>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <Button
 | 
				
			||||||
 | 
					              leftSection={<IconPlus size={16} />}
 | 
				
			||||||
 | 
					              variant="light"
 | 
				
			||||||
 | 
					              onClick={() =>
 | 
				
			||||||
 | 
					                form.setFieldValue('links', [
 | 
				
			||||||
 | 
					                  ...form.values.links,
 | 
				
			||||||
 | 
					                  { title: '', uri: '' },
 | 
				
			||||||
 | 
					                ])
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              disabled={loadingSubmit}
 | 
				
			||||||
 | 
					              mb="md"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              Add Link
 | 
				
			||||||
 | 
					            </Button>
 | 
				
			||||||
 | 
					          </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}>
 | 
				
			||||||
 | 
					            Create
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					        </Box>
 | 
				
			||||||
 | 
					      </form>
 | 
				
			||||||
 | 
					    </Modal>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ModalAddDocument
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,285 @@
 | 
				
			||||||
 | 
					import { useEffect, useState } from 'react'
 | 
				
			||||||
 | 
					import { useForm } from '@mantine/form'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Modal,
 | 
				
			||||||
 | 
					  Button,
 | 
				
			||||||
 | 
					  Text,
 | 
				
			||||||
 | 
					  Box,
 | 
				
			||||||
 | 
					  Checkbox,
 | 
				
			||||||
 | 
					  FileInput,
 | 
				
			||||||
 | 
					  Badge,
 | 
				
			||||||
 | 
					  TextInput,
 | 
				
			||||||
 | 
					  Group,
 | 
				
			||||||
 | 
					} from '@mantine/core'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { update } from '@/rtk/helpers/CRUD'
 | 
				
			||||||
 | 
					import { updateDocument } from '@/api/Admin'
 | 
				
			||||||
 | 
					import { getHeaderInfo } from '@/rtk/helpers/tokenCreator'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  IconFileTypeDoc,
 | 
				
			||||||
 | 
					  IconFileTypePdf,
 | 
				
			||||||
 | 
					  IconFileTypeXls,
 | 
				
			||||||
 | 
					} from '@tabler/icons-react'
 | 
				
			||||||
 | 
					import ModalFileDocument from './ModalFileDocument'
 | 
				
			||||||
 | 
					import { useDisclosure } from '@mantine/hooks'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MProps = {
 | 
				
			||||||
 | 
					  opened: boolean
 | 
				
			||||||
 | 
					  close: () => void
 | 
				
			||||||
 | 
					  selectDataRow: any
 | 
				
			||||||
 | 
					  setSelectDataRow: any
 | 
				
			||||||
 | 
					  getAllData: () => void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ModalEditDocument = ({
 | 
				
			||||||
 | 
					  opened,
 | 
				
			||||||
 | 
					  close,
 | 
				
			||||||
 | 
					  selectDataRow,
 | 
				
			||||||
 | 
					  setSelectDataRow,
 | 
				
			||||||
 | 
					  getAllData,
 | 
				
			||||||
 | 
					}: MProps) => {
 | 
				
			||||||
 | 
					  const [loadingSubmit, setLoadingSubmit] = useState(false)
 | 
				
			||||||
 | 
					  const [selectDataFileRow, setSelectDataFileRow] = useState<any>({})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [openedModalFile, { open: openModalFile, close: closeModalFile }] =
 | 
				
			||||||
 | 
					    useDisclosure(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const form = useForm({
 | 
				
			||||||
 | 
					    initialValues: {
 | 
				
			||||||
 | 
					      title: '',
 | 
				
			||||||
 | 
					      type: true,
 | 
				
			||||||
 | 
					      file: null as File | null,
 | 
				
			||||||
 | 
					      uri: '',
 | 
				
			||||||
 | 
					      is_active: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    form.setValues({
 | 
				
			||||||
 | 
					      title: selectDataRow?.title || '',
 | 
				
			||||||
 | 
					      type: selectDataRow?.type === 'file',
 | 
				
			||||||
 | 
					      uri: selectDataRow?.uri || '',
 | 
				
			||||||
 | 
					      is_active: selectDataRow?.is_active || true,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }, [selectDataRow])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleUpdate = async (data: any) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      setLoadingSubmit(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let formData: any = {}
 | 
				
			||||||
 | 
					      const header = await getHeaderInfo()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (data.type) {
 | 
				
			||||||
 | 
					        header.headers['Content-Type'] = 'multipart/form-data'
 | 
				
			||||||
 | 
					        const tmpFormData = new FormData()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tmpFormData.append('title', data.title)
 | 
				
			||||||
 | 
					        tmpFormData.append('type', 'file')
 | 
				
			||||||
 | 
					        tmpFormData.append('is_active', data.is_active ? '1' : '0')
 | 
				
			||||||
 | 
					        tmpFormData.append('existing_file', data.uri)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (data.file) {
 | 
				
			||||||
 | 
					          tmpFormData.append('file', data.file)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formData = tmpFormData
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        formData = {
 | 
				
			||||||
 | 
					          title: data.title,
 | 
				
			||||||
 | 
					          type: 'link',
 | 
				
			||||||
 | 
					          uri: data.uri,
 | 
				
			||||||
 | 
					          is_active: data.is_active ? '1' : '0',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const res = await update(
 | 
				
			||||||
 | 
					        updateDocument + `?id=${selectDataRow?.id}`,
 | 
				
			||||||
 | 
					        formData,
 | 
				
			||||||
 | 
					        getAllData,
 | 
				
			||||||
 | 
					        header,
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      if (res === true) {
 | 
				
			||||||
 | 
					        resetForm()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      console.error(error)
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      setLoadingSubmit(false)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const getFileTypeIcon = (uri: string) => {
 | 
				
			||||||
 | 
					    if (!uri) return null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const extension = uri.split('.').pop()?.toLowerCase()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (['doc'].includes(extension!)) {
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        <a
 | 
				
			||||||
 | 
					          href={`${import.meta.env.VITE_BACKEND_URL}${uri}`}
 | 
				
			||||||
 | 
					          download="Document Download"
 | 
				
			||||||
 | 
					          target="_self"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <IconFileTypeDoc style={{ color: '#1e62c1' }} />
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (['xls', 'xlsx'].includes(extension!)) {
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        <a
 | 
				
			||||||
 | 
					          href={`${import.meta.env.VITE_BACKEND_URL}${uri}`}
 | 
				
			||||||
 | 
					          download="Document Download"
 | 
				
			||||||
 | 
					          target="_self"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <IconFileTypeXls style={{ color: '#0e864b' }} />
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (['docx'].includes(extension!)) {
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        <IconFileTypeDoc
 | 
				
			||||||
 | 
					          onClick={() => {
 | 
				
			||||||
 | 
					            openModalFile()
 | 
				
			||||||
 | 
					            setSelectDataFileRow(selectDataRow)
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          style={{ color: '#1e62c1' }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <IconFileTypePdf
 | 
				
			||||||
 | 
					        onClick={() => {
 | 
				
			||||||
 | 
					          openModalFile()
 | 
				
			||||||
 | 
					          setSelectDataFileRow(selectDataRow)
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					        style={{ color: '#ff1b0e' }}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const resetForm = () => {
 | 
				
			||||||
 | 
					    close()
 | 
				
			||||||
 | 
					    form.reset()
 | 
				
			||||||
 | 
					    setSelectDataRow({})
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Modal
 | 
				
			||||||
 | 
					      opened={opened}
 | 
				
			||||||
 | 
					      onClose={resetForm}
 | 
				
			||||||
 | 
					      size="lg"
 | 
				
			||||||
 | 
					      title={
 | 
				
			||||||
 | 
					        <Text fw={700} fz={'lg'}>
 | 
				
			||||||
 | 
					          Update Document
 | 
				
			||||||
 | 
					        </Text>
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <form
 | 
				
			||||||
 | 
					        onSubmit={form.onSubmit((values) => {
 | 
				
			||||||
 | 
					          handleUpdate(values)
 | 
				
			||||||
 | 
					        })}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {form.values.type ? (
 | 
				
			||||||
 | 
					          <Badge tt="initial" color="orange" mb="md">
 | 
				
			||||||
 | 
					            Files
 | 
				
			||||||
 | 
					          </Badge>
 | 
				
			||||||
 | 
					        ) : (
 | 
				
			||||||
 | 
					          <Badge tt="initial" mb="md">
 | 
				
			||||||
 | 
					            Links
 | 
				
			||||||
 | 
					          </Badge>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {form.values.type ? (
 | 
				
			||||||
 | 
					          <Box>
 | 
				
			||||||
 | 
					            <TextInput
 | 
				
			||||||
 | 
					              label="Title"
 | 
				
			||||||
 | 
					              key={'title'}
 | 
				
			||||||
 | 
					              {...form.getInputProps('title')}
 | 
				
			||||||
 | 
					              maxLength={255}
 | 
				
			||||||
 | 
					              disabled={loadingSubmit}
 | 
				
			||||||
 | 
					              required
 | 
				
			||||||
 | 
					              mb={'md'}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <FileInput
 | 
				
			||||||
 | 
					              accept=".doc,.docx,.xls,.xlsx,.pdf"
 | 
				
			||||||
 | 
					              label="Upload Doc, Excel, PDF file"
 | 
				
			||||||
 | 
					              value={form.values.file}
 | 
				
			||||||
 | 
					              onChange={(file) => form.setFieldValue('file', file || null)}
 | 
				
			||||||
 | 
					              disabled={loadingSubmit}
 | 
				
			||||||
 | 
					              mb={'md'}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {form.values.file ? (
 | 
				
			||||||
 | 
					              ''
 | 
				
			||||||
 | 
					            ) : (
 | 
				
			||||||
 | 
					              <Group
 | 
				
			||||||
 | 
					                w="fit-content"
 | 
				
			||||||
 | 
					                style={{ cursor: 'pointer' }}
 | 
				
			||||||
 | 
					                mb={'md'}
 | 
				
			||||||
 | 
					                title={`File ${form.values.uri
 | 
				
			||||||
 | 
					                  .split('.')
 | 
				
			||||||
 | 
					                  .pop()
 | 
				
			||||||
 | 
					                  ?.toLowerCase()} detail`}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                {getFileTypeIcon(form.values.uri)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <Text>.{form.values.uri.split('.').pop()?.toLowerCase()}</Text>
 | 
				
			||||||
 | 
					              </Group>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </Box>
 | 
				
			||||||
 | 
					        ) : (
 | 
				
			||||||
 | 
					          <Box>
 | 
				
			||||||
 | 
					            <TextInput
 | 
				
			||||||
 | 
					              label="Title"
 | 
				
			||||||
 | 
					              key={'title'}
 | 
				
			||||||
 | 
					              {...form.getInputProps('title')}
 | 
				
			||||||
 | 
					              maxLength={255}
 | 
				
			||||||
 | 
					              disabled={loadingSubmit}
 | 
				
			||||||
 | 
					              required
 | 
				
			||||||
 | 
					              mb={'md'}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <TextInput
 | 
				
			||||||
 | 
					              label="Enter URI"
 | 
				
			||||||
 | 
					              key={'uri'}
 | 
				
			||||||
 | 
					              {...form.getInputProps('uri')}
 | 
				
			||||||
 | 
					              disabled={loadingSubmit}
 | 
				
			||||||
 | 
					              required
 | 
				
			||||||
 | 
					              mb={'md'}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </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}>
 | 
				
			||||||
 | 
					            Update
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					        </Box>
 | 
				
			||||||
 | 
					      </form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {openedModalFile && (
 | 
				
			||||||
 | 
					        <ModalFileDocument
 | 
				
			||||||
 | 
					          opened={openedModalFile}
 | 
				
			||||||
 | 
					          close={closeModalFile}
 | 
				
			||||||
 | 
					          selectDataRow={selectDataFileRow}
 | 
				
			||||||
 | 
					          setSelectDataRow={setSelectDataFileRow}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </Modal>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ModalEditDocument
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,115 @@
 | 
				
			||||||
 | 
					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}${
 | 
				
			||||||
 | 
					            import.meta.env.VITE_BACKEND_URL?.includes('localhost')
 | 
				
			||||||
 | 
					              ? ''
 | 
				
			||||||
 | 
					              : 'image/'
 | 
				
			||||||
 | 
					          }${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 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="staff,accountant">
 | 
				
			||||||
 | 
					        <BasePage
 | 
				
			||||||
 | 
					          main={
 | 
				
			||||||
 | 
					            <>
 | 
				
			||||||
 | 
					              <Document />
 | 
				
			||||||
 | 
					            </>
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ></BasePage>
 | 
				
			||||||
 | 
					      </ProtectedRoute>
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    path: '/timekeeping',
 | 
					    path: '/timekeeping',
 | 
				
			||||||
    element: (
 | 
					    element: (
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue