Create a workflow statistics page from Jira
This commit is contained in:
		
							parent
							
								
									306dd78acb
								
							
						
					
					
						commit
						30cc1c3cac
					
				| 
						 | 
				
			
			@ -0,0 +1,172 @@
 | 
			
		|||
<?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\Http\Response;
 | 
			
		||||
use App\Services\JiraService;
 | 
			
		||||
use GuzzleHttp\Promise\Utils;
 | 
			
		||||
use Maatwebsite\Excel\Facades\Excel;
 | 
			
		||||
use Illuminate\Http\JsonResponse;
 | 
			
		||||
 | 
			
		||||
class JiraController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    use HasOrderByRequest;
 | 
			
		||||
    use HasFilterRequest;
 | 
			
		||||
    use HasSearchRequest;
 | 
			
		||||
 | 
			
		||||
    protected $jiraService;
 | 
			
		||||
 | 
			
		||||
    public function __construct(JiraService $jiraService)
 | 
			
		||||
    {
 | 
			
		||||
        $this->jiraService = $jiraService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function fetchAllIssues($startAt = 0, $maxResults = 50)
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $allIssues = [];
 | 
			
		||||
            $projects = $this->jiraService->getAllProjects();
 | 
			
		||||
            $promises = [];
 | 
			
		||||
 | 
			
		||||
            foreach ($projects as $project) {
 | 
			
		||||
                if ($project['key'] !== 'GCT') {
 | 
			
		||||
                    $promises[] = $this->jiraService->getIssuesAsync($project['key'], $startAt, $maxResults)
 | 
			
		||||
                        ->then(function ($response) use ($project) {
 | 
			
		||||
                            $issues = [];
 | 
			
		||||
                            $data = json_decode($response->getBody()->getContents(), true);
 | 
			
		||||
                            foreach ($data['issues'] as $issue) {
 | 
			
		||||
                                $issues[] = [
 | 
			
		||||
                                    'summary' => $issue['fields']['summary'] ?? null,
 | 
			
		||||
                                    'desc' => $issue['fields']['description']['content'][0] ?? null,
 | 
			
		||||
                                    'assignee' => $issue['fields']['assignee']['displayName'] ?? null,
 | 
			
		||||
                                    'status' => $issue['fields']['status']['name'] ?? null,
 | 
			
		||||
                                    'worklogs' => json_encode(array_map(function ($log) {
 | 
			
		||||
                                        return [
 | 
			
		||||
                                            'author' => $log['author']['displayName'] ?? null,
 | 
			
		||||
                                            'timeSpent' => $log['timeSpent'] ?? null,
 | 
			
		||||
                                            'started' => $log['started'] ?? null,
 | 
			
		||||
                                            'updated' => $log['updated'] ?? null,
 | 
			
		||||
                                        ];
 | 
			
		||||
                                    }, $issue['fields']['worklog']['worklogs'] ?? []), JSON_PRETTY_PRINT),
 | 
			
		||||
                                    'originalEstimate' => isset($issue['fields']['timeoriginalestimate']) ? $issue['fields']['timeoriginalestimate'] / 60 / 60 : null,
 | 
			
		||||
                                    'timeSpent' => isset($issue['fields']['timespent']) ? $issue['fields']['timespent'] / 60 / 60 : null
 | 
			
		||||
                                ];
 | 
			
		||||
                            }
 | 
			
		||||
                            return ['project' => $project['name'], 'issues' => $issues];
 | 
			
		||||
                        });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $results = Utils::settle($promises)->wait();
 | 
			
		||||
 | 
			
		||||
            foreach ($results as $result) {
 | 
			
		||||
                if ($result['state'] === 'fulfilled') {
 | 
			
		||||
                    $allIssues[] = $result['value'];
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Handle the errors if necessary
 | 
			
		||||
                    \Log::error("Error fetching issues: " . $result['reason']->getMessage());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return response()->json($allIssues);
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            return response()->json(['error' => $e->getMessage()], 500);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAllProject()
 | 
			
		||||
    {
 | 
			
		||||
        $projects = $this->jiraService->getAllProjects();
 | 
			
		||||
 | 
			
		||||
        return response()->json([
 | 
			
		||||
            'data' => $projects,
 | 
			
		||||
            'status' => true
 | 
			
		||||
        ], 200);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function fetchIssuesByProject(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $project = ['key'=>$request->key, 'name'=> $request->name];
 | 
			
		||||
        $allIssues = [];
 | 
			
		||||
 | 
			
		||||
        if ($project['key'] !== 'GCT') {
 | 
			
		||||
            $startAt = 0;
 | 
			
		||||
            $issues = [];
 | 
			
		||||
            $total = 0;
 | 
			
		||||
            $issueLength = 0;
 | 
			
		||||
            $checked = true;
 | 
			
		||||
 | 
			
		||||
            // while ($checked) {
 | 
			
		||||
                $response = $this->jiraService->getIssues($project['key'], $startAt);
 | 
			
		||||
                $total = $response['total'];
 | 
			
		||||
                $issueLength = count($response['issues']);
 | 
			
		||||
 | 
			
		||||
                foreach ($response['issues'] as $issue) {
 | 
			
		||||
                    $issues[] = [
 | 
			
		||||
                        'summary' => $issue['fields']['summary'] ?? null,
 | 
			
		||||
                        'desc' => $issue['fields']['description']['content'][0] ?? null,
 | 
			
		||||
                        'assignee' => $issue['fields']['assignee']['displayName'] ?? null,
 | 
			
		||||
                        'status' => $issue['fields']['status']['name'] ?? null,
 | 
			
		||||
                        'worklogs' => json_encode(array_map(function ($log) {
 | 
			
		||||
                            return [
 | 
			
		||||
                                'author' => $log['author']['displayName'] ?? null,
 | 
			
		||||
                                'timeSpent' => $log['timeSpent'] ?? null,
 | 
			
		||||
                                'started' => $log['started'] ?? null,
 | 
			
		||||
                                'updated' => $log['updated'] ?? null,
 | 
			
		||||
                            ];
 | 
			
		||||
                        }, $issue['fields']['worklog']['worklogs'] ?? []), JSON_PRETTY_PRINT),
 | 
			
		||||
                        'originalEstimate' => isset($issue['fields']['timeoriginalestimate']) ? $issue['fields']['timeoriginalestimate'] / 60 / 60 : null,
 | 
			
		||||
                        'timeSpent' => isset($issue['fields']['timespent']) ? $issue['fields']['timespent'] / 60 / 60 : null
 | 
			
		||||
                    ];
 | 
			
		||||
                // }
 | 
			
		||||
 | 
			
		||||
                // if (($startAt + $issueLength >= $total && $total !== 0) || $total === 0) {
 | 
			
		||||
                //     $checked = false;
 | 
			
		||||
                // }
 | 
			
		||||
 | 
			
		||||
                // $startAt += $issueLength;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $allIssues[] = [
 | 
			
		||||
                'project' => $project['name'],
 | 
			
		||||
                'issues' => $issues
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
            return response()->json([
 | 
			
		||||
                'data' => $allIssues,
 | 
			
		||||
                'status' => true
 | 
			
		||||
            ], 200);
 | 
			
		||||
        }
 | 
			
		||||
        return response()->json([
 | 
			
		||||
            'data' => $allIssues,
 | 
			
		||||
            'status' => false
 | 
			
		||||
        ], 500);
 | 
			
		||||
    }
 | 
			
		||||
    public function exportToExcel()
 | 
			
		||||
    {
 | 
			
		||||
        $allIssues = $this->fetchAllIssues()->original;
 | 
			
		||||
        $fileName = 'allIssues.xlsx';
 | 
			
		||||
 | 
			
		||||
        Excel::store(new \App\Exports\IssuesExport($allIssues), $fileName);
 | 
			
		||||
 | 
			
		||||
        return response()->download(storage_path("app/{$fileName}"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAllUserWorkLogs(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $workLogs = $this->jiraService->getAllUserWorkLogs($request->startDate, $request->endDate);
 | 
			
		||||
            return response()->json([
 | 
			
		||||
                'data' => $workLogs,
 | 
			
		||||
                'status' => true
 | 
			
		||||
            ], 200);
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            return response()->json(['error' => $e->getMessage()], 500);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -7,10 +7,7 @@ use App\Traits\HasFilterRequest;
 | 
			
		|||
use App\Traits\HasOrderByRequest;
 | 
			
		||||
use App\Traits\HasSearchRequest;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Http\Response;
 | 
			
		||||
use Modules\Admin\app\Http\Requests\DiscountRequest;
 | 
			
		||||
use Modules\Admin\app\Models\Admin;
 | 
			
		||||
use Modules\Admin\app\Models\Discount;
 | 
			
		||||
use Modules\Admin\app\Models\Tracking;
 | 
			
		||||
 | 
			
		||||
class TrackingController extends Controller
 | 
			
		||||
| 
						 | 
				
			
			@ -148,39 +145,4 @@ class TrackingController extends Controller
 | 
			
		|||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Delete multiple discounts
 | 
			
		||||
    public function deletes(DiscountRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        $discounts = $request->get('discounts');
 | 
			
		||||
        $ids = collect($discounts)->pluck('id');
 | 
			
		||||
        Discount::whereIn('id', $ids)->delete();
 | 
			
		||||
        return response()->json([
 | 
			
		||||
            'data' => $ids,
 | 
			
		||||
            'status' => true
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update multiple discounts
 | 
			
		||||
    public function updates(DiscountRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        $discounts = $request->get('discounts');
 | 
			
		||||
        $ids = collect($discounts)->pluck('id');
 | 
			
		||||
 | 
			
		||||
        foreach ($discounts as $discountRequest) {
 | 
			
		||||
            // convert to object|array to array
 | 
			
		||||
            $discountRequest = collect($discountRequest)->toArray();
 | 
			
		||||
            // handle array
 | 
			
		||||
            $discount = Discount::find($discountRequest['id']);
 | 
			
		||||
            if ($discount) {
 | 
			
		||||
                // exclude id field
 | 
			
		||||
                unset($discount['id']);
 | 
			
		||||
 | 
			
		||||
                $discount->update($discountRequest);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return response()->json([
 | 
			
		||||
            'data' => Discount::whereIn('id', $ids)->get(),
 | 
			
		||||
            'status' => true
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ use Modules\Admin\app\Http\Controllers\CustomThemeController;
 | 
			
		|||
use Modules\Admin\app\Http\Controllers\DashboardController;
 | 
			
		||||
use Modules\Admin\app\Http\Controllers\DiscountController;
 | 
			
		||||
use Modules\Admin\app\Http\Controllers\DiscountTypeController;
 | 
			
		||||
use Modules\Admin\app\Http\Controllers\JiraController;
 | 
			
		||||
use Modules\Admin\app\Http\Controllers\OrderController;
 | 
			
		||||
use Modules\Admin\app\Http\Controllers\PackageController;
 | 
			
		||||
use Modules\Admin\app\Http\Controllers\SNCheckController;
 | 
			
		||||
| 
						 | 
				
			
			@ -142,6 +143,16 @@ Route::middleware('api')
 | 
			
		|||
                Route::get('/statistics-search-sn-by-month', [DashboardController::class, 'statisticSearchSNByMonth']);
 | 
			
		||||
                Route::get('/statistics-revenues-by-month', [DashboardController::class, 'statisticRevenuesByMonth']);
 | 
			
		||||
            });
 | 
			
		||||
            Route::group([
 | 
			
		||||
                'prefix' => 'jira',
 | 
			
		||||
            ], function () {
 | 
			
		||||
                
 | 
			
		||||
                Route::get('/fetch-issues', [JiraController::class, 'fetchAllIssues']);
 | 
			
		||||
                Route::get('/export-issues', [JiraController::class, 'exportToExcel']);
 | 
			
		||||
                Route::get('/all-project', [JiraController::class, 'getAllProject']);
 | 
			
		||||
                Route::get('/all-issue-by-project', [JiraController::class, 'fetchIssuesByProject']);
 | 
			
		||||
                Route::get('/worklogs', [JiraController::class, 'getAllUserWorkLogs']);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -153,3 +164,4 @@ Route::middleware('api')
 | 
			
		|||
        Route::get('/delete', [TrackingController::class, 'delete']);
 | 
			
		||||
        // Route::get('/clear-cache', [SettingController::class, 'clearCache']);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Exports;
 | 
			
		||||
 | 
			
		||||
use Maatwebsite\Excel\Concerns\Exportable;
 | 
			
		||||
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
 | 
			
		||||
class IssuesExport implements WithMultipleSheets {
 | 
			
		||||
    use Exportable;
 | 
			
		||||
 | 
			
		||||
    protected $data;
 | 
			
		||||
 | 
			
		||||
    public function __construct(array $data)
 | 
			
		||||
    {
 | 
			
		||||
        $this->data = $data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function sheets(): array
 | 
			
		||||
    {
 | 
			
		||||
        $sheets = [];
 | 
			
		||||
 | 
			
		||||
        foreach ($this->data as $projectData) {
 | 
			
		||||
            $sheets[] = new ProjectSheet($projectData);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $sheets;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Exports;
 | 
			
		||||
 | 
			
		||||
use Maatwebsite\Excel\Concerns\FromArray;
 | 
			
		||||
use Maatwebsite\Excel\Concerns\WithHeadings;
 | 
			
		||||
use Maatwebsite\Excel\Concerns\WithTitle;
 | 
			
		||||
 | 
			
		||||
class ProjectSheet implements FromArray, WithHeadings, WithTitle
 | 
			
		||||
{
 | 
			
		||||
    protected $projectData;
 | 
			
		||||
 | 
			
		||||
    public function __construct(array $projectData)
 | 
			
		||||
    {
 | 
			
		||||
        $this->projectData = $projectData;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function array(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->projectData['issues'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function headings(): array
 | 
			
		||||
    {
 | 
			
		||||
        return ['summary', 'desc', 'assignee', 'status', 'worklogs', 'originalEstimate', 'timeSpent'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function title(): string
 | 
			
		||||
    {
 | 
			
		||||
        // Return the project name or any other string based on $projectData
 | 
			
		||||
        return $this->projectData['project'];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,203 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Services;
 | 
			
		||||
 | 
			
		||||
use GuzzleHttp\Client;
 | 
			
		||||
use GuzzleHttp\Promise\Utils;
 | 
			
		||||
 | 
			
		||||
class JiraService
 | 
			
		||||
{
 | 
			
		||||
    protected $client;
 | 
			
		||||
    protected $baseUrl;
 | 
			
		||||
    protected $authHeader;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->baseUrl = env('JIRA_BASE_URL');
 | 
			
		||||
        $this->authHeader = 'Basic ' . base64_encode(env('JIRA_USERNAME') . ':' . env('JIRA_API_TOKEN'));
 | 
			
		||||
        $this->client = new Client([
 | 
			
		||||
            'base_uri' => $this->baseUrl,
 | 
			
		||||
            'headers' => [
 | 
			
		||||
                'Authorization' => $this->authHeader,
 | 
			
		||||
                'Accept' => 'application/json',
 | 
			
		||||
                'Content-Type' => 'application/json'
 | 
			
		||||
            ]
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAllProjects()
 | 
			
		||||
    {
 | 
			
		||||
        $response = $this->client->get('/rest/api/3/project');
 | 
			
		||||
        return json_decode($response->getBody()->getContents(), true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getIssuesAsync($projectKey, $startAt = 0, $maxResults = 50)
 | 
			
		||||
    {
 | 
			
		||||
        $body = [
 | 
			
		||||
            'expand' => ['names', 'schema', 'operations'],
 | 
			
		||||
            'fields' => ['summary', 'status', 'description', 'timeoriginalestimate', 'timespent', 'worklog', 'assignee'],
 | 
			
		||||
            'jql' => "project = '{$projectKey}' ORDER BY created DESC",
 | 
			
		||||
            'maxResults' => $maxResults,
 | 
			
		||||
            'startAt' => $startAt
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        return $this->client->postAsync($this->baseUrl . '/rest/api/3/search', [
 | 
			
		||||
            'body' => json_encode($body),
 | 
			
		||||
            'headers' => [
 | 
			
		||||
                'Authorization' => $this->authHeader,
 | 
			
		||||
                'Accept' => 'application/json',
 | 
			
		||||
                'Content-Type' => 'application/json'
 | 
			
		||||
            ]
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    public function getIssues($projectKey, $startAt = 0)
 | 
			
		||||
    {
 | 
			
		||||
        $body = [
 | 
			
		||||
            'expand' => ['names', 'schema', 'operations'],
 | 
			
		||||
            'fields' => ['summary', 'status', 'description', 'timeoriginalestimate', 'timespent', 'worklog', 'assignee'],
 | 
			
		||||
            'jql' => "project = '{$projectKey}' ORDER BY created DESC",
 | 
			
		||||
            'maxResults' => 100,
 | 
			
		||||
            'startAt' => $startAt
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        $response = $this->client->post('/rest/api/3/search', [
 | 
			
		||||
            'body' => json_encode($body)
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        return json_decode($response->getBody()->getContents(), true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAllUsers()
 | 
			
		||||
    {
 | 
			
		||||
        $response = $this->client->get('/rest/api/3/users/search', [
 | 
			
		||||
            'headers' => [
 | 
			
		||||
                'Authorization' => $this->authHeader,
 | 
			
		||||
                'Accept' => 'application/json'
 | 
			
		||||
            ]
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        return json_decode($response->getBody()->getContents(), true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getUserWorkLogs($accountId, $startDate, $endDate)
 | 
			
		||||
    {
 | 
			
		||||
        $body = [
 | 
			
		||||
            'jql' => "worklogAuthor = '{$accountId}'AND worklogDate >= '{$startDate}' AND worklogDate <= '{$endDate}'",
 | 
			
		||||
            'fields' => ['summary', 'status', 'timeoriginalestimate', 'timespent', 'project'],
 | 
			
		||||
            'maxResults' => 50
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        $response = $this->client->post('/rest/api/3/search', [
 | 
			
		||||
            'body' => json_encode($body),
 | 
			
		||||
            'headers' => [
 | 
			
		||||
                'Authorization' => $this->authHeader,
 | 
			
		||||
                'Accept' => 'application/json',
 | 
			
		||||
                'Content-Type' => 'application/json'
 | 
			
		||||
            ]
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $data_response = json_decode($response->getBody()->getContents(), true);
 | 
			
		||||
 | 
			
		||||
        if ($data_response['total'] == 0) {
 | 
			
		||||
            return $data_response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $promises = [];
 | 
			
		||||
        foreach ($data_response['issues'] as $index => $issue) {
 | 
			
		||||
            $issueId = $issue['id'];
 | 
			
		||||
 | 
			
		||||
            // Get the initial worklog data to determine total number of worklogs
 | 
			
		||||
            $promises[$index] = $this->client->getAsync("/rest/api/3/issue/{$issueId}/worklog", [
 | 
			
		||||
                'query' => [
 | 
			
		||||
                    'startAt' => 0,
 | 
			
		||||
                    'maxResults' => 1
 | 
			
		||||
                ]
 | 
			
		||||
            ])->then(function ($checkApiResponse) use ($issueId, $index) {
 | 
			
		||||
                $checkApi = json_decode($checkApiResponse->getBody()->getContents(), true);
 | 
			
		||||
                $maxResults = 50;
 | 
			
		||||
                $totalWorklogs = $checkApi['total'];
 | 
			
		||||
                return $this->client->getAsync("/rest/api/3/issue/{$issueId}/worklog", [
 | 
			
		||||
                    'query' => [
 | 
			
		||||
                        'startAt' => $totalWorklogs - $maxResults,
 | 
			
		||||
                        'maxResults' => $totalWorklogs
 | 
			
		||||
                    ]
 | 
			
		||||
                ])->then(function ($worklogResponse) use ($index) {
 | 
			
		||||
                    return [
 | 
			
		||||
                        'index' => $index,
 | 
			
		||||
                        'worklogs' => json_decode($worklogResponse->getBody()->getContents(), true)
 | 
			
		||||
                    ];
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Wait for all promises to complete
 | 
			
		||||
        $results = Utils::settle($promises)->wait();
 | 
			
		||||
 | 
			
		||||
        // Attach worklogs to issues
 | 
			
		||||
        foreach ($results as $result) {
 | 
			
		||||
            if ($result['state'] === 'fulfilled') {
 | 
			
		||||
                $data_response['issues'][$result['value']['index']]["fields"]['worklog'] = $result['value']['worklogs'];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $data_response;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // public function getUserWorkLogs($accountId, $startDate, $endDate)
 | 
			
		||||
    // {
 | 
			
		||||
    //     $body = [
 | 
			
		||||
    //         'jql' => "worklogAuthor = '{$accountId}'AND worklogDate >= '{$startDate}' AND worklogDate <= '{$endDate}'",
 | 
			
		||||
    //         'fields' => ['summary', 'status', 'timeoriginalestimate', 'timespent', 'project'],
 | 
			
		||||
    //         'maxResults' => 50
 | 
			
		||||
    //     ];
 | 
			
		||||
 | 
			
		||||
    //     $response = $this->client->post('/rest/api/3/search', [
 | 
			
		||||
    //         'body' => json_encode($body),
 | 
			
		||||
    //         'headers' => [
 | 
			
		||||
    //             'Authorization' => $this->authHeader,
 | 
			
		||||
    //             'Accept' => 'application/json',
 | 
			
		||||
    //             'Content-Type' => 'application/json'
 | 
			
		||||
    //         ]
 | 
			
		||||
    //     ]);
 | 
			
		||||
 | 
			
		||||
    //     $data_response = json_decode($response->getBody()->getContents(), true);
 | 
			
		||||
    //     // $allRespones = [];
 | 
			
		||||
    //     if ($data_response['total'] != 0) {
 | 
			
		||||
    //         foreach ($data_response['issues']  as $index => $issue) {
 | 
			
		||||
    //             $maxResults = 10;
 | 
			
		||||
    //             $check_api = $this->client->get("/rest/api/3/issue/{$issue['id']}/worklog", [
 | 
			
		||||
    //                 'query' => [
 | 
			
		||||
    //                     'startAt' => 0,
 | 
			
		||||
    //                     'maxResults' => 1
 | 
			
		||||
    //                 ]
 | 
			
		||||
    //             ]);
 | 
			
		||||
 | 
			
		||||
    //             $check_api = json_decode($check_api->getBody()->getContents(), true);
 | 
			
		||||
 | 
			
		||||
    //                 $response = $this->client->get("/rest/api/3/issue/{$issue['id']}/worklog", [
 | 
			
		||||
    //                     'query' => [
 | 
			
		||||
    //                         'startAt' => $check_api['total'] - $maxResults,
 | 
			
		||||
    //                         'maxResults' => $check_api['total']
 | 
			
		||||
    //                     ]
 | 
			
		||||
    //                 ]);
 | 
			
		||||
    //             $data_response['issues'][$index]["fields"]['worklogs'] = json_decode($response->getBody()->getContents(), true);
 | 
			
		||||
    //         }
 | 
			
		||||
    //     }
 | 
			
		||||
    //     return $data_response;
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    public function getAllUserWorkLogs($startDate, $endDate)
 | 
			
		||||
    {
 | 
			
		||||
        $users = $this->getAllUsers();
 | 
			
		||||
        $workLogs = [];
 | 
			
		||||
 | 
			
		||||
        foreach ($users as $user) {
 | 
			
		||||
            $userWorkLogs = $this->getUserWorkLogs($user['accountId'], $startDate, $endDate);
 | 
			
		||||
            $workLogs[] = ['username' => $user['displayName'], 'information' => $userWorkLogs];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $workLogs;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +13,7 @@
 | 
			
		|||
        "laravel/sanctum": "^3.2",
 | 
			
		||||
        "laravel/tinker": "^2.8",
 | 
			
		||||
        "laravel/ui": "^4.3",
 | 
			
		||||
        "maatwebsite/excel": "^3.1",
 | 
			
		||||
        "nwidart/laravel-modules": "^10.0",
 | 
			
		||||
        "pion/laravel-chunk-upload": "^1.5",
 | 
			
		||||
        "predis/predis": "^2.2",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
 | 
			
		||||
        "This file is @generated automatically"
 | 
			
		||||
    ],
 | 
			
		||||
    "content-hash": "879d4b76d94550aedcc3fa47d3db8f96",
 | 
			
		||||
    "content-hash": "4d9f50111be5d1e2be1581ccf00970b6",
 | 
			
		||||
    "packages": [
 | 
			
		||||
        {
 | 
			
		||||
            "name": "barryvdh/laravel-debugbar",
 | 
			
		||||
| 
						 | 
				
			
			@ -219,6 +219,87 @@
 | 
			
		|||
            ],
 | 
			
		||||
            "time": "2023-12-11T17:09:12+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "composer/semver",
 | 
			
		||||
            "version": "3.4.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/composer/semver.git",
 | 
			
		||||
                "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32",
 | 
			
		||||
                "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "php": "^5.3.2 || ^7.0 || ^8.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "phpstan/phpstan": "^1.4",
 | 
			
		||||
                "symfony/phpunit-bridge": "^4.2 || ^5"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "extra": {
 | 
			
		||||
                "branch-alias": {
 | 
			
		||||
                    "dev-main": "3.x-dev"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "psr-4": {
 | 
			
		||||
                    "Composer\\Semver\\": "src"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "notification-url": "https://packagist.org/downloads/",
 | 
			
		||||
            "license": [
 | 
			
		||||
                "MIT"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Nils Adermann",
 | 
			
		||||
                    "email": "naderman@naderman.de",
 | 
			
		||||
                    "homepage": "http://www.naderman.de"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Jordi Boggiano",
 | 
			
		||||
                    "email": "j.boggiano@seld.be",
 | 
			
		||||
                    "homepage": "http://seld.be"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Rob Bast",
 | 
			
		||||
                    "email": "rob.bast@gmail.com",
 | 
			
		||||
                    "homepage": "http://robbast.nl"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "Semver library that offers utilities, version constraint parsing and validation.",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "semantic",
 | 
			
		||||
                "semver",
 | 
			
		||||
                "validation",
 | 
			
		||||
                "versioning"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "irc": "ircs://irc.libera.chat:6697/composer",
 | 
			
		||||
                "issues": "https://github.com/composer/semver/issues",
 | 
			
		||||
                "source": "https://github.com/composer/semver/tree/3.4.0"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
                    "url": "https://packagist.com",
 | 
			
		||||
                    "type": "custom"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "url": "https://github.com/composer",
 | 
			
		||||
                    "type": "github"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "url": "https://tidelift.com/funding/github/packagist/composer/composer",
 | 
			
		||||
                    "type": "tidelift"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2023-08-31T09:50:34+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "dflydev/dot-access-data",
 | 
			
		||||
            "version": "v3.0.2",
 | 
			
		||||
| 
						 | 
				
			
			@ -642,6 +723,67 @@
 | 
			
		|||
            ],
 | 
			
		||||
            "time": "2023-10-06T06:47:41+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "ezyang/htmlpurifier",
 | 
			
		||||
            "version": "v4.17.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/ezyang/htmlpurifier.git",
 | 
			
		||||
                "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c",
 | 
			
		||||
                "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "cerdic/css-tidy": "^1.7 || ^2.0",
 | 
			
		||||
                "simpletest/simpletest": "dev-master"
 | 
			
		||||
            },
 | 
			
		||||
            "suggest": {
 | 
			
		||||
                "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
 | 
			
		||||
                "ext-bcmath": "Used for unit conversion and imagecrash protection",
 | 
			
		||||
                "ext-iconv": "Converts text to and from non-UTF-8 encodings",
 | 
			
		||||
                "ext-tidy": "Used for pretty-printing HTML"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "files": [
 | 
			
		||||
                    "library/HTMLPurifier.composer.php"
 | 
			
		||||
                ],
 | 
			
		||||
                "psr-0": {
 | 
			
		||||
                    "HTMLPurifier": "library/"
 | 
			
		||||
                },
 | 
			
		||||
                "exclude-from-classmap": [
 | 
			
		||||
                    "/library/HTMLPurifier/Language/"
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            "notification-url": "https://packagist.org/downloads/",
 | 
			
		||||
            "license": [
 | 
			
		||||
                "LGPL-2.1-or-later"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Edward Z. Yang",
 | 
			
		||||
                    "email": "admin@htmlpurifier.org",
 | 
			
		||||
                    "homepage": "http://ezyang.com"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "Standards compliant HTML filter written in PHP",
 | 
			
		||||
            "homepage": "http://htmlpurifier.org/",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "html"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/ezyang/htmlpurifier/issues",
 | 
			
		||||
                "source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2023-11-17T15:01:25+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "fruitcake/php-cors",
 | 
			
		||||
            "version": "v1.3.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -2237,6 +2379,275 @@
 | 
			
		|||
            ],
 | 
			
		||||
            "time": "2024-01-28T23:22:08+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "maatwebsite/excel",
 | 
			
		||||
            "version": "3.1.55",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/SpartnerNL/Laravel-Excel.git",
 | 
			
		||||
                "reference": "6d9d791dcdb01a9b6fd6f48d46f0d5fff86e6260"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/6d9d791dcdb01a9b6fd6f48d46f0d5fff86e6260",
 | 
			
		||||
                "reference": "6d9d791dcdb01a9b6fd6f48d46f0d5fff86e6260",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "composer/semver": "^3.3",
 | 
			
		||||
                "ext-json": "*",
 | 
			
		||||
                "illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0",
 | 
			
		||||
                "php": "^7.0||^8.0",
 | 
			
		||||
                "phpoffice/phpspreadsheet": "^1.18",
 | 
			
		||||
                "psr/simple-cache": "^1.0||^2.0||^3.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "laravel/scout": "^7.0||^8.0||^9.0||^10.0",
 | 
			
		||||
                "orchestra/testbench": "^6.0||^7.0||^8.0||^9.0",
 | 
			
		||||
                "predis/predis": "^1.1"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "extra": {
 | 
			
		||||
                "laravel": {
 | 
			
		||||
                    "providers": [
 | 
			
		||||
                        "Maatwebsite\\Excel\\ExcelServiceProvider"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "aliases": {
 | 
			
		||||
                        "Excel": "Maatwebsite\\Excel\\Facades\\Excel"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "psr-4": {
 | 
			
		||||
                    "Maatwebsite\\Excel\\": "src/"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "notification-url": "https://packagist.org/downloads/",
 | 
			
		||||
            "license": [
 | 
			
		||||
                "MIT"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Patrick Brouwers",
 | 
			
		||||
                    "email": "patrick@spartner.nl"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "Supercharged Excel exports and imports in Laravel",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "PHPExcel",
 | 
			
		||||
                "batch",
 | 
			
		||||
                "csv",
 | 
			
		||||
                "excel",
 | 
			
		||||
                "export",
 | 
			
		||||
                "import",
 | 
			
		||||
                "laravel",
 | 
			
		||||
                "php",
 | 
			
		||||
                "phpspreadsheet"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
 | 
			
		||||
                "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.55"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
                    "url": "https://laravel-excel.com/commercial-support",
 | 
			
		||||
                    "type": "custom"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "url": "https://github.com/patrickbrouwers",
 | 
			
		||||
                    "type": "github"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2024-02-20T08:27:10+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "maennchen/zipstream-php",
 | 
			
		||||
            "version": "3.1.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/maennchen/ZipStream-PHP.git",
 | 
			
		||||
                "reference": "b8174494eda667f7d13876b4a7bfef0f62a7c0d1"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/b8174494eda667f7d13876b4a7bfef0f62a7c0d1",
 | 
			
		||||
                "reference": "b8174494eda667f7d13876b4a7bfef0f62a7c0d1",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "ext-mbstring": "*",
 | 
			
		||||
                "ext-zlib": "*",
 | 
			
		||||
                "php-64bit": "^8.1"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "ext-zip": "*",
 | 
			
		||||
                "friendsofphp/php-cs-fixer": "^3.16",
 | 
			
		||||
                "guzzlehttp/guzzle": "^7.5",
 | 
			
		||||
                "mikey179/vfsstream": "^1.6",
 | 
			
		||||
                "php-coveralls/php-coveralls": "^2.5",
 | 
			
		||||
                "phpunit/phpunit": "^10.0",
 | 
			
		||||
                "vimeo/psalm": "^5.0"
 | 
			
		||||
            },
 | 
			
		||||
            "suggest": {
 | 
			
		||||
                "guzzlehttp/psr7": "^2.4",
 | 
			
		||||
                "psr/http-message": "^2.0"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "psr-4": {
 | 
			
		||||
                    "ZipStream\\": "src/"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "notification-url": "https://packagist.org/downloads/",
 | 
			
		||||
            "license": [
 | 
			
		||||
                "MIT"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Paul Duncan",
 | 
			
		||||
                    "email": "pabs@pablotron.org"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Jonatan Männchen",
 | 
			
		||||
                    "email": "jonatan@maennchen.ch"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Jesse Donat",
 | 
			
		||||
                    "email": "donatj@gmail.com"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "András Kolesár",
 | 
			
		||||
                    "email": "kolesar@kolesar.hu"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "stream",
 | 
			
		||||
                "zip"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/maennchen/ZipStream-PHP/issues",
 | 
			
		||||
                "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.0"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
                    "url": "https://github.com/maennchen",
 | 
			
		||||
                    "type": "github"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "url": "https://opencollective.com/zipstream",
 | 
			
		||||
                    "type": "open_collective"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2023-06-21T14:59:35+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "markbaker/complex",
 | 
			
		||||
            "version": "3.0.2",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/MarkBaker/PHPComplex.git",
 | 
			
		||||
                "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
 | 
			
		||||
                "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "php": "^7.2 || ^8.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
 | 
			
		||||
                "phpcompatibility/php-compatibility": "^9.3",
 | 
			
		||||
                "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
 | 
			
		||||
                "squizlabs/php_codesniffer": "^3.7"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "psr-4": {
 | 
			
		||||
                    "Complex\\": "classes/src/"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "notification-url": "https://packagist.org/downloads/",
 | 
			
		||||
            "license": [
 | 
			
		||||
                "MIT"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Mark Baker",
 | 
			
		||||
                    "email": "mark@lange.demon.co.uk"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "PHP Class for working with complex numbers",
 | 
			
		||||
            "homepage": "https://github.com/MarkBaker/PHPComplex",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "complex",
 | 
			
		||||
                "mathematics"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/MarkBaker/PHPComplex/issues",
 | 
			
		||||
                "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2022-12-06T16:21:08+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "markbaker/matrix",
 | 
			
		||||
            "version": "3.0.1",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/MarkBaker/PHPMatrix.git",
 | 
			
		||||
                "reference": "728434227fe21be27ff6d86621a1b13107a2562c"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
 | 
			
		||||
                "reference": "728434227fe21be27ff6d86621a1b13107a2562c",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "php": "^7.1 || ^8.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
 | 
			
		||||
                "phpcompatibility/php-compatibility": "^9.3",
 | 
			
		||||
                "phpdocumentor/phpdocumentor": "2.*",
 | 
			
		||||
                "phploc/phploc": "^4.0",
 | 
			
		||||
                "phpmd/phpmd": "2.*",
 | 
			
		||||
                "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
 | 
			
		||||
                "sebastian/phpcpd": "^4.0",
 | 
			
		||||
                "squizlabs/php_codesniffer": "^3.7"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "psr-4": {
 | 
			
		||||
                    "Matrix\\": "classes/src/"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "notification-url": "https://packagist.org/downloads/",
 | 
			
		||||
            "license": [
 | 
			
		||||
                "MIT"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Mark Baker",
 | 
			
		||||
                    "email": "mark@demon-angel.eu"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "PHP Class for working with matrices",
 | 
			
		||||
            "homepage": "https://github.com/MarkBaker/PHPMatrix",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "mathematics",
 | 
			
		||||
                "matrix",
 | 
			
		||||
                "vector"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/MarkBaker/PHPMatrix/issues",
 | 
			
		||||
                "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2022-12-02T22:17:43+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "maximebf/debugbar",
 | 
			
		||||
            "version": "v1.22.3",
 | 
			
		||||
| 
						 | 
				
			
			@ -2890,6 +3301,111 @@
 | 
			
		|||
            ],
 | 
			
		||||
            "time": "2024-01-28T10:04:15+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "phpoffice/phpspreadsheet",
 | 
			
		||||
            "version": "1.29.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
 | 
			
		||||
                "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0",
 | 
			
		||||
                "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "ext-ctype": "*",
 | 
			
		||||
                "ext-dom": "*",
 | 
			
		||||
                "ext-fileinfo": "*",
 | 
			
		||||
                "ext-gd": "*",
 | 
			
		||||
                "ext-iconv": "*",
 | 
			
		||||
                "ext-libxml": "*",
 | 
			
		||||
                "ext-mbstring": "*",
 | 
			
		||||
                "ext-simplexml": "*",
 | 
			
		||||
                "ext-xml": "*",
 | 
			
		||||
                "ext-xmlreader": "*",
 | 
			
		||||
                "ext-xmlwriter": "*",
 | 
			
		||||
                "ext-zip": "*",
 | 
			
		||||
                "ext-zlib": "*",
 | 
			
		||||
                "ezyang/htmlpurifier": "^4.15",
 | 
			
		||||
                "maennchen/zipstream-php": "^2.1 || ^3.0",
 | 
			
		||||
                "markbaker/complex": "^3.0",
 | 
			
		||||
                "markbaker/matrix": "^3.0",
 | 
			
		||||
                "php": "^7.4 || ^8.0",
 | 
			
		||||
                "psr/http-client": "^1.0",
 | 
			
		||||
                "psr/http-factory": "^1.0",
 | 
			
		||||
                "psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "dealerdirect/phpcodesniffer-composer-installer": "dev-main",
 | 
			
		||||
                "dompdf/dompdf": "^1.0 || ^2.0",
 | 
			
		||||
                "friendsofphp/php-cs-fixer": "^3.2",
 | 
			
		||||
                "mitoteam/jpgraph": "^10.3",
 | 
			
		||||
                "mpdf/mpdf": "^8.1.1",
 | 
			
		||||
                "phpcompatibility/php-compatibility": "^9.3",
 | 
			
		||||
                "phpstan/phpstan": "^1.1",
 | 
			
		||||
                "phpstan/phpstan-phpunit": "^1.0",
 | 
			
		||||
                "phpunit/phpunit": "^8.5 || ^9.0 || ^10.0",
 | 
			
		||||
                "squizlabs/php_codesniffer": "^3.7",
 | 
			
		||||
                "tecnickcom/tcpdf": "^6.5"
 | 
			
		||||
            },
 | 
			
		||||
            "suggest": {
 | 
			
		||||
                "dompdf/dompdf": "Option for rendering PDF with PDF Writer",
 | 
			
		||||
                "ext-intl": "PHP Internationalization Functions",
 | 
			
		||||
                "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
 | 
			
		||||
                "mpdf/mpdf": "Option for rendering PDF with PDF Writer",
 | 
			
		||||
                "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "psr-4": {
 | 
			
		||||
                    "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "notification-url": "https://packagist.org/downloads/",
 | 
			
		||||
            "license": [
 | 
			
		||||
                "MIT"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Maarten Balliauw",
 | 
			
		||||
                    "homepage": "https://blog.maartenballiauw.be"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Mark Baker",
 | 
			
		||||
                    "homepage": "https://markbakeruk.net"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Franck Lefevre",
 | 
			
		||||
                    "homepage": "https://rootslabs.net"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Erik Tilt"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Adrien Crivelli"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
 | 
			
		||||
            "homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "OpenXML",
 | 
			
		||||
                "excel",
 | 
			
		||||
                "gnumeric",
 | 
			
		||||
                "ods",
 | 
			
		||||
                "php",
 | 
			
		||||
                "spreadsheet",
 | 
			
		||||
                "xls",
 | 
			
		||||
                "xlsx"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
 | 
			
		||||
                "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2023-06-14T22:48:31+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "phpoption/phpoption",
 | 
			
		||||
            "version": "1.9.2",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -183,8 +183,8 @@
 | 
			
		|||
        <span class="four"><span class="screen-reader-text">4</span></span>
 | 
			
		||||
    </section>
 | 
			
		||||
    <div class="link-container">
 | 
			
		||||
        <a href="{{route('home')}}"
 | 
			
		||||
            class="more-link">Back to home page</a>
 | 
			
		||||
        <!-- <a href="{{route('home')}}"
 | 
			
		||||
            class="more-link">Back to home page</a> -->
 | 
			
		||||
    </div>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,3 +54,10 @@ export const statisticRevenuesByMonth = API_URL + 'v1/admin/dashboard/statistics
 | 
			
		|||
// Tracking
 | 
			
		||||
export const getListTracking = API_URL + 'v1/admin/tracking'
 | 
			
		||||
export const deleteTracking = API_URL + 'v1/admin/tracking/delete'
 | 
			
		||||
 | 
			
		||||
// // Tracking
 | 
			
		||||
export const fetchAllIssues = API_URL + 'v1/admin/jira/fetch-issues'
 | 
			
		||||
export const exportIssues = API_URL + 'v1/admin/jira/export-issues'
 | 
			
		||||
export const getAllProjects = API_URL + 'v1/admin/jira/all-project'
 | 
			
		||||
export const getAllIssuesByProject = API_URL + 'v1/admin/jira/all-issue-by-project'
 | 
			
		||||
export const getAllUserWorklogs = API_URL + 'v1/admin/jira/worklogs'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,8 +25,10 @@ import {
 | 
			
		|||
  // IconMail,
 | 
			
		||||
  IconMoon,
 | 
			
		||||
  IconPasswordUser,
 | 
			
		||||
  IconReport,
 | 
			
		||||
  IconScan,
 | 
			
		||||
  IconSettings,
 | 
			
		||||
  IconSubtask,
 | 
			
		||||
  IconSun
 | 
			
		||||
} from '@tabler/icons-react'
 | 
			
		||||
import { useCallback, useEffect, useState } from 'react'
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +39,8 @@ import classes from './NavbarSimpleColored.module.css'
 | 
			
		|||
const data = [
 | 
			
		||||
  // { link: '/dashboard', label: 'Dashboard', icon: IconHome },
 | 
			
		||||
  { link: '/tracking', label: 'Check in/out', icon: IconScan },
 | 
			
		||||
  { link: '/worklogs', label: 'Worklogs', icon: IconReport },
 | 
			
		||||
  { link: '/jira', label: 'Jira', icon: IconSubtask },
 | 
			
		||||
  { link: '/custom-theme', label: 'Custom Theme', icon: IconBrush },
 | 
			
		||||
  { link: '/general-setting', label: 'General Setting', icon: IconSettings },
 | 
			
		||||
  // { link: '/packages', label: 'Packages', icon: IconPackages },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
.root {
 | 
			
		||||
    --link-height: rem(38px);
 | 
			
		||||
    --indicator-size: rem(10px);
 | 
			
		||||
    --indicator-offset: calc((var(--link-height) - var(--indicator-size)) / 2);
 | 
			
		||||
    position: relative;
 | 
			
		||||
    /* padding-left: 20px; */
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .link {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    color: var(--mantine-color-text);
 | 
			
		||||
    line-height: var(--link-height);
 | 
			
		||||
    font-size: var(--mantine-font-size-sm);
 | 
			
		||||
    height: var(--link-height);
 | 
			
		||||
    border-top-right-radius: var(--mantine-radius-sm);
 | 
			
		||||
    border-bottom-right-radius: var(--mantine-radius-sm);
 | 
			
		||||
    border-left: rem(2px) solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-4));
 | 
			
		||||
  
 | 
			
		||||
    @mixin hover {
 | 
			
		||||
      background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .linkActive {
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    color: light-dark(var(--mantine-color-blue-7), var(--mantine-color-blue-4));
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .indicator {
 | 
			
		||||
    transition: transform 150ms ease;
 | 
			
		||||
    border: rem(2px) solid light-dark(var(--mantine-color-blue-7), var(--mantine-color-blue-4));
 | 
			
		||||
    background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-7));
 | 
			
		||||
    height: var(--indicator-size);
 | 
			
		||||
    width: var(--indicator-size);
 | 
			
		||||
    border-radius: var(--indicator-size);
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: calc(var(--indicator-size) / -2 + rem(1));
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
import { Avatar, Box } from '@mantine/core';
 | 
			
		||||
import cx from 'clsx';
 | 
			
		||||
import { useState } from 'react';
 | 
			
		||||
import classes from './TableOfContentsFloating.module.css';
 | 
			
		||||
 | 
			
		||||
type TLink = {
 | 
			
		||||
    label: string
 | 
			
		||||
    link: string
 | 
			
		||||
    order: number
 | 
			
		||||
    avartar: string
 | 
			
		||||
    key: string
 | 
			
		||||
}
 | 
			
		||||
export function TableOfContentsFloating({links, defaultActive, setCurrentProject, }
 | 
			
		||||
  :{links:TLink[],defaultActive:number, setCurrentProject:(name:string)=>void}) {
 | 
			
		||||
  const [active, setActive] = useState(defaultActive);
 | 
			
		||||
 | 
			
		||||
  const items = links.map((item, index) => (
 | 
			
		||||
    <Box<'a'>
 | 
			
		||||
      component="a"
 | 
			
		||||
      href={item.link}
 | 
			
		||||
      onClick={(event) => {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        setCurrentProject(item.label)
 | 
			
		||||
        setActive(index);
 | 
			
		||||
      }}
 | 
			
		||||
      key={item.label}
 | 
			
		||||
      className={cx(classes.link, { [classes.linkActive]: active === index })}
 | 
			
		||||
      style={{ paddingLeft: `calc(${item.order} * var(--mantine-spacing-md))`}}
 | 
			
		||||
    >
 | 
			
		||||
      <Avatar src={item.avartar} size={'xs'} mr={5}/>{item.label}
 | 
			
		||||
    </Box>
 | 
			
		||||
  ));
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={classes.root}>
 | 
			
		||||
      <div className={classes.links}>
 | 
			
		||||
        <div
 | 
			
		||||
          className={classes.indicator}
 | 
			
		||||
          style={{
 | 
			
		||||
            transform: `translateY(calc(${active} * var(--link-height) + var(--indicator-offset)))`
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        {items}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
.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;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,155 @@
 | 
			
		|||
import { getAllIssuesByProject, getAllProjects } from '@/api/Admin'
 | 
			
		||||
import { TableOfContentsFloating } from '@/components/TableOfContentsFloating/TableOfContentsFloating'
 | 
			
		||||
import { get } from '@/rtk/helpers/apiService'
 | 
			
		||||
import { Box, Loader, Text } from '@mantine/core'
 | 
			
		||||
import { useEffect, useState } from 'react'
 | 
			
		||||
import classes from './Jira.module.css'
 | 
			
		||||
type TProject = {
 | 
			
		||||
  id: string
 | 
			
		||||
  name: string
 | 
			
		||||
  key: string
 | 
			
		||||
  avatarUrls: {
 | 
			
		||||
    '48x48': string
 | 
			
		||||
    '24x24': string
 | 
			
		||||
    '16x16': string
 | 
			
		||||
    '32x32': string
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TIssue = {
 | 
			
		||||
  summary: string
 | 
			
		||||
  desc: string | null
 | 
			
		||||
  assignee: string | null
 | 
			
		||||
  status: string
 | 
			
		||||
  worklogs: any[]
 | 
			
		||||
  originalEstimate: number | null
 | 
			
		||||
  timeSpent: number | null
 | 
			
		||||
}
 | 
			
		||||
const Jira = () => {
 | 
			
		||||
  const [loader, setLoader] = useState(true)
 | 
			
		||||
  const [data, setData] = useState([])
 | 
			
		||||
  const [issuesInProject, setIssuesInProject] = useState<TIssue[]>([])
 | 
			
		||||
  const [listProject, setListProject] = useState<TProject[]>([])
 | 
			
		||||
  const [listStatus, setListStatus] = useState<string[]>([])
 | 
			
		||||
  const [currentProject, setCurrentProject] = useState<string>('Summary')
 | 
			
		||||
 | 
			
		||||
  const getUniqueStatuses = (issues: TIssue[]) => {
 | 
			
		||||
    const statuses = issues.map((issue) => issue.status)
 | 
			
		||||
    const uniqueStatuses = [...new Set(statuses)]
 | 
			
		||||
    return uniqueStatuses
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const getAllIssuesInProject = async (key: string, name: string) => {
 | 
			
		||||
    try {
 | 
			
		||||
      if (name !== 'Summary') {
 | 
			
		||||
        const res = await get(getAllIssuesByProject, { key: key, name: name })
 | 
			
		||||
        if (res.status) {
 | 
			
		||||
          var statusArray:string[] = getUniqueStatuses(res.data[0].issues)
 | 
			
		||||
          setListStatus(statusArray)
 | 
			
		||||
          setIssuesInProject(res.data[0].issues)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.log(error)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const getAllProject = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      const res = await get(getAllProjects)
 | 
			
		||||
      if (res.status) {
 | 
			
		||||
        var list: TProject[] = [
 | 
			
		||||
          {
 | 
			
		||||
            id: '',
 | 
			
		||||
            name: 'Summary',
 | 
			
		||||
            key: '',
 | 
			
		||||
            avatarUrls: {
 | 
			
		||||
              '48x48': '',
 | 
			
		||||
              '24x24': '',
 | 
			
		||||
              '16x16': '',
 | 
			
		||||
              '32x32': '',
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        ]
 | 
			
		||||
        setListProject(list.concat(res.data))
 | 
			
		||||
        setLoader(false)
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.log(error)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  console.log(listStatus)
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    getAllProject()
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    getAllIssuesInProject(
 | 
			
		||||
      listProject.find((p) => p.name === currentProject)?.key!,
 | 
			
		||||
      currentProject,
 | 
			
		||||
    )
 | 
			
		||||
  }, [currentProject])
 | 
			
		||||
  return loader ? (
 | 
			
		||||
    <Box ta={'center'}>
 | 
			
		||||
      <Loader size={40} mt={'15%'} />
 | 
			
		||||
    </Box>
 | 
			
		||||
  ) : (
 | 
			
		||||
    <div>
 | 
			
		||||
      <div className={classes.title}>
 | 
			
		||||
        <h3>
 | 
			
		||||
          <Text>Admin/</Text>Jira
 | 
			
		||||
        </h3>
 | 
			
		||||
      </div>
 | 
			
		||||
      <Box display={'flex'}>
 | 
			
		||||
        <Box w={'20%'} p={'md'}>
 | 
			
		||||
          <Text fw={700} mb={'md'}>
 | 
			
		||||
            Projects
 | 
			
		||||
          </Text>
 | 
			
		||||
          <Box
 | 
			
		||||
            h={'70vh'}
 | 
			
		||||
            style={{
 | 
			
		||||
              display: 'flex',
 | 
			
		||||
              flexFlow: 'column',
 | 
			
		||||
              maxHeight: '70vh',
 | 
			
		||||
              overflow: 'auto',
 | 
			
		||||
              paddingLeft: '20px',
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <TableOfContentsFloating
 | 
			
		||||
              links={listProject.map((p) => {
 | 
			
		||||
                return {
 | 
			
		||||
                  label: p.name,
 | 
			
		||||
                  link: '#',
 | 
			
		||||
                  order: 1,
 | 
			
		||||
                  avartar: p.avatarUrls['16x16'],
 | 
			
		||||
                  key: p.key,
 | 
			
		||||
                }
 | 
			
		||||
              })}
 | 
			
		||||
              setCurrentProject={setCurrentProject}
 | 
			
		||||
              defaultActive={0}
 | 
			
		||||
            />
 | 
			
		||||
          </Box>
 | 
			
		||||
        </Box>
 | 
			
		||||
        <Box w={'80%'} h={'100vh'} display={"flex"} style={{overflowX:"auto"}}>
 | 
			
		||||
          {
 | 
			
		||||
            listStatus?.map((sta)=>(
 | 
			
		||||
                <Box p={'sm'} style={{border:"solid 1px gray"}}>
 | 
			
		||||
                    <Text ta={'center'} style={{border:"solid 1px gray", borderRadius:"8px"}} mb={"md"} fw={600}>{sta}</Text>
 | 
			
		||||
                    <Box style={{maxHeight:"90vh", overflowX:"hidden"}}>
 | 
			
		||||
                        {
 | 
			
		||||
                             issuesInProject.filter((iss)=>iss.status === sta)?.map((iss) => (
 | 
			
		||||
                                <Text w={"200px"}  fz={15} style={{border:"solid 1px gray", padding:"4px", marginBottom:"5px"}}>{iss.summary}</Text>
 | 
			
		||||
                              ))
 | 
			
		||||
                        }
 | 
			
		||||
                    </Box>
 | 
			
		||||
                </Box>
 | 
			
		||||
            ))
 | 
			
		||||
          }
 | 
			
		||||
        </Box>
 | 
			
		||||
      </Box>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Jira
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
.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;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,373 @@
 | 
			
		|||
import { Avatar, Box, Button, Loader, Text } from '@mantine/core'
 | 
			
		||||
import React, { useEffect, useState } from 'react'
 | 
			
		||||
import classes from './Worklogs.module.css'
 | 
			
		||||
import { get } from '@/rtk/helpers/apiService'
 | 
			
		||||
import { getAllUserWorklogs } from '@/api/Admin'
 | 
			
		||||
import moment from 'moment'
 | 
			
		||||
import { DateInput, DatePicker } from '@mantine/dates'
 | 
			
		||||
 | 
			
		||||
interface WorkLog {
 | 
			
		||||
  self: string
 | 
			
		||||
  author: UserInfo
 | 
			
		||||
  updateAuthor: UserInfo
 | 
			
		||||
  created: string
 | 
			
		||||
  updated: string
 | 
			
		||||
  started: string
 | 
			
		||||
  timeSpent: string
 | 
			
		||||
  timeSpentSeconds: number
 | 
			
		||||
  id: string
 | 
			
		||||
  issueId: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface UserInfo {
 | 
			
		||||
  self: string
 | 
			
		||||
  accountId: string
 | 
			
		||||
  avatarUrls: AvatarUrls
 | 
			
		||||
  displayName: string
 | 
			
		||||
  active: boolean
 | 
			
		||||
  timeZone: string
 | 
			
		||||
  accountType: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface AvatarUrls {
 | 
			
		||||
  '48x48': string
 | 
			
		||||
  '24x24': string
 | 
			
		||||
  '16x16': string
 | 
			
		||||
  '32x32': string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Issue {
 | 
			
		||||
  expand: string
 | 
			
		||||
  id: string
 | 
			
		||||
  self: string
 | 
			
		||||
  key: string
 | 
			
		||||
  fields: IssueFields
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IssueFields {
 | 
			
		||||
  summary: string
 | 
			
		||||
  timespent: number
 | 
			
		||||
  timeoriginalestimate: number
 | 
			
		||||
  project: ProjectInfo
 | 
			
		||||
  worklog: WorkLogList
 | 
			
		||||
  status: StatusInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ProjectInfo {
 | 
			
		||||
  self: string
 | 
			
		||||
  id: string
 | 
			
		||||
  key: string
 | 
			
		||||
  name: string
 | 
			
		||||
  projectTypeKey: string
 | 
			
		||||
  simplified: boolean
 | 
			
		||||
  avatarUrls: AvatarUrls
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface WorkLogList {
 | 
			
		||||
  startAt: number
 | 
			
		||||
  maxResults: number
 | 
			
		||||
  total: number
 | 
			
		||||
  worklogs: WorkLog[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface StatusInfo {
 | 
			
		||||
  self: string
 | 
			
		||||
  description: string
 | 
			
		||||
  iconUrl: string
 | 
			
		||||
  name: string
 | 
			
		||||
  id: string
 | 
			
		||||
  statusCategory: StatusCategory
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface StatusCategory {
 | 
			
		||||
  self: string
 | 
			
		||||
  id: number
 | 
			
		||||
  key: string
 | 
			
		||||
  colorName: string
 | 
			
		||||
  name: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Information {
 | 
			
		||||
  startAt: number
 | 
			
		||||
  maxResults: number
 | 
			
		||||
  total: number
 | 
			
		||||
  issues: Issue[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface UserWorklog {
 | 
			
		||||
  username: string
 | 
			
		||||
  information: Information
 | 
			
		||||
}
 | 
			
		||||
const Worklogs = () => {
 | 
			
		||||
  const [loader, setLoader] = useState(true)
 | 
			
		||||
  const [updating, setUpdating] = useState(true)
 | 
			
		||||
  const [worklogs, setWorklogs] = useState<UserWorklog[]>([])
 | 
			
		||||
  const [date, setDate] = useState({
 | 
			
		||||
    startDate: moment(Date.now()).format('YYYY-MM-DD'),
 | 
			
		||||
    endDate: moment(Date.now()).format('YYYY-MM-DD'),
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const getAllWorklogs = async (
 | 
			
		||||
    startDate = date.startDate,
 | 
			
		||||
    endDate = date.endDate,
 | 
			
		||||
  ) => {
 | 
			
		||||
    try {
 | 
			
		||||
      setUpdating(false)
 | 
			
		||||
      const res = await get(getAllUserWorklogs, {
 | 
			
		||||
        startDate: startDate,
 | 
			
		||||
        endDate: endDate,
 | 
			
		||||
      })
 | 
			
		||||
      if (res.status) {
 | 
			
		||||
        const data = res.data.filter(
 | 
			
		||||
          (user: UserWorklog) => user.information.issues.length > 0,
 | 
			
		||||
        )
 | 
			
		||||
        setWorklogs(data)
 | 
			
		||||
        localStorage.setItem(
 | 
			
		||||
          'data',
 | 
			
		||||
          JSON.stringify({ time: Date.now(), data: data, date: date }),
 | 
			
		||||
        )
 | 
			
		||||
        setLoader(false)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      setUpdating(true)
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.log(error)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const localData = localStorage.getItem('data')
 | 
			
		||||
    if (localData !== null) {
 | 
			
		||||
      setWorklogs(JSON.parse(localData!).data)
 | 
			
		||||
      setDate(JSON.parse(localData!).date)
 | 
			
		||||
      setLoader(false)
 | 
			
		||||
      if (Date.now() - JSON.parse(localData!).time > 300000) {
 | 
			
		||||
        getAllWorklogs()
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      getAllWorklogs()
 | 
			
		||||
    }
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  return loader ? (
 | 
			
		||||
    <Box ta={'center'}>
 | 
			
		||||
      <Loader size={40} mt={'15%'} />
 | 
			
		||||
    </Box>
 | 
			
		||||
  ) : (
 | 
			
		||||
    <div>
 | 
			
		||||
      <div className={classes.title}>
 | 
			
		||||
        <h3>
 | 
			
		||||
          <Text>Admin/</Text>Worklogs
 | 
			
		||||
          {!updating ? (
 | 
			
		||||
            <Text fs={'italic'} fz={'xs'} c={'gray'}>
 | 
			
		||||
              Updating data in the background ...
 | 
			
		||||
            </Text>
 | 
			
		||||
          ) : (
 | 
			
		||||
            ''
 | 
			
		||||
          )}
 | 
			
		||||
        </h3>
 | 
			
		||||
      </div>
 | 
			
		||||
      <Box
 | 
			
		||||
        display={'flex'}
 | 
			
		||||
        w={'30%'}
 | 
			
		||||
        style={{
 | 
			
		||||
          float: 'right',
 | 
			
		||||
          margin: '10px',
 | 
			
		||||
          alignItems: 'end',
 | 
			
		||||
          justifyContent: 'space-between',
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <DateInput
 | 
			
		||||
          size="xs"
 | 
			
		||||
          label="From date:"
 | 
			
		||||
          value={new Date(date.startDate)}
 | 
			
		||||
          w={'40%'}
 | 
			
		||||
          clearable
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            setDate({ ...date, startDate: moment(e).format('YYYY-MM-DD') })
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <DateInput
 | 
			
		||||
          size="xs"
 | 
			
		||||
          label="To date:"
 | 
			
		||||
          value={new Date(date.endDate)}
 | 
			
		||||
          clearable
 | 
			
		||||
          w={'40%'}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            setDate({ ...date, endDate: moment(e).format('YYYY-MM-DD') })
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <Button
 | 
			
		||||
          size="xs"
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            getAllWorklogs()
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          Search
 | 
			
		||||
        </Button>
 | 
			
		||||
      </Box>
 | 
			
		||||
      <Box
 | 
			
		||||
        w={'100%'}
 | 
			
		||||
        h={'85vh'}
 | 
			
		||||
        display={'flex'}
 | 
			
		||||
        style={{ overflowX: 'auto', flexFlow: 'column' }}
 | 
			
		||||
      >
 | 
			
		||||
        {worklogs?.map((user, index) => (
 | 
			
		||||
          <Box
 | 
			
		||||
            p={'sm'}
 | 
			
		||||
            style={{
 | 
			
		||||
              border: 'solid 1px gray',
 | 
			
		||||
              borderColor: '#afafaf',
 | 
			
		||||
              marginBottom: '10px',
 | 
			
		||||
              backgroundColor:
 | 
			
		||||
                index % 2 === 0 ? 'rgb(201 201 201 / 28%)' : 'white',
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <Text ta={'left'} mb={'xs'} fw={800}>
 | 
			
		||||
              {user.username} 
 | 
			
		||||
              ({user?.information.issues.reduce(
 | 
			
		||||
                (total: number, issue: Issue) => {
 | 
			
		||||
                  var totalSpent = issue.fields.worklog.worklogs?.reduce(
 | 
			
		||||
                    (accumulator: number, currentValue: WorkLog) => {
 | 
			
		||||
                      if (
 | 
			
		||||
                        parseInt(moment(date.startDate).format('YYYYMMDD')) <=
 | 
			
		||||
                          parseInt(
 | 
			
		||||
                            moment(currentValue.started).format('YYYYMMDD'),
 | 
			
		||||
                          ) &&
 | 
			
		||||
                        parseInt(
 | 
			
		||||
                          moment(currentValue.started).format('YYYYMMDD'),
 | 
			
		||||
                        ) <= parseInt(moment(date.endDate).format('YYYYMMDD')) && currentValue.updateAuthor.displayName === user.username
 | 
			
		||||
                      ) {
 | 
			
		||||
                        return accumulator + currentValue.timeSpentSeconds
 | 
			
		||||
                      }
 | 
			
		||||
                      return accumulator
 | 
			
		||||
                    },
 | 
			
		||||
                    0,
 | 
			
		||||
                  )
 | 
			
		||||
                  return total + totalSpent
 | 
			
		||||
                },
 | 
			
		||||
                0,
 | 
			
		||||
              ) /
 | 
			
		||||
                60 /
 | 
			
		||||
                60}
 | 
			
		||||
              h)
 | 
			
		||||
            </Text>
 | 
			
		||||
            {user.information.issues.map((iss) => {
 | 
			
		||||
              if (
 | 
			
		||||
                iss.fields.worklog.worklogs.filter(
 | 
			
		||||
                  (w) =>
 | 
			
		||||
                    parseInt(moment(date.startDate).format('YYYYMMDD')) <=
 | 
			
		||||
                      parseInt(moment(w.started).format('YYYYMMDD')) &&
 | 
			
		||||
                    parseInt(moment(date.endDate).format('YYYYMMDD')) >=
 | 
			
		||||
                      parseInt(moment(w.started).format('YYYYMMDD')) && w.updateAuthor.displayName === user.username
 | 
			
		||||
                ).length > 0
 | 
			
		||||
              ) {
 | 
			
		||||
                return (
 | 
			
		||||
                  <Box
 | 
			
		||||
                    style={{
 | 
			
		||||
                      border: 'solid 1px gray',
 | 
			
		||||
                      padding: '10px',
 | 
			
		||||
                      marginBottom: '5px',
 | 
			
		||||
                      maxHeight: '90vh',
 | 
			
		||||
                      overflowX: 'hidden',
 | 
			
		||||
                      color: '#412d2d',
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <Box>
 | 
			
		||||
                      <Text
 | 
			
		||||
                        fz={14}
 | 
			
		||||
                        display={'flex'}
 | 
			
		||||
                        style={{ alignItems: 'center' }}
 | 
			
		||||
                      >
 | 
			
		||||
                        <b>Summary:</b>{' '}
 | 
			
		||||
                        <Avatar
 | 
			
		||||
                          src={iss.fields.project.avatarUrls['16x16']}
 | 
			
		||||
                          size={'xs'}
 | 
			
		||||
                          m={'0 5px'}
 | 
			
		||||
                        />
 | 
			
		||||
                        {iss.fields.project.name} -{' '}
 | 
			
		||||
                        <a
 | 
			
		||||
                          href={`https://apactechvn.atlassian.net/browse/${iss.key}`}
 | 
			
		||||
                          target="_blank"
 | 
			
		||||
                        >
 | 
			
		||||
                          {iss.fields.summary}
 | 
			
		||||
                        </a>
 | 
			
		||||
                      </Text>
 | 
			
		||||
                      <Text fz={14}>
 | 
			
		||||
                        <b>Estimate:</b>{' '}
 | 
			
		||||
                        {iss.fields.timeoriginalestimate / 60 / 60}h
 | 
			
		||||
                      </Text>
 | 
			
		||||
                      <Text fz={14}>
 | 
			
		||||
                        <b>Total time spent:</b>{' '}
 | 
			
		||||
                        {iss.fields.timespent / 60 / 60}h
 | 
			
		||||
                      </Text>
 | 
			
		||||
                      <Text fz={14}>
 | 
			
		||||
                        <b>Time spent/day:</b>{' '}
 | 
			
		||||
                        {iss.fields.worklog.worklogs?.reduce(
 | 
			
		||||
                          (accumulator: number, currentValue: WorkLog) => {
 | 
			
		||||
                            if (
 | 
			
		||||
                              (parseInt(
 | 
			
		||||
                                moment(date.startDate).format('YYYYMMDD'),
 | 
			
		||||
                              ) <=
 | 
			
		||||
                                parseInt(
 | 
			
		||||
                                  moment(currentValue.started).format(
 | 
			
		||||
                                    'YYYYMMDD',
 | 
			
		||||
                                  ),
 | 
			
		||||
                                )) &&
 | 
			
		||||
                              (parseInt(
 | 
			
		||||
                                moment(currentValue.started).format('YYYYMMDD'),
 | 
			
		||||
                              ) <=
 | 
			
		||||
                                parseInt(
 | 
			
		||||
                                  moment(date.endDate).format('YYYYMMDD')
 | 
			
		||||
                                )) && currentValue.updateAuthor.displayName === user.username
 | 
			
		||||
                            ) {
 | 
			
		||||
                              return accumulator + currentValue.timeSpentSeconds
 | 
			
		||||
                            }
 | 
			
		||||
                            return accumulator
 | 
			
		||||
                          },
 | 
			
		||||
                          0,
 | 
			
		||||
                        ) /
 | 
			
		||||
                          60 /
 | 
			
		||||
                          60}
 | 
			
		||||
                        h
 | 
			
		||||
                      </Text>
 | 
			
		||||
                    </Box>
 | 
			
		||||
                    {iss.fields.worklog.worklogs?.map((log) => {
 | 
			
		||||
                      if (
 | 
			
		||||
                        moment(date.startDate).format('YYYYMMDD') <=
 | 
			
		||||
                          moment(log.started).format('YYYYMMDD') &&
 | 
			
		||||
                        moment(log.started).format('YYYYMMDD') <=
 | 
			
		||||
                          moment(date.endDate).format('YYYYMMDD') && log.updateAuthor.displayName === user.username
 | 
			
		||||
                      ) {
 | 
			
		||||
                        return (
 | 
			
		||||
                          <Box
 | 
			
		||||
                            style={{
 | 
			
		||||
                              padding: '4px 8px',
 | 
			
		||||
                              marginBottom: '5px',
 | 
			
		||||
                              marginLeft: '10px',
 | 
			
		||||
                              backgroundColor: '#d9d9d9',
 | 
			
		||||
                            }}
 | 
			
		||||
                          >
 | 
			
		||||
                            <Text fz={13}>
 | 
			
		||||
                              <b>Start date:</b>{' '}
 | 
			
		||||
                              {moment(log.started).format('HH:mm YYYY/MM/DD')}
 | 
			
		||||
                            </Text>
 | 
			
		||||
                            <Text fz={13}>
 | 
			
		||||
                              <b>Time spent:</b> {log.timeSpent}
 | 
			
		||||
                            </Text>
 | 
			
		||||
                          </Box>
 | 
			
		||||
                        )
 | 
			
		||||
                      }
 | 
			
		||||
                    })}
 | 
			
		||||
                  </Box>
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
            })}
 | 
			
		||||
          </Box>
 | 
			
		||||
        ))}
 | 
			
		||||
      </Box>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Worklogs
 | 
			
		||||
| 
						 | 
				
			
			@ -6,9 +6,11 @@ import PageLogin from '@/pages/Auth/Login/Login'
 | 
			
		|||
import CustomTheme from '@/pages/CustomTheme/CustomTheme'
 | 
			
		||||
import Dashboard from '@/pages/Dashboard/Dashboard'
 | 
			
		||||
import GeneralSetting from '@/pages/GeneralSetting/GeneralSetting'
 | 
			
		||||
import Jira from '@/pages/Jira/Jira'
 | 
			
		||||
import PageNotFound from '@/pages/NotFound/NotFound'
 | 
			
		||||
import Tracking from '@/pages/Tracking/Tracking'
 | 
			
		||||
import PageWelcome from '@/pages/Welcome/Welcome'
 | 
			
		||||
import Worklogs from '@/pages/Worklogs/Worklogs'
 | 
			
		||||
import { Navigate } from 'react-router-dom'
 | 
			
		||||
 | 
			
		||||
const mainRoutes = [
 | 
			
		||||
| 
						 | 
				
			
			@ -85,6 +87,34 @@ const mainRoutes = [
 | 
			
		|||
      </ProtectedRoute>
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/jira',
 | 
			
		||||
    element: (
 | 
			
		||||
      <ProtectedRoute mode="route">
 | 
			
		||||
        <BasePage
 | 
			
		||||
          main={
 | 
			
		||||
            <>
 | 
			
		||||
              <Jira />
 | 
			
		||||
            </>
 | 
			
		||||
          }
 | 
			
		||||
        ></BasePage>
 | 
			
		||||
      </ProtectedRoute>
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/worklogs',
 | 
			
		||||
    element: (
 | 
			
		||||
      <ProtectedRoute mode="route">
 | 
			
		||||
        <BasePage
 | 
			
		||||
          main={
 | 
			
		||||
            <>
 | 
			
		||||
              <Worklogs />
 | 
			
		||||
            </>
 | 
			
		||||
          }
 | 
			
		||||
        ></BasePage>
 | 
			
		||||
      </ProtectedRoute>
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
  // {
 | 
			
		||||
  //   path: '/packages',
 | 
			
		||||
  //   element: (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue