update custom add worklog

This commit is contained in:
JOSEPH LE 2024-07-27 08:24:46 +07:00
parent e7e81dead7
commit 0ea97cb0c5
2 changed files with 305 additions and 142 deletions

View File

@ -86,7 +86,7 @@ class TimekeepingController extends Controller
]
]);
}
$this->createOrUpdateRecordForCurrentMonth($month, $year);
return response()->json(['status' => true, 'message' => 'Add successfully']);
}

View File

@ -1,12 +1,29 @@
import { getTheTimesheet, updateMultipleUserWorkingTime, updateWorkingDays } from '@/api/Admin'
import {
getTheTimesheet,
updateMultipleUserWorkingTime,
updateWorkingDays,
} from '@/api/Admin'
import { update } from '@/rtk/helpers/CRUD'
import { get } from '@/rtk/helpers/apiService'
import { Box, Button, Image, Menu, Select, Table, Text, TextInput, Tooltip } from '@mantine/core'
import {
Box,
Button,
Drawer,
Image,
Menu,
MultiSelect,
Select,
Table,
Text,
TextInput,
Tooltip
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { notifications } from '@mantine/notifications'
import { IconCheck, IconExclamationMark, IconX } from '@tabler/icons-react'
import moment from 'moment'
import { useEffect, useState } from 'react'
import classes from './Timekeeping.module.css'
import moment from 'moment'
interface User {
id: number
@ -42,9 +59,15 @@ interface UserData {
}
const Timekeeping = () => {
const [opened, { open, close }] = useDisclosure(false)
const [daysInMonth, setDaysInMonth] = useState(
Array.from({ length: 31 }, (_, index) => index + 1),
)
const [customAddData, setCustomAddData] = useState<{data:string[], type: string, day: number}>({
data: [],
type: '',
day: 0
})
const [workingDays, setWorkingDays] = useState(30)
const [data, setData] = useState<UserData[]>([])
const [date, setDate] = useState({
@ -60,9 +83,7 @@ const Timekeeping = () => {
})
if (res.status) {
setData(
res.data.filter((u: UserData) =>
u.user.permission.includes('staff'),
),
res.data.filter((u: UserData) => u.user.permission.includes('staff')),
)
setDaysInMonth(
Array.from({ length: getDaysInMonth() }, (_, index) => index + 1),
@ -102,27 +123,39 @@ const Timekeeping = () => {
return days.getDate()
}
const updateMultipleUser = async(users:number[], day:number, type:string)=>{
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)
await update(
updateMultipleUserWorkingTime,
{
users: users,
year: date.year,
month: date.month,
day: day,
type: type,
},
getTimeSheet,
)
} catch (error) {
console.log(error)
}
}
const handleUpdateWorkingDays = async()=>{
const handleUpdateWorkingDays = async () => {
try {
await update(updateWorkingDays, {
working_days: workingDays,
year: date.year,
month: date.month
}, getTimeSheet)
await update(
updateWorkingDays,
{
working_days: workingDays,
year: date.year,
month: date.month,
},
getTimeSheet,
)
} catch (error) {
console.log(error)
}
@ -132,6 +165,7 @@ const Timekeeping = () => {
getTimeSheet()
}, [date])
console.log(customAddData)
return (
<div>
<div className={classes.title}>
@ -140,24 +174,58 @@ const Timekeeping = () => {
Timekeeping
</h3>
</div>
<Drawer
opened={opened}
onClose={close}
position="right"
title={<strong>Add custom worklog</strong>}
>
<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!})}}
/>
<Button onClick={()=>{
if(customAddData.type === "" || customAddData.data.length === 0 || customAddData.day === 0){
notifications.show({
title: 'Error',
message: "Input data required",
color: 'red',
})
}else{
updateMultipleUser(customAddData.data.map((u)=>parseInt(u)), customAddData.day, customAddData.type)
}
}}>Submit</Button>
</Drawer>
<Box display={'flex'}>
<Box style={{display:"flex", flexFlow:"column"}} w={'30%'}>
<Box style={{ display: 'flex', flexFlow: 'column' }} w={'30%'}>
<Box w="100%" display={'flex'}>
<Select
w="50%"
value={date.month}
size="xs"
label="Month"
data={Array.from({ length: 12 }, (_, index) =>
{
return {
value: (1 + index).toString(),
label:(1 + index).toString(),
disabled: (1 + index) > parseInt(moment(Date.now()).format('MM'))
}
data={Array.from({ length: 12 }, (_, index) => {
return {
value: (1 + index).toString(),
label: (1 + index).toString(),
disabled:
1 + index > parseInt(moment(Date.now()).format('MM')),
}
)}
})}
onChange={(e) => {
setDate({ ...date, month: e! })
}}
@ -168,29 +236,60 @@ const Timekeeping = () => {
size="xs"
ml={'sm'}
label="Year"
data={Array.from({ length: 10 }, (_, index) =>
{
return {
value: (parseInt(moment(Date.now()).format('YYYY')) - 3 + index).toString(),
label:(parseInt(moment(Date.now()).format('YYYY')) - 3 + index).toString(),
disabled: (parseInt(moment(Date.now()).format('YYYY')) - 3 + index) > parseInt(moment(Date.now()).format('YYYY'))
}
data={Array.from({ length: 10 }, (_, index) => {
return {
value: (
parseInt(moment(Date.now()).format('YYYY')) -
3 +
index
).toString(),
label: (
parseInt(moment(Date.now()).format('YYYY')) -
3 +
index
).toString(),
disabled:
parseInt(moment(Date.now()).format('YYYY')) - 3 + index >
parseInt(moment(Date.now()).format('YYYY')),
}
)}
})}
onChange={(e) => {
setDate({ ...date, year: e! })
}}
></Select>
</Box>
<Box display={'flex'} style={{alignItems:'end'}}>
<TextInput type='number' size='xs' label="Working days" w={"20%"} value={workingDays} onChange={(e)=>{
<Box display={'flex'} style={{ alignItems: 'end' }}>
<TextInput
type="number"
size="xs"
label="Working days"
w={'20%'}
value={workingDays}
onChange={(e) => {
setWorkingDays(parseFloat(e.target.value))
}}/>
<Tooltip label="Save working days"><Button size='xs' ml={'sm'} onClick={()=>handleUpdateWorkingDays()}>Save</Button></Tooltip>
</Box>
}}
/>
<Tooltip label="Save working days">
<Button
size="xs"
ml={'sm'}
onClick={() => handleUpdateWorkingDays()}
>
Save
</Button>
</Tooltip>
</Box>
</Box>
<Box w="70%" pl={200} style={{display:'flex', alignItems:'end', justifyContent:"space-evenly"}}>
<Box style={{display:'flex', alignItems:'center'}}>
<Box
w="70%"
pl={200}
style={{
display: 'flex',
alignItems: 'end',
justifyContent: 'space-evenly',
}}
>
<Box style={{ display: 'flex', alignItems: 'center' }}>
<IconCheck
size={20}
style={{
@ -201,9 +300,11 @@ const Timekeeping = () => {
fontWeight: 700,
}}
/>
<Text ml={'sm'} fz='14px'>Work over 7 hours</Text>
<Text ml={'sm'} fz="14px">
Work over 7 hours
</Text>
</Box>
<Box style={{display:'flex', alignItems:'center'}}>
<Box style={{ display: 'flex', alignItems: 'center' }}>
<IconCheck
size={20}
style={{
@ -214,9 +315,11 @@ const Timekeeping = () => {
fontWeight: 700,
}}
/>
<Text ml={'sm'} fz='14px'>Work over 3.5 hours</Text>
<Text ml={'sm'} fz="14px">
Work over 3.5 hours
</Text>
</Box>
<Box style={{display:'flex', alignItems:'center'}}>
<Box style={{ display: 'flex', alignItems: 'center' }}>
<IconExclamationMark
size={20}
style={{
@ -227,9 +330,11 @@ const Timekeeping = () => {
fontWeight: 700,
}}
/>
<Text ml={'sm'} fz='14px'>Checked in</Text>
<Text ml={'sm'} fz="14px">
Checked in
</Text>
</Box>
<Box style={{display:'flex', alignItems:'center'}}>
<Box style={{ display: 'flex', alignItems: 'center' }}>
<IconX
size={20}
style={{
@ -240,7 +345,9 @@ const Timekeeping = () => {
fontWeight: 700,
}}
/>
<Text ml={'sm'} fz='14px'>Off</Text>
<Text ml={'sm'} fz="14px">
Off
</Text>
</Box>
</Box>
</Box>
@ -260,23 +367,49 @@ const Timekeeping = () => {
{daysInMonth.map((d) => {
return (
<Menu width={200} shadow="md">
<Menu.Target>
<Table.Th key={d} ta={'center'} style={{cursor:"pointer"}}>
<Menu.Target>
<Table.Th
key={d}
ta={'center'}
style={{ cursor: 'pointer' }}
>
<span>{d}</span>
</Table.Th>
</Menu.Target>
</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>
<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.Item>
<Text size="sm" onClick={()=>{
open()
setCustomAddData({...customAddData, day: d})
}}>
+ Add custom worklog
</Text>
</Menu.Item>
</Menu.Dropdown>
</Menu>
)
})}
</Table.Tr>
@ -286,7 +419,16 @@ const Timekeeping = () => {
<Table.Th ta={'center'}>Off</Table.Th>
{daysInMonth.map((d) => {
return (
<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%)' : ''}>
<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}`)}
</Table.Th>
)
@ -295,19 +437,35 @@ const Timekeeping = () => {
</Table.Thead>
<Table.Tbody>
{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)
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 (
<Table.Tr key={user.user.id} className={classes.tableTr}>
<Table.Td>{user.user.name}</Table.Td>
<Table.Td ta={'center'}>{totalDays}</Table.Td>
<Table.Td ta={'center'}>{workingDays-totalDays}</Table.Td>
<Table.Td ta={'center'}>{workingDays - totalDays}</Table.Td>
{daysInMonth.map((d) => {
var total =
user.history.find((h) => h.day === d)?.total ?? 0
return (
<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 >= 3.5 ?
<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 >= 3.5 ? (
<Tooltip
multiline
label={
@ -325,7 +483,9 @@ const Timekeeping = () => {
}}
key={v.id}
>
<p>{v.status + ': ' + v.time_string}</p>{' '}
<p>
{v.status + ': ' + v.time_string}
</p>{' '}
{v.image && (
<Image
w={100}
@ -335,13 +495,13 @@ const Timekeeping = () => {
'local',
)
? import.meta.env
.VITE_BACKEND_URL +
'storage/' +
v.image
.VITE_BACKEND_URL +
'storage/' +
v.image
: import.meta.env
.VITE_BACKEND_URL +
'image/storage/' +
v.image
.VITE_BACKEND_URL +
'image/storage/' +
v.image
}
/>
)}
@ -362,7 +522,8 @@ const Timekeeping = () => {
}}
/>
</Tooltip>
: <Tooltip
) : (
<Tooltip
multiline
label={
<div>
@ -379,7 +540,9 @@ const Timekeeping = () => {
}}
key={v.id}
>
<p>{v.status + ': ' + v.time_string}</p>{' '}
<p>
{v.status + ': ' + v.time_string}
</p>{' '}
{v.image && (
<Image
w={100}
@ -389,13 +552,13 @@ const Timekeeping = () => {
'local',
)
? import.meta.env
.VITE_BACKEND_URL +
'storage/' +
v.image
.VITE_BACKEND_URL +
'storage/' +
v.image
: import.meta.env
.VITE_BACKEND_URL +
'image/storage/' +
v.image
.VITE_BACKEND_URL +
'image/storage/' +
v.image
}
/>
)}
@ -416,72 +579,72 @@ const Timekeeping = () => {
}}
/>
</Tooltip>
: total >= 7 ? (
<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
)
) : total >= 7 ? (
<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
: import.meta.env
.VITE_BACKEND_URL +
'image/storage/' +
v.image
}
/>
)}
</Box>
)
})}
</div>
}
>
<IconCheck
size={20}
style={{
backgroundColor: 'green',
color: 'white',
borderRadius: '5px',
padding: '2px',
}}
/>
</Tooltip>
) : (
<IconX
}
/>
)}
</Box>
)
})}
</div>
}
>
<IconCheck
size={20}
style={{
backgroundColor: '#ff4646',
backgroundColor: 'green',
color: 'white',
borderRadius: '5px',
padding: '2px',
}}
/>
)}
</Tooltip>
) : (
<IconX
size={20}
style={{
backgroundColor: '#ff4646',
color: 'white',
borderRadius: '5px',
padding: '2px',
}}
/>
)}
</Table.Td>
)
})}