Bổ sung nút report excel trang timekeeping , leave management
This commit is contained in:
parent
fcaa2926fd
commit
93030b73c7
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Modules\Admin\app\Http\Controllers;
|
namespace Modules\Admin\app\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Exports\LeaveManagementExport;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Jobs\InitializeLeaveDays;
|
use App\Jobs\InitializeLeaveDays;
|
||||||
use App\Models\LeaveDays;
|
use App\Models\LeaveDays;
|
||||||
|
|
@ -9,6 +10,7 @@ use App\Models\Notes;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
|
|
||||||
class LeaveManagementController extends Controller
|
class LeaveManagementController extends Controller
|
||||||
{
|
{
|
||||||
|
|
@ -139,4 +141,20 @@ class LeaveManagementController extends Controller
|
||||||
|
|
||||||
return response()->json(['status' => true, 'message' => 'Updated successfully']);
|
return response()->json(['status' => true, 'message' => 'Updated successfully']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function export(Request $request)
|
||||||
|
{
|
||||||
|
$year = $request->query('year', now()->year);
|
||||||
|
$leaveDays = $this->getDataByYear($year);
|
||||||
|
|
||||||
|
if ($leaveDays->isEmpty()) {
|
||||||
|
return response()->json(['status' => false, 'message' => 'No data found']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentDate = date('d_His');
|
||||||
|
return Excel::download(
|
||||||
|
new LeaveManagementExport($leaveDays),
|
||||||
|
"LeaveManagement_{$year}_{$currentDate}.xlsx"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ use Illuminate\Support\Facades\DB;
|
||||||
use Modules\Admin\app\Models\Admin;
|
use Modules\Admin\app\Models\Admin;
|
||||||
use Modules\Admin\app\Models\MonthlyTimekeeping;
|
use Modules\Admin\app\Models\MonthlyTimekeeping;
|
||||||
use Modules\Admin\app\Models\Tracking;
|
use Modules\Admin\app\Models\Tracking;
|
||||||
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
|
use App\Exports\TimekeepingExport;
|
||||||
|
|
||||||
class TimekeepingController extends Controller
|
class TimekeepingController extends Controller
|
||||||
{
|
{
|
||||||
|
|
@ -149,7 +151,7 @@ class TimekeepingController extends Controller
|
||||||
|
|
||||||
return response()->json(['status' => true, 'message' => 'Add successfully']);
|
return response()->json(['status' => true, 'message' => 'Add successfully']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function updateCacheMonth(Request $request)
|
public function updateCacheMonth(Request $request)
|
||||||
|
|
@ -185,4 +187,38 @@ class TimekeepingController extends Controller
|
||||||
|
|
||||||
return response()->json(['message' => 'Delete fail', 'status' => false]);
|
return response()->json(['message' => 'Delete fail', 'status' => false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function export(Request $request)
|
||||||
|
{
|
||||||
|
// Validate request
|
||||||
|
$request->validate([
|
||||||
|
'month' => 'required|numeric|between:1,12',
|
||||||
|
'year' => 'required|numeric|min:2000',
|
||||||
|
'working_days' => 'required|numeric|between:1,31'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Reuse get() function to fetch data
|
||||||
|
$response = $this->get($request);
|
||||||
|
$responseData = json_decode($response->getContent(), true);
|
||||||
|
|
||||||
|
if (!$responseData['status']) {
|
||||||
|
return response()->json(['status' => false, 'message' => 'No data found']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lọc chỉ lấy user có permission staff
|
||||||
|
$staffData = array_filter($responseData['data'], function($user) {
|
||||||
|
return isset($user['user']['permission']) && $user['user']['permission'] === 'staff';
|
||||||
|
});
|
||||||
|
|
||||||
|
$currentDate = date('d_His');
|
||||||
|
return Excel::download(
|
||||||
|
new TimekeepingExport(
|
||||||
|
array_values($staffData), // Convert to indexed array after filtering
|
||||||
|
$request->month,
|
||||||
|
$request->year,
|
||||||
|
$request->working_days
|
||||||
|
),
|
||||||
|
"Timekeeping_{$request->month}_{$request->year}_{$currentDate}.xlsx"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,7 @@ Route::middleware('api')
|
||||||
Route::get('/delete', [TimekeepingController::class, 'deleteNote'])->middleware('check.permission:admin.hr');
|
Route::get('/delete', [TimekeepingController::class, 'deleteNote'])->middleware('check.permission:admin.hr');
|
||||||
Route::post('/update-cache-month', [TimekeepingController::class, 'updateCacheMonth'])->middleware('check.permission:admin');
|
Route::post('/update-cache-month', [TimekeepingController::class, 'updateCacheMonth'])->middleware('check.permission:admin');
|
||||||
Route::post('/update-working-days', [TimekeepingController::class, 'saveWorkingDays'])->middleware('check.permission:admin.hr');
|
Route::post('/update-working-days', [TimekeepingController::class, 'saveWorkingDays'])->middleware('check.permission:admin.hr');
|
||||||
|
Route::get('/export', [TimekeepingController::class, 'export'])->middleware('check.permission:admin.hr.staff.accountant');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group([
|
Route::group([
|
||||||
|
|
@ -146,6 +147,7 @@ Route::middleware('api')
|
||||||
'prefix' => 'leave-management',
|
'prefix' => 'leave-management',
|
||||||
], function () {
|
], function () {
|
||||||
Route::get('/', [LeaveManagementController::class, 'get'])->middleware('check.permission:admin.hr.staff.accountant');
|
Route::get('/', [LeaveManagementController::class, 'get'])->middleware('check.permission:admin.hr.staff.accountant');
|
||||||
|
Route::get('/export', [LeaveManagementController::class, 'export'])->middleware('check.permission:admin.hr.staff.accountant');
|
||||||
Route::post('/saveNoteLeave', [LeaveManagementController::class, 'saveNoteLeave'])->middleware('check.permission:admin.hr');
|
Route::post('/saveNoteLeave', [LeaveManagementController::class, 'saveNoteLeave'])->middleware('check.permission:admin.hr');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exports;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Maatwebsite\Excel\Concerns\FromArray;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||||
|
|
||||||
|
class LeaveManagementExport implements FromArray, WithHeadings, WithStyles
|
||||||
|
{
|
||||||
|
protected $data;
|
||||||
|
protected $year;
|
||||||
|
|
||||||
|
public function __construct($data)
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
$this->year = Carbon::now()->year;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function headings(): array
|
||||||
|
{
|
||||||
|
$months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
|
return array_merge(
|
||||||
|
['No.', 'User'],
|
||||||
|
$months,
|
||||||
|
['Total', 'Off', 'Remaining', 'Notes']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function array(): array
|
||||||
|
{
|
||||||
|
$headers = $this->headings(); // Lấy tiêu đề
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
foreach ($this->data as $index => $user) {
|
||||||
|
$totalDayOff = 0;
|
||||||
|
$totalDayLeave = $user['leaveDay']['ld_day'] + $user['leaveDay']['ld_date_additional'];
|
||||||
|
|
||||||
|
// Tính tổng ngày nghỉ theo tháng
|
||||||
|
$monthlyLeaves = array_fill(1, 12, 0);
|
||||||
|
foreach ($user['monthlyLeaveDays'] as $leaveDay) {
|
||||||
|
$monthlyLeaves[$leaveDay['month']] += $leaveDay['leave_days'];
|
||||||
|
$totalDayOff += $leaveDay['leave_days'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tạo dòng dữ liệu
|
||||||
|
$row = [
|
||||||
|
$index + 1,
|
||||||
|
$user['user']['name']
|
||||||
|
];
|
||||||
|
|
||||||
|
// Thêm dữ liệu các tháng
|
||||||
|
for ($month = 1; $month <= 12; $month++) {
|
||||||
|
$row[] = $monthlyLeaves[$month] ?: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thêm tổng số ngày
|
||||||
|
$row[] = $totalDayLeave;
|
||||||
|
$row[] = $totalDayOff;
|
||||||
|
$row[] = $totalDayLeave - $totalDayOff;
|
||||||
|
$row[] = $user['leaveDay']['ld_note'] ?? '';
|
||||||
|
|
||||||
|
$rows[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_merge([$headers], $rows); // Thêm tiêu đề vào đầu mảng
|
||||||
|
}
|
||||||
|
|
||||||
|
public function styles(Worksheet $sheet)
|
||||||
|
{
|
||||||
|
$lastRow = count($this->data) + 2;
|
||||||
|
$lastColumn = 'R';
|
||||||
|
|
||||||
|
// Thêm và style title
|
||||||
|
$sheet->mergeCells("A1:{$lastColumn}1");
|
||||||
|
$sheet->setCellValue('A1', "DANH SÁCH NGÀY NGHỈ NĂM {$this->year}");
|
||||||
|
$sheet->getStyle("A1:{$lastColumn}1")->applyFromArray([
|
||||||
|
'font' => [
|
||||||
|
'bold' => true,
|
||||||
|
'size' => 14
|
||||||
|
],
|
||||||
|
'alignment' => [
|
||||||
|
'horizontal' => Alignment::HORIZONTAL_CENTER,
|
||||||
|
'vertical' => Alignment::VERTICAL_CENTER
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Style cho header (dời xuống row 2)
|
||||||
|
$sheet->getStyle("A2:{$lastColumn}2")->applyFromArray([
|
||||||
|
'font' => ['bold' => true],
|
||||||
|
'alignment' => [
|
||||||
|
'horizontal' => Alignment::HORIZONTAL_CENTER,
|
||||||
|
'vertical' => Alignment::VERTICAL_CENTER
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Style cho toàn bộ bảng (bắt đầu từ row 1)
|
||||||
|
$sheet->getStyle("A1:{$lastColumn}{$lastRow}")->applyFromArray([
|
||||||
|
'borders' => [
|
||||||
|
'allBorders' => [
|
||||||
|
'borderStyle' => Border::BORDER_THIN
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'alignment' => [
|
||||||
|
'vertical' => Alignment::VERTICAL_CENTER
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Căn giữa cho các cột số liệu (điều chỉnh range bắt đầu từ row 3)
|
||||||
|
for ($col = 'C'; $col <= 'P'; $col++) {
|
||||||
|
$sheet->getStyle("{$col}3:{$col}{$lastRow}")
|
||||||
|
->getAlignment()
|
||||||
|
->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set độ rộng cột
|
||||||
|
$sheet->getColumnDimension('A')->setWidth(5); // No.
|
||||||
|
$sheet->getColumnDimension('B')->setWidth(30); // User
|
||||||
|
// Các tháng
|
||||||
|
for ($i = 'C'; $i <= 'N'; $i++) {
|
||||||
|
$sheet->getColumnDimension($i)->setWidth(8);
|
||||||
|
}
|
||||||
|
$sheet->getColumnDimension('O')->setWidth(8); // Total
|
||||||
|
$sheet->getColumnDimension('P')->setWidth(8); // Off
|
||||||
|
$sheet->getColumnDimension('Q')->setWidth(12); // Remaining
|
||||||
|
$sheet->getColumnDimension('R')->setWidth(30); // Notes
|
||||||
|
|
||||||
|
return $sheet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exports;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Maatwebsite\Excel\Concerns\FromArray;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||||
|
|
||||||
|
class TimekeepingExport implements FromArray, WithHeadings, WithStyles
|
||||||
|
{
|
||||||
|
protected $data;
|
||||||
|
protected $month;
|
||||||
|
protected $year;
|
||||||
|
protected $workingDays;
|
||||||
|
protected $daysInMonth;
|
||||||
|
|
||||||
|
public function __construct($data, $month, $year, $workingDays)
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
$this->month = $month;
|
||||||
|
$this->year = $year;
|
||||||
|
$this->workingDays = $workingDays;
|
||||||
|
$this->daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function array(): array
|
||||||
|
{
|
||||||
|
// Lấy headers
|
||||||
|
$headers = $this->headings();
|
||||||
|
|
||||||
|
// Lấy dữ liệu người dùng
|
||||||
|
$userRows = [];
|
||||||
|
foreach ($this->data as $user) {
|
||||||
|
// Kiểm tra permission staff
|
||||||
|
if (!isset($user['user']['permission']) || $user['user']['permission'] !== 'staff') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = [
|
||||||
|
$user['user']['name'] ?? 'Unknown',
|
||||||
|
0, // Total days
|
||||||
|
$this->workingDays, // Off days (initialize with working days)
|
||||||
|
];
|
||||||
|
|
||||||
|
$totalDays = 0;
|
||||||
|
// Add data for each day in month
|
||||||
|
for ($day = 1; $day <= $this->daysInMonth; $day++) {
|
||||||
|
$dayData = '';
|
||||||
|
if (isset($user['history'])) {
|
||||||
|
foreach ($user['history'] as $history) {
|
||||||
|
if ($history['day'] === $day) {
|
||||||
|
$total = $history['total'] ?? 0;
|
||||||
|
if ($total >= 7 * 3600) {
|
||||||
|
$dayData = '1';
|
||||||
|
$totalDays += 1;
|
||||||
|
} else if ($total >= 3.5 * 3600) {
|
||||||
|
$dayData = '0.5';
|
||||||
|
$totalDays += 0.5;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$row[] = $dayData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update total and off days
|
||||||
|
$row[1] = $totalDays;
|
||||||
|
$row[2] = $this->workingDays - $totalDays;
|
||||||
|
|
||||||
|
// Add Notes column with formatted content
|
||||||
|
$notes = [];
|
||||||
|
if (isset($user['history'])) {
|
||||||
|
foreach ($user['history'] as $history) {
|
||||||
|
if (!empty($history['notes'])) {
|
||||||
|
$dayNotes = [];
|
||||||
|
foreach ($history['notes'] as $note) {
|
||||||
|
$dayNotes[] = "- {$note['reasonName']} ({$note['timeTypeName']}): {$note['note']}";
|
||||||
|
}
|
||||||
|
if (!empty($dayNotes)) {
|
||||||
|
$notes[] = "Day {$history['day']}:\n" . implode("\n", $dayNotes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$row[] = !empty($notes) ? implode("\n\n", $notes) : '';
|
||||||
|
|
||||||
|
$userRows[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_merge($headers, $userRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function headings(): array
|
||||||
|
{
|
||||||
|
|
||||||
|
$firstRow = ['Day', '', ''];
|
||||||
|
// Second row: Day of week
|
||||||
|
$secondRow = ['', '', ''];
|
||||||
|
|
||||||
|
$date = Carbon::create($this->year, $this->month, 1);
|
||||||
|
|
||||||
|
for ($day = 1; $day <= $this->daysInMonth; $day++) {
|
||||||
|
$firstRow[] = $day;
|
||||||
|
$secondRow[] = $date->format('D');
|
||||||
|
$date->addDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Notes column
|
||||||
|
$firstRow[] = 'Notes';
|
||||||
|
$secondRow[] = '';
|
||||||
|
|
||||||
|
return [$firstRow, $secondRow];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function styles(Worksheet $sheet)
|
||||||
|
{
|
||||||
|
$lastColumn = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($this->daysInMonth + 4);
|
||||||
|
|
||||||
|
// Title and working days
|
||||||
|
$sheet->mergeCells("A1:{$lastColumn}1");
|
||||||
|
$sheet->setCellValue('A1', "DANH SÁCH CHẤM CÔNG THÁNG {$this->month} NĂM {$this->year}");
|
||||||
|
$sheet->mergeCells("A2:{$lastColumn}2");
|
||||||
|
$sheet->setCellValue('A2', "Số ngày làm việc: {$this->workingDays}");
|
||||||
|
|
||||||
|
// Merge cells for "Day" title and set value
|
||||||
|
$sheet->mergeCells("A3:C3");
|
||||||
|
$sheet->setCellValue('A3', 'Day');
|
||||||
|
|
||||||
|
// Set values for A4, B4, C4
|
||||||
|
$sheet->setCellValue('A4', 'User');
|
||||||
|
$sheet->setCellValue('B4', 'Total');
|
||||||
|
$sheet->setCellValue('C4', 'Off');
|
||||||
|
|
||||||
|
// Calculate last row (2 title rows + 2 header rows + data rows)
|
||||||
|
$lastRow = count($this->data) + 4;
|
||||||
|
|
||||||
|
// Styling
|
||||||
|
$sheet->getStyle("A1:{$lastColumn}1")->getFont()->setBold(true)->setSize(14);
|
||||||
|
$sheet->getStyle("A2:{$lastColumn}2")->getFont()->setBold(true);
|
||||||
|
$sheet->getStyle("A3:{$lastColumn}4")->getFont()->setBold(true);
|
||||||
|
|
||||||
|
// Border style
|
||||||
|
$borderStyle = [
|
||||||
|
'borders' => [
|
||||||
|
'allBorders' => [
|
||||||
|
'borderStyle' => Border::BORDER_THIN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Apply borders to the data area
|
||||||
|
$sheet->getStyle("A1:{$lastColumn}{$lastRow}")->applyFromArray($borderStyle);
|
||||||
|
|
||||||
|
// Center align all cells except Notes column
|
||||||
|
$sheet->getStyle("A1:{$lastColumn}{$lastRow}")
|
||||||
|
->getAlignment()
|
||||||
|
->setHorizontal(Alignment::HORIZONTAL_CENTER)
|
||||||
|
->setVertical(Alignment::VERTICAL_CENTER);
|
||||||
|
|
||||||
|
// Left align Notes column
|
||||||
|
$noteColumn = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($this->daysInMonth + 4);
|
||||||
|
$sheet->getStyle("{$noteColumn}5:{$noteColumn}{$lastRow}")
|
||||||
|
->getAlignment()
|
||||||
|
->setHorizontal(Alignment::HORIZONTAL_LEFT);
|
||||||
|
|
||||||
|
// Set column widths
|
||||||
|
$sheet->getColumnDimension('A')->setWidth(30);
|
||||||
|
$sheet->getColumnDimension('B')->setWidth(10);
|
||||||
|
$sheet->getColumnDimension('C')->setWidth(10);
|
||||||
|
for ($i = 4; $i <= $this->daysInMonth + 3; $i++) {
|
||||||
|
$sheet->getColumnDimension(\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i))->setWidth(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set width for Note column
|
||||||
|
$sheet->getColumnDimension($noteColumn)->setWidth(40);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -20,6 +20,7 @@ export const getDetailIssByKey = API_URL + 'v1/admin/jira/issue/detail'
|
||||||
|
|
||||||
//Timekeeping
|
//Timekeeping
|
||||||
export const getTheTimesheet = API_URL + 'v1/admin/timekeeping'
|
export const getTheTimesheet = API_URL + 'v1/admin/timekeeping'
|
||||||
|
export const exportTimekeeping = API_URL + 'v1/admin/timekeeping/export'
|
||||||
export const updateMultipleUserWorkingTime =
|
export const updateMultipleUserWorkingTime =
|
||||||
API_URL + 'v1/admin/timekeeping/addMutilple'
|
API_URL + 'v1/admin/timekeeping/addMutilple'
|
||||||
export const updateNote = API_URL + 'v1/admin/timekeeping/addNote'
|
export const updateNote = API_URL + 'v1/admin/timekeeping/addNote'
|
||||||
|
|
@ -36,6 +37,7 @@ export const getListMaster = API_URL + 'v1/admin/category/get-list-master'
|
||||||
export const getLeaveManagement = API_URL + 'v1/admin/leave-management'
|
export const getLeaveManagement = API_URL + 'v1/admin/leave-management'
|
||||||
export const updateNoteLeave =
|
export const updateNoteLeave =
|
||||||
API_URL + 'v1/admin/leave-management/saveNoteLeave'
|
API_URL + 'v1/admin/leave-management/saveNoteLeave'
|
||||||
|
export const exportLeaveManagement = API_URL + 'v1/admin/leave-management/export'
|
||||||
|
|
||||||
//Tickets
|
//Tickets
|
||||||
export const getTickets = API_URL + 'v1/admin/ticket/all'
|
export const getTickets = API_URL + 'v1/admin/ticket/all'
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { getLeaveManagement, updateNoteLeave } from '@/api/Admin'
|
import { getLeaveManagement, updateNoteLeave, exportLeaveManagement } from '@/api/Admin'
|
||||||
import { update } from '@/rtk/helpers/CRUD'
|
import { update } from '@/rtk/helpers/CRUD'
|
||||||
import { get } from '@/rtk/helpers/apiService'
|
import { get, exportFile } from '@/rtk/helpers/apiService'
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
|
|
@ -20,7 +20,7 @@ import { notifications } from '@mantine/notifications'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { IconEdit } from '@tabler/icons-react'
|
import { IconEdit, IconFileExcel } from '@tabler/icons-react'
|
||||||
|
|
||||||
import classes from './LeaveManagement.module.css'
|
import classes from './LeaveManagement.module.css'
|
||||||
|
|
||||||
|
|
@ -245,6 +245,29 @@ const LeaveManagement = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
const timestamp = moment().format('DDMMYYYY_HHmmss')
|
||||||
|
const fileName = `LeaveManagement_${date.year}_${timestamp}.xlsx`
|
||||||
|
|
||||||
|
await exportFile(
|
||||||
|
exportLeaveManagement,
|
||||||
|
{
|
||||||
|
year: parseInt(date.year)
|
||||||
|
},
|
||||||
|
fileName
|
||||||
|
)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Export error:', error)
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Export failed',
|
||||||
|
color: 'red',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={classes.title}>
|
<div className={classes.title}>
|
||||||
|
|
@ -391,22 +414,18 @@ const LeaveManagement = () => {
|
||||||
pl={200}
|
pl={200}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
// alignItems: 'end',
|
|
||||||
justifyContent: 'end',
|
justifyContent: 'end',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box display={'flex'} style={{ alignItems: 'end' }}>
|
<Box display={'flex'} style={{ alignItems: 'end' }}>
|
||||||
{/* <Tooltip label="Save working days">
|
<Button
|
||||||
<Button
|
size="xs"
|
||||||
size="xs"
|
ml={'sm'}
|
||||||
ml={'sm'}
|
onClick={handleExport}
|
||||||
onClick={() => {
|
leftSection={<IconFileExcel size={16} />}
|
||||||
//form add user new
|
>
|
||||||
}}
|
Export Excel
|
||||||
>
|
</Button>
|
||||||
Add
|
|
||||||
</Button>
|
|
||||||
</Tooltip> */}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
deleteNote,
|
deleteNote,
|
||||||
|
exportTimekeeping,
|
||||||
getListMaster,
|
getListMaster,
|
||||||
getTheTimesheet,
|
getTheTimesheet,
|
||||||
updateMultipleUserWorkingTime,
|
updateMultipleUserWorkingTime,
|
||||||
|
|
@ -7,7 +8,7 @@ import {
|
||||||
updateWorkingDays,
|
updateWorkingDays,
|
||||||
} from '@/api/Admin'
|
} from '@/api/Admin'
|
||||||
import { update, Xdelete } from '@/rtk/helpers/CRUD'
|
import { update, Xdelete } from '@/rtk/helpers/CRUD'
|
||||||
import { get } from '@/rtk/helpers/apiService'
|
import { exportFile, get } from '@/rtk/helpers/apiService'
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
|
|
@ -31,6 +32,7 @@ import {
|
||||||
IconPointFilled,
|
IconPointFilled,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
IconX,
|
IconX,
|
||||||
|
IconFileExcel,
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
@ -435,12 +437,29 @@ const Timekeeping = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
const timestamp = moment().format('DDMMYYYY_HHmmss')
|
||||||
|
const fileName = `Timekeeping_${date.month}_${date.year}_${timestamp}.xlsx`
|
||||||
|
|
||||||
|
await exportFile(
|
||||||
|
exportTimekeeping,
|
||||||
|
{
|
||||||
|
month: date.month,
|
||||||
|
year: date.year,
|
||||||
|
working_days: workingDays
|
||||||
|
},
|
||||||
|
fileName
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Export error:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={classes.title}>
|
<div className={classes.title}>
|
||||||
<h3>
|
<h3>Timekeeping</h3>
|
||||||
Timekeeping
|
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<Drawer
|
<Drawer
|
||||||
opened={opened1}
|
opened={opened1}
|
||||||
|
|
@ -511,7 +530,6 @@ const Timekeeping = () => {
|
||||||
<span style={{ paddingLeft: '10px', paddingRight: '10px' }}>|</span>
|
<span style={{ paddingLeft: '10px', paddingRight: '10px' }}>|</span>
|
||||||
<span style={{ fontWeight: 'bold' }}>Day</span>: {customAddNotes.day}
|
<span style={{ fontWeight: 'bold' }}>Day</span>: {customAddNotes.day}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
mb={'md'}
|
mb={'md'}
|
||||||
searchable
|
searchable
|
||||||
|
|
@ -688,6 +706,14 @@ const Timekeeping = () => {
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Button
|
||||||
|
onClick={handleExport}
|
||||||
|
size="xs"
|
||||||
|
ml="xl"
|
||||||
|
leftSection={<IconFileExcel size={16} />}
|
||||||
|
>
|
||||||
|
Export Excel
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
|
|
@ -866,13 +892,22 @@ const Timekeeping = () => {
|
||||||
// offset={{ mainAxis: 5, crossAxis: 0 }}
|
// offset={{ mainAxis: 5, crossAxis: 0 }}
|
||||||
label={showTooltipAllNote(user)}
|
label={showTooltipAllNote(user)}
|
||||||
>
|
>
|
||||||
<div style={{display:'flex', alignItems:'center'}}><Avatar size={'md'} mr={'md'} src={import.meta.env.VITE_BACKEND_URL.includes('local')
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
? import.meta.env.VITE_BACKEND_URL +
|
<Avatar
|
||||||
'storage/' +
|
size={'md'}
|
||||||
user.user.avatar
|
mr={'md'}
|
||||||
: import.meta.env.VITE_BACKEND_URL +
|
src={
|
||||||
'image/storage/' +
|
import.meta.env.VITE_BACKEND_URL.includes('local')
|
||||||
user.user.avatar}/>{user.user.name}</div>
|
? import.meta.env.VITE_BACKEND_URL +
|
||||||
|
'storage/' +
|
||||||
|
user.user.avatar
|
||||||
|
: import.meta.env.VITE_BACKEND_URL +
|
||||||
|
'image/storage/' +
|
||||||
|
user.user.avatar
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{user.user.name}
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td ta={'center'}>{totalDays}</Table.Td>
|
<Table.Td ta={'center'}>{totalDays}</Table.Td>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { getFormDataHeader, getHeaderInfo } from '@/rtk/helpers/tokenCreator'
|
||||||
import { removeTokens } from '@/rtk/localStorage'
|
import { removeTokens } from '@/rtk/localStorage'
|
||||||
import { notifications } from '@mantine/notifications'
|
import { notifications } from '@mantine/notifications'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import moment from 'moment'
|
||||||
const handleResponse = (response: any) => {
|
const handleResponse = (response: any) => {
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
removeTokens()
|
removeTokens()
|
||||||
|
|
@ -156,3 +157,45 @@ export const postImage = async (url: string, body: any, method: any) => {
|
||||||
throw handleResponse(err.response)
|
throw handleResponse(err.response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const exportFile = async (url: string, params: any = {}, fileName: string) => {
|
||||||
|
const header = await getHeaderInfo()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
...header,
|
||||||
|
params,
|
||||||
|
responseType: 'blob'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
const blob = new Blob([response.data])
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = downloadUrl
|
||||||
|
link.download = fileName
|
||||||
|
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(downloadUrl)
|
||||||
|
document.body.removeChild(link)
|
||||||
|
|
||||||
|
// notifications.show({
|
||||||
|
// title: 'Success',
|
||||||
|
// message: 'Export successfully',
|
||||||
|
// color: 'green',
|
||||||
|
// })
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Export failed')
|
||||||
|
} catch (error: any) {
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: error.message || 'Export failed',
|
||||||
|
color: 'red',
|
||||||
|
})
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue