1027 lines
30 KiB
TypeScript
1027 lines
30 KiB
TypeScript
import {
|
|
deleteNote,
|
|
getListMaster,
|
|
getTheTimesheet,
|
|
updateMultipleUserWorkingTime,
|
|
updateNote,
|
|
updateWorkingDays,
|
|
} from '@/api/Admin'
|
|
import { update, Xdelete } from '@/rtk/helpers/CRUD'
|
|
import { get } from '@/rtk/helpers/apiService'
|
|
import {
|
|
Avatar,
|
|
Box,
|
|
Button,
|
|
Drawer,
|
|
Image,
|
|
Menu,
|
|
MultiSelect,
|
|
Select,
|
|
Table,
|
|
Text,
|
|
Textarea,
|
|
TextInput,
|
|
Tooltip,
|
|
} from '@mantine/core'
|
|
import { useDisclosure } from '@mantine/hooks'
|
|
import { notifications } from '@mantine/notifications'
|
|
import {
|
|
IconCheck,
|
|
IconExclamationMark,
|
|
IconPointFilled,
|
|
IconTrash,
|
|
IconX,
|
|
} from '@tabler/icons-react'
|
|
import moment from 'moment'
|
|
import { useEffect, useState } from 'react'
|
|
|
|
import classes from './Timekeeping.module.css'
|
|
|
|
interface User {
|
|
id: number
|
|
name: string
|
|
email: string
|
|
email_verified_at: string | null
|
|
permission: string
|
|
remember_token: string | null
|
|
avatar: string
|
|
created_at: string | null
|
|
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
|
|
user_id: number
|
|
status: string
|
|
time_string: string
|
|
image: string
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
interface NoteValue {
|
|
id: number
|
|
note: string
|
|
reason: string
|
|
timeType: string
|
|
reasonName: string
|
|
timeTypeName: string
|
|
}
|
|
|
|
interface History {
|
|
values: HistoryValue[]
|
|
total: number
|
|
day: number
|
|
notes: NoteValue[]
|
|
}
|
|
|
|
interface UserData {
|
|
user: User
|
|
history: History[]
|
|
}
|
|
|
|
const Timekeeping = () => {
|
|
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),
|
|
)
|
|
const [customAddData, setCustomAddData] = useState<{
|
|
data: string[]
|
|
type: string
|
|
day: number
|
|
}>({
|
|
data: [],
|
|
type: '',
|
|
day: 0,
|
|
})
|
|
const [customAddNotes, setCustomAddNotes] = useState<{
|
|
user: {
|
|
id: number
|
|
name: string
|
|
}
|
|
type: string
|
|
reason: string
|
|
note: string
|
|
day: number
|
|
notes: NoteValue[]
|
|
}>({
|
|
user: {
|
|
id: 0,
|
|
name: '',
|
|
},
|
|
type: '',
|
|
reason: '',
|
|
note: '',
|
|
day: 0,
|
|
notes: [],
|
|
})
|
|
|
|
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, {
|
|
month: date.month,
|
|
year: date.year,
|
|
})
|
|
if (res.status) {
|
|
setData(
|
|
res.data.filter((u: UserData) => u.user.permission.includes('staff')),
|
|
)
|
|
|
|
if (
|
|
res.data.find(
|
|
(item: UserData) => item.user.id === customAddNotes.user.id,
|
|
)?.user.id
|
|
) {
|
|
setCustomAddNotes({
|
|
...customAddNotes,
|
|
notes: (res.data
|
|
.find((item: UserData) => item.user.id === customAddNotes.user.id)
|
|
?.history?.find(
|
|
(itemHistory: History) =>
|
|
itemHistory.day === customAddNotes.day,
|
|
)?.notes ?? []) as NoteValue[],
|
|
})
|
|
}
|
|
|
|
setDaysInMonth(
|
|
Array.from({ length: getDaysInMonth() }, (_, index) => index + 1),
|
|
)
|
|
|
|
setWorkingDays(res.working_days ?? 30)
|
|
}
|
|
} 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,
|
|
)
|
|
|
|
setDisableBtn(false)
|
|
} catch (error) {
|
|
console.log(error)
|
|
}
|
|
}
|
|
|
|
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(
|
|
updateWorkingDays,
|
|
{
|
|
working_days: workingDays,
|
|
year: date.year,
|
|
month: date.month,
|
|
},
|
|
getTimeSheet,
|
|
)
|
|
} catch (error) {
|
|
console.log(error)
|
|
}
|
|
}
|
|
// const handleUpdateCacheMonth = async () => {
|
|
// try {
|
|
// await update(
|
|
// updateCacheMonth,
|
|
// {
|
|
// year: date.year,
|
|
// month: date.month,
|
|
// },
|
|
// getTimeSheet,
|
|
// )
|
|
// } catch (error) {
|
|
// console.log(error)
|
|
// }
|
|
// }
|
|
|
|
useEffect(() => {
|
|
getTimeSheet()
|
|
}, [date])
|
|
|
|
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>
|
|
)
|
|
}
|
|
|
|
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>
|
|
)
|
|
}
|
|
|
|
const handleDelete = async (id: number) => {
|
|
try {
|
|
await Xdelete(
|
|
deleteNote,
|
|
{ id: id, month: date.month, year: date.year },
|
|
getTimeSheet,
|
|
)
|
|
} catch (error) {
|
|
console.log(error)
|
|
}
|
|
}
|
|
|
|
const handleOpenNote = (user: UserData, d: number) => {
|
|
open2()
|
|
setCustomAddNotes({
|
|
...customAddNotes,
|
|
day: d,
|
|
user: {
|
|
id: user.user.id,
|
|
name: user.user.name,
|
|
},
|
|
notes:
|
|
user.history.find((h) => h.day === d && h.notes && h.notes.length > 0)
|
|
?.notes ?? [],
|
|
})
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div className={classes.title}>
|
|
<h3>
|
|
Timekeeping
|
|
</h3>
|
|
</div>
|
|
<Drawer
|
|
opened={opened1}
|
|
onClose={close1}
|
|
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={() => {
|
|
setDisableBtn(true)
|
|
if (
|
|
customAddData.type === '' ||
|
|
customAddData.data.length === 0 ||
|
|
customAddData.day === 0
|
|
) {
|
|
notifications.show({
|
|
title: 'Error',
|
|
message: 'Input data required',
|
|
color: 'red',
|
|
})
|
|
setDisableBtn(false)
|
|
} else {
|
|
updateMultipleUser(
|
|
customAddData.data.map((u) => parseInt(u)),
|
|
customAddData.day,
|
|
customAddData.type,
|
|
)
|
|
}
|
|
}}
|
|
disabled={disableBtn}
|
|
>
|
|
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>
|
|
{customAddNotes.notes.map((item, index) => {
|
|
return (
|
|
<Box
|
|
key={index}
|
|
display="flex"
|
|
className="text-muted"
|
|
style={{
|
|
marginTop: '10px',
|
|
border: '1px solid #ccc',
|
|
borderRadius: '5px',
|
|
marginBottom: '10px',
|
|
padding: '10px',
|
|
justifyContent: 'space-around',
|
|
}}
|
|
>
|
|
<Box w={'90%'}>
|
|
<TextInput
|
|
readOnly
|
|
variant="unstyled"
|
|
type="text"
|
|
size="xs"
|
|
label={`${item.reasonName} - ${item.timeTypeName}`}
|
|
w={'100%'}
|
|
value={item.note}
|
|
/>
|
|
</Box>
|
|
<Box
|
|
className={classes.optionIcon}
|
|
style={{ alignItems: 'center' }}
|
|
>
|
|
<IconTrash
|
|
className={classes.deleteIcon}
|
|
onClick={async () => {
|
|
await handleDelete(item.id)
|
|
// handleUpdateCacheMonth()
|
|
// close2()
|
|
}}
|
|
width={20}
|
|
height={20}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
)
|
|
})}
|
|
</Drawer>
|
|
<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) => {
|
|
return {
|
|
value: (1 + index).toString(),
|
|
label: (1 + index).toString(),
|
|
disabled:
|
|
1 + index > parseInt(moment(Date.now()).format('MM')),
|
|
}
|
|
})}
|
|
onChange={(e) => {
|
|
setDate({ ...date, month: e! })
|
|
}}
|
|
></Select>
|
|
<Select
|
|
w="50%"
|
|
value={date.year}
|
|
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')),
|
|
}
|
|
})}
|
|
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) => {
|
|
setWorkingDays(parseFloat(e.target.value))
|
|
}}
|
|
/>
|
|
<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' }}>
|
|
<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" key={d}>
|
|
<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.Item>
|
|
<Text
|
|
size="sm"
|
|
onClick={() => {
|
|
open1()
|
|
setCustomAddData({ ...customAddData, day: d })
|
|
}}
|
|
>
|
|
+ Add custom worklog
|
|
</Text>
|
|
</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>
|
|
<Tooltip
|
|
// position={'auto'}
|
|
multiline
|
|
// opened
|
|
// offset={{ mainAxis: 5, crossAxis: 0 }}
|
|
label={showTooltipAllNote(user)}
|
|
>
|
|
<div style={{display:'flex', alignItems:'center'}}><Avatar size={'md'} mr={'md'} src={import.meta.env.VITE_BACKEND_URL.includes('local')
|
|
? import.meta.env.VITE_BACKEND_URL +
|
|
'storage/' +
|
|
user.user.avatar
|
|
: import.meta.env.VITE_BACKEND_URL +
|
|
'image/storage/' +
|
|
user.user.avatar}/>{user.user.name}</div>
|
|
</Tooltip>
|
|
</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
|
|
var notes = user.history.find((h) => h.day === d)?.notes
|
|
return (
|
|
<Table.Td
|
|
pos={'relative'}
|
|
key={d}
|
|
ta={'center'}
|
|
bg={
|
|
getDayName(`${date.year}-${date.month}-${d}`) ===
|
|
'Su' ||
|
|
getDayName(`${date.year}-${date.month}-${d}`) === 'Sa'
|
|
? 'rgb(251 255 196 / 78%)'
|
|
: ''
|
|
}
|
|
>
|
|
<Box
|
|
pos={'absolute'}
|
|
top={-3}
|
|
right={-3}
|
|
display={notes && notes.length > 0 ? 'block' : 'none'}
|
|
>
|
|
<IconPointFilled
|
|
width={15}
|
|
height={15}
|
|
style={{ color: '#2767e1' }}
|
|
/>
|
|
</Box>
|
|
{total / 60 / 60 < 7 &&
|
|
user.history.find(
|
|
(h) => h.day === d && h.values && h.values.length > 0,
|
|
) ? (
|
|
total / 60 / 60 >= 3.5 ? (
|
|
<Tooltip
|
|
multiline
|
|
label={showTooltip(user, total, d)}
|
|
>
|
|
<IconCheck
|
|
size={20}
|
|
style={{
|
|
backgroundColor: 'orange',
|
|
color: 'white',
|
|
borderRadius: '5px',
|
|
padding: '2px',
|
|
fontWeight: 700,
|
|
}}
|
|
onClick={() => {
|
|
handleOpenNote(user, d)
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
) : (
|
|
<Tooltip
|
|
multiline
|
|
label={showTooltip(user, total, d)}
|
|
>
|
|
<IconExclamationMark
|
|
size={20}
|
|
style={{
|
|
backgroundColor: 'orange',
|
|
color: 'white',
|
|
borderRadius: '5px',
|
|
padding: '2px',
|
|
fontWeight: 700,
|
|
}}
|
|
onClick={() => {
|
|
handleOpenNote(user, d)
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
)
|
|
) : total >= 7 ? (
|
|
<>
|
|
<Tooltip
|
|
multiline
|
|
label={showTooltip(user, total, d)}
|
|
>
|
|
<IconCheck
|
|
size={20}
|
|
style={{
|
|
backgroundColor: 'green',
|
|
color: 'white',
|
|
borderRadius: '5px',
|
|
padding: '2px',
|
|
}}
|
|
onClick={() => {
|
|
handleOpenNote(user, d)
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
</>
|
|
) : (
|
|
<>
|
|
{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={() => {
|
|
handleOpenNote(user, d)
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
) : (
|
|
<IconX
|
|
size={20}
|
|
style={{
|
|
backgroundColor: '#ff4646',
|
|
color: 'white',
|
|
borderRadius: '5px',
|
|
padding: '2px',
|
|
}}
|
|
id={`indexBySN${user.user.id}_${d}`}
|
|
onClick={() => {
|
|
handleOpenNote(user, d)
|
|
}}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
</Table.Td>
|
|
)
|
|
})}
|
|
</Table.Tr>
|
|
)
|
|
})}
|
|
</Table.Tbody>
|
|
</Table>
|
|
</Box>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default Timekeeping
|