Thực hiện chức năng ngày phép
This commit is contained in:
parent
2cc8472bc6
commit
2fdd687fc9
|
|
@ -0,0 +1,125 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Admin\app\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Jobs\InitializeLeaveDays;
|
||||||
|
use App\Models\LeaveDays;
|
||||||
|
use App\Models\Notes;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class LeaveManagementController extends Controller
|
||||||
|
{
|
||||||
|
public function get(Request $request)
|
||||||
|
{
|
||||||
|
$yearNow = $request->query('year', now()->year);
|
||||||
|
$year = $request->year ?? $yearNow;
|
||||||
|
|
||||||
|
$leaveDays = self::getDataByYear($year);
|
||||||
|
if ($leaveDays->count() == 0) {
|
||||||
|
InitializeLeaveDays::dispatch($year);
|
||||||
|
$leaveDays = self::getDataByYear($year);
|
||||||
|
}
|
||||||
|
return AbstractController::ResultSuccess($leaveDays);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDataByYear($year)
|
||||||
|
{
|
||||||
|
$totalLeaveDaysByMonth = Notes::join('categories', function ($join) {
|
||||||
|
$join->on('notes.n_time_type', '=', 'categories.c_code')
|
||||||
|
->where('categories.c_type', 'TIME_TYPE');
|
||||||
|
})
|
||||||
|
->select(
|
||||||
|
DB::raw('notes.n_user_id as n_user_id'),
|
||||||
|
DB::raw('notes.n_year as year'),
|
||||||
|
DB::raw('notes.n_month as month'),
|
||||||
|
DB::raw('SUM(categories.c_value) as leave_days')
|
||||||
|
)
|
||||||
|
->where('notes.n_year', $year)
|
||||||
|
->where('notes.n_reason', 'ONLEAVE')
|
||||||
|
->groupBy("notes.n_user_id", DB::raw('notes.n_month'), DB::raw('notes.n_year'))
|
||||||
|
->get()
|
||||||
|
->map(function ($item) {
|
||||||
|
return [
|
||||||
|
"n_user_id" => $item->n_user_id,
|
||||||
|
"month" => $item->month,
|
||||||
|
"leave_days" => $item->leave_days
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
$leaveDays = LeaveDays::join('users', 'leave_days.ld_user_id', '=', 'users.id')
|
||||||
|
->select(
|
||||||
|
'leave_days.*',
|
||||||
|
'users.id as user_id',
|
||||||
|
'users.name as user_name',
|
||||||
|
'users.email',
|
||||||
|
'users.created_at as user_created_at',
|
||||||
|
'users.permission',
|
||||||
|
'users.updated_at as user_updated_at',
|
||||||
|
'users.remember_token',
|
||||||
|
'users.email_verified_at'
|
||||||
|
)
|
||||||
|
->where('leave_days.ld_year', $year)
|
||||||
|
->get()
|
||||||
|
->map(function ($item) use ($totalLeaveDaysByMonth) {
|
||||||
|
|
||||||
|
$monthlyLeaveDays = [];
|
||||||
|
foreach ($totalLeaveDaysByMonth as $key => $totalDays) {
|
||||||
|
if ($item->ld_user_id == $totalDays["n_user_id"]) {
|
||||||
|
$monthlyLeaveDays[] = $totalDays;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'user' => [
|
||||||
|
'id' => $item->user_id,
|
||||||
|
'name' => $item->user_name,
|
||||||
|
'email' => $item->email,
|
||||||
|
'created_at' => $item->user_created_at,
|
||||||
|
'permission' => $item->permission,
|
||||||
|
'updated_at' => $item->user_updated_at,
|
||||||
|
'remember_token' => $item->remember_token,
|
||||||
|
'email_verified_at' => $item->email_verified_at,
|
||||||
|
],
|
||||||
|
'leaveDay' => [
|
||||||
|
'id' => $item->id,
|
||||||
|
'ld_user_id' => $item->ld_user_id,
|
||||||
|
'ld_day' => $item->ld_day,
|
||||||
|
'ld_year' => $item->ld_year,
|
||||||
|
'ld_date_additional' => $item->ld_date_additional,
|
||||||
|
'ld_note' => $item->ld_note,
|
||||||
|
'created_at' => $item->created_at,
|
||||||
|
'updated_at' => $item->updated_at,
|
||||||
|
],
|
||||||
|
'monthlyLeaveDays' => $monthlyLeaveDays,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
return $leaveDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveNoteLeave(Request $request)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'totalLeave' => 'required|numeric|min:0|max:20',
|
||||||
|
// 'dayAdditional' => 'required|numeric|min:0|max:20',
|
||||||
|
// 'note' => 'required|string|max:255',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return AbstractController::ResultError($validator->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$validatedData = $request->all();
|
||||||
|
$leaveDays = LeaveDays::find($validatedData['id']);
|
||||||
|
|
||||||
|
$leaveDays->ld_day = $validatedData['totalLeave'];
|
||||||
|
$leaveDays->ld_date_additional = $validatedData['dayAdditional']; // Assuming you have this field to store additional days
|
||||||
|
$leaveDays->ld_note = $validatedData['note'];
|
||||||
|
|
||||||
|
$leaveDays->save();
|
||||||
|
|
||||||
|
return response()->json(['status' => true, 'message' => 'Updated successfully']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ 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\JiraController;
|
use Modules\Admin\app\Http\Controllers\JiraController;
|
||||||
|
use Modules\Admin\app\Http\Controllers\LeaveManagementController;
|
||||||
use Modules\Admin\app\Http\Controllers\SettingController;
|
use Modules\Admin\app\Http\Controllers\SettingController;
|
||||||
use Modules\Admin\app\Http\Controllers\TimekeepingController;
|
use Modules\Admin\app\Http\Controllers\TimekeepingController;
|
||||||
use Modules\Admin\app\Http\Controllers\TrackingController;
|
use Modules\Admin\app\Http\Controllers\TrackingController;
|
||||||
|
|
@ -126,6 +127,14 @@ Route::middleware('api')
|
||||||
Route::get('/get-list-master', [CategoryController::class, 'getListMaster']);
|
Route::get('/get-list-master', [CategoryController::class, 'getListMaster']);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::group([
|
||||||
|
'prefix' => 'leave-management',
|
||||||
|
], function () {
|
||||||
|
Route::get('/', [LeaveManagementController::class, 'get'])->middleware('check.permission:admin.hr.staff');
|
||||||
|
Route::post('/saveNoteLeave', [LeaveManagementController::class, 'saveNoteLeave'])->middleware('check.permission:admin.hr');
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\Jobs\DeductLeaveDays;
|
||||||
|
|
||||||
|
class DeductLeaveDaysCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'leave:deduct {year?}';
|
||||||
|
protected $description = 'Trừ đi số ngày nghỉ thêm vào năm hiện tại';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$year = $this->argument('year');
|
||||||
|
DeductLeaveDays::dispatch($year);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\Jobs\InitializeLeaveDays;
|
||||||
|
|
||||||
|
class InitializeLeaveDaysCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'initialize:leavedays {year?}';
|
||||||
|
protected $description = 'Initialize leave days for users';
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$year = $this->argument('year');
|
||||||
|
InitializeLeaveDays::dispatch($year);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,20 +2,30 @@
|
||||||
|
|
||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
|
use App\Jobs\DeductLeaveDays;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
class Kernel extends ConsoleKernel
|
class Kernel extends ConsoleKernel
|
||||||
{
|
{
|
||||||
|
protected $commands = [
|
||||||
|
\App\Console\Commands\InitializeLeaveDaysCommand::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define the application's command schedule.
|
* Define the application's command schedule.
|
||||||
*/
|
*/
|
||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
$schedule->command('clean_logs')->daily();
|
$schedule->command('clean_logs')->daily();
|
||||||
// Chạy command 'daily:api-call' vào mỗi ngày lúc 9h sáng
|
// Chạy command 'daily:api-call' vào mỗi ngày lúc 9h sáng
|
||||||
// $schedule->command('daily:api-call')
|
// $schedule->command('daily:api-call')
|
||||||
// ->dailyAt('18:00');
|
// ->dailyAt('18:00');
|
||||||
|
|
||||||
|
// Chạy command vào ngày 31/12 lúc 23:59:59 mỗi năm
|
||||||
|
$schedule->command('initialize:leavedays')->yearlyOn(12, 31, '23:59:59');
|
||||||
|
$schedule->command('leave:deduct')->yearlyOn(3, 31, '23:59:59');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -23,7 +33,7 @@ class Kernel extends ConsoleKernel
|
||||||
*/
|
*/
|
||||||
protected function commands(): void
|
protected function commands(): void
|
||||||
{
|
{
|
||||||
$this->load(__DIR__.'/Commands');
|
$this->load(__DIR__ . '/Commands');
|
||||||
|
|
||||||
require base_path('routes/console.php');
|
require base_path('routes/console.php');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\LeaveDays;
|
||||||
|
use App\Models\Notes;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class DeductLeaveDays implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
protected $year;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($year = null)
|
||||||
|
{
|
||||||
|
// Nếu không có năm được truyền vào, sử dụng năm hiện tại
|
||||||
|
$this->year = $year ?? Carbon::now()->year;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$users = User::get();
|
||||||
|
foreach ($users as $user) {
|
||||||
|
$existingData = LeaveDays::where('ld_user_id', $user->id)
|
||||||
|
->where('ld_year', $this->year)
|
||||||
|
->where('ld_date_additional', ">", 0)
|
||||||
|
->first();
|
||||||
|
if (!$existingData) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalLeaveDaysByMonth = Notes::join('categories', function ($join) {
|
||||||
|
$join->on('notes.n_time_type', '=', 'categories.c_code')
|
||||||
|
->where('categories.c_type', 'TIME_TYPE');
|
||||||
|
})
|
||||||
|
->select(
|
||||||
|
DB::raw('notes.n_user_id as n_user_id'),
|
||||||
|
DB::raw('notes.n_year as year'),
|
||||||
|
DB::raw('SUM(categories.c_value) as leave_days')
|
||||||
|
)
|
||||||
|
->where('notes.n_year', $this->year)
|
||||||
|
->where('notes.n_user_id', $user->id)
|
||||||
|
->where('notes.n_reason', 'ONLEAVE')
|
||||||
|
->groupBy(DB::raw('notes.n_year'))
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($totalLeaveDaysByMonth) {
|
||||||
|
//Nếu ngày phép thừa năm trước chưa sử dụng hết => cập nhật lại ngày đó
|
||||||
|
if ($existingData->ld_date_additional > $totalLeaveDaysByMonth->leave_days) {
|
||||||
|
LeaveDays::where('ld_year', $this->year)
|
||||||
|
->where('ld_user_id', $user->id)
|
||||||
|
->update([
|
||||||
|
'ld_date_additional' => $totalLeaveDaysByMonth->leave_days,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LeaveDays::where('ld_year', $this->year)
|
||||||
|
->where('ld_user_id', $user->id)
|
||||||
|
->update([
|
||||||
|
'ld_date_additional' => "0",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\LeaveDays;
|
||||||
|
use App\Models\Notes;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class InitializeLeaveDays implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $year;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($year = null)
|
||||||
|
{
|
||||||
|
// Nếu không có năm được truyền vào, sử dụng năm hiện tại
|
||||||
|
$this->year = $year ?? Carbon::now()->year;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$users = User::get();
|
||||||
|
$ld_day = 12;
|
||||||
|
foreach ($users as $user) {
|
||||||
|
// Kiểm tra xem dữ liệu của user này đã tồn tại cho năm hiện tại chưa
|
||||||
|
$existingData = LeaveDays::where('ld_user_id', $user->id)
|
||||||
|
->where('ld_year', $this->year)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existingData) {
|
||||||
|
// Nếu dữ liệu đã tồn tại, bỏ qua user này
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểm tra dữ liệu của user này trong năm trước
|
||||||
|
$previousYearData = LeaveDays::where('ld_user_id', $user->id)
|
||||||
|
->where('ld_year', $this->year - 1)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$ld_date_additional = 0;
|
||||||
|
$ld_note = '';
|
||||||
|
|
||||||
|
if ($previousYearData) {
|
||||||
|
$ld_date_additional = $previousYearData->ld_day;
|
||||||
|
$totalLeaveDaysByMonth = Notes::join('categories', function ($join) {
|
||||||
|
$join->on('notes.n_time_type', '=', 'categories.c_code')
|
||||||
|
->where('categories.c_type', 'TIME_TYPE');
|
||||||
|
})
|
||||||
|
->select(
|
||||||
|
DB::raw('notes.n_user_id as n_user_id'),
|
||||||
|
DB::raw('notes.n_year as year'),
|
||||||
|
DB::raw('SUM(categories.c_value) as leave_days')
|
||||||
|
)
|
||||||
|
->where('notes.n_year', $this->year - 1)
|
||||||
|
->where('notes.n_user_id', $user->id)
|
||||||
|
->where('notes.n_reason', 'ONLEAVE')
|
||||||
|
->groupBy(DB::raw('notes.n_year'))
|
||||||
|
->first();
|
||||||
|
if ($totalLeaveDaysByMonth) {
|
||||||
|
$ld_date_additional = $ld_date_additional - $totalLeaveDaysByMonth->leave_days;
|
||||||
|
if ($ld_date_additional < 0) {
|
||||||
|
$ld_date_additional = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$ld_note = 'Cộng dồn ngày phép năm cũ';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tạo dữ liệu cho năm hiện tại
|
||||||
|
LeaveDays::insert([
|
||||||
|
'ld_user_id' => $user->id,
|
||||||
|
'ld_day' => $ld_day,
|
||||||
|
'ld_year' => $this->year,
|
||||||
|
'ld_date_additional' => $ld_date_additional,
|
||||||
|
'ld_note' => $ld_note,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class LeaveDays extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'id', 'ld_user_id', 'ld_day', 'ld_year', 'ld_date_additional', 'ld_note'
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $table = 'leave_days';
|
||||||
|
}
|
||||||
|
|
@ -19,9 +19,6 @@ return new class extends Migration
|
||||||
$table->float('ld_date_additional')->default(0);
|
$table->float('ld_date_additional')->default(0);
|
||||||
$table->text('ld_note')->nullable();
|
$table->text('ld_note')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
||||||
// Nếu cần khóa ngoại, uncomment dòng dưới và điều chỉnh tên bảng và cột phù hợp
|
|
||||||
// $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,4 +71,8 @@ export const updateNote = API_URL + 'v1/admin/timekeeping/addNote'
|
||||||
export const updateWorkingDays = API_URL + 'v1/admin/timekeeping/update-working-days'
|
export const updateWorkingDays = API_URL + 'v1/admin/timekeeping/update-working-days'
|
||||||
|
|
||||||
//Category
|
//Category
|
||||||
export const getListMaster = API_URL + 'v1/admin/category/get-list-master'
|
export const getListMaster = API_URL + 'v1/admin/category/get-list-master'
|
||||||
|
|
||||||
|
//LeaveManagement
|
||||||
|
export const getLeaveManagement = API_URL + 'v1/admin/leave-management'
|
||||||
|
export const updateNoteLeave = API_URL + 'v1/admin/leave-management/saveNoteLeave'
|
||||||
|
|
@ -13,7 +13,7 @@ import {
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
useComputedColorScheme,
|
useComputedColorScheme,
|
||||||
useMantineColorScheme
|
useMantineColorScheme,
|
||||||
} from '@mantine/core'
|
} from '@mantine/core'
|
||||||
import { notifications } from '@mantine/notifications'
|
import { notifications } from '@mantine/notifications'
|
||||||
import {
|
import {
|
||||||
|
|
@ -27,7 +27,8 @@ import {
|
||||||
IconReport,
|
IconReport,
|
||||||
IconScan,
|
IconScan,
|
||||||
IconSubtask,
|
IconSubtask,
|
||||||
IconSun
|
IconSun,
|
||||||
|
IconCalendarClock,
|
||||||
} 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'
|
||||||
|
|
@ -39,6 +40,11 @@ const data = [
|
||||||
{ link: '/timekeeping', label: 'Timekeeping', icon: IconCalendar },
|
{ link: '/timekeeping', label: 'Timekeeping', icon: IconCalendar },
|
||||||
{ link: '/tracking', label: 'Check in/out', icon: IconScan },
|
{ link: '/tracking', label: 'Check in/out', icon: IconScan },
|
||||||
{ link: '/worklogs', label: 'Worklogs', icon: IconReport },
|
{ link: '/worklogs', label: 'Worklogs', icon: IconReport },
|
||||||
|
{
|
||||||
|
link: '/leave-management',
|
||||||
|
label: 'Leave Management',
|
||||||
|
icon: IconCalendarClock,
|
||||||
|
},
|
||||||
// { link: '/jira', label: 'Jira', icon: IconSubtask },
|
// { link: '/jira', label: 'Jira', icon: IconSubtask },
|
||||||
// { link: '/custom-theme', label: 'Custom Theme', icon: IconBrush },
|
// { link: '/custom-theme', label: 'Custom Theme', icon: IconBrush },
|
||||||
// { link: '/general-setting', label: 'General Setting', icon: IconSettings },
|
// { link: '/general-setting', label: 'General Setting', icon: IconSettings },
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
.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%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteIcon:hover {
|
||||||
|
background-color: rgba(203, 203, 203, 0.809);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editIcon {
|
||||||
|
color: rgb(9, 132, 132);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editIcon:hover {
|
||||||
|
background-color: rgba(203, 203, 203, 0.809);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
background-color: light-dark(white, #2d353c);
|
||||||
|
text-align: center;
|
||||||
|
border: solid 1px rgb(255, 145, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialogText {
|
||||||
|
color: light-dark(#2d353c, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableTr:hover {
|
||||||
|
background-color: rgb(205, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popoverFooter {
|
||||||
|
margin-top: 5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.historyRow td {
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,465 @@
|
||||||
|
import { getLeaveManagement, updateNoteLeave } from '@/api/Admin'
|
||||||
|
import { update } from '@/rtk/helpers/CRUD'
|
||||||
|
import { get } from '@/rtk/helpers/apiService'
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Drawer,
|
||||||
|
Menu,
|
||||||
|
Select,
|
||||||
|
Table,
|
||||||
|
Text,
|
||||||
|
Textarea,
|
||||||
|
TextInput,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core'
|
||||||
|
import { useDisclosure } from '@mantine/hooks'
|
||||||
|
import { notifications } from '@mantine/notifications'
|
||||||
|
import moment from 'moment'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import { IconEdit } from '@tabler/icons-react'
|
||||||
|
|
||||||
|
import classes from './LeaveManagement.module.css'
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
email_verified_at: string | null
|
||||||
|
permission: string
|
||||||
|
remember_token: string | null
|
||||||
|
created_at: string | null
|
||||||
|
updated_at: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LeaveDay {
|
||||||
|
id: number
|
||||||
|
ld_user_id: number
|
||||||
|
ld_year: number
|
||||||
|
ld_day: number
|
||||||
|
ld_date_additional: number
|
||||||
|
ld_note: string
|
||||||
|
created_at: string | null
|
||||||
|
updated_at: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MonthlyLeaveDays {
|
||||||
|
month: number
|
||||||
|
leave_days: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserData {
|
||||||
|
user: User
|
||||||
|
leaveDay: LeaveDay
|
||||||
|
monthlyLeaveDays: MonthlyLeaveDays[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const LeaveManagement = () => {
|
||||||
|
const [opened1, { open: open1, close: close1 }] = useDisclosure(false)
|
||||||
|
const [disableBtn, setDisableBtn] = useState(false)
|
||||||
|
const monthInYear = getMonthNames()
|
||||||
|
const [customAddNotes, setCustomAddNotes] = useState<{
|
||||||
|
id: number
|
||||||
|
user: {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
note: string
|
||||||
|
totalLeave: string
|
||||||
|
dayAdditional: string
|
||||||
|
}>({
|
||||||
|
id: 0,
|
||||||
|
user: {
|
||||||
|
id: 0,
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
note: '',
|
||||||
|
totalLeave: '',
|
||||||
|
dayAdditional: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const [data, setData] = useState<UserData[]>([])
|
||||||
|
const [date, setDate] = useState({
|
||||||
|
year: new Date().getFullYear().toString(),
|
||||||
|
})
|
||||||
|
const getLeaveList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await get(getLeaveManagement, {
|
||||||
|
year: date.year,
|
||||||
|
})
|
||||||
|
if (res.status) {
|
||||||
|
setData(
|
||||||
|
res.data.filter((u: UserData) => u.user.permission.includes('staff')),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log(error)
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: error.message ?? error,
|
||||||
|
color: 'red',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getLeaveList()
|
||||||
|
}, [date])
|
||||||
|
|
||||||
|
const updateInfoNote = async (
|
||||||
|
id: number,
|
||||||
|
users: {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
},
|
||||||
|
totalLeave: string,
|
||||||
|
dayAdditional: string,
|
||||||
|
note: string,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
await update(
|
||||||
|
updateNoteLeave,
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
users: users,
|
||||||
|
totalLeave: totalLeave,
|
||||||
|
dayAdditional: dayAdditional,
|
||||||
|
note: note,
|
||||||
|
},
|
||||||
|
getLeaveList,
|
||||||
|
)
|
||||||
|
|
||||||
|
setDisableBtn(false)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMonthNames() {
|
||||||
|
const monthNames = [
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
name: 'January',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 2,
|
||||||
|
name: 'February',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 3,
|
||||||
|
name: 'March',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 4,
|
||||||
|
name: 'April',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
name: 'May',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 6,
|
||||||
|
name: 'June',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 7,
|
||||||
|
name: 'July',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 8,
|
||||||
|
name: 'August',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 9,
|
||||||
|
name: 'September',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 10,
|
||||||
|
name: 'October',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 11,
|
||||||
|
name: 'November',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 12,
|
||||||
|
name: 'December',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
return monthNames.map((month) => {
|
||||||
|
return {
|
||||||
|
value: month.value,
|
||||||
|
name: month.name.substring(0, 3),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(customAddNotes, 'customAddNotes')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={classes.title}>
|
||||||
|
<h3>
|
||||||
|
<Text>Admin/</Text> Leave Management
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<Drawer
|
||||||
|
opened={opened1}
|
||||||
|
onClose={close1}
|
||||||
|
position="right"
|
||||||
|
title={<strong>Update Day Leave</strong>}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
mb={'md'}
|
||||||
|
value={customAddNotes.totalLeave}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
if (value) {
|
||||||
|
const floatValue = parseFloat(value)
|
||||||
|
if (
|
||||||
|
/^\d*\.?\d?$/.test(value) &&
|
||||||
|
floatValue >= 0 &&
|
||||||
|
floatValue <= 20
|
||||||
|
) {
|
||||||
|
setCustomAddNotes({
|
||||||
|
...customAddNotes,
|
||||||
|
totalLeave: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setCustomAddNotes({
|
||||||
|
...customAddNotes,
|
||||||
|
totalLeave: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
label={'Total Leave'}
|
||||||
|
placeholder="Input placeholder"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
mb={'md'}
|
||||||
|
value={customAddNotes.dayAdditional}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
if (value) {
|
||||||
|
const floatValue = parseFloat(value)
|
||||||
|
if (
|
||||||
|
/^\d*\.?\d?$/.test(value) &&
|
||||||
|
floatValue >= 0 &&
|
||||||
|
floatValue <= 20
|
||||||
|
) {
|
||||||
|
setCustomAddNotes({
|
||||||
|
...customAddNotes,
|
||||||
|
dayAdditional: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setCustomAddNotes({
|
||||||
|
...customAddNotes,
|
||||||
|
dayAdditional: '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
label={'Day additional leave'}
|
||||||
|
placeholder="Input placeholder"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Textarea
|
||||||
|
mb={'md'}
|
||||||
|
label="Note"
|
||||||
|
value={customAddNotes.note}
|
||||||
|
onChange={(e) => {
|
||||||
|
setCustomAddNotes({ ...customAddNotes, note: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setDisableBtn(true)
|
||||||
|
if (
|
||||||
|
customAddNotes.id === 0 ||
|
||||||
|
customAddNotes.totalLeave === '' ||
|
||||||
|
customAddNotes.totalLeave === '0'
|
||||||
|
// customAddNotes.dayAdditional === 0 ||
|
||||||
|
// customAddNotes.note === ''
|
||||||
|
) {
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Input data required',
|
||||||
|
color: 'red',
|
||||||
|
})
|
||||||
|
setDisableBtn(false)
|
||||||
|
} else {
|
||||||
|
updateInfoNote(
|
||||||
|
customAddNotes.id,
|
||||||
|
customAddNotes.user,
|
||||||
|
customAddNotes.totalLeave,
|
||||||
|
customAddNotes.dayAdditional,
|
||||||
|
customAddNotes.note,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={disableBtn}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Drawer>
|
||||||
|
<Box display={'flex'}>
|
||||||
|
<Box style={{ display: 'flex', flexFlow: 'column' }} w={'30%'}>
|
||||||
|
<Box w="100%" display={'flex'}>
|
||||||
|
<Select
|
||||||
|
w="50%"
|
||||||
|
value={date.year}
|
||||||
|
size="xs"
|
||||||
|
ml={'sm'}
|
||||||
|
label="Year"
|
||||||
|
data={Array.from({ length: 10 }, (_, index) => {
|
||||||
|
return {
|
||||||
|
value: (
|
||||||
|
parseInt(moment(Date.now()).format('YYYY')) -
|
||||||
|
3 +
|
||||||
|
index
|
||||||
|
).toString(),
|
||||||
|
label: (
|
||||||
|
parseInt(moment(Date.now()).format('YYYY')) -
|
||||||
|
3 +
|
||||||
|
index
|
||||||
|
).toString(),
|
||||||
|
disabled:
|
||||||
|
parseInt(moment(Date.now()).format('YYYY')) - 3 + index >
|
||||||
|
parseInt(moment(Date.now()).format('YYYY')),
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDate({ ...date, year: e! })
|
||||||
|
}}
|
||||||
|
></Select>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
w="70%"
|
||||||
|
pl={200}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
// alignItems: 'end',
|
||||||
|
justifyContent: 'end',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box display={'flex'} style={{ alignItems: 'end' }}>
|
||||||
|
<Tooltip label="Save working days">
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
ml={'sm'}
|
||||||
|
onClick={() => {
|
||||||
|
//form add user new
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Table
|
||||||
|
striped
|
||||||
|
highlightOnHover
|
||||||
|
withTableBorder
|
||||||
|
withColumnBorders
|
||||||
|
mt={'md'}
|
||||||
|
>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr bg={'#228be66b'}>
|
||||||
|
<Table.Th></Table.Th>
|
||||||
|
<Table.Th>User</Table.Th>
|
||||||
|
{monthInYear.map((d) => {
|
||||||
|
return (
|
||||||
|
<Menu width={200} shadow="md" key={d.value}>
|
||||||
|
<Menu.Target>
|
||||||
|
<Table.Th ta={'center'} style={{ cursor: 'pointer' }}>
|
||||||
|
<span>{d.name}</span>
|
||||||
|
</Table.Th>
|
||||||
|
</Menu.Target>
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<Table.Th ta={'center'}>Total Leave</Table.Th>
|
||||||
|
<Table.Th ta={'center'}>Day Off</Table.Th>
|
||||||
|
<Table.Th ta={'center'}>Remaining</Table.Th>
|
||||||
|
<Table.Th ta={'center'}>Notes</Table.Th>
|
||||||
|
<Table.Th ta={'center'}></Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{data.map((user, index) => {
|
||||||
|
let totalDayOff = 0
|
||||||
|
let totalDayLeave =
|
||||||
|
user.leaveDay.ld_day + user.leaveDay.ld_date_additional
|
||||||
|
let ld_note = user.leaveDay.ld_note
|
||||||
|
return (
|
||||||
|
<Table.Tr key={user.user.id} className={classes.tableTr}>
|
||||||
|
<Table.Td ta={'center'}>{index + 1}</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Tooltip multiline label={user.user.name}>
|
||||||
|
<div>{user.user.name}</div>
|
||||||
|
</Tooltip>
|
||||||
|
</Table.Td>
|
||||||
|
|
||||||
|
{monthInYear.map((d, i) => {
|
||||||
|
let total =
|
||||||
|
user.monthlyLeaveDays.find((item) => {
|
||||||
|
return item.month == d.value
|
||||||
|
})?.leave_days ?? 0
|
||||||
|
totalDayOff = totalDayOff + total
|
||||||
|
return (
|
||||||
|
<Table.Td key={i} ta={'center'}>
|
||||||
|
{total === 0 ? '' : total}
|
||||||
|
</Table.Td>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
<Table.Td ta={'center'}>{totalDayLeave}</Table.Td>
|
||||||
|
<Table.Td ta={'center'}>{totalDayOff}</Table.Td>
|
||||||
|
<Table.Td ta={'center'}>
|
||||||
|
{totalDayLeave - totalDayOff}
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>{ld_note}</Table.Td>
|
||||||
|
<Table.Td ta={'center'}>
|
||||||
|
<IconEdit
|
||||||
|
color="green"
|
||||||
|
onClick={() => {
|
||||||
|
let totalLeave =
|
||||||
|
user.leaveDay.ld_day == 0
|
||||||
|
? ''
|
||||||
|
: String(user.leaveDay.ld_day)
|
||||||
|
let dayAdditional =
|
||||||
|
user.leaveDay.ld_date_additional == 0
|
||||||
|
? ''
|
||||||
|
: String(user.leaveDay.ld_date_additional)
|
||||||
|
open1()
|
||||||
|
setCustomAddNotes({
|
||||||
|
...customAddNotes,
|
||||||
|
id: user.leaveDay.id,
|
||||||
|
note: ld_note,
|
||||||
|
totalLeave: totalLeave,
|
||||||
|
dayAdditional: dayAdditional,
|
||||||
|
user: {
|
||||||
|
id: user.user.id,
|
||||||
|
name: user.user.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LeaveManagement
|
||||||
|
|
@ -7,6 +7,7 @@ import CustomTheme from '@/pages/CustomTheme/CustomTheme'
|
||||||
import Dashboard from '@/pages/Dashboard/Dashboard'
|
import Dashboard from '@/pages/Dashboard/Dashboard'
|
||||||
import GeneralSetting from '@/pages/GeneralSetting/GeneralSetting'
|
import GeneralSetting from '@/pages/GeneralSetting/GeneralSetting'
|
||||||
import Jira from '@/pages/Jira/Jira'
|
import Jira from '@/pages/Jira/Jira'
|
||||||
|
import LeaveManagement from '@/pages/LeaveManagement/LeaveManagement'
|
||||||
import PageNotFound from '@/pages/NotFound/NotFound'
|
import PageNotFound from '@/pages/NotFound/NotFound'
|
||||||
import Timekeeping from '@/pages/Timekeeping/Timekeeping'
|
import Timekeeping from '@/pages/Timekeeping/Timekeeping'
|
||||||
import Tracking from '@/pages/Tracking/Tracking'
|
import Tracking from '@/pages/Tracking/Tracking'
|
||||||
|
|
@ -88,20 +89,20 @@ const mainRoutes = [
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
path: '/jira',
|
// path: '/jira',
|
||||||
element: (
|
// element: (
|
||||||
<ProtectedRoute mode="route">
|
// <ProtectedRoute mode="route">
|
||||||
<BasePage
|
// <BasePage
|
||||||
main={
|
// main={
|
||||||
<>
|
// <>
|
||||||
<Jira />
|
// <Jira />
|
||||||
</>
|
// </>
|
||||||
}
|
// }
|
||||||
></BasePage>
|
// ></BasePage>
|
||||||
</ProtectedRoute>
|
// </ProtectedRoute>
|
||||||
),
|
// ),
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
path: '/worklogs',
|
path: '/worklogs',
|
||||||
element: (
|
element: (
|
||||||
|
|
@ -130,6 +131,20 @@ const mainRoutes = [
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/leave-management',
|
||||||
|
element: (
|
||||||
|
<ProtectedRoute mode="route">
|
||||||
|
<BasePage
|
||||||
|
main={
|
||||||
|
<>
|
||||||
|
<LeaveManagement />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
></BasePage>
|
||||||
|
</ProtectedRoute>
|
||||||
|
),
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// path: '/packages',
|
// path: '/packages',
|
||||||
// element: (
|
// element: (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue