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\Helper\Cache\CurrentMonthTimekeeping;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Notes;
|
||||||
use App\Traits\AnalyzeData;
|
use App\Traits\AnalyzeData;
|
||||||
use App\Traits\HasFilterRequest;
|
use App\Traits\HasFilterRequest;
|
||||||
use App\Traits\HasOrderByRequest;
|
use App\Traits\HasOrderByRequest;
|
||||||
|
|
@ -23,7 +24,7 @@ class TimekeepingController extends Controller
|
||||||
use HasFilterRequest;
|
use HasFilterRequest;
|
||||||
use HasSearchRequest;
|
use HasSearchRequest;
|
||||||
use AnalyzeData;
|
use AnalyzeData;
|
||||||
|
|
||||||
public function get(Request $request)
|
public function get(Request $request)
|
||||||
{
|
{
|
||||||
$currentDate = Carbon::now();
|
$currentDate = Carbon::now();
|
||||||
|
|
@ -32,11 +33,9 @@ class TimekeepingController extends Controller
|
||||||
$data = MonthlyTimekeeping::where('month', '=', $request->month)->where('year', '=', $request->year)->first();
|
$data = MonthlyTimekeeping::where('month', '=', $request->month)->where('year', '=', $request->year)->first();
|
||||||
if ($currentMonth == (int)$request->month && $currentYear == (int)$request->year) {
|
if ($currentMonth == (int)$request->month && $currentYear == (int)$request->year) {
|
||||||
$cacheData = CurrentMonthTimekeeping::getCacheCurrentMonthTimekeeping();
|
$cacheData = CurrentMonthTimekeeping::getCacheCurrentMonthTimekeeping();
|
||||||
// $result = $this->analyzeCurrentMonthTimeKeepingData($currentMonth, $currentYear);
|
|
||||||
// dd($result);die;
|
|
||||||
if ($cacheData) {
|
if ($cacheData) {
|
||||||
$cacheData->data = json_decode($cacheData->data, true);
|
$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 {
|
} else {
|
||||||
$result = $this->analyzeCurrentMonthTimeKeepingData($currentMonth, $currentYear);
|
$result = $this->analyzeCurrentMonthTimeKeepingData($currentMonth, $currentYear);
|
||||||
if ($data) {
|
if ($data) {
|
||||||
|
|
@ -49,7 +48,7 @@ class TimekeepingController extends Controller
|
||||||
} else {
|
} else {
|
||||||
if ($data) {
|
if ($data) {
|
||||||
$data['data'] = json_decode($data['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 {
|
} else {
|
||||||
$result = $this->analyzeCurrentMonthTimeKeepingData($request->month, $request->year);
|
$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)]);
|
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)
|
public function saveWorkingDays(Request $request)
|
||||||
{
|
{
|
||||||
$data = MonthlyTimekeeping::where('month', '=', $request->month)->where('year', '=', $request->year)->first();
|
$data = MonthlyTimekeeping::where('month', '=', $request->month)->where('year', '=', $request->year)->first();
|
||||||
if($data){
|
if ($data) {
|
||||||
$data->update(['working_days'=>$request->working_days]);
|
$data->update(['working_days' => $request->working_days]);
|
||||||
$this->createOrUpdateRecordForCurrentMonth($request->month, $request->year);
|
$this->createOrUpdateRecordForCurrentMonth($request->month, $request->year);
|
||||||
return response()->json(['status' => true, 'message' => 'Update successful']);
|
return response()->json(['status' => true, 'message' => 'Update successful']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(['status' => false, 'message' => 'Update failed']);
|
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 () {
|
], function () {
|
||||||
Route::get('/', [TimekeepingController::class, 'get'])->middleware('check.permission:admin.hr.staff');
|
Route::get('/', [TimekeepingController::class, 'get'])->middleware('check.permission:admin.hr.staff');
|
||||||
Route::post('/addMutilple', [TimekeepingController::class, 'addWorkingTimeForMultipleUser'])->middleware('check.permission:admin.hr');
|
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');
|
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;
|
namespace App\Traits;
|
||||||
|
|
||||||
use App\Helper\Cache\CurrentMonthTimekeeping;
|
use App\Helper\Cache\CurrentMonthTimekeeping;
|
||||||
|
use App\Models\Notes;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
@ -32,44 +33,72 @@ trait AnalyzeData
|
||||||
$endOfMonth = $now->endOfMonth()->endOfDay()->toDateTimeString();
|
$endOfMonth = $now->endOfMonth()->endOfDay()->toDateTimeString();
|
||||||
$admins = Admin::all();
|
$admins = Admin::all();
|
||||||
$history = DB::table('tracking')->select('*')->whereBetween('tracking.created_at', [$startOfMonth, $endOfMonth])->orderBy('tracking.created_at', 'asc')->get();
|
$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);
|
$history = collect($history);
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach ($admins as $key => $admin) {
|
foreach ($admins as $key => $admin) {
|
||||||
if ($key == 0) {
|
$user_data = [];
|
||||||
$user_data = [];
|
for ($i = 1; $i <= $daysInMonth; $i++) {
|
||||||
for ($i = 1; $i <= $daysInMonth; $i++) {
|
// Tạo ngày cụ thể trong tháng
|
||||||
// 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');
|
||||||
$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
|
||||||
// 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) {
|
||||||
$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;
|
|
||||||
});
|
|
||||||
// echo($hasEntry);
|
// echo($hasEntry);
|
||||||
// dd($date,$admin,$history,$daysInMonth);
|
return Carbon::parse($entry->created_at)->setTimezone(env('TIME_ZONE'))->format('Y-m-d') === $date && $entry->user_id == $admin->id;
|
||||||
if (count($hasEntry) > 0) {
|
});
|
||||||
$values = array_values($hasEntry->toArray());
|
$hasNotes = $dataNotes->filter(function ($entry) use ($i, $admin) {
|
||||||
$last_checkin = null;
|
return $entry->n_user_id == $admin->id && $entry->n_day == $i;
|
||||||
$total = 0;
|
});
|
||||||
foreach ($values as $value) {
|
// dd($date,$admin,$history,$daysInMonth);
|
||||||
$createdAt = Carbon::parse($value->created_at)->setTimezone(env('TIME_ZONE'));
|
if (count($hasEntry) > 0) {
|
||||||
if ($value->status == 'check out' && $last_checkin != null) {
|
$values = array_values($hasEntry->toArray());
|
||||||
$lastCheckInTime = Carbon::parse($last_checkin)->setTimezone(env('TIME_ZONE'));
|
$last_checkin = null;
|
||||||
// Tính thời gian làm việc bằng hiệu của thời gian check out và check in
|
$total = 0;
|
||||||
$workingTime = $createdAt->diffInSeconds($lastCheckInTime);
|
foreach ($values as $value) {
|
||||||
$total += $workingTime;
|
$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'));
|
||||||
if ($value->status == 'check in') {
|
// Tính thời gian làm việc bằng hiệu của thời gian check out và check in
|
||||||
$last_checkin = $createdAt;
|
$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;
|
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
|
//Timekeeping
|
||||||
export const getTheTimesheet = API_URL + 'v1/admin/timekeeping'
|
export const getTheTimesheet = API_URL + 'v1/admin/timekeeping'
|
||||||
export const updateMultipleUserWorkingTime = API_URL + 'v1/admin/timekeeping/addMutilple'
|
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;
|
display: flex;
|
||||||
justify-content: end;
|
justify-content: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.historyRow td {
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import {
|
import {
|
||||||
|
getListMaster,
|
||||||
getTheTimesheet,
|
getTheTimesheet,
|
||||||
updateMultipleUserWorkingTime,
|
updateMultipleUserWorkingTime,
|
||||||
|
updateNote,
|
||||||
updateWorkingDays,
|
updateWorkingDays,
|
||||||
} from '@/api/Admin'
|
} from '@/api/Admin'
|
||||||
import { update } from '@/rtk/helpers/CRUD'
|
import { update } from '@/rtk/helpers/CRUD'
|
||||||
|
|
@ -15,6 +17,7 @@ import {
|
||||||
Select,
|
Select,
|
||||||
Table,
|
Table,
|
||||||
Text,
|
Text,
|
||||||
|
Textarea,
|
||||||
TextInput,
|
TextInput,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@mantine/core'
|
} from '@mantine/core'
|
||||||
|
|
@ -39,6 +42,18 @@ interface User {
|
||||||
updated_at: string | null
|
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 {
|
interface HistoryValue {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
|
|
@ -50,10 +65,19 @@ interface HistoryValue {
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NoteValue {
|
||||||
|
note: string
|
||||||
|
reason: string
|
||||||
|
timeType: string
|
||||||
|
reasonName: string
|
||||||
|
timeTypeName: string
|
||||||
|
}
|
||||||
|
|
||||||
interface History {
|
interface History {
|
||||||
values: HistoryValue[]
|
values: HistoryValue[]
|
||||||
total: number
|
total: number
|
||||||
day: number
|
day: number
|
||||||
|
notes: NoteValue[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserData {
|
interface UserData {
|
||||||
|
|
@ -62,7 +86,8 @@ interface UserData {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Timekeeping = () => {
|
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 [disableBtn, setDisableBtn] = useState(false)
|
||||||
const [daysInMonth, setDaysInMonth] = useState(
|
const [daysInMonth, setDaysInMonth] = useState(
|
||||||
Array.from({ length: 31 }, (_, index) => index + 1),
|
Array.from({ length: 31 }, (_, index) => index + 1),
|
||||||
|
|
@ -76,31 +101,67 @@ const Timekeeping = () => {
|
||||||
type: '',
|
type: '',
|
||||||
day: 0,
|
day: 0,
|
||||||
})
|
})
|
||||||
const [workingDays, setWorkingDays] = useState(30)
|
const [customAddNotes, setCustomAddNotes] = useState<{
|
||||||
const [isOpen, setIsOpen] = useState('')
|
user: {
|
||||||
const popoverRef = useRef<HTMLElement>(null)
|
id: number
|
||||||
|
name: string
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
console.log(data) // Replace with actual API call
|
type: string
|
||||||
setIsOpen(false)
|
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 [data, setData] = useState<UserData[]>([])
|
||||||
const [date, setDate] = useState({
|
const [date, setDate] = useState({
|
||||||
month: (new Date().getMonth() + 1).toString(),
|
month: (new Date().getMonth() + 1).toString(),
|
||||||
year: new Date().getFullYear().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 () => {
|
const getTimeSheet = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await get(getTheTimesheet, {
|
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 () => {
|
const handleUpdateWorkingDays = async () => {
|
||||||
try {
|
try {
|
||||||
await update(
|
await update(
|
||||||
|
|
@ -193,49 +285,87 @@ const Timekeeping = () => {
|
||||||
getTimeSheet()
|
getTimeSheet()
|
||||||
}, [date])
|
}, [date])
|
||||||
|
|
||||||
// useEffect(() => {
|
const showTooltipNote = (user: UserData, d: number) => {
|
||||||
// const handleClickOutside = (event: MouseEvent | React.MouseEvent) => {
|
return user.history
|
||||||
// if (
|
.find((h) => h.day === d && h.notes && h.notes.length > 0)
|
||||||
// popoverRef.current &&
|
?.notes.map((v, index) => {
|
||||||
// !(popoverRef.current as HTMLElement).contains(event.target as Node)
|
return (
|
||||||
// ) {
|
<p key={index}>
|
||||||
// setIsOpen('')
|
- {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)
|
const showTooltip = (user: UserData, total: number, d: number) => {
|
||||||
|
return (
|
||||||
// return () => {
|
<div key={d}>
|
||||||
// document.removeEventListener('click', handleClickOutside)
|
{`Total: ${(total / 60 / 60).toFixed(1)}h`}
|
||||||
// }
|
{user.history
|
||||||
// }, [popoverRef])
|
.find((h) => h.day === d)
|
||||||
|
?.values.map((v) => {
|
||||||
useEffect(() => {
|
return (
|
||||||
const handleClickOutside = (event: React.MouseEvent) => {
|
<Box
|
||||||
if (
|
style={{
|
||||||
popoverRef.current &&
|
display: 'flex',
|
||||||
!(popoverRef.current as HTMLElement).contains(event.target as Node)
|
alignItems: 'center',
|
||||||
) {
|
justifyContent: 'space-between',
|
||||||
setIsOpen('')
|
}}
|
||||||
}
|
key={v.id}
|
||||||
}
|
>
|
||||||
|
<p>{v.status + ': ' + v.time_string}</p>{' '}
|
||||||
document.addEventListener('mousedown', handleClickOutside)
|
{v.image && (
|
||||||
document.addEventListener('scroll', handleClickOutside)
|
<Image
|
||||||
document
|
w={100}
|
||||||
.getElementById('my-child-div')
|
h={100}
|
||||||
?.addEventListener('scroll', handleClickOutside)
|
src={
|
||||||
|
import.meta.env.VITE_BACKEND_URL.includes('local')
|
||||||
return () => {
|
? import.meta.env.VITE_BACKEND_URL +
|
||||||
document.removeEventListener('mousedown', handleClickOutside)
|
'storage/' +
|
||||||
document.removeEventListener('scroll', handleClickOutside)
|
v.image
|
||||||
document
|
: import.meta.env.VITE_BACKEND_URL +
|
||||||
.getElementById('my-child-div')
|
'image/storage/' +
|
||||||
?.removeEventListener('scroll', handleClickOutside)
|
v.image
|
||||||
}
|
}
|
||||||
}, [popoverRef])
|
/>
|
||||||
|
)}
|
||||||
// console.log(daysInMonth, 'daysInMonth')
|
</Box>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
{showTooltipNote(user, d)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={classes.title}>
|
<div className={classes.title}>
|
||||||
|
|
@ -245,8 +375,8 @@ const Timekeeping = () => {
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<Drawer
|
<Drawer
|
||||||
opened={opened}
|
opened={opened1}
|
||||||
onClose={close}
|
onClose={close1}
|
||||||
position="right"
|
position="right"
|
||||||
title={<strong>Add custom worklog</strong>}
|
title={<strong>Add custom worklog</strong>}
|
||||||
>
|
>
|
||||||
|
|
@ -299,6 +429,85 @@ const Timekeeping = () => {
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</Drawer>
|
</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 display={'flex'}>
|
||||||
<Box style={{ display: 'flex', flexFlow: 'column' }} w={'30%'}>
|
<Box style={{ display: 'flex', flexFlow: 'column' }} w={'30%'}>
|
||||||
<Box w="100%" display={'flex'}>
|
<Box w="100%" display={'flex'}>
|
||||||
|
|
@ -493,7 +702,7 @@ const Timekeeping = () => {
|
||||||
<Text
|
<Text
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
open()
|
open1()
|
||||||
setCustomAddData({ ...customAddData, day: d })
|
setCustomAddData({ ...customAddData, day: d })
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -539,22 +748,11 @@ const Timekeeping = () => {
|
||||||
<Table.Tr key={user.user.id} className={classes.tableTr}>
|
<Table.Tr key={user.user.id} className={classes.tableTr}>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
// position={'auto'}
|
||||||
multiline
|
multiline
|
||||||
label={
|
// opened
|
||||||
<div>
|
// offset={{ mainAxis: 5, crossAxis: 0 }}
|
||||||
<p style={{ fontWeight: 'bold' }}>Day 1:</p>
|
label={showTooltipAllNote(user)}
|
||||||
<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>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<div>{user.user.name}</div>
|
<div>{user.user.name}</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -577,52 +775,13 @@ const Timekeeping = () => {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{total / 60 / 60 < 7 &&
|
{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 ? (
|
total / 60 / 60 >= 3.5 ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
multiline
|
multiline
|
||||||
label={
|
label={showTooltip(user, total, d)}
|
||||||
<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>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<IconCheck
|
<IconCheck
|
||||||
size={20}
|
size={20}
|
||||||
|
|
@ -633,53 +792,23 @@ const Timekeeping = () => {
|
||||||
padding: '2px',
|
padding: '2px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
}}
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
open2()
|
||||||
|
setCustomAddNotes({
|
||||||
|
...customAddNotes,
|
||||||
|
day: d,
|
||||||
|
user: {
|
||||||
|
id: user.user.id,
|
||||||
|
name: user.user.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
multiline
|
multiline
|
||||||
label={
|
label={showTooltip(user, total, d)}
|
||||||
<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>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<IconExclamationMark
|
<IconExclamationMark
|
||||||
size={20}
|
size={20}
|
||||||
|
|
@ -690,6 +819,17 @@ const Timekeeping = () => {
|
||||||
padding: '2px',
|
padding: '2px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
}}
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
open2()
|
||||||
|
setCustomAddNotes({
|
||||||
|
...customAddNotes,
|
||||||
|
day: d,
|
||||||
|
user: {
|
||||||
|
id: user.user.id,
|
||||||
|
name: user.user.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
|
|
@ -697,48 +837,7 @@ const Timekeeping = () => {
|
||||||
<>
|
<>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
multiline
|
multiline
|
||||||
label={
|
label={showTooltip(user, total, d)}
|
||||||
<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>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<IconCheck
|
<IconCheck
|
||||||
size={20}
|
size={20}
|
||||||
|
|
@ -748,155 +847,53 @@ const Timekeeping = () => {
|
||||||
borderRadius: '5px',
|
borderRadius: '5px',
|
||||||
padding: '2px',
|
padding: '2px',
|
||||||
}}
|
}}
|
||||||
id={`indexBySN${user.user.id}_${d}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsOpen(
|
open2()
|
||||||
isOpen == user.user.email + d
|
setCustomAddNotes({
|
||||||
? ''
|
...customAddNotes,
|
||||||
: user.user.email + d,
|
day: d,
|
||||||
)
|
user: {
|
||||||
|
id: user.user.id,
|
||||||
|
name: user.user.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</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
|
{user.history.find(
|
||||||
multiline
|
(h) =>
|
||||||
label={
|
h.day === d && h.notes && h.notes.length > 0,
|
||||||
<div key={d}>
|
) ? (
|
||||||
<p>
|
<Tooltip
|
||||||
- Work For Home (Buổi Sáng): Bị bể bánh xe
|
multiline
|
||||||
</p>
|
label={showTooltipNote(user, d)}
|
||||||
<p>- Nghỉ phép (Buổi Chiều): Bị cảm</p>
|
>
|
||||||
</div>
|
<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
|
<IconX
|
||||||
size={20}
|
size={20}
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -907,140 +904,18 @@ const Timekeeping = () => {
|
||||||
}}
|
}}
|
||||||
id={`indexBySN${user.user.id}_${d}`}
|
id={`indexBySN${user.user.id}_${d}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsOpen(
|
open2()
|
||||||
isOpen == user.user.email + d
|
setCustomAddNotes({
|
||||||
? ''
|
...customAddNotes,
|
||||||
: user.user.email + d,
|
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>
|
</Table.Td>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue