ManagementSystem/FRONTEND/src/pages/Timekeeping/Timekeeping.tsx

474 lines
18 KiB
TypeScript

import { getTheTimesheet, updateMultipleUserWorkingTime } from '@/api/Admin'
import { get } from '@/rtk/helpers/apiService'
import { Box, Image, Menu, Select, Table, Text, TextInput, Tooltip } from '@mantine/core'
import { IconCheck, IconExclamationMark, IconX } from '@tabler/icons-react'
import { useEffect, useState } from 'react'
import classes from './Timekeeping.module.css'
import { notifications } from '@mantine/notifications'
import moment from 'moment'
import { update } from '@/rtk/helpers/CRUD'
interface User {
id: number
name: string
email: string
email_verified_at: string | null
permission: string
remember_token: string | null
created_at: string | null
updated_at: string | null
}
interface HistoryValue {
id: number
name: string
user_id: number
status: string
time_string: string
image: string
created_at: string
updated_at: string
}
interface History {
values: HistoryValue[]
total: number
day: number
}
interface UserData {
user: User
history: History[]
}
const Timekeeping = () => {
const [daysInMonth, setDaysInMonth] = useState(
Array.from({ length: 31 }, (_, index) => index + 1),
)
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 getTimeSheet = async () => {
try {
const res = await get(getTheTimesheet, {
month: date.month,
year: date.year,
})
if (res.status) {
setData(
res.data.filter((u: UserData) =>
u.user.permission.includes('staff'),
),
)
setDaysInMonth(
Array.from({ length: getDaysInMonth() }, (_, index) => index + 1),
)
localStorage.getItem('workingdays') ? setWorkingDays(parseFloat(localStorage.getItem('workingdays')!)) : setWorkingDays(getDaysInMonth())
}
} catch (error: any) {
console.log(error)
notifications.show({
title: 'Error',
message: error.message ?? error,
color: 'red',
})
}
}
function getDayName(dateString: string) {
// Tạo đối tượng Date từ chuỗi ngày tháng năm
const date = new Date(dateString)
// Lấy ngày trong tuần (0-6)
const dayNumber = date.getDay()
// Mảng chứa các tên ngày bằng tiếng Việt
const daysInVietnamese = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
// Trả về tên ngày bằng tiếng Việt
return daysInVietnamese[dayNumber]
}
function getDaysInMonth() {
// Tạo một đối tượng Date cho ngày đầu tiên của tháng tiếp theo
// Chú ý: tháng trong đối tượng Date là từ 0 (tháng 1) đến 11 (tháng 12)
const days = new Date(parseInt(date.year), parseInt(date.month), 0)
// Trả về ngày, tức là số ngày trong tháng
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(() => {
getTimeSheet()
}, [date])
return (
<div>
<div className={classes.title}>
<h3>
<Text>Admin/</Text>
Timekeeping
</h3>
</div>
<Box display={'flex'}>
<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) =>
(1 + index).toString(),
)}
onChange={(e) => {
setDate({ ...date, month: e! })
}}
></Select>
<Select
w="50%"
value={date.year}
size="xs"
ml={'sm'}
label="Year"
data={Array.from({ length: 20 }, (_, index) =>
(2023 + 1 + index).toString(),
)}
onChange={(e) => {
setDate({ ...date, year: e! })
}}
></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 w="70%" pl={200} style={{display:'flex', alignItems:'end', justifyContent:"space-evenly"}}>
<Box style={{display:'flex', alignItems:'center'}}>
<IconCheck
size={20}
style={{
backgroundColor: 'green',
color: 'white',
borderRadius: '5px',
padding: '2px',
fontWeight: 700,
}}
/>
<Text ml={'sm'} fz='14px'>Work over 7 hours</Text>
</Box>
<Box style={{display:'flex', alignItems:'center'}}>
<IconCheck
size={20}
style={{
backgroundColor: 'orange',
color: 'white',
borderRadius: '5px',
padding: '2px',
fontWeight: 700,
}}
/>
<Text ml={'sm'} fz='14px'>Work over 3.5 hours</Text>
</Box>
<Box style={{display:'flex', alignItems:'center'}}>
<IconExclamationMark
size={20}
style={{
backgroundColor: 'orange',
color: 'white',
borderRadius: '5px',
padding: '2px',
fontWeight: 700,
}}
/>
<Text ml={'sm'} fz='14px'>Checked in</Text>
</Box>
<Box style={{display:'flex', alignItems:'center'}}>
<IconX
size={20}
style={{
backgroundColor: '#ff4646',
color: 'white',
borderRadius: '5px',
padding: '2px',
fontWeight: 700,
}}
/>
<Text ml={'sm'} fz='14px'>Off</Text>
</Box>
</Box>
</Box>
<Box>
<Table
striped
highlightOnHover
withTableBorder
withColumnBorders
mt={'md'}
>
<Table.Thead>
<Table.Tr bg={'#228be66b'}>
<Table.Th>Day</Table.Th>
<Table.Th></Table.Th>
<Table.Th></Table.Th>
{daysInMonth.map((d) => {
return (
<Menu width={200} shadow="md">
<Menu.Target>
<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 bg={'#228be66b'}>
<Table.Th></Table.Th>
<Table.Th ta={'center'}>Total</Table.Th>
<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%)' : ''}>
{getDayName(`${date.year}-${date.month}-${d}`)}
</Table.Th>
)
})}
</Table.Tr>
</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)
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>
{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 ?
<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>
}
>
<IconCheck
size={20}
style={{
backgroundColor: 'orange',
color: 'white',
borderRadius: '5px',
padding: '2px',
fontWeight: 700,
}}
/>
</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>
}
>
<IconExclamationMark
size={20}
style={{
backgroundColor: 'orange',
color: 'white',
borderRadius: '5px',
padding: '2px',
fontWeight: 700,
}}
/>
</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
.VITE_BACKEND_URL +
'storage/' +
v.image
: 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>
) : (
parseInt(moment(Date.now()).format('DD')) >= d &&
<IconX
size={20}
style={{
backgroundColor: '#ff4646',
color: 'white',
borderRadius: '5px',
padding: '2px',
}}
/>
)}
</Table.Td>
)
})}
</Table.Tr>
)
})}
</Table.Tbody>
</Table>
</Box>
</div>
)
}
export default Timekeeping