Merge branch 'master' of https://gitea.nswteam.net/joseph/ManagemetSystem
This commit is contained in:
commit
e0839a4ea8
|
|
@ -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']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
@ -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 && (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue