This commit is contained in:
JOSEPH LE 2024-06-15 11:37:34 +07:00
commit e0839a4ea8
4 changed files with 141 additions and 53 deletions

View File

@ -43,10 +43,12 @@ class TimekeepingController extends Controller
$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; return Carbon::parse($entry->created_at)->setTimezone(env('TIME_ZONE'))->format('Y-m-d') === $date && $entry->user_id == $admin->id;
}); });
// echo($hasEntry);
if (count($hasEntry) > 0) {
if (count($hasEntry) > 0) {
$values = array_values($hasEntry->toArray()); $values = array_values($hasEntry->toArray());
$last_checkin = null; $last_checkin = null;
$total = 0; $total = 0;
@ -71,4 +73,37 @@ class TimekeepingController extends Controller
} }
return response()->json(['status'=> true, 'data'=>$result]); return response()->json(['status'=> true, 'data'=>$result]);
} }
public function addWorkingTimeForMultipleUser(Request $request)
{
$user_ids = $request->users;
$year = $request->year;
$month = $request->month;
$day = $request->day;
$type = $request->type;
foreach($user_ids as $id){
$user = Admin::find($id);
$date = Carbon::create($year, $month, $day)->setTimezone(env('TIME_ZONE'));
$start = $date->copy()->setTime(7, 31, 11);
$end = $type == 'half' ? $date->copy()->setTime(11, 31, 11) : $date->copy()->setTime(17, 1, 11);
Tracking::insert([
[
'name' => $user->name,
'user_id' => $user->id,
'status' => 'check in',
'time_string' => $start ->format('Y-m-d H:i:s'),
'created_at' => $start->setTimezone('UTC')
],
[
'name' => $user->name,
'user_id' => $user->id,
'status' => 'check out',
'time_string' => $end->format('Y-m-d H:i:s'),
'created_at' => $end->setTimezone('UTC')
]
]);
}
return response()->json(['status'=> true, 'message'=> 'Add successfully']);
}
} }

View File

@ -100,18 +100,19 @@ Route::middleware('api')
Route::get('/all-project', [JiraController::class, 'getAllProject']); Route::get('/all-project', [JiraController::class, 'getAllProject']);
Route::get('/all-issue-by-project', [JiraController::class, 'fetchIssuesByProject']); Route::get('/all-issue-by-project', [JiraController::class, 'fetchIssuesByProject']);
Route::get('/worklogs', [JiraController::class, 'getAllUserWorkLogs'])->middleware('check.permission:admin'); Route::get('/worklogs', [JiraController::class, 'getAllUserWorkLogs'])->middleware('check.permission:admin');
}); });
Route::group([ Route::group([
'prefix' => 'timekeeping', 'prefix' => 'timekeeping',
], 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::group([
'prefix' => 'tracking', Route::group([
], function () { 'prefix' => 'tracking',
Route::post('/create', [TrackingController::class, 'create'])->middleware('check.permission:admin.hr'); ], function () {
Route::post('/create', [TrackingController::class, 'create'])->middleware('check.permission:admin.hr');
Route::post('/update', [TrackingController::class, 'update'])->middleware('check.permission:admin.hr'); Route::post('/update', [TrackingController::class, 'update'])->middleware('check.permission:admin.hr');
Route::get('/delete', [TrackingController::class, 'delete'])->middleware('check.permission:admin.hr'); Route::get('/delete', [TrackingController::class, 'delete'])->middleware('check.permission:admin.hr');
}); });

View File

@ -65,4 +65,5 @@ export const getAllIssuesByProject = API_URL + 'v1/admin/jira/all-issue-by-proje
export const getAllUserWorklogs = API_URL + 'v1/admin/jira/worklogs' 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'

View File

@ -1,11 +1,12 @@
import { getTheTimesheet } from '@/api/Admin' import { getTheTimesheet, updateMultipleUserWorkingTime } from '@/api/Admin'
import { get } from '@/rtk/helpers/apiService' import { get } from '@/rtk/helpers/apiService'
import { Box, Image, Select, Table, Text, Tooltip } from '@mantine/core' import { Box, Image, Menu, Select, Table, Text, TextInput, Tooltip } from '@mantine/core'
import { IconCheck, IconExclamationMark, IconX } from '@tabler/icons-react' import { IconCheck, IconExclamationMark, IconX } from '@tabler/icons-react'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import classes from './Timekeeping.module.css' import classes from './Timekeeping.module.css'
import { notifications } from '@mantine/notifications' import { notifications } from '@mantine/notifications'
import moment from 'moment' import moment from 'moment'
import { update } from '@/rtk/helpers/CRUD'
interface User { interface User {
id: number id: number
@ -44,6 +45,7 @@ const Timekeeping = () => {
const [daysInMonth, setDaysInMonth] = useState( const [daysInMonth, setDaysInMonth] = useState(
Array.from({ length: 31 }, (_, index) => index + 1), Array.from({ length: 31 }, (_, index) => index + 1),
) )
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(),
@ -59,12 +61,14 @@ const Timekeeping = () => {
if (res.status) { if (res.status) {
setData( setData(
res.data.filter((u: UserData) => res.data.filter((u: UserData) =>
!u.user.email.includes('admin'), u.user.permission.includes('staff'),
), ),
) )
setDaysInMonth( setDaysInMonth(
Array.from({ length: getDaysInMonth() }, (_, index) => index + 1), Array.from({ length: getDaysInMonth() }, (_, index) => index + 1),
) )
localStorage.getItem('workingdays') ? setWorkingDays(parseFloat(localStorage.getItem('workingdays')!)) : setWorkingDays(getDaysInMonth())
} }
} catch (error: any) { } catch (error: any) {
console.log(error) console.log(error)
@ -98,6 +102,20 @@ const Timekeeping = () => {
return days.getDate() return days.getDate()
} }
const updateMultipleUser = async(users:number[], day:number, type:string)=>{
try {
await update(updateMultipleUserWorkingTime, {
users: users,
year: date.year,
month: date.month,
day:day,
type: type
}, getTimeSheet)
} catch (error) {
console.log(error)
}
}
useEffect(() => { useEffect(() => {
getTimeSheet() getTimeSheet()
}, [date]) }, [date])
@ -111,34 +129,42 @@ const Timekeeping = () => {
</h3> </h3>
</div> </div>
<Box display={'flex'}> <Box display={'flex'}>
<Box w="50%" display={'flex'}> <Box style={{display:"flex", flexFlow:"column"}} w={'30%'}>
<Select <Box w="100%" display={'flex'}>
w="10%" <Select
value={date.month} w="50%"
size="xs" value={date.month}
label="Month" size="xs"
data={Array.from({ length: 12 }, (_, index) => label="Month"
(1 + index).toString(), data={Array.from({ length: 12 }, (_, index) =>
)} (1 + index).toString(),
onChange={(e) => { )}
setDate({ ...date, month: e! }) onChange={(e) => {
}} setDate({ ...date, month: e! })
></Select> }}
<Select ></Select>
w="10%" <Select
value={date.year} w="50%"
size="xs" value={date.year}
ml={'sm'} size="xs"
label="Year" ml={'sm'}
data={Array.from({ length: 20 }, (_, index) => label="Year"
(2023 + 1 + index).toString(), data={Array.from({ length: 20 }, (_, index) =>
)} (2023 + 1 + index).toString(),
onChange={(e) => { )}
setDate({ ...date, year: e! }) onChange={(e) => {
}} setDate({ ...date, year: e! })
></Select> }}
></Select>
</Box>
<Box>
<TextInput type='number' size='xs' label="Working days" w={"20%"} value={workingDays} onChange={(e)=>{
setWorkingDays(parseFloat(e.target.value))
localStorage.setItem('workingdays', e.target.value)
}}/>
</Box>
</Box> </Box>
<Box w="50%" style={{display:'flex', alignItems:'center', justifyContent:"space-evenly"}}> <Box w="70%" pl={200} style={{display:'flex', alignItems:'end', justifyContent:"space-evenly"}}>
<Box style={{display:'flex', alignItems:'center'}}> <Box style={{display:'flex', alignItems:'center'}}>
<IconCheck <IconCheck
size={20} size={20}
@ -204,19 +230,38 @@ const Timekeeping = () => {
<Table.Thead> <Table.Thead>
<Table.Tr bg={'#228be66b'}> <Table.Tr bg={'#228be66b'}>
<Table.Th>Day</Table.Th> <Table.Th>Day</Table.Th>
<Table.Th></Table.Th>
<Table.Th></Table.Th>
{daysInMonth.map((d) => { {daysInMonth.map((d) => {
return ( return (
<Table.Th key={d} ta={'center'}> <Menu width={200} shadow="md">
{d} <Menu.Target>
</Table.Th> <Table.Th key={d} ta={'center'} style={{cursor:"pointer"}}>
<span>{d}</span>
</Table.Th>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item onClick={()=>updateMultipleUser(data.map((u)=>u.user.id), d, 'half')}>
+ Add half a day's work
</Menu.Item>
<Menu.Item
onClick={()=>updateMultipleUser(data.map((u)=>u.user.id), d, 'one')}
>
+ Add 1 day of work
</Menu.Item>
</Menu.Dropdown>
</Menu>
) )
})} })}
</Table.Tr> </Table.Tr>
<Table.Tr bg={'#228be66b'}> <Table.Tr bg={'#228be66b'}>
<Table.Th></Table.Th> <Table.Th></Table.Th>
<Table.Th ta={'center'}>Total</Table.Th>
<Table.Th ta={'center'}>Off</Table.Th>
{daysInMonth.map((d) => { {daysInMonth.map((d) => {
return ( return (
<Table.Th key={d} ta={'center'}> <Table.Th key={d} ta={'center'} bg={(getDayName(`${date.year}-${date.month}-${d}`)=== "Su" || getDayName(`${date.year}-${date.month}-${d}`) === "Sa") ? 'rgb(251 255 196 / 78%)' : ''}>
{getDayName(`${date.year}-${date.month}-${d}`)} {getDayName(`${date.year}-${date.month}-${d}`)}
</Table.Th> </Table.Th>
) )
@ -225,16 +270,19 @@ const Timekeeping = () => {
</Table.Thead> </Table.Thead>
<Table.Tbody> <Table.Tbody>
{data.map((user) => { {data.map((user) => {
let totalDays = user.history.filter((h) => h.total / 60 / 60 >= 7).length + (user.history.filter((h) => h.total / 60 / 60 < 7 && h.total / 60 / 60 >= 3.5).length / 2)
return ( return (
<Table.Tr key={user.user.id} className={classes.tableTr}> <Table.Tr key={user.user.id} className={classes.tableTr}>
<Table.Td>{user.user.name}</Table.Td> <Table.Td>{user.user.name}</Table.Td>
<Table.Td ta={'center'}>{totalDays}</Table.Td>
<Table.Td ta={'center'}>{workingDays-totalDays}</Table.Td>
{daysInMonth.map((d) => { {daysInMonth.map((d) => {
var total = var total =
user.history.find((h) => h.day === d)?.total ?? 0 user.history.find((h) => h.day === d)?.total ?? 0
return ( return (
<Table.Td key={d} ta={'center'}> <Table.Td key={d} ta={'center'} bg={(getDayName(`${date.year}-${date.month}-${d}`)=== "Su" || getDayName(`${date.year}-${date.month}-${d}`) === "Sa") ? 'rgb(251 255 196 / 78%)' : ''}>
{total / 60 / 60 < 7 && user.history.find((h) => h.day === d) ? {total / 60 / 60 < 7 && user.history.find((h) => h.day === d) ?
total / 60 / 60 >= 3.5 ? total / 60 / 60 >= 3.5 ?
<Tooltip <Tooltip
multiline multiline
label={ label={
@ -250,6 +298,7 @@ const Timekeeping = () => {
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
key={v.id}
> >
<p>{v.status + ': ' + v.time_string}</p>{' '} <p>{v.status + ': ' + v.time_string}</p>{' '}
{v.image && ( {v.image && (
@ -291,7 +340,7 @@ const Timekeeping = () => {
: <Tooltip : <Tooltip
multiline multiline
label={ label={
<div key={d}> <div>
{`Total: ${(total / 60 / 60).toFixed(1)}h`} {`Total: ${(total / 60 / 60).toFixed(1)}h`}
{user.history {user.history
.find((h) => h.day === d) .find((h) => h.day === d)
@ -303,6 +352,7 @@ const Timekeeping = () => {
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
key={v.id}
> >
<p>{v.status + ': ' + v.time_string}</p>{' '} <p>{v.status + ': ' + v.time_string}</p>{' '}
{v.image && ( {v.image && (
@ -357,6 +407,7 @@ const Timekeeping = () => {
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
key={v.id}
> >
<p>{v.status + ': ' + v.time_string}</p>{' '} <p>{v.status + ': ' + v.time_string}</p>{' '}
{v.image && ( {v.image && (