Bổ sung note cho Timekepping

This commit is contained in:
Truong Vo 2024-08-05 17:02:29 +07:00
parent b203e8d82c
commit ae5737c3bf
8 changed files with 544 additions and 517 deletions

View File

@ -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']);
}
}

View File

@ -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');
});

View File

@ -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 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();
}
}

View File

@ -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;
}

View File

@ -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');
}
};

View File

@ -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'

View File

@ -55,3 +55,8 @@
display: flex;
justify-content: end;
}
.historyRow td {
padding-top: 5px;
padding-bottom: 5px;
}

View File

@ -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>