commit
						824b2fd87c
					
				| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
namespace Modules\Admin\app\Http\Controllers;
 | 
			
		||||
 | 
			
		||||
use App\Exports\LeaveManagementExport;
 | 
			
		||||
use App\Http\Controllers\Controller;
 | 
			
		||||
use App\Jobs\InitializeLeaveDays;
 | 
			
		||||
use App\Models\LeaveDays;
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +10,7 @@ use App\Models\Notes;
 | 
			
		|||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Support\Facades\DB;
 | 
			
		||||
use Illuminate\Support\Facades\Validator;
 | 
			
		||||
use Maatwebsite\Excel\Facades\Excel;
 | 
			
		||||
 | 
			
		||||
class LeaveManagementController extends Controller
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -139,4 +141,20 @@ class LeaveManagementController extends Controller
 | 
			
		|||
 | 
			
		||||
        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\MonthlyTimekeeping;
 | 
			
		||||
use Modules\Admin\app\Models\Tracking;
 | 
			
		||||
use Maatwebsite\Excel\Facades\Excel;
 | 
			
		||||
use App\Exports\TimekeepingExport;
 | 
			
		||||
 | 
			
		||||
class TimekeepingController extends Controller
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +151,7 @@ class TimekeepingController extends Controller
 | 
			
		|||
 | 
			
		||||
        return response()->json(['status' => true, 'message' => 'Add successfully']);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function updateCacheMonth(Request $request)
 | 
			
		||||
| 
						 | 
				
			
			@ -185,4 +187,38 @@ class TimekeepingController extends Controller
 | 
			
		|||
 | 
			
		||||
        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::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::get('/export', [TimekeepingController::class, 'export'])->middleware('check.permission:admin.hr.staff.accountant');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            Route::group([
 | 
			
		||||
| 
						 | 
				
			
			@ -146,6 +147,7 @@ Route::middleware('api')
 | 
			
		|||
                'prefix' => 'leave-management',
 | 
			
		||||
            ], function () {
 | 
			
		||||
                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');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
export const getTheTimesheet = API_URL + 'v1/admin/timekeeping'
 | 
			
		||||
export const exportTimekeeping = API_URL + 'v1/admin/timekeeping/export'
 | 
			
		||||
export const updateMultipleUserWorkingTime =
 | 
			
		||||
  API_URL + 'v1/admin/timekeeping/addMutilple'
 | 
			
		||||
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 updateNoteLeave =
 | 
			
		||||
  API_URL + 'v1/admin/leave-management/saveNoteLeave'
 | 
			
		||||
export const exportLeaveManagement = API_URL + 'v1/admin/leave-management/export'
 | 
			
		||||
 | 
			
		||||
//Tickets
 | 
			
		||||
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 { get } from '@/rtk/helpers/apiService'
 | 
			
		||||
import { get, exportFile } from '@/rtk/helpers/apiService'
 | 
			
		||||
import {
 | 
			
		||||
  Avatar,
 | 
			
		||||
  Box,
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ import { notifications } from '@mantine/notifications'
 | 
			
		|||
import moment from 'moment'
 | 
			
		||||
import { useEffect, useState } from 'react'
 | 
			
		||||
 | 
			
		||||
import { IconEdit } from '@tabler/icons-react'
 | 
			
		||||
import { IconEdit, IconFileExcel } from '@tabler/icons-react'
 | 
			
		||||
 | 
			
		||||
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 (
 | 
			
		||||
    <div>
 | 
			
		||||
      <div className={classes.title}>
 | 
			
		||||
| 
						 | 
				
			
			@ -391,22 +414,18 @@ const LeaveManagement = () => {
 | 
			
		|||
          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> */}
 | 
			
		||||
            <Button
 | 
			
		||||
              size="xs"
 | 
			
		||||
              ml={'sm'}
 | 
			
		||||
              onClick={handleExport}
 | 
			
		||||
              leftSection={<IconFileExcel size={16} />}
 | 
			
		||||
            >
 | 
			
		||||
              Export Excel
 | 
			
		||||
            </Button>
 | 
			
		||||
          </Box>
 | 
			
		||||
        </Box>
 | 
			
		||||
      </Box>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
import {
 | 
			
		||||
  deleteNote,
 | 
			
		||||
  exportTimekeeping,
 | 
			
		||||
  getListMaster,
 | 
			
		||||
  getTheTimesheet,
 | 
			
		||||
  updateMultipleUserWorkingTime,
 | 
			
		||||
| 
						 | 
				
			
			@ -7,7 +8,7 @@ import {
 | 
			
		|||
  updateWorkingDays,
 | 
			
		||||
} from '@/api/Admin'
 | 
			
		||||
import { update, Xdelete } from '@/rtk/helpers/CRUD'
 | 
			
		||||
import { get } from '@/rtk/helpers/apiService'
 | 
			
		||||
import { exportFile, get } from '@/rtk/helpers/apiService'
 | 
			
		||||
import {
 | 
			
		||||
  Avatar,
 | 
			
		||||
  Box,
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +32,7 @@ import {
 | 
			
		|||
  IconPointFilled,
 | 
			
		||||
  IconTrash,
 | 
			
		||||
  IconX,
 | 
			
		||||
  IconFileExcel,
 | 
			
		||||
} from '@tabler/icons-react'
 | 
			
		||||
import moment from 'moment'
 | 
			
		||||
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 (
 | 
			
		||||
    <div>
 | 
			
		||||
      <div className={classes.title}>
 | 
			
		||||
        <h3>
 | 
			
		||||
          Timekeeping
 | 
			
		||||
        </h3>
 | 
			
		||||
        <h3>Timekeeping</h3>
 | 
			
		||||
      </div>
 | 
			
		||||
      <Drawer
 | 
			
		||||
        opened={opened1}
 | 
			
		||||
| 
						 | 
				
			
			@ -511,7 +530,6 @@ const Timekeeping = () => {
 | 
			
		|||
          <span style={{ paddingLeft: '10px', paddingRight: '10px' }}>|</span>
 | 
			
		||||
          <span style={{ fontWeight: 'bold' }}>Day</span>: {customAddNotes.day}
 | 
			
		||||
        </p>
 | 
			
		||||
 | 
			
		||||
        <Select
 | 
			
		||||
          mb={'md'}
 | 
			
		||||
          searchable
 | 
			
		||||
| 
						 | 
				
			
			@ -688,6 +706,14 @@ const Timekeeping = () => {
 | 
			
		|||
                Save
 | 
			
		||||
              </Button>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
            <Button 
 | 
			
		||||
              onClick={handleExport} 
 | 
			
		||||
              size="xs" 
 | 
			
		||||
              ml="xl"
 | 
			
		||||
              leftSection={<IconFileExcel size={16} />}
 | 
			
		||||
            >
 | 
			
		||||
              Export Excel
 | 
			
		||||
            </Button>
 | 
			
		||||
          </Box>
 | 
			
		||||
        </Box>
 | 
			
		||||
        <Box
 | 
			
		||||
| 
						 | 
				
			
			@ -866,13 +892,22 @@ const Timekeeping = () => {
 | 
			
		|||
                      // offset={{ mainAxis: 5, crossAxis: 0 }}
 | 
			
		||||
                      label={showTooltipAllNote(user)}
 | 
			
		||||
                    >
 | 
			
		||||
                      <div style={{display:'flex', alignItems:'center'}}><Avatar size={'md'} mr={'md'} src={import.meta.env.VITE_BACKEND_URL.includes('local')
 | 
			
		||||
                          ? 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>
 | 
			
		||||
                      <div style={{ display: 'flex', alignItems: 'center' }}>
 | 
			
		||||
                        <Avatar
 | 
			
		||||
                          size={'md'}
 | 
			
		||||
                          mr={'md'}
 | 
			
		||||
                          src={
 | 
			
		||||
                            import.meta.env.VITE_BACKEND_URL.includes('local')
 | 
			
		||||
                              ? 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>
 | 
			
		||||
                  </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 { notifications } from '@mantine/notifications'
 | 
			
		||||
import axios from 'axios'
 | 
			
		||||
import moment from 'moment'
 | 
			
		||||
const handleResponse = (response: any) => {
 | 
			
		||||
  if (response.status === 401) {
 | 
			
		||||
    removeTokens()
 | 
			
		||||
| 
						 | 
				
			
			@ -156,3 +157,45 @@ export const postImage = async (url: string, body: any, method: any) => {
 | 
			
		|||
    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