Bổ sung note cho Timekepping
This commit is contained in:
		
							parent
							
								
									b203e8d82c
								
							
						
					
					
						commit
						ae5737c3bf
					
				| 
						 | 
				
			
			@ -4,6 +4,7 @@ namespace Modules\Admin\app\Http\Controllers;
 | 
			
		|||
 | 
			
		||||
use App\Helper\Cache\CurrentMonthTimekeeping;
 | 
			
		||||
use App\Http\Controllers\Controller;
 | 
			
		||||
use App\Models\Notes;
 | 
			
		||||
use App\Traits\AnalyzeData;
 | 
			
		||||
use App\Traits\HasFilterRequest;
 | 
			
		||||
use App\Traits\HasOrderByRequest;
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +24,7 @@ class TimekeepingController extends Controller
 | 
			
		|||
    use HasFilterRequest;
 | 
			
		||||
    use HasSearchRequest;
 | 
			
		||||
    use AnalyzeData;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function get(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $currentDate = Carbon::now();
 | 
			
		||||
| 
						 | 
				
			
			@ -32,11 +33,9 @@ class TimekeepingController extends Controller
 | 
			
		|||
        $data = MonthlyTimekeeping::where('month', '=', $request->month)->where('year', '=', $request->year)->first();
 | 
			
		||||
        if ($currentMonth == (int)$request->month && $currentYear == (int)$request->year) {
 | 
			
		||||
            $cacheData = CurrentMonthTimekeeping::getCacheCurrentMonthTimekeeping();
 | 
			
		||||
            // $result = $this->analyzeCurrentMonthTimeKeepingData($currentMonth, $currentYear);
 | 
			
		||||
            // dd($result);die;
 | 
			
		||||
            if ($cacheData) {
 | 
			
		||||
                $cacheData->data = json_decode($cacheData->data, true);
 | 
			
		||||
                return response()->json(['status' => true, 'data' => $cacheData->data, 'working_days'=> $cacheData->working_days, 'message' => 'Get from cache']);
 | 
			
		||||
                return response()->json(['status' => true, 'data' => $cacheData->data, 'working_days' => $cacheData->working_days, 'message' => 'Get from cache']);
 | 
			
		||||
            } else {
 | 
			
		||||
                $result = $this->analyzeCurrentMonthTimeKeepingData($currentMonth, $currentYear);
 | 
			
		||||
                if ($data) {
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +48,7 @@ class TimekeepingController extends Controller
 | 
			
		|||
        } else {
 | 
			
		||||
            if ($data) {
 | 
			
		||||
                $data['data'] = json_decode($data['data']);
 | 
			
		||||
                return response()->json(['status' => true, 'data' => $data['data'], 'working_days'=> $data['working_days'], 'message' => 'Get from DB']);
 | 
			
		||||
                return response()->json(['status' => true, 'data' => $data['data'], 'working_days' => $data['working_days'], 'message' => 'Get from DB']);
 | 
			
		||||
            } else {
 | 
			
		||||
                $result = $this->analyzeCurrentMonthTimeKeepingData($request->month, $request->year);
 | 
			
		||||
                MonthlyTimekeeping::create(['month' => $request->month, 'year' => $request->year, 'working_days' => Carbon::create((int)$request->year, (int)$request->month)->daysInMonth, 'data' => json_encode($result)]);
 | 
			
		||||
| 
						 | 
				
			
			@ -95,12 +94,42 @@ class TimekeepingController extends Controller
 | 
			
		|||
    public function saveWorkingDays(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $data = MonthlyTimekeeping::where('month', '=', $request->month)->where('year', '=', $request->year)->first();
 | 
			
		||||
        if($data){
 | 
			
		||||
            $data->update(['working_days'=>$request->working_days]);
 | 
			
		||||
        if ($data) {
 | 
			
		||||
            $data->update(['working_days' => $request->working_days]);
 | 
			
		||||
            $this->createOrUpdateRecordForCurrentMonth($request->month, $request->year);
 | 
			
		||||
            return response()->json(['status' => true, 'message' => 'Update successful']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return response()->json(['status' => false, 'message' => 'Update failed']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function addNoteForUser(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $user_id = $request->users["id"] ?? "";
 | 
			
		||||
        $month = $request->month;
 | 
			
		||||
        $year = $request->year;
 | 
			
		||||
        $day = $request->day;
 | 
			
		||||
        $time_type = $request->type;
 | 
			
		||||
        $reason = $request->reason;
 | 
			
		||||
        $note = $request->note;
 | 
			
		||||
 | 
			
		||||
        if ($user_id == "") {
 | 
			
		||||
            return response()->json(['status' => false, 'message' => 'User not found!']);
 | 
			
		||||
        }
 | 
			
		||||
        Notes::insert([
 | 
			
		||||
            [
 | 
			
		||||
                "n_user_id" => $user_id,
 | 
			
		||||
                "n_day" => $day,
 | 
			
		||||
                "n_month" => $month,
 | 
			
		||||
                "n_year" => $year,
 | 
			
		||||
                "n_time_type" => $time_type,
 | 
			
		||||
                "n_reason" => $reason,
 | 
			
		||||
                "n_note" => $note
 | 
			
		||||
            ]
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $this->createOrUpdateRecordForCurrentMonth($month, $year);
 | 
			
		||||
 | 
			
		||||
        return response()->json(['status' => true, 'message' => 'Add successfully']);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -108,6 +108,7 @@ Route::middleware('api')
 | 
			
		|||
            ], function () {
 | 
			
		||||
                Route::get('/', [TimekeepingController::class, 'get'])->middleware('check.permission:admin.hr.staff');
 | 
			
		||||
                Route::post('/addMutilple', [TimekeepingController::class, 'addWorkingTimeForMultipleUser'])->middleware('check.permission:admin.hr');
 | 
			
		||||
                Route::post('/addNote', [TimekeepingController::class, 'addNoteForUser'])->middleware('check.permission:admin.hr');
 | 
			
		||||
                Route::post('/update-working-days', [TimekeepingController::class, 'saveWorkingDays'])->middleware('check.permission:admin.hr');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,49 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Models;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
			
		||||
use Illuminate\Database\Eloquent\Model;
 | 
			
		||||
use Illuminate\Support\Facades\DB;
 | 
			
		||||
 | 
			
		||||
class Notes extends Model
 | 
			
		||||
{
 | 
			
		||||
    use HasFactory;
 | 
			
		||||
 | 
			
		||||
    protected $fillable = [
 | 
			
		||||
        'n_user_id', 'n_day', 'n_month', 'n_year', 'n_time_type', 'n_reason', 'n_note',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Lấy thông tin bảng notes dựa vào tháng và năm.
 | 
			
		||||
     *
 | 
			
		||||
     * @param int $month
 | 
			
		||||
     * @param int $year
 | 
			
		||||
     * @return \Illuminate\Database\Eloquent\Collection
 | 
			
		||||
     */
 | 
			
		||||
    public static function getNotesByMonthAndYear($month, $year)
 | 
			
		||||
    {
 | 
			
		||||
        return self::leftJoin("categories as reason", function ($join) {
 | 
			
		||||
            $join->on('n_reason', '=', 'reason.c_code');
 | 
			
		||||
            $join->on('reason.c_type', DB::raw("CONCAT('REASON')"));
 | 
			
		||||
        })
 | 
			
		||||
            ->leftJoin("categories as timeTypes", function ($join) {
 | 
			
		||||
                $join->on('n_time_type', '=', 'timeTypes.c_code');
 | 
			
		||||
                $join->on('timeTypes.c_type', DB::raw("CONCAT('TIME_TYPE')"));
 | 
			
		||||
            })
 | 
			
		||||
            ->where('n_month', $month)
 | 
			
		||||
            ->where('n_year', $year)
 | 
			
		||||
            ->select(
 | 
			
		||||
                'n_user_id',
 | 
			
		||||
                'n_day',
 | 
			
		||||
                'n_month',
 | 
			
		||||
                'n_year',
 | 
			
		||||
                'n_time_type',
 | 
			
		||||
                'n_reason',
 | 
			
		||||
                'n_note',
 | 
			
		||||
                'reason.c_name as reason_name',
 | 
			
		||||
                'timeTypes.c_name as time_type_name'
 | 
			
		||||
            )
 | 
			
		||||
            ->get();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
namespace App\Traits;
 | 
			
		||||
 | 
			
		||||
use App\Helper\Cache\CurrentMonthTimekeeping;
 | 
			
		||||
use App\Models\Notes;
 | 
			
		||||
use Carbon\Carbon;
 | 
			
		||||
use Illuminate\Http\JsonResponse;
 | 
			
		||||
use Illuminate\Support\Facades\DB;
 | 
			
		||||
| 
						 | 
				
			
			@ -32,44 +33,72 @@ trait AnalyzeData
 | 
			
		|||
        $endOfMonth = $now->endOfMonth()->endOfDay()->toDateTimeString();
 | 
			
		||||
        $admins = Admin::all();
 | 
			
		||||
        $history = DB::table('tracking')->select('*')->whereBetween('tracking.created_at', [$startOfMonth, $endOfMonth])->orderBy('tracking.created_at', 'asc')->get();
 | 
			
		||||
        $dataNotes = Notes::getNotesByMonthAndYear((int)$month, (int)$year);
 | 
			
		||||
 | 
			
		||||
        $history = collect($history);
 | 
			
		||||
        $result = [];
 | 
			
		||||
        foreach ($admins as $key => $admin) {
 | 
			
		||||
            if ($key == 0) {
 | 
			
		||||
                $user_data = [];
 | 
			
		||||
                for ($i = 1; $i <= $daysInMonth; $i++) {
 | 
			
		||||
                    // Tạo ngày cụ thể trong tháng
 | 
			
		||||
                    $date = Carbon::create($now->year, $now->month, $i)->setTimezone(env('TIME_ZONE'))->format('Y-m-d');
 | 
			
		||||
                    // Kiểm tra xem có mục nào trong $history có created_at trùng với $date
 | 
			
		||||
                    $hasEntry = $history->filter(function ($entry) use ($date, $admin) {
 | 
			
		||||
                        // echo($hasEntry);
 | 
			
		||||
                        return Carbon::parse($entry->created_at)->setTimezone(env('TIME_ZONE'))->format('Y-m-d') === $date && $entry->user_id == $admin->id;
 | 
			
		||||
                    });
 | 
			
		||||
            $user_data = [];
 | 
			
		||||
            for ($i = 1; $i <= $daysInMonth; $i++) {
 | 
			
		||||
                // Tạo ngày cụ thể trong tháng
 | 
			
		||||
                $date = Carbon::create($now->year, $now->month, $i)->setTimezone(env('TIME_ZONE'))->format('Y-m-d');
 | 
			
		||||
                // Kiểm tra xem có mục nào trong $history có created_at trùng với $date
 | 
			
		||||
                $hasEntry = $history->filter(function ($entry) use ($date, $admin) {
 | 
			
		||||
                    // echo($hasEntry);
 | 
			
		||||
                    // dd($date,$admin,$history,$daysInMonth);
 | 
			
		||||
                    if (count($hasEntry) > 0) {
 | 
			
		||||
                        $values = array_values($hasEntry->toArray());
 | 
			
		||||
                        $last_checkin = null;
 | 
			
		||||
                        $total = 0;
 | 
			
		||||
                        foreach ($values as $value) {
 | 
			
		||||
                            $createdAt = Carbon::parse($value->created_at)->setTimezone(env('TIME_ZONE'));
 | 
			
		||||
                            if ($value->status == 'check out' && $last_checkin != null) {
 | 
			
		||||
                                $lastCheckInTime = Carbon::parse($last_checkin)->setTimezone(env('TIME_ZONE'));
 | 
			
		||||
                                // Tính thời gian làm việc bằng hiệu của thời gian check out và check in
 | 
			
		||||
                                $workingTime = $createdAt->diffInSeconds($lastCheckInTime);
 | 
			
		||||
                                $total += $workingTime;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            if ($value->status == 'check in') {
 | 
			
		||||
                                $last_checkin = $createdAt;
 | 
			
		||||
                            }
 | 
			
		||||
                    return Carbon::parse($entry->created_at)->setTimezone(env('TIME_ZONE'))->format('Y-m-d') === $date && $entry->user_id == $admin->id;
 | 
			
		||||
                });
 | 
			
		||||
                $hasNotes = $dataNotes->filter(function ($entry) use ($i, $admin) {
 | 
			
		||||
                    return $entry->n_user_id == $admin->id && $entry->n_day == $i;
 | 
			
		||||
                });
 | 
			
		||||
                // dd($date,$admin,$history,$daysInMonth);
 | 
			
		||||
                if (count($hasEntry) > 0) {
 | 
			
		||||
                    $values = array_values($hasEntry->toArray());
 | 
			
		||||
                    $last_checkin = null;
 | 
			
		||||
                    $total = 0;
 | 
			
		||||
                    foreach ($values as $value) {
 | 
			
		||||
                        $createdAt = Carbon::parse($value->created_at)->setTimezone(env('TIME_ZONE'));
 | 
			
		||||
                        if ($value->status == 'check out' && $last_checkin != null) {
 | 
			
		||||
                            $lastCheckInTime = Carbon::parse($last_checkin)->setTimezone(env('TIME_ZONE'));
 | 
			
		||||
                            // Tính thời gian làm việc bằng hiệu của thời gian check out và check in
 | 
			
		||||
                            $workingTime = $createdAt->diffInSeconds($lastCheckInTime);
 | 
			
		||||
                            $total += $workingTime;
 | 
			
		||||
                        }
 | 
			
		||||
                        $user_data[] = ['values' => array_values($hasEntry->toArray()), 'total' => $total, 'day' => $i];
 | 
			
		||||
 | 
			
		||||
                        if ($value->status == 'check in') {
 | 
			
		||||
                            $last_checkin = $createdAt;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    $notes = [];
 | 
			
		||||
                    if (count($hasNotes) > 0) {
 | 
			
		||||
                        foreach ($hasNotes as $k_Note => $value_Note) {
 | 
			
		||||
                            $notes[$k_Note] = [
 | 
			
		||||
                                'timeType' => $value_Note->n_time_type,
 | 
			
		||||
                                'timeTypeName' => $value_Note->time_type_name,
 | 
			
		||||
                                'reason' => $value_Note->n_reason,
 | 
			
		||||
                                'reasonName' => $value_Note->reason_name,
 | 
			
		||||
                                'note' => $value_Note->n_note
 | 
			
		||||
                            ];
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    $user_data[] = ['values' => array_values($hasEntry->toArray()), 'notes' => array_values($notes), 'total' => $total, 'day' => $i];
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (count($hasNotes) > 0) {
 | 
			
		||||
                        $notes = [];
 | 
			
		||||
                        foreach ($hasNotes as $k_Note => $value_Note) {
 | 
			
		||||
                            $notes[$k_Note] = [
 | 
			
		||||
                                'timeType' => $value_Note->n_time_type,
 | 
			
		||||
                                'timeTypeName' => $value_Note->time_type_name,
 | 
			
		||||
                                'reason' => $value_Note->n_reason,
 | 
			
		||||
                                'reasonName' => $value_Note->reason_name,
 | 
			
		||||
                                'note' => $value_Note->n_note
 | 
			
		||||
                            ];
 | 
			
		||||
                        }
 | 
			
		||||
                        $user_data[] = ['values' => [], 'notes' => array_values($notes), 'total' => 0, 'day' => $i];
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $result[] = ['user' => $admin, 'history' => $user_data];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $result[] = ['user' => $admin, 'history' => $user_data];
 | 
			
		||||
        }
 | 
			
		||||
        return $result;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Migrations\Migration;
 | 
			
		||||
use Illuminate\Database\Schema\Blueprint;
 | 
			
		||||
use Illuminate\Support\Facades\Schema;
 | 
			
		||||
 | 
			
		||||
return new class extends Migration
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Run the migrations.
 | 
			
		||||
     */
 | 
			
		||||
    public function up(): void
 | 
			
		||||
    {
 | 
			
		||||
        Schema::create('notes', function (Blueprint $table) {
 | 
			
		||||
            $table->id();
 | 
			
		||||
            $table->string('n_user_id');
 | 
			
		||||
            $table->integer('n_day');
 | 
			
		||||
            $table->integer('n_month');
 | 
			
		||||
            $table->integer('n_year');
 | 
			
		||||
            $table->string('n_time_type');
 | 
			
		||||
            $table->string('n_reason');
 | 
			
		||||
            $table->text('n_note');
 | 
			
		||||
 | 
			
		||||
            $table->timestamps();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reverse the migrations.
 | 
			
		||||
     */
 | 
			
		||||
    public function down(): void
 | 
			
		||||
    {
 | 
			
		||||
        Schema::dropIfExists('notes');
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -67,4 +67,8 @@ export const getAllUserWorklogs = API_URL + 'v1/admin/jira/worklogs'
 | 
			
		|||
//Timekeeping
 | 
			
		||||
export const getTheTimesheet = API_URL + 'v1/admin/timekeeping'
 | 
			
		||||
export const updateMultipleUserWorkingTime = API_URL + 'v1/admin/timekeeping/addMutilple'
 | 
			
		||||
export const updateWorkingDays = API_URL + 'v1/admin/timekeeping/update-working-days'
 | 
			
		||||
export const updateNote = API_URL + 'v1/admin/timekeeping/addNote'
 | 
			
		||||
export const updateWorkingDays = API_URL + 'v1/admin/timekeeping/update-working-days'
 | 
			
		||||
 | 
			
		||||
//Category
 | 
			
		||||
export const getListMaster = API_URL + 'v1/admin/category/get-list-master'
 | 
			
		||||
| 
						 | 
				
			
			@ -55,3 +55,8 @@
 | 
			
		|||
  display: flex;
 | 
			
		||||
  justify-content: end;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.historyRow td {
 | 
			
		||||
  padding-top: 5px;
 | 
			
		||||
  padding-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,8 @@
 | 
			
		|||
import {
 | 
			
		||||
  getListMaster,
 | 
			
		||||
  getTheTimesheet,
 | 
			
		||||
  updateMultipleUserWorkingTime,
 | 
			
		||||
  updateNote,
 | 
			
		||||
  updateWorkingDays,
 | 
			
		||||
} from '@/api/Admin'
 | 
			
		||||
import { update } from '@/rtk/helpers/CRUD'
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +17,7 @@ import {
 | 
			
		|||
  Select,
 | 
			
		||||
  Table,
 | 
			
		||||
  Text,
 | 
			
		||||
  Textarea,
 | 
			
		||||
  TextInput,
 | 
			
		||||
  Tooltip,
 | 
			
		||||
} from '@mantine/core'
 | 
			
		||||
| 
						 | 
				
			
			@ -39,6 +42,18 @@ interface User {
 | 
			
		|||
  updated_at: string | null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface DataReason {
 | 
			
		||||
  id: number
 | 
			
		||||
  c_code: string
 | 
			
		||||
  c_name: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface DataTimeType {
 | 
			
		||||
  id: number
 | 
			
		||||
  c_code: string
 | 
			
		||||
  c_name: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface HistoryValue {
 | 
			
		||||
  id: number
 | 
			
		||||
  name: string
 | 
			
		||||
| 
						 | 
				
			
			@ -50,10 +65,19 @@ interface HistoryValue {
 | 
			
		|||
  updated_at: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface NoteValue {
 | 
			
		||||
  note: string
 | 
			
		||||
  reason: string
 | 
			
		||||
  timeType: string
 | 
			
		||||
  reasonName: string
 | 
			
		||||
  timeTypeName: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface History {
 | 
			
		||||
  values: HistoryValue[]
 | 
			
		||||
  total: number
 | 
			
		||||
  day: number
 | 
			
		||||
  notes: NoteValue[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface UserData {
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +86,8 @@ interface UserData {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
const Timekeeping = () => {
 | 
			
		||||
  const [opened, { open, close }] = useDisclosure(false)
 | 
			
		||||
  const [opened1, { open: open1, close: close1 }] = useDisclosure(false)
 | 
			
		||||
  const [opened2, { open: open2, close: close2 }] = useDisclosure(false)
 | 
			
		||||
  const [disableBtn, setDisableBtn] = useState(false)
 | 
			
		||||
  const [daysInMonth, setDaysInMonth] = useState(
 | 
			
		||||
    Array.from({ length: 31 }, (_, index) => index + 1),
 | 
			
		||||
| 
						 | 
				
			
			@ -76,31 +101,67 @@ const Timekeeping = () => {
 | 
			
		|||
    type: '',
 | 
			
		||||
    day: 0,
 | 
			
		||||
  })
 | 
			
		||||
  const [workingDays, setWorkingDays] = useState(30)
 | 
			
		||||
  const [isOpen, setIsOpen] = useState('')
 | 
			
		||||
  const popoverRef = useRef<HTMLElement>(null)
 | 
			
		||||
 | 
			
		||||
  const [selectedOption1, setSelectedOption1] = useState('')
 | 
			
		||||
  const [selectedOption2, setSelectedOption2] = useState('')
 | 
			
		||||
  const [textValue, setTextValue] = useState('')
 | 
			
		||||
 | 
			
		||||
  const handleSave = () => {
 | 
			
		||||
    // Send data to API
 | 
			
		||||
    const data = {
 | 
			
		||||
      option1: selectedOption1,
 | 
			
		||||
      option2: selectedOption2,
 | 
			
		||||
      text: textValue,
 | 
			
		||||
  const [customAddNotes, setCustomAddNotes] = useState<{
 | 
			
		||||
    user: {
 | 
			
		||||
      id: number
 | 
			
		||||
      name: string
 | 
			
		||||
    }
 | 
			
		||||
    console.log(data) // Replace with actual API call
 | 
			
		||||
    setIsOpen(false)
 | 
			
		||||
  }
 | 
			
		||||
    type: string
 | 
			
		||||
    reason: string
 | 
			
		||||
    note: string
 | 
			
		||||
    day: number
 | 
			
		||||
  }>({
 | 
			
		||||
    user: {
 | 
			
		||||
      id: 0,
 | 
			
		||||
      name: '',
 | 
			
		||||
    },
 | 
			
		||||
    type: '',
 | 
			
		||||
    reason: '',
 | 
			
		||||
    note: '',
 | 
			
		||||
    day: 0,
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const [workingDays, setWorkingDays] = useState(30)
 | 
			
		||||
  const [data, setData] = useState<UserData[]>([])
 | 
			
		||||
  const [date, setDate] = useState({
 | 
			
		||||
    month: (new Date().getMonth() + 1).toString(),
 | 
			
		||||
    year: new Date().getFullYear().toString(),
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const [dataTimeType, setDataTimeType] = useState<DataTimeType[]>([])
 | 
			
		||||
  const [dataReason, setDataReason] = useState<DataReason[]>([])
 | 
			
		||||
 | 
			
		||||
  const getListMasterByType = async (type: string) => {
 | 
			
		||||
    try {
 | 
			
		||||
      const params = {
 | 
			
		||||
        type: type,
 | 
			
		||||
      }
 | 
			
		||||
      const res = await get(getListMaster, params)
 | 
			
		||||
      if (res.status) {
 | 
			
		||||
        return res.data
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error: any) {
 | 
			
		||||
      notifications.show({
 | 
			
		||||
        title: 'Error',
 | 
			
		||||
        message: error.message ?? error,
 | 
			
		||||
        color: 'red',
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    return []
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const fetchData = async () => {
 | 
			
		||||
      const resultTimeType = await getListMasterByType('TIME_TYPE')
 | 
			
		||||
      setDataTimeType(resultTimeType)
 | 
			
		||||
 | 
			
		||||
      const resultReason = await getListMasterByType('REASON')
 | 
			
		||||
      setDataReason(resultReason)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fetchData()
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  const getTimeSheet = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      const res = await get(getTheTimesheet, {
 | 
			
		||||
| 
						 | 
				
			
			@ -173,6 +234,37 @@ const Timekeeping = () => {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const updateInfoNote = async (
 | 
			
		||||
    users: {
 | 
			
		||||
      id: number
 | 
			
		||||
      name: string
 | 
			
		||||
    },
 | 
			
		||||
    day: number,
 | 
			
		||||
    type: string,
 | 
			
		||||
    reason: string,
 | 
			
		||||
    note: string,
 | 
			
		||||
  ) => {
 | 
			
		||||
    try {
 | 
			
		||||
      await update(
 | 
			
		||||
        updateNote,
 | 
			
		||||
        {
 | 
			
		||||
          month: date.month,
 | 
			
		||||
          year: date.year,
 | 
			
		||||
          users: users,
 | 
			
		||||
          type: type,
 | 
			
		||||
          reason: reason,
 | 
			
		||||
          note: note,
 | 
			
		||||
          day: day,
 | 
			
		||||
        },
 | 
			
		||||
        getTimeSheet,
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      setDisableBtn(false)
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.log(error)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleUpdateWorkingDays = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      await update(
 | 
			
		||||
| 
						 | 
				
			
			@ -193,49 +285,87 @@ const Timekeeping = () => {
 | 
			
		|||
    getTimeSheet()
 | 
			
		||||
  }, [date])
 | 
			
		||||
 | 
			
		||||
  // useEffect(() => {
 | 
			
		||||
  //   const handleClickOutside = (event: MouseEvent | React.MouseEvent) => {
 | 
			
		||||
  //     if (
 | 
			
		||||
  //       popoverRef.current &&
 | 
			
		||||
  //       !(popoverRef.current as HTMLElement).contains(event.target as Node)
 | 
			
		||||
  //     ) {
 | 
			
		||||
  //       setIsOpen('')
 | 
			
		||||
  //     }
 | 
			
		||||
  //   }
 | 
			
		||||
  const showTooltipNote = (user: UserData, d: number) => {
 | 
			
		||||
    return user.history
 | 
			
		||||
      .find((h) => h.day === d && h.notes && h.notes.length > 0)
 | 
			
		||||
      ?.notes.map((v, index) => {
 | 
			
		||||
        return (
 | 
			
		||||
          <p key={index}>
 | 
			
		||||
            - {v.reasonName} ({v.timeTypeName}): {v.note}
 | 
			
		||||
          </p>
 | 
			
		||||
        )
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
  const showTooltipAllNote = (user: UserData) => {
 | 
			
		||||
    return (
 | 
			
		||||
      <table style={{ borderCollapse: 'collapse', width: '100%' }}>
 | 
			
		||||
        <tbody>
 | 
			
		||||
          {user.history
 | 
			
		||||
            .filter((h) => h.notes && h.notes.length > 0)
 | 
			
		||||
            .map((h, index) => (
 | 
			
		||||
              <tr key={index} className={classes.historyRow}>
 | 
			
		||||
                <td
 | 
			
		||||
                  style={{
 | 
			
		||||
                    border: '0',
 | 
			
		||||
                    verticalAlign: 'top',
 | 
			
		||||
                    fontWeight: 'bold',
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  Day {h.day}:
 | 
			
		||||
                </td>
 | 
			
		||||
                <td style={{ border: '0', paddingLeft: '10px' }}>
 | 
			
		||||
                  {h.notes.map((v, noteIndex) => (
 | 
			
		||||
                    <p key={noteIndex} style={{ margin: '0' }}>
 | 
			
		||||
                      - {v.reasonName} ({v.timeTypeName}): {v.note}
 | 
			
		||||
                    </p>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            ))}
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //   document.addEventListener('click', handleClickOutside)
 | 
			
		||||
 | 
			
		||||
  //   return () => {
 | 
			
		||||
  //     document.removeEventListener('click', handleClickOutside)
 | 
			
		||||
  //   }
 | 
			
		||||
  // }, [popoverRef])
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const handleClickOutside = (event: React.MouseEvent) => {
 | 
			
		||||
      if (
 | 
			
		||||
        popoverRef.current &&
 | 
			
		||||
        !(popoverRef.current as HTMLElement).contains(event.target as Node)
 | 
			
		||||
      ) {
 | 
			
		||||
        setIsOpen('')
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('mousedown', handleClickOutside)
 | 
			
		||||
    document.addEventListener('scroll', handleClickOutside)
 | 
			
		||||
    document
 | 
			
		||||
      .getElementById('my-child-div')
 | 
			
		||||
      ?.addEventListener('scroll', handleClickOutside)
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      document.removeEventListener('mousedown', handleClickOutside)
 | 
			
		||||
      document.removeEventListener('scroll', handleClickOutside)
 | 
			
		||||
      document
 | 
			
		||||
        .getElementById('my-child-div')
 | 
			
		||||
        ?.removeEventListener('scroll', handleClickOutside)
 | 
			
		||||
    }
 | 
			
		||||
  }, [popoverRef])
 | 
			
		||||
 | 
			
		||||
  // console.log(daysInMonth, 'daysInMonth')
 | 
			
		||||
  const showTooltip = (user: UserData, total: number, d: number) => {
 | 
			
		||||
    return (
 | 
			
		||||
      <div key={d}>
 | 
			
		||||
        {`Total: ${(total / 60 / 60).toFixed(1)}h`}
 | 
			
		||||
        {user.history
 | 
			
		||||
          .find((h) => h.day === d)
 | 
			
		||||
          ?.values.map((v) => {
 | 
			
		||||
            return (
 | 
			
		||||
              <Box
 | 
			
		||||
                style={{
 | 
			
		||||
                  display: 'flex',
 | 
			
		||||
                  alignItems: 'center',
 | 
			
		||||
                  justifyContent: 'space-between',
 | 
			
		||||
                }}
 | 
			
		||||
                key={v.id}
 | 
			
		||||
              >
 | 
			
		||||
                <p>{v.status + ': ' + v.time_string}</p>{' '}
 | 
			
		||||
                {v.image && (
 | 
			
		||||
                  <Image
 | 
			
		||||
                    w={100}
 | 
			
		||||
                    h={100}
 | 
			
		||||
                    src={
 | 
			
		||||
                      import.meta.env.VITE_BACKEND_URL.includes('local')
 | 
			
		||||
                        ? import.meta.env.VITE_BACKEND_URL +
 | 
			
		||||
                          'storage/' +
 | 
			
		||||
                          v.image
 | 
			
		||||
                        : import.meta.env.VITE_BACKEND_URL +
 | 
			
		||||
                          'image/storage/' +
 | 
			
		||||
                          v.image
 | 
			
		||||
                    }
 | 
			
		||||
                  />
 | 
			
		||||
                )}
 | 
			
		||||
              </Box>
 | 
			
		||||
            )
 | 
			
		||||
          })}
 | 
			
		||||
        {showTooltipNote(user, d)}
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <div className={classes.title}>
 | 
			
		||||
| 
						 | 
				
			
			@ -245,8 +375,8 @@ const Timekeeping = () => {
 | 
			
		|||
        </h3>
 | 
			
		||||
      </div>
 | 
			
		||||
      <Drawer
 | 
			
		||||
        opened={opened}
 | 
			
		||||
        onClose={close}
 | 
			
		||||
        opened={opened1}
 | 
			
		||||
        onClose={close1}
 | 
			
		||||
        position="right"
 | 
			
		||||
        title={<strong>Add custom worklog</strong>}
 | 
			
		||||
      >
 | 
			
		||||
| 
						 | 
				
			
			@ -299,6 +429,85 @@ const Timekeeping = () => {
 | 
			
		|||
          Submit
 | 
			
		||||
        </Button>
 | 
			
		||||
      </Drawer>
 | 
			
		||||
 | 
			
		||||
      {/* Form Save Note */}
 | 
			
		||||
      <Drawer
 | 
			
		||||
        opened={opened2}
 | 
			
		||||
        onClose={close2}
 | 
			
		||||
        position="right"
 | 
			
		||||
        title={<strong>Save Note</strong>}
 | 
			
		||||
      >
 | 
			
		||||
        <p>
 | 
			
		||||
          <span style={{ fontWeight: 'bold' }}>User</span>:{' '}
 | 
			
		||||
          {customAddNotes.user.name}
 | 
			
		||||
          <span style={{ paddingLeft: '10px', paddingRight: '10px' }}>|</span>
 | 
			
		||||
          <span style={{ fontWeight: 'bold' }}>Day</span>: {customAddNotes.day}
 | 
			
		||||
        </p>
 | 
			
		||||
 | 
			
		||||
        <Select
 | 
			
		||||
          mb={'md'}
 | 
			
		||||
          searchable
 | 
			
		||||
          label="Reason"
 | 
			
		||||
          data={dataReason.map((user) => ({
 | 
			
		||||
            value: user.c_code.toString(),
 | 
			
		||||
            label: user.c_name,
 | 
			
		||||
          }))}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            setCustomAddNotes({ ...customAddNotes, reason: e! })
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        {customAddNotes.reason != '' && (
 | 
			
		||||
          <Select
 | 
			
		||||
            mb={'md'}
 | 
			
		||||
            label="Time Type"
 | 
			
		||||
            data={dataTimeType.map((item) => {
 | 
			
		||||
              return { value: item.c_code.toString(), label: item.c_name }
 | 
			
		||||
            })}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              setCustomAddNotes({ ...customAddNotes, type: e! })
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
        {customAddNotes.type != '' && (
 | 
			
		||||
          <Textarea
 | 
			
		||||
            mb={'md'}
 | 
			
		||||
            label="Note"
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              setCustomAddNotes({ ...customAddNotes, note: e.target.value })
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
        <Button
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            setDisableBtn(true)
 | 
			
		||||
            if (
 | 
			
		||||
              customAddNotes.type === '' ||
 | 
			
		||||
              customAddNotes.reason === '' ||
 | 
			
		||||
              customAddNotes.note === '' ||
 | 
			
		||||
              customAddNotes.day === 0 ||
 | 
			
		||||
              customAddNotes.user.id === 0
 | 
			
		||||
            ) {
 | 
			
		||||
              notifications.show({
 | 
			
		||||
                title: 'Error',
 | 
			
		||||
                message: 'Input data required',
 | 
			
		||||
                color: 'red',
 | 
			
		||||
              })
 | 
			
		||||
              setDisableBtn(false)
 | 
			
		||||
            } else {
 | 
			
		||||
              updateInfoNote(
 | 
			
		||||
                customAddNotes.user,
 | 
			
		||||
                customAddNotes.day,
 | 
			
		||||
                customAddNotes.type,
 | 
			
		||||
                customAddNotes.reason,
 | 
			
		||||
                customAddNotes.note,
 | 
			
		||||
              )
 | 
			
		||||
            }
 | 
			
		||||
          }}
 | 
			
		||||
          disabled={disableBtn}
 | 
			
		||||
        >
 | 
			
		||||
          Save
 | 
			
		||||
        </Button>
 | 
			
		||||
      </Drawer>
 | 
			
		||||
      <Box display={'flex'}>
 | 
			
		||||
        <Box style={{ display: 'flex', flexFlow: 'column' }} w={'30%'}>
 | 
			
		||||
          <Box w="100%" display={'flex'}>
 | 
			
		||||
| 
						 | 
				
			
			@ -493,7 +702,7 @@ const Timekeeping = () => {
 | 
			
		|||
                        <Text
 | 
			
		||||
                          size="sm"
 | 
			
		||||
                          onClick={() => {
 | 
			
		||||
                            open()
 | 
			
		||||
                            open1()
 | 
			
		||||
                            setCustomAddData({ ...customAddData, day: d })
 | 
			
		||||
                          }}
 | 
			
		||||
                        >
 | 
			
		||||
| 
						 | 
				
			
			@ -539,22 +748,11 @@ const Timekeeping = () => {
 | 
			
		|||
                <Table.Tr key={user.user.id} className={classes.tableTr}>
 | 
			
		||||
                  <Table.Td>
 | 
			
		||||
                    <Tooltip
 | 
			
		||||
                      // position={'auto'}
 | 
			
		||||
                      multiline
 | 
			
		||||
                      label={
 | 
			
		||||
                        <div>
 | 
			
		||||
                          <p style={{ fontWeight: 'bold' }}>Day 1:</p>
 | 
			
		||||
                          <p style={{ paddingLeft: '10px' }}>
 | 
			
		||||
                            - Work For Home (Buổi Sáng): Bị bể bánh xe
 | 
			
		||||
                          </p>
 | 
			
		||||
                          <p style={{ paddingLeft: '10px' }}>- Nghỉ phép (Buổi Chiều): Bị cảm</p>
 | 
			
		||||
 | 
			
		||||
                          <p style={{ fontWeight: 'bold' }}>Day 2:</p>
 | 
			
		||||
                          <p style={{ paddingLeft: '10px' }}>
 | 
			
		||||
                            - Work For Home (Buổi Sáng): Bị bể bánh xe
 | 
			
		||||
                          </p>
 | 
			
		||||
                          <p style={{ paddingLeft: '10px' }}>- Nghỉ phép (Buổi Chiều): Bị cảm</p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      }
 | 
			
		||||
                      // opened
 | 
			
		||||
                      // offset={{ mainAxis: 5, crossAxis: 0 }}
 | 
			
		||||
                      label={showTooltipAllNote(user)}
 | 
			
		||||
                    >
 | 
			
		||||
                      <div>{user.user.name}</div>
 | 
			
		||||
                    </Tooltip>
 | 
			
		||||
| 
						 | 
				
			
			@ -577,52 +775,13 @@ const Timekeeping = () => {
 | 
			
		|||
                        }
 | 
			
		||||
                      >
 | 
			
		||||
                        {total / 60 / 60 < 7 &&
 | 
			
		||||
                        user.history.find((h) => h.day === d) ? (
 | 
			
		||||
                        user.history.find(
 | 
			
		||||
                          (h) => h.day === d && h.values && h.values.length > 0,
 | 
			
		||||
                        ) ? (
 | 
			
		||||
                          total / 60 / 60 >= 3.5 ? (
 | 
			
		||||
                            <Tooltip
 | 
			
		||||
                              multiline
 | 
			
		||||
                              label={
 | 
			
		||||
                                <div key={d}>
 | 
			
		||||
                                  {`Total: ${(total / 60 / 60).toFixed(1)}h`}
 | 
			
		||||
                                  {user.history
 | 
			
		||||
                                    .find((h) => h.day === d)
 | 
			
		||||
                                    ?.values.map((v) => {
 | 
			
		||||
                                      return (
 | 
			
		||||
                                        <Box
 | 
			
		||||
                                          style={{
 | 
			
		||||
                                            display: 'flex',
 | 
			
		||||
                                            alignItems: 'center',
 | 
			
		||||
                                            justifyContent: 'space-between',
 | 
			
		||||
                                          }}
 | 
			
		||||
                                          key={v.id}
 | 
			
		||||
                                        >
 | 
			
		||||
                                          <p>
 | 
			
		||||
                                            {v.status + ': ' + v.time_string}
 | 
			
		||||
                                          </p>{' '}
 | 
			
		||||
                                          {v.image && (
 | 
			
		||||
                                            <Image
 | 
			
		||||
                                              w={100}
 | 
			
		||||
                                              h={100}
 | 
			
		||||
                                              src={
 | 
			
		||||
                                                import.meta.env.VITE_BACKEND_URL.includes(
 | 
			
		||||
                                                  'local',
 | 
			
		||||
                                                )
 | 
			
		||||
                                                  ? import.meta.env
 | 
			
		||||
                                                      .VITE_BACKEND_URL +
 | 
			
		||||
                                                    'storage/' +
 | 
			
		||||
                                                    v.image
 | 
			
		||||
                                                  : import.meta.env
 | 
			
		||||
                                                      .VITE_BACKEND_URL +
 | 
			
		||||
                                                    'image/storage/' +
 | 
			
		||||
                                                    v.image
 | 
			
		||||
                                              }
 | 
			
		||||
                                            />
 | 
			
		||||
                                          )}
 | 
			
		||||
                                        </Box>
 | 
			
		||||
                                      )
 | 
			
		||||
                                    })}
 | 
			
		||||
                                </div>
 | 
			
		||||
                              }
 | 
			
		||||
                              label={showTooltip(user, total, d)}
 | 
			
		||||
                            >
 | 
			
		||||
                              <IconCheck
 | 
			
		||||
                                size={20}
 | 
			
		||||
| 
						 | 
				
			
			@ -633,53 +792,23 @@ const Timekeeping = () => {
 | 
			
		|||
                                  padding: '2px',
 | 
			
		||||
                                  fontWeight: 700,
 | 
			
		||||
                                }}
 | 
			
		||||
                                onClick={() => {
 | 
			
		||||
                                  open2()
 | 
			
		||||
                                  setCustomAddNotes({
 | 
			
		||||
                                    ...customAddNotes,
 | 
			
		||||
                                    day: d,
 | 
			
		||||
                                    user: {
 | 
			
		||||
                                      id: user.user.id,
 | 
			
		||||
                                      name: user.user.name,
 | 
			
		||||
                                    },
 | 
			
		||||
                                  })
 | 
			
		||||
                                }}
 | 
			
		||||
                              />
 | 
			
		||||
                            </Tooltip>
 | 
			
		||||
                          ) : (
 | 
			
		||||
                            <Tooltip
 | 
			
		||||
                              multiline
 | 
			
		||||
                              label={
 | 
			
		||||
                                <div>
 | 
			
		||||
                                  {`Total: ${(total / 60 / 60).toFixed(1)}h`}
 | 
			
		||||
                                  {user.history
 | 
			
		||||
                                    .find((h) => h.day === d)
 | 
			
		||||
                                    ?.values.map((v) => {
 | 
			
		||||
                                      return (
 | 
			
		||||
                                        <Box
 | 
			
		||||
                                          style={{
 | 
			
		||||
                                            display: 'flex',
 | 
			
		||||
                                            alignItems: 'center',
 | 
			
		||||
                                            justifyContent: 'space-between',
 | 
			
		||||
                                          }}
 | 
			
		||||
                                          key={v.id}
 | 
			
		||||
                                        >
 | 
			
		||||
                                          <p>
 | 
			
		||||
                                            {v.status + ': ' + v.time_string}
 | 
			
		||||
                                          </p>{' '}
 | 
			
		||||
                                          {v.image && (
 | 
			
		||||
                                            <Image
 | 
			
		||||
                                              w={100}
 | 
			
		||||
                                              h={100}
 | 
			
		||||
                                              src={
 | 
			
		||||
                                                import.meta.env.VITE_BACKEND_URL.includes(
 | 
			
		||||
                                                  'local',
 | 
			
		||||
                                                )
 | 
			
		||||
                                                  ? import.meta.env
 | 
			
		||||
                                                      .VITE_BACKEND_URL +
 | 
			
		||||
                                                    'storage/' +
 | 
			
		||||
                                                    v.image
 | 
			
		||||
                                                  : import.meta.env
 | 
			
		||||
                                                      .VITE_BACKEND_URL +
 | 
			
		||||
                                                    'image/storage/' +
 | 
			
		||||
                                                    v.image
 | 
			
		||||
                                              }
 | 
			
		||||
                                            />
 | 
			
		||||
                                          )}
 | 
			
		||||
                                        </Box>
 | 
			
		||||
                                      )
 | 
			
		||||
                                    })}
 | 
			
		||||
                                </div>
 | 
			
		||||
                              }
 | 
			
		||||
                              label={showTooltip(user, total, d)}
 | 
			
		||||
                            >
 | 
			
		||||
                              <IconExclamationMark
 | 
			
		||||
                                size={20}
 | 
			
		||||
| 
						 | 
				
			
			@ -690,6 +819,17 @@ const Timekeeping = () => {
 | 
			
		|||
                                  padding: '2px',
 | 
			
		||||
                                  fontWeight: 700,
 | 
			
		||||
                                }}
 | 
			
		||||
                                onClick={() => {
 | 
			
		||||
                                  open2()
 | 
			
		||||
                                  setCustomAddNotes({
 | 
			
		||||
                                    ...customAddNotes,
 | 
			
		||||
                                    day: d,
 | 
			
		||||
                                    user: {
 | 
			
		||||
                                      id: user.user.id,
 | 
			
		||||
                                      name: user.user.name,
 | 
			
		||||
                                    },
 | 
			
		||||
                                  })
 | 
			
		||||
                                }}
 | 
			
		||||
                              />
 | 
			
		||||
                            </Tooltip>
 | 
			
		||||
                          )
 | 
			
		||||
| 
						 | 
				
			
			@ -697,48 +837,7 @@ const Timekeeping = () => {
 | 
			
		|||
                          <>
 | 
			
		||||
                            <Tooltip
 | 
			
		||||
                              multiline
 | 
			
		||||
                              label={
 | 
			
		||||
                                <div>
 | 
			
		||||
                                  {`Total: ${(total / 60 / 60).toFixed(1)}h`}
 | 
			
		||||
                                  {user.history
 | 
			
		||||
                                    .find((h) => h.day === d)
 | 
			
		||||
                                    ?.values.map((v) => {
 | 
			
		||||
                                      return (
 | 
			
		||||
                                        <Box
 | 
			
		||||
                                          style={{
 | 
			
		||||
                                            display: 'flex',
 | 
			
		||||
                                            alignItems: 'center',
 | 
			
		||||
                                            justifyContent: 'space-between',
 | 
			
		||||
                                          }}
 | 
			
		||||
                                          key={v.id}
 | 
			
		||||
                                        >
 | 
			
		||||
                                          <p>
 | 
			
		||||
                                            {v.status + ': ' + v.time_string}
 | 
			
		||||
                                          </p>{' '}
 | 
			
		||||
                                          {v.image && (
 | 
			
		||||
                                            <Image
 | 
			
		||||
                                              w={100}
 | 
			
		||||
                                              h={100}
 | 
			
		||||
                                              src={
 | 
			
		||||
                                                import.meta.env.VITE_BACKEND_URL.includes(
 | 
			
		||||
                                                  'local',
 | 
			
		||||
                                                )
 | 
			
		||||
                                                  ? import.meta.env
 | 
			
		||||
                                                      .VITE_BACKEND_URL +
 | 
			
		||||
                                                    'storage/' +
 | 
			
		||||
                                                    v.image
 | 
			
		||||
                                                  : import.meta.env
 | 
			
		||||
                                                      .VITE_BACKEND_URL +
 | 
			
		||||
                                                    'image/storage/' +
 | 
			
		||||
                                                    v.image
 | 
			
		||||
                                              }
 | 
			
		||||
                                            />
 | 
			
		||||
                                          )}
 | 
			
		||||
                                        </Box>
 | 
			
		||||
                                      )
 | 
			
		||||
                                    })}
 | 
			
		||||
                                </div>
 | 
			
		||||
                              }
 | 
			
		||||
                              label={showTooltip(user, total, d)}
 | 
			
		||||
                            >
 | 
			
		||||
                              <IconCheck
 | 
			
		||||
                                size={20}
 | 
			
		||||
| 
						 | 
				
			
			@ -748,155 +847,53 @@ const Timekeeping = () => {
 | 
			
		|||
                                  borderRadius: '5px',
 | 
			
		||||
                                  padding: '2px',
 | 
			
		||||
                                }}
 | 
			
		||||
                                id={`indexBySN${user.user.id}_${d}`}
 | 
			
		||||
                                onClick={() => {
 | 
			
		||||
                                  setIsOpen(
 | 
			
		||||
                                    isOpen == user.user.email + d
 | 
			
		||||
                                      ? ''
 | 
			
		||||
                                      : user.user.email + d,
 | 
			
		||||
                                  )
 | 
			
		||||
                                  open2()
 | 
			
		||||
                                  setCustomAddNotes({
 | 
			
		||||
                                    ...customAddNotes,
 | 
			
		||||
                                    day: d,
 | 
			
		||||
                                    user: {
 | 
			
		||||
                                      id: user.user.id,
 | 
			
		||||
                                      name: user.user.name,
 | 
			
		||||
                                    },
 | 
			
		||||
                                  })
 | 
			
		||||
                                }}
 | 
			
		||||
                              />
 | 
			
		||||
                            </Tooltip>
 | 
			
		||||
 | 
			
		||||
                            <Popover
 | 
			
		||||
                              placement="bottom"
 | 
			
		||||
                              isOpen={isOpen === user.user.email + d}
 | 
			
		||||
                              target={`indexBySN${user.user.id}_${d}`}
 | 
			
		||||
                              toggle={() =>
 | 
			
		||||
                                setIsOpen(
 | 
			
		||||
                                  isOpen == user.user.email + d
 | 
			
		||||
                                    ? ''
 | 
			
		||||
                                    : user.user.email + d,
 | 
			
		||||
                                )
 | 
			
		||||
                              }
 | 
			
		||||
                              innerRef={popoverRef}
 | 
			
		||||
                            >
 | 
			
		||||
                              <PopoverBody
 | 
			
		||||
                                style={{
 | 
			
		||||
                                  width: '300px',
 | 
			
		||||
                                  backgroundColor: 'white',
 | 
			
		||||
                                  boxShadow:
 | 
			
		||||
                                    '0 5px 15px rgba(30, 32, 37, 0.12)',
 | 
			
		||||
                                }}
 | 
			
		||||
                              >
 | 
			
		||||
                                <div>
 | 
			
		||||
                                  <div>
 | 
			
		||||
                                    <label htmlFor="option1">
 | 
			
		||||
                                      Select Option 1:
 | 
			
		||||
                                    </label>
 | 
			
		||||
                                    <select
 | 
			
		||||
                                      id="option1"
 | 
			
		||||
                                      value={selectedOption1}
 | 
			
		||||
                                      onChange={(e) =>
 | 
			
		||||
                                        setSelectedOption1(e.target.value)
 | 
			
		||||
                                      }
 | 
			
		||||
                                      className="form-select"
 | 
			
		||||
                                    >
 | 
			
		||||
                                      <option value="">Select...</option>
 | 
			
		||||
                                      <option value="option1">Option 1</option>
 | 
			
		||||
                                      <option value="option2">Option 2</option>
 | 
			
		||||
                                    </select>
 | 
			
		||||
                                  </div>
 | 
			
		||||
                                  {selectedOption1 && (
 | 
			
		||||
                                    <div className="mt-2">
 | 
			
		||||
                                      <label htmlFor="option2">
 | 
			
		||||
                                        Select Option 2:
 | 
			
		||||
                                      </label>
 | 
			
		||||
                                      <select
 | 
			
		||||
                                        id="option2"
 | 
			
		||||
                                        value={selectedOption2}
 | 
			
		||||
                                        onChange={(e) =>
 | 
			
		||||
                                          setSelectedOption2(e.target.value)
 | 
			
		||||
                                        }
 | 
			
		||||
                                        className="form-select"
 | 
			
		||||
                                      >
 | 
			
		||||
                                        <option value="">Select...</option>
 | 
			
		||||
                                        <option value="optionA">
 | 
			
		||||
                                          Option A
 | 
			
		||||
                                        </option>
 | 
			
		||||
                                        <option value="optionB">
 | 
			
		||||
                                          Option B
 | 
			
		||||
                                        </option>
 | 
			
		||||
                                      </select>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                  )}
 | 
			
		||||
                                  {selectedOption2 && (
 | 
			
		||||
                                    <div className="mt-2">
 | 
			
		||||
                                      <label htmlFor="textInput">
 | 
			
		||||
                                        Enter Text:
 | 
			
		||||
                                      </label>
 | 
			
		||||
                                      <textarea
 | 
			
		||||
                                        id="textInput"
 | 
			
		||||
                                        value={textValue}
 | 
			
		||||
                                        onChange={(e) =>
 | 
			
		||||
                                          setTextValue(e.target.value)
 | 
			
		||||
                                        }
 | 
			
		||||
                                        className="form-control"
 | 
			
		||||
                                      />
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                  )}
 | 
			
		||||
 | 
			
		||||
                                  <MultiSelect
 | 
			
		||||
                                    mb={'md'}
 | 
			
		||||
                                    searchable
 | 
			
		||||
                                    label="User(s)"
 | 
			
		||||
                                    data={data.map((user) => {
 | 
			
		||||
                                      return {
 | 
			
		||||
                                        value: user.user.id.toString(),
 | 
			
		||||
                                        label: user.user.name,
 | 
			
		||||
                                      }
 | 
			
		||||
                                    })}
 | 
			
		||||
                                    onChange={(e) => {
 | 
			
		||||
                                      setCustomAddData({
 | 
			
		||||
                                        ...customAddData,
 | 
			
		||||
                                        data: e,
 | 
			
		||||
                                      })
 | 
			
		||||
                                    }}
 | 
			
		||||
                                  />
 | 
			
		||||
                                  <Select
 | 
			
		||||
                                    mb={'md'}
 | 
			
		||||
                                    label="Type"
 | 
			
		||||
                                    data={[
 | 
			
		||||
                                      { value: 'half', label: 'Half day' },
 | 
			
		||||
                                      { value: 'one', label: 'A day' },
 | 
			
		||||
                                    ]}
 | 
			
		||||
                                    onChange={(e) => {
 | 
			
		||||
                                      setCustomAddData({
 | 
			
		||||
                                        ...customAddData,
 | 
			
		||||
                                        type: e!,
 | 
			
		||||
                                      })
 | 
			
		||||
                                    }}
 | 
			
		||||
                                  />
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div className={classes.popoverFooter}>
 | 
			
		||||
                                  <Button
 | 
			
		||||
                                    variant="filled"
 | 
			
		||||
                                    color="red"
 | 
			
		||||
                                    onClick={() => setIsOpen('')}
 | 
			
		||||
                                  >
 | 
			
		||||
                                    Close
 | 
			
		||||
                                  </Button>
 | 
			
		||||
                                  <Button variant="filled" onClick={handleSave}>
 | 
			
		||||
                                    Save
 | 
			
		||||
                                  </Button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                              </PopoverBody>
 | 
			
		||||
                            </Popover>
 | 
			
		||||
                          </>
 | 
			
		||||
                        ) : (
 | 
			
		||||
                          <>
 | 
			
		||||
                            <Tooltip
 | 
			
		||||
                              multiline
 | 
			
		||||
                              label={
 | 
			
		||||
                                <div key={d}>
 | 
			
		||||
                                  <p>
 | 
			
		||||
                                    - Work For Home (Buổi Sáng): Bị bể bánh xe
 | 
			
		||||
                                  </p>
 | 
			
		||||
                                  <p>- Nghỉ phép (Buổi Chiều): Bị cảm</p>
 | 
			
		||||
                                </div>
 | 
			
		||||
                              }
 | 
			
		||||
                            >
 | 
			
		||||
                            {user.history.find(
 | 
			
		||||
                              (h) =>
 | 
			
		||||
                                h.day === d && h.notes && h.notes.length > 0,
 | 
			
		||||
                            ) ? (
 | 
			
		||||
                              <Tooltip
 | 
			
		||||
                                multiline
 | 
			
		||||
                                label={showTooltipNote(user, d)}
 | 
			
		||||
                              >
 | 
			
		||||
                                <IconX
 | 
			
		||||
                                  size={20}
 | 
			
		||||
                                  style={{
 | 
			
		||||
                                    backgroundColor: '#ff4646',
 | 
			
		||||
                                    color: 'white',
 | 
			
		||||
                                    borderRadius: '5px',
 | 
			
		||||
                                    padding: '2px',
 | 
			
		||||
                                  }}
 | 
			
		||||
                                  id={`indexBySN${user.user.id}_${d}`}
 | 
			
		||||
                                  onClick={() => {
 | 
			
		||||
                                    open2()
 | 
			
		||||
                                    setCustomAddNotes({
 | 
			
		||||
                                      ...customAddNotes,
 | 
			
		||||
                                      day: d,
 | 
			
		||||
                                      user: {
 | 
			
		||||
                                        id: user.user.id,
 | 
			
		||||
                                        name: user.user.name,
 | 
			
		||||
                                      },
 | 
			
		||||
                                    })
 | 
			
		||||
                                  }}
 | 
			
		||||
                                />
 | 
			
		||||
                              </Tooltip>
 | 
			
		||||
                            ) : (
 | 
			
		||||
                              <IconX
 | 
			
		||||
                                size={20}
 | 
			
		||||
                                style={{
 | 
			
		||||
| 
						 | 
				
			
			@ -907,140 +904,18 @@ const Timekeeping = () => {
 | 
			
		|||
                                }}
 | 
			
		||||
                                id={`indexBySN${user.user.id}_${d}`}
 | 
			
		||||
                                onClick={() => {
 | 
			
		||||
                                  setIsOpen(
 | 
			
		||||
                                    isOpen == user.user.email + d
 | 
			
		||||
                                      ? ''
 | 
			
		||||
                                      : user.user.email + d,
 | 
			
		||||
                                  )
 | 
			
		||||
                                  open2()
 | 
			
		||||
                                  setCustomAddNotes({
 | 
			
		||||
                                    ...customAddNotes,
 | 
			
		||||
                                    day: d,
 | 
			
		||||
                                    user: {
 | 
			
		||||
                                      id: user.user.id,
 | 
			
		||||
                                      name: user.user.name,
 | 
			
		||||
                                    },
 | 
			
		||||
                                  })
 | 
			
		||||
                                }}
 | 
			
		||||
                              />
 | 
			
		||||
                            </Tooltip>
 | 
			
		||||
 | 
			
		||||
                            <Popover
 | 
			
		||||
                              placement="bottom"
 | 
			
		||||
                              isOpen={isOpen === user.user.email + d}
 | 
			
		||||
                              target={`indexBySN${user.user.id}_${d}`}
 | 
			
		||||
                              toggle={() =>
 | 
			
		||||
                                setIsOpen(
 | 
			
		||||
                                  isOpen == user.user.email + d
 | 
			
		||||
                                    ? ''
 | 
			
		||||
                                    : user.user.email + d,
 | 
			
		||||
                                )
 | 
			
		||||
                              }
 | 
			
		||||
                              innerRef={popoverRef}
 | 
			
		||||
                            >
 | 
			
		||||
                              <PopoverBody
 | 
			
		||||
                                style={{
 | 
			
		||||
                                  width: '300px',
 | 
			
		||||
                                  backgroundColor: 'white',
 | 
			
		||||
                                  boxShadow:
 | 
			
		||||
                                    '0 5px 15px rgba(30, 32, 37, 0.12)',
 | 
			
		||||
                                  padding: '20px',
 | 
			
		||||
                                }}
 | 
			
		||||
                              >
 | 
			
		||||
                                <div>
 | 
			
		||||
                                  <div>
 | 
			
		||||
                                    <label htmlFor="option1">
 | 
			
		||||
                                      Select Option 1:
 | 
			
		||||
                                    </label>
 | 
			
		||||
                                    <select
 | 
			
		||||
                                      id="option1"
 | 
			
		||||
                                      value={selectedOption1}
 | 
			
		||||
                                      onChange={(e) =>
 | 
			
		||||
                                        setSelectedOption1(e.target.value)
 | 
			
		||||
                                      }
 | 
			
		||||
                                      className="form-select"
 | 
			
		||||
                                    >
 | 
			
		||||
                                      <option value="">Select...</option>
 | 
			
		||||
                                      <option value="option1">Option 1</option>
 | 
			
		||||
                                      <option value="option2">Option 2</option>
 | 
			
		||||
                                    </select>
 | 
			
		||||
                                  </div>
 | 
			
		||||
                                  {selectedOption1 && (
 | 
			
		||||
                                    <div className="mt-2">
 | 
			
		||||
                                      <label htmlFor="option2">
 | 
			
		||||
                                        Select Option 2:
 | 
			
		||||
                                      </label>
 | 
			
		||||
                                      <select
 | 
			
		||||
                                        id="option2"
 | 
			
		||||
                                        value={selectedOption2}
 | 
			
		||||
                                        onChange={(e) =>
 | 
			
		||||
                                          setSelectedOption2(e.target.value)
 | 
			
		||||
                                        }
 | 
			
		||||
                                        className="form-select"
 | 
			
		||||
                                      >
 | 
			
		||||
                                        <option value="">Select...</option>
 | 
			
		||||
                                        <option value="optionA">
 | 
			
		||||
                                          Option A
 | 
			
		||||
                                        </option>
 | 
			
		||||
                                        <option value="optionB">
 | 
			
		||||
                                          Option B
 | 
			
		||||
                                        </option>
 | 
			
		||||
                                      </select>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                  )}
 | 
			
		||||
                                  {selectedOption2 && (
 | 
			
		||||
                                    <div className="mt-2">
 | 
			
		||||
                                      <label htmlFor="textInput">
 | 
			
		||||
                                        Enter Text:
 | 
			
		||||
                                      </label>
 | 
			
		||||
                                      <textarea
 | 
			
		||||
                                        id="textInput"
 | 
			
		||||
                                        value={textValue}
 | 
			
		||||
                                        onChange={(e) =>
 | 
			
		||||
                                          setTextValue(e.target.value)
 | 
			
		||||
                                        }
 | 
			
		||||
                                        className="form-control"
 | 
			
		||||
                                      />
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                  )}
 | 
			
		||||
 | 
			
		||||
                                  <MultiSelect
 | 
			
		||||
                                    mb={'md'}
 | 
			
		||||
                                    searchable
 | 
			
		||||
                                    label="User(s)"
 | 
			
		||||
                                    data={data.map((user) => {
 | 
			
		||||
                                      return {
 | 
			
		||||
                                        value: user.user.id.toString(),
 | 
			
		||||
                                        label: user.user.name,
 | 
			
		||||
                                      }
 | 
			
		||||
                                    })}
 | 
			
		||||
                                    onChange={(e) => {
 | 
			
		||||
                                      setCustomAddData({
 | 
			
		||||
                                        ...customAddData,
 | 
			
		||||
                                        data: e,
 | 
			
		||||
                                      })
 | 
			
		||||
                                    }}
 | 
			
		||||
                                  />
 | 
			
		||||
                                  <Select
 | 
			
		||||
                                    mb={'md'}
 | 
			
		||||
                                    label="Type"
 | 
			
		||||
                                    data={[
 | 
			
		||||
                                      { value: 'half', label: 'Half day' },
 | 
			
		||||
                                      { value: 'one', label: 'A day' },
 | 
			
		||||
                                    ]}
 | 
			
		||||
                                    onChange={(e) => {
 | 
			
		||||
                                      setCustomAddData({
 | 
			
		||||
                                        ...customAddData,
 | 
			
		||||
                                        type: e!,
 | 
			
		||||
                                      })
 | 
			
		||||
                                    }}
 | 
			
		||||
                                  />
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div className={classes.popoverFooter}>
 | 
			
		||||
                                  <Button
 | 
			
		||||
                                    variant="filled"
 | 
			
		||||
                                    color="red"
 | 
			
		||||
                                    onClick={() => setIsOpen('')}
 | 
			
		||||
                                  >
 | 
			
		||||
                                    Close
 | 
			
		||||
                                  </Button>
 | 
			
		||||
                                  <Button variant="filled" onClick={handleSave}>
 | 
			
		||||
                                    Save
 | 
			
		||||
                                  </Button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                              </PopoverBody>
 | 
			
		||||
                            </Popover>
 | 
			
		||||
                            )}
 | 
			
		||||
                          </>
 | 
			
		||||
                        )}
 | 
			
		||||
                      </Table.Td>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue